From 0407360dcf522dcd96c9d1fd9fd8c1808a016177 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Date: Mon, 14 Mar 2016 16:33:46 -0700 Subject: [PATCH] mkvmuxer: Write last block in each Cluster with Duration This helps browsers implementing Media Source Extensions (MSE) to know the exact duration of a Cluster without relying on the next Cluster. Change-Id: Idd0422e432430c5702a4864740f89fc6d3c85189 --- mkvmuxer.cpp | 172 +++++++++++++++--- mkvmuxer.hpp | 61 ++++++- sample_muxer.cpp | 9 + testing/muxer_tests.cc | 151 +++++++++++++-- .../testdata/accurate_cluster_duration.webm | Bin 0 -> 376 bytes .../accurate_cluster_duration_last_frame.webm | Bin 0 -> 384 bytes .../accurate_cluster_duration_two_tracks.webm | Bin 0 -> 533 bytes 7 files changed, 345 insertions(+), 48 deletions(-) create mode 100644 testing/testdata/accurate_cluster_duration.webm create mode 100644 testing/testdata/accurate_cluster_duration_last_frame.webm create mode 100644 testing/testdata/accurate_cluster_duration_two_tracks.webm diff --git a/mkvmuxer.cpp b/mkvmuxer.cpp index 7bff98c..8d212fa 100644 --- a/mkvmuxer.cpp +++ b/mkvmuxer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "mkvmuxerutil.hpp" #include "mkvparser/mkvparser.hpp" @@ -148,6 +149,7 @@ Frame::Frame() additional_(NULL), additional_length_(0), duration_(0), + duration_set_(false), frame_(NULL), is_key_(false), length_(0), @@ -180,6 +182,7 @@ bool Frame::CopyFrom(const Frame& frame) { return false; } duration_ = frame.duration(); + duration_set_ = frame.duration_set(); is_key_ = frame.is_key(); track_number_ = frame.track_number(); timestamp_ = frame.timestamp(); @@ -240,6 +243,11 @@ bool Frame::CanBeSimpleBlock() const { return additional_ == NULL && discard_padding_ == 0 && duration_ == 0; } +void Frame::set_duration(uint64 duration) { + duration_ = duration; + duration_set_ = true; +} + void Frame::set_reference_block_timestamp(int64 reference_block_timestamp) { reference_block_timestamp_ = reference_block_timestamp; reference_block_timestamp_set_ = true; @@ -2071,7 +2079,8 @@ bool Tags::ExpandTagsArray() { // // Cluster class -Cluster::Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale) +Cluster::Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale, + bool write_last_frame_with_duration) : blocks_added_(0), finalized_(false), header_written_(false), @@ -2080,6 +2089,7 @@ Cluster::Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale) size_position_(-1), timecode_(timecode), timecode_scale_(timecode_scale), + write_last_frame_with_duration_(write_last_frame_with_duration), writer_(NULL) {} Cluster::~Cluster() {} @@ -2092,7 +2102,9 @@ bool Cluster::Init(IMkvWriter* ptr_writer) { return true; } -bool Cluster::AddFrame(const Frame* const frame) { return DoWriteFrame(frame); } +bool Cluster::AddFrame(const Frame* const frame) { + return QueueOrWriteFrame(frame); +} bool Cluster::AddFrame(const uint8* data, uint64 length, uint64 track_number, uint64 abs_timecode, bool is_key) { @@ -2102,7 +2114,7 @@ bool Cluster::AddFrame(const uint8* data, uint64 length, uint64 track_number, frame.set_track_number(track_number); frame.set_timestamp(abs_timecode); frame.set_is_key(is_key); - return DoWriteFrame(&frame); + return QueueOrWriteFrame(&frame); } bool Cluster::AddFrameWithAdditional(const uint8* data, uint64 length, @@ -2121,7 +2133,7 @@ bool Cluster::AddFrameWithAdditional(const uint8* data, uint64 length, frame.set_track_number(track_number); frame.set_timestamp(abs_timecode); frame.set_is_key(is_key); - return DoWriteFrame(&frame); + return QueueOrWriteFrame(&frame); } bool Cluster::AddFrameWithDiscardPadding(const uint8* data, uint64 length, @@ -2135,7 +2147,7 @@ bool Cluster::AddFrameWithDiscardPadding(const uint8* data, uint64 length, frame.set_track_number(track_number); frame.set_timestamp(abs_timecode); frame.set_is_key(is_key); - return DoWriteFrame(&frame); + return QueueOrWriteFrame(&frame); } bool Cluster::AddMetadata(const uint8* data, uint64 length, uint64 track_number, @@ -2147,13 +2159,58 @@ bool Cluster::AddMetadata(const uint8* data, uint64 length, uint64 track_number, frame.set_timestamp(abs_timecode); frame.set_duration(duration_timecode); frame.set_is_key(true); // All metadata blocks are keyframes. - return DoWriteFrame(&frame); + return QueueOrWriteFrame(&frame); } void Cluster::AddPayloadSize(uint64 size) { payload_size_ += size; } bool Cluster::Finalize() { - if (!writer_ || finalized_ || size_position_ == -1) + return !write_last_frame_with_duration_ && Finalize(false, 0); +} + +bool Cluster::Finalize(bool set_last_frame_duration, uint64 duration) { + if (!writer_ || finalized_) + return false; + + if (write_last_frame_with_duration_) { + // Write out held back Frames. This essentially performs a k-way merge + // across all tracks in the increasing order of timestamps. + while (!stored_frames_.empty()) { + Frame* frame = stored_frames_.begin()->second.front(); + + // Get the next frame to write (frame with least timestamp across all + // tracks). + for (FrameMapIterator frames_iterator = ++stored_frames_.begin(); + frames_iterator != stored_frames_.end(); ++frames_iterator) { + if (frames_iterator->second.front()->timestamp() < frame->timestamp()) { + frame = frames_iterator->second.front(); + } + } + + // Set the duration if it's the last frame for the track. + if (set_last_frame_duration && + stored_frames_[frame->track_number()].size() == 1 && + !frame->duration_set()) { + frame->set_duration(duration - frame->timestamp()); + if (!frame->is_key() && !frame->reference_block_timestamp_set()) { + frame->set_reference_block_timestamp( + last_block_timestamp_[frame->track_number()]); + } + } + + // Write the frame and remove it from |stored_frames_|. + const bool wrote_frame = DoWriteFrame(frame); + stored_frames_[frame->track_number()].pop_front(); + if (stored_frames_[frame->track_number()].empty()) { + stored_frames_.erase(frame->track_number()); + } + delete frame; + if (!wrote_frame) + return false; + } + } + + if (size_position_ == -1) return false; if (writer_->Seekable()) { @@ -2220,6 +2277,62 @@ bool Cluster::DoWriteFrame(const Frame* const frame) { return false; PostWriteBlock(element_size); + last_block_timestamp_[frame->track_number()] = frame->timestamp(); + return true; +} + +bool Cluster::QueueOrWriteFrame(const Frame* const frame) { + if (!frame || !frame->IsValid()) + return false; + + // If |write_last_frame_with_duration_| is not set, then write the frame right + // away. + if (!write_last_frame_with_duration_) { + return DoWriteFrame(frame); + } + + // Queue the current frame. + uint64 track_number = frame->track_number(); + Frame* const frame_to_store = new Frame(); + frame_to_store->CopyFrom(*frame); + stored_frames_[track_number].push_back(frame_to_store); + + // Iterate through all queued frames in the current track except the last one + // and write it if it is okay to do so (i.e.) no other track has an held back + // frame with timestamp <= the timestamp of the frame in question. + std::vector::iterator> frames_to_erase; + for (std::list::iterator + current_track_iterator = stored_frames_[track_number].begin(), + end = --stored_frames_[track_number].end(); + current_track_iterator != end; ++current_track_iterator) { + const Frame* const frame_to_write = *current_track_iterator; + bool okay_to_write = true; + for (FrameMapIterator track_iterator = stored_frames_.begin(); + track_iterator != stored_frames_.end(); ++track_iterator) { + if (track_iterator->first == track_number) { + continue; + } + if (track_iterator->second.front()->timestamp() < + frame_to_write->timestamp()) { + okay_to_write = false; + break; + } + } + if (okay_to_write) { + const bool wrote_frame = DoWriteFrame(frame_to_write); + delete frame_to_write; + if (!wrote_frame) + return false; + frames_to_erase.push_back(current_track_iterator); + } else { + break; + } + } + for (std::vector::iterator>::iterator iterator = + frames_to_erase.begin(); + iterator != frames_to_erase.end(); ++iterator) { + stored_frames_[track_number].erase(*iterator); + } return true; } @@ -2716,15 +2829,17 @@ bool Segment::Finalize() { if (WriteFramesAll() < 0) return false; + if (cluster_list_size_ > 0) { + // Update last cluster's size + Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; + + // For the last frame of the last Cluster, we don't write it as a BlockGroup + // with Duration unless the frame itself has duration set explicitly. + if (!old_cluster || !old_cluster->Finalize(false, 0)) + return false; + } + if (mode_ == kFile) { - if (cluster_list_size_ > 0) { - // Update last cluster's size - Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; - - if (!old_cluster || !old_cluster->Finalize()) - return false; - } - if (chunking_ && chunk_writer_cluster_) { chunk_writer_cluster_->Close(); chunk_count_++; @@ -3023,6 +3138,10 @@ bool Segment::AddGenericFrame(const Frame* frame) { void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; } +void Segment::AccurateClusterDuration(bool accurate_cluster_duration) { + accurate_cluster_duration_ = accurate_cluster_duration; +} + bool Segment::SetChunking(bool chunking, const char* filename) { if (chunk_count_ > 0) return false; @@ -3288,19 +3407,17 @@ bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) { if (!WriteFramesLessThan(frame_timestamp_ns)) return false; - if (mode_ == kFile) { - if (cluster_list_size_ > 0) { - // Update old cluster's size - Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; + if (cluster_list_size_ > 0) { + // Update old cluster's size + Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; - if (!old_cluster || !old_cluster->Finalize()) - return false; - } - - if (output_cues_) - new_cuepoint_ = true; + if (!old_cluster || !old_cluster->Finalize(true, frame_timestamp_ns)) + return false; } + if (output_cues_) + new_cuepoint_ = true; + if (chunking_ && cluster_list_size_ > 0) { chunk_writer_cluster_->Close(); chunk_count_++; @@ -3327,8 +3444,9 @@ bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) { Cluster*& cluster = cluster_list_[cluster_list_size_]; const int64 offset = MaxOffset(); - cluster = new (std::nothrow) Cluster(cluster_timecode, // NOLINT - offset, segment_info_.timecode_scale()); + cluster = new (std::nothrow) + Cluster(cluster_timecode, offset, segment_info_.timecode_scale(), + accurate_cluster_duration_); if (!cluster) return false; diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp index b449d00..d3198ef 100644 --- a/mkvmuxer.hpp +++ b/mkvmuxer.hpp @@ -10,6 +10,8 @@ #define MKVMUXER_HPP #include +#include +#include #include "mkvmuxertypes.hpp" #include "webmids.hpp" @@ -101,8 +103,9 @@ class Frame { uint64 add_id() const { return add_id_; } const uint8* additional() const { return additional_; } uint64 additional_length() const { return additional_length_; } - void set_duration(uint64 duration) { duration_ = duration; } + void set_duration(uint64 duration); uint64 duration() const { return duration_; } + bool duration_set() const { return duration_set_; } const uint8* frame() const { return frame_; } void set_is_key(bool key) { is_key_ = key; } bool is_key() const { return is_key_; } @@ -134,6 +137,11 @@ class Frame { // Duration of the frame in nanoseconds. uint64 duration_; + // Flag indicating that |duration_| has been set. Setting duration causes the + // frame to be written out as a Block with BlockDuration instead of as a + // SimpleBlock. + bool duration_set_; + // Pointer to the data. Owned by this class. uint8* frame_; @@ -978,7 +986,8 @@ class Cluster { // |timecode| is the absolute timecode of the cluster. |cues_pos| is the // position for the cluster within the segment that should be written in // the cues element. |timecode_scale| is the timecode scale of the segment. - Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale); + Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale, + bool write_last_frame_with_duration = false); ~Cluster(); bool Init(IMkvWriter* ptr_writer); @@ -1055,9 +1064,22 @@ class Cluster { void AddPayloadSize(uint64 size); // Closes the cluster so no more data can be written to it. Will update the - // cluster's size if |writer_| is seekable. Returns true on success. + // cluster's size if |writer_| is seekable. Returns true on success. This + // variant of Finalize() fails when |write_last_frame_with_duration_| is set + // to true. bool Finalize(); + // Closes the cluster so no more data can be written to it. Will update the + // cluster's size if |writer_| is seekable. Returns true on success. + // Inputs: + // set_last_frame_duration: Boolean indicating whether or not the duration + // of the last frame should be set. If set to + // false, the |duration| value is ignored and + // |write_last_frame_with_duration_| will not be + // honored. + // duration: Duration of the Cluster in timecode scale. + bool Finalize(bool set_last_frame_duration, uint64 duration); + // Returns the size in bytes for the entire Cluster element. uint64 Size() const; @@ -1071,8 +1093,17 @@ class Cluster { int64 position_for_cues() const { return position_for_cues_; } uint64 timecode() const { return timecode_; } uint64 timecode_scale() const { return timecode_scale_; } + void set_write_last_frame_with_duration(bool write_last_frame_with_duration) { + write_last_frame_with_duration_ = write_last_frame_with_duration; + } + bool write_last_frame_with_duration() const { + return write_last_frame_with_duration_; + } private: + // Iterator type for the |stored_frames_| map. + typedef std::map >::iterator FrameMapIterator; + // Utility method that confirms that blocks can still be added, and that the // cluster header has been written. Used by |DoWriteFrame*|. Returns true // when successful. @@ -1085,6 +1116,10 @@ class Cluster { // Does some verification and calls WriteFrame. bool DoWriteFrame(const Frame* const frame); + // Either holds back the given frame, or writes it out depending on whether or + // not |write_last_frame_with_duration_| is set. + bool QueueOrWriteFrame(const Frame* const frame); + // Outputs the Cluster header to |writer_|. Returns true on success. bool WriteClusterHeader(); @@ -1112,6 +1147,19 @@ class Cluster { // The timecode scale of the Segment containing the cluster. const uint64 timecode_scale_; + // Flag indicating whether the last frame of the cluster should be written as + // a Block with Duration. If set to true, then it will result in holding back + // of frames and the parameterized version of Finalize() must be called to + // finish writing the Cluster. + bool write_last_frame_with_duration_; + + // Map used to hold back frames, if required. Track number is the key. + std::map > stored_frames_; + + // Map from track number to the timestamp of the last block written for that + // track. + std::map last_block_timestamp_; + // Pointer to the writer object. Not owned by this class. IMkvWriter* writer_; @@ -1387,6 +1435,9 @@ class Segment { // Toggles whether to output a cues element. void OutputCues(bool output_cues); + // Toggles whether to write the last frame in each Cluster with Duration. + void AccurateClusterDuration(bool accurate_cluster_duration); + // Sets if the muxer will output files in chunks or not. |chunking| is a // flag telling whether or not to turn on chunking. |filename| is the base // filename for the chunk files. The header chunk file will be named @@ -1587,6 +1638,10 @@ class Segment { // Flag whether or not the muxer should output a Cues element. bool output_cues_; + // Flag whether or not the last frame in each Cluster will have a Duration + // element in it. + bool accurate_cluster_duration_; + // The size of the EBML header, used to validate the header if // WriteEbmlHeader() is called more than once. int32 ebml_header_size_; diff --git a/sample_muxer.cpp b/sample_muxer.cpp index 90a1156..37e04d0 100644 --- a/sample_muxer.cpp +++ b/sample_muxer.cpp @@ -54,6 +54,8 @@ void Usage() { printf(" -video_track_number >0 Changes the video track number\n"); printf(" -chunking Chunk output\n"); printf(" -copy_tags >0 Copies the tags\n"); + printf(" -accurate_cluster_duration "); + printf(">0 Writes the last frame in each cluster with Duration\n"); printf("\n"); printf("Video options:\n"); printf(" -display_width Display width in pixels\n"); @@ -164,6 +166,7 @@ int main(int argc, char* argv[]) { bool chunking = false; bool copy_tags = false; const char* chunk_name = NULL; + bool accurate_cluster_duration = false; bool output_cues_block_number = true; @@ -219,6 +222,10 @@ int main(int argc, char* argv[]) { chunk_name = argv[++i]; } else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) { copy_tags = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-accurate_cluster_duration", argv[i]) && + i < argc_check) { + accurate_cluster_duration = + strtol(argv[++i], &end, 10) == 0 ? false : true; } else if (!strcmp("-display_width", argv[i]) && i < argc_check) { display_width = strtol(argv[++i], &end, 10); } else if (!strcmp("-display_height", argv[i]) && i < argc_check) { @@ -297,6 +304,8 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } + muxer_segment.AccurateClusterDuration(accurate_cluster_duration); + if (live_mode) muxer_segment.set_mode(libwebm::mkvmuxer::Segment::kLive); else diff --git a/testing/muxer_tests.cc b/testing/muxer_tests.cc index 2dff053..2e8143e 100644 --- a/testing/muxer_tests.cc +++ b/testing/muxer_tests.cc @@ -96,13 +96,14 @@ class MuxerTest : public testing::Test { is_writer_open_ = false; } - bool SegmentInit(bool output_cues) { + bool SegmentInit(bool output_cues, bool accurate_cluster_duration) { if (!segment_.Init(writer_.get())) return false; SegmentInfo* const info = segment_.GetSegmentInfo(); info->set_writing_app(kAppString); info->set_muxing_app(kAppString); segment_.OutputCues(output_cues); + segment_.AccurateClusterDuration(accurate_cluster_duration); return true; } @@ -116,7 +117,7 @@ class MuxerTest : public testing::Test { }; TEST_F(MuxerTest, SegmentInfo) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); SegmentInfo* const info = segment_.GetSegmentInfo(); info->set_timecode_scale(kTimeCodeScale); info->set_duration(2.345); @@ -133,7 +134,7 @@ TEST_F(MuxerTest, SegmentInfo) { } TEST_F(MuxerTest, AddTracks) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); // Add a Video Track AddVideoTrack(); @@ -177,7 +178,7 @@ TEST_F(MuxerTest, AddTracks) { } TEST_F(MuxerTest, AddChapters) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); // Add a Chapter @@ -194,7 +195,7 @@ TEST_F(MuxerTest, AddChapters) { } TEST_F(MuxerTest, SimpleBlock) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); // Valid Frame @@ -222,7 +223,7 @@ TEST_F(MuxerTest, SimpleBlock) { } TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); Frame frame; @@ -254,7 +255,7 @@ TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) { } TEST_F(MuxerTest, MetadataBlock) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); Track* const track = segment_.AddTrack(kMetadataTrackNumber); track->set_uid(kMetadataTrackNumber); track->set_type(kMetadataTrackType); @@ -285,7 +286,7 @@ TEST_F(MuxerTest, MetadataBlock) { } TEST_F(MuxerTest, TrackType) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); Track* const track = segment_.AddTrack(kMetadataTrackNumber); track->set_uid(kMetadataTrackNumber); track->set_codec_id(kMetadataCodecId); @@ -305,7 +306,7 @@ TEST_F(MuxerTest, TrackType) { } TEST_F(MuxerTest, BlockWithAdditional) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); // Valid Frame @@ -346,7 +347,7 @@ TEST_F(MuxerTest, BlockWithAdditional) { } TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); Frame frame; @@ -381,7 +382,7 @@ TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) { } TEST_F(MuxerTest, SegmentDurationComputation) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); Frame frame; @@ -409,7 +410,7 @@ TEST_F(MuxerTest, SegmentDurationComputation) { } TEST_F(MuxerTest, ForceNewCluster) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, @@ -431,7 +432,7 @@ TEST_F(MuxerTest, ForceNewCluster) { } TEST_F(MuxerTest, OutputCues) { - EXPECT_TRUE(SegmentInit(true)); + EXPECT_TRUE(SegmentInit(true, false)); AddVideoTrack(); EXPECT_TRUE( @@ -451,7 +452,7 @@ TEST_F(MuxerTest, OutputCues) { } TEST_F(MuxerTest, CuesBeforeClusters) { - EXPECT_TRUE(SegmentInit(true)); + EXPECT_TRUE(SegmentInit(true, false)); AddVideoTrack(); EXPECT_TRUE( @@ -484,7 +485,7 @@ TEST_F(MuxerTest, CuesBeforeClusters) { } TEST_F(MuxerTest, MaxClusterSize) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); const uint64 kMaxClusterSize = 20; segment_.set_max_cluster_size(kMaxClusterSize); @@ -509,7 +510,7 @@ TEST_F(MuxerTest, MaxClusterSize) { } TEST_F(MuxerTest, MaxClusterDuration) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddVideoTrack(); const uint64 kMaxClusterDuration = 4000000; segment_.set_max_cluster_duration(kMaxClusterDuration); @@ -537,7 +538,7 @@ TEST_F(MuxerTest, MaxClusterDuration) { TEST_F(MuxerTest, SetCuesTrackNumber) { const uint64 kTrackNumber = 10; - EXPECT_TRUE(SegmentInit(true)); + EXPECT_TRUE(SegmentInit(true, false)); const uint64 vid_track = segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber); EXPECT_EQ(kTrackNumber, vid_track); @@ -566,7 +567,7 @@ TEST_F(MuxerTest, SetCuesTrackNumber) { } TEST_F(MuxerTest, BlockWithDiscardPadding) { - EXPECT_TRUE(SegmentInit(false)); + EXPECT_TRUE(SegmentInit(false, false)); AddAudioTrack(); int timecode = 1000; @@ -588,6 +589,120 @@ TEST_F(MuxerTest, BlockWithDiscardPadding) { EXPECT_TRUE(CompareFiles(GetTestFilePath("discard_padding.webm"), filename_)); } +TEST_F(MuxerTest, AccurateClusterDuration) { + EXPECT_TRUE(SegmentInit(false, true)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(2000000); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(6000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("accurate_cluster_duration.webm"), + filename_)); +} + +// Tests AccurateClusterDuration flag with the duration of the very last block +// of the file set explicitly. +TEST_F(MuxerTest, AccurateClusterDurationExplicitLastFrameDuration) { + EXPECT_TRUE(SegmentInit(false, true)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(2000000); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(6000000); + frame.set_duration(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + // SegmentInfo's duration is in timecode scale + EXPECT_EQ(8, segment_.GetSegmentInfo()->duration()); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles( + GetTestFilePath("accurate_cluster_duration_last_frame.webm"), filename_)); +} + +TEST_F(MuxerTest, AccurateClusterDurationTwoTracks) { + EXPECT_TRUE(SegmentInit(false, true)); + AddVideoTrack(); + AddAudioTrack(); + + Frame video_frame; + video_frame.Init(dummy_data_, kFrameLength); + video_frame.set_track_number(kVideoTrackNumber); + Frame audio_frame; + audio_frame.Init(dummy_data_, kFrameLength); + audio_frame.set_track_number(kAudioTrackNumber); + std::array cluster_timestamps = {{0, 40000000}}; + for (const std::uint64_t cluster_timestamp : cluster_timestamps) { + // Add video and audio frames with timestamp 0. + video_frame.set_timestamp(cluster_timestamp); + video_frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + audio_frame.set_timestamp(cluster_timestamp); + audio_frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame)); + + // Add 3 consecutive audio frames. + std::array audio_timestamps = { + {10000000, 20000000, 30000000}}; + for (const std::uint64_t audio_timestamp : audio_timestamps) { + audio_frame.set_timestamp(cluster_timestamp + audio_timestamp); + // Explicitly set duration for the very last audio frame. + if (cluster_timestamp == 40000000 && audio_timestamp == 30000000) { + audio_frame.set_duration(10000000); + } + EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame)); + } + + // Add a video frame with timestamp 33ms. + video_frame.set_is_key(false); + // Explicitly set duration for the very last video frame. + if (cluster_timestamp == 40000000) { + video_frame.set_duration(7000000); + } + video_frame.set_timestamp(cluster_timestamp + 33000000); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + segment_.ForceNewClusterOnNextFrame(); + } + segment_.Finalize(); + + // SegmentInfo's duration is in timecode scale + EXPECT_EQ(80, segment_.GetSegmentInfo()->duration()); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles( + GetTestFilePath("accurate_cluster_duration_two_tracks.webm"), filename_)); +} + } // namespace test } // namespace libwebm diff --git a/testing/testdata/accurate_cluster_duration.webm b/testing/testdata/accurate_cluster_duration.webm new file mode 100644 index 0000000000000000000000000000000000000000..6c8b59ef0652c85e351afc393dc2722b72809227 GIT binary patch literal 376 zcmb1gy}x+AQ(GgW({~{L)X3uWxsk)EsiizMDc7mJk;$pGkx3%BA)S!{1Qka*t6uwe4SzD5+c&TeEvQltI6k!dlSiUrd^dYF)O{cdDJ M)h7JBkquoN0HDWbKL7v# literal 0 HcmV?d00001 diff --git a/testing/testdata/accurate_cluster_duration_last_frame.webm b/testing/testdata/accurate_cluster_duration_last_frame.webm new file mode 100644 index 0000000000000000000000000000000000000000..159f5b2f82c2b819aee2f1c640ba9e72e7c03e47 GIT binary patch literal 384 zcmb1gy}x+AQ(GgW({~{L)X3uWxsk)EsiizMDc7mJk;$pGkx3%BA)S!{1Q-JZeP@@f z^4;AXyt+lyb7flan#Mc`Pb_3zHjuX#!jpI2Rtn^ud$Sj;k6Z#|#V)Pu8=Lu^99%kE z9D(NeHcZaVF3T;gNG*yl&C4u_FG(#fDGqnUCJJ)XoOQFVH!>Cb+Ze&8$ TCjGpT4WbN56~pgF7N9Z!ZQW{O literal 0 HcmV?d00001 diff --git a/testing/testdata/accurate_cluster_duration_two_tracks.webm b/testing/testdata/accurate_cluster_duration_two_tracks.webm new file mode 100644 index 0000000000000000000000000000000000000000..3f3677cdd3f7d45ec5c41581f1366ddf0e073c59 GIT binary patch literal 533 zcmb1gy}x+AQ(GgW({~{L)X3uWxsk)EsiizMDc7mJk;$pGkx3%BA)S!{1Q?$R`pzy{ z<-5B(cy)`Y=gPF;HH~=?o><7bY#?tfgeUL3trW<6`(`g#AGrj`id|aQH#YM-IkSeiH*1ihL7;2oit6gbFl`~w7)2z(-;F?Vc2v7H K=w`PD&~5-nVvDQ* literal 0 HcmV?d00001