diff --git a/mkvmuxer.cpp b/mkvmuxer.cpp index d6c1e85..12d8d21 100644 --- a/mkvmuxer.cpp +++ b/mkvmuxer.cpp @@ -124,7 +124,8 @@ Frame::Frame() is_key_(false), length_(0), track_number_(0), - timestamp_(0) { + timestamp_(0), + discard_padding_(0) { } Frame::~Frame() { @@ -1513,6 +1514,21 @@ bool Cluster::AddFrameWithAdditional(const uint8* frame, &WriteBlockWithAdditional); } +bool Cluster::AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 abs_timecode, + bool is_key) { + return DoWriteBlockWithDiscardPadding(frame, + length, + discard_padding, + track_number, + abs_timecode, + is_key ? 1 : 0, + &WriteBlockWithDiscardPadding); +} + bool Cluster::AddMetadata(const uint8* frame, uint64 length, uint64 track_number, @@ -1559,6 +1575,42 @@ uint64 Cluster::Size() const { return element_size; } +template +bool Cluster::PreWriteBlock(Type* write_function) { + if (write_function == NULL) + return false; + + if (finalized_) + return false; + + if (!header_written_) { + if (!WriteClusterHeader()) + return false; + } + + return true; +} + +void Cluster::PostWriteBlock(uint64 element_size) { + AddPayloadSize(element_size); + ++blocks_added_; +} + +bool Cluster::IsValidTrackNumber(uint64 track_number) const { + return (track_number > 0 && track_number <= 0x7E); +} + +int64 Cluster::GetRelativeTimecode(int64 abs_timecode) const { + const int64 cluster_timecode = this->Cluster::timecode(); + const int64 rel_timecode = + static_cast(abs_timecode) - cluster_timecode; + + if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode) + return -1; + + return rel_timecode; +} + bool Cluster::DoWriteBlock( const uint8* frame, uint64 length, @@ -1569,46 +1621,26 @@ bool Cluster::DoWriteBlock( 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) + if (!IsValidTrackNumber(track_number)) return false; - const int64 cluster_timecode = this->Cluster::timecode(); - const int64 rel_timecode = - static_cast(abs_timecode) - cluster_timecode; - + const int64 rel_timecode = GetRelativeTimecode(abs_timecode); if (rel_timecode < 0) return false; - if (rel_timecode > kMaxBlockTimecode) + if (!PreWriteBlock(write_block)) 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_++; - + PostWriteBlock(element_size); return true; } @@ -1626,33 +1658,16 @@ bool Cluster::DoWriteBlockWithAdditional( additional == NULL || additional_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) + if (!IsValidTrackNumber(track_number)) return false; - const int64 cluster_timecode = this->Cluster::timecode(); - const int64 rel_timecode = - static_cast(abs_timecode) - cluster_timecode; - + const int64 rel_timecode = GetRelativeTimecode(abs_timecode); if (rel_timecode < 0) return false; - if (rel_timecode > kMaxBlockTimecode) + if (!PreWriteBlock(write_block)) 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, @@ -1662,13 +1677,45 @@ bool Cluster::DoWriteBlockWithAdditional( track_number, rel_timecode, generic_arg); - if (element_size == 0) return false; - AddPayloadSize(element_size); - blocks_added_++; + PostWriteBlock(element_size); + return true; +} +bool Cluster::DoWriteBlockWithDiscardPadding( + const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 abs_timecode, + uint64 generic_arg, + WriteBlockDiscardPadding write_block) { + if (frame == NULL || length == 0 || discard_padding <= 0) + return false; + + if (!IsValidTrackNumber(track_number)) + return false; + + const int64 rel_timecode = GetRelativeTimecode(abs_timecode); + if (rel_timecode < 0) + return false; + + if (!PreWriteBlock(write_block)) + return false; + + const uint64 element_size = (*write_block)(writer_, + frame, + length, + discard_padding, + track_number, + rel_timecode, + generic_arg); + if (element_size == 0) + return false; + + PostWriteBlock(element_size); return true; } @@ -2348,8 +2395,8 @@ bool Segment::AddFrame(const uint8* frame, // audio that is associated with the start time of a video key-frame is // muxed into the same cluster. if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) { - Frame* const new_frame = new Frame(); - if (!new_frame->Init(frame, length)) + Frame* const new_frame = new (std::nothrow) Frame(); + if (new_frame == NULL || !new_frame->Init(frame, length)) return false; new_frame->set_track_number(track_number); new_frame->set_timestamp(timestamp); @@ -2400,7 +2447,7 @@ bool Segment::AddFrameWithAdditional(const uint8* frame, uint64 track_number, uint64 timestamp, bool is_key) { - if (!frame || !additional) + if (frame == NULL || additional == NULL) return false; if (!CheckHeaderInfo()) @@ -2414,8 +2461,8 @@ bool Segment::AddFrameWithAdditional(const uint8* frame, // audio that is associated with the start time of a video key-frame is // muxed into the same cluster. if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) { - Frame* const new_frame = new Frame(); - if (!new_frame->Init(frame, length)) + Frame* const new_frame = new (std::nothrow) Frame(); + if (new_frame == NULL || !new_frame->Init(frame, length)) return false; new_frame->set_track_number(track_number); new_frame->set_timestamp(timestamp); @@ -2427,6 +2474,74 @@ bool Segment::AddFrameWithAdditional(const uint8* frame, return true; } + if (!DoNewClusterProcessing(track_number, timestamp, is_key)) + return false; + + if (cluster_list_size_ < 1) + return false; + + Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; + if (cluster == NULL) + return false; + + const uint64 timecode_scale = segment_info_.timecode_scale(); + const uint64 abs_timecode = timestamp / timecode_scale; + + if (!cluster->AddFrameWithAdditional(frame, + length, + additional, + additional_length, + add_id, + track_number, + abs_timecode, + is_key)) + return false; + + if (new_cuepoint_ && cues_track_ == track_number) { + if (!AddCuePoint(timestamp, cues_track_)) + return false; + } + + if (timestamp > last_timestamp_) + last_timestamp_ = timestamp; + + return true; +} + +bool Segment::AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 timestamp, + bool is_key) { + if (frame == NULL || discard_padding <= 0) + return false; + + if (!CheckHeaderInfo()) + return false; + + // Check for non-monotonically increasing timestamps. + if (timestamp < last_timestamp_) + return false; + + // If the segment has a video track hold onto audio frames to make sure the + // audio that is associated with the start time of a video key-frame is + // muxed into the same cluster. + if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) { + Frame* const new_frame = new (std::nothrow) Frame(); + if (new_frame == NULL || !new_frame->Init(frame, length)) + return false; + new_frame->set_track_number(track_number); + new_frame->set_timestamp(timestamp); + new_frame->set_is_key(is_key); + new_frame->set_discard_padding(discard_padding); + + if (!QueueFrame(new_frame)) + return false; + + return true; + } + if (!DoNewClusterProcessing(track_number, timestamp, is_key)) return false; @@ -2440,15 +2555,13 @@ bool Segment::AddFrameWithAdditional(const uint8* frame, const uint64 timecode_scale = segment_info_.timecode_scale(); const uint64 abs_timecode = timestamp / timecode_scale; - if (!cluster->AddFrameWithAdditional(frame, - length, - additional, - additional_length, - add_id, - track_number, - abs_timecode, - is_key)) + if (!cluster->AddFrameWithDiscardPadding(frame, length, + discard_padding, + track_number, + abs_timecode, + is_key)) { return false; + } if (new_cuepoint_ && cues_track_ == track_number) { if (!AddCuePoint(timestamp, cues_track_)) @@ -2523,6 +2636,12 @@ bool Segment::AddGenericFrame(const Frame* frame) { frame->track_number(), frame->timestamp(), frame->is_key()); + } else if (frame->discard_padding() > 0) { + return AddFrameWithDiscardPadding(frame->frame(), frame->length(), + frame->discard_padding(), + frame->track_number(), + frame->timestamp(), + frame->is_key()); } else { return AddFrame(frame->frame(), frame->length(), @@ -3006,12 +3125,24 @@ int Segment::WriteFramesAll() { 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(), - frame_timecode, - frame->is_key())) - return -1; + if (frame->discard_padding() > 0) { + if (!cluster->AddFrameWithDiscardPadding(frame->frame(), + frame->length(), + frame->discard_padding(), + frame->track_number(), + frame_timecode, + frame->is_key())) { + return -1; + } + } else { + if (!cluster->AddFrame(frame->frame(), + frame->length(), + frame->track_number(), + frame_timecode, + frame->is_key())) { + return -1; + } + } if (new_cuepoint_ && cues_track_ == frame->track_number()) { if (!AddCuePoint(frame_timestamp, cues_track_)) @@ -3057,13 +3188,26 @@ bool Segment::WriteFramesLessThan(uint64 timestamp) { const Frame* const frame_prev = frames_[i-1]; const uint64 frame_timestamp = frame_prev->timestamp(); const uint64 frame_timecode = frame_timestamp / timecode_scale; + const int64 discard_padding = frame_prev->discard_padding(); - if (!cluster->AddFrame(frame_prev->frame(), - frame_prev->length(), - frame_prev->track_number(), - frame_timecode, - frame_prev->is_key())) - return false; + if (discard_padding > 0) { + if (!cluster->AddFrameWithDiscardPadding(frame_prev->frame(), + frame_prev->length(), + discard_padding, + frame_prev->track_number(), + frame_timecode, + frame_prev->is_key())) { + return false; + } + } else { + if (!cluster->AddFrame(frame_prev->frame(), + frame_prev->length(), + frame_prev->track_number(), + frame_timecode, + frame_prev->is_key())) { + return false; + } + } if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { if (!AddCuePoint(frame_timestamp, cues_track_)) diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp index 8f169f9..63a315e 100644 --- a/mkvmuxer.hpp +++ b/mkvmuxer.hpp @@ -90,6 +90,10 @@ class Frame { uint64 track_number() const { return track_number_; } void set_timestamp(uint64 timestamp) { timestamp_ = timestamp; } uint64 timestamp() const { return timestamp_; } + void set_discard_padding(uint64 discard_padding) { + discard_padding_ = discard_padding; + } + uint64 discard_padding() const { return discard_padding_; } private: // Id of the Additional data. @@ -118,6 +122,9 @@ class Frame { // Timestamp of the data in nanoseconds. uint64 timestamp_; + + // Discard padding for the frame. + int64 discard_padding_; }; /////////////////////////////////////////////////////////////// @@ -745,6 +752,24 @@ class Cluster { uint64 abs_timecode, bool is_key); + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // frame: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // abs_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 AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 abs_timecode, + bool is_key); + // Writes a frame of metadata to the output medium; returns true on // success. // Inputs: @@ -803,6 +828,35 @@ class Cluster { int64 timecode, uint64 is_key); + // Signature that matches WriteBlockWithDiscardPadding + // in the muxer utilities package. + typedef uint64 (*WriteBlockDiscardPadding)(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key); + + // Utility method that confirms that blocks can still be added, and that the + // cluster header has been written. Used by |DoWriteBlock*|. Returns true + // when successful. + template + bool PreWriteBlock(Type* write_function); + + // Utility method used by the |DoWriteBlock*| methods that handles the book + // keeping required after each block is written. + void PostWriteBlock(uint64 element_size); + + // 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. + bool IsValidTrackNumber(uint64 track_number) const; + + // Given |abs_timecode|, calculates timecode relative to most recent timecode. + // Returns -1 on failure, or a relative timecode. + int64 GetRelativeTimecode(int64 abs_timecode) const; + // Used to implement AddFrame and AddMetadata. bool DoWriteBlock(const uint8* frame, uint64 length, @@ -822,6 +876,15 @@ class Cluster { uint64 generic_arg, WriteBlockAdditional write_block); + // Used to implement AddFrameWithDiscardPadding + bool DoWriteBlockWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlockDiscardPadding write_block); + // Outputs the Cluster header to |writer_|. Returns true on success. bool WriteClusterHeader(); @@ -1055,6 +1118,24 @@ class Segment { uint64 timestamp, bool is_key); + // Writes a frame with DiscardPadding to the output medium; returns true on + // success. + // Inputs: + // frame: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Absolute timestamp of the frame, expressed in nanosecond + // units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithDiscardPadding(const uint8* frame, + uint64 length, + int64 discard_padding, + uint64 track_number, + uint64 timestamp, + bool is_key); + // Writes a Frame to the output medium. Chooses the correct way of writing // the frame (Block vs SimpleBlock) based on the parameters passed. // Inputs: diff --git a/mkvmuxerutil.cpp b/mkvmuxerutil.cpp index e46cd7b..7aaec40 100644 --- a/mkvmuxerutil.cpp +++ b/mkvmuxerutil.cpp @@ -74,6 +74,10 @@ uint64 EbmlMasterElementSize(uint64 type, uint64 value) { return ebml_size; } +uint64 EbmlElementSize(uint64 type, int64 value) { + return EbmlElementSize(type, static_cast(value)); +} + uint64 EbmlElementSize(uint64 type, uint64 value) { // Size of EBML ID int32 ebml_size = GetUIntSize(type); @@ -464,7 +468,8 @@ uint64 WriteMetadataBlock(IMkvWriter* writer, return blockg_elem_size; } -// Writes a WebM Block with Additional. The structure is as follows +// Writes a WebM BlockGroup with BlockAdditional data. The structure is as +// follows: // Indentation shows sub-levels // BlockGroup // Block @@ -553,6 +558,70 @@ uint64 WriteBlockWithAdditional(IMkvWriter* writer, return block_group_elem_size; } +// Writes a WebM BlockGroup with DiscardPadding. The structure is as follows: +// Indentation shows sub-levels +// BlockGroup +// Block +// Data +// DiscardPadding +uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key) { + if (!data || length < 1 || discard_padding <= 0) + return 0; + + const uint64 block_payload_size = 4 + length; + const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock, + block_payload_size) + + block_payload_size; + const uint64 discard_padding_elem_size = EbmlElementSize(kMkvDiscardPadding, + discard_padding); + const uint64 block_group_payload_size = block_elem_size + + discard_padding_elem_size; + const uint64 block_group_elem_size = EbmlMasterElementSize( + kMkvBlockGroup, + block_group_payload_size) + + block_group_payload_size; + + if (!WriteEbmlMasterElement(writer, kMkvBlockGroup, + block_group_payload_size)) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlock, block_payload_size)) + return 0; + + if (WriteUInt(writer, track_number)) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + uint64 flags = 0; + if (is_key) + flags |= 0x80; + if (SerializeInt(writer, flags, 1)) + return 0; + + if (writer->Write(data, static_cast(length))) + return 0; + + if (WriteID(writer, kMkvDiscardPadding)) + return 0; + + const uint64 size = GetUIntSize(discard_padding); + if (WriteUInt(writer, size)) + return false; + + if (SerializeInt(writer, discard_padding, static_cast(size))) + return false; + + return block_group_elem_size; +} + uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) { if (!writer) return false; diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp index 1380f2c..d196ad3 100644 --- a/mkvmuxerutil.hpp +++ b/mkvmuxerutil.hpp @@ -25,6 +25,7 @@ int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size); int32 GetUIntSize(uint64 value); int32 GetCodedUIntSize(uint64 value); uint64 EbmlMasterElementSize(uint64 type, uint64 value); +uint64 EbmlElementSize(uint64 type, int64 value); uint64 EbmlElementSize(uint64 type, uint64 value); uint64 EbmlElementSize(uint64 type, float value); uint64 EbmlElementSize(uint64 type, const char* value); @@ -113,6 +114,25 @@ uint64 WriteBlockWithAdditional(IMkvWriter* writer, int64 timecode, uint64 is_key); +// Output an Mkv Block with a DiscardPadding element. +// Inputs: +// data: Pointer to the data. +// length: Length of the data. +// discard_padding: DiscardPadding value. +// track_number: Track to add the data to. Value returned by Add track +// functions. Only values in the range [1, 126] are +// permitted. +// timecode: Relative timecode of the Block. Only values in the +// range [0, 2^15) are permitted. +// is_key: Non-zero value specifies that frame is a key frame. +uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer, + const uint8* data, + uint64 length, + int64 discard_padding, + uint64 track_number, + int64 timecode, + uint64 is_key); + // Output a void element. |size| must be the entire size in bytes that will be // void. The function will calculate the size of the void header and subtract // it from |size|. diff --git a/sample_muxer.cpp b/sample_muxer.cpp index f367492..0dfc028 100644 --- a/sample_muxer.cpp +++ b/sample_muxer.cpp @@ -23,6 +23,7 @@ #include "sample_muxer_metadata.h" +using mkvmuxer::int64; using mkvmuxer::uint64; #ifdef _MSC_VER @@ -475,6 +476,7 @@ int main(int argc, char* argv[]) { (track_type == Track::kVideo && output_video)) { const int frame_count = block->GetFrameCount(); const bool is_key = block->IsKey(); + const int64 discard_padding = block->GetDiscardPadding(); for (int i = 0; i < frame_count; ++i) { const mkvparser::Block::Frame& frame = block->GetFrame(i); @@ -494,11 +496,21 @@ int main(int argc, char* argv[]) { if (track_type == Track::kAudio) track_num = aud_track; - if (!muxer_segment.AddFrame(data, - frame.len, - track_num, - time_ns, - is_key)) { + bool frame_added = false; + if (discard_padding) { + frame_added = + muxer_segment.AddFrameWithDiscardPadding(data, frame.len, + discard_padding, + track_num, + time_ns, + is_key); + } else { + frame_added = muxer_segment.AddFrame(data, frame.len, + track_num, + time_ns, + is_key); + } + if (!frame_added) { printf("\n Could not add frame.\n"); return EXIT_FAILURE; } diff --git a/webmids.hpp b/webmids.hpp index cb26646..65fab96 100644 --- a/webmids.hpp +++ b/webmids.hpp @@ -57,6 +57,7 @@ enum MkvId { kMkvBlockMore = 0xA6, kMkvBlockAddID = 0xEE, kMkvBlockAdditional = 0xA5, + kMkvDiscardPadding = 0x75A2, //Track kMkvTracks = 0x1654AE6B, kMkvTrackEntry = 0xAE,