|  | // | 
|  | // Copyright (c) 2016, Alliance for Open Media. All rights reserved | 
|  | // | 
|  | // This source code is subject to the terms of the BSD 2 Clause License and | 
|  | // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
|  | // was not distributed with this source code in the LICENSE file, you can | 
|  | // obtain it at www.aomedia.org/license/software. If the Alliance for Open | 
|  | // Media Patent License 1.0 was not distributed with this source code in the | 
|  | // PATENTS file, you can obtain it at www.aomedia.org/license/patent. | 
|  | // | 
|  | #include "mkvmuxer.hpp" | 
|  |  | 
|  | #include <climits> | 
|  | #include <cstdio> | 
|  | #include <cstdlib> | 
|  | #include <cstring> | 
|  | #include <ctime> | 
|  | #include <new> | 
|  |  | 
|  | #include "mkvmuxerutil.hpp" | 
|  | #include "mkvparser.hpp" | 
|  | #include "mkvwriter.hpp" | 
|  | #include "webmids.hpp" | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | // Disable MSVC warnings that suggest making code non-portable. | 
|  | #pragma warning(disable : 4996) | 
|  | #endif | 
|  |  | 
|  | namespace mkvmuxer { | 
|  |  | 
|  | namespace { | 
|  | // Deallocate the string designated by |dst|, and then copy the |src| | 
|  | // string to |dst|.  The caller owns both the |src| string and the | 
|  | // |dst| copy (hence the caller is responsible for eventually | 
|  | // deallocating the strings, either directly, or indirectly via | 
|  | // StrCpy).  Returns true if the source string was successfully copied | 
|  | // to the destination. | 
|  | bool StrCpy(const char* src, char** dst_ptr) { | 
|  | if (dst_ptr == NULL) | 
|  | return false; | 
|  |  | 
|  | char*& dst = *dst_ptr; | 
|  |  | 
|  | delete[] dst; | 
|  | dst = NULL; | 
|  |  | 
|  | if (src == NULL) | 
|  | return true; | 
|  |  | 
|  | const size_t size = strlen(src) + 1; | 
|  |  | 
|  | dst = new (std::nothrow) char[size];  // NOLINT | 
|  | if (dst == NULL) | 
|  | return false; | 
|  |  | 
|  | strcpy(dst, src);  // NOLINT | 
|  | return true; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // IMkvWriter Class | 
|  |  | 
|  | IMkvWriter::IMkvWriter() {} | 
|  |  | 
|  | IMkvWriter::~IMkvWriter() {} | 
|  |  | 
|  | bool WriteEbmlHeader(IMkvWriter* writer, uint64 doc_type_version) { | 
|  | // Level 0 | 
|  | uint64 size = EbmlElementSize(kMkvEBMLVersion, 1ULL); | 
|  | size += EbmlElementSize(kMkvEBMLReadVersion, 1ULL); | 
|  | size += EbmlElementSize(kMkvEBMLMaxIDLength, 4ULL); | 
|  | size += EbmlElementSize(kMkvEBMLMaxSizeLength, 8ULL); | 
|  | size += EbmlElementSize(kMkvDocType, "webm"); | 
|  | size += EbmlElementSize(kMkvDocTypeVersion, doc_type_version); | 
|  | size += EbmlElementSize(kMkvDocTypeReadVersion, 2ULL); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvEBML, size)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvEBMLVersion, 1ULL)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvEBMLReadVersion, 1ULL)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvEBMLMaxIDLength, 4ULL)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvEBMLMaxSizeLength, 8ULL)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvDocType, "webm")) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvDocTypeVersion, doc_type_version)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvDocTypeReadVersion, 2ULL)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool WriteEbmlHeader(IMkvWriter* writer) { | 
|  | return WriteEbmlHeader(writer, mkvmuxer::Segment::kDefaultDocTypeVersion); | 
|  | } | 
|  |  | 
|  | bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst, | 
|  | mkvmuxer::int64 start, int64 size) { | 
|  | // TODO(vigneshv): Check if this is a reasonable value. | 
|  | const uint32 kBufSize = 2048; | 
|  | uint8* buf = new uint8[kBufSize]; | 
|  | int64 offset = start; | 
|  | while (size > 0) { | 
|  | const int64 read_len = (size > kBufSize) ? kBufSize : size; | 
|  | if (source->Read(offset, static_cast<long>(read_len), buf)) | 
|  | return false; | 
|  | dst->Write(buf, static_cast<uint32>(read_len)); | 
|  | offset += read_len; | 
|  | size -= read_len; | 
|  | } | 
|  | delete[] buf; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Frame Class | 
|  |  | 
|  | Frame::Frame() | 
|  | : add_id_(0), | 
|  | additional_(NULL), | 
|  | additional_length_(0), | 
|  | duration_(0), | 
|  | frame_(NULL), | 
|  | is_key_(false), | 
|  | length_(0), | 
|  | track_number_(0), | 
|  | timestamp_(0), | 
|  | discard_padding_(0), | 
|  | reference_block_timestamp_(0), | 
|  | reference_block_timestamp_set_(false) {} | 
|  |  | 
|  | Frame::~Frame() { | 
|  | delete[] frame_; | 
|  | delete[] additional_; | 
|  | } | 
|  |  | 
|  | bool Frame::CopyFrom(const Frame& frame) { | 
|  | delete[] frame_; | 
|  | frame_ = NULL; | 
|  | length_ = 0; | 
|  | if (frame.length() > 0 && frame.frame() != NULL && | 
|  | !Init(frame.frame(), frame.length())) { | 
|  | return false; | 
|  | } | 
|  | add_id_ = 0; | 
|  | delete[] additional_; | 
|  | additional_ = NULL; | 
|  | additional_length_ = 0; | 
|  | if (frame.additional_length() > 0 && frame.additional() != NULL && | 
|  | !AddAdditionalData(frame.additional(), frame.additional_length(), | 
|  | frame.add_id())) { | 
|  | return false; | 
|  | } | 
|  | duration_ = frame.duration(); | 
|  | is_key_ = frame.is_key(); | 
|  | track_number_ = frame.track_number(); | 
|  | timestamp_ = frame.timestamp(); | 
|  | discard_padding_ = frame.discard_padding(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Frame::Init(const uint8* frame, uint64 length) { | 
|  | uint8* const data = | 
|  | new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT | 
|  | if (!data) | 
|  | return false; | 
|  |  | 
|  | delete[] frame_; | 
|  | frame_ = data; | 
|  | length_ = length; | 
|  |  | 
|  | memcpy(frame_, frame, static_cast<size_t>(length_)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Frame::AddAdditionalData(const uint8* additional, uint64 length, | 
|  | uint64 add_id) { | 
|  | uint8* const data = | 
|  | new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT | 
|  | if (!data) | 
|  | return false; | 
|  |  | 
|  | delete[] additional_; | 
|  | additional_ = data; | 
|  | additional_length_ = length; | 
|  | add_id_ = add_id; | 
|  |  | 
|  | memcpy(additional_, additional, static_cast<size_t>(additional_length_)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Frame::IsValid() const { | 
|  | if (length_ == 0 || !frame_) { | 
|  | return false; | 
|  | } | 
|  | if ((additional_length_ != 0 && !additional_) || | 
|  | (additional_ != NULL && additional_length_ == 0)) { | 
|  | return false; | 
|  | } | 
|  | if (track_number_ == 0 || track_number_ > kMaxTrackNumber) { | 
|  | return false; | 
|  | } | 
|  | if (!CanBeSimpleBlock() && !is_key_ && !reference_block_timestamp_set_) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Frame::CanBeSimpleBlock() const { | 
|  | return additional_ == NULL && discard_padding_ == 0 && duration_ == 0; | 
|  | } | 
|  |  | 
|  | void Frame::set_reference_block_timestamp(int64 reference_block_timestamp) { | 
|  | reference_block_timestamp_ = reference_block_timestamp; | 
|  | reference_block_timestamp_set_ = true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // CuePoint Class | 
|  |  | 
|  | CuePoint::CuePoint() | 
|  | : time_(0), | 
|  | track_(0), | 
|  | cluster_pos_(0), | 
|  | block_number_(1), | 
|  | output_block_number_(true) {} | 
|  |  | 
|  | CuePoint::~CuePoint() {} | 
|  |  | 
|  | bool CuePoint::Write(IMkvWriter* writer) const { | 
|  | if (!writer || track_ < 1 || cluster_pos_ < 1) | 
|  | return false; | 
|  |  | 
|  | uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_); | 
|  | size += EbmlElementSize(kMkvCueTrack, track_); | 
|  | if (output_block_number_ && block_number_ > 1) | 
|  | size += EbmlElementSize(kMkvCueBlockNumber, block_number_); | 
|  | const uint64 track_pos_size = | 
|  | EbmlMasterElementSize(kMkvCueTrackPositions, size) + size; | 
|  | const uint64 payload_size = | 
|  | EbmlElementSize(kMkvCueTime, time_) + track_pos_size; | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvCuePoint, payload_size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvCueTime, time_)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvCueTrackPositions, size)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvCueTrack, track_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvCueClusterPosition, cluster_pos_)) | 
|  | return false; | 
|  | if (output_block_number_ && block_number_ > 1) | 
|  | if (!WriteEbmlElement(writer, kMkvCueBlockNumber, block_number_)) | 
|  | return false; | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (stop_position - payload_position != static_cast<int64>(payload_size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 CuePoint::PayloadSize() const { | 
|  | uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_); | 
|  | size += EbmlElementSize(kMkvCueTrack, track_); | 
|  | if (output_block_number_ && block_number_ > 1) | 
|  | size += EbmlElementSize(kMkvCueBlockNumber, block_number_); | 
|  | const uint64 track_pos_size = | 
|  | EbmlMasterElementSize(kMkvCueTrackPositions, size) + size; | 
|  | const uint64 payload_size = | 
|  | EbmlElementSize(kMkvCueTime, time_) + track_pos_size; | 
|  |  | 
|  | return payload_size; | 
|  | } | 
|  |  | 
|  | uint64 CuePoint::Size() const { | 
|  | const uint64 payload_size = PayloadSize(); | 
|  | return EbmlMasterElementSize(kMkvCuePoint, payload_size) + payload_size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Cues Class | 
|  |  | 
|  | Cues::Cues() | 
|  | : cue_entries_capacity_(0), | 
|  | cue_entries_size_(0), | 
|  | cue_entries_(NULL), | 
|  | output_block_number_(true) {} | 
|  |  | 
|  | Cues::~Cues() { | 
|  | if (cue_entries_) { | 
|  | for (int32 i = 0; i < cue_entries_size_; ++i) { | 
|  | CuePoint* const cue = cue_entries_[i]; | 
|  | delete cue; | 
|  | } | 
|  | delete[] cue_entries_; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Cues::AddCue(CuePoint* cue) { | 
|  | if (!cue) | 
|  | return false; | 
|  |  | 
|  | if ((cue_entries_size_ + 1) > cue_entries_capacity_) { | 
|  | // Add more CuePoints. | 
|  | const int32 new_capacity = | 
|  | (!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2; | 
|  |  | 
|  | if (new_capacity < 1) | 
|  | return false; | 
|  |  | 
|  | CuePoint** const cues = | 
|  | new (std::nothrow) CuePoint*[new_capacity];  // NOLINT | 
|  | if (!cues) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < cue_entries_size_; ++i) { | 
|  | cues[i] = cue_entries_[i]; | 
|  | } | 
|  |  | 
|  | delete[] cue_entries_; | 
|  |  | 
|  | cue_entries_ = cues; | 
|  | cue_entries_capacity_ = new_capacity; | 
|  | } | 
|  |  | 
|  | cue->set_output_block_number(output_block_number_); | 
|  | cue_entries_[cue_entries_size_++] = cue; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | CuePoint* Cues::GetCueByIndex(int32 index) const { | 
|  | if (cue_entries_ == NULL) | 
|  | return NULL; | 
|  |  | 
|  | if (index >= cue_entries_size_) | 
|  | return NULL; | 
|  |  | 
|  | return cue_entries_[index]; | 
|  | } | 
|  |  | 
|  | uint64 Cues::Size() { | 
|  | uint64 size = 0; | 
|  | for (int32 i = 0; i < cue_entries_size_; ++i) | 
|  | size += GetCueByIndex(i)->Size(); | 
|  | size += EbmlMasterElementSize(kMkvCues, size); | 
|  | return size; | 
|  | } | 
|  |  | 
|  | bool Cues::Write(IMkvWriter* writer) const { | 
|  | if (!writer) | 
|  | return false; | 
|  |  | 
|  | uint64 size = 0; | 
|  | for (int32 i = 0; i < cue_entries_size_; ++i) { | 
|  | const CuePoint* const cue = GetCueByIndex(i); | 
|  |  | 
|  | if (!cue) | 
|  | return false; | 
|  |  | 
|  | size += cue->Size(); | 
|  | } | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvCues, size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < cue_entries_size_; ++i) { | 
|  | const CuePoint* const cue = GetCueByIndex(i); | 
|  |  | 
|  | if (!cue->Write(writer)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // ContentEncAESSettings Class | 
|  |  | 
|  | ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {} | 
|  |  | 
|  | uint64 ContentEncAESSettings::Size() const { | 
|  | const uint64 payload = PayloadSize(); | 
|  | const uint64 size = | 
|  | EbmlMasterElementSize(kMkvContentEncAESSettings, payload) + payload; | 
|  | return size; | 
|  | } | 
|  |  | 
|  | bool ContentEncAESSettings::Write(IMkvWriter* writer) const { | 
|  | const uint64 payload = PayloadSize(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvContentEncAESSettings, payload)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvAESSettingsCipherMode, cipher_mode_)) | 
|  | return false; | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(payload)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 ContentEncAESSettings::PayloadSize() const { | 
|  | uint64 size = EbmlElementSize(kMkvAESSettingsCipherMode, cipher_mode_); | 
|  | return size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // ContentEncoding Class | 
|  |  | 
|  | ContentEncoding::ContentEncoding() | 
|  | : enc_algo_(5), | 
|  | enc_key_id_(NULL), | 
|  | encoding_order_(0), | 
|  | encoding_scope_(1), | 
|  | encoding_type_(1), | 
|  | enc_key_id_length_(0) {} | 
|  |  | 
|  | ContentEncoding::~ContentEncoding() { delete[] enc_key_id_; } | 
|  |  | 
|  | bool ContentEncoding::SetEncryptionID(const uint8* id, uint64 length) { | 
|  | if (!id || length < 1) | 
|  | return false; | 
|  |  | 
|  | delete[] enc_key_id_; | 
|  |  | 
|  | enc_key_id_ = | 
|  | new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT | 
|  | if (!enc_key_id_) | 
|  | return false; | 
|  |  | 
|  | memcpy(enc_key_id_, id, static_cast<size_t>(length)); | 
|  | enc_key_id_length_ = length; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 ContentEncoding::Size() const { | 
|  | const uint64 encryption_size = EncryptionSize(); | 
|  | const uint64 encoding_size = EncodingSize(0, encryption_size); | 
|  | const uint64 encodings_size = | 
|  | EbmlMasterElementSize(kMkvContentEncoding, encoding_size) + encoding_size; | 
|  |  | 
|  | return encodings_size; | 
|  | } | 
|  |  | 
|  | bool ContentEncoding::Write(IMkvWriter* writer) const { | 
|  | const uint64 encryption_size = EncryptionSize(); | 
|  | const uint64 encoding_size = EncodingSize(0, encryption_size); | 
|  | const uint64 size = | 
|  | EbmlMasterElementSize(kMkvContentEncoding, encoding_size) + encoding_size; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvContentEncoding, encoding_size)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvContentEncodingOrder, encoding_order_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvContentEncodingScope, encoding_scope_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvContentEncodingType, encoding_type_)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvContentEncryption, encryption_size)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvContentEncAlgo, enc_algo_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvContentEncKeyID, enc_key_id_, | 
|  | enc_key_id_length_)) | 
|  | return false; | 
|  |  | 
|  | if (!enc_aes_settings_.Write(writer)) | 
|  | return false; | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 ContentEncoding::EncodingSize(uint64 compresion_size, | 
|  | uint64 encryption_size) const { | 
|  | // TODO(fgalligan): Add support for compression settings. | 
|  | if (compresion_size != 0) | 
|  | return 0; | 
|  |  | 
|  | uint64 encoding_size = 0; | 
|  |  | 
|  | if (encryption_size > 0) { | 
|  | encoding_size += | 
|  | EbmlMasterElementSize(kMkvContentEncryption, encryption_size) + | 
|  | encryption_size; | 
|  | } | 
|  | encoding_size += EbmlElementSize(kMkvContentEncodingType, encoding_type_); | 
|  | encoding_size += EbmlElementSize(kMkvContentEncodingScope, encoding_scope_); | 
|  | encoding_size += EbmlElementSize(kMkvContentEncodingOrder, encoding_order_); | 
|  |  | 
|  | return encoding_size; | 
|  | } | 
|  |  | 
|  | uint64 ContentEncoding::EncryptionSize() const { | 
|  | const uint64 aes_size = enc_aes_settings_.Size(); | 
|  |  | 
|  | uint64 encryption_size = | 
|  | EbmlElementSize(kMkvContentEncKeyID, enc_key_id_, enc_key_id_length_); | 
|  | encryption_size += EbmlElementSize(kMkvContentEncAlgo, enc_algo_); | 
|  |  | 
|  | return encryption_size + aes_size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Track Class | 
|  |  | 
|  | Track::Track(unsigned int* seed) | 
|  | : codec_id_(NULL), | 
|  | codec_private_(NULL), | 
|  | language_(NULL), | 
|  | max_block_additional_id_(0), | 
|  | name_(NULL), | 
|  | number_(0), | 
|  | type_(0), | 
|  | uid_(MakeUID(seed)), | 
|  | codec_delay_(0), | 
|  | seek_pre_roll_(0), | 
|  | default_duration_(0), | 
|  | codec_private_length_(0), | 
|  | content_encoding_entries_(NULL), | 
|  | content_encoding_entries_size_(0) {} | 
|  |  | 
|  | Track::~Track() { | 
|  | delete[] codec_id_; | 
|  | delete[] codec_private_; | 
|  | delete[] language_; | 
|  | delete[] name_; | 
|  |  | 
|  | if (content_encoding_entries_) { | 
|  | for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { | 
|  | ContentEncoding* const encoding = content_encoding_entries_[i]; | 
|  | delete encoding; | 
|  | } | 
|  | delete[] content_encoding_entries_; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Track::AddContentEncoding() { | 
|  | const uint32 count = content_encoding_entries_size_ + 1; | 
|  |  | 
|  | ContentEncoding** const content_encoding_entries = | 
|  | new (std::nothrow) ContentEncoding*[count];  // NOLINT | 
|  | if (!content_encoding_entries) | 
|  | return false; | 
|  |  | 
|  | ContentEncoding* const content_encoding = | 
|  | new (std::nothrow) ContentEncoding();  // NOLINT | 
|  | if (!content_encoding) { | 
|  | delete[] content_encoding_entries; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { | 
|  | content_encoding_entries[i] = content_encoding_entries_[i]; | 
|  | } | 
|  |  | 
|  | delete[] content_encoding_entries_; | 
|  |  | 
|  | content_encoding_entries_ = content_encoding_entries; | 
|  | content_encoding_entries_[content_encoding_entries_size_] = content_encoding; | 
|  | content_encoding_entries_size_ = count; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | ContentEncoding* Track::GetContentEncodingByIndex(uint32 index) const { | 
|  | if (content_encoding_entries_ == NULL) | 
|  | return NULL; | 
|  |  | 
|  | if (index >= content_encoding_entries_size_) | 
|  | return NULL; | 
|  |  | 
|  | return content_encoding_entries_[index]; | 
|  | } | 
|  |  | 
|  | uint64 Track::PayloadSize() const { | 
|  | uint64 size = EbmlElementSize(kMkvTrackNumber, number_); | 
|  | size += EbmlElementSize(kMkvTrackUID, uid_); | 
|  | size += EbmlElementSize(kMkvTrackType, type_); | 
|  | if (codec_id_) | 
|  | size += EbmlElementSize(kMkvCodecID, codec_id_); | 
|  | if (codec_private_) | 
|  | size += EbmlElementSize(kMkvCodecPrivate, codec_private_, | 
|  | codec_private_length_); | 
|  | if (language_) | 
|  | size += EbmlElementSize(kMkvLanguage, language_); | 
|  | if (name_) | 
|  | size += EbmlElementSize(kMkvName, name_); | 
|  | if (max_block_additional_id_) | 
|  | size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); | 
|  | if (codec_delay_) | 
|  | size += EbmlElementSize(kMkvCodecDelay, codec_delay_); | 
|  | if (seek_pre_roll_) | 
|  | size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_); | 
|  | if (default_duration_) | 
|  | size += EbmlElementSize(kMkvDefaultDuration, default_duration_); | 
|  |  | 
|  | if (content_encoding_entries_size_ > 0) { | 
|  | uint64 content_encodings_size = 0; | 
|  | for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { | 
|  | ContentEncoding* const encoding = content_encoding_entries_[i]; | 
|  | content_encodings_size += encoding->Size(); | 
|  | } | 
|  |  | 
|  | size += | 
|  | EbmlMasterElementSize(kMkvContentEncodings, content_encodings_size) + | 
|  | content_encodings_size; | 
|  | } | 
|  |  | 
|  | return size; | 
|  | } | 
|  |  | 
|  | uint64 Track::Size() const { | 
|  | uint64 size = PayloadSize(); | 
|  | size += EbmlMasterElementSize(kMkvTrackEntry, size); | 
|  | return size; | 
|  | } | 
|  |  | 
|  | bool Track::Write(IMkvWriter* writer) const { | 
|  | if (!writer) | 
|  | return false; | 
|  |  | 
|  | // mandatory elements without a default value. | 
|  | if (!type_ || !codec_id_) | 
|  | return false; | 
|  |  | 
|  | // |size| may be bigger than what is written out in this function because | 
|  | // derived classes may write out more data in the Track element. | 
|  | const uint64 payload_size = PayloadSize(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvTrackEntry, payload_size)) | 
|  | return false; | 
|  |  | 
|  | uint64 size = EbmlElementSize(kMkvTrackNumber, number_); | 
|  | size += EbmlElementSize(kMkvTrackUID, uid_); | 
|  | size += EbmlElementSize(kMkvTrackType, type_); | 
|  | if (codec_id_) | 
|  | size += EbmlElementSize(kMkvCodecID, codec_id_); | 
|  | if (codec_private_) | 
|  | size += EbmlElementSize(kMkvCodecPrivate, codec_private_, | 
|  | codec_private_length_); | 
|  | if (language_) | 
|  | size += EbmlElementSize(kMkvLanguage, language_); | 
|  | if (name_) | 
|  | size += EbmlElementSize(kMkvName, name_); | 
|  | if (max_block_additional_id_) | 
|  | size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_); | 
|  | if (codec_delay_) | 
|  | size += EbmlElementSize(kMkvCodecDelay, codec_delay_); | 
|  | if (seek_pre_roll_) | 
|  | size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_); | 
|  | if (default_duration_) | 
|  | size += EbmlElementSize(kMkvDefaultDuration, default_duration_); | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvTrackNumber, number_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvTrackUID, uid_)) | 
|  | 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_delay_) { | 
|  | if (!WriteEbmlElement(writer, kMkvCodecDelay, codec_delay_)) | 
|  | return false; | 
|  | } | 
|  | if (seek_pre_roll_) { | 
|  | if (!WriteEbmlElement(writer, kMkvSeekPreRoll, seek_pre_roll_)) | 
|  | return false; | 
|  | } | 
|  | if (default_duration_) { | 
|  | if (!WriteEbmlElement(writer, kMkvDefaultDuration, default_duration_)) | 
|  | return false; | 
|  | } | 
|  | if (codec_id_) { | 
|  | if (!WriteEbmlElement(writer, kMkvCodecID, codec_id_)) | 
|  | return false; | 
|  | } | 
|  | if (codec_private_) { | 
|  | if (!WriteEbmlElement(writer, kMkvCodecPrivate, codec_private_, | 
|  | codec_private_length_)) | 
|  | return false; | 
|  | } | 
|  | if (language_) { | 
|  | if (!WriteEbmlElement(writer, kMkvLanguage, language_)) | 
|  | return false; | 
|  | } | 
|  | if (name_) { | 
|  | if (!WriteEbmlElement(writer, kMkvName, name_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | if (content_encoding_entries_size_ > 0) { | 
|  | uint64 content_encodings_size = 0; | 
|  | for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { | 
|  | ContentEncoding* const encoding = content_encoding_entries_[i]; | 
|  | content_encodings_size += encoding->Size(); | 
|  | } | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvContentEncodings, | 
|  | content_encodings_size)) | 
|  | return false; | 
|  |  | 
|  | for (uint32 i = 0; i < content_encoding_entries_size_; ++i) { | 
|  | ContentEncoding* const encoding = content_encoding_entries_[i]; | 
|  | if (!encoding->Write(writer)) | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | stop_position = writer->Position(); | 
|  | if (stop_position < 0) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Track::SetCodecPrivate(const uint8* codec_private, uint64 length) { | 
|  | if (!codec_private || length < 1) | 
|  | return false; | 
|  |  | 
|  | delete[] codec_private_; | 
|  |  | 
|  | codec_private_ = | 
|  | new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT | 
|  | if (!codec_private_) | 
|  | return false; | 
|  |  | 
|  | memcpy(codec_private_, codec_private, static_cast<size_t>(length)); | 
|  | codec_private_length_ = length; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Track::set_codec_id(const char* codec_id) { | 
|  | if (codec_id) { | 
|  | delete[] codec_id_; | 
|  |  | 
|  | const size_t length = strlen(codec_id) + 1; | 
|  | codec_id_ = new (std::nothrow) char[length];  // NOLINT | 
|  | if (codec_id_) { | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(codec_id_, length, codec_id); | 
|  | #else | 
|  | strcpy(codec_id_, codec_id); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(fgalligan): Vet the language parameter. | 
|  | void Track::set_language(const char* language) { | 
|  | if (language) { | 
|  | delete[] language_; | 
|  |  | 
|  | const size_t length = strlen(language) + 1; | 
|  | language_ = new (std::nothrow) char[length];  // NOLINT | 
|  | if (language_) { | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(language_, length, language); | 
|  | #else | 
|  | strcpy(language_, language); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void Track::set_name(const char* name) { | 
|  | if (name) { | 
|  | delete[] name_; | 
|  |  | 
|  | const size_t length = strlen(name) + 1; | 
|  | name_ = new (std::nothrow) char[length];  // NOLINT | 
|  | if (name_) { | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(name_, length, name); | 
|  | #else | 
|  | strcpy(name_, name); | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // VideoTrack Class | 
|  |  | 
|  | VideoTrack::VideoTrack(unsigned int* seed) | 
|  | : Track(seed), | 
|  | display_height_(0), | 
|  | display_width_(0), | 
|  | crop_left_(0), | 
|  | crop_right_(0), | 
|  | crop_top_(0), | 
|  | crop_bottom_(0), | 
|  | frame_rate_(0.0), | 
|  | height_(0), | 
|  | stereo_mode_(0), | 
|  | alpha_mode_(0), | 
|  | width_(0) {} | 
|  |  | 
|  | VideoTrack::~VideoTrack() {} | 
|  |  | 
|  | bool VideoTrack::SetStereoMode(uint64 stereo_mode) { | 
|  | if (stereo_mode != kMono && stereo_mode != kSideBySideLeftIsFirst && | 
|  | stereo_mode != kTopBottomRightIsFirst && | 
|  | stereo_mode != kTopBottomLeftIsFirst && | 
|  | stereo_mode != kSideBySideRightIsFirst) | 
|  | return false; | 
|  |  | 
|  | stereo_mode_ = 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(); | 
|  |  | 
|  | uint64 size = VideoPayloadSize(); | 
|  | size += EbmlMasterElementSize(kMkvVideo, size); | 
|  |  | 
|  | return parent_size + size; | 
|  | } | 
|  |  | 
|  | bool VideoTrack::Write(IMkvWriter* writer) const { | 
|  | if (!Track::Write(writer)) | 
|  | return false; | 
|  |  | 
|  | const uint64 size = VideoPayloadSize(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvVideo, size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvPixelWidth, width_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvPixelHeight, height_)) | 
|  | return false; | 
|  | if (display_width_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvDisplayWidth, display_width_)) | 
|  | return false; | 
|  | } | 
|  | if (display_height_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvDisplayHeight, display_height_)) | 
|  | return false; | 
|  | } | 
|  | if (crop_left_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvPixelCropLeft, crop_left_)) | 
|  | return false; | 
|  | } | 
|  | if (crop_right_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvPixelCropRight, crop_right_)) | 
|  | return false; | 
|  | } | 
|  | if (crop_top_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvPixelCropTop, crop_top_)) | 
|  | return false; | 
|  | } | 
|  | if (crop_bottom_ > 0) { | 
|  | if (!WriteEbmlElement(writer, kMkvPixelCropBottom, crop_bottom_)) | 
|  | return false; | 
|  | } | 
|  | 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, | 
|  | static_cast<float>(frame_rate_))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 VideoTrack::VideoPayloadSize() const { | 
|  | uint64 size = EbmlElementSize(kMkvPixelWidth, width_); | 
|  | size += EbmlElementSize(kMkvPixelHeight, height_); | 
|  | if (display_width_ > 0) | 
|  | size += EbmlElementSize(kMkvDisplayWidth, display_width_); | 
|  | if (display_height_ > 0) | 
|  | size += EbmlElementSize(kMkvDisplayHeight, display_height_); | 
|  | if (crop_left_ > 0) | 
|  | size += EbmlElementSize(kMkvPixelCropLeft, crop_left_); | 
|  | if (crop_right_ > 0) | 
|  | size += EbmlElementSize(kMkvPixelCropRight, crop_right_); | 
|  | if (crop_top_ > 0) | 
|  | size += EbmlElementSize(kMkvPixelCropTop, crop_top_); | 
|  | if (crop_bottom_ > 0) | 
|  | size += EbmlElementSize(kMkvPixelCropBottom, crop_bottom_); | 
|  | 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<float>(frame_rate_)); | 
|  |  | 
|  | return size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // AudioTrack Class | 
|  |  | 
|  | AudioTrack::AudioTrack(unsigned int* seed) | 
|  | : Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {} | 
|  |  | 
|  | AudioTrack::~AudioTrack() {} | 
|  |  | 
|  | uint64 AudioTrack::PayloadSize() const { | 
|  | const uint64 parent_size = Track::PayloadSize(); | 
|  |  | 
|  | uint64 size = | 
|  | EbmlElementSize(kMkvSamplingFrequency, static_cast<float>(sample_rate_)); | 
|  | size += EbmlElementSize(kMkvChannels, channels_); | 
|  | if (bit_depth_ > 0) | 
|  | size += EbmlElementSize(kMkvBitDepth, bit_depth_); | 
|  | size += EbmlMasterElementSize(kMkvAudio, size); | 
|  |  | 
|  | return parent_size + size; | 
|  | } | 
|  |  | 
|  | bool AudioTrack::Write(IMkvWriter* writer) const { | 
|  | if (!Track::Write(writer)) | 
|  | return false; | 
|  |  | 
|  | // Calculate AudioSettings size. | 
|  | uint64 size = | 
|  | EbmlElementSize(kMkvSamplingFrequency, static_cast<float>(sample_rate_)); | 
|  | size += EbmlElementSize(kMkvChannels, channels_); | 
|  | if (bit_depth_ > 0) | 
|  | size += EbmlElementSize(kMkvBitDepth, bit_depth_); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvAudio, size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvSamplingFrequency, | 
|  | static_cast<float>(sample_rate_))) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvChannels, channels_)) | 
|  | return false; | 
|  | if (bit_depth_ > 0) | 
|  | if (!WriteEbmlElement(writer, kMkvBitDepth, bit_depth_)) | 
|  | return false; | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Tracks Class | 
|  |  | 
|  | const char Tracks::kOpusCodecId[] = "A_OPUS"; | 
|  | const char Tracks::kVorbisCodecId[] = "A_VORBIS"; | 
|  | const char Tracks::kAOMCodecId[] = "V_AOM"; | 
|  | const char Tracks::kAV1CodecId[] = "V_AV1"; | 
|  |  | 
|  | Tracks::Tracks() : track_entries_(NULL), track_entries_size_(0) {} | 
|  |  | 
|  | Tracks::~Tracks() { | 
|  | if (track_entries_) { | 
|  | for (uint32 i = 0; i < track_entries_size_; ++i) { | 
|  | Track* const track = track_entries_[i]; | 
|  | delete track; | 
|  | } | 
|  | delete[] track_entries_; | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Tracks::AddTrack(Track* track, int32 number) { | 
|  | if (number < 0) | 
|  | return false; | 
|  |  | 
|  | // This muxer only supports track numbers in the range [1, 126], in | 
|  | // order to be able (to use Matroska integer representation) to | 
|  | // serialize the block header (of which the track number is a part) | 
|  | // for a frame using exactly 4 bytes. | 
|  |  | 
|  | if (number > 0x7E) | 
|  | return false; | 
|  |  | 
|  | uint32 track_num = number; | 
|  |  | 
|  | if (track_num > 0) { | 
|  | // Check to make sure a track does not already have |track_num|. | 
|  | for (uint32 i = 0; i < track_entries_size_; ++i) { | 
|  | if (track_entries_[i]->number() == track_num) | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | const uint32 count = track_entries_size_ + 1; | 
|  |  | 
|  | Track** const track_entries = new (std::nothrow) Track*[count];  // NOLINT | 
|  | if (!track_entries) | 
|  | return false; | 
|  |  | 
|  | for (uint32 i = 0; i < track_entries_size_; ++i) { | 
|  | track_entries[i] = track_entries_[i]; | 
|  | } | 
|  |  | 
|  | delete[] track_entries_; | 
|  |  | 
|  | // Find the lowest availible track number > 0. | 
|  | if (track_num == 0) { | 
|  | track_num = count; | 
|  |  | 
|  | // Check to make sure a track does not already have |track_num|. | 
|  | bool exit = false; | 
|  | do { | 
|  | exit = true; | 
|  | for (uint32 i = 0; i < track_entries_size_; ++i) { | 
|  | if (track_entries[i]->number() == track_num) { | 
|  | track_num++; | 
|  | exit = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } while (!exit); | 
|  | } | 
|  | track->set_number(track_num); | 
|  |  | 
|  | track_entries_ = track_entries; | 
|  | track_entries_[track_entries_size_] = track; | 
|  | track_entries_size_ = count; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const Track* Tracks::GetTrackByIndex(uint32 index) const { | 
|  | if (track_entries_ == NULL) | 
|  | return NULL; | 
|  |  | 
|  | if (index >= track_entries_size_) | 
|  | return NULL; | 
|  |  | 
|  | return track_entries_[index]; | 
|  | } | 
|  |  | 
|  | Track* Tracks::GetTrackByNumber(uint64 track_number) const { | 
|  | const int32 count = track_entries_size(); | 
|  | for (int32 i = 0; i < count; ++i) { | 
|  | if (track_entries_[i]->number() == track_number) | 
|  | return track_entries_[i]; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | bool Tracks::TrackIsAudio(uint64 track_number) const { | 
|  | const Track* const track = GetTrackByNumber(track_number); | 
|  |  | 
|  | if (track->type() == kAudio) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Tracks::TrackIsVideo(uint64 track_number) const { | 
|  | const Track* const track = GetTrackByNumber(track_number); | 
|  |  | 
|  | if (track->type() == kVideo) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool Tracks::Write(IMkvWriter* writer) const { | 
|  | uint64 size = 0; | 
|  | const int32 count = track_entries_size(); | 
|  | for (int32 i = 0; i < count; ++i) { | 
|  | const Track* const track = GetTrackByIndex(i); | 
|  |  | 
|  | if (!track) | 
|  | return false; | 
|  |  | 
|  | size += track->Size(); | 
|  | } | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvTracks, size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < count; ++i) { | 
|  | const Track* const track = GetTrackByIndex(i); | 
|  | if (!track->Write(writer)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Chapter Class | 
|  |  | 
|  | bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); } | 
|  |  | 
|  | void Chapter::set_time(const Segment& segment, uint64 start_ns, uint64 end_ns) { | 
|  | const SegmentInfo* const info = segment.GetSegmentInfo(); | 
|  | const uint64 timecode_scale = info->timecode_scale(); | 
|  | start_timecode_ = start_ns / timecode_scale; | 
|  | end_timecode_ = end_ns / timecode_scale; | 
|  | } | 
|  |  | 
|  | bool Chapter::add_string(const char* title, const char* language, | 
|  | const char* country) { | 
|  | if (!ExpandDisplaysArray()) | 
|  | return false; | 
|  |  | 
|  | Display& d = displays_[displays_count_++]; | 
|  | d.Init(); | 
|  |  | 
|  | if (!d.set_title(title)) | 
|  | return false; | 
|  |  | 
|  | if (!d.set_language(language)) | 
|  | return false; | 
|  |  | 
|  | if (!d.set_country(country)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Chapter::Chapter() { | 
|  | // This ctor only constructs the object.  Proper initialization is | 
|  | // done in Init() (called in Chapters::AddChapter()).  The only | 
|  | // reason we bother implementing this ctor is because we had to | 
|  | // declare it as private (along with the dtor), in order to prevent | 
|  | // clients from creating Chapter instances (a privelege we grant | 
|  | // only to the Chapters class).  Doing no initialization here also | 
|  | // means that creating arrays of chapter objects is more efficient, | 
|  | // because we only initialize each new chapter object as it becomes | 
|  | // active on the array. | 
|  | } | 
|  |  | 
|  | Chapter::~Chapter() {} | 
|  |  | 
|  | void Chapter::Init(unsigned int* seed) { | 
|  | id_ = NULL; | 
|  | start_timecode_ = 0; | 
|  | end_timecode_ = 0; | 
|  | displays_ = NULL; | 
|  | displays_size_ = 0; | 
|  | displays_count_ = 0; | 
|  | uid_ = MakeUID(seed); | 
|  | } | 
|  |  | 
|  | void Chapter::ShallowCopy(Chapter* dst) const { | 
|  | dst->id_ = id_; | 
|  | dst->start_timecode_ = start_timecode_; | 
|  | dst->end_timecode_ = end_timecode_; | 
|  | dst->uid_ = uid_; | 
|  | dst->displays_ = displays_; | 
|  | dst->displays_size_ = displays_size_; | 
|  | dst->displays_count_ = displays_count_; | 
|  | } | 
|  |  | 
|  | void Chapter::Clear() { | 
|  | StrCpy(NULL, &id_); | 
|  |  | 
|  | while (displays_count_ > 0) { | 
|  | Display& d = displays_[--displays_count_]; | 
|  | d.Clear(); | 
|  | } | 
|  |  | 
|  | delete[] displays_; | 
|  | displays_ = NULL; | 
|  |  | 
|  | displays_size_ = 0; | 
|  | } | 
|  |  | 
|  | bool Chapter::ExpandDisplaysArray() { | 
|  | if (displays_size_ > displays_count_) | 
|  | return true;  // nothing to do yet | 
|  |  | 
|  | const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_; | 
|  |  | 
|  | Display* const displays = new (std::nothrow) Display[size];  // NOLINT | 
|  | if (displays == NULL) | 
|  | return false; | 
|  |  | 
|  | for (int idx = 0; idx < displays_count_; ++idx) { | 
|  | displays[idx] = displays_[idx];  // shallow copy | 
|  | } | 
|  |  | 
|  | delete[] displays_; | 
|  |  | 
|  | displays_ = displays; | 
|  | displays_size_ = size; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 Chapter::WriteAtom(IMkvWriter* writer) const { | 
|  | uint64 payload_size = EbmlElementSize(kMkvChapterStringUID, id_) + | 
|  | EbmlElementSize(kMkvChapterUID, uid_) + | 
|  | EbmlElementSize(kMkvChapterTimeStart, start_timecode_) + | 
|  | EbmlElementSize(kMkvChapterTimeEnd, end_timecode_); | 
|  |  | 
|  | for (int idx = 0; idx < displays_count_; ++idx) { | 
|  | const Display& d = displays_[idx]; | 
|  | payload_size += d.WriteDisplay(NULL); | 
|  | } | 
|  |  | 
|  | const uint64 atom_size = | 
|  | EbmlMasterElementSize(kMkvChapterAtom, payload_size) + payload_size; | 
|  |  | 
|  | if (writer == NULL) | 
|  | return atom_size; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvChapterAtom, payload_size)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvChapterStringUID, id_)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvChapterUID, uid_)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvChapterTimeStart, start_timecode_)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvChapterTimeEnd, end_timecode_)) | 
|  | return 0; | 
|  |  | 
|  | for (int idx = 0; idx < displays_count_; ++idx) { | 
|  | const Display& d = displays_[idx]; | 
|  |  | 
|  | if (!d.WriteDisplay(writer)) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != atom_size) | 
|  | return 0; | 
|  |  | 
|  | return atom_size; | 
|  | } | 
|  |  | 
|  | void Chapter::Display::Init() { | 
|  | title_ = NULL; | 
|  | language_ = NULL; | 
|  | country_ = NULL; | 
|  | } | 
|  |  | 
|  | void Chapter::Display::Clear() { | 
|  | StrCpy(NULL, &title_); | 
|  | StrCpy(NULL, &language_); | 
|  | StrCpy(NULL, &country_); | 
|  | } | 
|  |  | 
|  | bool Chapter::Display::set_title(const char* title) { | 
|  | return StrCpy(title, &title_); | 
|  | } | 
|  |  | 
|  | bool Chapter::Display::set_language(const char* language) { | 
|  | return StrCpy(language, &language_); | 
|  | } | 
|  |  | 
|  | bool Chapter::Display::set_country(const char* country) { | 
|  | return StrCpy(country, &country_); | 
|  | } | 
|  |  | 
|  | uint64 Chapter::Display::WriteDisplay(IMkvWriter* writer) const { | 
|  | uint64 payload_size = EbmlElementSize(kMkvChapString, title_); | 
|  |  | 
|  | if (language_) | 
|  | payload_size += EbmlElementSize(kMkvChapLanguage, language_); | 
|  |  | 
|  | if (country_) | 
|  | payload_size += EbmlElementSize(kMkvChapCountry, country_); | 
|  |  | 
|  | const uint64 display_size = | 
|  | EbmlMasterElementSize(kMkvChapterDisplay, payload_size) + payload_size; | 
|  |  | 
|  | if (writer == NULL) | 
|  | return display_size; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvChapterDisplay, payload_size)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvChapString, title_)) | 
|  | return 0; | 
|  |  | 
|  | if (language_) { | 
|  | if (!WriteEbmlElement(writer, kMkvChapLanguage, language_)) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (country_) { | 
|  | if (!WriteEbmlElement(writer, kMkvChapCountry, country_)) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != display_size) | 
|  | return 0; | 
|  |  | 
|  | return display_size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Chapters Class | 
|  |  | 
|  | Chapters::Chapters() : chapters_size_(0), chapters_count_(0), chapters_(NULL) {} | 
|  |  | 
|  | Chapters::~Chapters() { | 
|  | while (chapters_count_ > 0) { | 
|  | Chapter& chapter = chapters_[--chapters_count_]; | 
|  | chapter.Clear(); | 
|  | } | 
|  |  | 
|  | delete[] chapters_; | 
|  | chapters_ = NULL; | 
|  | } | 
|  |  | 
|  | int Chapters::Count() const { return chapters_count_; } | 
|  |  | 
|  | Chapter* Chapters::AddChapter(unsigned int* seed) { | 
|  | if (!ExpandChaptersArray()) | 
|  | return NULL; | 
|  |  | 
|  | Chapter& chapter = chapters_[chapters_count_++]; | 
|  | chapter.Init(seed); | 
|  |  | 
|  | return &chapter; | 
|  | } | 
|  |  | 
|  | bool Chapters::Write(IMkvWriter* writer) const { | 
|  | if (writer == NULL) | 
|  | return false; | 
|  |  | 
|  | const uint64 payload_size = WriteEdition(NULL);  // return size only | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvChapters, payload_size)) | 
|  | return false; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (WriteEdition(writer) == 0)  // error | 
|  | return false; | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != payload_size) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Chapters::ExpandChaptersArray() { | 
|  | if (chapters_size_ > chapters_count_) | 
|  | return true;  // nothing to do yet | 
|  |  | 
|  | const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_; | 
|  |  | 
|  | Chapter* const chapters = new (std::nothrow) Chapter[size];  // NOLINT | 
|  | if (chapters == NULL) | 
|  | return false; | 
|  |  | 
|  | for (int idx = 0; idx < chapters_count_; ++idx) { | 
|  | const Chapter& src = chapters_[idx]; | 
|  | Chapter* const dst = chapters + idx; | 
|  | src.ShallowCopy(dst); | 
|  | } | 
|  |  | 
|  | delete[] chapters_; | 
|  |  | 
|  | chapters_ = chapters; | 
|  | chapters_size_ = size; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 Chapters::WriteEdition(IMkvWriter* writer) const { | 
|  | uint64 payload_size = 0; | 
|  |  | 
|  | for (int idx = 0; idx < chapters_count_; ++idx) { | 
|  | const Chapter& chapter = chapters_[idx]; | 
|  | payload_size += chapter.WriteAtom(NULL); | 
|  | } | 
|  |  | 
|  | const uint64 edition_size = | 
|  | EbmlMasterElementSize(kMkvEditionEntry, payload_size) + payload_size; | 
|  |  | 
|  | if (writer == NULL)  // return size only | 
|  | return edition_size; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvEditionEntry, payload_size)) | 
|  | return 0;  // error | 
|  |  | 
|  | for (int idx = 0; idx < chapters_count_; ++idx) { | 
|  | const Chapter& chapter = chapters_[idx]; | 
|  |  | 
|  | const uint64 chapter_size = chapter.WriteAtom(writer); | 
|  | if (chapter_size == 0)  // error | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != edition_size) | 
|  | return 0; | 
|  |  | 
|  | return edition_size; | 
|  | } | 
|  |  | 
|  | // Tag Class | 
|  |  | 
|  | bool Tag::add_simple_tag(const char* tag_name, const char* tag_string) { | 
|  | if (!ExpandSimpleTagsArray()) | 
|  | return false; | 
|  |  | 
|  | SimpleTag& st = simple_tags_[simple_tags_count_++]; | 
|  | st.Init(); | 
|  |  | 
|  | if (!st.set_tag_name(tag_name)) | 
|  | return false; | 
|  |  | 
|  | if (!st.set_tag_string(tag_string)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Tag::Tag() { | 
|  | simple_tags_ = NULL; | 
|  | simple_tags_size_ = 0; | 
|  | simple_tags_count_ = 0; | 
|  | } | 
|  |  | 
|  | Tag::~Tag() {} | 
|  |  | 
|  | void Tag::ShallowCopy(Tag* dst) const { | 
|  | dst->simple_tags_ = simple_tags_; | 
|  | dst->simple_tags_size_ = simple_tags_size_; | 
|  | dst->simple_tags_count_ = simple_tags_count_; | 
|  | } | 
|  |  | 
|  | void Tag::Clear() { | 
|  | while (simple_tags_count_ > 0) { | 
|  | SimpleTag& st = simple_tags_[--simple_tags_count_]; | 
|  | st.Clear(); | 
|  | } | 
|  |  | 
|  | delete[] simple_tags_; | 
|  | simple_tags_ = NULL; | 
|  |  | 
|  | simple_tags_size_ = 0; | 
|  | } | 
|  |  | 
|  | bool Tag::ExpandSimpleTagsArray() { | 
|  | if (simple_tags_size_ > simple_tags_count_) | 
|  | return true;  // nothing to do yet | 
|  |  | 
|  | const int size = (simple_tags_size_ == 0) ? 1 : 2 * simple_tags_size_; | 
|  |  | 
|  | SimpleTag* const simple_tags = new (std::nothrow) SimpleTag[size];  // NOLINT | 
|  | if (simple_tags == NULL) | 
|  | return false; | 
|  |  | 
|  | for (int idx = 0; idx < simple_tags_count_; ++idx) { | 
|  | simple_tags[idx] = simple_tags_[idx];  // shallow copy | 
|  | } | 
|  |  | 
|  | delete[] simple_tags_; | 
|  |  | 
|  | simple_tags_ = simple_tags; | 
|  | simple_tags_size_ = size; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 Tag::Write(IMkvWriter* writer) const { | 
|  | uint64 payload_size = 0; | 
|  |  | 
|  | for (int idx = 0; idx < simple_tags_count_; ++idx) { | 
|  | const SimpleTag& st = simple_tags_[idx]; | 
|  | payload_size += st.Write(NULL); | 
|  | } | 
|  |  | 
|  | const uint64 tag_size = | 
|  | EbmlMasterElementSize(kMkvTag, payload_size) + payload_size; | 
|  |  | 
|  | if (writer == NULL) | 
|  | return tag_size; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvTag, payload_size)) | 
|  | return 0; | 
|  |  | 
|  | for (int idx = 0; idx < simple_tags_count_; ++idx) { | 
|  | const SimpleTag& st = simple_tags_[idx]; | 
|  |  | 
|  | if (!st.Write(writer)) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != tag_size) | 
|  | return 0; | 
|  |  | 
|  | return tag_size; | 
|  | } | 
|  |  | 
|  | // Tag::SimpleTag | 
|  |  | 
|  | void Tag::SimpleTag::Init() { | 
|  | tag_name_ = NULL; | 
|  | tag_string_ = NULL; | 
|  | } | 
|  |  | 
|  | void Tag::SimpleTag::Clear() { | 
|  | StrCpy(NULL, &tag_name_); | 
|  | StrCpy(NULL, &tag_string_); | 
|  | } | 
|  |  | 
|  | bool Tag::SimpleTag::set_tag_name(const char* tag_name) { | 
|  | return StrCpy(tag_name, &tag_name_); | 
|  | } | 
|  |  | 
|  | bool Tag::SimpleTag::set_tag_string(const char* tag_string) { | 
|  | return StrCpy(tag_string, &tag_string_); | 
|  | } | 
|  |  | 
|  | uint64 Tag::SimpleTag::Write(IMkvWriter* writer) const { | 
|  | uint64 payload_size = EbmlElementSize(kMkvTagName, tag_name_); | 
|  |  | 
|  | payload_size += EbmlElementSize(kMkvTagString, tag_string_); | 
|  |  | 
|  | const uint64 simple_tag_size = | 
|  | EbmlMasterElementSize(kMkvSimpleTag, payload_size) + payload_size; | 
|  |  | 
|  | if (writer == NULL) | 
|  | return simple_tag_size; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvSimpleTag, payload_size)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvTagName, tag_name_)) | 
|  | return 0; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvTagString, tag_string_)) | 
|  | return 0; | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != simple_tag_size) | 
|  | return 0; | 
|  |  | 
|  | return simple_tag_size; | 
|  | } | 
|  |  | 
|  | // Tags Class | 
|  |  | 
|  | Tags::Tags() : tags_size_(0), tags_count_(0), tags_(NULL) {} | 
|  |  | 
|  | Tags::~Tags() { | 
|  | while (tags_count_ > 0) { | 
|  | Tag& tag = tags_[--tags_count_]; | 
|  | tag.Clear(); | 
|  | } | 
|  |  | 
|  | delete[] tags_; | 
|  | tags_ = NULL; | 
|  | } | 
|  |  | 
|  | int Tags::Count() const { return tags_count_; } | 
|  |  | 
|  | Tag* Tags::AddTag() { | 
|  | if (!ExpandTagsArray()) | 
|  | return NULL; | 
|  |  | 
|  | Tag& tag = tags_[tags_count_++]; | 
|  |  | 
|  | return &tag; | 
|  | } | 
|  |  | 
|  | bool Tags::Write(IMkvWriter* writer) const { | 
|  | if (writer == NULL) | 
|  | return false; | 
|  |  | 
|  | uint64 payload_size = 0; | 
|  |  | 
|  | for (int idx = 0; idx < tags_count_; ++idx) { | 
|  | const Tag& tag = tags_[idx]; | 
|  | payload_size += tag.Write(NULL); | 
|  | } | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvTags, payload_size)) | 
|  | return false; | 
|  |  | 
|  | const int64 start = writer->Position(); | 
|  |  | 
|  | for (int idx = 0; idx < tags_count_; ++idx) { | 
|  | const Tag& tag = tags_[idx]; | 
|  |  | 
|  | const uint64 tag_size = tag.Write(writer); | 
|  | if (tag_size == 0)  // error | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | const int64 stop = writer->Position(); | 
|  |  | 
|  | if (stop >= start && uint64(stop - start) != payload_size) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Tags::ExpandTagsArray() { | 
|  | if (tags_size_ > tags_count_) | 
|  | return true;  // nothing to do yet | 
|  |  | 
|  | const int size = (tags_size_ == 0) ? 1 : 2 * tags_size_; | 
|  |  | 
|  | Tag* const tags = new (std::nothrow) Tag[size];  // NOLINT | 
|  | if (tags == NULL) | 
|  | return false; | 
|  |  | 
|  | for (int idx = 0; idx < tags_count_; ++idx) { | 
|  | const Tag& src = tags_[idx]; | 
|  | Tag* const dst = tags + idx; | 
|  | src.ShallowCopy(dst); | 
|  | } | 
|  |  | 
|  | delete[] tags_; | 
|  |  | 
|  | tags_ = tags; | 
|  | tags_size_ = size; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Cluster class | 
|  |  | 
|  | Cluster::Cluster(uint64 timecode, int64 cues_pos, uint64 timecode_scale) | 
|  | : blocks_added_(0), | 
|  | finalized_(false), | 
|  | header_written_(false), | 
|  | payload_size_(0), | 
|  | position_for_cues_(cues_pos), | 
|  | size_position_(-1), | 
|  | timecode_(timecode), | 
|  | timecode_scale_(timecode_scale), | 
|  | writer_(NULL) {} | 
|  |  | 
|  | Cluster::~Cluster() {} | 
|  |  | 
|  | bool Cluster::Init(IMkvWriter* ptr_writer) { | 
|  | if (!ptr_writer) { | 
|  | return false; | 
|  | } | 
|  | writer_ = ptr_writer; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Cluster::AddFrame(const Frame* const frame) { return DoWriteFrame(frame); } | 
|  |  | 
|  | bool Cluster::AddFrame(const uint8* data, uint64 length, uint64 track_number, | 
|  | uint64 abs_timecode, bool is_key) { | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(abs_timecode); | 
|  | frame.set_is_key(is_key); | 
|  | return DoWriteFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Cluster::AddFrameWithAdditional(const uint8* data, uint64 length, | 
|  | const uint8* additional, | 
|  | uint64 additional_length, uint64 add_id, | 
|  | uint64 track_number, uint64 abs_timecode, | 
|  | bool is_key) { | 
|  | if (!additional || additional_length == 0) { | 
|  | return false; | 
|  | } | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length) || | 
|  | !frame.AddAdditionalData(additional, additional_length, add_id)) { | 
|  | return false; | 
|  | } | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(abs_timecode); | 
|  | frame.set_is_key(is_key); | 
|  | return DoWriteFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Cluster::AddFrameWithDiscardPadding(const uint8* data, uint64 length, | 
|  | int64 discard_padding, | 
|  | uint64 track_number, | 
|  | uint64 abs_timecode, bool is_key) { | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_discard_padding(discard_padding); | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(abs_timecode); | 
|  | frame.set_is_key(is_key); | 
|  | return DoWriteFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Cluster::AddMetadata(const uint8* data, uint64 length, uint64 track_number, | 
|  | uint64 abs_timecode, uint64 duration_timecode) { | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(abs_timecode); | 
|  | frame.set_duration(duration_timecode); | 
|  | frame.set_is_key(true);  // All metadata blocks are keyframes. | 
|  | return DoWriteFrame(&frame); | 
|  | } | 
|  |  | 
|  | void Cluster::AddPayloadSize(uint64 size) { payload_size_ += size; } | 
|  |  | 
|  | bool Cluster::Finalize() { | 
|  | if (!writer_ || finalized_ || size_position_ == -1) | 
|  | return false; | 
|  |  | 
|  | if (writer_->Seekable()) { | 
|  | const int64 pos = writer_->Position(); | 
|  |  | 
|  | if (writer_->Position(size_position_)) | 
|  | return false; | 
|  |  | 
|  | if (WriteUIntSize(writer_, payload_size(), 8)) | 
|  | return false; | 
|  |  | 
|  | if (writer_->Position(pos)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | finalized_ = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 Cluster::Size() const { | 
|  | const uint64 element_size = | 
|  | EbmlMasterElementSize(kMkvCluster, 0xFFFFFFFFFFFFFFFFULL) + payload_size_; | 
|  | return element_size; | 
|  | } | 
|  |  | 
|  | bool Cluster::PreWriteBlock() { | 
|  | if (finalized_) | 
|  | return false; | 
|  |  | 
|  | if (!header_written_) { | 
|  | if (!WriteClusterHeader()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Cluster::PostWriteBlock(uint64 element_size) { | 
|  | AddPayloadSize(element_size); | 
|  | ++blocks_added_; | 
|  | } | 
|  |  | 
|  | int64 Cluster::GetRelativeTimecode(int64 abs_timecode) const { | 
|  | const int64 cluster_timecode = this->Cluster::timecode(); | 
|  | const int64 rel_timecode = | 
|  | static_cast<int64>(abs_timecode) - cluster_timecode; | 
|  |  | 
|  | if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode) | 
|  | return -1; | 
|  |  | 
|  | return rel_timecode; | 
|  | } | 
|  |  | 
|  | bool Cluster::DoWriteFrame(const Frame* const frame) { | 
|  | if (!frame || !frame->IsValid()) | 
|  | return false; | 
|  |  | 
|  | if (!PreWriteBlock()) | 
|  | return false; | 
|  |  | 
|  | const uint64 element_size = WriteFrame(writer_, frame, this); | 
|  | if (element_size == 0) | 
|  | return false; | 
|  |  | 
|  | PostWriteBlock(element_size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Cluster::WriteClusterHeader() { | 
|  | if (finalized_) | 
|  | return false; | 
|  |  | 
|  | if (WriteID(writer_, kMkvCluster)) | 
|  | return false; | 
|  |  | 
|  | // Save for later. | 
|  | size_position_ = writer_->Position(); | 
|  |  | 
|  | // Write "unknown" (EBML coded -1) as cluster size value. We need to write 8 | 
|  | // bytes because we do not know how big our cluster will be. | 
|  | if (SerializeInt(writer_, kEbmlUnknownValue, 8)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer_, kMkvTimecode, timecode())) | 
|  | return false; | 
|  | AddPayloadSize(EbmlElementSize(kMkvTimecode, timecode())); | 
|  | header_written_ = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // SeekHead Class | 
|  |  | 
|  | SeekHead::SeekHead() : start_pos_(0ULL) { | 
|  | for (int32 i = 0; i < kSeekEntryCount; ++i) { | 
|  | seek_entry_id_[i] = 0; | 
|  | seek_entry_pos_[i] = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | SeekHead::~SeekHead() {} | 
|  |  | 
|  | bool SeekHead::Finalize(IMkvWriter* writer) const { | 
|  | if (writer->Seekable()) { | 
|  | if (start_pos_ == -1) | 
|  | return false; | 
|  |  | 
|  | uint64 payload_size = 0; | 
|  | uint64 entry_size[kSeekEntryCount]; | 
|  |  | 
|  | for (int32 i = 0; i < kSeekEntryCount; ++i) { | 
|  | if (seek_entry_id_[i] != 0) { | 
|  | entry_size[i] = | 
|  | EbmlElementSize(kMkvSeekID, static_cast<uint64>(seek_entry_id_[i])); | 
|  | entry_size[i] += EbmlElementSize(kMkvSeekPosition, seek_entry_pos_[i]); | 
|  |  | 
|  | payload_size += | 
|  | EbmlMasterElementSize(kMkvSeek, entry_size[i]) + entry_size[i]; | 
|  | } | 
|  | } | 
|  |  | 
|  | // No SeekHead elements | 
|  | if (payload_size == 0) | 
|  | return true; | 
|  |  | 
|  | const int64 pos = writer->Position(); | 
|  | if (writer->Position(start_pos_)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvSeekHead, payload_size)) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < kSeekEntryCount; ++i) { | 
|  | if (seek_entry_id_[i] != 0) { | 
|  | if (!WriteEbmlMasterElement(writer, kMkvSeek, entry_size[i])) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvSeekID, | 
|  | static_cast<uint64>(seek_entry_id_[i]))) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvSeekPosition, seek_entry_pos_[i])) | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | const uint64 total_entry_size = kSeekEntryCount * MaxEntrySize(); | 
|  | const uint64 total_size = | 
|  | EbmlMasterElementSize(kMkvSeekHead, total_entry_size) + | 
|  | total_entry_size; | 
|  | const int64 size_left = total_size - (writer->Position() - start_pos_); | 
|  |  | 
|  | const uint64 bytes_written = WriteVoidElement(writer, size_left); | 
|  | if (!bytes_written) | 
|  | return false; | 
|  |  | 
|  | if (writer->Position(pos)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SeekHead::Write(IMkvWriter* writer) { | 
|  | const uint64 entry_size = kSeekEntryCount * MaxEntrySize(); | 
|  | const uint64 size = EbmlMasterElementSize(kMkvSeekHead, entry_size); | 
|  |  | 
|  | start_pos_ = writer->Position(); | 
|  |  | 
|  | const uint64 bytes_written = WriteVoidElement(writer, size + entry_size); | 
|  | if (!bytes_written) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SeekHead::AddSeekEntry(uint32 id, uint64 pos) { | 
|  | for (int32 i = 0; i < kSeekEntryCount; ++i) { | 
|  | if (seek_entry_id_[i] == 0) { | 
|  | seek_entry_id_[i] = id; | 
|  | seek_entry_pos_[i] = pos; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32 SeekHead::GetId(int index) const { | 
|  | if (index < 0 || index >= kSeekEntryCount) | 
|  | return UINT_MAX; | 
|  | return seek_entry_id_[index]; | 
|  | } | 
|  |  | 
|  | uint64 SeekHead::GetPosition(int index) const { | 
|  | if (index < 0 || index >= kSeekEntryCount) | 
|  | return ULLONG_MAX; | 
|  | return seek_entry_pos_[index]; | 
|  | } | 
|  |  | 
|  | bool SeekHead::SetSeekEntry(int index, uint32 id, uint64 position) { | 
|  | if (index < 0 || index >= kSeekEntryCount) | 
|  | return false; | 
|  | seek_entry_id_[index] = id; | 
|  | seek_entry_pos_[index] = position; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 SeekHead::MaxEntrySize() const { | 
|  | const uint64 max_entry_payload_size = | 
|  | EbmlElementSize(kMkvSeekID, 0xffffffffULL) + | 
|  | EbmlElementSize(kMkvSeekPosition, 0xffffffffffffffffULL); | 
|  | const uint64 max_entry_size = | 
|  | EbmlMasterElementSize(kMkvSeek, max_entry_payload_size) + | 
|  | max_entry_payload_size; | 
|  |  | 
|  | return max_entry_size; | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // SegmentInfo Class | 
|  |  | 
|  | SegmentInfo::SegmentInfo() | 
|  | : duration_(-1.0), | 
|  | muxing_app_(NULL), | 
|  | timecode_scale_(1000000ULL), | 
|  | writing_app_(NULL), | 
|  | date_utc_(LLONG_MIN), | 
|  | duration_pos_(-1) {} | 
|  |  | 
|  | SegmentInfo::~SegmentInfo() { | 
|  | delete[] muxing_app_; | 
|  | delete[] writing_app_; | 
|  | } | 
|  |  | 
|  | bool SegmentInfo::Init() { | 
|  | int32 major; | 
|  | int32 minor; | 
|  | int32 build; | 
|  | int32 revision; | 
|  | GetVersion(&major, &minor, &build, &revision); | 
|  | char temp[256]; | 
|  | #ifdef _MSC_VER | 
|  | sprintf_s(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major, | 
|  | minor, build, revision); | 
|  | #else | 
|  | snprintf(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major, | 
|  | minor, build, revision); | 
|  | #endif | 
|  |  | 
|  | const size_t app_len = strlen(temp) + 1; | 
|  |  | 
|  | delete[] muxing_app_; | 
|  |  | 
|  | muxing_app_ = new (std::nothrow) char[app_len];  // NOLINT | 
|  | if (!muxing_app_) | 
|  | return false; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(muxing_app_, app_len, temp); | 
|  | #else | 
|  | strcpy(muxing_app_, temp); | 
|  | #endif | 
|  |  | 
|  | set_writing_app(temp); | 
|  | if (!writing_app_) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SegmentInfo::Finalize(IMkvWriter* writer) const { | 
|  | if (!writer) | 
|  | return false; | 
|  |  | 
|  | if (duration_ > 0.0) { | 
|  | if (writer->Seekable()) { | 
|  | if (duration_pos_ == -1) | 
|  | return false; | 
|  |  | 
|  | const int64 pos = writer->Position(); | 
|  |  | 
|  | if (writer->Position(duration_pos_)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvDuration, | 
|  | static_cast<float>(duration_))) | 
|  | return false; | 
|  |  | 
|  | if (writer->Position(pos)) | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool SegmentInfo::Write(IMkvWriter* writer) { | 
|  | if (!writer || !muxing_app_ || !writing_app_) | 
|  | return false; | 
|  |  | 
|  | uint64 size = EbmlElementSize(kMkvTimecodeScale, timecode_scale_); | 
|  | if (duration_ > 0.0) | 
|  | size += EbmlElementSize(kMkvDuration, static_cast<float>(duration_)); | 
|  | if (date_utc_ != LLONG_MIN) | 
|  | size += EbmlDateElementSize(kMkvDateUTC); | 
|  | size += EbmlElementSize(kMkvMuxingApp, muxing_app_); | 
|  | size += EbmlElementSize(kMkvWritingApp, writing_app_); | 
|  |  | 
|  | if (!WriteEbmlMasterElement(writer, kMkvInfo, size)) | 
|  | return false; | 
|  |  | 
|  | const int64 payload_position = writer->Position(); | 
|  | if (payload_position < 0) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvTimecodeScale, timecode_scale_)) | 
|  | return false; | 
|  |  | 
|  | if (duration_ > 0.0) { | 
|  | // Save for later | 
|  | duration_pos_ = writer->Position(); | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvDuration, static_cast<float>(duration_))) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (date_utc_ != LLONG_MIN) | 
|  | WriteEbmlDateElement(writer, kMkvDateUTC, date_utc_); | 
|  |  | 
|  | if (!WriteEbmlElement(writer, kMkvMuxingApp, muxing_app_)) | 
|  | return false; | 
|  | if (!WriteEbmlElement(writer, kMkvWritingApp, writing_app_)) | 
|  | return false; | 
|  |  | 
|  | const int64 stop_position = writer->Position(); | 
|  | if (stop_position < 0 || | 
|  | stop_position - payload_position != static_cast<int64>(size)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void SegmentInfo::set_muxing_app(const char* app) { | 
|  | if (app) { | 
|  | const size_t length = strlen(app) + 1; | 
|  | char* temp_str = new (std::nothrow) char[length];  // NOLINT | 
|  | if (!temp_str) | 
|  | return; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(temp_str, length, app); | 
|  | #else | 
|  | strcpy(temp_str, app); | 
|  | #endif | 
|  |  | 
|  | delete[] muxing_app_; | 
|  | muxing_app_ = temp_str; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SegmentInfo::set_writing_app(const char* app) { | 
|  | if (app) { | 
|  | const size_t length = strlen(app) + 1; | 
|  | char* temp_str = new (std::nothrow) char[length];  // NOLINT | 
|  | if (!temp_str) | 
|  | return; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(temp_str, length, app); | 
|  | #else | 
|  | strcpy(temp_str, app); | 
|  | #endif | 
|  |  | 
|  | delete[] writing_app_; | 
|  | writing_app_ = temp_str; | 
|  | } | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////// | 
|  | // | 
|  | // Segment Class | 
|  |  | 
|  | Segment::Segment() | 
|  | : chunk_count_(0), | 
|  | chunk_name_(NULL), | 
|  | chunk_writer_cluster_(NULL), | 
|  | chunk_writer_cues_(NULL), | 
|  | chunk_writer_header_(NULL), | 
|  | chunking_(false), | 
|  | chunking_base_name_(NULL), | 
|  | cluster_list_(NULL), | 
|  | cluster_list_capacity_(0), | 
|  | cluster_list_size_(0), | 
|  | cues_position_(kAfterClusters), | 
|  | cues_track_(0), | 
|  | force_new_cluster_(false), | 
|  | frames_(NULL), | 
|  | frames_capacity_(0), | 
|  | frames_size_(0), | 
|  | has_video_(false), | 
|  | header_written_(false), | 
|  | last_block_duration_(0), | 
|  | last_timestamp_(0), | 
|  | max_cluster_duration_(kDefaultMaxClusterDuration), | 
|  | max_cluster_size_(0), | 
|  | mode_(kFile), | 
|  | new_cuepoint_(false), | 
|  | output_cues_(true), | 
|  | payload_pos_(0), | 
|  | size_position_(0), | 
|  | doc_type_version_(kDefaultDocTypeVersion), | 
|  | doc_type_version_written_(0), | 
|  | writer_cluster_(NULL), | 
|  | writer_cues_(NULL), | 
|  | writer_header_(NULL) { | 
|  | const time_t curr_time = time(NULL); | 
|  | seed_ = static_cast<unsigned int>(curr_time); | 
|  | #ifdef _WIN32 | 
|  | srand(seed_); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | Segment::~Segment() { | 
|  | if (cluster_list_) { | 
|  | for (int32 i = 0; i < cluster_list_size_; ++i) { | 
|  | Cluster* const cluster = cluster_list_[i]; | 
|  | delete cluster; | 
|  | } | 
|  | delete[] cluster_list_; | 
|  | } | 
|  |  | 
|  | if (frames_) { | 
|  | for (int32 i = 0; i < frames_size_; ++i) { | 
|  | Frame* const frame = frames_[i]; | 
|  | delete frame; | 
|  | } | 
|  | delete[] frames_; | 
|  | } | 
|  |  | 
|  | delete[] chunk_name_; | 
|  | delete[] chunking_base_name_; | 
|  |  | 
|  | if (chunk_writer_cluster_) { | 
|  | chunk_writer_cluster_->Close(); | 
|  | delete chunk_writer_cluster_; | 
|  | } | 
|  | if (chunk_writer_cues_) { | 
|  | chunk_writer_cues_->Close(); | 
|  | delete chunk_writer_cues_; | 
|  | } | 
|  | if (chunk_writer_header_) { | 
|  | chunk_writer_header_->Close(); | 
|  | delete chunk_writer_header_; | 
|  | } | 
|  | } | 
|  |  | 
|  | void Segment::MoveCuesBeforeClustersHelper(uint64 diff, int32 index, | 
|  | uint64* cues_size) { | 
|  | CuePoint* const cue_point = cues_.GetCueByIndex(index); | 
|  | if (cue_point == NULL) | 
|  | return; | 
|  | const uint64 old_cue_point_size = cue_point->Size(); | 
|  | const uint64 cluster_pos = cue_point->cluster_pos() + diff; | 
|  | cue_point->set_cluster_pos(cluster_pos);  // update the new cluster position | 
|  | // New size of the cue is computed as follows | 
|  | //    Let a = current sum of size of all CuePoints | 
|  | //    Let b = Increase in Cue Point's size due to this iteration | 
|  | //    Let c = Increase in size of Cues Element's length due to this iteration | 
|  | //            (This is computed as CodedSize(a + b) - CodedSize(a)) | 
|  | //    Let d = b + c. Now d is the |diff| passed to the next recursive call. | 
|  | //    Let e = a + b. Now e is the |cues_size| passed to the next recursive | 
|  | //                   call. | 
|  | const uint64 cue_point_size_diff = cue_point->Size() - old_cue_point_size; | 
|  | const uint64 cue_size_diff = | 
|  | GetCodedUIntSize(*cues_size + cue_point_size_diff) - | 
|  | GetCodedUIntSize(*cues_size); | 
|  | *cues_size += cue_point_size_diff; | 
|  | diff = cue_size_diff + cue_point_size_diff; | 
|  | if (diff > 0) { | 
|  | for (int32 i = 0; i < cues_.cue_entries_size(); ++i) { | 
|  | MoveCuesBeforeClustersHelper(diff, i, cues_size); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void Segment::MoveCuesBeforeClusters() { | 
|  | const uint64 current_cue_size = cues_.Size(); | 
|  | uint64 cue_size = 0; | 
|  | for (int32 i = 0; i < cues_.cue_entries_size(); ++i) | 
|  | cue_size += cues_.GetCueByIndex(i)->Size(); | 
|  | for (int32 i = 0; i < cues_.cue_entries_size(); ++i) | 
|  | MoveCuesBeforeClustersHelper(current_cue_size, i, &cue_size); | 
|  |  | 
|  | // Adjust the Seek Entry to reflect the change in position | 
|  | // of Cluster and Cues | 
|  | int32 cluster_index = 0; | 
|  | int32 cues_index = 0; | 
|  | for (int32 i = 0; i < SeekHead::kSeekEntryCount; ++i) { | 
|  | if (seek_head_.GetId(i) == kMkvCluster) | 
|  | cluster_index = i; | 
|  | if (seek_head_.GetId(i) == kMkvCues) | 
|  | cues_index = i; | 
|  | } | 
|  | seek_head_.SetSeekEntry(cues_index, kMkvCues, | 
|  | seek_head_.GetPosition(cluster_index)); | 
|  | seek_head_.SetSeekEntry(cluster_index, kMkvCluster, | 
|  | cues_.Size() + seek_head_.GetPosition(cues_index)); | 
|  | } | 
|  |  | 
|  | bool Segment::Init(IMkvWriter* ptr_writer) { | 
|  | if (!ptr_writer) { | 
|  | return false; | 
|  | } | 
|  | writer_cluster_ = ptr_writer; | 
|  | writer_cues_ = ptr_writer; | 
|  | writer_header_ = ptr_writer; | 
|  | return segment_info_.Init(); | 
|  | } | 
|  |  | 
|  | bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, | 
|  | IMkvWriter* writer) { | 
|  | if (!writer->Seekable() || chunking_) | 
|  | return false; | 
|  | const int64 cluster_offset = | 
|  | cluster_list_[0]->size_position() - GetUIntSize(kMkvCluster); | 
|  |  | 
|  | // Copy the headers. | 
|  | if (!ChunkedCopy(reader, writer, 0, cluster_offset)) | 
|  | return false; | 
|  |  | 
|  | // Recompute cue positions and seek entries. | 
|  | MoveCuesBeforeClusters(); | 
|  |  | 
|  | // Write cues and seek entries. | 
|  | // TODO(vigneshv): As of now, it's safe to call seek_head_.Finalize() for the | 
|  | // second time with a different writer object. But the name Finalize() doesn't | 
|  | // indicate something we want to call more than once. So consider renaming it | 
|  | // to write() or some such. | 
|  | if (!cues_.Write(writer) || !seek_head_.Finalize(writer)) | 
|  | return false; | 
|  |  | 
|  | // Copy the Clusters. | 
|  | if (!ChunkedCopy(reader, writer, cluster_offset, | 
|  | cluster_end_offset_ - cluster_offset)) | 
|  | return false; | 
|  |  | 
|  | // Update the Segment size in case the Cues size has changed. | 
|  | const int64 pos = writer->Position(); | 
|  | const int64 segment_size = writer->Position() - payload_pos_; | 
|  | if (writer->Position(size_position_) || | 
|  | WriteUIntSize(writer, segment_size, 8) || writer->Position(pos)) | 
|  | return false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Segment::Finalize() { | 
|  | if (WriteFramesAll() < 0) | 
|  | return false; | 
|  |  | 
|  | if (mode_ == kFile) { | 
|  | if (cluster_list_size_ > 0) { | 
|  | // Update last cluster's size | 
|  | Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  |  | 
|  | if (!old_cluster || !old_cluster->Finalize()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (chunking_ && chunk_writer_cluster_) { | 
|  | chunk_writer_cluster_->Close(); | 
|  | chunk_count_++; | 
|  | } | 
|  |  | 
|  | const double duration = | 
|  | (static_cast<double>(last_timestamp_) + last_block_duration_) / | 
|  | segment_info_.timecode_scale(); | 
|  | segment_info_.set_duration(duration); | 
|  | if (!segment_info_.Finalize(writer_header_)) | 
|  | return false; | 
|  |  | 
|  | if (output_cues_) | 
|  | if (!seek_head_.AddSeekEntry(kMkvCues, MaxOffset())) | 
|  | return false; | 
|  |  | 
|  | if (chunking_) { | 
|  | if (!chunk_writer_cues_) | 
|  | return false; | 
|  |  | 
|  | char* name = NULL; | 
|  | if (!UpdateChunkName("cues", &name)) | 
|  | return false; | 
|  |  | 
|  | const bool cues_open = chunk_writer_cues_->Open(name); | 
|  | delete[] name; | 
|  | if (!cues_open) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | cluster_end_offset_ = writer_cluster_->Position(); | 
|  |  | 
|  | // Write the seek headers and cues | 
|  | if (output_cues_) | 
|  | if (!cues_.Write(writer_cues_)) | 
|  | return false; | 
|  |  | 
|  | if (!seek_head_.Finalize(writer_header_)) | 
|  | return false; | 
|  |  | 
|  | if (writer_header_->Seekable()) { | 
|  | if (size_position_ == -1) | 
|  | return false; | 
|  |  | 
|  | const int64 segment_size = MaxOffset(); | 
|  | if (segment_size < 1) | 
|  | return false; | 
|  |  | 
|  | const int64 pos = writer_header_->Position(); | 
|  | UpdateDocTypeVersion(); | 
|  | if (doc_type_version_ != doc_type_version_written_) { | 
|  | if (writer_header_->Position(0)) | 
|  | return false; | 
|  |  | 
|  | if (!WriteEbmlHeader(writer_header_, doc_type_version_)) | 
|  | return false; | 
|  | if (writer_header_->Position() != ebml_header_size_) | 
|  | return false; | 
|  |  | 
|  | doc_type_version_written_ = doc_type_version_; | 
|  | } | 
|  |  | 
|  | if (writer_header_->Position(size_position_)) | 
|  | return false; | 
|  |  | 
|  | if (WriteUIntSize(writer_header_, segment_size, 8)) | 
|  | return false; | 
|  |  | 
|  | if (writer_header_->Position(pos)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (chunking_) { | 
|  | // Do not close any writers until the segment size has been written, | 
|  | // otherwise the size may be off. | 
|  | if (!chunk_writer_cues_ || !chunk_writer_header_) | 
|  | return false; | 
|  |  | 
|  | chunk_writer_cues_->Close(); | 
|  | chunk_writer_header_->Close(); | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | Track* Segment::AddTrack(int32 number) { | 
|  | Track* const track = new (std::nothrow) Track(&seed_);  // NOLINT | 
|  |  | 
|  | if (!track) | 
|  | return NULL; | 
|  |  | 
|  | if (!tracks_.AddTrack(track, number)) { | 
|  | delete track; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return track; | 
|  | } | 
|  |  | 
|  | Chapter* Segment::AddChapter() { return chapters_.AddChapter(&seed_); } | 
|  |  | 
|  | Tag* Segment::AddTag() { return tags_.AddTag(); } | 
|  |  | 
|  | uint64 Segment::AddVideoTrack(int32 width, int32 height, int32 number) { | 
|  | VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_);  // NOLINT | 
|  | if (!track) | 
|  | return 0; | 
|  |  | 
|  | track->set_type(Tracks::kVideo); | 
|  | track->set_codec_id(Tracks::kAOMCodecId); | 
|  | track->set_width(width); | 
|  | track->set_height(height); | 
|  |  | 
|  | tracks_.AddTrack(track, number); | 
|  | has_video_ = true; | 
|  |  | 
|  | return track->number(); | 
|  | } | 
|  |  | 
|  | bool Segment::AddCuePoint(uint64 timestamp, uint64 track) { | 
|  | if (cluster_list_size_ < 1) | 
|  | return false; | 
|  |  | 
|  | const Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  | if (!cluster) | 
|  | return false; | 
|  |  | 
|  | CuePoint* const cue = new (std::nothrow) CuePoint();  // NOLINT | 
|  | if (!cue) | 
|  | return false; | 
|  |  | 
|  | cue->set_time(timestamp / segment_info_.timecode_scale()); | 
|  | cue->set_block_number(cluster->blocks_added()); | 
|  | cue->set_cluster_pos(cluster->position_for_cues()); | 
|  | cue->set_track(track); | 
|  | if (!cues_.AddCue(cue)) | 
|  | return false; | 
|  |  | 
|  | new_cuepoint_ = false; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | uint64 Segment::AddAudioTrack(int32 sample_rate, int32 channels, int32 number) { | 
|  | AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_);  // NOLINT | 
|  | if (!track) | 
|  | return 0; | 
|  |  | 
|  | track->set_type(Tracks::kAudio); | 
|  | track->set_codec_id(Tracks::kVorbisCodecId); | 
|  | track->set_sample_rate(sample_rate); | 
|  | track->set_channels(channels); | 
|  |  | 
|  | tracks_.AddTrack(track, number); | 
|  |  | 
|  | return track->number(); | 
|  | } | 
|  |  | 
|  | bool Segment::AddFrame(const uint8* data, uint64 length, uint64 track_number, | 
|  | uint64 timestamp, bool is_key) { | 
|  | if (!data) | 
|  | return false; | 
|  |  | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(timestamp); | 
|  | frame.set_is_key(is_key); | 
|  | return AddGenericFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Segment::AddFrameWithAdditional(const uint8* data, uint64 length, | 
|  | const uint8* additional, | 
|  | uint64 additional_length, uint64 add_id, | 
|  | uint64 track_number, uint64 timestamp, | 
|  | bool is_key) { | 
|  | if (!data || !additional) | 
|  | return false; | 
|  |  | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length) || | 
|  | !frame.AddAdditionalData(additional, additional_length, add_id)) { | 
|  | return false; | 
|  | } | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(timestamp); | 
|  | frame.set_is_key(is_key); | 
|  | return AddGenericFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Segment::AddFrameWithDiscardPadding(const uint8* data, uint64 length, | 
|  | int64 discard_padding, | 
|  | uint64 track_number, uint64 timestamp, | 
|  | bool is_key) { | 
|  | if (!data) | 
|  | return false; | 
|  |  | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_discard_padding(discard_padding); | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(timestamp); | 
|  | frame.set_is_key(is_key); | 
|  | return AddGenericFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Segment::AddMetadata(const uint8* data, uint64 length, uint64 track_number, | 
|  | uint64 timestamp_ns, uint64 duration_ns) { | 
|  | if (!data) | 
|  | return false; | 
|  |  | 
|  | Frame frame; | 
|  | if (!frame.Init(data, length)) | 
|  | return false; | 
|  | frame.set_track_number(track_number); | 
|  | frame.set_timestamp(timestamp_ns); | 
|  | frame.set_duration(duration_ns); | 
|  | frame.set_is_key(true);  // All metadata blocks are keyframes. | 
|  | return AddGenericFrame(&frame); | 
|  | } | 
|  |  | 
|  | bool Segment::AddGenericFrame(const Frame* frame) { | 
|  | if (!frame) | 
|  | return false; | 
|  |  | 
|  | if (!CheckHeaderInfo()) | 
|  | return false; | 
|  |  | 
|  | // Check for non-monotonically increasing timestamps. | 
|  | if (frame->timestamp() < last_timestamp_) | 
|  | return false; | 
|  |  | 
|  | // Check if the track number is valid. | 
|  | if (!tracks_.GetTrackByNumber(frame->track_number())) | 
|  | return false; | 
|  |  | 
|  | if (frame->discard_padding() != 0) | 
|  | doc_type_version_ = 4; | 
|  |  | 
|  | // 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(frame->track_number()) && | 
|  | !force_new_cluster_) { | 
|  | Frame* const new_frame = new (std::nothrow) Frame(); | 
|  | if (!new_frame || !new_frame->CopyFrom(*frame)) | 
|  | return false; | 
|  | return QueueFrame(new_frame); | 
|  | } | 
|  |  | 
|  | if (!DoNewClusterProcessing(frame->track_number(), frame->timestamp(), | 
|  | frame->is_key())) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (cluster_list_size_ < 1) | 
|  | return false; | 
|  |  | 
|  | Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  | if (!cluster) | 
|  | return false; | 
|  |  | 
|  | // If the Frame is not a SimpleBlock, then set the reference_block_timestamp | 
|  | // if it is not set already. | 
|  | bool frame_created = false; | 
|  | if (!frame->CanBeSimpleBlock() && !frame->is_key() && | 
|  | !frame->reference_block_timestamp_set()) { | 
|  | Frame* const new_frame = new (std::nothrow) Frame(); | 
|  | if (!new_frame->CopyFrom(*frame)) | 
|  | return false; | 
|  | new_frame->set_reference_block_timestamp( | 
|  | last_track_timestamp_[frame->track_number() - 1]); | 
|  | frame = new_frame; | 
|  | frame_created = true; | 
|  | } | 
|  |  | 
|  | if (!cluster->AddFrame(frame)) | 
|  | return false; | 
|  |  | 
|  | if (new_cuepoint_ && cues_track_ == frame->track_number()) { | 
|  | if (!AddCuePoint(frame->timestamp(), cues_track_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | last_timestamp_ = frame->timestamp(); | 
|  | last_track_timestamp_[frame->track_number() - 1] = frame->timestamp(); | 
|  | last_block_duration_ = frame->duration(); | 
|  |  | 
|  | if (frame_created) | 
|  | delete frame; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; } | 
|  |  | 
|  | bool Segment::SetChunking(bool chunking, const char* filename) { | 
|  | if (chunk_count_ > 0) | 
|  | return false; | 
|  |  | 
|  | if (chunking) { | 
|  | if (!filename) | 
|  | return false; | 
|  |  | 
|  | // Check if we are being set to what is already set. | 
|  | if (chunking_ && !strcmp(filename, chunking_base_name_)) | 
|  | return true; | 
|  |  | 
|  | const size_t name_length = strlen(filename) + 1; | 
|  | char* const temp = new (std::nothrow) char[name_length];  // NOLINT | 
|  | if (!temp) | 
|  | return false; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(temp, name_length, filename); | 
|  | #else | 
|  | strcpy(temp, filename); | 
|  | #endif | 
|  |  | 
|  | delete[] chunking_base_name_; | 
|  | chunking_base_name_ = temp; | 
|  |  | 
|  | if (!UpdateChunkName("chk", &chunk_name_)) | 
|  | return false; | 
|  |  | 
|  | if (!chunk_writer_cluster_) { | 
|  | chunk_writer_cluster_ = new (std::nothrow) MkvWriter();  // NOLINT | 
|  | if (!chunk_writer_cluster_) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!chunk_writer_cues_) { | 
|  | chunk_writer_cues_ = new (std::nothrow) MkvWriter();  // NOLINT | 
|  | if (!chunk_writer_cues_) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!chunk_writer_header_) { | 
|  | chunk_writer_header_ = new (std::nothrow) MkvWriter();  // NOLINT | 
|  | if (!chunk_writer_header_) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!chunk_writer_cluster_->Open(chunk_name_)) | 
|  | return false; | 
|  |  | 
|  | const size_t header_length = strlen(filename) + strlen(".hdr") + 1; | 
|  | char* const header = new (std::nothrow) char[header_length];  // NOLINT | 
|  | if (!header) | 
|  | return false; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_); | 
|  | strcat_s(header, header_length, ".hdr"); | 
|  | #else | 
|  | strcpy(header, chunking_base_name_); | 
|  | strcat(header, ".hdr"); | 
|  | #endif | 
|  | if (!chunk_writer_header_->Open(header)) { | 
|  | delete[] header; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | writer_cluster_ = chunk_writer_cluster_; | 
|  | writer_cues_ = chunk_writer_cues_; | 
|  | writer_header_ = chunk_writer_header_; | 
|  |  | 
|  | delete[] header; | 
|  | } | 
|  |  | 
|  | chunking_ = chunking; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Segment::CuesTrack(uint64 track_number) { | 
|  | const Track* const track = GetTrackByNumber(track_number); | 
|  | if (!track) | 
|  | return false; | 
|  |  | 
|  | cues_track_ = track_number; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Segment::ForceNewClusterOnNextFrame() { force_new_cluster_ = true; } | 
|  |  | 
|  | Track* Segment::GetTrackByNumber(uint64 track_number) const { | 
|  | return tracks_.GetTrackByNumber(track_number); | 
|  | } | 
|  |  | 
|  | bool Segment::WriteSegmentHeader() { | 
|  | UpdateDocTypeVersion(); | 
|  |  | 
|  | // TODO(fgalligan): Support more than one segment. | 
|  | if (!WriteEbmlHeader(writer_header_, doc_type_version_)) | 
|  | return false; | 
|  | doc_type_version_written_ = doc_type_version_; | 
|  | ebml_header_size_ = static_cast<int32>(writer_header_->Position()); | 
|  |  | 
|  | // Write "unknown" (-1) as segment size value. If mode is kFile, Segment | 
|  | // will write over duration when the file is finalized. | 
|  | if (WriteID(writer_header_, kMkvSegment)) | 
|  | return false; | 
|  |  | 
|  | // Save for later. | 
|  | size_position_ = writer_header_->Position(); | 
|  |  | 
|  | // Write "unknown" (EBML coded -1) as segment size value. We need to write 8 | 
|  | // bytes because if we are going to overwrite the segment size later we do | 
|  | // not know how big our segment will be. | 
|  | if (SerializeInt(writer_header_, kEbmlUnknownValue, 8)) | 
|  | return false; | 
|  |  | 
|  | payload_pos_ = writer_header_->Position(); | 
|  |  | 
|  | if (mode_ == kFile && writer_header_->Seekable()) { | 
|  | // Set the duration > 0.0 so SegmentInfo will write out the duration. When | 
|  | // the muxer is done writing we will set the correct duration and have | 
|  | // SegmentInfo upadte it. | 
|  | segment_info_.set_duration(1.0); | 
|  |  | 
|  | if (!seek_head_.Write(writer_header_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!seek_head_.AddSeekEntry(kMkvInfo, MaxOffset())) | 
|  | return false; | 
|  | if (!segment_info_.Write(writer_header_)) | 
|  | return false; | 
|  |  | 
|  | if (!seek_head_.AddSeekEntry(kMkvTracks, MaxOffset())) | 
|  | return false; | 
|  | if (!tracks_.Write(writer_header_)) | 
|  | return false; | 
|  |  | 
|  | if (chapters_.Count() > 0) { | 
|  | if (!seek_head_.AddSeekEntry(kMkvChapters, MaxOffset())) | 
|  | return false; | 
|  | if (!chapters_.Write(writer_header_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (tags_.Count() > 0) { | 
|  | if (!seek_head_.AddSeekEntry(kMkvTags, MaxOffset())) | 
|  | return false; | 
|  | if (!tags_.Write(writer_header_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) { | 
|  | if (!chunk_writer_header_) | 
|  | return false; | 
|  |  | 
|  | chunk_writer_header_->Close(); | 
|  | } | 
|  |  | 
|  | header_written_ = true; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Here we are testing whether to create a new cluster, given a frame | 
|  | // having time frame_timestamp_ns. | 
|  | // | 
|  | int Segment::TestFrame(uint64 track_number, uint64 frame_timestamp_ns, | 
|  | bool is_key) const { | 
|  | if (force_new_cluster_) | 
|  | return 1; | 
|  |  | 
|  | // If no clusters have been created yet, then create a new cluster | 
|  | // and write this frame immediately, in the new cluster.  This path | 
|  | // should only be followed once, the first time we attempt to write | 
|  | // a frame. | 
|  |  | 
|  | if (cluster_list_size_ <= 0) | 
|  | return 1; | 
|  |  | 
|  | // There exists at least one cluster. We must compare the frame to | 
|  | // the last cluster, in order to determine whether the frame is | 
|  | // written to the existing cluster, or that a new cluster should be | 
|  | // created. | 
|  |  | 
|  | const uint64 timecode_scale = segment_info_.timecode_scale(); | 
|  | const uint64 frame_timecode = frame_timestamp_ns / timecode_scale; | 
|  |  | 
|  | const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  | const uint64 last_cluster_timecode = last_cluster->timecode(); | 
|  |  | 
|  | // For completeness we test for the case when the frame's timecode | 
|  | // is less than the cluster's timecode.  Although in principle that | 
|  | // is allowed, this muxer doesn't actually write clusters like that, | 
|  | // so this indicates a bug somewhere in our algorithm. | 
|  |  | 
|  | if (frame_timecode < last_cluster_timecode)  // should never happen | 
|  | return -1; | 
|  |  | 
|  | // If the frame has a timestamp significantly larger than the last | 
|  | // cluster (in Matroska, cluster-relative timestamps are serialized | 
|  | // using a 16-bit signed integer), then we cannot write this frame | 
|  | // to that cluster, and so we must create a new cluster. | 
|  |  | 
|  | const int64 delta_timecode = frame_timecode - last_cluster_timecode; | 
|  |  | 
|  | if (delta_timecode > kMaxBlockTimecode) | 
|  | return 2; | 
|  |  | 
|  | // We decide to create a new cluster when we have a video keyframe. | 
|  | // This will flush queued (audio) frames, and write the keyframe | 
|  | // immediately, in the newly-created cluster. | 
|  |  | 
|  | if (is_key && tracks_.TrackIsVideo(track_number)) | 
|  | return 1; | 
|  |  | 
|  | // Create a new cluster if we have accumulated too many frames | 
|  | // already, where "too many" is defined as "the total time of frames | 
|  | // in the cluster exceeds a threshold". | 
|  |  | 
|  | const uint64 delta_ns = delta_timecode * timecode_scale; | 
|  |  | 
|  | if (max_cluster_duration_ > 0 && delta_ns >= max_cluster_duration_) | 
|  | return 1; | 
|  |  | 
|  | // This is similar to the case above, with the difference that a new | 
|  | // cluster is created when the size of the current cluster exceeds a | 
|  | // threshold. | 
|  |  | 
|  | const uint64 cluster_size = last_cluster->payload_size(); | 
|  |  | 
|  | if (max_cluster_size_ > 0 && cluster_size >= max_cluster_size_) | 
|  | return 1; | 
|  |  | 
|  | // There's no need to create a new cluster, so emit this frame now. | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) { | 
|  | const int32 new_size = cluster_list_size_ + 1; | 
|  |  | 
|  | if (new_size > cluster_list_capacity_) { | 
|  | // Add more clusters. | 
|  | const int32 new_capacity = | 
|  | (cluster_list_capacity_ <= 0) ? 1 : cluster_list_capacity_ * 2; | 
|  | Cluster** const clusters = | 
|  | new (std::nothrow) Cluster*[new_capacity];  // NOLINT | 
|  | if (!clusters) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < cluster_list_size_; ++i) { | 
|  | clusters[i] = cluster_list_[i]; | 
|  | } | 
|  |  | 
|  | delete[] cluster_list_; | 
|  |  | 
|  | cluster_list_ = clusters; | 
|  | cluster_list_capacity_ = new_capacity; | 
|  | } | 
|  |  | 
|  | if (!WriteFramesLessThan(frame_timestamp_ns)) | 
|  | return false; | 
|  |  | 
|  | if (mode_ == kFile) { | 
|  | if (cluster_list_size_ > 0) { | 
|  | // Update old cluster's size | 
|  | Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  |  | 
|  | if (!old_cluster || !old_cluster->Finalize()) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (output_cues_) | 
|  | new_cuepoint_ = true; | 
|  | } | 
|  |  | 
|  | if (chunking_ && cluster_list_size_ > 0) { | 
|  | chunk_writer_cluster_->Close(); | 
|  | chunk_count_++; | 
|  |  | 
|  | if (!UpdateChunkName("chk", &chunk_name_)) | 
|  | return false; | 
|  | if (!chunk_writer_cluster_->Open(chunk_name_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const uint64 timecode_scale = segment_info_.timecode_scale(); | 
|  | const uint64 frame_timecode = frame_timestamp_ns / timecode_scale; | 
|  |  | 
|  | uint64 cluster_timecode = frame_timecode; | 
|  |  | 
|  | if (frames_size_ > 0) { | 
|  | const Frame* const f = frames_[0];  // earliest queued frame | 
|  | const uint64 ns = f->timestamp(); | 
|  | const uint64 tc = ns / timecode_scale; | 
|  |  | 
|  | if (tc < cluster_timecode) | 
|  | cluster_timecode = tc; | 
|  | } | 
|  |  | 
|  | Cluster*& cluster = cluster_list_[cluster_list_size_]; | 
|  | const int64 offset = MaxOffset(); | 
|  | cluster = new (std::nothrow) Cluster(cluster_timecode,  // NOLINT | 
|  | offset, segment_info_.timecode_scale()); | 
|  | if (!cluster) | 
|  | return false; | 
|  |  | 
|  | if (!cluster->Init(writer_cluster_)) | 
|  | return false; | 
|  |  | 
|  | cluster_list_size_ = new_size; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool Segment::DoNewClusterProcessing(uint64 track_number, | 
|  | uint64 frame_timestamp_ns, bool is_key) { | 
|  | for (;;) { | 
|  | // Based on the characteristics of the current frame and current | 
|  | // cluster, decide whether to create a new cluster. | 
|  | const int result = TestFrame(track_number, frame_timestamp_ns, is_key); | 
|  | if (result < 0)  // error | 
|  | return false; | 
|  |  | 
|  | // Always set force_new_cluster_ to false after TestFrame. | 
|  | force_new_cluster_ = false; | 
|  |  | 
|  | // A non-zero result means create a new cluster. | 
|  | if (result > 0 && !MakeNewCluster(frame_timestamp_ns)) | 
|  | return false; | 
|  |  | 
|  | // Write queued (audio) frames. | 
|  | const int frame_count = WriteFramesAll(); | 
|  | if (frame_count < 0)  // error | 
|  | return false; | 
|  |  | 
|  | // Write the current frame to the current cluster (if TestFrame | 
|  | // returns 0) or to a newly created cluster (TestFrame returns 1). | 
|  | if (result <= 1) | 
|  | return true; | 
|  |  | 
|  | // TestFrame returned 2, which means there was a large time | 
|  | // difference between the cluster and the frame itself.  Do the | 
|  | // test again, comparing the frame to the new cluster. | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Segment::CheckHeaderInfo() { | 
|  | if (!header_written_) { | 
|  | if (!WriteSegmentHeader()) | 
|  | return false; | 
|  |  | 
|  | if (!seek_head_.AddSeekEntry(kMkvCluster, MaxOffset())) | 
|  | return false; | 
|  |  | 
|  | if (output_cues_ && cues_track_ == 0) { | 
|  | // Check for a video track | 
|  | for (uint32 i = 0; i < tracks_.track_entries_size(); ++i) { | 
|  | const Track* const track = tracks_.GetTrackByIndex(i); | 
|  | if (!track) | 
|  | return false; | 
|  |  | 
|  | if (tracks_.TrackIsVideo(track->number())) { | 
|  | cues_track_ = track->number(); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Set first track found | 
|  | if (cues_track_ == 0) { | 
|  | const Track* const track = tracks_.GetTrackByIndex(0); | 
|  | if (!track) | 
|  | return false; | 
|  |  | 
|  | cues_track_ = track->number(); | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Segment::UpdateDocTypeVersion() { | 
|  | for (uint32 index = 0; index < tracks_.track_entries_size(); ++index) { | 
|  | const Track* track = tracks_.GetTrackByIndex(index); | 
|  | if (track == NULL) | 
|  | break; | 
|  | if ((track->codec_delay() || track->seek_pre_roll()) && | 
|  | doc_type_version_ < 4) { | 
|  | doc_type_version_ = 4; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Segment::UpdateChunkName(const char* ext, char** name) const { | 
|  | if (!name || !ext) | 
|  | return false; | 
|  |  | 
|  | char ext_chk[64]; | 
|  | #ifdef _MSC_VER | 
|  | sprintf_s(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext); | 
|  | #else | 
|  | snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext); | 
|  | #endif | 
|  |  | 
|  | const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1; | 
|  | char* const str = new (std::nothrow) char[length];  // NOLINT | 
|  | if (!str) | 
|  | return false; | 
|  |  | 
|  | #ifdef _MSC_VER | 
|  | strcpy_s(str, length - strlen(ext_chk), chunking_base_name_); | 
|  | strcat_s(str, length, ext_chk); | 
|  | #else | 
|  | strcpy(str, chunking_base_name_); | 
|  | strcat(str, ext_chk); | 
|  | #endif | 
|  |  | 
|  | delete[] * name; | 
|  | *name = str; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int64 Segment::MaxOffset() { | 
|  | if (!writer_header_) | 
|  | return -1; | 
|  |  | 
|  | int64 offset = writer_header_->Position() - payload_pos_; | 
|  |  | 
|  | if (chunking_) { | 
|  | for (int32 i = 0; i < cluster_list_size_; ++i) { | 
|  | Cluster* const cluster = cluster_list_[i]; | 
|  | offset += cluster->Size(); | 
|  | } | 
|  |  | 
|  | if (writer_cues_) | 
|  | offset += writer_cues_->Position(); | 
|  | } | 
|  |  | 
|  | return offset; | 
|  | } | 
|  |  | 
|  | bool Segment::QueueFrame(Frame* frame) { | 
|  | const int32 new_size = frames_size_ + 1; | 
|  |  | 
|  | if (new_size > frames_capacity_) { | 
|  | // Add more frames. | 
|  | const int32 new_capacity = (!frames_capacity_) ? 2 : frames_capacity_ * 2; | 
|  |  | 
|  | if (new_capacity < 1) | 
|  | return false; | 
|  |  | 
|  | Frame** const frames = new (std::nothrow) Frame*[new_capacity];  // NOLINT | 
|  | if (!frames) | 
|  | return false; | 
|  |  | 
|  | for (int32 i = 0; i < frames_size_; ++i) { | 
|  | frames[i] = frames_[i]; | 
|  | } | 
|  |  | 
|  | delete[] frames_; | 
|  | frames_ = frames; | 
|  | frames_capacity_ = new_capacity; | 
|  | } | 
|  |  | 
|  | frames_[frames_size_++] = frame; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int Segment::WriteFramesAll() { | 
|  | if (frames_ == NULL) | 
|  | return 0; | 
|  |  | 
|  | if (cluster_list_size_ < 1) | 
|  | return -1; | 
|  |  | 
|  | Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  |  | 
|  | if (!cluster) | 
|  | return -1; | 
|  |  | 
|  | for (int32 i = 0; i < frames_size_; ++i) { | 
|  | Frame*& frame = frames_[i]; | 
|  | // TODO(jzern/vigneshv): using Segment::AddGenericFrame here would limit the | 
|  | // places where |doc_type_version_| needs to be updated. | 
|  | if (frame->discard_padding() != 0) | 
|  | doc_type_version_ = 4; | 
|  | if (!cluster->AddFrame(frame)) | 
|  | return -1; | 
|  |  | 
|  | if (new_cuepoint_ && cues_track_ == frame->track_number()) { | 
|  | if (!AddCuePoint(frame->timestamp(), cues_track_)) | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (frame->timestamp() > last_timestamp_) { | 
|  | last_timestamp_ = frame->timestamp(); | 
|  | last_track_timestamp_[frame->track_number() - 1] = frame->timestamp(); | 
|  | } | 
|  |  | 
|  | delete frame; | 
|  | frame = NULL; | 
|  | } | 
|  |  | 
|  | const int result = frames_size_; | 
|  | frames_size_ = 0; | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool Segment::WriteFramesLessThan(uint64 timestamp) { | 
|  | // Check |cluster_list_size_| to see if this is the first cluster. If it is | 
|  | // the first cluster the audio frames that are less than the first video | 
|  | // timesatmp will be written in a later step. | 
|  | if (frames_size_ > 0 && cluster_list_size_ > 0) { | 
|  | if (!frames_) | 
|  | return false; | 
|  |  | 
|  | Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; | 
|  | if (!cluster) | 
|  | return false; | 
|  |  | 
|  | int32 shift_left = 0; | 
|  |  | 
|  | // TODO(fgalligan): Change this to use the durations of frames instead of | 
|  | // the next frame's start time if the duration is accurate. | 
|  | for (int32 i = 1; i < frames_size_; ++i) { | 
|  | const Frame* const frame_curr = frames_[i]; | 
|  |  | 
|  | if (frame_curr->timestamp() > timestamp) | 
|  | break; | 
|  |  | 
|  | const Frame* const frame_prev = frames_[i - 1]; | 
|  | if (frame_prev->discard_padding() != 0) | 
|  | doc_type_version_ = 4; | 
|  | if (!cluster->AddFrame(frame_prev)) | 
|  | return false; | 
|  |  | 
|  | if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { | 
|  | if (!AddCuePoint(frame_prev->timestamp(), cues_track_)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ++shift_left; | 
|  | if (frame_prev->timestamp() > last_timestamp_) { | 
|  | last_timestamp_ = frame_prev->timestamp(); | 
|  | last_track_timestamp_[frame_prev->track_number() - 1] = | 
|  | frame_prev->timestamp(); | 
|  | } | 
|  |  | 
|  | delete frame_prev; | 
|  | } | 
|  |  | 
|  | if (shift_left > 0) { | 
|  | if (shift_left >= frames_size_) | 
|  | return false; | 
|  |  | 
|  | const int32 new_frames_size = frames_size_ - shift_left; | 
|  | for (int32 i = 0; i < new_frames_size; ++i) { | 
|  | frames_[i] = frames_[i + shift_left]; | 
|  | } | 
|  |  | 
|  | frames_size_ = new_frames_size; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace mkvmuxer |