From 42ad02cf3187a2e38c83b0c406ea2763f9f0736e Mon Sep 17 00:00:00 2001 From: Jacob Su Date: Fri, 5 Dec 2025 20:43:56 +0800 Subject: [PATCH] refine fmp4 trun box's enties set the trun box entry[i].sample_flags by the the keyFrame. set sample_flags, sample_depends_on and sample_is_non_sync_sample bits according to ISO 14496-12 section 8.6.4.1 and 8.8.3.1 --- trunk/src/kernel/srs_kernel_mp4.cpp | 41 +++++- trunk/src/utest/srs_utest_manual_mp4.cpp | 172 +++++++++++++++++++++++ 2 files changed, 207 insertions(+), 6 deletions(-) diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index 21212a4b6..efa73bc06 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -5791,20 +5791,49 @@ srs_error_t SrsMp4SampleManager::write(SrsMp4TrackFragmentBox *traf, uint64_t dt srs_error_t err = srs_success; SrsMp4TrackFragmentRunBox *trun = traf->trun(); + if (trun == NULL) { + trun = new SrsMp4TrackFragmentRunBox(); + traf->set_trun(trun); + } trun->flags_ = SrsMp4TrunFlagsDataOffset | SrsMp4TrunFlagsSampleDuration | SrsMp4TrunFlagsSampleSize | SrsMp4TrunFlagsSampleFlag | SrsMp4TrunFlagsSampleCtsOffset; - SrsMp4Sample *previous = NULL; - + // ISO_IEC_14496-12-base-format-2012.pdf, 8.8.8.1 page 57 + // Because trun->flags_ has not include SrsMp4TrunFlagsFirstSample(0x000004), + // so trun->first_sample_flags_ is not present, and each SrsMp4TrunEntry has sample_flags_. + // ISO_IEC_14496-12-base-format-2012.pdf, 8.8.3.1 page 53 define the sample_flags_'s layout. + // int(32) sample_flags = { + // bit(4) reserved = 0; + // int(2) is_leading; + // int(2) sample_depends_on; + // int(2) sample_is_depends_on; + // int(2) sample_has_redundancy; + // bit(3) sample_padding_value; + // bit(1) sample_is_non_sync_sample; + // int(16) sample_degradation_priority; + // } + // ISO_IEC_14496-12-base-format-2012.pdf, 8.6.4.1, 8.6.4.3 page 41 define the sample_flags_'s values. + // sample_depends_on == 1: this sample does depend on others; + // sample_depends_on == 2: this sample does not depend on others; vector::iterator it; for (it = samples_.begin(); it != samples_.end(); ++it) { SrsMp4Sample *sample = *it; SrsMp4TrunEntry *entry = new SrsMp4TrunEntry(trun); - if (!previous) { - previous = sample; - entry->sample_flags_ = 0x02000000; + if (sample->type_ == SrsFrameTypeVideo) { + if (sample->frame_type_ == SrsVideoAvcFrameTypeKeyFrame) { + // For video keyframe: no dependencies (sample_depends_on = 2), IS a sync sample (sample_is_non_sync_sample = 0) + // ISO 14496-12 section 8.8.3.1: sample_is_non_sync_sample = 0 means this IS a sync sample + // This provides equivalent information to being in a sync sample table (stss box) + entry->sample_flags_ = 0x02000000 | 0x00000000; + } else { + // For video non-keyframe: has dependencies (sample_depends_on = 1), NOT a sync sample (sample_is_non_sync_sample = 1) + // ISO 14496-12 section 8.8.3.1: sample_is_non_sync_sample = 1 means this is NOT a sync sample + entry->sample_flags_ = 0x01000000 | 0x00010000; + } } else { - entry->sample_flags_ = 0x01000000; + // For audio, all samples are independent: no dependencies (sample_depends_on = 2), IS a sync sample (sample_is_non_sync_sample = 0) + // ISO 14496-12 section 8.8.3.1: sample_is_non_sync_sample = 0 means this IS a sync sample + entry->sample_flags_ = 0x02000000 | 0x00000000; } vector::iterator iter = (it + 1); diff --git a/trunk/src/utest/srs_utest_manual_mp4.cpp b/trunk/src/utest/srs_utest_manual_mp4.cpp index 02eb05a79..fbcdf863f 100644 --- a/trunk/src/utest/srs_utest_manual_mp4.cpp +++ b/trunk/src/utest/srs_utest_manual_mp4.cpp @@ -2595,3 +2595,175 @@ VOID TEST(KernelMp4Test, SrsMp4DvrJitter) EXPECT_EQ(0, jitter.get_first_sample_delta(SrsFrameTypeAudio)); } } + +VOID TEST(KernelMp4Test, SrsMp4SampleManagerWrite) +{ + srs_error_t err; + + // Test with video keyframe samples + if (true) { + SrsMp4SampleManager manager; + SrsMp4TrackFragmentBox traf; + + // Create a video keyframe sample + SrsMp4Sample *video_sample = new SrsMp4Sample(); + video_sample->type_ = SrsFrameTypeVideo; + video_sample->dts_ = 1000; + video_sample->pts_ = 1000; + video_sample->tbn_ = 90000; + video_sample->frame_type_ = SrsVideoAvcFrameTypeKeyFrame; + video_sample->nb_data_ = 100; + + // Add sample to manager + manager.append(video_sample); + + // Write samples to traf with appropriate next_dts + err = manager.write(&traf, video_sample->dts_ + 1000); + HELPER_EXPECT_SUCCESS(err); + + // Verify TRUN box was created with correct entries + SrsMp4TrackFragmentRunBox *trun = traf.trun(); + ASSERT_TRUE(trun != NULL); + ASSERT_EQ(1, (int)trun->entries_.size()); + + // Verify TRUN entry for video keyframe + SrsMp4TrunEntry *entry = trun->entries_.at(0); + EXPECT_EQ(1000, entry->sample_duration_); + EXPECT_EQ(100, entry->sample_size_); + EXPECT_EQ(0, entry->sample_composition_time_offset_); + // Keyframe should have sample_depends_on_no and sample_is_non_sync_sample=0 (sync sample) + EXPECT_EQ(0, entry->sample_flags_ & 0x00010000); + } + + // Test with video non-keyframe samples + if (true) { + SrsMp4SampleManager manager; + SrsMp4TrackFragmentBox traf; + + // Create a video non-keyframe sample + SrsMp4Sample *video_sample = new SrsMp4Sample(); + video_sample->type_ = SrsFrameTypeVideo; + video_sample->dts_ = 2000; + video_sample->pts_ = 2000; + video_sample->tbn_ = 90000; + video_sample->frame_type_ = SrsVideoAvcFrameTypeInterFrame; + video_sample->nb_data_ = 50; + + // Add sample to manager + manager.append(video_sample); + + // Write samples to traf with appropriate next_dts + err = manager.write(&traf, video_sample->dts_ + 1000); + HELPER_EXPECT_SUCCESS(err); + + // Verify TRUN box was created with correct entries + SrsMp4TrackFragmentRunBox *trun = traf.trun(); + ASSERT_TRUE(trun != NULL); + ASSERT_EQ(1, (int)trun->entries_.size()); + + // Verify TRUN entry for video non-keyframe + SrsMp4TrunEntry *entry = trun->entries_.at(0); + EXPECT_EQ(1000, entry->sample_duration_); + EXPECT_EQ(50, entry->sample_size_); + EXPECT_EQ(0, entry->sample_composition_time_offset_); + // Non-keyframe should have sample_depends_on_yes and sample_is_non_sync_sample=1 (non-sync sample) + EXPECT_EQ((uint32_t)(0x00010000 | 0x00000000), entry->sample_flags_ & 0x00010000); + } + + // Test with audio samples + if (true) { + SrsMp4SampleManager manager; + SrsMp4TrackFragmentBox traf; + + // Create an audio sample + SrsMp4Sample *audio_sample = new SrsMp4Sample(); + audio_sample->type_ = SrsFrameTypeAudio; + audio_sample->dts_ = 3000; + audio_sample->pts_ = 3000; + audio_sample->tbn_ = 44100; + audio_sample->nb_data_ = 200; + + // Add sample to manager + manager.append(audio_sample); + + // Write samples to traf with appropriate next_dts + err = manager.write(&traf, audio_sample->dts_ + 1000); + HELPER_EXPECT_SUCCESS(err); + + // Verify TRUN box was created with correct entries + SrsMp4TrackFragmentRunBox *trun = traf.trun(); + ASSERT_TRUE(trun != NULL); + ASSERT_EQ(1, (int)trun->entries_.size()); + + // Verify TRUN entry for audio sample + SrsMp4TrunEntry *entry = trun->entries_.at(0); + EXPECT_EQ(1000, entry->sample_duration_); + EXPECT_EQ(200, entry->sample_size_); + EXPECT_EQ(0, entry->sample_composition_time_offset_); + // Audio should have sample_depends_on_no and sample_is_non_sync_sample=0 (sync sample) + EXPECT_EQ(0, entry->sample_flags_ & 0x00010000); + } + + // Test with multiple samples (video keyframe, video non-keyframe, audio) + if (true) { + SrsMp4SampleManager manager; + SrsMp4TrackFragmentBox traf; + + // Create video keyframe sample + SrsMp4Sample *keyframe = new SrsMp4Sample(); + keyframe->type_ = SrsFrameTypeVideo; + keyframe->dts_ = 1000; + keyframe->pts_ = 1000; + keyframe->tbn_ = 90000; + keyframe->frame_type_ = SrsVideoAvcFrameTypeKeyFrame; + keyframe->nb_data_ = 100; + + // Create video non-keyframe sample + SrsMp4Sample *deltaframe = new SrsMp4Sample(); + deltaframe->type_ = SrsFrameTypeVideo; + deltaframe->dts_ = 2000; + deltaframe->pts_ = 2000; + deltaframe->tbn_ = 90000; + deltaframe->frame_type_ = SrsVideoAvcFrameTypeInterFrame; + deltaframe->nb_data_ = 50; + + // Create audio sample + SrsMp4Sample *audio = new SrsMp4Sample(); + audio->type_ = SrsFrameTypeAudio; + audio->dts_ = 3000; + audio->pts_ = 3000; + audio->tbn_ = 44100; + audio->nb_data_ = 200; + + // Add samples to manager + manager.append(keyframe); + manager.append(deltaframe); + manager.append(audio); + + // Write samples to traf with appropriate next_dts + uint64_t next_dts = audio->dts_ + 1000; + err = manager.write(&traf, next_dts); + HELPER_EXPECT_SUCCESS(err); + + // Verify TRUN box was created with correct entries + SrsMp4TrackFragmentRunBox *trun = traf.trun(); + ASSERT_TRUE(trun != NULL); + ASSERT_EQ(3, (int)trun->entries_.size()); + + // Verify each entry + SrsMp4TrunEntry *entry1 = trun->entries_.at(0); + EXPECT_EQ(1000, entry1->sample_duration_); + EXPECT_EQ(100, entry1->sample_size_); + EXPECT_EQ(0, entry1->sample_flags_ & 0x00010000); // Keyframe (sample_is_non_sync_sample=0) + + SrsMp4TrunEntry *entry2 = trun->entries_.at(1); + EXPECT_EQ(1000, entry2->sample_duration_); + EXPECT_EQ(50, entry2->sample_size_); + EXPECT_EQ((uint32_t)(0x00010000 | 0x00000000), entry2->sample_flags_ & 0x00010000); // Non-keyframe (sample_is_non_sync_sample=1) + + SrsMp4TrunEntry *entry3 = trun->entries_.at(2); + EXPECT_EQ(1000, entry3->sample_duration_); + EXPECT_EQ(200, entry3->sample_size_); + EXPECT_EQ(0, entry3->sample_flags_ & 0x00010000); // Audio (sample_is_non_sync_sample=0) + } +}