DVR: Fix HEVC mp4 recording error. v6.0.185 (#4605) (#4605)

When recording HEVC streams to MP4, DVR fails with error "doesn't support
hvcC change" (ERROR_MP4_HVCC_CHANGE).

The root cause is in video_avc_demux(): the SrsVideoFrame object is reused
across frames, and its initialize() method does not reset avc_packet_type.
When a sequence header is processed, avc_packet_type is set to 0 
(SrsVideoAvcFrameTraitSequenceHeader). When the next video info frame
arrives (which only appears in HEVC streams), the function returns early
without assigning video->avc_packet_type, so it retains the value 0 from
the previous sequence header frame.

When DVR processes this video info frame, it checks avc_packet_type and
incorrectly identifies it as a sequence header. Since the real HEVC sequence
header was already recorded, DVR returns the "hvcC change" error.

The fix assigns video->avc_packet_type = packet_type before returning
early for VideoInfoFrame, ensuring DVR correctly identifies the frame
type.

Fix #4603 for 6.0release

---------

Co-authored-by: OSSRS-AI <winlinam@gmail.com>
This commit is contained in:
Jacob Su 2025-12-07 07:32:58 +08:00 committed by GitHub
parent 9f8670b2a8
commit 1316ceb7e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 87 additions and 1 deletions

View File

@ -7,6 +7,7 @@ The changelog for SRS.
<a name="v6-changes"></a>
## SRS 6.0 Changelog
* v6.0, 2025-12-06, Merge [#4605](https://github.com/ossrs/srs/pull/4605): DVR: Fix HEVC mp4 recording error. v6.0.185 (#4605)
* v6.0, 2025-12-03, Merge [#4588](https://github.com/ossrs/srs/pull/4588): RTMP: Ignore FMLE start packet after flash publish. v6.0.184 (#4588)
* v6.0, 2025-10-21, Merge [#4535](https://github.com/ossrs/srs/issues/4535): Bridge: Fix heap-use-after-free in SrsCompositeBridge iterator. v6.0.183 (#4535)
* v6.0, 2025-10-17, Merge [#4534](https://github.com/ossrs/srs/pull/4534): HLS: Fix a iterator bug in hls_ctx cleanup function. v6.0.182 (#4534)

View File

@ -9,6 +9,6 @@
#define VERSION_MAJOR 6
#define VERSION_MINOR 0
#define VERSION_REVISION 184
#define VERSION_REVISION 185
#endif

View File

@ -970,6 +970,12 @@ srs_error_t SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp)
// ignore info frame without error,
// @see https://github.com/ossrs/srs/issues/288#issuecomment-69863909
if (video->frame_type == SrsVideoAvcFrameTypeVideoInfoFrame) {
// For non-ext header Video Info Frame, try to read packet type from stream if available
if (!is_ext_header && stream->left() > 0) {
packet_type = (SrsVideoAvcFrameTrait)stream->read_1bytes();
}
video->avc_packet_type = packet_type;
srs_warn("avc ignore the info frame");
return err;
}

View File

@ -529,6 +529,85 @@ VOID TEST(KernelCodecTest, VideoFormatSepcialAsan_DJI_M30)
HELPER_EXPECT_SUCCESS(f.on_video(0, data, sizeof(data)));
}
VOID TEST(KernelCodecTest, VideoFrameVideoInfoFrameHandling)
{
srs_error_t err;
// Test both AVC and HEVC video codecs
SrsFormat format;
HELPER_EXPECT_SUCCESS(format.initialize());
// Test 1: AVC Video Info Frame handling with additional NALU data
{
// AVC Video Info Frame
// Frame type 5 (Video Info Frame) | Codec ID 7 (AVC)
uint8_t avc_video_info_frame[] = {
0x57, // Frame type 5 (Video Info Frame) | Codec ID 7 (AVC)
0x01, // AVC packet type 1 (NALU)
0x00, 0x00, 0x00, // Composition time 0
// NALU data (mock IDR frame)
0x00, 0x00, 0x00, 0x01, // NAL unit start code
0x65, 0x88, 0x84, 0x00 // IDR NALU (mock data)
};
// Call on_video with AVC video info frame - should not return error
err = format.on_video(2000, (char *)avc_video_info_frame, sizeof(avc_video_info_frame));
EXPECT_TRUE(err == NULL) << "Failed to handle AVC Video Info Frame with NALU data";
// Verify avc_packet_type is correctly set to SrsVideoAvcFrameTraitNALU (1)
EXPECT_TRUE(format.video != NULL) << "Video packet should be initialized after on_video call";
EXPECT_EQ(format.video->avc_packet_type, SrsVideoAvcFrameTraitNALU)
<< "Expected avc_packet_type to be SrsVideoAvcFrameTraitNALU (1), got "
<< format.video->avc_packet_type;
srs_freep(err);
}
// Test 2: HEVC Video Info Frame handling with coded frames
{
// HEVC Video Info Frame (enhanced RTMP format)
uint8_t hevc_video_info_frame[] = {
0xD1, // Frame type 5 (Video Info Frame) | Extended header flag (0x80) | Packet type 1
0x68, 0x76, 0x63, 0x31, // 'hvc1' FourCC (HEVC)
0x00, 0x00, 0x00 // Composition time 0 (for coded frames packet type)
};
// Call on_video with HEVC video info frame - should not return error
err = format.on_video(4000, (char *)hevc_video_info_frame, sizeof(hevc_video_info_frame));
EXPECT_TRUE(err == NULL) << "Failed to handle HEVC Video Info Frame with coded frames";
// Verify avc_packet_type is correctly set to SrsVideoHEVCFrameTraitPacketTypeCodedFrames (1)
EXPECT_TRUE(format.video != NULL) << "Video packet should be initialized after on_video call";
EXPECT_EQ(format.video->avc_packet_type, SrsVideoHEVCFrameTraitPacketTypeCodedFrames)
<< "Expected avc_packet_type to be SrsVideoHEVCFrameTraitPacketTypeCodedFrames (1), got "
<< format.video->avc_packet_type;
srs_freep(err);
}
// Test 3: HEVC Video Info Frame handling with coded frames X (optimized zero composition time)
{
// HEVC Video Info Frame with coded frames X (optimized)
uint8_t hevc_video_info_frame[] = {
0xD3, // Frame type 5 (Video Info Frame) | Extended header flag (0x80) | Packet type 3 (coded frames X)
0x68, 0x76, 0x63, 0x31 // 'hvc1' FourCC (HEVC)
// No composition time field for coded frames X (implied to be zero)
};
// Call on_video with HEVC video info frame - should not return error
err = format.on_video(7000, (char *)hevc_video_info_frame, sizeof(hevc_video_info_frame));
EXPECT_TRUE(err == NULL) << "Failed to handle HEVC Video Info Frame with coded frames X";
// Verify avc_packet_type is correctly set to SrsVideoHEVCFrameTraitPacketTypeCodedFramesX (3)
EXPECT_TRUE(format.video != NULL) << "Video packet should be initialized after on_video call";
EXPECT_EQ(format.video->avc_packet_type, SrsVideoHEVCFrameTraitPacketTypeCodedFramesX)
<< "Expected avc_packet_type to be SrsVideoHEVCFrameTraitPacketTypeCodedFramesX (3), got "
<< format.video->avc_packet_type;
srs_freep(err);
}
}
VOID TEST(KernelCodecTest, VideoFormatRbspSimple)
{
// |---------------------|----------------------------|