diff --git a/mkvmuxer.cpp b/mkvmuxer.cpp index d53b20b..7538dd9 100644 --- a/mkvmuxer.cpp +++ b/mkvmuxer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "mkvmuxerutil.hpp" @@ -1024,28 +1025,14 @@ bool Cluster::Init(IMkvWriter* ptr_writer) { bool Cluster::AddFrame(const uint8* frame, uint64 length, uint64 track_number, - int16 timecode, + uint64 abs_timecode, bool is_key) { - if (finalized_) - return false; - - if (!header_written_) - if (!WriteClusterHeader()) - return false; - - const uint64 element_size = WriteSimpleBlock(writer_, - frame, - length, - static_cast(track_number), - timecode, - is_key); - if (!element_size) - return false; - - AddPayloadSize(element_size); - blocks_added_++; - - return true; + return DoWriteBlock(frame, + length, + track_number, + abs_timecode, + is_key ? 1 : 0, + &WriteSimpleBlock); } void Cluster::AddPayloadSize(uint64 size) { @@ -1081,6 +1068,59 @@ uint64 Cluster::Size() const { return element_size; } +bool Cluster::DoWriteBlock( + const uint8* frame, + uint64 length, + uint64 track_number, + uint64 abs_timecode, + uint64 generic_arg, + WriteBlock write_block) { + if (frame == NULL || length == 0) + return false; + + // To simplify things, we require that there be fewer than 127 + // tracks -- this allows us to serialize the track number value for + // a stream using a single byte, per the Matroska encoding. + + if (track_number == 0 || track_number > 0x7E) + return false; + + const int64 cluster_timecode = this->Cluster::timecode(); + const int64 rel_timecode = + static_cast(abs_timecode) - cluster_timecode; + + if (rel_timecode < 0) + return false; + + if (rel_timecode > std::numeric_limits::max()) + return false; + + if (write_block == NULL) + return false; + + if (finalized_) + return false; + + if (!header_written_) + if (!WriteClusterHeader()) + return false; + + const uint64 element_size = (*write_block)(writer_, + frame, + length, + track_number, + rel_timecode, + generic_arg); + + if (element_size == 0) + return false; + + AddPayloadSize(element_size); + blocks_added_++; + + return true; +} + bool Cluster::WriteClusterHeader() { if (finalized_) return false; @@ -1710,10 +1750,14 @@ bool Segment::AddFrame(const uint8* frame, if (!cluster) return false; - int64 block_timecode = timestamp / segment_info_.timecode_scale(); - block_timecode -= static_cast(cluster->timecode()); + const uint64 timecode_scale = segment_info_.timecode_scale(); + const uint64 abs_timecode = timestamp / timecode_scale; - if (block_timecode < 0) + if (!cluster->AddFrame(frame, + length, + track_number, + abs_timecode, + is_key)) return false; if (new_cuepoint_ && cues_track_ == track_number) { @@ -1721,13 +1765,6 @@ bool Segment::AddFrame(const uint8* frame, return false; } - if (!cluster->AddFrame(frame, - length, - track_number, - static_cast(block_timecode), - is_key)) - return false; - if (timestamp > last_timestamp_) last_timestamp_ = timestamp; @@ -2027,32 +2064,30 @@ bool Segment::WriteFramesAll() { if (!cluster) return false; + const uint64 timecode_scale = segment_info_.timecode_scale(); + for (int32 i = 0; i < frames_size_; ++i) { - Frame* const frame = frames_[i]; - - int64 block_timecode = - frame->timestamp() / segment_info_.timecode_scale(); - block_timecode -= static_cast(cluster->timecode()); - - if (block_timecode < 0) - return false; - - if (new_cuepoint_ && cues_track_ == frame->track_number()) { - if (!AddCuePoint(frame->timestamp())) - return false; - } + Frame*& frame = frames_[i]; + const uint64 frame_timestamp = frame->timestamp(); // ns + const uint64 frame_timecode = frame_timestamp / timecode_scale; if (!cluster->AddFrame(frame->frame(), frame->length(), frame->track_number(), - static_cast(block_timecode), + frame_timecode, frame->is_key())) return false; - if (frame->timestamp() > last_timestamp_) - last_timestamp_ = frame->timestamp(); + if (new_cuepoint_ && cues_track_ == frame->track_number()) { + if (!AddCuePoint(frame_timestamp)) + return false; + } + + if (frame_timestamp > last_timestamp_) + last_timestamp_ = frame_timestamp; delete frame; + frame = NULL; } frames_size_ = 0; @@ -2070,10 +2105,10 @@ bool Segment::WriteFramesLessThan(uint64 timestamp) { return false; Cluster* const cluster = cluster_list_[cluster_list_size_-1]; - if (!cluster) return false; + const uint64 timecode_scale = segment_info_.timecode_scale(); int32 shift_left = 0; // TODO(fgalligan): Change this to use the durations of frames instead of @@ -2085,29 +2120,24 @@ bool Segment::WriteFramesLessThan(uint64 timestamp) { break; const Frame* const frame_prev = frames_[i-1]; - - int64 block_timecode = - frame_prev->timestamp() / segment_info_.timecode_scale(); - block_timecode -= static_cast(cluster->timecode()); - - if (block_timecode < 0) - return false; - - if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { - if (!AddCuePoint(frame_prev->timestamp())) - return false; - } + const uint64 frame_timestamp = frame_prev->timestamp(); + const uint64 frame_timecode = frame_timestamp / timecode_scale; if (!cluster->AddFrame(frame_prev->frame(), frame_prev->length(), frame_prev->track_number(), - static_cast(block_timecode), + frame_timecode, frame_prev->is_key())) return false; + if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { + if (!AddCuePoint(frame_timestamp)) + return false; + } + ++shift_left; - if (frame_prev->timestamp() > last_timestamp_) - last_timestamp_ = frame_prev->timestamp(); + if (frame_timestamp > last_timestamp_) + last_timestamp_ = frame_timestamp; delete frame_prev; } diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp index b93c3e5..9e38cfe 100644 --- a/mkvmuxer.hpp +++ b/mkvmuxer.hpp @@ -489,13 +489,14 @@ class Cluster { // frame: Pointer to the data // length: Length of the data // track_number: Track to add the data to. Value returned by Add track - // functions. - // timestamp: Timecode of the frame relative to the cluster timecode. + // functions. The range of allowed values is [1, 126]. + // timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. // is_key: Flag telling whether or not this frame is a key frame. bool AddFrame(const uint8* frame, uint64 length, uint64 track_number, - short timecode, + uint64 timecode, // timecode units (absolute) bool is_key); // Increments the size of the cluster's data in bytes. @@ -514,6 +515,23 @@ class Cluster { uint64 timecode() const { return timecode_; } private: + // Signature that matches either of WriteSimpleBlock or WriteMetadataBlock + // in the muxer utilities package. + typedef uint64 (*WriteBlock)(IMkvWriter* writer, + const uint8* data, + uint64 length, + uint64 track_number, + int64 timecode, + uint64 generic_arg); + + // Used to implement AddFrame. + bool DoWriteBlock(const uint8* frame, + uint64 length, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlock write_block); + // Outputs the Cluster header to |writer_|. Returns true on success. bool WriteClusterHeader(); @@ -670,7 +688,7 @@ class Segment { bool AddFrame(const uint8* frame, uint64 length, uint64 track_number, - uint64 timestamp, + uint64 timestamp_ns, bool is_key); // Adds a video track to the segment. Returns the number of the track on diff --git a/mkvmuxerutil.cpp b/mkvmuxerutil.cpp index 3931dd6..8f053c5 100644 --- a/mkvmuxerutil.cpp +++ b/mkvmuxerutil.cpp @@ -309,7 +309,7 @@ uint64 WriteSimpleBlock(IMkvWriter* writer, uint64 length, uint64 track_number, int64 timecode, - bool is_key) { + uint64 is_key) { if (!writer) return false; diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp index cfc75d5..867e2a9 100644 --- a/mkvmuxerutil.hpp +++ b/mkvmuxerutil.hpp @@ -62,13 +62,13 @@ bool WriteEbmlElement(IMkvWriter* writer, // permitted. // timecode: Relative timecode of the Block. Only values in the // range [0, 2^15) are permitted. -// is_key: Flag telling whether or not this frame is a key frame. +// is_key: Non-zero value specifies that frame is a key frame. uint64 WriteSimpleBlock(IMkvWriter* writer, const uint8* data, uint64 length, uint64 track_number, int64 timecode, - bool is_key); + uint64 is_key); // Output a metadata keyframe, using a Block Group element. // Inputs: