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:
parent
008aa63d6a
commit
0407360dcf
172
mkvmuxer.cpp
172
mkvmuxer.cpp
@ -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;
|
||||
|
||||
|
61
mkvmuxer.hpp
61
mkvmuxer.hpp
@ -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_;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
BIN
testing/testdata/accurate_cluster_duration.webm
vendored
Normal file
BIN
testing/testdata/accurate_cluster_duration.webm
vendored
Normal file
Binary file not shown.
BIN
testing/testdata/accurate_cluster_duration_last_frame.webm
vendored
Normal file
BIN
testing/testdata/accurate_cluster_duration_last_frame.webm
vendored
Normal file
Binary file not shown.
BIN
testing/testdata/accurate_cluster_duration_two_tracks.webm
vendored
Normal file
BIN
testing/testdata/accurate_cluster_duration_two_tracks.webm
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user