diff --git a/mkvmuxer.cpp b/mkvmuxer.cpp index 24da6a1..2b258b5 100644 --- a/mkvmuxer.cpp +++ b/mkvmuxer.cpp @@ -460,6 +460,7 @@ Track::Track(unsigned int* seed) : codec_id_(NULL), codec_private_(NULL), language_(NULL), + max_block_additional_id_(0), name_(NULL), number_(0), type_(0), @@ -535,6 +536,8 @@ uint64 Track::PayloadSize() const { size += EbmlElementSize(kMkvLanguage, language_); if (name_) size += EbmlElementSize(kMkvName, name_); + if (max_block_additional_id_) + size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); if (content_encoding_entries_size_ > 0) { uint64 content_encodings_size = 0; @@ -581,6 +584,8 @@ bool Track::Write(IMkvWriter* writer) const { size += EbmlElementSize(kMkvLanguage, language_); if (name_) size += EbmlElementSize(kMkvName, name_); + if (max_block_additional_id_) + size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); const int64 payload_position = writer->Position(); if (payload_position < 0) @@ -592,6 +597,11 @@ bool Track::Write(IMkvWriter* writer) const { return false; if (!WriteEbmlElement(writer, kMkvTrackType, type_)) return false; + if (max_block_additional_id_) + if (!WriteEbmlElement(writer, + kMkvMaxBlockAdditionID, + max_block_additional_id_)) + return false; if (codec_id_) { if (!WriteEbmlElement(writer, kMkvCodecID, codec_id_)) return false; @@ -719,6 +729,7 @@ VideoTrack::VideoTrack(unsigned int* seed) frame_rate_(0.0), height_(0), stereo_mode_(0), + alpha_mode_(0), width_(0) { } @@ -737,6 +748,15 @@ bool VideoTrack::SetStereoMode(uint64 stereo_mode) { return true; } +bool VideoTrack::SetAlphaMode(uint64 alpha_mode) { + if (alpha_mode != kNoAlpha && + alpha_mode != kAlpha) + return false; + + alpha_mode_ = alpha_mode; + return true; +} + uint64 VideoTrack::PayloadSize() const { const uint64 parent_size = Track::PayloadSize(); @@ -772,6 +792,9 @@ bool VideoTrack::Write(IMkvWriter* writer) const { if (stereo_mode_ > kMono) if (!WriteEbmlElement(writer, kMkvStereoMode, stereo_mode_)) return false; + if (alpha_mode_ > kNoAlpha) + if (!WriteEbmlElement(writer, kMkvAlphaMode, alpha_mode_)) + return false; if (frame_rate_ > 0.0) if (!WriteEbmlElement(writer, kMkvFrameRate, @@ -795,6 +818,8 @@ uint64 VideoTrack::VideoPayloadSize() const { size += EbmlElementSize(kMkvDisplayHeight, display_height_); if (stereo_mode_ > kMono) size += EbmlElementSize(kMkvStereoMode, stereo_mode_); + if (alpha_mode_ > kNoAlpha) + size += EbmlElementSize(kMkvAlphaMode, alpha_mode_); if (frame_rate_ > 0.0) size += EbmlElementSize(kMkvFrameRate, static_cast(frame_rate_)); @@ -1395,6 +1420,25 @@ bool Cluster::AddFrame(const uint8* frame, &WriteSimpleBlock); } +bool Cluster::AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 abs_timecode, + bool is_key) { + return DoWriteBlockWithAdditional(frame, + length, + additional, + additional_length, + add_id, + track_number, + abs_timecode, + is_key ? 1 : 0, + &WriteBlockWithAdditional); +} + bool Cluster::AddMetadata(const uint8* frame, uint64 length, uint64 track_number, @@ -1494,6 +1538,66 @@ bool Cluster::DoWriteBlock( return true; } +bool Cluster::DoWriteBlockWithAdditional( + const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 abs_timecode, + uint64 generic_arg, + WriteBlockAdditional write_block) { + if (frame == NULL || length == 0 || + 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) + 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 > kMaxBlockTimecode) + 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, + additional, + additional_length, + add_id, + 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; @@ -2100,6 +2204,75 @@ bool Segment::AddFrame(const uint8* frame, return true; } +bool Segment::AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 timestamp, + bool is_key) { + if (!frame || !additional) + 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 Frame(); + if (!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); + + if (!QueueFrame(new_frame)) + return false; + + 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) + 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::AddMetadata(const uint8* frame, uint64 length, uint64 track_number, diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp index 1a40b14..37c57fa 100644 --- a/mkvmuxer.hpp +++ b/mkvmuxer.hpp @@ -296,6 +296,10 @@ class Track { const uint8* codec_private() const { return codec_private_; } void set_language(const char* language); const char* language() const { return language_; } + void set_max_block_additional_id(uint64 max_block_additional_id) { + max_block_additional_id_ = max_block_additional_id; + } + uint64 max_block_additional_id() const { return max_block_additional_id_; } void set_name(const char* name); const char* name() const { return name_; } void set_number(uint64 number) { number_ = number; } @@ -314,6 +318,7 @@ class Track { char* codec_id_; uint8* codec_private_; char* language_; + uint64 max_block_additional_id_; char* name_; uint64 number_; uint64 type_; @@ -344,6 +349,11 @@ class VideoTrack : public Track { kSideBySideRightIsFirst = 11 }; + enum AlphaMode { + kNoAlpha = 0, + kAlpha = 1 + }; + // The |seed| parameter is used to synthesize a UID for the track. explicit VideoTrack(unsigned int* seed); virtual ~VideoTrack(); @@ -358,6 +368,9 @@ class VideoTrack : public Track { // Sets the video's stereo mode. Returns true on success. bool SetStereoMode(uint64 stereo_mode); + // Sets the video's alpha mode. Returns true on success. + bool SetAlphaMode(uint64 alpha_mode); + void set_display_height(uint64 height) { display_height_ = height; } uint64 display_height() const { return display_height_; } void set_display_width(uint64 width) { display_width_ = width; } @@ -367,6 +380,7 @@ class VideoTrack : public Track { void set_height(uint64 height) { height_ = height; } uint64 height() const { return height_; } uint64 stereo_mode() { return stereo_mode_; } + uint64 alpha_mode() { return alpha_mode_; } void set_width(uint64 width) { width_ = width; } uint64 width() const { return width_; } @@ -380,6 +394,7 @@ class VideoTrack : public Track { double frame_rate_; uint64 height_; uint64 stereo_mode_; + uint64 alpha_mode_; uint64 width_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack); @@ -661,6 +676,28 @@ class Cluster { uint64 timecode, // timecode units (absolute) 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 + // additional: Pointer to the additional data + // additional_length: Length of the additional data + // add_id: Value of BlockAddID element + // 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 AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 abs_timecode, + bool is_key); + // Writes a frame of metadata to the output medium; returns true on // success. // Inputs: @@ -706,6 +743,18 @@ class Cluster { int64 timecode, uint64 generic_arg); + // Signature that matches WriteBlockWithAdditional + // in the muxer utilities package. + typedef uint64 (*WriteBlockAdditional)(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 add_id, + uint64 additional_length, + uint64 track_number, + int64 timecode, + uint64 is_key); + // Used to implement AddFrame and AddMetadata. bool DoWriteBlock(const uint8* frame, uint64 length, @@ -714,6 +763,17 @@ class Cluster { uint64 generic_arg, WriteBlock write_block); + // Used to implement AddFrameWithAdditional + bool DoWriteBlockWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 absolute_timecode, + uint64 generic_arg, + WriteBlockAdditional write_block); + // Outputs the Cluster header to |writer_|. Returns true on success. bool WriteClusterHeader(); @@ -908,6 +968,15 @@ class Segment { uint64 timestamp_ns, uint64 duration_ns); + bool AddFrameWithAdditional(const uint8* frame, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + uint64 timestamp, + bool is_key); + // Adds a video track to the segment. Returns the number of the track on // success, 0 on error. |number| is the number to use for the video track. // |number| must be >= 0. If |number| == 0 then the muxer will decide on diff --git a/mkvmuxerutil.cpp b/mkvmuxerutil.cpp index 2419093..ed87b7d 100644 --- a/mkvmuxerutil.cpp +++ b/mkvmuxerutil.cpp @@ -464,6 +464,95 @@ uint64 WriteMetadataBlock(IMkvWriter* writer, return blockg_elem_size; } +// Writes a WebM Block with Additional. The structure is as follows +// Indentation shows sub-levels +// BlockGroup +// Block +// Data +// BlockAdditions +// BlockMore +// BlockAddID +// 1 (Denotes Alpha) +// BlockAdditional +// Data +uint64 WriteBlockWithAdditional(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + uint64 track_number, + int64 timecode, + uint64 is_key) { + if (!data || !additional || length < 1 || additional_length < 1) + return 0; + + const uint64 block_payload_size = 4 + length; + const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock, + block_payload_size) + + block_payload_size; + const uint64 block_additional_elem_size = EbmlElementSize(kMkvBlockAdditional, + additional, + additional_length); + const uint64 block_addid_elem_size = EbmlElementSize(kMkvBlockAddID, add_id); + + const uint64 block_more_payload_size = block_addid_elem_size + + block_additional_elem_size; + const uint64 block_more_elem_size = EbmlMasterElementSize( + kMkvBlockMore, + block_more_payload_size) + + block_more_payload_size; + const uint64 block_additions_payload_size = block_more_elem_size; + const uint64 block_additions_elem_size = EbmlMasterElementSize( + kMkvBlockMore, + block_additions_payload_size) + + block_additions_payload_size; + const uint64 block_group_payload_size = block_elem_size + + block_additions_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 (!WriteEbmlMasterElement(writer, kMkvBlockAdditions, + block_additions_payload_size)) + return 0; + + if (!WriteEbmlMasterElement(writer, kMkvBlockMore, block_more_payload_size)) + return 0; + + if (!WriteEbmlElement(writer, kMkvBlockAddID, add_id)) + return 0; + + if (!WriteEbmlElement(writer, kMkvBlockAdditional, + additional, additional_length)) + return 0; + + return block_group_elem_size; +} + uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) { if (!writer) return false; diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp index e1dfb74..671d387 100644 --- a/mkvmuxerutil.hpp +++ b/mkvmuxerutil.hpp @@ -88,6 +88,29 @@ uint64 WriteMetadataBlock(IMkvWriter* writer, int64 timecode, uint64 duration_timecode); +// Output an Mkv Block with BlockAdditional data. +// Inputs: +// data: Pointer to the data. +// length: Length of the data. +// additional: Pointer to the additional data +// additional_length: Length of the additional data. +// add_id: Value of BlockAddID element. +// 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 WriteBlockWithAdditional(IMkvWriter* writer, + const uint8* data, + uint64 length, + const uint8* additional, + uint64 additional_length, + uint64 add_id, + 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/webmids.hpp b/webmids.hpp index a3a14f2..fe02926 100644 --- a/webmids.hpp +++ b/webmids.hpp @@ -53,6 +53,10 @@ enum MkvId { kMkvReferenceBlock = 0xFB, kMkvLaceNumber = 0xCC, kMkvSimpleBlock = 0xA3, + kMkvBlockAdditions = 0x75A1, + kMkvBlockMore = 0xA6, + kMkvBlockAddID = 0xEE, + kMkvBlockAdditional = 0xA5, //Track kMkvTracks = 0x1654AE6B, kMkvTrackEntry = 0xAE, @@ -69,10 +73,12 @@ enum MkvId { kMkvCodecID = 0x86, kMkvCodecPrivate = 0x63A2, kMkvCodecName = 0x258688, + kMkvMaxBlockAdditionID = 0x55EE, //video kMkvVideo = 0xE0, kMkvFlagInterlaced = 0x9A, kMkvStereoMode = 0x53B8, + kMkvAlphaMode = 0x53C0, kMkvPixelWidth = 0xB0, kMkvPixelHeight = 0xBA, kMkvPixelCropBottom = 0x54AA,