text: // Copyright (c) 2012 The WebM project authors. All Rights Reserved.
  //
  // Use of this source code is governed by a BSD-style license
  // that can be found in the LICENSE file in the root of the source
  // tree. An additional intellectual property rights grant can be found
  // in the file PATENTS.  All contributing project authors may
  // be found in the AUTHORS file in the root of the source tree.
  
  #include "mkvmuxer/mkvmuxer.h"
  
  #include <stdint.h>
  
  #include <cfloat>
  #include <climits>
  #include <cstdio>
  #include <cstdlib>
  #include <cstring>
  #include <ctime>
  #include <memory>
  #include <new>
  #include <string>
  #include <vector>
  
  #include "common/webmids.h"
  #include "mkvmuxer/mkvmuxerutil.h"
  #include "mkvmuxer/mkvwriter.h"
  #include "mkvparser/mkvparser.h"
  
  namespace mkvmuxer {
  
  const float PrimaryChromaticity::kChromaticityMin = 0.0f;
  const float PrimaryChromaticity::kChromaticityMax = 1.0f;
  const float MasteringMetadata::kMinLuminance = 0.0f;
  const float MasteringMetadata::kMinLuminanceMax = 999.99f;
  const float MasteringMetadata::kMaxLuminanceMax = 9999.99f;
  const float MasteringMetadata::kValueNotPresent = FLT_MAX;
  const uint64_t Colour::kValueNotPresent = UINT64_MAX;
  
  namespace {
  
  const char kDocTypeWebm[] = "webm";
  const char kDocTypeMatroska[] = "matroska";
  
  // 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;
  }
  
  typedef std::unique_ptr<PrimaryChromaticity> PrimaryChromaticityPtr;
  bool CopyChromaticity(const PrimaryChromaticity* src,
                        PrimaryChromaticityPtr* dst) {
    if (!dst)
      return false;
  
    dst->reset(new (std::nothrow) PrimaryChromaticity(src->x(), src->y()));
    if (!dst->get())
      return false;
  
    return true;
  }
  
  }  // namespace
  
  ///////////////////////////////////////////////////////////////
  //
  // IMkvWriter Class
  
  IMkvWriter::IMkvWriter() {}
  
  IMkvWriter::~IMkvWriter() {}
  
  bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version,
                       const char* const doc_type) {
    // Level 0
    uint64_t size =
        EbmlElementSize(libwebm::kMkvEBMLVersion, static_cast<uint64>(1));
    size += EbmlElementSize(libwebm::kMkvEBMLReadVersion, static_cast<uint64>(1));
    size += EbmlElementSize(libwebm::kMkvEBMLMaxIDLength, static_cast<uint64>(4));
    size +=
        EbmlElementSize(libwebm::kMkvEBMLMaxSizeLength, static_cast<uint64>(8));
    size += EbmlElementSize(libwebm::kMkvDocType, doc_type);
    size += EbmlElementSize(libwebm::kMkvDocTypeVersion,
                            static_cast<uint64>(doc_type_version));
    size +=
        EbmlElementSize(libwebm::kMkvDocTypeReadVersion, static_cast<uint64>(2));
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvEBML, size))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvEBMLVersion,
                          static_cast<uint64>(1))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvEBMLReadVersion,
                          static_cast<uint64>(1))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxIDLength,
                          static_cast<uint64>(4))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxSizeLength,
                          static_cast<uint64>(8))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvDocType, doc_type))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeVersion,
                          static_cast<uint64>(doc_type_version))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeReadVersion,
                          static_cast<uint64>(2))) {
      return false;
    }
  
    return true;
  }
  
  bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version) {
    return WriteEbmlHeader(writer, doc_type_version, kDocTypeWebm);
  }
  
  bool WriteEbmlHeader(IMkvWriter* writer) {
    return WriteEbmlHeader(writer, mkvmuxer::Segment::kDefaultDocTypeVersion);
  }
  
  bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst,
                   int64_t start, int64_t size) {
    // TODO(vigneshv): Check if this is a reasonable value.
    const uint32_t kBufSize = 2048;
    uint8_t* buf = new uint8_t[kBufSize];
    int64_t offset = start;
    while (size > 0) {
      const int64_t read_len = (size > kBufSize) ? kBufSize : size;
      if (source->Read(offset, static_cast<long>(read_len), buf))
        return false;
      dst->Write(buf, static_cast<uint32_t>(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),
        duration_set_(false),
        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();
    duration_set_ = frame.duration_set();
    is_key_ = frame.is_key();
    track_number_ = frame.track_number();
    timestamp_ = frame.timestamp();
    discard_padding_ = frame.discard_padding();
    reference_block_timestamp_ = frame.reference_block_timestamp();
    reference_block_timestamp_set_ = frame.reference_block_timestamp_set();
    return true;
  }
  
  bool Frame::Init(const uint8_t* frame, uint64_t length) {
    uint8_t* const data =
        new (std::nothrow) uint8_t[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_t* additional, uint64_t length,
                                uint64_t add_id) {
    uint8_t* const data =
        new (std::nothrow) uint8_t[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_duration(uint64_t duration) {
    duration_ = duration;
    duration_set_ = true;
  }
  
  void Frame::set_reference_block_timestamp(int64_t 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_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
                                    static_cast<uint64>(cluster_pos_));
    size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
    if (output_block_number_ && block_number_ > 1)
      size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
                              static_cast<uint64>(block_number_));
    const uint64_t track_pos_size =
        EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
    const uint64_t payload_size =
        EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
        track_pos_size;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvCuePoint, payload_size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvCueTime,
                          static_cast<uint64>(time_))) {
      return false;
    }
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvCueTrackPositions, size))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvCueTrack,
                          static_cast<uint64>(track_))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvCueClusterPosition,
                          static_cast<uint64>(cluster_pos_))) {
      return false;
    }
    if (output_block_number_ && block_number_ > 1) {
      if (!WriteEbmlElement(writer, libwebm::kMkvCueBlockNumber,
                            static_cast<uint64>(block_number_))) {
        return false;
      }
    }
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0)
      return false;
  
    if (stop_position - payload_position != static_cast<int64_t>(payload_size))
      return false;
  
    return true;
  }
  
  uint64_t CuePoint::PayloadSize() const {
    uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
                                    static_cast<uint64>(cluster_pos_));
    size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
    if (output_block_number_ && block_number_ > 1)
      size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
                              static_cast<uint64>(block_number_));
    const uint64_t track_pos_size =
        EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
    const uint64_t payload_size =
        EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
        track_pos_size;
  
    return payload_size;
  }
  
  uint64_t CuePoint::Size() const {
    const uint64_t payload_size = PayloadSize();
    return EbmlMasterElementSize(libwebm::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_t 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_t 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_t 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_t index) const {
    if (cue_entries_ == NULL)
      return NULL;
  
    if (index >= cue_entries_size_)
      return NULL;
  
    return cue_entries_[index];
  }
  
  uint64_t Cues::Size() {
    uint64_t size = 0;
    for (int32_t i = 0; i < cue_entries_size_; ++i)
      size += GetCueByIndex(i)->Size();
    size += EbmlMasterElementSize(libwebm::kMkvCues, size);
    return size;
  }
  
  bool Cues::Write(IMkvWriter* writer) const {
    if (!writer)
      return false;
  
    uint64_t size = 0;
    for (int32_t i = 0; i < cue_entries_size_; ++i) {
      const CuePoint* const cue = GetCueByIndex(i);
  
      if (!cue)
        return false;
  
      size += cue->Size();
    }
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvCues, size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    for (int32_t i = 0; i < cue_entries_size_; ++i) {
      const CuePoint* const cue = GetCueByIndex(i);
  
      if (!cue->Write(writer))
        return false;
    }
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0)
      return false;
  
    if (stop_position - payload_position != static_cast<int64_t>(size))
      return false;
  
    return true;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // ContentEncAESSettings Class
  
  ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {}
  
  uint64_t ContentEncAESSettings::Size() const {
    const uint64_t payload = PayloadSize();
    const uint64_t size =
        EbmlMasterElementSize(libwebm::kMkvContentEncAESSettings, payload) +
        payload;
    return size;
  }
  
  bool ContentEncAESSettings::Write(IMkvWriter* writer) const {
    const uint64_t payload = PayloadSize();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncAESSettings,
                                payload))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvAESSettingsCipherMode,
                          static_cast<uint64>(cipher_mode_))) {
      return false;
    }
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(payload))
      return false;
  
    return true;
  }
  
  uint64_t ContentEncAESSettings::PayloadSize() const {
    uint64_t size = EbmlElementSize(libwebm::kMkvAESSettingsCipherMode,
                                    static_cast<uint64>(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_t* id, uint64_t length) {
    if (!id || length < 1)
      return false;
  
    delete[] enc_key_id_;
  
    enc_key_id_ =
        new (std::nothrow) uint8_t[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_t ContentEncoding::Size() const {
    const uint64_t encryption_size = EncryptionSize();
    const uint64_t encoding_size = EncodingSize(0, encryption_size);
    const uint64_t encodings_size =
        EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
        encoding_size;
  
    return encodings_size;
  }
  
  bool ContentEncoding::Write(IMkvWriter* writer) const {
    const uint64_t encryption_size = EncryptionSize();
    const uint64_t encoding_size = EncodingSize(0, encryption_size);
    const uint64_t size =
        EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
        encoding_size;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncoding,
                                encoding_size))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingOrder,
                          static_cast<uint64>(encoding_order_)))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingScope,
                          static_cast<uint64>(encoding_scope_)))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingType,
                          static_cast<uint64>(encoding_type_)))
      return false;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncryption,
                                encryption_size))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvContentEncAlgo,
                          static_cast<uint64>(enc_algo_))) {
      return false;
    }
    if (!WriteEbmlElement(writer, libwebm::kMkvContentEncKeyID, enc_key_id_,
                          enc_key_id_length_))
      return false;
  
    if (!enc_aes_settings_.Write(writer))
      return false;
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(size))
      return false;
  
    return true;
  }
  
  uint64_t ContentEncoding::EncodingSize(uint64_t compresion_size,
                                         uint64_t encryption_size) const {
    // TODO(fgalligan): Add support for compression settings.
    if (compresion_size != 0)
      return 0;
  
    uint64_t encoding_size = 0;
  
    if (encryption_size > 0) {
      encoding_size +=
          EbmlMasterElementSize(libwebm::kMkvContentEncryption, encryption_size) +
          encryption_size;
    }
    encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingType,
                                     static_cast<uint64>(encoding_type_));
    encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingScope,
                                     static_cast<uint64>(encoding_scope_));
    encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingOrder,
                                     static_cast<uint64>(encoding_order_));
  
    return encoding_size;
  }
  
  uint64_t ContentEncoding::EncryptionSize() const {
    const uint64_t aes_size = enc_aes_settings_.Size();
  
    uint64_t encryption_size = EbmlElementSize(libwebm::kMkvContentEncKeyID,
                                               enc_key_id_, enc_key_id_length_);
    encryption_size += EbmlElementSize(libwebm::kMkvContentEncAlgo,
                                       static_cast<uint64>(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_t 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_t 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_t 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_t index) const {
    if (content_encoding_entries_ == NULL)
      return NULL;
  
    if (index >= content_encoding_entries_size_)
      return NULL;
  
    return content_encoding_entries_[index];
  }
  
  uint64_t Track::PayloadSize() const {
    uint64_t size =
        EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
    size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
    size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
    if (codec_id_)
      size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
    if (codec_private_)
      size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
                              codec_private_length_);
    if (language_)
      size += EbmlElementSize(libwebm::kMkvLanguage, language_);
    if (name_)
      size += EbmlElementSize(libwebm::kMkvName, name_);
    if (max_block_additional_id_) {
      size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
                              static_cast<uint64>(max_block_additional_id_));
    }
    if (codec_delay_) {
      size += EbmlElementSize(libwebm::kMkvCodecDelay,
                              static_cast<uint64>(codec_delay_));
    }
    if (seek_pre_roll_) {
      size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
                              static_cast<uint64>(seek_pre_roll_));
    }
    if (default_duration_) {
      size += EbmlElementSize(libwebm::kMkvDefaultDuration,
                              static_cast<uint64>(default_duration_));
    }
  
    if (content_encoding_entries_size_ > 0) {
      uint64_t content_encodings_size = 0;
      for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
        ContentEncoding* const encoding = content_encoding_entries_[i];
        content_encodings_size += encoding->Size();
      }
  
      size += EbmlMasterElementSize(libwebm::kMkvContentEncodings,
                                    content_encodings_size) +
              content_encodings_size;
    }
  
    return size;
  }
  
  uint64_t Track::Size() const {
    uint64_t size = PayloadSize();
    size += EbmlMasterElementSize(libwebm::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;
  
    // AV1 tracks require a CodecPrivate. See
    // https://github.com/Matroska-Org/matroska-specification/blob/av1-mappin/codec/av1.md
    // TODO(tomfinegan): Update the above link to the AV1 Matroska mappings to
    // point to a stable version once it is finalized, or our own WebM mappings
    // page on webmproject.org should we decide to release them.
    if (!strcmp(codec_id_, Tracks::kAv1CodecId) && !codec_private_)
      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_t payload_size = PayloadSize();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvTrackEntry, payload_size))
      return false;
  
    uint64_t size =
        EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
    size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
    size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
    if (codec_id_)
      size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
    if (codec_private_)
      size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
                              static_cast<uint64>(codec_private_length_));
    if (language_)
      size += EbmlElementSize(libwebm::kMkvLanguage, language_);
    if (name_)
      size += EbmlElementSize(libwebm::kMkvName, name_);
    if (max_block_additional_id_)
      size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
                              static_cast<uint64>(max_block_additional_id_));
    if (codec_delay_)
      size += EbmlElementSize(libwebm::kMkvCodecDelay,
                              static_cast<uint64>(codec_delay_));
    if (seek_pre_roll_)
      size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
                              static_cast<uint64>(seek_pre_roll_));
    if (default_duration_)
      size += EbmlElementSize(libwebm::kMkvDefaultDuration,
                              static_cast<uint64>(default_duration_));
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvTrackNumber,
                          static_cast<uint64>(number_)))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvTrackUID,
                          static_cast<uint64>(uid_)))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvTrackType,
                          static_cast<uint64>(type_)))
      return false;
    if (max_block_additional_id_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvMaxBlockAdditionID,
                            static_cast<uint64>(max_block_additional_id_))) {
        return false;
      }
    }
    if (codec_delay_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvCodecDelay,
                            static_cast<uint64>(codec_delay_)))
        return false;
    }
    if (seek_pre_roll_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvSeekPreRoll,
                            static_cast<uint64>(seek_pre_roll_)))
        return false;
    }
    if (default_duration_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvDefaultDuration,
                            static_cast<uint64>(default_duration_)))
        return false;
    }
    if (codec_id_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvCodecID, codec_id_))
        return false;
    }
    if (codec_private_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvCodecPrivate, codec_private_,
                            static_cast<uint64>(codec_private_length_)))
        return false;
    }
    if (language_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvLanguage, language_))
        return false;
    }
    if (name_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvName, name_))
        return false;
    }
  
    int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(size))
      return false;
  
    if (content_encoding_entries_size_ > 0) {
      uint64_t content_encodings_size = 0;
      for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
        ContentEncoding* const encoding = content_encoding_entries_[i];
        content_encodings_size += encoding->Size();
      }
  
      if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncodings,
                                  content_encodings_size))
        return false;
  
      for (uint32_t 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_t* codec_private, uint64_t length) {
    if (!codec_private || length < 1)
      return false;
  
    delete[] codec_private_;
  
    codec_private_ =
        new (std::nothrow) uint8_t[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
      }
    }
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // Colour and its child elements
  
  uint64_t PrimaryChromaticity::PrimaryChromaticitySize(
      libwebm::MkvId x_id, libwebm::MkvId y_id) const {
    return EbmlElementSize(x_id, x_) + EbmlElementSize(y_id, y_);
  }
  
  bool PrimaryChromaticity::Write(IMkvWriter* writer, libwebm::MkvId x_id,
                                  libwebm::MkvId y_id) const {
    if (!Valid()) {
      return false;
    }
    return WriteEbmlElement(writer, x_id, x_) &&
           WriteEbmlElement(writer, y_id, y_);
  }
  
  bool PrimaryChromaticity::Valid() const {
    return (x_ >= kChromaticityMin && x_ <= kChromaticityMax &&
            y_ >= kChromaticityMin && y_ <= kChromaticityMax);
  }
  
  uint64_t MasteringMetadata::MasteringMetadataSize() const {
    uint64_t size = PayloadSize();
  
    if (size > 0)
      size += EbmlMasterElementSize(libwebm::kMkvMasteringMetadata, size);
  
    return size;
  }
  
  bool MasteringMetadata::Valid() const {
    if (luminance_min_ != kValueNotPresent) {
      if (luminance_min_ < kMinLuminance || luminance_min_ > kMinLuminanceMax ||
          luminance_min_ > luminance_max_) {
        return false;
      }
    }
    if (luminance_max_ != kValueNotPresent) {
      if (luminance_max_ < kMinLuminance || luminance_max_ > kMaxLuminanceMax ||
          luminance_max_ < luminance_min_) {
        return false;
      }
    }
    if (r_ && !r_->Valid())
      return false;
    if (g_ && !g_->Valid())
      return false;
    if (b_ && !b_->Valid())
      return false;
    if (white_point_ && !white_point_->Valid())
      return false;
  
    return true;
  }
  
  bool MasteringMetadata::Write(IMkvWriter* writer) const {
    const uint64_t size = PayloadSize();
  
    // Don't write an empty element.
    if (size == 0)
      return true;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvMasteringMetadata, size))
      return false;
    if (luminance_max_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvLuminanceMax, luminance_max_)) {
      return false;
    }
    if (luminance_min_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvLuminanceMin, luminance_min_)) {
      return false;
    }
    if (r_ &&
        !r_->Write(writer, libwebm::kMkvPrimaryRChromaticityX,
                   libwebm::kMkvPrimaryRChromaticityY)) {
      return false;
    }
    if (g_ &&
        !g_->Write(writer, libwebm::kMkvPrimaryGChromaticityX,
                   libwebm::kMkvPrimaryGChromaticityY)) {
      return false;
    }
    if (b_ &&
        !b_->Write(writer, libwebm::kMkvPrimaryBChromaticityX,
                   libwebm::kMkvPrimaryBChromaticityY)) {
      return false;
    }
    if (white_point_ &&
        !white_point_->Write(writer, libwebm::kMkvWhitePointChromaticityX,
                             libwebm::kMkvWhitePointChromaticityY)) {
      return false;
    }
  
    return true;
  }
  
  bool MasteringMetadata::SetChromaticity(
      const PrimaryChromaticity* r, const PrimaryChromaticity* g,
      const PrimaryChromaticity* b, const PrimaryChromaticity* white_point) {
    PrimaryChromaticityPtr r_ptr(nullptr);
    if (r) {
      if (!CopyChromaticity(r, &r_ptr))
        return false;
    }
    PrimaryChromaticityPtr g_ptr(nullptr);
    if (g) {
      if (!CopyChromaticity(g, &g_ptr))
        return false;
    }
    PrimaryChromaticityPtr b_ptr(nullptr);
    if (b) {
      if (!CopyChromaticity(b, &b_ptr))
        return false;
    }
    PrimaryChromaticityPtr wp_ptr(nullptr);
    if (white_point) {
      if (!CopyChromaticity(white_point, &wp_ptr))
        return false;
    }
  
    r_ = r_ptr.release();
    g_ = g_ptr.release();
    b_ = b_ptr.release();
    white_point_ = wp_ptr.release();
    return true;
  }
  
  uint64_t MasteringMetadata::PayloadSize() const {
    uint64_t size = 0;
  
    if (luminance_max_ != kValueNotPresent)
      size += EbmlElementSize(libwebm::kMkvLuminanceMax, luminance_max_);
    if (luminance_min_ != kValueNotPresent)
      size += EbmlElementSize(libwebm::kMkvLuminanceMin, luminance_min_);
  
    if (r_) {
      size += r_->PrimaryChromaticitySize(libwebm::kMkvPrimaryRChromaticityX,
                                          libwebm::kMkvPrimaryRChromaticityY);
    }
    if (g_) {
      size += g_->PrimaryChromaticitySize(libwebm::kMkvPrimaryGChromaticityX,
                                          libwebm::kMkvPrimaryGChromaticityY);
    }
    if (b_) {
      size += b_->PrimaryChromaticitySize(libwebm::kMkvPrimaryBChromaticityX,
                                          libwebm::kMkvPrimaryBChromaticityY);
    }
    if (white_point_) {
      size += white_point_->PrimaryChromaticitySize(
          libwebm::kMkvWhitePointChromaticityX,
          libwebm::kMkvWhitePointChromaticityY);
    }
  
    return size;
  }
  
  uint64_t Colour::ColourSize() const {
    uint64_t size = PayloadSize();
  
    if (size > 0)
      size += EbmlMasterElementSize(libwebm::kMkvColour, size);
  
    return size;
  }
  
  bool Colour::Valid() const {
    if (mastering_metadata_ && !mastering_metadata_->Valid())
      return false;
    if (matrix_coefficients_ != kValueNotPresent &&
        !IsMatrixCoefficientsValueValid(matrix_coefficients_)) {
      return false;
    }
    if (chroma_siting_horz_ != kValueNotPresent &&
        !IsChromaSitingHorzValueValid(chroma_siting_horz_)) {
      return false;
    }
    if (chroma_siting_vert_ != kValueNotPresent &&
        !IsChromaSitingVertValueValid(chroma_siting_vert_)) {
      return false;
    }
    if (range_ != kValueNotPresent && !IsColourRangeValueValid(range_))
      return false;
    if (transfer_characteristics_ != kValueNotPresent &&
        !IsTransferCharacteristicsValueValid(transfer_characteristics_)) {
      return false;
    }
    if (primaries_ != kValueNotPresent && !IsPrimariesValueValid(primaries_))
      return false;
  
    return true;
  }
  
  bool Colour::Write(IMkvWriter* writer) const {
    const uint64_t size = PayloadSize();
  
    // Don't write an empty element.
    if (size == 0)
      return true;
  
    // Don't write an invalid element.
    if (!Valid())
      return false;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvColour, size))
      return false;
  
    if (matrix_coefficients_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvMatrixCoefficients,
                          static_cast<uint64>(matrix_coefficients_))) {
      return false;
    }
    if (bits_per_channel_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvBitsPerChannel,
                          static_cast<uint64>(bits_per_channel_))) {
      return false;
    }
    if (chroma_subsampling_horz_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingHorz,
                          static_cast<uint64>(chroma_subsampling_horz_))) {
      return false;
    }
    if (chroma_subsampling_vert_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingVert,
                          static_cast<uint64>(chroma_subsampling_vert_))) {
      return false;
    }
  
    if (cb_subsampling_horz_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingHorz,
                          static_cast<uint64>(cb_subsampling_horz_))) {
      return false;
    }
    if (cb_subsampling_vert_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingVert,
                          static_cast<uint64>(cb_subsampling_vert_))) {
      return false;
    }
    if (chroma_siting_horz_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvChromaSitingHorz,
                          static_cast<uint64>(chroma_siting_horz_))) {
      return false;
    }
    if (chroma_siting_vert_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvChromaSitingVert,
                          static_cast<uint64>(chroma_siting_vert_))) {
      return false;
    }
    if (range_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvRange,
                          static_cast<uint64>(range_))) {
      return false;
    }
    if (transfer_characteristics_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvTransferCharacteristics,
                          static_cast<uint64>(transfer_characteristics_))) {
      return false;
    }
    if (primaries_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvPrimaries,
                          static_cast<uint64>(primaries_))) {
      return false;
    }
    if (max_cll_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvMaxCLL,
                          static_cast<uint64>(max_cll_))) {
      return false;
    }
    if (max_fall_ != kValueNotPresent &&
        !WriteEbmlElement(writer, libwebm::kMkvMaxFALL,
                          static_cast<uint64>(max_fall_))) {
      return false;
    }
  
    if (mastering_metadata_ && !mastering_metadata_->Write(writer))
      return false;
  
    return true;
  }
  
  bool Colour::SetMasteringMetadata(const MasteringMetadata& mastering_metadata) {
    std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata());
    if (!mm_ptr.get())
      return false;
  
    mm_ptr->set_luminance_max(mastering_metadata.luminance_max());
    mm_ptr->set_luminance_min(mastering_metadata.luminance_min());
  
    if (!mm_ptr->SetChromaticity(mastering_metadata.r(), mastering_metadata.g(),
                                 mastering_metadata.b(),
                                 mastering_metadata.white_point())) {
      return false;
    }
  
    delete mastering_metadata_;
    mastering_metadata_ = mm_ptr.release();
    return true;
  }
  
  uint64_t Colour::PayloadSize() const {
    uint64_t size = 0;
  
    if (matrix_coefficients_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvMatrixCoefficients,
                              static_cast<uint64>(matrix_coefficients_));
    }
    if (bits_per_channel_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvBitsPerChannel,
                              static_cast<uint64>(bits_per_channel_));
    }
    if (chroma_subsampling_horz_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvChromaSubsamplingHorz,
                              static_cast<uint64>(chroma_subsampling_horz_));
    }
    if (chroma_subsampling_vert_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvChromaSubsamplingVert,
                              static_cast<uint64>(chroma_subsampling_vert_));
    }
    if (cb_subsampling_horz_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvCbSubsamplingHorz,
                              static_cast<uint64>(cb_subsampling_horz_));
    }
    if (cb_subsampling_vert_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvCbSubsamplingVert,
                              static_cast<uint64>(cb_subsampling_vert_));
    }
    if (chroma_siting_horz_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvChromaSitingHorz,
                              static_cast<uint64>(chroma_siting_horz_));
    }
    if (chroma_siting_vert_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvChromaSitingVert,
                              static_cast<uint64>(chroma_siting_vert_));
    }
    if (range_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvRange, static_cast<uint64>(range_));
    }
    if (transfer_characteristics_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvTransferCharacteristics,
                              static_cast<uint64>(transfer_characteristics_));
    }
    if (primaries_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvPrimaries,
                              static_cast<uint64>(primaries_));
    }
    if (max_cll_ != kValueNotPresent) {
      size += EbmlElementSize(libwebm::kMkvMaxCLL, static_cast<uint64>(max_cll_));
    }
    if (max_fall_ != kValueNotPresent) {
      size +=
          EbmlElementSize(libwebm::kMkvMaxFALL, static_cast<uint64>(max_fall_));
    }
  
    if (mastering_metadata_)
      size += mastering_metadata_->MasteringMetadataSize();
  
    return size;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // Projection element
  
  uint64_t Projection::ProjectionSize() const {
    uint64_t size = PayloadSize();
  
    if (size > 0)
      size += EbmlMasterElementSize(libwebm::kMkvProjection, size);
  
    return size;
  }
  
  bool Projection::Write(IMkvWriter* writer) const {
    const uint64_t size = PayloadSize();
  
    // Don't write an empty element.
    if (size == 0)
      return true;
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvProjection, size))
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvProjectionType,
                          static_cast<uint64>(type_))) {
      return false;
    }
  
    if (private_data_length_ > 0 && private_data_ != NULL &&
        !WriteEbmlElement(writer, libwebm::kMkvProjectionPrivate, private_data_,
                          private_data_length_)) {
      return false;
    }
  
    if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseYaw, pose_yaw_))
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPosePitch,
                          pose_pitch_)) {
      return false;
    }
  
    if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseRoll, pose_roll_)) {
      return false;
    }
  
    return true;
  }
  
  bool Projection::SetProjectionPrivate(const uint8_t* data,
                                        uint64_t data_length) {
    if (data == NULL || data_length == 0) {
      return false;
    }
  
    if (data_length != static_cast<size_t>(data_length)) {
      return false;
    }
  
    uint8_t* new_private_data =
        new (std::nothrow) uint8_t[static_cast<size_t>(data_length)];
    if (new_private_data == NULL) {
      return false;
    }
  
    delete[] private_data_;
    private_data_ = new_private_data;
    private_data_length_ = data_length;
    memcpy(private_data_, data, static_cast<size_t>(data_length));
  
    return true;
  }
  
  uint64_t Projection::PayloadSize() const {
    uint64_t size =
        EbmlElementSize(libwebm::kMkvProjection, static_cast<uint64>(type_));
  
    if (private_data_length_ > 0 && private_data_ != NULL) {
      size += EbmlElementSize(libwebm::kMkvProjectionPrivate, private_data_,
                              private_data_length_);
    }
  
    size += EbmlElementSize(libwebm::kMkvProjectionPoseYaw, pose_yaw_);
    size += EbmlElementSize(libwebm::kMkvProjectionPosePitch, pose_pitch_);
    size += EbmlElementSize(libwebm::kMkvProjectionPoseRoll, pose_roll_);
  
    return size;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // VideoTrack Class
  
  VideoTrack::VideoTrack(unsigned int* seed)
      : Track(seed),
        display_height_(0),
        display_width_(0),
        pixel_height_(0),
        pixel_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),
        colour_space_(NULL),
        colour_(NULL),
        projection_(NULL) {}
  
  VideoTrack::~VideoTrack() {
    delete colour_;
    delete projection_;
  }
  
  bool VideoTrack::SetStereoMode(uint64_t 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_t alpha_mode) {
    if (alpha_mode != kNoAlpha && alpha_mode != kAlpha)
      return false;
  
    alpha_mode_ = alpha_mode;
    return true;
  }
  
  uint64_t VideoTrack::PayloadSize() const {
    const uint64_t parent_size = Track::PayloadSize();
  
    uint64_t size = VideoPayloadSize();
    size += EbmlMasterElementSize(libwebm::kMkvVideo, size);
  
    return parent_size + size;
  }
  
  bool VideoTrack::Write(IMkvWriter* writer) const {
    if (!Track::Write(writer))
      return false;
  
    const uint64_t size = VideoPayloadSize();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvVideo, size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(
            writer, libwebm::kMkvPixelWidth,
            static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_)))
      return false;
    if (!WriteEbmlElement(
            writer, libwebm::kMkvPixelHeight,
            static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_)))
      return false;
    if (display_width_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvDisplayWidth,
                            static_cast<uint64>(display_width_)))
        return false;
    }
    if (display_height_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvDisplayHeight,
                            static_cast<uint64>(display_height_)))
        return false;
    }
    if (crop_left_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropLeft,
                            static_cast<uint64>(crop_left_)))
        return false;
    }
    if (crop_right_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropRight,
                            static_cast<uint64>(crop_right_)))
        return false;
    }
    if (crop_top_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropTop,
                            static_cast<uint64>(crop_top_)))
        return false;
    }
    if (crop_bottom_ > 0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropBottom,
                            static_cast<uint64>(crop_bottom_)))
        return false;
    }
    if (stereo_mode_ > kMono) {
      if (!WriteEbmlElement(writer, libwebm::kMkvStereoMode,
                            static_cast<uint64>(stereo_mode_)))
        return false;
    }
    if (alpha_mode_ > kNoAlpha) {
      if (!WriteEbmlElement(writer, libwebm::kMkvAlphaMode,
                            static_cast<uint64>(alpha_mode_)))
        return false;
    }
    if (colour_space_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvColourSpace, colour_space_))
        return false;
    }
    if (frame_rate_ > 0.0) {
      if (!WriteEbmlElement(writer, libwebm::kMkvFrameRate,
                            static_cast<float>(frame_rate_))) {
        return false;
      }
    }
    if (colour_) {
      if (!colour_->Write(writer))
        return false;
    }
    if (projection_) {
      if (!projection_->Write(writer))
        return false;
    }
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(size)) {
      return false;
    }
  
    return true;
  }
  
  void VideoTrack::set_colour_space(const char* colour_space) {
    if (colour_space) {
      delete[] colour_space_;
  
      const size_t length = strlen(colour_space) + 1;
      colour_space_ = new (std::nothrow) char[length];  // NOLINT
      if (colour_space_) {
  #ifdef _MSC_VER
        strcpy_s(colour_space_, length, colour_space);
  #else
        strcpy(colour_space_, colour_space);
  #endif
      }
    }
  }
  
  bool VideoTrack::SetColour(const Colour& colour) {
    std::unique_ptr<Colour> colour_ptr(new Colour());
    if (!colour_ptr.get())
      return false;
  
    if (colour.mastering_metadata()) {
      if (!colour_ptr->SetMasteringMetadata(*colour.mastering_metadata()))
        return false;
    }
  
    colour_ptr->set_matrix_coefficients(colour.matrix_coefficients());
    colour_ptr->set_bits_per_channel(colour.bits_per_channel());
    colour_ptr->set_chroma_subsampling_horz(colour.chroma_subsampling_horz());
    colour_ptr->set_chroma_subsampling_vert(colour.chroma_subsampling_vert());
    colour_ptr->set_cb_subsampling_horz(colour.cb_subsampling_horz());
    colour_ptr->set_cb_subsampling_vert(colour.cb_subsampling_vert());
    colour_ptr->set_chroma_siting_horz(colour.chroma_siting_horz());
    colour_ptr->set_chroma_siting_vert(colour.chroma_siting_vert());
    colour_ptr->set_range(colour.range());
    colour_ptr->set_transfer_characteristics(colour.transfer_characteristics());
    colour_ptr->set_primaries(colour.primaries());
    colour_ptr->set_max_cll(colour.max_cll());
    colour_ptr->set_max_fall(colour.max_fall());
    delete colour_;
    colour_ = colour_ptr.release();
    return true;
  }
  
  bool VideoTrack::SetProjection(const Projection& projection) {
    std::unique_ptr<Projection> projection_ptr(new Projection());
    if (!projection_ptr.get())
      return false;
  
    if (projection.private_data()) {
      if (!projection_ptr->SetProjectionPrivate(
              projection.private_data(), projection.private_data_length())) {
        return false;
      }
    }
  
    projection_ptr->set_type(projection.type());
    projection_ptr->set_pose_yaw(projection.pose_yaw());
    projection_ptr->set_pose_pitch(projection.pose_pitch());
    projection_ptr->set_pose_roll(projection.pose_roll());
    delete projection_;
    projection_ = projection_ptr.release();
    return true;
  }
  
  uint64_t VideoTrack::VideoPayloadSize() const {
    uint64_t size = EbmlElementSize(
        libwebm::kMkvPixelWidth,
        static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_));
    size += EbmlElementSize(
        libwebm::kMkvPixelHeight,
        static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_));
    if (display_width_ > 0)
      size += EbmlElementSize(libwebm::kMkvDisplayWidth,
                              static_cast<uint64>(display_width_));
    if (display_height_ > 0)
      size += EbmlElementSize(libwebm::kMkvDisplayHeight,
                              static_cast<uint64>(display_height_));
    if (crop_left_ > 0)
      size += EbmlElementSize(libwebm::kMkvPixelCropLeft,
                              static_cast<uint64>(crop_left_));
    if (crop_right_ > 0)
      size += EbmlElementSize(libwebm::kMkvPixelCropRight,
                              static_cast<uint64>(crop_right_));
    if (crop_top_ > 0)
      size += EbmlElementSize(libwebm::kMkvPixelCropTop,
                              static_cast<uint64>(crop_top_));
    if (crop_bottom_ > 0)
      size += EbmlElementSize(libwebm::kMkvPixelCropBottom,
                              static_cast<uint64>(crop_bottom_));
    if (stereo_mode_ > kMono)
      size += EbmlElementSize(libwebm::kMkvStereoMode,
                              static_cast<uint64>(stereo_mode_));
    if (alpha_mode_ > kNoAlpha)
      size += EbmlElementSize(libwebm::kMkvAlphaMode,
                              static_cast<uint64>(alpha_mode_));
    if (frame_rate_ > 0.0)
      size += EbmlElementSize(libwebm::kMkvFrameRate,
                              static_cast<float>(frame_rate_));
    if (colour_space_)
      size += EbmlElementSize(libwebm::kMkvColourSpace, colour_space_);
    if (colour_)
      size += colour_->ColourSize();
    if (projection_)
      size += projection_->ProjectionSize();
  
    return size;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // AudioTrack Class
  
  AudioTrack::AudioTrack(unsigned int* seed)
      : Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {}
  
  AudioTrack::~AudioTrack() {}
  
  uint64_t AudioTrack::PayloadSize() const {
    const uint64_t parent_size = Track::PayloadSize();
  
    uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
                                    static_cast<float>(sample_rate_));
    size +=
        EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
    if (bit_depth_ > 0)
      size +=
          EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
    size += EbmlMasterElementSize(libwebm::kMkvAudio, size);
  
    return parent_size + size;
  }
  
  bool AudioTrack::Write(IMkvWriter* writer) const {
    if (!Track::Write(writer))
      return false;
  
    // Calculate AudioSettings size.
    uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
                                    static_cast<float>(sample_rate_));
    size +=
        EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
    if (bit_depth_ > 0)
      size +=
          EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvAudio, size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvSamplingFrequency,
                          static_cast<float>(sample_rate_)))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvChannels,
                          static_cast<uint64>(channels_)))
      return false;
    if (bit_depth_ > 0)
      if (!WriteEbmlElement(writer, libwebm::kMkvBitDepth,
                            static_cast<uint64>(bit_depth_)))
        return false;
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(size))
      return false;
  
    return true;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // Tracks Class
  
  const char Tracks::kOpusCodecId[] = "A_OPUS";
  const char Tracks::kVorbisCodecId[] = "A_VORBIS";
  const char Tracks::kAv1CodecId[] = "V_AV1";
  const char Tracks::kVp8CodecId[] = "V_VP8";
  const char Tracks::kVp9CodecId[] = "V_VP9";
  const char Tracks::kWebVttCaptionsId[] = "D_WEBVTT/CAPTIONS";
  const char Tracks::kWebVttDescriptionsId[] = "D_WEBVTT/DESCRIPTIONS";
  const char Tracks::kWebVttMetadataId[] = "D_WEBVTT/METADATA";
  const char Tracks::kWebVttSubtitlesId[] = "D_WEBVTT/SUBTITLES";
  
  Tracks::Tracks()
      : track_entries_(NULL), track_entries_size_(0), wrote_tracks_(false) {}
  
  Tracks::~Tracks() {
    if (track_entries_) {
      for (uint32_t i = 0; i < track_entries_size_; ++i) {
        Track* const track = track_entries_[i];
        delete track;
      }
      delete[] track_entries_;
    }
  }
  
  bool Tracks::AddTrack(Track* track, int32_t number) {
    if (number < 0 || wrote_tracks_)
      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_t track_num = number;
  
    if (track_num > 0) {
      // Check to make sure a track does not already have |track_num|.
      for (uint32_t i = 0; i < track_entries_size_; ++i) {
        if (track_entries_[i]->number() == track_num)
          return false;
      }
    }
  
    const uint32_t count = track_entries_size_ + 1;
  
    Track** const track_entries = new (std::nothrow) Track*[count];  // NOLINT
    if (!track_entries)
      return false;
  
    for (uint32_t 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_t 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_t index) const {
    if (track_entries_ == NULL)
      return NULL;
  
    if (index >= track_entries_size_)
      return NULL;
  
    return track_entries_[index];
  }
  
  Track* Tracks::GetTrackByNumber(uint64_t track_number) const {
    const int32_t count = track_entries_size();
    for (int32_t i = 0; i < count; ++i) {
      if (track_entries_[i]->number() == track_number)
        return track_entries_[i];
    }
  
    return NULL;
  }
  
  bool Tracks::TrackIsAudio(uint64_t track_number) const {
    const Track* const track = GetTrackByNumber(track_number);
  
    if (track->type() == kAudio)
      return true;
  
    return false;
  }
  
  bool Tracks::TrackIsVideo(uint64_t 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_t size = 0;
    const int32_t count = track_entries_size();
    for (int32_t i = 0; i < count; ++i) {
      const Track* const track = GetTrackByIndex(i);
  
      if (!track)
        return false;
  
      size += track->Size();
    }
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvTracks, size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    for (int32_t i = 0; i < count; ++i) {
      const Track* const track = GetTrackByIndex(i);
      if (!track->Write(writer))
        return false;
    }
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(size))
      return false;
  
    wrote_tracks_ = true;
    return true;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // Chapter Class
  
  bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); }
  
  void Chapter::set_time(const Segment& segment, uint64_t start_ns,
                         uint64_t end_ns) {
    const SegmentInfo* const info = segment.GetSegmentInfo();
    const uint64_t 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_t Chapter::WriteAtom(IMkvWriter* writer) const {
    uint64_t payload_size =
        EbmlElementSize(libwebm::kMkvChapterStringUID, id_) +
        EbmlElementSize(libwebm::kMkvChapterUID, static_cast<uint64>(uid_)) +
        EbmlElementSize(libwebm::kMkvChapterTimeStart,
                        static_cast<uint64>(start_timecode_)) +
        EbmlElementSize(libwebm::kMkvChapterTimeEnd,
                        static_cast<uint64>(end_timecode_));
  
    for (int idx = 0; idx < displays_count_; ++idx) {
      const Display& d = displays_[idx];
      payload_size += d.WriteDisplay(NULL);
    }
  
    const uint64_t atom_size =
        EbmlMasterElementSize(libwebm::kMkvChapterAtom, payload_size) +
        payload_size;
  
    if (writer == NULL)
      return atom_size;
  
    const int64_t start = writer->Position();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterAtom, payload_size))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvChapterStringUID, id_))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvChapterUID,
                          static_cast<uint64>(uid_)))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeStart,
                          static_cast<uint64>(start_timecode_)))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeEnd,
                          static_cast<uint64>(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_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t Chapter::Display::WriteDisplay(IMkvWriter* writer) const {
    uint64_t payload_size = EbmlElementSize(libwebm::kMkvChapString, title_);
  
    if (language_)
      payload_size += EbmlElementSize(libwebm::kMkvChapLanguage, language_);
  
    if (country_)
      payload_size += EbmlElementSize(libwebm::kMkvChapCountry, country_);
  
    const uint64_t display_size =
        EbmlMasterElementSize(libwebm::kMkvChapterDisplay, payload_size) +
        payload_size;
  
    if (writer == NULL)
      return display_size;
  
    const int64_t start = writer->Position();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterDisplay,
                                payload_size))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvChapString, title_))
      return 0;
  
    if (language_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvChapLanguage, language_))
        return 0;
    }
  
    if (country_) {
      if (!WriteEbmlElement(writer, libwebm::kMkvChapCountry, country_))
        return 0;
    }
  
    const int64_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t payload_size = WriteEdition(NULL);  // return size only
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapters, payload_size))
      return false;
  
    const int64_t start = writer->Position();
  
    if (WriteEdition(writer) == 0)  // error
      return false;
  
    const int64_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t Chapters::WriteEdition(IMkvWriter* writer) const {
    uint64_t payload_size = 0;
  
    for (int idx = 0; idx < chapters_count_; ++idx) {
      const Chapter& chapter = chapters_[idx];
      payload_size += chapter.WriteAtom(NULL);
    }
  
    const uint64_t edition_size =
        EbmlMasterElementSize(libwebm::kMkvEditionEntry, payload_size) +
        payload_size;
  
    if (writer == NULL)  // return size only
      return edition_size;
  
    const int64_t start = writer->Position();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvEditionEntry, payload_size))
      return 0;  // error
  
    for (int idx = 0; idx < chapters_count_; ++idx) {
      const Chapter& chapter = chapters_[idx];
  
      const uint64_t chapter_size = chapter.WriteAtom(writer);
      if (chapter_size == 0)  // error
        return 0;
    }
  
    const int64_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t Tag::Write(IMkvWriter* writer) const {
    uint64_t 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_t tag_size =
        EbmlMasterElementSize(libwebm::kMkvTag, payload_size) + payload_size;
  
    if (writer == NULL)
      return tag_size;
  
    const int64_t start = writer->Position();
  
    if (!WriteEbmlMasterElement(writer, libwebm::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_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t Tag::SimpleTag::Write(IMkvWriter* writer) const {
    uint64_t payload_size = EbmlElementSize(libwebm::kMkvTagName, tag_name_);
  
    payload_size += EbmlElementSize(libwebm::kMkvTagString, tag_string_);
  
    const uint64_t simple_tag_size =
        EbmlMasterElementSize(libwebm::kMkvSimpleTag, payload_size) +
        payload_size;
  
    if (writer == NULL)
      return simple_tag_size;
  
    const int64_t start = writer->Position();
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvSimpleTag, payload_size))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvTagName, tag_name_))
      return 0;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvTagString, tag_string_))
      return 0;
  
    const int64_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t payload_size = 0;
  
    for (int idx = 0; idx < tags_count_; ++idx) {
      const Tag& tag = tags_[idx];
      payload_size += tag.Write(NULL);
    }
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvTags, payload_size))
      return false;
  
    const int64_t start = writer->Position();
  
    for (int idx = 0; idx < tags_count_; ++idx) {
      const Tag& tag = tags_[idx];
  
      const uint64_t tag_size = tag.Write(writer);
      if (tag_size == 0)  // error
        return 0;
    }
  
    const int64_t stop = writer->Position();
  
    if (stop >= start && uint64_t(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_t timecode, int64_t cues_pos, uint64_t timecode_scale,
                   bool write_last_frame_with_duration, bool fixed_size_timecode)
      : blocks_added_(0),
        finalized_(false),
        fixed_size_timecode_(fixed_size_timecode),
        header_written_(false),
        payload_size_(0),
        position_for_cues_(cues_pos),
        size_position_(-1),
        timecode_(timecode),
        timecode_scale_(timecode_scale),
        write_last_frame_with_duration_(write_last_frame_with_duration),
        writer_(NULL) {}
  
  Cluster::~Cluster() {
    // Delete any stored frames that are left behind. This will happen if the
    // Cluster was not Finalized for whatever reason.
    while (!stored_frames_.empty()) {
      while (!stored_frames_.begin()->second.empty()) {
        delete stored_frames_.begin()->second.front();
        stored_frames_.begin()->second.pop_front();
      }
      stored_frames_.erase(stored_frames_.begin()->first);
    }
  }
  
  bool Cluster::Init(IMkvWriter* ptr_writer) {
    if (!ptr_writer) {
      return false;
    }
    writer_ = ptr_writer;
    return true;
  }
  
  bool Cluster::AddFrame(const Frame* const frame) {
    return QueueOrWriteFrame(frame);
  }
  
  bool Cluster::AddFrame(const uint8_t* data, uint64_t length,
                         uint64_t track_number, uint64_t 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 QueueOrWriteFrame(&frame);
  }
  
  bool Cluster::AddFrameWithAdditional(const uint8_t* data, uint64_t length,
                                       const uint8_t* additional,
                                       uint64_t additional_length,
                                       uint64_t add_id, uint64_t track_number,
                                       uint64_t 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 QueueOrWriteFrame(&frame);
  }
  
  bool Cluster::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
                                           int64_t discard_padding,
                                           uint64_t track_number,
                                           uint64_t 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 QueueOrWriteFrame(&frame);
  }
  
  bool Cluster::AddMetadata(const uint8_t* data, uint64_t length,
                            uint64_t track_number, uint64_t abs_timecode,
                            uint64_t 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 QueueOrWriteFrame(&frame);
  }
  
  void Cluster::AddPayloadSize(uint64_t size) { payload_size_ += size; }
  
  bool Cluster::Finalize() {
    return !write_last_frame_with_duration_ && Finalize(false, 0);
  }
  
  bool Cluster::Finalize(bool set_last_frame_duration, uint64_t duration) {
    if (!writer_ || finalized_)
      return false;
  
    if (write_last_frame_with_duration_) {
      // Write out held back Frames. This essentially performs a k-way merge
      // across all tracks in the increasing order of timestamps.
      while (!stored_frames_.empty()) {
        Frame* frame = stored_frames_.begin()->second.front();
  
        // Get the next frame to write (frame with least timestamp across all
        // tracks).
        for (FrameMapIterator frames_iterator = ++stored_frames_.begin();
             frames_iterator != stored_frames_.end(); ++frames_iterator) {
          if (frames_iterator->second.front()->timestamp() < frame->timestamp()) {
            frame = frames_iterator->second.front();
          }
        }
  
        // Set the duration if it's the last frame for the track.
        if (set_last_frame_duration &&
            stored_frames_[frame->track_number()].size() == 1 &&
            !frame->duration_set()) {
          frame->set_duration(duration - frame->timestamp());
          if (!frame->is_key() && !frame->reference_block_timestamp_set()) {
            frame->set_reference_block_timestamp(
                last_block_timestamp_[frame->track_number()]);
          }
        }
  
        // Write the frame and remove it from |stored_frames_|.
        const bool wrote_frame = DoWriteFrame(frame);
        stored_frames_[frame->track_number()].pop_front();
        if (stored_frames_[frame->track_number()].empty()) {
          stored_frames_.erase(frame->track_number());
        }
        delete frame;
        if (!wrote_frame)
          return false;
      }
    }
  
    if (size_position_ == -1)
      return false;
  
    if (writer_->Seekable()) {
      const int64_t 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_t Cluster::Size() const {
    const uint64_t element_size =
        EbmlMasterElementSize(libwebm::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_t element_size) {
    AddPayloadSize(element_size);
    ++blocks_added_;
  }
  
  int64_t Cluster::GetRelativeTimecode(int64_t abs_timecode) const {
    const int64_t cluster_timecode = this->Cluster::timecode();
    const int64_t rel_timecode =
        static_cast<int64_t>(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_t element_size = WriteFrame(writer_, frame, this);
    if (element_size == 0)
      return false;
  
    PostWriteBlock(element_size);
    last_block_timestamp_[frame->track_number()] = frame->timestamp();
    return true;
  }
  
  bool Cluster::QueueOrWriteFrame(const Frame* const frame) {
    if (!frame || !frame->IsValid())
      return false;
  
    // If |write_last_frame_with_duration_| is not set, then write the frame right
    // away.
    if (!write_last_frame_with_duration_) {
      return DoWriteFrame(frame);
    }
  
    // Queue the current frame.
    uint64_t track_number = frame->track_number();
    Frame* const frame_to_store = new Frame();
    frame_to_store->CopyFrom(*frame);
    stored_frames_[track_number].push_back(frame_to_store);
  
    // Iterate through all queued frames in the current track except the last one
    // and write it if it is okay to do so (i.e.) no other track has an held back
    // frame with timestamp <= the timestamp of the frame in question.
    std::vector<std::list<Frame*>::iterator> frames_to_erase;
    for (std::list<Frame*>::iterator
             current_track_iterator = stored_frames_[track_number].begin(),
             end = --stored_frames_[track_number].end();
         current_track_iterator != end; ++current_track_iterator) {
      const Frame* const frame_to_write = *current_track_iterator;
      bool okay_to_write = true;
      for (FrameMapIterator track_iterator = stored_frames_.begin();
           track_iterator != stored_frames_.end(); ++track_iterator) {
        if (track_iterator->first == track_number) {
          continue;
        }
        if (track_iterator->second.front()->timestamp() <
            frame_to_write->timestamp()) {
          okay_to_write = false;
          break;
        }
      }
      if (okay_to_write) {
        const bool wrote_frame = DoWriteFrame(frame_to_write);
        delete frame_to_write;
        if (!wrote_frame)
          return false;
        frames_to_erase.push_back(current_track_iterator);
      } else {
        break;
      }
    }
    for (std::vector<std::list<Frame*>::iterator>::iterator iterator =
             frames_to_erase.begin();
         iterator != frames_to_erase.end(); ++iterator) {
      stored_frames_[track_number].erase(*iterator);
    }
    return true;
  }
  
  bool Cluster::WriteClusterHeader() {
    if (finalized_)
      return false;
  
    if (WriteID(writer_, libwebm::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_, libwebm::kMkvTimecode, timecode(),
                          fixed_size_timecode_ ? 8 : 0)) {
      return false;
    }
    AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode(),
                                   fixed_size_timecode_ ? 8 : 0));
    header_written_ = true;
  
    return true;
  }
  
  ///////////////////////////////////////////////////////////////
  //
  // SeekHead Class
  
  SeekHead::SeekHead() : start_pos_(0ULL) {
    for (int32_t 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_t payload_size = 0;
      uint64_t entry_size[kSeekEntryCount];
  
      for (int32_t i = 0; i < kSeekEntryCount; ++i) {
        if (seek_entry_id_[i] != 0) {
          entry_size[i] = EbmlElementSize(libwebm::kMkvSeekID,
                                          static_cast<uint64>(seek_entry_id_[i]));
          entry_size[i] += EbmlElementSize(
              libwebm::kMkvSeekPosition, static_cast<uint64>(seek_entry_pos_[i]));
  
          payload_size +=
              EbmlMasterElementSize(libwebm::kMkvSeek, entry_size[i]) +
              entry_size[i];
        }
      }
  
      // No SeekHead elements
      if (payload_size == 0)
        return true;
  
      const int64_t pos = writer->Position();
      if (writer->Position(start_pos_))
        return false;
  
      if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeekHead, payload_size))
        return false;
  
      for (int32_t i = 0; i < kSeekEntryCount; ++i) {
        if (seek_entry_id_[i] != 0) {
          if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeek, entry_size[i]))
            return false;
  
          if (!WriteEbmlElement(writer, libwebm::kMkvSeekID,
                                static_cast<uint64>(seek_entry_id_[i])))
            return false;
  
          if (!WriteEbmlElement(writer, libwebm::kMkvSeekPosition,
                                static_cast<uint64>(seek_entry_pos_[i])))
            return false;
        }
      }
  
      const uint64_t total_entry_size = kSeekEntryCount * MaxEntrySize();
      const uint64_t total_size =
          EbmlMasterElementSize(libwebm::kMkvSeekHead, total_entry_size) +
          total_entry_size;
      const int64_t size_left = total_size - (writer->Position() - start_pos_);
  
      const uint64_t 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_t entry_size = kSeekEntryCount * MaxEntrySize();
    const uint64_t size =
        EbmlMasterElementSize(libwebm::kMkvSeekHead, entry_size);
  
    start_pos_ = writer->Position();
  
    const uint64_t bytes_written = WriteVoidElement(writer, size + entry_size);
    if (!bytes_written)
      return false;
  
    return true;
  }
  
  bool SeekHead::AddSeekEntry(uint32_t id, uint64_t pos) {
    for (int32_t 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_t SeekHead::GetId(int index) const {
    if (index < 0 || index >= kSeekEntryCount)
      return UINT_MAX;
    return seek_entry_id_[index];
  }
  
  uint64_t SeekHead::GetPosition(int index) const {
    if (index < 0 || index >= kSeekEntryCount)
      return ULLONG_MAX;
    return seek_entry_pos_[index];
  }
  
  bool SeekHead::SetSeekEntry(int index, uint32_t id, uint64_t position) {
    if (index < 0 || index >= kSeekEntryCount)
      return false;
    seek_entry_id_[index] = id;
    seek_entry_pos_[index] = position;
    return true;
  }
  
  uint64_t SeekHead::MaxEntrySize() const {
    const uint64_t max_entry_payload_size =
        EbmlElementSize(libwebm::kMkvSeekID,
                        static_cast<uint64>(UINT64_C(0xffffffff))) +
        EbmlElementSize(libwebm::kMkvSeekPosition,
                        static_cast<uint64>(UINT64_C(0xffffffffffffffff)));
    const uint64_t max_entry_size =
        EbmlMasterElementSize(libwebm::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_t major;
    int32_t minor;
    int32_t build;
    int32_t 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_t pos = writer->Position();
  
        if (writer->Position(duration_pos_))
          return false;
  
        if (!WriteEbmlElement(writer, libwebm::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_t size = EbmlElementSize(libwebm::kMkvTimecodeScale,
                                    static_cast<uint64>(timecode_scale_));
    if (duration_ > 0.0)
      size +=
          EbmlElementSize(libwebm::kMkvDuration, static_cast<float>(duration_));
    if (date_utc_ != LLONG_MIN)
      size += EbmlDateElementSize(libwebm::kMkvDateUTC);
    size += EbmlElementSize(libwebm::kMkvMuxingApp, muxing_app_);
    size += EbmlElementSize(libwebm::kMkvWritingApp, writing_app_);
  
    if (!WriteEbmlMasterElement(writer, libwebm::kMkvInfo, size))
      return false;
  
    const int64_t payload_position = writer->Position();
    if (payload_position < 0)
      return false;
  
    if (!WriteEbmlElement(writer, libwebm::kMkvTimecodeScale,
                          static_cast<uint64>(timecode_scale_)))
      return false;
  
    if (duration_ > 0.0) {
      // Save for later
      duration_pos_ = writer->Position();
  
      if (!WriteEbmlElement(writer, libwebm::kMkvDuration,
                            static_cast<float>(duration_)))
        return false;
    }
  
    if (date_utc_ != LLONG_MIN)
      WriteEbmlDateElement(writer, libwebm::kMkvDateUTC, date_utc_);
  
    if (!WriteEbmlElement(writer, libwebm::kMkvMuxingApp, muxing_app_))
      return false;
    if (!WriteEbmlElement(writer, libwebm::kMkvWritingApp, writing_app_))
      return false;
  
    const int64_t stop_position = writer->Position();
    if (stop_position < 0 ||
        stop_position - payload_position != static_cast<int64_t>(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),
        accurate_cluster_duration_(false),
        fixed_size_cluster_timecode_(false),
        estimate_file_duration_(false),
        payload_pos_(0),
        size_position_(0),
        doc_type_version_(kDefaultDocTypeVersion),
        doc_type_version_written_(0),
        duration_(0.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_t i = 0; i < cluster_list_size_; ++i) {
        Cluster* const cluster = cluster_list_[i];
        delete cluster;
      }
      delete[] cluster_list_;
    }
  
    if (frames_) {
      for (int32_t 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_t diff, int32_t index,
                                             uint64_t* cues_size) {
    CuePoint* const cue_point = cues_.GetCueByIndex(index);
    if (cue_point == NULL)
      return;
    const uint64_t old_cue_point_size = cue_point->Size();
    const uint64_t 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_t cue_point_size_diff = cue_point->Size() - old_cue_point_size;
    const uint64_t 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_t i = 0; i < cues_.cue_entries_size(); ++i) {
        MoveCuesBeforeClustersHelper(diff, i, cues_size);
      }
    }
  }
  
  void Segment::MoveCuesBeforeClusters() {
    const uint64_t current_cue_size = cues_.Size();
    uint64_t cue_size = 0;
    for (int32_t i = 0; i < cues_.cue_entries_size(); ++i)
      cue_size += cues_.GetCueByIndex(i)->Size();
    for (int32_t 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_t cluster_index = 0;
    int32_t cues_index = 0;
    for (int32_t i = 0; i < SeekHead::kSeekEntryCount; ++i) {
      if (seek_head_.GetId(i) == libwebm::kMkvCluster)
        cluster_index = i;
      if (seek_head_.GetId(i) == libwebm::kMkvCues)
        cues_index = i;
    }
    seek_head_.SetSeekEntry(cues_index, libwebm::kMkvCues,
                            seek_head_.GetPosition(cluster_index));
    seek_head_.SetSeekEntry(cluster_index, libwebm::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;
    memset(&track_frames_written_, 0,
           sizeof(track_frames_written_[0]) * kMaxTrackNumber);
    memset(&last_track_timestamp_, 0,
           sizeof(last_track_timestamp_[0]) * kMaxTrackNumber);
    return segment_info_.Init();
  }
  
  bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
                                              IMkvWriter* writer) {
    if (!writer->Seekable() || chunking_)
      return false;
    const int64_t cluster_offset =
        cluster_list_[0]->size_position() - GetUIntSize(libwebm::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_t pos = writer->Position();
    const int64_t 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;
  
    // In kLive mode, call Cluster::Finalize only if |accurate_cluster_duration_|
    // is set. In all other modes, always call Cluster::Finalize.
    if ((mode_ == kLive ? accurate_cluster_duration_ : true) &&
        cluster_list_size_ > 0) {
      // Update last cluster's size
      Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
  
      // For the last frame of the last Cluster, we don't write it as a BlockGroup
      // with Duration unless the frame itself has duration set explicitly.
      if (!old_cluster || !old_cluster->Finalize(false, 0))
        return false;
    }
  
    if (mode_ == kFile) {
      if (chunking_ && chunk_writer_cluster_) {
        chunk_writer_cluster_->Close();
        chunk_count_++;
      }
  
      double duration =
          (static_cast<double>(last_timestamp_) + last_block_duration_) /
          segment_info_.timecode_scale();
      if (duration_ > 0.0) {
        duration = duration_;
      } else {
        if (last_block_duration_ == 0 && estimate_file_duration_) {
          const int num_tracks = static_cast<int>(tracks_.track_entries_size());
          for (int i = 0; i < num_tracks; ++i) {
            if (track_frames_written_[i] < 2)
              continue;
  
            // Estimate the duration for the last block of a Track.
            const double nano_per_frame =
                static_cast<double>(last_track_timestamp_[i]) /
                (track_frames_written_[i] - 1);
            const double track_duration =
                (last_track_timestamp_[i] + nano_per_frame) /
                segment_info_.timecode_scale();
            if (track_duration > duration)
              duration = track_duration;
          }
        }
      }
      segment_info_.set_duration(duration);
      if (!segment_info_.Finalize(writer_header_))
        return false;
  
      if (output_cues_)
        if (!seek_head_.AddSeekEntry(libwebm::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_t segment_size = MaxOffset();
        if (segment_size < 1)
          return false;
  
        const int64_t pos = writer_header_->Position();
        UpdateDocTypeVersion();
        if (doc_type_version_ != doc_type_version_written_) {
          if (writer_header_->Position(0))
            return false;
  
          const char* const doc_type =
              DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
          if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
            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_t 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_t Segment::AddVideoTrack(int32_t width, int32_t height, int32_t number) {
    VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_);  // NOLINT
    if (!track)
      return 0;
  
    track->set_type(Tracks::kVideo);
    track->set_codec_id(Tracks::kVp8CodecId);
    track->set_width(width);
    track->set_height(height);
  
    if (!tracks_.AddTrack(track, number)) {
      delete track;
      return 0;
    }
    has_video_ = true;
  
    return track->number();
  }
  
  bool Segment::AddCuePoint(uint64_t timestamp, uint64_t 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)) {
      delete cue;
      return false;
    }
  
    new_cuepoint_ = false;
    return true;
  }
  
  uint64_t Segment::AddAudioTrack(int32_t sample_rate, int32_t channels,
                                  int32_t 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);
  
    if (!tracks_.AddTrack(track, number)) {
      delete track;
      return 0;
    }
  
    return track->number();
  }
  
  bool Segment::AddFrame(const uint8_t* data, uint64_t length,
                         uint64_t track_number, uint64_t 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_t* data, uint64_t length,
                                       const uint8_t* additional,
                                       uint64_t additional_length,
                                       uint64_t add_id, uint64_t track_number,
                                       uint64_t 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_t* data, uint64_t length,
                                           int64_t discard_padding,
                                           uint64_t track_number,
                                           uint64_t 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_t* data, uint64_t length,
                            uint64_t track_number, uint64_t timestamp_ns,
                            uint64_t 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 (cluster_list_size_ > 0) {
      const uint64_t timecode_scale = segment_info_.timecode_scale();
      const uint64_t frame_timecode = frame->timestamp() / timecode_scale;
  
      const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
      const uint64_t last_cluster_timecode = last_cluster->timecode();
  
      const uint64_t rel_timecode = frame_timecode - last_cluster_timecode;
      if (rel_timecode > kMaxBlockTimecode) {
        force_new_cluster_ = true;
      }
    }
  
    // 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)) {
        delete new_frame;
        return false;
      }
      if (!QueueFrame(new_frame)) {
        delete new_frame;
        return false;
      }
      track_frames_written_[frame->track_number() - 1]++;
      return true;
    }
  
    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 || !new_frame->CopyFrom(*frame)) {
        delete new_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();
    track_frames_written_[frame->track_number() - 1]++;
  
    if (frame_created)
      delete frame;
    return true;
  }
  
  void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; }
  
  void Segment::AccurateClusterDuration(bool accurate_cluster_duration) {
    accurate_cluster_duration_ = accurate_cluster_duration;
  }
  
  void Segment::UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode) {
    fixed_size_cluster_timecode_ = fixed_size_cluster_timecode;
  }
  
  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_t 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_t track_number) const {
    return tracks_.GetTrackByNumber(track_number);
  }
  
  bool Segment::WriteSegmentHeader() {
    UpdateDocTypeVersion();
  
    const char* const doc_type =
        DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
    if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
      return false;
    doc_type_version_written_ = doc_type_version_;
    ebml_header_size_ = static_cast<int32_t>(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_, libwebm::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(libwebm::kMkvInfo, MaxOffset()))
      return false;
    if (!segment_info_.Write(writer_header_))
      return false;
  
    if (!seek_head_.AddSeekEntry(libwebm::kMkvTracks, MaxOffset()))
      return false;
    if (!tracks_.Write(writer_header_))
      return false;
  
    if (chapters_.Count() > 0) {
      if (!seek_head_.AddSeekEntry(libwebm::kMkvChapters, MaxOffset()))
        return false;
      if (!chapters_.Write(writer_header_))
        return false;
    }
  
    if (tags_.Count() > 0) {
      if (!seek_head_.AddSeekEntry(libwebm::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_t track_number, uint64_t 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_t timecode_scale = segment_info_.timecode_scale();
    const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
  
    const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
    const uint64_t 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_t 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_t 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_t 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_t frame_timestamp_ns) {
    const int32_t new_size = cluster_list_size_ + 1;
  
    if (new_size > cluster_list_capacity_) {
      // Add more clusters.
      const int32_t 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_t 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 (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(true, frame_timestamp_ns))
        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_t timecode_scale = segment_info_.timecode_scale();
    const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
  
    uint64_t cluster_timecode = frame_timecode;
  
    if (frames_size_ > 0) {
      const Frame* const f = frames_[0];  // earliest queued frame
      const uint64_t ns = f->timestamp();
      const uint64_t tc = ns / timecode_scale;
  
      if (tc < cluster_timecode)
        cluster_timecode = tc;
    }
  
    Cluster*& cluster = cluster_list_[cluster_list_size_];
    const int64_t offset = MaxOffset();
    cluster = new (std::nothrow)
        Cluster(cluster_timecode, offset, segment_info_.timecode_scale(),
                accurate_cluster_duration_, fixed_size_cluster_timecode_);
    if (!cluster)
      return false;
  
    if (!cluster->Init(writer_cluster_))
      return false;
  
    cluster_list_size_ = new_size;
    return true;
  }
  
  bool Segment::DoNewClusterProcessing(uint64_t track_number,
                                       uint64_t 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(libwebm::kMkvCluster, MaxOffset()))
        return false;
  
      if (output_cues_ && cues_track_ == 0) {
        // Check for a video track
        for (uint32_t 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_t 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_t Segment::MaxOffset() {
    if (!writer_header_)
      return -1;
  
    int64_t offset = writer_header_->Position() - payload_pos_;
  
    if (chunking_) {
      for (int32_t 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_t new_size = frames_size_ + 1;
  
    if (new_size > frames_capacity_) {
      // Add more frames.
      const int32_t 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_t 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_t 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_t 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_t 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_t 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_t new_frames_size = frames_size_ - shift_left;
        for (int32_t i = 0; i < new_frames_size; ++i) {
          frames_[i] = frames_[i + shift_left];
        }
  
        frames_size_ = new_frames_size;
      }
    }
  
    return true;
  }
  
  bool Segment::DocTypeIsWebm() const {
    const int kNumCodecIds = 9;
  
    // TODO(vigneshv): Tweak .clang-format.
    const char* kWebmCodecIds[kNumCodecIds] = {
        Tracks::kOpusCodecId,          Tracks::kVorbisCodecId,
        Tracks::kAv1CodecId,           Tracks::kVp8CodecId,
        Tracks::kVp9CodecId,           Tracks::kWebVttCaptionsId,
        Tracks::kWebVttDescriptionsId, Tracks::kWebVttMetadataId,
        Tracks::kWebVttSubtitlesId};
  
    const int num_tracks = static_cast<int>(tracks_.track_entries_size());
    for (int track_index = 0; track_index < num_tracks; ++track_index) {
      const Track* const track = tracks_.GetTrackByIndex(track_index);
      const std::string codec_id = track->codec_id();
  
      bool id_is_webm = false;
      for (int id_index = 0; id_index < kNumCodecIds; ++id_index) {
        if (codec_id == kWebmCodecIds[id_index]) {
          id_is_webm = true;
          break;
        }
      }
  
      if (!id_is_webm)
        return false;
    }
  
    return true;
  }
  
  }  // namespace mkvmuxer
  