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 <ctime>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "mkvmuxerutil.hpp"
|
#include "mkvmuxerutil.hpp"
|
||||||
#include "mkvparser/mkvparser.hpp"
|
#include "mkvparser/mkvparser.hpp"
|
||||||
@ -148,6 +149,7 @@ Frame::Frame()
|
|||||||
additional_(NULL),
|
additional_(NULL),
|
||||||
additional_length_(0),
|
additional_length_(0),
|
||||||
duration_(0),
|
duration_(0),
|
||||||
|
duration_set_(false),
|
||||||
frame_(NULL),
|
frame_(NULL),
|
||||||
is_key_(false),
|
is_key_(false),
|
||||||
length_(0),
|
length_(0),
|
||||||
@ -180,6 +182,7 @@ bool Frame::CopyFrom(const Frame& frame) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
duration_ = frame.duration();
|
duration_ = frame.duration();
|
||||||
|
duration_set_ = frame.duration_set();
|
||||||
is_key_ = frame.is_key();
|
is_key_ = frame.is_key();
|
||||||
track_number_ = frame.track_number();
|
track_number_ = frame.track_number();
|
||||||
timestamp_ = frame.timestamp();
|
timestamp_ = frame.timestamp();
|
||||||
@ -240,6 +243,11 @@ bool Frame::CanBeSimpleBlock() const {
|
|||||||
return additional_ == NULL && discard_padding_ == 0 && duration_ == 0;
|
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) {
|
void Frame::set_reference_block_timestamp(int64 reference_block_timestamp) {
|
||||||
reference_block_timestamp_ = reference_block_timestamp;
|
reference_block_timestamp_ = reference_block_timestamp;
|
||||||
reference_block_timestamp_set_ = true;
|
reference_block_timestamp_set_ = true;
|
||||||
@ -2071,7 +2079,8 @@ bool Tags::ExpandTagsArray() {
|
|||||||
//
|
//
|
||||||
// Cluster class
|
// 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),
|
: blocks_added_(0),
|
||||||
finalized_(false),
|
finalized_(false),
|
||||||
header_written_(false),
|
header_written_(false),
|
||||||
@ -2080,6 +2089,7 @@ Cluster::Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale)
|
|||||||
size_position_(-1),
|
size_position_(-1),
|
||||||
timecode_(timecode),
|
timecode_(timecode),
|
||||||
timecode_scale_(timecode_scale),
|
timecode_scale_(timecode_scale),
|
||||||
|
write_last_frame_with_duration_(write_last_frame_with_duration),
|
||||||
writer_(NULL) {}
|
writer_(NULL) {}
|
||||||
|
|
||||||
Cluster::~Cluster() {}
|
Cluster::~Cluster() {}
|
||||||
@ -2092,7 +2102,9 @@ bool Cluster::Init(IMkvWriter* ptr_writer) {
|
|||||||
return true;
|
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,
|
bool Cluster::AddFrame(const uint8* data, uint64 length, uint64 track_number,
|
||||||
uint64 abs_timecode, bool is_key) {
|
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_track_number(track_number);
|
||||||
frame.set_timestamp(abs_timecode);
|
frame.set_timestamp(abs_timecode);
|
||||||
frame.set_is_key(is_key);
|
frame.set_is_key(is_key);
|
||||||
return DoWriteFrame(&frame);
|
return QueueOrWriteFrame(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Cluster::AddFrameWithAdditional(const uint8* data, uint64 length,
|
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_track_number(track_number);
|
||||||
frame.set_timestamp(abs_timecode);
|
frame.set_timestamp(abs_timecode);
|
||||||
frame.set_is_key(is_key);
|
frame.set_is_key(is_key);
|
||||||
return DoWriteFrame(&frame);
|
return QueueOrWriteFrame(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Cluster::AddFrameWithDiscardPadding(const uint8* data, uint64 length,
|
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_track_number(track_number);
|
||||||
frame.set_timestamp(abs_timecode);
|
frame.set_timestamp(abs_timecode);
|
||||||
frame.set_is_key(is_key);
|
frame.set_is_key(is_key);
|
||||||
return DoWriteFrame(&frame);
|
return QueueOrWriteFrame(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Cluster::AddMetadata(const uint8* data, uint64 length, uint64 track_number,
|
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_timestamp(abs_timecode);
|
||||||
frame.set_duration(duration_timecode);
|
frame.set_duration(duration_timecode);
|
||||||
frame.set_is_key(true); // All metadata blocks are keyframes.
|
frame.set_is_key(true); // All metadata blocks are keyframes.
|
||||||
return DoWriteFrame(&frame);
|
return QueueOrWriteFrame(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cluster::AddPayloadSize(uint64 size) { payload_size_ += size; }
|
void Cluster::AddPayloadSize(uint64 size) { payload_size_ += size; }
|
||||||
|
|
||||||
bool Cluster::Finalize() {
|
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;
|
return false;
|
||||||
|
|
||||||
if (writer_->Seekable()) {
|
if (writer_->Seekable()) {
|
||||||
@ -2220,6 +2277,62 @@ bool Cluster::DoWriteFrame(const Frame* const frame) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
PostWriteBlock(element_size);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2716,15 +2829,17 @@ bool Segment::Finalize() {
|
|||||||
if (WriteFramesAll() < 0)
|
if (WriteFramesAll() < 0)
|
||||||
return false;
|
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 (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_) {
|
if (chunking_ && chunk_writer_cluster_) {
|
||||||
chunk_writer_cluster_->Close();
|
chunk_writer_cluster_->Close();
|
||||||
chunk_count_++;
|
chunk_count_++;
|
||||||
@ -3023,6 +3138,10 @@ bool Segment::AddGenericFrame(const Frame* frame) {
|
|||||||
|
|
||||||
void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; }
|
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) {
|
bool Segment::SetChunking(bool chunking, const char* filename) {
|
||||||
if (chunk_count_ > 0)
|
if (chunk_count_ > 0)
|
||||||
return false;
|
return false;
|
||||||
@ -3288,19 +3407,17 @@ bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) {
|
|||||||
if (!WriteFramesLessThan(frame_timestamp_ns))
|
if (!WriteFramesLessThan(frame_timestamp_ns))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (mode_ == kFile) {
|
if (cluster_list_size_ > 0) {
|
||||||
if (cluster_list_size_ > 0) {
|
// Update old cluster's size
|
||||||
// Update old cluster's size
|
Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
|
||||||
Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
|
|
||||||
|
|
||||||
if (!old_cluster || !old_cluster->Finalize())
|
if (!old_cluster || !old_cluster->Finalize(true, frame_timestamp_ns))
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (output_cues_)
|
|
||||||
new_cuepoint_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (output_cues_)
|
||||||
|
new_cuepoint_ = true;
|
||||||
|
|
||||||
if (chunking_ && cluster_list_size_ > 0) {
|
if (chunking_ && cluster_list_size_ > 0) {
|
||||||
chunk_writer_cluster_->Close();
|
chunk_writer_cluster_->Close();
|
||||||
chunk_count_++;
|
chunk_count_++;
|
||||||
@ -3327,8 +3444,9 @@ bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) {
|
|||||||
|
|
||||||
Cluster*& cluster = cluster_list_[cluster_list_size_];
|
Cluster*& cluster = cluster_list_[cluster_list_size_];
|
||||||
const int64 offset = MaxOffset();
|
const int64 offset = MaxOffset();
|
||||||
cluster = new (std::nothrow) Cluster(cluster_timecode, // NOLINT
|
cluster = new (std::nothrow)
|
||||||
offset, segment_info_.timecode_scale());
|
Cluster(cluster_timecode, offset, segment_info_.timecode_scale(),
|
||||||
|
accurate_cluster_duration_);
|
||||||
if (!cluster)
|
if (!cluster)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
61
mkvmuxer.hpp
61
mkvmuxer.hpp
@ -10,6 +10,8 @@
|
|||||||
#define MKVMUXER_HPP
|
#define MKVMUXER_HPP
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "mkvmuxertypes.hpp"
|
#include "mkvmuxertypes.hpp"
|
||||||
#include "webmids.hpp"
|
#include "webmids.hpp"
|
||||||
@ -101,8 +103,9 @@ class Frame {
|
|||||||
uint64 add_id() const { return add_id_; }
|
uint64 add_id() const { return add_id_; }
|
||||||
const uint8* additional() const { return additional_; }
|
const uint8* additional() const { return additional_; }
|
||||||
uint64 additional_length() const { return additional_length_; }
|
uint64 additional_length() const { return additional_length_; }
|
||||||
void set_duration(uint64 duration) { duration_ = duration; }
|
void set_duration(uint64 duration);
|
||||||
uint64 duration() const { return duration_; }
|
uint64 duration() const { return duration_; }
|
||||||
|
bool duration_set() const { return duration_set_; }
|
||||||
const uint8* frame() const { return frame_; }
|
const uint8* frame() const { return frame_; }
|
||||||
void set_is_key(bool key) { is_key_ = key; }
|
void set_is_key(bool key) { is_key_ = key; }
|
||||||
bool is_key() const { return is_key_; }
|
bool is_key() const { return is_key_; }
|
||||||
@ -134,6 +137,11 @@ class Frame {
|
|||||||
// Duration of the frame in nanoseconds.
|
// Duration of the frame in nanoseconds.
|
||||||
uint64 duration_;
|
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.
|
// Pointer to the data. Owned by this class.
|
||||||
uint8* frame_;
|
uint8* frame_;
|
||||||
|
|
||||||
@ -978,7 +986,8 @@ class Cluster {
|
|||||||
// |timecode| is the absolute timecode of the cluster. |cues_pos| is the
|
// |timecode| is the absolute timecode of the cluster. |cues_pos| is the
|
||||||
// position for the cluster within the segment that should be written in
|
// position for the cluster within the segment that should be written in
|
||||||
// the cues element. |timecode_scale| is the timecode scale of the segment.
|
// 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();
|
~Cluster();
|
||||||
|
|
||||||
bool Init(IMkvWriter* ptr_writer);
|
bool Init(IMkvWriter* ptr_writer);
|
||||||
@ -1055,9 +1064,22 @@ class Cluster {
|
|||||||
void AddPayloadSize(uint64 size);
|
void AddPayloadSize(uint64 size);
|
||||||
|
|
||||||
// Closes the cluster so no more data can be written to it. Will update the
|
// 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();
|
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.
|
// Returns the size in bytes for the entire Cluster element.
|
||||||
uint64 Size() const;
|
uint64 Size() const;
|
||||||
|
|
||||||
@ -1071,8 +1093,17 @@ class Cluster {
|
|||||||
int64 position_for_cues() const { return position_for_cues_; }
|
int64 position_for_cues() const { return position_for_cues_; }
|
||||||
uint64 timecode() const { return timecode_; }
|
uint64 timecode() const { return timecode_; }
|
||||||
uint64 timecode_scale() const { return timecode_scale_; }
|
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:
|
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
|
// Utility method that confirms that blocks can still be added, and that the
|
||||||
// cluster header has been written. Used by |DoWriteFrame*|. Returns true
|
// cluster header has been written. Used by |DoWriteFrame*|. Returns true
|
||||||
// when successful.
|
// when successful.
|
||||||
@ -1085,6 +1116,10 @@ class Cluster {
|
|||||||
// Does some verification and calls WriteFrame.
|
// Does some verification and calls WriteFrame.
|
||||||
bool DoWriteFrame(const Frame* const frame);
|
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.
|
// Outputs the Cluster header to |writer_|. Returns true on success.
|
||||||
bool WriteClusterHeader();
|
bool WriteClusterHeader();
|
||||||
|
|
||||||
@ -1112,6 +1147,19 @@ class Cluster {
|
|||||||
// The timecode scale of the Segment containing the cluster.
|
// The timecode scale of the Segment containing the cluster.
|
||||||
const uint64 timecode_scale_;
|
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.
|
// Pointer to the writer object. Not owned by this class.
|
||||||
IMkvWriter* writer_;
|
IMkvWriter* writer_;
|
||||||
|
|
||||||
@ -1387,6 +1435,9 @@ class Segment {
|
|||||||
// Toggles whether to output a cues element.
|
// Toggles whether to output a cues element.
|
||||||
void OutputCues(bool output_cues);
|
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
|
// 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
|
// 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
|
// 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.
|
// Flag whether or not the muxer should output a Cues element.
|
||||||
bool output_cues_;
|
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
|
// The size of the EBML header, used to validate the header if
|
||||||
// WriteEbmlHeader() is called more than once.
|
// WriteEbmlHeader() is called more than once.
|
||||||
int32 ebml_header_size_;
|
int32 ebml_header_size_;
|
||||||
|
@ -54,6 +54,8 @@ void Usage() {
|
|||||||
printf(" -video_track_number <int> >0 Changes the video track number\n");
|
printf(" -video_track_number <int> >0 Changes the video track number\n");
|
||||||
printf(" -chunking <string> Chunk output\n");
|
printf(" -chunking <string> Chunk output\n");
|
||||||
printf(" -copy_tags <int> >0 Copies the tags\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("\n");
|
||||||
printf("Video options:\n");
|
printf("Video options:\n");
|
||||||
printf(" -display_width <int> Display width in pixels\n");
|
printf(" -display_width <int> Display width in pixels\n");
|
||||||
@ -164,6 +166,7 @@ int main(int argc, char* argv[]) {
|
|||||||
bool chunking = false;
|
bool chunking = false;
|
||||||
bool copy_tags = false;
|
bool copy_tags = false;
|
||||||
const char* chunk_name = NULL;
|
const char* chunk_name = NULL;
|
||||||
|
bool accurate_cluster_duration = false;
|
||||||
|
|
||||||
bool output_cues_block_number = true;
|
bool output_cues_block_number = true;
|
||||||
|
|
||||||
@ -219,6 +222,10 @@ int main(int argc, char* argv[]) {
|
|||||||
chunk_name = argv[++i];
|
chunk_name = argv[++i];
|
||||||
} else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) {
|
||||||
copy_tags = strtol(argv[++i], &end, 10) == 0 ? false : true;
|
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) {
|
} else if (!strcmp("-display_width", argv[i]) && i < argc_check) {
|
||||||
display_width = strtol(argv[++i], &end, 10);
|
display_width = strtol(argv[++i], &end, 10);
|
||||||
} else if (!strcmp("-display_height", argv[i]) && i < argc_check) {
|
} else if (!strcmp("-display_height", argv[i]) && i < argc_check) {
|
||||||
@ -297,6 +304,8 @@ int main(int argc, char* argv[]) {
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
muxer_segment.AccurateClusterDuration(accurate_cluster_duration);
|
||||||
|
|
||||||
if (live_mode)
|
if (live_mode)
|
||||||
muxer_segment.set_mode(libwebm::mkvmuxer::Segment::kLive);
|
muxer_segment.set_mode(libwebm::mkvmuxer::Segment::kLive);
|
||||||
else
|
else
|
||||||
|
@ -96,13 +96,14 @@ class MuxerTest : public testing::Test {
|
|||||||
is_writer_open_ = false;
|
is_writer_open_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SegmentInit(bool output_cues) {
|
bool SegmentInit(bool output_cues, bool accurate_cluster_duration) {
|
||||||
if (!segment_.Init(writer_.get()))
|
if (!segment_.Init(writer_.get()))
|
||||||
return false;
|
return false;
|
||||||
SegmentInfo* const info = segment_.GetSegmentInfo();
|
SegmentInfo* const info = segment_.GetSegmentInfo();
|
||||||
info->set_writing_app(kAppString);
|
info->set_writing_app(kAppString);
|
||||||
info->set_muxing_app(kAppString);
|
info->set_muxing_app(kAppString);
|
||||||
segment_.OutputCues(output_cues);
|
segment_.OutputCues(output_cues);
|
||||||
|
segment_.AccurateClusterDuration(accurate_cluster_duration);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ class MuxerTest : public testing::Test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(MuxerTest, SegmentInfo) {
|
TEST_F(MuxerTest, SegmentInfo) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
SegmentInfo* const info = segment_.GetSegmentInfo();
|
SegmentInfo* const info = segment_.GetSegmentInfo();
|
||||||
info->set_timecode_scale(kTimeCodeScale);
|
info->set_timecode_scale(kTimeCodeScale);
|
||||||
info->set_duration(2.345);
|
info->set_duration(2.345);
|
||||||
@ -133,7 +134,7 @@ TEST_F(MuxerTest, SegmentInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, AddTracks) {
|
TEST_F(MuxerTest, AddTracks) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
|
|
||||||
// Add a Video Track
|
// Add a Video Track
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
@ -177,7 +178,7 @@ TEST_F(MuxerTest, AddTracks) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, AddChapters) {
|
TEST_F(MuxerTest, AddChapters) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
// Add a Chapter
|
// Add a Chapter
|
||||||
@ -194,7 +195,7 @@ TEST_F(MuxerTest, AddChapters) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, SimpleBlock) {
|
TEST_F(MuxerTest, SimpleBlock) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
// Valid Frame
|
// Valid Frame
|
||||||
@ -222,7 +223,7 @@ TEST_F(MuxerTest, SimpleBlock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
|
TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
Frame frame;
|
Frame frame;
|
||||||
@ -254,7 +255,7 @@ TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, MetadataBlock) {
|
TEST_F(MuxerTest, MetadataBlock) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
|
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
|
||||||
track->set_uid(kMetadataTrackNumber);
|
track->set_uid(kMetadataTrackNumber);
|
||||||
track->set_type(kMetadataTrackType);
|
track->set_type(kMetadataTrackType);
|
||||||
@ -285,7 +286,7 @@ TEST_F(MuxerTest, MetadataBlock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, TrackType) {
|
TEST_F(MuxerTest, TrackType) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
|
Track* const track = segment_.AddTrack(kMetadataTrackNumber);
|
||||||
track->set_uid(kMetadataTrackNumber);
|
track->set_uid(kMetadataTrackNumber);
|
||||||
track->set_codec_id(kMetadataCodecId);
|
track->set_codec_id(kMetadataCodecId);
|
||||||
@ -305,7 +306,7 @@ TEST_F(MuxerTest, TrackType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, BlockWithAdditional) {
|
TEST_F(MuxerTest, BlockWithAdditional) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
// Valid Frame
|
// Valid Frame
|
||||||
@ -346,7 +347,7 @@ TEST_F(MuxerTest, BlockWithAdditional) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
|
TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
Frame frame;
|
Frame frame;
|
||||||
@ -381,7 +382,7 @@ TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, SegmentDurationComputation) {
|
TEST_F(MuxerTest, SegmentDurationComputation) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
Frame frame;
|
Frame frame;
|
||||||
@ -409,7 +410,7 @@ TEST_F(MuxerTest, SegmentDurationComputation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, ForceNewCluster) {
|
TEST_F(MuxerTest, ForceNewCluster) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
|
EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
|
||||||
@ -431,7 +432,7 @@ TEST_F(MuxerTest, ForceNewCluster) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, OutputCues) {
|
TEST_F(MuxerTest, OutputCues) {
|
||||||
EXPECT_TRUE(SegmentInit(true));
|
EXPECT_TRUE(SegmentInit(true, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
EXPECT_TRUE(
|
EXPECT_TRUE(
|
||||||
@ -451,7 +452,7 @@ TEST_F(MuxerTest, OutputCues) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, CuesBeforeClusters) {
|
TEST_F(MuxerTest, CuesBeforeClusters) {
|
||||||
EXPECT_TRUE(SegmentInit(true));
|
EXPECT_TRUE(SegmentInit(true, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
|
|
||||||
EXPECT_TRUE(
|
EXPECT_TRUE(
|
||||||
@ -484,7 +485,7 @@ TEST_F(MuxerTest, CuesBeforeClusters) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, MaxClusterSize) {
|
TEST_F(MuxerTest, MaxClusterSize) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
const uint64 kMaxClusterSize = 20;
|
const uint64 kMaxClusterSize = 20;
|
||||||
segment_.set_max_cluster_size(kMaxClusterSize);
|
segment_.set_max_cluster_size(kMaxClusterSize);
|
||||||
@ -509,7 +510,7 @@ TEST_F(MuxerTest, MaxClusterSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, MaxClusterDuration) {
|
TEST_F(MuxerTest, MaxClusterDuration) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddVideoTrack();
|
AddVideoTrack();
|
||||||
const uint64 kMaxClusterDuration = 4000000;
|
const uint64 kMaxClusterDuration = 4000000;
|
||||||
segment_.set_max_cluster_duration(kMaxClusterDuration);
|
segment_.set_max_cluster_duration(kMaxClusterDuration);
|
||||||
@ -537,7 +538,7 @@ TEST_F(MuxerTest, MaxClusterDuration) {
|
|||||||
|
|
||||||
TEST_F(MuxerTest, SetCuesTrackNumber) {
|
TEST_F(MuxerTest, SetCuesTrackNumber) {
|
||||||
const uint64 kTrackNumber = 10;
|
const uint64 kTrackNumber = 10;
|
||||||
EXPECT_TRUE(SegmentInit(true));
|
EXPECT_TRUE(SegmentInit(true, false));
|
||||||
const uint64 vid_track =
|
const uint64 vid_track =
|
||||||
segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber);
|
segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber);
|
||||||
EXPECT_EQ(kTrackNumber, vid_track);
|
EXPECT_EQ(kTrackNumber, vid_track);
|
||||||
@ -566,7 +567,7 @@ TEST_F(MuxerTest, SetCuesTrackNumber) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MuxerTest, BlockWithDiscardPadding) {
|
TEST_F(MuxerTest, BlockWithDiscardPadding) {
|
||||||
EXPECT_TRUE(SegmentInit(false));
|
EXPECT_TRUE(SegmentInit(false, false));
|
||||||
AddAudioTrack();
|
AddAudioTrack();
|
||||||
|
|
||||||
int timecode = 1000;
|
int timecode = 1000;
|
||||||
@ -588,6 +589,120 @@ TEST_F(MuxerTest, BlockWithDiscardPadding) {
|
|||||||
EXPECT_TRUE(CompareFiles(GetTestFilePath("discard_padding.webm"), filename_));
|
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 test
|
||||||
} // namespace libwebm
|
} // 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…
x
Reference in New Issue
Block a user