// Copyright (c) 2012 The WebM project authors. All Rights Reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. An additional intellectual property rights grant can be found // in the file PATENTS. All contributing project authors may // be found in the AUTHORS file in the root of the source tree. #ifndef MKVMUXER_HPP #define MKVMUXER_HPP #include #include "mkvmuxertypes.hpp" #include "webmids.hpp" // For a description of the WebM elements see // http://www.webmproject.org/code/specs/container/. namespace mkvparser { class IMkvReader; } // end namespace namespace mkvmuxer { class MkvWriter; class Segment; const uint64 kMaxTrackNumber = 126; /////////////////////////////////////////////////////////////// // Interface used by the mkvmuxer to write out the Mkv data. class IMkvWriter { public: // Writes out |len| bytes of |buf|. Returns 0 on success. virtual int32 Write(const void* buf, uint32 len) = 0; // Returns the offset of the output position from the beginning of the // output. virtual int64 Position() const = 0; // Set the current File position. Returns 0 on success. virtual int32 Position(int64 position) = 0; // Returns true if the writer is seekable. virtual bool Seekable() const = 0; // Element start notification. Called whenever an element identifier is about // to be written to the stream. |element_id| is the element identifier, and // |position| is the location in the WebM stream where the first octet of the // element identifier will be written. // Note: the |MkvId| enumeration in webmids.hpp defines element values. virtual void ElementStartNotify(uint64 element_id, int64 position) = 0; protected: IMkvWriter(); virtual ~IMkvWriter(); private: LIBWEBM_DISALLOW_COPY_AND_ASSIGN(IMkvWriter); }; // Writes out the EBML header for a WebM file. This function must be called // before any other libwebm writing functions are called. bool WriteEbmlHeader(IMkvWriter* writer, uint64 doc_type_version); // Deprecated. Writes out EBML header with doc_type_version as // kDefaultDocTypeVersion. Exists for backward compatibility. bool WriteEbmlHeader(IMkvWriter* writer); // Copies in Chunk from source to destination between the given byte positions bool ChunkedCopy(mkvparser::IMkvReader* source, IMkvWriter* dst, int64 start, int64 size); /////////////////////////////////////////////////////////////// // Class to hold data the will be written to a block. class Frame { public: Frame(); ~Frame(); // Sets this frame's contents based on |frame|. Returns true on success. On // failure, this frame's existing contents may be lost. bool CopyFrom(const Frame& frame); // Copies |frame| data into |frame_|. Returns true on success. bool Init(const uint8* frame, uint64 length); // Copies |additional| data into |additional_|. Returns true on success. bool AddAdditionalData(const uint8* additional, uint64 length, uint64 add_id); // Returns true if the frame has valid parameters. bool IsValid() const; // Returns true if the frame can be written as a SimpleBlock based on current // parameters. bool CanBeSimpleBlock() const; 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; } uint64 duration() const { return duration_; } const uint8* frame() const { return frame_; } void set_is_key(bool key) { is_key_ = key; } bool is_key() const { return is_key_; } uint64 length() const { return length_; } void set_track_number(uint64 track_number) { track_number_ = track_number; } uint64 track_number() const { return track_number_; } void set_timestamp(uint64 timestamp) { timestamp_ = timestamp; } uint64 timestamp() const { return timestamp_; } void set_discard_padding(int64 discard_padding) { discard_padding_ = discard_padding; } int64 discard_padding() const { return discard_padding_; } void set_reference_block_timestamp(int64 reference_block_timestamp); int64 reference_block_timestamp() const { return reference_block_timestamp_; } bool reference_block_timestamp_set() const { return reference_block_timestamp_set_; } private: // Id of the Additional data. uint64 add_id_; // Pointer to additional data. Owned by this class. uint8* additional_; // Length of the additional data. uint64 additional_length_; // Duration of the frame in nanoseconds. uint64 duration_; // Pointer to the data. Owned by this class. uint8* frame_; // Flag telling if the data should set the key flag of a block. bool is_key_; // Length of the data. uint64 length_; // Mkv track number the data is associated with. uint64 track_number_; // Timestamp of the data in nanoseconds. uint64 timestamp_; // Discard padding for the frame. int64 discard_padding_; // Reference block timestamp. int64 reference_block_timestamp_; // Flag indicating if |reference_block_timestamp_| has been set. bool reference_block_timestamp_set_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Frame); }; /////////////////////////////////////////////////////////////// // Class to hold one cue point in a Cues element. class CuePoint { public: CuePoint(); ~CuePoint(); // Returns the size in bytes for the entire CuePoint element. uint64 Size() const; // Output the CuePoint element to the writer. Returns true on success. bool Write(IMkvWriter* writer) const; void set_time(uint64 time) { time_ = time; } uint64 time() const { return time_; } void set_track(uint64 track) { track_ = track; } uint64 track() const { return track_; } void set_cluster_pos(uint64 cluster_pos) { cluster_pos_ = cluster_pos; } uint64 cluster_pos() const { return cluster_pos_; } void set_block_number(uint64 block_number) { block_number_ = block_number; } uint64 block_number() const { return block_number_; } void set_output_block_number(bool output_block_number) { output_block_number_ = output_block_number; } bool output_block_number() const { return output_block_number_; } private: // Returns the size in bytes for the payload of the CuePoint element. uint64 PayloadSize() const; // Absolute timecode according to the segment time base. uint64 time_; // The Track element associated with the CuePoint. uint64 track_; // The position of the Cluster containing the Block. uint64 cluster_pos_; // Number of the Block within the Cluster, starting from 1. uint64 block_number_; // If true the muxer will write out the block number for the cue if the // block number is different than the default of 1. Default is set to true. bool output_block_number_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(CuePoint); }; /////////////////////////////////////////////////////////////// // Cues element. class Cues { public: Cues(); ~Cues(); // Adds a cue point to the Cues element. Returns true on success. bool AddCue(CuePoint* cue); // Returns the cue point by index. Returns NULL if there is no cue point // match. CuePoint* GetCueByIndex(int32 index) const; // Returns the total size of the Cues element uint64 Size(); // Output the Cues element to the writer. Returns true on success. bool Write(IMkvWriter* writer) const; int32 cue_entries_size() const { return cue_entries_size_; } void set_output_block_number(bool output_block_number) { output_block_number_ = output_block_number; } bool output_block_number() const { return output_block_number_; } private: // Number of allocated elements in |cue_entries_|. int32 cue_entries_capacity_; // Number of CuePoints in |cue_entries_|. int32 cue_entries_size_; // CuePoint list. CuePoint** cue_entries_; // If true the muxer will write out the block number for the cue if the // block number is different than the default of 1. Default is set to true. bool output_block_number_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cues); }; /////////////////////////////////////////////////////////////// // ContentEncAESSettings element class ContentEncAESSettings { public: enum { kCTR = 1 }; ContentEncAESSettings(); ~ContentEncAESSettings() {} // Returns the size in bytes for the ContentEncAESSettings element. uint64 Size() const; // Writes out the ContentEncAESSettings element to |writer|. Returns true on // success. bool Write(IMkvWriter* writer) const; uint64 cipher_mode() const { return cipher_mode_; } private: // Returns the size in bytes for the payload of the ContentEncAESSettings // element. uint64 PayloadSize() const; // Sub elements uint64 cipher_mode_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncAESSettings); }; /////////////////////////////////////////////////////////////// // ContentEncoding element // Elements used to describe if the track data has been encrypted or // compressed with zlib or header stripping. // Currently only whole frames can be encrypted with AES. This dictates that // ContentEncodingOrder will be 0, ContentEncodingScope will be 1, // ContentEncodingType will be 1, and ContentEncAlgo will be 5. class ContentEncoding { public: ContentEncoding(); ~ContentEncoding(); // Sets the content encryption id. Copies |length| bytes from |id| to // |enc_key_id_|. Returns true on success. bool SetEncryptionID(const uint8* id, uint64 length); // Returns the size in bytes for the ContentEncoding element. uint64 Size() const; // Writes out the ContentEncoding element to |writer|. Returns true on // success. bool Write(IMkvWriter* writer) const; uint64 enc_algo() const { return enc_algo_; } uint64 encoding_order() const { return encoding_order_; } uint64 encoding_scope() const { return encoding_scope_; } uint64 encoding_type() const { return encoding_type_; } ContentEncAESSettings* enc_aes_settings() { return &enc_aes_settings_; } private: // Returns the size in bytes for the encoding elements. uint64 EncodingSize(uint64 compresion_size, uint64 encryption_size) const; // Returns the size in bytes for the encryption elements. uint64 EncryptionSize() const; // Track element names uint64 enc_algo_; uint8* enc_key_id_; uint64 encoding_order_; uint64 encoding_scope_; uint64 encoding_type_; // ContentEncAESSettings element. ContentEncAESSettings enc_aes_settings_; // Size of the ContentEncKeyID data in bytes. uint64 enc_key_id_length_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); }; /////////////////////////////////////////////////////////////// // Colour element. struct PrimaryChromaticity { PrimaryChromaticity(float x_val, float y_val) : x(x_val), y(y_val) {} PrimaryChromaticity() : x(0), y(0) {} ~PrimaryChromaticity() {} uint64 PrimaryChromaticityPayloadSize(MkvId x_id, MkvId y_id) const; bool Write(IMkvWriter* writer, MkvId x_id, MkvId y_id) const; float x; float y; }; class MasteringMetadata { public: static const float kUnspecifiedMmValue; MasteringMetadata() : luminance_max(kUnspecifiedMmValue), luminance_min(kUnspecifiedMmValue), r_(NULL), g_(NULL), b_(NULL), white_point_(NULL) {} ~MasteringMetadata() { delete r_; delete g_; delete b_; delete white_point_; } uint64 MasteringMetadataPayloadSize() const; bool Write(IMkvWriter* writer) const; // Copies non-null chromaticity. bool SetChromaticity(const PrimaryChromaticity* r, const PrimaryChromaticity* g, const PrimaryChromaticity* b, const PrimaryChromaticity* white_point); const PrimaryChromaticity* r() const { return r_; } const PrimaryChromaticity* g() const { return g_; } const PrimaryChromaticity* b() const { return b_; } const PrimaryChromaticity* white_point() const { return white_point_; } float luminance_max; float luminance_min; private: PrimaryChromaticity* r_; PrimaryChromaticity* g_; PrimaryChromaticity* b_; PrimaryChromaticity* white_point_; }; class Colour { public: static const uint64 kUnspecifiedColourValue; Colour() : matrix(kUnspecifiedColourValue), bits_per_channel(kUnspecifiedColourValue), chroma_subsampling(kUnspecifiedColourValue), chroma_siting_horz(kUnspecifiedColourValue), chroma_siting_vert(kUnspecifiedColourValue), range(kUnspecifiedColourValue), transfer_function(kUnspecifiedColourValue), primaries(kUnspecifiedColourValue), max_cll(kUnspecifiedColourValue), max_fall(kUnspecifiedColourValue), mastering_metadata_(NULL) {} ~Colour() { delete mastering_metadata_; } uint64 ColourPayloadSize() const; bool Write(IMkvWriter* writer) const; // Deep copies |mastering_metadata|. bool SetMasteringMetadata(const MasteringMetadata& mastering_metadata); const MasteringMetadata* mastering_metadata() const { return mastering_metadata_; } uint64 matrix; uint64 bits_per_channel; uint64 chroma_subsampling; uint64 chroma_siting_horz; uint64 chroma_siting_vert; uint64 range; uint64 transfer_function; uint64 primaries; uint64 max_cll; uint64 max_fall; private: MasteringMetadata* mastering_metadata_; }; /////////////////////////////////////////////////////////////// // Track element. class Track { public: // The |seed| parameter is used to synthesize a UID for the track. explicit Track(unsigned int* seed); virtual ~Track(); // Adds a ContentEncoding element to the Track. Returns true on success. virtual bool AddContentEncoding(); // Returns the ContentEncoding by index. Returns NULL if there is no // ContentEncoding match. ContentEncoding* GetContentEncodingByIndex(uint32 index) const; // Returns the size in bytes for the payload of the Track element. virtual uint64 PayloadSize() const; // Returns the size in bytes of the Track element. virtual uint64 Size() const; // Output the Track element to the writer. Returns true on success. virtual bool Write(IMkvWriter* writer) const; // Sets the CodecPrivate element of the Track element. Copies |length| // bytes from |codec_private| to |codec_private_|. Returns true on success. bool SetCodecPrivate(const uint8* codec_private, uint64 length); void set_codec_id(const char* codec_id); const char* codec_id() const { return codec_id_; } 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; } uint64 number() const { return number_; } void set_type(uint64 type) { type_ = type; } uint64 type() const { return type_; } void set_uid(uint64 uid) { uid_ = uid; } uint64 uid() const { return uid_; } void set_codec_delay(uint64 codec_delay) { codec_delay_ = codec_delay; } uint64 codec_delay() const { return codec_delay_; } void set_seek_pre_roll(uint64 seek_pre_roll) { seek_pre_roll_ = seek_pre_roll; } uint64 seek_pre_roll() const { return seek_pre_roll_; } void set_default_duration(uint64 default_duration) { default_duration_ = default_duration; } uint64 default_duration() const { return default_duration_; } uint64 codec_private_length() const { return codec_private_length_; } uint32 content_encoding_entries_size() const { return content_encoding_entries_size_; } private: // Track element names. char* codec_id_; uint8* codec_private_; char* language_; uint64 max_block_additional_id_; char* name_; uint64 number_; uint64 type_; uint64 uid_; uint64 codec_delay_; uint64 seek_pre_roll_; uint64 default_duration_; // Size of the CodecPrivate data in bytes. uint64 codec_private_length_; // ContentEncoding element list. ContentEncoding** content_encoding_entries_; // Number of ContentEncoding elements added. uint32 content_encoding_entries_size_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track); }; /////////////////////////////////////////////////////////////// // Track that has video specific elements. class VideoTrack : public Track { public: // Supported modes for stereo 3D. enum StereoMode { kMono = 0, kSideBySideLeftIsFirst = 1, kTopBottomRightIsFirst = 2, kTopBottomLeftIsFirst = 3, 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(); // Returns the size in bytes for the payload of the Track element plus the // video specific elements. virtual uint64 PayloadSize() const; // Output the VideoTrack element to the writer. Returns true on success. virtual bool Write(IMkvWriter* writer) const; // 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; } uint64 display_width() const { return display_width_; } void set_crop_left(uint64 crop_left) { crop_left_ = crop_left; } uint64 crop_left() const { return crop_left_; } void set_crop_right(uint64 crop_right) { crop_right_ = crop_right; } uint64 crop_right() const { return crop_right_; } void set_crop_top(uint64 crop_top) { crop_top_ = crop_top; } uint64 crop_top() const { return crop_top_; } void set_crop_bottom(uint64 crop_bottom) { crop_bottom_ = crop_bottom; } uint64 crop_bottom() const { return crop_bottom_; } void set_frame_rate(double frame_rate) { frame_rate_ = frame_rate; } double frame_rate() const { return frame_rate_; } 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_; } Colour* colour() { return colour_; } // Deep copies |colour|. bool SetColour(const Colour& colour); private: // Returns the size in bytes of the Video element. uint64 VideoPayloadSize() const; // Video track element names. uint64 display_height_; uint64 display_width_; uint64 crop_left_; uint64 crop_right_; uint64 crop_top_; uint64 crop_bottom_; double frame_rate_; uint64 height_; uint64 stereo_mode_; uint64 alpha_mode_; uint64 width_; Colour* colour_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack); }; /////////////////////////////////////////////////////////////// // Track that has audio specific elements. class AudioTrack : public Track { public: // The |seed| parameter is used to synthesize a UID for the track. explicit AudioTrack(unsigned int* seed); virtual ~AudioTrack(); // Returns the size in bytes for the payload of the Track element plus the // audio specific elements. virtual uint64 PayloadSize() const; // Output the AudioTrack element to the writer. Returns true on success. virtual bool Write(IMkvWriter* writer) const; void set_bit_depth(uint64 bit_depth) { bit_depth_ = bit_depth; } uint64 bit_depth() const { return bit_depth_; } void set_channels(uint64 channels) { channels_ = channels; } uint64 channels() const { return channels_; } void set_sample_rate(double sample_rate) { sample_rate_ = sample_rate; } double sample_rate() const { return sample_rate_; } private: // Audio track element names. uint64 bit_depth_; uint64 channels_; double sample_rate_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(AudioTrack); }; /////////////////////////////////////////////////////////////// // Tracks element class Tracks { public: // Audio and video type defined by the Matroska specs. enum { kVideo = 0x1, kAudio = 0x2 }; static const char kOpusCodecId[]; static const char kVorbisCodecId[]; static const char kVp8CodecId[]; static const char kVp9CodecId[]; static const char kVp10CodecId[]; Tracks(); ~Tracks(); // Adds a Track element to the Tracks object. |track| will be owned and // deleted by the Tracks object. Returns true on success. |number| is the // number to use for the track. |number| must be >= 0. If |number| == 0 // then the muxer will decide on the track number. bool AddTrack(Track* track, int32 number); // Returns the track by index. Returns NULL if there is no track match. const Track* GetTrackByIndex(uint32 idx) const; // Search the Tracks and return the track that matches |tn|. Returns NULL // if there is no track match. Track* GetTrackByNumber(uint64 track_number) const; // Returns true if the track number is an audio track. bool TrackIsAudio(uint64 track_number) const; // Returns true if the track number is a video track. bool TrackIsVideo(uint64 track_number) const; // Output the Tracks element to the writer. Returns true on success. bool Write(IMkvWriter* writer) const; uint32 track_entries_size() const { return track_entries_size_; } private: // Track element list. Track** track_entries_; // Number of Track elements added. uint32 track_entries_size_; // Whether or not Tracks element has already been written via IMkvWriter. mutable bool wrote_tracks_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks); }; /////////////////////////////////////////////////////////////// // Chapter element // class Chapter { public: // Set the identifier for this chapter. (This corresponds to the // Cue Identifier line in WebVTT.) // TODO(matthewjheaney): the actual serialization of this item in // MKV is pending. bool set_id(const char* id); // Converts the nanosecond start and stop times of this chapter to // their corresponding timecode values, and stores them that way. void set_time(const Segment& segment, uint64 start_time_ns, uint64 end_time_ns); // Sets the uid for this chapter. Primarily used to enable // deterministic output from the muxer. void set_uid(const uint64 uid) { uid_ = uid; } // Add a title string to this chapter, per the semantics described // here: // http://www.matroska.org/technical/specs/index.html // // The title ("chapter string") is a UTF-8 string. // // The language has ISO 639-2 representation, described here: // http://www.loc.gov/standards/iso639-2/englangn.html // http://www.loc.gov/standards/iso639-2/php/English_list.php // If you specify NULL as the language value, this implies // English ("eng"). // // The country value corresponds to the codes listed here: // http://www.iana.org/domains/root/db/ // // The function returns false if the string could not be allocated. bool add_string(const char* title, const char* language, const char* country); private: friend class Chapters; // For storage of chapter titles that differ by language. class Display { public: // Establish representation invariant for new Display object. void Init(); // Reclaim resources, in anticipation of destruction. void Clear(); // Copies the title to the |title_| member. Returns false on // error. bool set_title(const char* title); // Copies the language to the |language_| member. Returns false // on error. bool set_language(const char* language); // Copies the country to the |country_| member. Returns false on // error. bool set_country(const char* country); // If |writer| is non-NULL, serialize the Display sub-element of // the Atom into the stream. Returns the Display element size on // success, 0 if error. uint64 WriteDisplay(IMkvWriter* writer) const; private: char* title_; char* language_; char* country_; }; Chapter(); ~Chapter(); // Establish the representation invariant for a newly-created // Chapter object. The |seed| parameter is used to create the UID // for this chapter atom. void Init(unsigned int* seed); // Copies this Chapter object to a different one. This is used when // expanding a plain array of Chapter objects (see Chapters). void ShallowCopy(Chapter* dst) const; // Reclaim resources used by this Chapter object, pending its // destruction. void Clear(); // If there is no storage remaining on the |displays_| array for a // new display object, creates a new, longer array and copies the // existing Display objects to the new array. Returns false if the // array cannot be expanded. bool ExpandDisplaysArray(); // If |writer| is non-NULL, serialize the Atom sub-element into the // stream. Returns the total size of the element on success, 0 if // error. uint64 WriteAtom(IMkvWriter* writer) const; // The string identifier for this chapter (corresponds to WebVTT cue // identifier). char* id_; // Start timecode of the chapter. uint64 start_timecode_; // Stop timecode of the chapter. uint64 end_timecode_; // The binary identifier for this chapter. uint64 uid_; // The Atom element can contain multiple Display sub-elements, as // the same logical title can be rendered in different languages. Display* displays_; // The physical length (total size) of the |displays_| array. int displays_size_; // The logical length (number of active elements) on the |displays_| // array. int displays_count_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter); }; /////////////////////////////////////////////////////////////// // Chapters element // class Chapters { public: Chapters(); ~Chapters(); Chapter* AddChapter(unsigned int* seed); // Returns the number of chapters that have been added. int Count() const; // Output the Chapters element to the writer. Returns true on success. bool Write(IMkvWriter* writer) const; private: // Expands the chapters_ array if there is not enough space to contain // another chapter object. Returns true on success. bool ExpandChaptersArray(); // If |writer| is non-NULL, serialize the Edition sub-element of the // Chapters element into the stream. Returns the Edition element // size on success, 0 if error. uint64 WriteEdition(IMkvWriter* writer) const; // Total length of the chapters_ array. int chapters_size_; // Number of active chapters on the chapters_ array. int chapters_count_; // Array for storage of chapter objects. Chapter* chapters_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters); }; /////////////////////////////////////////////////////////////// // Tag element // class Tag { public: bool add_simple_tag(const char* tag_name, const char* tag_string); private: // Tags calls Clear and the destructor of Tag friend class Tags; // For storage of simple tags class SimpleTag { public: // Establish representation invariant for new SimpleTag object. void Init(); // Reclaim resources, in anticipation of destruction. void Clear(); // Copies the title to the |tag_name_| member. Returns false on // error. bool set_tag_name(const char* tag_name); // Copies the language to the |tag_string_| member. Returns false // on error. bool set_tag_string(const char* tag_string); // If |writer| is non-NULL, serialize the SimpleTag sub-element of // the Atom into the stream. Returns the SimpleTag element size on // success, 0 if error. uint64 Write(IMkvWriter* writer) const; private: char* tag_name_; char* tag_string_; }; Tag(); ~Tag(); // Copies this Tag object to a different one. This is used when // expanding a plain array of Tag objects (see Tags). void ShallowCopy(Tag* dst) const; // Reclaim resources used by this Tag object, pending its // destruction. void Clear(); // If there is no storage remaining on the |simple_tags_| array for a // new display object, creates a new, longer array and copies the // existing SimpleTag objects to the new array. Returns false if the // array cannot be expanded. bool ExpandSimpleTagsArray(); // If |writer| is non-NULL, serialize the Tag sub-element into the // stream. Returns the total size of the element on success, 0 if // error. uint64 Write(IMkvWriter* writer) const; // The Atom element can contain multiple SimpleTag sub-elements SimpleTag* simple_tags_; // The physical length (total size) of the |simple_tags_| array. int simple_tags_size_; // The logical length (number of active elements) on the |simple_tags_| // array. int simple_tags_count_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tag); }; /////////////////////////////////////////////////////////////// // Tags element // class Tags { public: Tags(); ~Tags(); Tag* AddTag(); // Returns the number of tags that have been added. int Count() const; // Output the Tags element to the writer. Returns true on success. bool Write(IMkvWriter* writer) const; private: // Expands the tags_ array if there is not enough space to contain // another tag object. Returns true on success. bool ExpandTagsArray(); // Total length of the tags_ array. int tags_size_; // Number of active tags on the tags_ array. int tags_count_; // Array for storage of tag objects. Tag* tags_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tags); }; /////////////////////////////////////////////////////////////// // Cluster element // // Notes: // |Init| must be called before any other method in this class. class Cluster { public: // |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(); bool Init(IMkvWriter* ptr_writer); // Adds a frame to be output in the file. The frame is written out through // |writer_| if successful. Returns true on success. bool AddFrame(const Frame* frame); // Adds a frame to be output in the file. The frame is written out through // |writer_| if successful. Returns true on success. // Inputs: // data: Pointer to the data // length: Length of the data // track_number: Track to add the data to. Value returned by Add track // 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* data, uint64 length, uint64 track_number, 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: // 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. 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* data, uint64 length, const uint8* additional, uint64 additional_length, uint64 add_id, uint64 track_number, 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: // data: 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* data, 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: // data: Pointer to the data // length: Length of the data // track_number: Track to add the data to. Value returned by Add track // functions. The range of allowed values is [1, 126]. // timecode: Absolute (not relative to cluster) timestamp of the // metadata frame, expressed in timecode units. // duration: Duration of metadata frame, in timecode units. // // The metadata frame is written as a block group, with a duration // sub-element but no reference time sub-elements (indicating that // it is considered a keyframe, per Matroska semantics). bool AddMetadata(const uint8* data, uint64 length, uint64 track_number, uint64 timecode, uint64 duration); // Increments the size of the cluster's data in bytes. 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. bool Finalize(); // Returns the size in bytes for the entire Cluster element. uint64 Size() 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; int64 size_position() const { return size_position_; } int32 blocks_added() const { return blocks_added_; } uint64 payload_size() const { return payload_size_; } int64 position_for_cues() const { return position_for_cues_; } uint64 timecode() const { return timecode_; } uint64 timecode_scale() const { return timecode_scale_; } private: // 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. bool PreWriteBlock(); // Utility method used by the |DoWriteFrame*| methods that handles the book // keeping required after each block is written. void PostWriteBlock(uint64 element_size); // Does some verification and calls WriteFrame. bool DoWriteFrame(const Frame* const frame); // Outputs the Cluster header to |writer_|. Returns true on success. bool WriteClusterHeader(); // Number of blocks added to the cluster. int32 blocks_added_; // Flag telling if the cluster has been closed. bool finalized_; // Flag telling if the cluster's header has been written. bool header_written_; // The size of the cluster elements in bytes. uint64 payload_size_; // The file position used for cue points. const int64 position_for_cues_; // The file position of the cluster's size element. int64 size_position_; // The absolute timecode of the cluster. const uint64 timecode_; // The timecode scale of the Segment containing the cluster. const uint64 timecode_scale_; // Pointer to the writer object. Not owned by this class. IMkvWriter* writer_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cluster); }; /////////////////////////////////////////////////////////////// // SeekHead element class SeekHead { public: SeekHead(); ~SeekHead(); // TODO(fgalligan): Change this to reserve a certain size. Then check how // big the seek entry to be added is as not every seek entry will be the // maximum size it could be. // Adds a seek entry to be written out when the element is finalized. |id| // must be the coded mkv element id. |pos| is the file position of the // element. Returns true on success. bool AddSeekEntry(uint32 id, uint64 pos); // Writes out SeekHead and SeekEntry elements. Returns true on success. bool Finalize(IMkvWriter* writer) const; // Returns the id of the Seek Entry at the given index. Returns -1 if index is // out of range. uint32 GetId(int index) const; // Returns the position of the Seek Entry at the given index. Returns -1 if // index is out of range. uint64 GetPosition(int index) const; // Sets the Seek Entry id and position at given index. // Returns true on success. bool SetSeekEntry(int index, uint32 id, uint64 position); // Reserves space by writing out a Void element which will be updated with // a SeekHead element later. Returns true on success. bool Write(IMkvWriter* writer); // We are going to put a cap on the number of Seek Entries. const static int32 kSeekEntryCount = 5; private: // Returns the maximum size in bytes of one seek entry. uint64 MaxEntrySize() const; // Seek entry id element list. uint32 seek_entry_id_[kSeekEntryCount]; // Seek entry pos element list. uint64 seek_entry_pos_[kSeekEntryCount]; // The file position of SeekHead element. int64 start_pos_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SeekHead); }; /////////////////////////////////////////////////////////////// // Segment Information element class SegmentInfo { public: SegmentInfo(); ~SegmentInfo(); // Will update the duration if |duration_| is > 0.0. Returns true on success. bool Finalize(IMkvWriter* writer) const; // Sets |muxing_app_| and |writing_app_|. bool Init(); // Output the Segment Information element to the writer. Returns true on // success. bool Write(IMkvWriter* writer); void set_duration(double duration) { duration_ = duration; } double duration() const { return duration_; } void set_muxing_app(const char* app); const char* muxing_app() const { return muxing_app_; } void set_timecode_scale(uint64 scale) { timecode_scale_ = scale; } uint64 timecode_scale() const { return timecode_scale_; } void set_writing_app(const char* app); const char* writing_app() const { return writing_app_; } void set_date_utc(int64 date_utc) { date_utc_ = date_utc; } int64 date_utc() const { return date_utc_; } private: // Segment Information element names. // Initially set to -1 to signify that a duration has not been set and should // not be written out. double duration_; // Set to libwebm-%d.%d.%d.%d, major, minor, build, revision. char* muxing_app_; uint64 timecode_scale_; // Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision. char* writing_app_; // LLONG_MIN when DateUTC is not set. int64 date_utc_; // The file position of the duration element. int64 duration_pos_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SegmentInfo); }; /////////////////////////////////////////////////////////////// // This class represents the main segment in a WebM file. Currently only // supports one Segment element. // // Notes: // |Init| must be called before any other method in this class. class Segment { public: enum Mode { kLive = 0x1, kFile = 0x2 }; enum CuesPosition { kAfterClusters = 0x0, // Position Cues after Clusters - Default kBeforeClusters = 0x1 // Position Cues before Clusters }; const static uint32 kDefaultDocTypeVersion = 2; const static uint64 kDefaultMaxClusterDuration = 30000000000ULL; Segment(); ~Segment(); // Initializes |SegmentInfo| and returns result. Always returns false when // |ptr_writer| is NULL. bool Init(IMkvWriter* ptr_writer); // Adds a generic track to the segment. Returns the newly-allocated // track object (which is owned by the segment) on success, NULL on // error. |number| is the number to use for the track. |number| // must be >= 0. If |number| == 0 then the muxer will decide on the // track number. Track* AddTrack(int32 number); // Adds a Vorbis audio track to the segment. Returns the number of the track // on success, 0 on error. |number| is the number to use for the audio track. // |number| must be >= 0. If |number| == 0 then the muxer will decide on // the track number. uint64 AddAudioTrack(int32 sample_rate, int32 channels, int32 number); // Adds an empty chapter to the chapters of this segment. Returns // non-NULL on success. After adding the chapter, the caller should // populate its fields via the Chapter member functions. Chapter* AddChapter(); // Adds an empty tag to the tags of this segment. Returns // non-NULL on success. After adding the tag, the caller should // populate its fields via the Tag member functions. Tag* AddTag(); // Adds a cue point to the Cues element. |timestamp| is the time in // nanoseconds of the cue's time. |track| is the Track of the Cue. This // function must be called after AddFrame to calculate the correct // BlockNumber for the CuePoint. Returns true on success. bool AddCuePoint(uint64 timestamp, uint64 track); // Adds a frame to be output in the file. Returns true on success. // Inputs: // data: Pointer to the data // length: Length of the data // track_number: Track to add the data to. Value returned by Add track // functions. // timestamp: Timestamp of the frame in nanoseconds from 0. // is_key: Flag telling whether or not this frame is a key frame. bool AddFrame(const uint8* data, uint64 length, uint64 track_number, uint64 timestamp_ns, bool is_key); // Writes a frame of metadata to the output medium; returns true on // success. // Inputs: // data: Pointer to the data // length: Length of the data // track_number: Track to add the data to. Value returned by Add track // functions. // timecode: Absolute timestamp of the metadata frame, expressed // in nanosecond units. // duration: Duration of metadata frame, in nanosecond units. // // The metadata frame is written as a block group, with a duration // sub-element but no reference time sub-elements (indicating that // it is considered a keyframe, per Matroska semantics). bool AddMetadata(const uint8* data, uint64 length, uint64 track_number, uint64 timestamp_ns, uint64 duration_ns); // Writes a frame with additional data to the output medium; returns true on // success. // Inputs: // data: Pointer to the data. // length: Length of the data. // additional: Pointer to additional data. // additional_length: Length of additional data. // add_id: Additional ID which identifies the type of additional data. // 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 AddFrameWithAdditional(const uint8* data, uint64 length, const uint8* additional, uint64 additional_length, uint64 add_id, uint64 track_number, uint64 timestamp, bool is_key); // Writes a frame with DiscardPadding to the output medium; returns true on // success. // Inputs: // data: 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* data, 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: // frame: frame object bool AddGenericFrame(const Frame* frame); // Adds a VP8 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 // the track number. uint64 AddVideoTrack(int32 width, int32 height, int32 number); // This function must be called after Finalize() if you need a copy of the // output with Cues written before the Clusters. It will return false if the // writer is not seekable of if chunking is set to true. // Input parameters: // reader - an IMkvReader object created with the same underlying file of the // current writer object. Make sure to close the existing writer // object before creating this so that all the data is properly // flushed and available for reading. // writer - an IMkvWriter object pointing to a *different* file than the one // pointed by the current writer object. This file will contain the // Cues element before the Clusters. bool CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, IMkvWriter* writer); // Sets which track to use for the Cues element. Must have added the track // before calling this function. Returns true on success. |track_number| is // returned by the Add track functions. bool CuesTrack(uint64 track_number); // This will force the muxer to create a new Cluster when the next frame is // added. void ForceNewClusterOnNextFrame(); // Writes out any frames that have not been written out. Finalizes the last // cluster. May update the size and duration of the segment. May output the // Cues element. May finalize the SeekHead element. Returns true on success. bool Finalize(); // Returns the Cues object. Cues* GetCues() { return &cues_; } // Returns the Segment Information object. const SegmentInfo* GetSegmentInfo() const { return &segment_info_; } SegmentInfo* GetSegmentInfo() { return &segment_info_; } // Search the Tracks and return the track that matches |track_number|. // Returns NULL if there is no track match. Track* GetTrackByNumber(uint64 track_number) const; // Toggles whether to output a cues element. void OutputCues(bool output_cues); // 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 // |filename|.hdr and the data chunks will be named // |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing // to files so the muxer will use the default MkvWriter class to control // what data is written to what files. Returns true on success. // TODO: Should we change the IMkvWriter Interface to add Open and Close? // That will force the interface to be dependent on files. bool SetChunking(bool chunking, const char* filename); bool chunking() const { return chunking_; } uint64 cues_track() const { return cues_track_; } void set_max_cluster_duration(uint64 max_cluster_duration) { max_cluster_duration_ = max_cluster_duration; } uint64 max_cluster_duration() const { return max_cluster_duration_; } void set_max_cluster_size(uint64 max_cluster_size) { max_cluster_size_ = max_cluster_size; } uint64 max_cluster_size() const { return max_cluster_size_; } void set_mode(Mode mode) { mode_ = mode; } Mode mode() const { return mode_; } CuesPosition cues_position() const { return cues_position_; } bool output_cues() const { return output_cues_; } const SegmentInfo* segment_info() const { return &segment_info_; } private: // Checks if header information has been output and initialized. If not it // will output the Segment element and initialize the SeekHead elment and // Cues elements. bool CheckHeaderInfo(); // Sets |doc_type_version_| based on the current element requirements. void UpdateDocTypeVersion(); // Sets |name| according to how many chunks have been written. |ext| is the // file extension. |name| must be deleted by the calling app. Returns true // on success. bool UpdateChunkName(const char* ext, char** name) const; // Returns the maximum offset within the segment's payload. When chunking // this function is needed to determine offsets of elements within the // chunked files. Returns -1 on error. int64 MaxOffset(); // Adds the frame to our frame array. bool QueueFrame(Frame* frame); // Output all frames that are queued. Returns -1 on error, otherwise // it returns the number of frames written. int WriteFramesAll(); // Output all frames that are queued that have an end time that is less // then |timestamp|. Returns true on success and if there are no frames // queued. bool WriteFramesLessThan(uint64 timestamp); // Outputs the segment header, Segment Information element, SeekHead element, // and Tracks element to |writer_|. bool WriteSegmentHeader(); // Given a frame with the specified timestamp (nanosecond units) and // keyframe status, determine whether a new cluster should be // created, before writing enqueued frames and the frame itself. The // function returns one of the following values: // -1 = error: an out-of-order frame was detected // 0 = do not create a new cluster, and write frame to the existing cluster // 1 = create a new cluster, and write frame to that new cluster // 2 = create a new cluster, and re-run test int TestFrame(uint64 track_num, uint64 timestamp_ns, bool key) const; // Create a new cluster, using the earlier of the first enqueued // frame, or the indicated time. Returns true on success. bool MakeNewCluster(uint64 timestamp_ns); // Checks whether a new cluster needs to be created, and if so // creates a new cluster. Returns false if creation of a new cluster // was necessary but creation was not successful. bool DoNewClusterProcessing(uint64 track_num, uint64 timestamp_ns, bool key); // Adjusts Cue Point values (to place Cues before Clusters) so that they // reflect the correct offsets. void MoveCuesBeforeClusters(); // This function recursively computes the correct cluster offsets (this is // done to move the Cues before Clusters). It recursively updates the change // in size (which indicates a change in cluster offset) until no sizes change. // Parameters: // diff - indicates the difference in size of the Cues element that needs to // accounted for. // index - index in the list of Cues which is currently being adjusted. // cue_size - sum of size of all the CuePoint elements. void MoveCuesBeforeClustersHelper(uint64 diff, int index, uint64* cue_size); // Seeds the random number generator used to make UIDs. unsigned int seed_; // WebM elements Cues cues_; SeekHead seek_head_; SegmentInfo segment_info_; Tracks tracks_; Chapters chapters_; Tags tags_; // Number of chunks written. int chunk_count_; // Current chunk filename. char* chunk_name_; // Default MkvWriter object created by this class used for writing clusters // out in separate files. MkvWriter* chunk_writer_cluster_; // Default MkvWriter object created by this class used for writing Cues // element out to a file. MkvWriter* chunk_writer_cues_; // Default MkvWriter object created by this class used for writing the // Matroska header out to a file. MkvWriter* chunk_writer_header_; // Flag telling whether or not the muxer is chunking output to multiple // files. bool chunking_; // Base filename for the chunked files. char* chunking_base_name_; // File position offset where the Clusters end. int64 cluster_end_offset_; // List of clusters. Cluster** cluster_list_; // Number of cluster pointers allocated in the cluster list. int32 cluster_list_capacity_; // Number of clusters in the cluster list. int32 cluster_list_size_; // Indicates whether Cues should be written before or after Clusters CuesPosition cues_position_; // Track number that is associated with the cues element for this segment. uint64 cues_track_; // Tells the muxer to force a new cluster on the next Block. bool force_new_cluster_; // List of stored audio frames. These variables are used to store frames so // the muxer can follow the guideline "Audio blocks that contain the video // key frame's timecode should be in the same cluster as the video key frame // block." Frame** frames_; // Number of frame pointers allocated in the frame list. int32 frames_capacity_; // Number of frames in the frame list. int32 frames_size_; // Flag telling if a video track has been added to the segment. bool has_video_; // Flag telling if the segment's header has been written. bool header_written_; // Duration of the last block in nanoseconds. uint64 last_block_duration_; // Last timestamp in nanoseconds added to a cluster. uint64 last_timestamp_; // Last timestamp in nanoseconds by track number added to a cluster. uint64 last_track_timestamp_[kMaxTrackNumber]; // Maximum time in nanoseconds for a cluster duration. This variable is a // guideline and some clusters may have a longer duration. Default is 30 // seconds. uint64 max_cluster_duration_; // Maximum size in bytes for a cluster. This variable is a guideline and // some clusters may have a larger size. Default is 0 which signifies that // the muxer will decide the size. uint64 max_cluster_size_; // The mode that segment is in. If set to |kLive| the writer must not // seek backwards. Mode mode_; // Flag telling the muxer that a new cue point should be added. bool new_cuepoint_; // TODO(fgalligan): Should we add support for more than one Cues element? // Flag whether or not the muxer should output a Cues element. bool output_cues_; // The size of the EBML header, used to validate the header if // WriteEbmlHeader() is called more than once. int32 ebml_header_size_; // The file position of the segment's payload. int64 payload_pos_; // The file position of the element's size. int64 size_position_; // Current DocTypeVersion (|doc_type_version_|) and that written in // WriteSegmentHeader(). // WriteEbmlHeader() will be called from Finalize() if |doc_type_version_| // differs from |doc_type_version_written_|. uint32 doc_type_version_; uint32 doc_type_version_written_; // Pointer to the writer objects. Not owned by this class. IMkvWriter* writer_cluster_; IMkvWriter* writer_cues_; IMkvWriter* writer_header_; LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment); }; } // end namespace mkvmuxer #endif // MKVMUXER_HPP