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
This commit is contained in:
Vignesh Venkatasubramanian 2016-03-14 16:33:46 -07:00
parent 008aa63d6a
commit 0407360dcf
7 changed files with 345 additions and 48 deletions

View File

@ -18,6 +18,7 @@
#include <ctime>
#include <memory>
#include <new>
#include <vector>
#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<std::list<Frame*>::iterator> frames_to_erase;
for (std::list<Frame *>::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<std::list<Frame*>::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;

View File

@ -10,6 +10,8 @@
#define MKVMUXER_HPP
#include <cstddef>
#include <list>
#include <map>
#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<uint64, std::list<Frame*> >::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<uint64, std::list<Frame*> > stored_frames_;
// Map from track number to the timestamp of the last block written for that
// track.
std::map<uint64, uint64> 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_;

View File

@ -54,6 +54,8 @@ void Usage() {
printf(" -video_track_number <int> >0 Changes the video track number\n");
printf(" -chunking <string> Chunk output\n");
printf(" -copy_tags <int> >0 Copies the tags\n");
printf(" -accurate_cluster_duration <int> ");
printf(">0 Writes the last frame in each cluster with Duration\n");
printf("\n");
printf("Video options:\n");
printf(" -display_width <int> 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

View File

@ -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<std::uint64_t, 2> 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<std::uint64_t, 3> 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

Binary file not shown.

Binary file not shown.

Binary file not shown.