Pulling libwebm from upstream
Changes from upstream:
249629d make Mkv(Reader|Writer)(FILE*) explicit
7f3cda4 mkvparser: fix a bunch of windows warnings
5c06178 Merge "clang-format on mkvparser.[ch]pp"
4df111e clang-format on mkvparser.[ch]pp
7b24501 clang-format re-run.
c6767b9 Change AlignTrailingComments to false in .clang-format
9097a06 Merge "muxer: Reject file if TrackType is never specified"
eddf974 Merge "clang-format on mkvmuxertypes.hpp and webmids.hpp"
def325c muxer: Reject file if TrackType is never specified
41f869c Merge "clang-format on webvttparser.(cc|h)"
fd0be37 clang-format on webvttparser.(cc|h)
207d8a1 Merge "clang-format on mkvmuxerutil.[ch]pp"
02429eb Merge "clang-format on mkvwriter.[ch]pp"
0cf7b1b Merge "clang-format on mkvreader.[ch]pp"
2e80fed Merge "clang-format on sample.cpp"
3402e12 Merge "clang-format on sample_muxer.cpp"
1a685db Merge "clang-format on sample_muxer_metadata.(cc|h)"
6634c7f Merge "clang-format on vttreader.cc"
7566004 Merge "clang-format on vttdemux.cc"
9915b84 clang-format on mkvreader.[ch]pp
7437254 clang-format on mkvmuxertypes.hpp and webmids.hpp
0d5a98c clang-format on sample_muxer.cpp
e3485c9 clang-format on vttdemux.cc
46cc823 clang-format on dumpvtt.cc
5218bd2 clang-format on vttreader.cc
1a0130d clang-format on sample_muxer_metadata.(cc|h)
867f189 clang-format on sample.cpp
4c7bec5 clang-format on mkvwriter.[ch]pp
9ead078 clang-format on mkvmuxerutil.[ch]pp
fb6b6e6 clang-format on mkvmuxer.[ch]pp
ce77592 Update .clang-format to allow short functions in one line
0a24fe4 Merge "Add support for DateUTC and DefaultDuration in MKV Muxer."
11d5b66 Merge "Add .clang-format"
a1a3b14 Add .clang-format
0fcec38 Add support for DateUTC and DefaultDuration in MKV Muxer.
Change-Id: Ia0ed161ffc3d63c2eba8ed145707ffe543617976
		
	
		
			
				
	
	
		
			3076 lines
		
	
	
		
			83 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3076 lines
		
	
	
		
			83 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// 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.hpp"
 | 
						|
 | 
						|
#include <climits>
 | 
						|
#include <cstdio>
 | 
						|
#include <cstdlib>
 | 
						|
#include <cstring>
 | 
						|
#include <ctime>
 | 
						|
#include <new>
 | 
						|
 | 
						|
#include "mkvmuxerutil.hpp"
 | 
						|
#include "mkvparser.hpp"
 | 
						|
#include "mkvwriter.hpp"
 | 
						|
#include "webmids.hpp"
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
// Disable MSVC warnings that suggest making code non-portable.
 | 
						|
#pragma warning(disable : 4996)
 | 
						|
#endif
 | 
						|
 | 
						|
namespace mkvmuxer {
 | 
						|
 | 
						|
namespace {
 | 
						|
// Deallocate the string designated by |dst|, and then copy the |src|
 | 
						|
// string to |dst|.  The caller owns both the |src| string and the
 | 
						|
// |dst| copy (hence the caller is responsible for eventually
 | 
						|
// deallocating the strings, either directly, or indirectly via
 | 
						|
// StrCpy).  Returns true if the source string was successfully copied
 | 
						|
// to the destination.
 | 
						|
bool StrCpy(const char* src, char** dst_ptr) {
 | 
						|
  if (dst_ptr == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  char*& dst = *dst_ptr;
 | 
						|
 | 
						|
  delete[] dst;
 | 
						|
  dst = NULL;
 | 
						|
 | 
						|
  if (src == NULL)
 | 
						|
    return true;
 | 
						|
 | 
						|
  const size_t size = strlen(src) + 1;
 | 
						|
 | 
						|
  dst = new (std::nothrow) char[size];  // NOLINT
 | 
						|
  if (dst == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  strcpy(dst, src);  // NOLINT
 | 
						|
  return true;
 | 
						|
}
 | 
						|
}  // namespace
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// IMkvWriter Class
 | 
						|
 | 
						|
IMkvWriter::IMkvWriter() {}
 | 
						|
 | 
						|
IMkvWriter::~IMkvWriter() {}
 | 
						|
 | 
						|
bool WriteEbmlHeader(IMkvWriter* writer) {
 | 
						|
  // Level 0
 | 
						|
  uint64 size = EbmlElementSize(kMkvEBMLVersion, 1ULL);
 | 
						|
  size += EbmlElementSize(kMkvEBMLReadVersion, 1ULL);
 | 
						|
  size += EbmlElementSize(kMkvEBMLMaxIDLength, 4ULL);
 | 
						|
  size += EbmlElementSize(kMkvEBMLMaxSizeLength, 8ULL);
 | 
						|
  size += EbmlElementSize(kMkvDocType, "webm");
 | 
						|
  size += EbmlElementSize(kMkvDocTypeVersion, 2ULL);
 | 
						|
  size += EbmlElementSize(kMkvDocTypeReadVersion, 2ULL);
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvEBML, size))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvEBMLVersion, 1ULL))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvEBMLReadVersion, 1ULL))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvEBMLMaxIDLength, 4ULL))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvEBMLMaxSizeLength, 8ULL))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvDocType, "webm"))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvDocTypeVersion, 2ULL))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvDocTypeReadVersion, 2ULL))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst,
 | 
						|
                 mkvmuxer::int64 start, int64 size) {
 | 
						|
  // TODO(vigneshv): Check if this is a reasonable value.
 | 
						|
  const uint32 kBufSize = 2048;
 | 
						|
  uint8* buf = new uint8[kBufSize];
 | 
						|
  int64 offset = start;
 | 
						|
  while (size > 0) {
 | 
						|
    const int64 read_len = (size > kBufSize) ? kBufSize : size;
 | 
						|
    if (source->Read(offset, static_cast<long>(read_len), buf))
 | 
						|
      return false;
 | 
						|
    dst->Write(buf, static_cast<uint32>(read_len));
 | 
						|
    offset += read_len;
 | 
						|
    size -= read_len;
 | 
						|
  }
 | 
						|
  delete[] buf;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Frame Class
 | 
						|
 | 
						|
Frame::Frame()
 | 
						|
    : add_id_(0),
 | 
						|
      additional_(NULL),
 | 
						|
      additional_length_(0),
 | 
						|
      duration_(0),
 | 
						|
      frame_(NULL),
 | 
						|
      is_key_(false),
 | 
						|
      length_(0),
 | 
						|
      track_number_(0),
 | 
						|
      timestamp_(0),
 | 
						|
      discard_padding_(0) {}
 | 
						|
 | 
						|
Frame::~Frame() {
 | 
						|
  delete[] frame_;
 | 
						|
  delete[] additional_;
 | 
						|
}
 | 
						|
 | 
						|
bool Frame::Init(const uint8* frame, uint64 length) {
 | 
						|
  uint8* const data =
 | 
						|
      new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT
 | 
						|
  if (!data)
 | 
						|
    return false;
 | 
						|
 | 
						|
  delete[] frame_;
 | 
						|
  frame_ = data;
 | 
						|
  length_ = length;
 | 
						|
 | 
						|
  memcpy(frame_, frame, static_cast<size_t>(length_));
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Frame::AddAdditionalData(const uint8* additional, uint64 length,
 | 
						|
                              uint64 add_id) {
 | 
						|
  uint8* const data =
 | 
						|
      new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT
 | 
						|
  if (!data)
 | 
						|
    return false;
 | 
						|
 | 
						|
  delete[] additional_;
 | 
						|
  additional_ = data;
 | 
						|
  additional_length_ = length;
 | 
						|
  add_id_ = add_id;
 | 
						|
 | 
						|
  memcpy(additional_, additional, static_cast<size_t>(additional_length_));
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// CuePoint Class
 | 
						|
 | 
						|
CuePoint::CuePoint()
 | 
						|
    : time_(0),
 | 
						|
      track_(0),
 | 
						|
      cluster_pos_(0),
 | 
						|
      block_number_(1),
 | 
						|
      output_block_number_(true) {}
 | 
						|
 | 
						|
CuePoint::~CuePoint() {}
 | 
						|
 | 
						|
bool CuePoint::Write(IMkvWriter* writer) const {
 | 
						|
  if (!writer || track_ < 1 || cluster_pos_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_);
 | 
						|
  size += EbmlElementSize(kMkvCueTrack, track_);
 | 
						|
  if (output_block_number_ && block_number_ > 1)
 | 
						|
    size += EbmlElementSize(kMkvCueBlockNumber, block_number_);
 | 
						|
  const uint64 track_pos_size =
 | 
						|
      EbmlMasterElementSize(kMkvCueTrackPositions, size) + size;
 | 
						|
  const uint64 payload_size =
 | 
						|
      EbmlElementSize(kMkvCueTime, time_) + track_pos_size;
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvCuePoint, payload_size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvCueTime, time_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvCueTrackPositions, size))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvCueTrack, track_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvCueClusterPosition, cluster_pos_))
 | 
						|
    return false;
 | 
						|
  if (output_block_number_ && block_number_ > 1)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvCueBlockNumber, block_number_))
 | 
						|
      return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (stop_position - payload_position != static_cast<int64>(payload_size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 CuePoint::PayloadSize() const {
 | 
						|
  uint64 size = EbmlElementSize(kMkvCueClusterPosition, cluster_pos_);
 | 
						|
  size += EbmlElementSize(kMkvCueTrack, track_);
 | 
						|
  if (output_block_number_ && block_number_ > 1)
 | 
						|
    size += EbmlElementSize(kMkvCueBlockNumber, block_number_);
 | 
						|
  const uint64 track_pos_size =
 | 
						|
      EbmlMasterElementSize(kMkvCueTrackPositions, size) + size;
 | 
						|
  const uint64 payload_size =
 | 
						|
      EbmlElementSize(kMkvCueTime, time_) + track_pos_size;
 | 
						|
 | 
						|
  return payload_size;
 | 
						|
}
 | 
						|
 | 
						|
uint64 CuePoint::Size() const {
 | 
						|
  const uint64 payload_size = PayloadSize();
 | 
						|
  return EbmlMasterElementSize(kMkvCuePoint, payload_size) + payload_size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Cues Class
 | 
						|
 | 
						|
Cues::Cues()
 | 
						|
    : cue_entries_capacity_(0),
 | 
						|
      cue_entries_size_(0),
 | 
						|
      cue_entries_(NULL),
 | 
						|
      output_block_number_(true) {}
 | 
						|
 | 
						|
Cues::~Cues() {
 | 
						|
  if (cue_entries_) {
 | 
						|
    for (int32 i = 0; i < cue_entries_size_; ++i) {
 | 
						|
      CuePoint* const cue = cue_entries_[i];
 | 
						|
      delete cue;
 | 
						|
    }
 | 
						|
    delete[] cue_entries_;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool Cues::AddCue(CuePoint* cue) {
 | 
						|
  if (!cue)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if ((cue_entries_size_ + 1) > cue_entries_capacity_) {
 | 
						|
    // Add more CuePoints.
 | 
						|
    const int32 new_capacity =
 | 
						|
        (!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2;
 | 
						|
 | 
						|
    if (new_capacity < 1)
 | 
						|
      return false;
 | 
						|
 | 
						|
    CuePoint** const cues =
 | 
						|
        new (std::nothrow) CuePoint* [new_capacity];  // NOLINT
 | 
						|
    if (!cues)
 | 
						|
      return false;
 | 
						|
 | 
						|
    for (int32 i = 0; i < cue_entries_size_; ++i) {
 | 
						|
      cues[i] = cue_entries_[i];
 | 
						|
    }
 | 
						|
 | 
						|
    delete[] cue_entries_;
 | 
						|
 | 
						|
    cue_entries_ = cues;
 | 
						|
    cue_entries_capacity_ = new_capacity;
 | 
						|
  }
 | 
						|
 | 
						|
  cue->set_output_block_number(output_block_number_);
 | 
						|
  cue_entries_[cue_entries_size_++] = cue;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
CuePoint* Cues::GetCueByIndex(int32 index) const {
 | 
						|
  if (cue_entries_ == NULL)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  if (index >= cue_entries_size_)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  return cue_entries_[index];
 | 
						|
}
 | 
						|
 | 
						|
uint64 Cues::Size() {
 | 
						|
  uint64 size = 0;
 | 
						|
  for (int32 i = 0; i < cue_entries_size_; ++i)
 | 
						|
    size += GetCueByIndex(i)->Size();
 | 
						|
  size += EbmlMasterElementSize(kMkvCues, size);
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
bool Cues::Write(IMkvWriter* writer) const {
 | 
						|
  if (!writer)
 | 
						|
    return false;
 | 
						|
 | 
						|
  uint64 size = 0;
 | 
						|
  for (int32 i = 0; i < cue_entries_size_; ++i) {
 | 
						|
    const CuePoint* const cue = GetCueByIndex(i);
 | 
						|
 | 
						|
    if (!cue)
 | 
						|
      return false;
 | 
						|
 | 
						|
    size += cue->Size();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvCues, size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  for (int32 i = 0; i < cue_entries_size_; ++i) {
 | 
						|
    const CuePoint* const cue = GetCueByIndex(i);
 | 
						|
 | 
						|
    if (!cue->Write(writer))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// ContentEncAESSettings Class
 | 
						|
 | 
						|
ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {}
 | 
						|
 | 
						|
uint64 ContentEncAESSettings::Size() const {
 | 
						|
  const uint64 payload = PayloadSize();
 | 
						|
  const uint64 size =
 | 
						|
      EbmlMasterElementSize(kMkvContentEncAESSettings, payload) + payload;
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
bool ContentEncAESSettings::Write(IMkvWriter* writer) const {
 | 
						|
  const uint64 payload = PayloadSize();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvContentEncAESSettings, payload))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvAESSettingsCipherMode, cipher_mode_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(payload))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 ContentEncAESSettings::PayloadSize() const {
 | 
						|
  uint64 size = EbmlElementSize(kMkvAESSettingsCipherMode, cipher_mode_);
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// ContentEncoding Class
 | 
						|
 | 
						|
ContentEncoding::ContentEncoding()
 | 
						|
    : enc_algo_(5),
 | 
						|
      enc_key_id_(NULL),
 | 
						|
      encoding_order_(0),
 | 
						|
      encoding_scope_(1),
 | 
						|
      encoding_type_(1),
 | 
						|
      enc_key_id_length_(0) {}
 | 
						|
 | 
						|
ContentEncoding::~ContentEncoding() { delete[] enc_key_id_; }
 | 
						|
 | 
						|
bool ContentEncoding::SetEncryptionID(const uint8* id, uint64 length) {
 | 
						|
  if (!id || length < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  delete[] enc_key_id_;
 | 
						|
 | 
						|
  enc_key_id_ =
 | 
						|
      new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT
 | 
						|
  if (!enc_key_id_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  memcpy(enc_key_id_, id, static_cast<size_t>(length));
 | 
						|
  enc_key_id_length_ = length;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 ContentEncoding::Size() const {
 | 
						|
  const uint64 encryption_size = EncryptionSize();
 | 
						|
  const uint64 encoding_size = EncodingSize(0, encryption_size);
 | 
						|
  const uint64 encodings_size =
 | 
						|
      EbmlMasterElementSize(kMkvContentEncoding, encoding_size) + encoding_size;
 | 
						|
 | 
						|
  return encodings_size;
 | 
						|
}
 | 
						|
 | 
						|
bool ContentEncoding::Write(IMkvWriter* writer) const {
 | 
						|
  const uint64 encryption_size = EncryptionSize();
 | 
						|
  const uint64 encoding_size = EncodingSize(0, encryption_size);
 | 
						|
  const uint64 size =
 | 
						|
      EbmlMasterElementSize(kMkvContentEncoding, encoding_size) + encoding_size;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvContentEncoding, encoding_size))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvContentEncodingOrder, encoding_order_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvContentEncodingScope, encoding_scope_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvContentEncodingType, encoding_type_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvContentEncryption, encryption_size))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvContentEncAlgo, enc_algo_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvContentEncKeyID, enc_key_id_,
 | 
						|
                        enc_key_id_length_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!enc_aes_settings_.Write(writer))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 ContentEncoding::EncodingSize(uint64 compresion_size,
 | 
						|
                                     uint64 encryption_size) const {
 | 
						|
  // TODO(fgalligan): Add support for compression settings.
 | 
						|
  if (compresion_size != 0)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  uint64 encoding_size = 0;
 | 
						|
 | 
						|
  if (encryption_size > 0) {
 | 
						|
    encoding_size +=
 | 
						|
        EbmlMasterElementSize(kMkvContentEncryption, encryption_size) +
 | 
						|
        encryption_size;
 | 
						|
  }
 | 
						|
  encoding_size += EbmlElementSize(kMkvContentEncodingType, encoding_type_);
 | 
						|
  encoding_size += EbmlElementSize(kMkvContentEncodingScope, encoding_scope_);
 | 
						|
  encoding_size += EbmlElementSize(kMkvContentEncodingOrder, encoding_order_);
 | 
						|
 | 
						|
  return encoding_size;
 | 
						|
}
 | 
						|
 | 
						|
uint64 ContentEncoding::EncryptionSize() const {
 | 
						|
  const uint64 aes_size = enc_aes_settings_.Size();
 | 
						|
 | 
						|
  uint64 encryption_size =
 | 
						|
      EbmlElementSize(kMkvContentEncKeyID, enc_key_id_, enc_key_id_length_);
 | 
						|
  encryption_size += EbmlElementSize(kMkvContentEncAlgo, enc_algo_);
 | 
						|
 | 
						|
  return encryption_size + aes_size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Track Class
 | 
						|
 | 
						|
Track::Track(unsigned int* seed)
 | 
						|
    : codec_id_(NULL),
 | 
						|
      codec_private_(NULL),
 | 
						|
      language_(NULL),
 | 
						|
      max_block_additional_id_(0),
 | 
						|
      name_(NULL),
 | 
						|
      number_(0),
 | 
						|
      type_(0),
 | 
						|
      uid_(MakeUID(seed)),
 | 
						|
      codec_delay_(0),
 | 
						|
      seek_pre_roll_(0),
 | 
						|
      default_duration_(0),
 | 
						|
      codec_private_length_(0),
 | 
						|
      content_encoding_entries_(NULL),
 | 
						|
      content_encoding_entries_size_(0) {}
 | 
						|
 | 
						|
Track::~Track() {
 | 
						|
  delete[] codec_id_;
 | 
						|
  delete[] codec_private_;
 | 
						|
  delete[] language_;
 | 
						|
  delete[] name_;
 | 
						|
 | 
						|
  if (content_encoding_entries_) {
 | 
						|
    for (uint32 i = 0; i < content_encoding_entries_size_; ++i) {
 | 
						|
      ContentEncoding* const encoding = content_encoding_entries_[i];
 | 
						|
      delete encoding;
 | 
						|
    }
 | 
						|
    delete[] content_encoding_entries_;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool Track::AddContentEncoding() {
 | 
						|
  const uint32 count = content_encoding_entries_size_ + 1;
 | 
						|
 | 
						|
  ContentEncoding** const content_encoding_entries =
 | 
						|
      new (std::nothrow) ContentEncoding* [count];  // NOLINT
 | 
						|
  if (!content_encoding_entries)
 | 
						|
    return false;
 | 
						|
 | 
						|
  ContentEncoding* const content_encoding =
 | 
						|
      new (std::nothrow) ContentEncoding();  // NOLINT
 | 
						|
  if (!content_encoding) {
 | 
						|
    delete[] content_encoding_entries;
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  for (uint32 i = 0; i < content_encoding_entries_size_; ++i) {
 | 
						|
    content_encoding_entries[i] = content_encoding_entries_[i];
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] content_encoding_entries_;
 | 
						|
 | 
						|
  content_encoding_entries_ = content_encoding_entries;
 | 
						|
  content_encoding_entries_[content_encoding_entries_size_] = content_encoding;
 | 
						|
  content_encoding_entries_size_ = count;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
ContentEncoding* Track::GetContentEncodingByIndex(uint32 index) const {
 | 
						|
  if (content_encoding_entries_ == NULL)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  if (index >= content_encoding_entries_size_)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  return content_encoding_entries_[index];
 | 
						|
}
 | 
						|
 | 
						|
uint64 Track::PayloadSize() const {
 | 
						|
  uint64 size = EbmlElementSize(kMkvTrackNumber, number_);
 | 
						|
  size += EbmlElementSize(kMkvTrackUID, uid_);
 | 
						|
  size += EbmlElementSize(kMkvTrackType, type_);
 | 
						|
  if (codec_id_)
 | 
						|
    size += EbmlElementSize(kMkvCodecID, codec_id_);
 | 
						|
  if (codec_private_)
 | 
						|
    size += EbmlElementSize(kMkvCodecPrivate, codec_private_,
 | 
						|
                            codec_private_length_);
 | 
						|
  if (language_)
 | 
						|
    size += EbmlElementSize(kMkvLanguage, language_);
 | 
						|
  if (name_)
 | 
						|
    size += EbmlElementSize(kMkvName, name_);
 | 
						|
  if (max_block_additional_id_)
 | 
						|
    size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_);
 | 
						|
  if (codec_delay_)
 | 
						|
    size += EbmlElementSize(kMkvCodecDelay, codec_delay_);
 | 
						|
  if (seek_pre_roll_)
 | 
						|
    size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_);
 | 
						|
  if (default_duration_)
 | 
						|
    size += EbmlElementSize(kMkvDefaultDuration, default_duration_);
 | 
						|
 | 
						|
  if (content_encoding_entries_size_ > 0) {
 | 
						|
    uint64 content_encodings_size = 0;
 | 
						|
    for (uint32 i = 0; i < content_encoding_entries_size_; ++i) {
 | 
						|
      ContentEncoding* const encoding = content_encoding_entries_[i];
 | 
						|
      content_encodings_size += encoding->Size();
 | 
						|
    }
 | 
						|
 | 
						|
    size +=
 | 
						|
        EbmlMasterElementSize(kMkvContentEncodings, content_encodings_size) +
 | 
						|
        content_encodings_size;
 | 
						|
  }
 | 
						|
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
uint64 Track::Size() const {
 | 
						|
  uint64 size = PayloadSize();
 | 
						|
  size += EbmlMasterElementSize(kMkvTrackEntry, size);
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
bool Track::Write(IMkvWriter* writer) const {
 | 
						|
  if (!writer)
 | 
						|
    return false;
 | 
						|
 | 
						|
  // |size| may be bigger than what is written out in this function because
 | 
						|
  // derived classes may write out more data in the Track element.
 | 
						|
  const uint64 payload_size = PayloadSize();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvTrackEntry, payload_size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // |type_| has to be specified before the Track can be written.
 | 
						|
  if (!type_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  uint64 size = EbmlElementSize(kMkvTrackNumber, number_);
 | 
						|
  size += EbmlElementSize(kMkvTrackUID, uid_);
 | 
						|
  size += EbmlElementSize(kMkvTrackType, type_);
 | 
						|
  if (codec_id_)
 | 
						|
    size += EbmlElementSize(kMkvCodecID, codec_id_);
 | 
						|
  if (codec_private_)
 | 
						|
    size += EbmlElementSize(kMkvCodecPrivate, codec_private_,
 | 
						|
                            codec_private_length_);
 | 
						|
  if (language_)
 | 
						|
    size += EbmlElementSize(kMkvLanguage, language_);
 | 
						|
  if (name_)
 | 
						|
    size += EbmlElementSize(kMkvName, name_);
 | 
						|
  if (max_block_additional_id_)
 | 
						|
    size += EbmlElementSize(kMkvMaxBlockAdditionID, max_block_additional_id_);
 | 
						|
  if (codec_delay_)
 | 
						|
    size += EbmlElementSize(kMkvCodecDelay, codec_delay_);
 | 
						|
  if (seek_pre_roll_)
 | 
						|
    size += EbmlElementSize(kMkvSeekPreRoll, seek_pre_roll_);
 | 
						|
  if (default_duration_)
 | 
						|
    size += EbmlElementSize(kMkvDefaultDuration, default_duration_);
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvTrackNumber, number_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvTrackUID, uid_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvTrackType, type_))
 | 
						|
    return false;
 | 
						|
  if (max_block_additional_id_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvMaxBlockAdditionID,
 | 
						|
                          max_block_additional_id_)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (codec_delay_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvCodecDelay, codec_delay_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (seek_pre_roll_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvSeekPreRoll, seek_pre_roll_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (default_duration_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvDefaultDuration, default_duration_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (codec_id_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvCodecID, codec_id_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (codec_private_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvCodecPrivate, codec_private_,
 | 
						|
                          codec_private_length_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (language_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvLanguage, language_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
  if (name_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvName, name_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (content_encoding_entries_size_ > 0) {
 | 
						|
    uint64 content_encodings_size = 0;
 | 
						|
    for (uint32 i = 0; i < content_encoding_entries_size_; ++i) {
 | 
						|
      ContentEncoding* const encoding = content_encoding_entries_[i];
 | 
						|
      content_encodings_size += encoding->Size();
 | 
						|
    }
 | 
						|
 | 
						|
    if (!WriteEbmlMasterElement(writer, kMkvContentEncodings,
 | 
						|
                                content_encodings_size))
 | 
						|
      return false;
 | 
						|
 | 
						|
    for (uint32 i = 0; i < content_encoding_entries_size_; ++i) {
 | 
						|
      ContentEncoding* const encoding = content_encoding_entries_[i];
 | 
						|
      if (!encoding->Write(writer))
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  stop_position = writer->Position();
 | 
						|
  if (stop_position < 0)
 | 
						|
    return false;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Track::SetCodecPrivate(const uint8* codec_private, uint64 length) {
 | 
						|
  if (!codec_private || length < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  delete[] codec_private_;
 | 
						|
 | 
						|
  codec_private_ =
 | 
						|
      new (std::nothrow) uint8[static_cast<size_t>(length)];  // NOLINT
 | 
						|
  if (!codec_private_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  memcpy(codec_private_, codec_private, static_cast<size_t>(length));
 | 
						|
  codec_private_length_ = length;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void Track::set_codec_id(const char* codec_id) {
 | 
						|
  if (codec_id) {
 | 
						|
    delete[] codec_id_;
 | 
						|
 | 
						|
    const size_t length = strlen(codec_id) + 1;
 | 
						|
    codec_id_ = new (std::nothrow) char[length];  // NOLINT
 | 
						|
    if (codec_id_) {
 | 
						|
#ifdef _MSC_VER
 | 
						|
      strcpy_s(codec_id_, length, codec_id);
 | 
						|
#else
 | 
						|
      strcpy(codec_id_, codec_id);
 | 
						|
#endif
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// TODO(fgalligan): Vet the language parameter.
 | 
						|
void Track::set_language(const char* language) {
 | 
						|
  if (language) {
 | 
						|
    delete[] language_;
 | 
						|
 | 
						|
    const size_t length = strlen(language) + 1;
 | 
						|
    language_ = new (std::nothrow) char[length];  // NOLINT
 | 
						|
    if (language_) {
 | 
						|
#ifdef _MSC_VER
 | 
						|
      strcpy_s(language_, length, language);
 | 
						|
#else
 | 
						|
      strcpy(language_, language);
 | 
						|
#endif
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void Track::set_name(const char* name) {
 | 
						|
  if (name) {
 | 
						|
    delete[] name_;
 | 
						|
 | 
						|
    const size_t length = strlen(name) + 1;
 | 
						|
    name_ = new (std::nothrow) char[length];  // NOLINT
 | 
						|
    if (name_) {
 | 
						|
#ifdef _MSC_VER
 | 
						|
      strcpy_s(name_, length, name);
 | 
						|
#else
 | 
						|
      strcpy(name_, name);
 | 
						|
#endif
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// VideoTrack Class
 | 
						|
 | 
						|
VideoTrack::VideoTrack(unsigned int* seed)
 | 
						|
    : Track(seed),
 | 
						|
      display_height_(0),
 | 
						|
      display_width_(0),
 | 
						|
      frame_rate_(0.0),
 | 
						|
      height_(0),
 | 
						|
      stereo_mode_(0),
 | 
						|
      alpha_mode_(0),
 | 
						|
      width_(0) {}
 | 
						|
 | 
						|
VideoTrack::~VideoTrack() {}
 | 
						|
 | 
						|
bool VideoTrack::SetStereoMode(uint64 stereo_mode) {
 | 
						|
  if (stereo_mode != kMono && stereo_mode != kSideBySideLeftIsFirst &&
 | 
						|
      stereo_mode != kTopBottomRightIsFirst &&
 | 
						|
      stereo_mode != kTopBottomLeftIsFirst &&
 | 
						|
      stereo_mode != kSideBySideRightIsFirst)
 | 
						|
    return false;
 | 
						|
 | 
						|
  stereo_mode_ = stereo_mode;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool VideoTrack::SetAlphaMode(uint64 alpha_mode) {
 | 
						|
  if (alpha_mode != kNoAlpha && alpha_mode != kAlpha)
 | 
						|
    return false;
 | 
						|
 | 
						|
  alpha_mode_ = alpha_mode;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 VideoTrack::PayloadSize() const {
 | 
						|
  const uint64 parent_size = Track::PayloadSize();
 | 
						|
 | 
						|
  uint64 size = VideoPayloadSize();
 | 
						|
  size += EbmlMasterElementSize(kMkvVideo, size);
 | 
						|
 | 
						|
  return parent_size + size;
 | 
						|
}
 | 
						|
 | 
						|
bool VideoTrack::Write(IMkvWriter* writer) const {
 | 
						|
  if (!Track::Write(writer))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 size = VideoPayloadSize();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvVideo, size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvPixelWidth, width_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvPixelHeight, height_))
 | 
						|
    return false;
 | 
						|
  if (display_width_ > 0)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvDisplayWidth, display_width_))
 | 
						|
      return false;
 | 
						|
  if (display_height_ > 0)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvDisplayHeight, display_height_))
 | 
						|
      return false;
 | 
						|
  if (stereo_mode_ > kMono)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvStereoMode, stereo_mode_))
 | 
						|
      return false;
 | 
						|
  if (alpha_mode_ > kNoAlpha)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvAlphaMode, alpha_mode_))
 | 
						|
      return false;
 | 
						|
  if (frame_rate_ > 0.0)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvFrameRate,
 | 
						|
                          static_cast<float>(frame_rate_)))
 | 
						|
      return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 VideoTrack::VideoPayloadSize() const {
 | 
						|
  uint64 size = EbmlElementSize(kMkvPixelWidth, width_);
 | 
						|
  size += EbmlElementSize(kMkvPixelHeight, height_);
 | 
						|
  if (display_width_ > 0)
 | 
						|
    size += EbmlElementSize(kMkvDisplayWidth, display_width_);
 | 
						|
  if (display_height_ > 0)
 | 
						|
    size += EbmlElementSize(kMkvDisplayHeight, display_height_);
 | 
						|
  if (stereo_mode_ > kMono)
 | 
						|
    size += EbmlElementSize(kMkvStereoMode, stereo_mode_);
 | 
						|
  if (alpha_mode_ > kNoAlpha)
 | 
						|
    size += EbmlElementSize(kMkvAlphaMode, alpha_mode_);
 | 
						|
  if (frame_rate_ > 0.0)
 | 
						|
    size += EbmlElementSize(kMkvFrameRate, static_cast<float>(frame_rate_));
 | 
						|
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// AudioTrack Class
 | 
						|
 | 
						|
AudioTrack::AudioTrack(unsigned int* seed)
 | 
						|
    : Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {}
 | 
						|
 | 
						|
AudioTrack::~AudioTrack() {}
 | 
						|
 | 
						|
uint64 AudioTrack::PayloadSize() const {
 | 
						|
  const uint64 parent_size = Track::PayloadSize();
 | 
						|
 | 
						|
  uint64 size =
 | 
						|
      EbmlElementSize(kMkvSamplingFrequency, static_cast<float>(sample_rate_));
 | 
						|
  size += EbmlElementSize(kMkvChannels, channels_);
 | 
						|
  if (bit_depth_ > 0)
 | 
						|
    size += EbmlElementSize(kMkvBitDepth, bit_depth_);
 | 
						|
  size += EbmlMasterElementSize(kMkvAudio, size);
 | 
						|
 | 
						|
  return parent_size + size;
 | 
						|
}
 | 
						|
 | 
						|
bool AudioTrack::Write(IMkvWriter* writer) const {
 | 
						|
  if (!Track::Write(writer))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Calculate AudioSettings size.
 | 
						|
  uint64 size =
 | 
						|
      EbmlElementSize(kMkvSamplingFrequency, static_cast<float>(sample_rate_));
 | 
						|
  size += EbmlElementSize(kMkvChannels, channels_);
 | 
						|
  if (bit_depth_ > 0)
 | 
						|
    size += EbmlElementSize(kMkvBitDepth, bit_depth_);
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvAudio, size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvSamplingFrequency,
 | 
						|
                        static_cast<float>(sample_rate_)))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChannels, channels_))
 | 
						|
    return false;
 | 
						|
  if (bit_depth_ > 0)
 | 
						|
    if (!WriteEbmlElement(writer, kMkvBitDepth, bit_depth_))
 | 
						|
      return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Tracks Class
 | 
						|
 | 
						|
const char Tracks::kOpusCodecId[] = "A_OPUS";
 | 
						|
const char Tracks::kVorbisCodecId[] = "A_VORBIS";
 | 
						|
const char Tracks::kVp8CodecId[] = "V_VP8";
 | 
						|
const char Tracks::kVp9CodecId[] = "V_VP9";
 | 
						|
 | 
						|
Tracks::Tracks() : track_entries_(NULL), track_entries_size_(0) {}
 | 
						|
 | 
						|
Tracks::~Tracks() {
 | 
						|
  if (track_entries_) {
 | 
						|
    for (uint32 i = 0; i < track_entries_size_; ++i) {
 | 
						|
      Track* const track = track_entries_[i];
 | 
						|
      delete track;
 | 
						|
    }
 | 
						|
    delete[] track_entries_;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool Tracks::AddTrack(Track* track, int32 number) {
 | 
						|
  if (number < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  // This muxer only supports track numbers in the range [1, 126], in
 | 
						|
  // order to be able (to use Matroska integer representation) to
 | 
						|
  // serialize the block header (of which the track number is a part)
 | 
						|
  // for a frame using exactly 4 bytes.
 | 
						|
 | 
						|
  if (number > 0x7E)
 | 
						|
    return false;
 | 
						|
 | 
						|
  uint32 track_num = number;
 | 
						|
 | 
						|
  if (track_num > 0) {
 | 
						|
    // Check to make sure a track does not already have |track_num|.
 | 
						|
    for (uint32 i = 0; i < track_entries_size_; ++i) {
 | 
						|
      if (track_entries_[i]->number() == track_num)
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const uint32 count = track_entries_size_ + 1;
 | 
						|
 | 
						|
  Track** const track_entries = new (std::nothrow) Track* [count];  // NOLINT
 | 
						|
  if (!track_entries)
 | 
						|
    return false;
 | 
						|
 | 
						|
  for (uint32 i = 0; i < track_entries_size_; ++i) {
 | 
						|
    track_entries[i] = track_entries_[i];
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] track_entries_;
 | 
						|
 | 
						|
  // Find the lowest availible track number > 0.
 | 
						|
  if (track_num == 0) {
 | 
						|
    track_num = count;
 | 
						|
 | 
						|
    // Check to make sure a track does not already have |track_num|.
 | 
						|
    bool exit = false;
 | 
						|
    do {
 | 
						|
      exit = true;
 | 
						|
      for (uint32 i = 0; i < track_entries_size_; ++i) {
 | 
						|
        if (track_entries[i]->number() == track_num) {
 | 
						|
          track_num++;
 | 
						|
          exit = false;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } while (!exit);
 | 
						|
  }
 | 
						|
  track->set_number(track_num);
 | 
						|
 | 
						|
  track_entries_ = track_entries;
 | 
						|
  track_entries_[track_entries_size_] = track;
 | 
						|
  track_entries_size_ = count;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
const Track* Tracks::GetTrackByIndex(uint32 index) const {
 | 
						|
  if (track_entries_ == NULL)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  if (index >= track_entries_size_)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  return track_entries_[index];
 | 
						|
}
 | 
						|
 | 
						|
Track* Tracks::GetTrackByNumber(uint64 track_number) const {
 | 
						|
  const int32 count = track_entries_size();
 | 
						|
  for (int32 i = 0; i < count; ++i) {
 | 
						|
    if (track_entries_[i]->number() == track_number)
 | 
						|
      return track_entries_[i];
 | 
						|
  }
 | 
						|
 | 
						|
  return NULL;
 | 
						|
}
 | 
						|
 | 
						|
bool Tracks::TrackIsAudio(uint64 track_number) const {
 | 
						|
  const Track* const track = GetTrackByNumber(track_number);
 | 
						|
 | 
						|
  if (track->type() == kAudio)
 | 
						|
    return true;
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Tracks::TrackIsVideo(uint64 track_number) const {
 | 
						|
  const Track* const track = GetTrackByNumber(track_number);
 | 
						|
 | 
						|
  if (track->type() == kVideo)
 | 
						|
    return true;
 | 
						|
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Tracks::Write(IMkvWriter* writer) const {
 | 
						|
  uint64 size = 0;
 | 
						|
  const int32 count = track_entries_size();
 | 
						|
  for (int32 i = 0; i < count; ++i) {
 | 
						|
    const Track* const track = GetTrackByIndex(i);
 | 
						|
 | 
						|
    if (!track)
 | 
						|
      return false;
 | 
						|
 | 
						|
    size += track->Size();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvTracks, size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  for (int32 i = 0; i < count; ++i) {
 | 
						|
    const Track* const track = GetTrackByIndex(i);
 | 
						|
    if (!track->Write(writer))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Chapter Class
 | 
						|
 | 
						|
bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); }
 | 
						|
 | 
						|
void Chapter::set_time(const Segment& segment, uint64 start_ns, uint64 end_ns) {
 | 
						|
  const SegmentInfo* const info = segment.GetSegmentInfo();
 | 
						|
  const uint64 timecode_scale = info->timecode_scale();
 | 
						|
  start_timecode_ = start_ns / timecode_scale;
 | 
						|
  end_timecode_ = end_ns / timecode_scale;
 | 
						|
}
 | 
						|
 | 
						|
bool Chapter::add_string(const char* title, const char* language,
 | 
						|
                         const char* country) {
 | 
						|
  if (!ExpandDisplaysArray())
 | 
						|
    return false;
 | 
						|
 | 
						|
  Display& d = displays_[displays_count_++];
 | 
						|
  d.Init();
 | 
						|
 | 
						|
  if (!d.set_title(title))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!d.set_language(language))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!d.set_country(country))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
Chapter::Chapter() {
 | 
						|
  // This ctor only constructs the object.  Proper initialization is
 | 
						|
  // done in Init() (called in Chapters::AddChapter()).  The only
 | 
						|
  // reason we bother implementing this ctor is because we had to
 | 
						|
  // declare it as private (along with the dtor), in order to prevent
 | 
						|
  // clients from creating Chapter instances (a privelege we grant
 | 
						|
  // only to the Chapters class).  Doing no initialization here also
 | 
						|
  // means that creating arrays of chapter objects is more efficient,
 | 
						|
  // because we only initialize each new chapter object as it becomes
 | 
						|
  // active on the array.
 | 
						|
}
 | 
						|
 | 
						|
Chapter::~Chapter() {}
 | 
						|
 | 
						|
void Chapter::Init(unsigned int* seed) {
 | 
						|
  id_ = NULL;
 | 
						|
  displays_ = NULL;
 | 
						|
  displays_size_ = 0;
 | 
						|
  displays_count_ = 0;
 | 
						|
  uid_ = MakeUID(seed);
 | 
						|
}
 | 
						|
 | 
						|
void Chapter::ShallowCopy(Chapter* dst) const {
 | 
						|
  dst->id_ = id_;
 | 
						|
  dst->start_timecode_ = start_timecode_;
 | 
						|
  dst->end_timecode_ = end_timecode_;
 | 
						|
  dst->uid_ = uid_;
 | 
						|
  dst->displays_ = displays_;
 | 
						|
  dst->displays_size_ = displays_size_;
 | 
						|
  dst->displays_count_ = displays_count_;
 | 
						|
}
 | 
						|
 | 
						|
void Chapter::Clear() {
 | 
						|
  StrCpy(NULL, &id_);
 | 
						|
 | 
						|
  while (displays_count_ > 0) {
 | 
						|
    Display& d = displays_[--displays_count_];
 | 
						|
    d.Clear();
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] displays_;
 | 
						|
  displays_ = NULL;
 | 
						|
 | 
						|
  displays_size_ = 0;
 | 
						|
}
 | 
						|
 | 
						|
bool Chapter::ExpandDisplaysArray() {
 | 
						|
  if (displays_size_ > displays_count_)
 | 
						|
    return true;  // nothing to do yet
 | 
						|
 | 
						|
  const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_;
 | 
						|
 | 
						|
  Display* const displays = new (std::nothrow) Display[size];  // NOLINT
 | 
						|
  if (displays == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  for (int idx = 0; idx < displays_count_; ++idx) {
 | 
						|
    displays[idx] = displays_[idx];  // shallow copy
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] displays_;
 | 
						|
 | 
						|
  displays_ = displays;
 | 
						|
  displays_size_ = size;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 Chapter::WriteAtom(IMkvWriter* writer) const {
 | 
						|
  uint64 payload_size = EbmlElementSize(kMkvChapterStringUID, id_) +
 | 
						|
                        EbmlElementSize(kMkvChapterUID, uid_) +
 | 
						|
                        EbmlElementSize(kMkvChapterTimeStart, start_timecode_) +
 | 
						|
                        EbmlElementSize(kMkvChapterTimeEnd, end_timecode_);
 | 
						|
 | 
						|
  for (int idx = 0; idx < displays_count_; ++idx) {
 | 
						|
    const Display& d = displays_[idx];
 | 
						|
    payload_size += d.WriteDisplay(NULL);
 | 
						|
  }
 | 
						|
 | 
						|
  const uint64 atom_size =
 | 
						|
      EbmlMasterElementSize(kMkvChapterAtom, payload_size) + payload_size;
 | 
						|
 | 
						|
  if (writer == NULL)
 | 
						|
    return atom_size;
 | 
						|
 | 
						|
  const int64 start = writer->Position();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvChapterAtom, payload_size))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChapterStringUID, id_))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChapterUID, uid_))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChapterTimeStart, start_timecode_))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChapterTimeEnd, end_timecode_))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  for (int idx = 0; idx < displays_count_; ++idx) {
 | 
						|
    const Display& d = displays_[idx];
 | 
						|
 | 
						|
    if (!d.WriteDisplay(writer))
 | 
						|
      return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  const int64 stop = writer->Position();
 | 
						|
 | 
						|
  if (stop >= start && uint64(stop - start) != atom_size)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  return atom_size;
 | 
						|
}
 | 
						|
 | 
						|
void Chapter::Display::Init() {
 | 
						|
  title_ = NULL;
 | 
						|
  language_ = NULL;
 | 
						|
  country_ = NULL;
 | 
						|
}
 | 
						|
 | 
						|
void Chapter::Display::Clear() {
 | 
						|
  StrCpy(NULL, &title_);
 | 
						|
  StrCpy(NULL, &language_);
 | 
						|
  StrCpy(NULL, &country_);
 | 
						|
}
 | 
						|
 | 
						|
bool Chapter::Display::set_title(const char* title) {
 | 
						|
  return StrCpy(title, &title_);
 | 
						|
}
 | 
						|
 | 
						|
bool Chapter::Display::set_language(const char* language) {
 | 
						|
  return StrCpy(language, &language_);
 | 
						|
}
 | 
						|
 | 
						|
bool Chapter::Display::set_country(const char* country) {
 | 
						|
  return StrCpy(country, &country_);
 | 
						|
}
 | 
						|
 | 
						|
uint64 Chapter::Display::WriteDisplay(IMkvWriter* writer) const {
 | 
						|
  uint64 payload_size = EbmlElementSize(kMkvChapString, title_);
 | 
						|
 | 
						|
  if (language_)
 | 
						|
    payload_size += EbmlElementSize(kMkvChapLanguage, language_);
 | 
						|
 | 
						|
  if (country_)
 | 
						|
    payload_size += EbmlElementSize(kMkvChapCountry, country_);
 | 
						|
 | 
						|
  const uint64 display_size =
 | 
						|
      EbmlMasterElementSize(kMkvChapterDisplay, payload_size) + payload_size;
 | 
						|
 | 
						|
  if (writer == NULL)
 | 
						|
    return display_size;
 | 
						|
 | 
						|
  const int64 start = writer->Position();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvChapterDisplay, payload_size))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvChapString, title_))
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (language_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvChapLanguage, language_))
 | 
						|
      return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  if (country_) {
 | 
						|
    if (!WriteEbmlElement(writer, kMkvChapCountry, country_))
 | 
						|
      return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  const int64 stop = writer->Position();
 | 
						|
 | 
						|
  if (stop >= start && uint64(stop - start) != display_size)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  return display_size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Chapters Class
 | 
						|
 | 
						|
Chapters::Chapters() : chapters_size_(0), chapters_count_(0), chapters_(NULL) {}
 | 
						|
 | 
						|
Chapters::~Chapters() {
 | 
						|
  while (chapters_count_ > 0) {
 | 
						|
    Chapter& chapter = chapters_[--chapters_count_];
 | 
						|
    chapter.Clear();
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] chapters_;
 | 
						|
  chapters_ = NULL;
 | 
						|
}
 | 
						|
 | 
						|
int Chapters::Count() const { return chapters_count_; }
 | 
						|
 | 
						|
Chapter* Chapters::AddChapter(unsigned int* seed) {
 | 
						|
  if (!ExpandChaptersArray())
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  Chapter& chapter = chapters_[chapters_count_++];
 | 
						|
  chapter.Init(seed);
 | 
						|
 | 
						|
  return &chapter;
 | 
						|
}
 | 
						|
 | 
						|
bool Chapters::Write(IMkvWriter* writer) const {
 | 
						|
  if (writer == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 payload_size = WriteEdition(NULL);  // return size only
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvChapters, payload_size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 start = writer->Position();
 | 
						|
 | 
						|
  if (WriteEdition(writer) == 0)  // error
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 stop = writer->Position();
 | 
						|
 | 
						|
  if (stop >= start && uint64(stop - start) != payload_size)
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Chapters::ExpandChaptersArray() {
 | 
						|
  if (chapters_size_ > chapters_count_)
 | 
						|
    return true;  // nothing to do yet
 | 
						|
 | 
						|
  const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_;
 | 
						|
 | 
						|
  Chapter* const chapters = new (std::nothrow) Chapter[size];  // NOLINT
 | 
						|
  if (chapters == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  for (int idx = 0; idx < chapters_count_; ++idx) {
 | 
						|
    const Chapter& src = chapters_[idx];
 | 
						|
    Chapter* const dst = chapters + idx;
 | 
						|
    src.ShallowCopy(dst);
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] chapters_;
 | 
						|
 | 
						|
  chapters_ = chapters;
 | 
						|
  chapters_size_ = size;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 Chapters::WriteEdition(IMkvWriter* writer) const {
 | 
						|
  uint64 payload_size = 0;
 | 
						|
 | 
						|
  for (int idx = 0; idx < chapters_count_; ++idx) {
 | 
						|
    const Chapter& chapter = chapters_[idx];
 | 
						|
    payload_size += chapter.WriteAtom(NULL);
 | 
						|
  }
 | 
						|
 | 
						|
  const uint64 edition_size =
 | 
						|
      EbmlMasterElementSize(kMkvEditionEntry, payload_size) + payload_size;
 | 
						|
 | 
						|
  if (writer == NULL)  // return size only
 | 
						|
    return edition_size;
 | 
						|
 | 
						|
  const int64 start = writer->Position();
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvEditionEntry, payload_size))
 | 
						|
    return 0;  // error
 | 
						|
 | 
						|
  for (int idx = 0; idx < chapters_count_; ++idx) {
 | 
						|
    const Chapter& chapter = chapters_[idx];
 | 
						|
 | 
						|
    const uint64 chapter_size = chapter.WriteAtom(writer);
 | 
						|
    if (chapter_size == 0)  // error
 | 
						|
      return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  const int64 stop = writer->Position();
 | 
						|
 | 
						|
  if (stop >= start && uint64(stop - start) != edition_size)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  return edition_size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Cluster class
 | 
						|
 | 
						|
Cluster::Cluster(uint64 timecode, int64 cues_pos)
 | 
						|
    : blocks_added_(0),
 | 
						|
      finalized_(false),
 | 
						|
      header_written_(false),
 | 
						|
      payload_size_(0),
 | 
						|
      position_for_cues_(cues_pos),
 | 
						|
      size_position_(-1),
 | 
						|
      timecode_(timecode),
 | 
						|
      writer_(NULL) {}
 | 
						|
 | 
						|
Cluster::~Cluster() {}
 | 
						|
 | 
						|
bool Cluster::Init(IMkvWriter* ptr_writer) {
 | 
						|
  if (!ptr_writer) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  writer_ = ptr_writer;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::AddFrame(const uint8* frame, uint64 length, uint64 track_number,
 | 
						|
                       uint64 abs_timecode, bool is_key) {
 | 
						|
  return DoWriteBlock(frame, length, track_number, abs_timecode, is_key ? 1 : 0,
 | 
						|
                      &WriteSimpleBlock);
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::AddFrameWithAdditional(const uint8* frame, uint64 length,
 | 
						|
                                     const uint8* additional,
 | 
						|
                                     uint64 additional_length, uint64 add_id,
 | 
						|
                                     uint64 track_number, uint64 abs_timecode,
 | 
						|
                                     bool is_key) {
 | 
						|
  return DoWriteBlockWithAdditional(
 | 
						|
      frame, length, additional, additional_length, add_id, track_number,
 | 
						|
      abs_timecode, is_key ? 1 : 0, &WriteBlockWithAdditional);
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::AddFrameWithDiscardPadding(const uint8* frame, uint64 length,
 | 
						|
                                         int64 discard_padding,
 | 
						|
                                         uint64 track_number,
 | 
						|
                                         uint64 abs_timecode, bool is_key) {
 | 
						|
  return DoWriteBlockWithDiscardPadding(
 | 
						|
      frame, length, discard_padding, track_number, abs_timecode,
 | 
						|
      is_key ? 1 : 0, &WriteBlockWithDiscardPadding);
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::AddMetadata(const uint8* frame, uint64 length,
 | 
						|
                          uint64 track_number, uint64 abs_timecode,
 | 
						|
                          uint64 duration_timecode) {
 | 
						|
  return DoWriteBlock(frame, length, track_number, abs_timecode,
 | 
						|
                      duration_timecode, &WriteMetadataBlock);
 | 
						|
}
 | 
						|
 | 
						|
void Cluster::AddPayloadSize(uint64 size) { payload_size_ += size; }
 | 
						|
 | 
						|
bool Cluster::Finalize() {
 | 
						|
  if (!writer_ || finalized_ || size_position_ == -1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (writer_->Seekable()) {
 | 
						|
    const int64 pos = writer_->Position();
 | 
						|
 | 
						|
    if (writer_->Position(size_position_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (WriteUIntSize(writer_, payload_size(), 8))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (writer_->Position(pos))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  finalized_ = true;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 Cluster::Size() const {
 | 
						|
  const uint64 element_size =
 | 
						|
      EbmlMasterElementSize(kMkvCluster, 0xFFFFFFFFFFFFFFFFULL) + payload_size_;
 | 
						|
  return element_size;
 | 
						|
}
 | 
						|
 | 
						|
template <typename Type>
 | 
						|
bool Cluster::PreWriteBlock(Type* write_function) {
 | 
						|
  if (write_function == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (finalized_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!header_written_) {
 | 
						|
    if (!WriteClusterHeader())
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void Cluster::PostWriteBlock(uint64 element_size) {
 | 
						|
  AddPayloadSize(element_size);
 | 
						|
  ++blocks_added_;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::IsValidTrackNumber(uint64 track_number) const {
 | 
						|
  return (track_number > 0 && track_number <= 0x7E);
 | 
						|
}
 | 
						|
 | 
						|
int64 Cluster::GetRelativeTimecode(int64 abs_timecode) const {
 | 
						|
  const int64 cluster_timecode = this->Cluster::timecode();
 | 
						|
  const int64 rel_timecode =
 | 
						|
      static_cast<int64>(abs_timecode) - cluster_timecode;
 | 
						|
 | 
						|
  if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  return rel_timecode;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::DoWriteBlock(const uint8* frame, uint64 length,
 | 
						|
                           uint64 track_number, uint64 abs_timecode,
 | 
						|
                           uint64 generic_arg, WriteBlock write_block) {
 | 
						|
  if (frame == NULL || length == 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!IsValidTrackNumber(track_number))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 rel_timecode = GetRelativeTimecode(abs_timecode);
 | 
						|
  if (rel_timecode < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!PreWriteBlock(write_block))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 element_size = (*write_block)(
 | 
						|
      writer_, frame, length, track_number, rel_timecode, generic_arg);
 | 
						|
  if (element_size == 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  PostWriteBlock(element_size);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::DoWriteBlockWithAdditional(
 | 
						|
    const uint8* frame, uint64 length, const uint8* additional,
 | 
						|
    uint64 additional_length, uint64 add_id, uint64 track_number,
 | 
						|
    uint64 abs_timecode, uint64 generic_arg, WriteBlockAdditional write_block) {
 | 
						|
  if (frame == NULL || length == 0 || additional == NULL ||
 | 
						|
      additional_length == 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!IsValidTrackNumber(track_number))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 rel_timecode = GetRelativeTimecode(abs_timecode);
 | 
						|
  if (rel_timecode < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!PreWriteBlock(write_block))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 element_size =
 | 
						|
      (*write_block)(writer_, frame, length, additional, additional_length,
 | 
						|
                     add_id, track_number, rel_timecode, generic_arg);
 | 
						|
  if (element_size == 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  PostWriteBlock(element_size);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::DoWriteBlockWithDiscardPadding(
 | 
						|
    const uint8* frame, uint64 length, int64 discard_padding,
 | 
						|
    uint64 track_number, uint64 abs_timecode, uint64 generic_arg,
 | 
						|
    WriteBlockDiscardPadding write_block) {
 | 
						|
  if (frame == NULL || length == 0 || discard_padding <= 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!IsValidTrackNumber(track_number))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 rel_timecode = GetRelativeTimecode(abs_timecode);
 | 
						|
  if (rel_timecode < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!PreWriteBlock(write_block))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 element_size =
 | 
						|
      (*write_block)(writer_, frame, length, discard_padding, track_number,
 | 
						|
                     rel_timecode, generic_arg);
 | 
						|
  if (element_size == 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  PostWriteBlock(element_size);
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Cluster::WriteClusterHeader() {
 | 
						|
  if (finalized_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (WriteID(writer_, kMkvCluster))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Save for later.
 | 
						|
  size_position_ = writer_->Position();
 | 
						|
 | 
						|
  // Write "unknown" (EBML coded -1) as cluster size value. We need to write 8
 | 
						|
  // bytes because we do not know how big our cluster will be.
 | 
						|
  if (SerializeInt(writer_, kEbmlUnknownValue, 8))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer_, kMkvTimecode, timecode()))
 | 
						|
    return false;
 | 
						|
  AddPayloadSize(EbmlElementSize(kMkvTimecode, timecode()));
 | 
						|
  header_written_ = true;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// SeekHead Class
 | 
						|
 | 
						|
SeekHead::SeekHead() : start_pos_(0ULL) {
 | 
						|
  for (int32 i = 0; i < kSeekEntryCount; ++i) {
 | 
						|
    seek_entry_id_[i] = 0;
 | 
						|
    seek_entry_pos_[i] = 0;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
SeekHead::~SeekHead() {}
 | 
						|
 | 
						|
bool SeekHead::Finalize(IMkvWriter* writer) const {
 | 
						|
  if (writer->Seekable()) {
 | 
						|
    if (start_pos_ == -1)
 | 
						|
      return false;
 | 
						|
 | 
						|
    uint64 payload_size = 0;
 | 
						|
    uint64 entry_size[kSeekEntryCount];
 | 
						|
 | 
						|
    for (int32 i = 0; i < kSeekEntryCount; ++i) {
 | 
						|
      if (seek_entry_id_[i] != 0) {
 | 
						|
        entry_size[i] =
 | 
						|
            EbmlElementSize(kMkvSeekID, static_cast<uint64>(seek_entry_id_[i]));
 | 
						|
        entry_size[i] += EbmlElementSize(kMkvSeekPosition, seek_entry_pos_[i]);
 | 
						|
 | 
						|
        payload_size +=
 | 
						|
            EbmlMasterElementSize(kMkvSeek, entry_size[i]) + entry_size[i];
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // No SeekHead elements
 | 
						|
    if (payload_size == 0)
 | 
						|
      return true;
 | 
						|
 | 
						|
    const int64 pos = writer->Position();
 | 
						|
    if (writer->Position(start_pos_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (!WriteEbmlMasterElement(writer, kMkvSeekHead, payload_size))
 | 
						|
      return false;
 | 
						|
 | 
						|
    for (int32 i = 0; i < kSeekEntryCount; ++i) {
 | 
						|
      if (seek_entry_id_[i] != 0) {
 | 
						|
        if (!WriteEbmlMasterElement(writer, kMkvSeek, entry_size[i]))
 | 
						|
          return false;
 | 
						|
 | 
						|
        if (!WriteEbmlElement(writer, kMkvSeekID,
 | 
						|
                              static_cast<uint64>(seek_entry_id_[i])))
 | 
						|
          return false;
 | 
						|
 | 
						|
        if (!WriteEbmlElement(writer, kMkvSeekPosition, seek_entry_pos_[i]))
 | 
						|
          return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    const uint64 total_entry_size = kSeekEntryCount * MaxEntrySize();
 | 
						|
    const uint64 total_size =
 | 
						|
        EbmlMasterElementSize(kMkvSeekHead, total_entry_size) +
 | 
						|
        total_entry_size;
 | 
						|
    const int64 size_left = total_size - (writer->Position() - start_pos_);
 | 
						|
 | 
						|
    const uint64 bytes_written = WriteVoidElement(writer, size_left);
 | 
						|
    if (!bytes_written)
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (writer->Position(pos))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool SeekHead::Write(IMkvWriter* writer) {
 | 
						|
  const uint64 entry_size = kSeekEntryCount * MaxEntrySize();
 | 
						|
  const uint64 size = EbmlMasterElementSize(kMkvSeekHead, entry_size);
 | 
						|
 | 
						|
  start_pos_ = writer->Position();
 | 
						|
 | 
						|
  const uint64 bytes_written = WriteVoidElement(writer, size + entry_size);
 | 
						|
  if (!bytes_written)
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool SeekHead::AddSeekEntry(uint32 id, uint64 pos) {
 | 
						|
  for (int32 i = 0; i < kSeekEntryCount; ++i) {
 | 
						|
    if (seek_entry_id_[i] == 0) {
 | 
						|
      seek_entry_id_[i] = id;
 | 
						|
      seek_entry_pos_[i] = pos;
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
uint32 SeekHead::GetId(int index) const {
 | 
						|
  if (index < 0 || index >= kSeekEntryCount)
 | 
						|
    return UINT_MAX;
 | 
						|
  return seek_entry_id_[index];
 | 
						|
}
 | 
						|
 | 
						|
uint64 SeekHead::GetPosition(int index) const {
 | 
						|
  if (index < 0 || index >= kSeekEntryCount)
 | 
						|
    return ULLONG_MAX;
 | 
						|
  return seek_entry_pos_[index];
 | 
						|
}
 | 
						|
 | 
						|
bool SeekHead::SetSeekEntry(int index, uint32 id, uint64 position) {
 | 
						|
  if (index < 0 || index >= kSeekEntryCount)
 | 
						|
    return false;
 | 
						|
  seek_entry_id_[index] = id;
 | 
						|
  seek_entry_pos_[index] = position;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 SeekHead::MaxEntrySize() const {
 | 
						|
  const uint64 max_entry_payload_size =
 | 
						|
      EbmlElementSize(kMkvSeekID, 0xffffffffULL) +
 | 
						|
      EbmlElementSize(kMkvSeekPosition, 0xffffffffffffffffULL);
 | 
						|
  const uint64 max_entry_size =
 | 
						|
      EbmlMasterElementSize(kMkvSeek, max_entry_payload_size) +
 | 
						|
      max_entry_payload_size;
 | 
						|
 | 
						|
  return max_entry_size;
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// SegmentInfo Class
 | 
						|
 | 
						|
SegmentInfo::SegmentInfo()
 | 
						|
    : duration_(-1.0),
 | 
						|
      muxing_app_(NULL),
 | 
						|
      timecode_scale_(1000000ULL),
 | 
						|
      writing_app_(NULL),
 | 
						|
      date_utc_(LLONG_MIN),
 | 
						|
      duration_pos_(-1) {}
 | 
						|
 | 
						|
SegmentInfo::~SegmentInfo() {
 | 
						|
  delete[] muxing_app_;
 | 
						|
  delete[] writing_app_;
 | 
						|
}
 | 
						|
 | 
						|
bool SegmentInfo::Init() {
 | 
						|
  int32 major;
 | 
						|
  int32 minor;
 | 
						|
  int32 build;
 | 
						|
  int32 revision;
 | 
						|
  GetVersion(&major, &minor, &build, &revision);
 | 
						|
  char temp[256];
 | 
						|
#ifdef _MSC_VER
 | 
						|
  sprintf_s(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
 | 
						|
            minor, build, revision);
 | 
						|
#else
 | 
						|
  snprintf(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
 | 
						|
           minor, build, revision);
 | 
						|
#endif
 | 
						|
 | 
						|
  const size_t app_len = strlen(temp) + 1;
 | 
						|
 | 
						|
  delete[] muxing_app_;
 | 
						|
 | 
						|
  muxing_app_ = new (std::nothrow) char[app_len];  // NOLINT
 | 
						|
  if (!muxing_app_)
 | 
						|
    return false;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
  strcpy_s(muxing_app_, app_len, temp);
 | 
						|
#else
 | 
						|
  strcpy(muxing_app_, temp);
 | 
						|
#endif
 | 
						|
 | 
						|
  set_writing_app(temp);
 | 
						|
  if (!writing_app_)
 | 
						|
    return false;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool SegmentInfo::Finalize(IMkvWriter* writer) const {
 | 
						|
  if (!writer)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (duration_ > 0.0) {
 | 
						|
    if (writer->Seekable()) {
 | 
						|
      if (duration_pos_ == -1)
 | 
						|
        return false;
 | 
						|
 | 
						|
      const int64 pos = writer->Position();
 | 
						|
 | 
						|
      if (writer->Position(duration_pos_))
 | 
						|
        return false;
 | 
						|
 | 
						|
      if (!WriteEbmlElement(writer, kMkvDuration,
 | 
						|
                            static_cast<float>(duration_)))
 | 
						|
        return false;
 | 
						|
 | 
						|
      if (writer->Position(pos))
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool SegmentInfo::Write(IMkvWriter* writer) {
 | 
						|
  if (!writer || !muxing_app_ || !writing_app_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  uint64 size = EbmlElementSize(kMkvTimecodeScale, timecode_scale_);
 | 
						|
  if (duration_ > 0.0)
 | 
						|
    size += EbmlElementSize(kMkvDuration, static_cast<float>(duration_));
 | 
						|
  if (date_utc_ != LLONG_MIN)
 | 
						|
    size += EbmlDateElementSize(kMkvDateUTC, date_utc_);
 | 
						|
  size += EbmlElementSize(kMkvMuxingApp, muxing_app_);
 | 
						|
  size += EbmlElementSize(kMkvWritingApp, writing_app_);
 | 
						|
 | 
						|
  if (!WriteEbmlMasterElement(writer, kMkvInfo, size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 payload_position = writer->Position();
 | 
						|
  if (payload_position < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvTimecodeScale, timecode_scale_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (duration_ > 0.0) {
 | 
						|
    // Save for later
 | 
						|
    duration_pos_ = writer->Position();
 | 
						|
 | 
						|
    if (!WriteEbmlElement(writer, kMkvDuration, static_cast<float>(duration_)))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (date_utc_ != LLONG_MIN)
 | 
						|
    WriteEbmlDateElement(writer, kMkvDateUTC, date_utc_);
 | 
						|
 | 
						|
  if (!WriteEbmlElement(writer, kMkvMuxingApp, muxing_app_))
 | 
						|
    return false;
 | 
						|
  if (!WriteEbmlElement(writer, kMkvWritingApp, writing_app_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  const int64 stop_position = writer->Position();
 | 
						|
  if (stop_position < 0 ||
 | 
						|
      stop_position - payload_position != static_cast<int64>(size))
 | 
						|
    return false;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void SegmentInfo::set_muxing_app(const char* app) {
 | 
						|
  if (app) {
 | 
						|
    const size_t length = strlen(app) + 1;
 | 
						|
    char* temp_str = new (std::nothrow) char[length];  // NOLINT
 | 
						|
    if (!temp_str)
 | 
						|
      return;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
    strcpy_s(temp_str, length, app);
 | 
						|
#else
 | 
						|
    strcpy(temp_str, app);
 | 
						|
#endif
 | 
						|
 | 
						|
    delete[] muxing_app_;
 | 
						|
    muxing_app_ = temp_str;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void SegmentInfo::set_writing_app(const char* app) {
 | 
						|
  if (app) {
 | 
						|
    const size_t length = strlen(app) + 1;
 | 
						|
    char* temp_str = new (std::nothrow) char[length];  // NOLINT
 | 
						|
    if (!temp_str)
 | 
						|
      return;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
    strcpy_s(temp_str, length, app);
 | 
						|
#else
 | 
						|
    strcpy(temp_str, app);
 | 
						|
#endif
 | 
						|
 | 
						|
    delete[] writing_app_;
 | 
						|
    writing_app_ = temp_str;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
// Segment Class
 | 
						|
 | 
						|
Segment::Segment()
 | 
						|
    : chunk_count_(0),
 | 
						|
      chunk_name_(NULL),
 | 
						|
      chunk_writer_cluster_(NULL),
 | 
						|
      chunk_writer_cues_(NULL),
 | 
						|
      chunk_writer_header_(NULL),
 | 
						|
      chunking_(false),
 | 
						|
      chunking_base_name_(NULL),
 | 
						|
      cluster_list_(NULL),
 | 
						|
      cluster_list_capacity_(0),
 | 
						|
      cluster_list_size_(0),
 | 
						|
      cues_position_(kAfterClusters),
 | 
						|
      cues_track_(0),
 | 
						|
      force_new_cluster_(false),
 | 
						|
      frames_(NULL),
 | 
						|
      frames_capacity_(0),
 | 
						|
      frames_size_(0),
 | 
						|
      has_video_(false),
 | 
						|
      header_written_(false),
 | 
						|
      last_block_duration_(0),
 | 
						|
      last_timestamp_(0),
 | 
						|
      max_cluster_duration_(kDefaultMaxClusterDuration),
 | 
						|
      max_cluster_size_(0),
 | 
						|
      mode_(kFile),
 | 
						|
      new_cuepoint_(false),
 | 
						|
      output_cues_(true),
 | 
						|
      payload_pos_(0),
 | 
						|
      size_position_(0),
 | 
						|
      writer_cluster_(NULL),
 | 
						|
      writer_cues_(NULL),
 | 
						|
      writer_header_(NULL) {
 | 
						|
  const time_t curr_time = time(NULL);
 | 
						|
  seed_ = static_cast<unsigned int>(curr_time);
 | 
						|
#ifdef _WIN32
 | 
						|
  srand(seed_);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
Segment::~Segment() {
 | 
						|
  if (cluster_list_) {
 | 
						|
    for (int32 i = 0; i < cluster_list_size_; ++i) {
 | 
						|
      Cluster* const cluster = cluster_list_[i];
 | 
						|
      delete cluster;
 | 
						|
    }
 | 
						|
    delete[] cluster_list_;
 | 
						|
  }
 | 
						|
 | 
						|
  if (frames_) {
 | 
						|
    for (int32 i = 0; i < frames_size_; ++i) {
 | 
						|
      Frame* const frame = frames_[i];
 | 
						|
      delete frame;
 | 
						|
    }
 | 
						|
    delete[] frames_;
 | 
						|
  }
 | 
						|
 | 
						|
  delete[] chunk_name_;
 | 
						|
  delete[] chunking_base_name_;
 | 
						|
 | 
						|
  if (chunk_writer_cluster_) {
 | 
						|
    chunk_writer_cluster_->Close();
 | 
						|
    delete chunk_writer_cluster_;
 | 
						|
  }
 | 
						|
  if (chunk_writer_cues_) {
 | 
						|
    chunk_writer_cues_->Close();
 | 
						|
    delete chunk_writer_cues_;
 | 
						|
  }
 | 
						|
  if (chunk_writer_header_) {
 | 
						|
    chunk_writer_header_->Close();
 | 
						|
    delete chunk_writer_header_;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void Segment::MoveCuesBeforeClustersHelper(uint64 diff, int32 index,
 | 
						|
                                           uint64* cues_size) {
 | 
						|
  const uint64 old_cues_size = *cues_size;
 | 
						|
  CuePoint* const cue_point = cues_.GetCueByIndex(index);
 | 
						|
  if (cue_point == NULL)
 | 
						|
    return;
 | 
						|
  const uint64 old_cue_point_size = cue_point->Size();
 | 
						|
  const uint64 cluster_pos = cue_point->cluster_pos() + diff;
 | 
						|
  cue_point->set_cluster_pos(cluster_pos);  // update the new cluster position
 | 
						|
  // New size of the cue is computed as follows
 | 
						|
  //    Let a = current size of Cues Element
 | 
						|
  //    Let b = Difference in Cue Point's size after this pass
 | 
						|
  //    Let c = Difference in length of Cues Element's size
 | 
						|
  //            (This is computed as CodedSize(a + b) - CodedSize(a)
 | 
						|
  //    Let d = a + b + c. Now d is the new size of the Cues element which is
 | 
						|
  //                       passed on to the next recursive call.
 | 
						|
  const uint64 cue_point_size_diff = cue_point->Size() - old_cue_point_size;
 | 
						|
  const uint64 cue_size_diff =
 | 
						|
      GetCodedUIntSize(*cues_size + cue_point_size_diff) -
 | 
						|
      GetCodedUIntSize(*cues_size);
 | 
						|
  *cues_size += cue_point_size_diff + cue_size_diff;
 | 
						|
  diff = *cues_size - old_cues_size;
 | 
						|
  if (diff > 0) {
 | 
						|
    for (int32 i = 0; i < cues_.cue_entries_size(); ++i) {
 | 
						|
      MoveCuesBeforeClustersHelper(diff, i, cues_size);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void Segment::MoveCuesBeforeClusters() {
 | 
						|
  const uint64 current_cue_size = cues_.Size();
 | 
						|
  uint64 cue_size = current_cue_size;
 | 
						|
  for (int32 i = 0; i < cues_.cue_entries_size(); i++)
 | 
						|
    MoveCuesBeforeClustersHelper(current_cue_size, i, &cue_size);
 | 
						|
 | 
						|
  // Adjust the Seek Entry to reflect the change in position
 | 
						|
  // of Cluster and Cues
 | 
						|
  int32 cluster_index = 0;
 | 
						|
  int32 cues_index = 0;
 | 
						|
  for (int32 i = 0; i < SeekHead::kSeekEntryCount; ++i) {
 | 
						|
    if (seek_head_.GetId(i) == kMkvCluster)
 | 
						|
      cluster_index = i;
 | 
						|
    if (seek_head_.GetId(i) == kMkvCues)
 | 
						|
      cues_index = i;
 | 
						|
  }
 | 
						|
  seek_head_.SetSeekEntry(cues_index, kMkvCues,
 | 
						|
                          seek_head_.GetPosition(cluster_index));
 | 
						|
  seek_head_.SetSeekEntry(cluster_index, kMkvCluster,
 | 
						|
                          cues_.Size() + seek_head_.GetPosition(cues_index));
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::Init(IMkvWriter* ptr_writer) {
 | 
						|
  if (!ptr_writer) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  writer_cluster_ = ptr_writer;
 | 
						|
  writer_cues_ = ptr_writer;
 | 
						|
  writer_header_ = ptr_writer;
 | 
						|
  return segment_info_.Init();
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
 | 
						|
                                            IMkvWriter* writer) {
 | 
						|
  if (!writer->Seekable() || chunking_)
 | 
						|
    return false;
 | 
						|
  const int64 cluster_offset =
 | 
						|
      cluster_list_[0]->size_position() - GetUIntSize(kMkvCluster);
 | 
						|
 | 
						|
  // Copy the headers.
 | 
						|
  if (!ChunkedCopy(reader, writer, 0, cluster_offset))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Recompute cue positions and seek entries.
 | 
						|
  MoveCuesBeforeClusters();
 | 
						|
 | 
						|
  // Write cues and seek entries.
 | 
						|
  // TODO(vigneshv): As of now, it's safe to call seek_head_.Finalize() for the
 | 
						|
  // second time with a different writer object. But the name Finalize() doesn't
 | 
						|
  // indicate something we want to call more than once. So consider renaming it
 | 
						|
  // to write() or some such.
 | 
						|
  if (!cues_.Write(writer) || !seek_head_.Finalize(writer))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Copy the Clusters.
 | 
						|
  if (!ChunkedCopy(reader, writer, cluster_offset,
 | 
						|
                   cluster_end_offset_ - cluster_offset))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Update the Segment size in case the Cues size has changed.
 | 
						|
  const int64 pos = writer->Position();
 | 
						|
  const int64 segment_size = writer->Position() - payload_pos_;
 | 
						|
  if (writer->Position(size_position_) ||
 | 
						|
      WriteUIntSize(writer, segment_size, 8) || writer->Position(pos))
 | 
						|
    return false;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::Finalize() {
 | 
						|
  if (WriteFramesAll() < 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (mode_ == kFile) {
 | 
						|
    if (cluster_list_size_ > 0) {
 | 
						|
      // Update last cluster's size
 | 
						|
      Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
 | 
						|
      if (!old_cluster || !old_cluster->Finalize())
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (chunking_ && chunk_writer_cluster_) {
 | 
						|
      chunk_writer_cluster_->Close();
 | 
						|
      chunk_count_++;
 | 
						|
    }
 | 
						|
 | 
						|
    const double duration =
 | 
						|
        (static_cast<double>(last_timestamp_) + last_block_duration_) /
 | 
						|
        segment_info_.timecode_scale();
 | 
						|
    segment_info_.set_duration(duration);
 | 
						|
    if (!segment_info_.Finalize(writer_header_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (output_cues_)
 | 
						|
      if (!seek_head_.AddSeekEntry(kMkvCues, MaxOffset()))
 | 
						|
        return false;
 | 
						|
 | 
						|
    if (chunking_) {
 | 
						|
      if (!chunk_writer_cues_)
 | 
						|
        return false;
 | 
						|
 | 
						|
      char* name = NULL;
 | 
						|
      if (!UpdateChunkName("cues", &name))
 | 
						|
        return false;
 | 
						|
 | 
						|
      const bool cues_open = chunk_writer_cues_->Open(name);
 | 
						|
      delete[] name;
 | 
						|
      if (!cues_open)
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    cluster_end_offset_ = writer_cluster_->Position();
 | 
						|
 | 
						|
    // Write the seek headers and cues
 | 
						|
    if (output_cues_)
 | 
						|
      if (!cues_.Write(writer_cues_))
 | 
						|
        return false;
 | 
						|
 | 
						|
    if (!seek_head_.Finalize(writer_header_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (writer_header_->Seekable()) {
 | 
						|
      if (size_position_ == -1)
 | 
						|
        return false;
 | 
						|
 | 
						|
      const int64 pos = writer_header_->Position();
 | 
						|
      const int64 segment_size = MaxOffset();
 | 
						|
 | 
						|
      if (segment_size < 1)
 | 
						|
        return false;
 | 
						|
 | 
						|
      if (writer_header_->Position(size_position_))
 | 
						|
        return false;
 | 
						|
 | 
						|
      if (WriteUIntSize(writer_header_, segment_size, 8))
 | 
						|
        return false;
 | 
						|
 | 
						|
      if (writer_header_->Position(pos))
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (chunking_) {
 | 
						|
      // Do not close any writers until the segment size has been written,
 | 
						|
      // otherwise the size may be off.
 | 
						|
      if (!chunk_writer_cues_ || !chunk_writer_header_)
 | 
						|
        return false;
 | 
						|
 | 
						|
      chunk_writer_cues_->Close();
 | 
						|
      chunk_writer_header_->Close();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
Track* Segment::AddTrack(int32 number) {
 | 
						|
  Track* const track = new (std::nothrow) Track(&seed_);  // NOLINT
 | 
						|
 | 
						|
  if (!track)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  if (!tracks_.AddTrack(track, number)) {
 | 
						|
    delete track;
 | 
						|
    return NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  return track;
 | 
						|
}
 | 
						|
 | 
						|
Chapter* Segment::AddChapter() { return chapters_.AddChapter(&seed_); }
 | 
						|
 | 
						|
uint64 Segment::AddVideoTrack(int32 width, int32 height, int32 number) {
 | 
						|
  VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_);  // NOLINT
 | 
						|
  if (!track)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  track->set_type(Tracks::kVideo);
 | 
						|
  track->set_codec_id(Tracks::kVp8CodecId);
 | 
						|
  track->set_width(width);
 | 
						|
  track->set_height(height);
 | 
						|
 | 
						|
  tracks_.AddTrack(track, number);
 | 
						|
  has_video_ = true;
 | 
						|
 | 
						|
  return track->number();
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddCuePoint(uint64 timestamp, uint64 track) {
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
  if (!cluster)
 | 
						|
    return false;
 | 
						|
 | 
						|
  CuePoint* const cue = new (std::nothrow) CuePoint();  // NOLINT
 | 
						|
  if (!cue)
 | 
						|
    return false;
 | 
						|
 | 
						|
  cue->set_time(timestamp / segment_info_.timecode_scale());
 | 
						|
  cue->set_block_number(cluster->blocks_added());
 | 
						|
  cue->set_cluster_pos(cluster->position_for_cues());
 | 
						|
  cue->set_track(track);
 | 
						|
  if (!cues_.AddCue(cue))
 | 
						|
    return false;
 | 
						|
 | 
						|
  new_cuepoint_ = false;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
uint64 Segment::AddAudioTrack(int32 sample_rate, int32 channels, int32 number) {
 | 
						|
  AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_);  // NOLINT
 | 
						|
  if (!track)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  track->set_type(Tracks::kAudio);
 | 
						|
  track->set_codec_id(Tracks::kVorbisCodecId);
 | 
						|
  track->set_sample_rate(sample_rate);
 | 
						|
  track->set_channels(channels);
 | 
						|
 | 
						|
  tracks_.AddTrack(track, number);
 | 
						|
 | 
						|
  return track->number();
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddFrame(const uint8* frame, uint64 length, uint64 track_number,
 | 
						|
                       uint64 timestamp, bool is_key) {
 | 
						|
  if (!frame)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!CheckHeaderInfo())
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Check for non-monotonically increasing timestamps.
 | 
						|
  if (timestamp < last_timestamp_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  // If the segment has a video track hold onto audio frames to make sure the
 | 
						|
  // audio that is associated with the start time of a video key-frame is
 | 
						|
  // muxed into the same cluster.
 | 
						|
  if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) {
 | 
						|
    Frame* const new_frame = new (std::nothrow) Frame();
 | 
						|
    if (new_frame == NULL || !new_frame->Init(frame, length))
 | 
						|
      return false;
 | 
						|
    new_frame->set_track_number(track_number);
 | 
						|
    new_frame->set_timestamp(timestamp);
 | 
						|
    new_frame->set_is_key(is_key);
 | 
						|
 | 
						|
    if (!QueueFrame(new_frame))
 | 
						|
      return false;
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!DoNewClusterProcessing(track_number, timestamp, is_key))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
  if (!cluster)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 abs_timecode = timestamp / timecode_scale;
 | 
						|
 | 
						|
  if (!cluster->AddFrame(frame, length, track_number, abs_timecode, is_key))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (new_cuepoint_ && cues_track_ == track_number) {
 | 
						|
    if (!AddCuePoint(timestamp, cues_track_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (timestamp > last_timestamp_)
 | 
						|
    last_timestamp_ = timestamp;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddFrameWithAdditional(const uint8* frame, uint64 length,
 | 
						|
                                     const uint8* additional,
 | 
						|
                                     uint64 additional_length, uint64 add_id,
 | 
						|
                                     uint64 track_number, uint64 timestamp,
 | 
						|
                                     bool is_key) {
 | 
						|
  if (frame == NULL || additional == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!CheckHeaderInfo())
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Check for non-monotonically increasing timestamps.
 | 
						|
  if (timestamp < last_timestamp_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  // If the segment has a video track hold onto audio frames to make sure the
 | 
						|
  // audio that is associated with the start time of a video key-frame is
 | 
						|
  // muxed into the same cluster.
 | 
						|
  if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) {
 | 
						|
    Frame* const new_frame = new (std::nothrow) Frame();
 | 
						|
    if (new_frame == NULL || !new_frame->Init(frame, length))
 | 
						|
      return false;
 | 
						|
    new_frame->set_track_number(track_number);
 | 
						|
    new_frame->set_timestamp(timestamp);
 | 
						|
    new_frame->set_is_key(is_key);
 | 
						|
 | 
						|
    if (!QueueFrame(new_frame))
 | 
						|
      return false;
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!DoNewClusterProcessing(track_number, timestamp, is_key))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
  if (cluster == NULL)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 abs_timecode = timestamp / timecode_scale;
 | 
						|
 | 
						|
  if (!cluster->AddFrameWithAdditional(frame, length, additional,
 | 
						|
                                       additional_length, add_id, track_number,
 | 
						|
                                       abs_timecode, is_key))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (new_cuepoint_ && cues_track_ == track_number) {
 | 
						|
    if (!AddCuePoint(timestamp, cues_track_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (timestamp > last_timestamp_)
 | 
						|
    last_timestamp_ = timestamp;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddFrameWithDiscardPadding(const uint8* frame, uint64 length,
 | 
						|
                                         int64 discard_padding,
 | 
						|
                                         uint64 track_number, uint64 timestamp,
 | 
						|
                                         bool is_key) {
 | 
						|
  if (frame == NULL || discard_padding <= 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!CheckHeaderInfo())
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Check for non-monotonically increasing timestamps.
 | 
						|
  if (timestamp < last_timestamp_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  // If the segment has a video track hold onto audio frames to make sure the
 | 
						|
  // audio that is associated with the start time of a video key-frame is
 | 
						|
  // muxed into the same cluster.
 | 
						|
  if (has_video_ && tracks_.TrackIsAudio(track_number) && !force_new_cluster_) {
 | 
						|
    Frame* const new_frame = new (std::nothrow) Frame();
 | 
						|
    if (new_frame == NULL || !new_frame->Init(frame, length))
 | 
						|
      return false;
 | 
						|
    new_frame->set_track_number(track_number);
 | 
						|
    new_frame->set_timestamp(timestamp);
 | 
						|
    new_frame->set_is_key(is_key);
 | 
						|
    new_frame->set_discard_padding(discard_padding);
 | 
						|
 | 
						|
    if (!QueueFrame(new_frame))
 | 
						|
      return false;
 | 
						|
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!DoNewClusterProcessing(track_number, timestamp, is_key))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
  if (!cluster)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 abs_timecode = timestamp / timecode_scale;
 | 
						|
 | 
						|
  if (!cluster->AddFrameWithDiscardPadding(
 | 
						|
          frame, length, discard_padding, track_number, abs_timecode, is_key)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (new_cuepoint_ && cues_track_ == track_number) {
 | 
						|
    if (!AddCuePoint(timestamp, cues_track_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (timestamp > last_timestamp_)
 | 
						|
    last_timestamp_ = timestamp;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddMetadata(const uint8* frame, uint64 length,
 | 
						|
                          uint64 track_number, uint64 timestamp_ns,
 | 
						|
                          uint64 duration_ns) {
 | 
						|
  if (!frame)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!CheckHeaderInfo())
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Check for non-monotonically increasing timestamps.
 | 
						|
  if (timestamp_ns < last_timestamp_)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!DoNewClusterProcessing(track_number, timestamp_ns, true))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return false;
 | 
						|
 | 
						|
  Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
 | 
						|
  if (!cluster)
 | 
						|
    return false;
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 abs_timecode = timestamp_ns / timecode_scale;
 | 
						|
  const uint64 duration_timecode = duration_ns / timecode_scale;
 | 
						|
 | 
						|
  if (!cluster->AddMetadata(frame, length, track_number, abs_timecode,
 | 
						|
                            duration_timecode))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (timestamp_ns > last_timestamp_)
 | 
						|
    last_timestamp_ = timestamp_ns;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::AddGenericFrame(const Frame* frame) {
 | 
						|
  last_block_duration_ = frame->duration();
 | 
						|
  if (!tracks_.TrackIsAudio(frame->track_number()) &&
 | 
						|
      !tracks_.TrackIsVideo(frame->track_number()) && frame->duration() > 0) {
 | 
						|
    return AddMetadata(frame->frame(), frame->length(), frame->track_number(),
 | 
						|
                       frame->timestamp(), frame->duration());
 | 
						|
  } else if (frame->additional() && frame->additional_length() > 0) {
 | 
						|
    return AddFrameWithAdditional(
 | 
						|
        frame->frame(), frame->length(), frame->additional(),
 | 
						|
        frame->additional_length(), frame->add_id(), frame->track_number(),
 | 
						|
        frame->timestamp(), frame->is_key());
 | 
						|
  } else if (frame->discard_padding() > 0) {
 | 
						|
    return AddFrameWithDiscardPadding(
 | 
						|
        frame->frame(), frame->length(), frame->discard_padding(),
 | 
						|
        frame->track_number(), frame->timestamp(), frame->is_key());
 | 
						|
  } else {
 | 
						|
    return AddFrame(frame->frame(), frame->length(), frame->track_number(),
 | 
						|
                    frame->timestamp(), frame->is_key());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; }
 | 
						|
 | 
						|
bool Segment::SetChunking(bool chunking, const char* filename) {
 | 
						|
  if (chunk_count_ > 0)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (chunking) {
 | 
						|
    if (!filename)
 | 
						|
      return false;
 | 
						|
 | 
						|
    // Check if we are being set to what is already set.
 | 
						|
    if (chunking_ && !strcmp(filename, chunking_base_name_))
 | 
						|
      return true;
 | 
						|
 | 
						|
    const size_t name_length = strlen(filename) + 1;
 | 
						|
    char* const temp = new (std::nothrow) char[name_length];  // NOLINT
 | 
						|
    if (!temp)
 | 
						|
      return false;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
    strcpy_s(temp, name_length, filename);
 | 
						|
#else
 | 
						|
    strcpy(temp, filename);
 | 
						|
#endif
 | 
						|
 | 
						|
    delete[] chunking_base_name_;
 | 
						|
    chunking_base_name_ = temp;
 | 
						|
 | 
						|
    if (!UpdateChunkName("chk", &chunk_name_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (!chunk_writer_cluster_) {
 | 
						|
      chunk_writer_cluster_ = new (std::nothrow) MkvWriter();  // NOLINT
 | 
						|
      if (!chunk_writer_cluster_)
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!chunk_writer_cues_) {
 | 
						|
      chunk_writer_cues_ = new (std::nothrow) MkvWriter();  // NOLINT
 | 
						|
      if (!chunk_writer_cues_)
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!chunk_writer_header_) {
 | 
						|
      chunk_writer_header_ = new (std::nothrow) MkvWriter();  // NOLINT
 | 
						|
      if (!chunk_writer_header_)
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!chunk_writer_cluster_->Open(chunk_name_))
 | 
						|
      return false;
 | 
						|
 | 
						|
    const size_t header_length = strlen(filename) + strlen(".hdr") + 1;
 | 
						|
    char* const header = new (std::nothrow) char[header_length];  // NOLINT
 | 
						|
    if (!header)
 | 
						|
      return false;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
    strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_);
 | 
						|
    strcat_s(header, header_length, ".hdr");
 | 
						|
#else
 | 
						|
    strcpy(header, chunking_base_name_);
 | 
						|
    strcat(header, ".hdr");
 | 
						|
#endif
 | 
						|
    if (!chunk_writer_header_->Open(header)) {
 | 
						|
      delete[] header;
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    writer_cluster_ = chunk_writer_cluster_;
 | 
						|
    writer_cues_ = chunk_writer_cues_;
 | 
						|
    writer_header_ = chunk_writer_header_;
 | 
						|
 | 
						|
    delete[] header;
 | 
						|
  }
 | 
						|
 | 
						|
  chunking_ = chunking;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::CuesTrack(uint64 track_number) {
 | 
						|
  const Track* const track = GetTrackByNumber(track_number);
 | 
						|
  if (!track)
 | 
						|
    return false;
 | 
						|
 | 
						|
  cues_track_ = track_number;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void Segment::ForceNewClusterOnNextFrame() { force_new_cluster_ = true; }
 | 
						|
 | 
						|
Track* Segment::GetTrackByNumber(uint64 track_number) const {
 | 
						|
  return tracks_.GetTrackByNumber(track_number);
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::WriteSegmentHeader() {
 | 
						|
  // TODO(fgalligan): Support more than one segment.
 | 
						|
  if (!WriteEbmlHeader(writer_header_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Write "unknown" (-1) as segment size value. If mode is kFile, Segment
 | 
						|
  // will write over duration when the file is finalized.
 | 
						|
  if (WriteID(writer_header_, kMkvSegment))
 | 
						|
    return false;
 | 
						|
 | 
						|
  // Save for later.
 | 
						|
  size_position_ = writer_header_->Position();
 | 
						|
 | 
						|
  // Write "unknown" (EBML coded -1) as segment size value. We need to write 8
 | 
						|
  // bytes because if we are going to overwrite the segment size later we do
 | 
						|
  // not know how big our segment will be.
 | 
						|
  if (SerializeInt(writer_header_, kEbmlUnknownValue, 8))
 | 
						|
    return false;
 | 
						|
 | 
						|
  payload_pos_ = writer_header_->Position();
 | 
						|
 | 
						|
  if (mode_ == kFile && writer_header_->Seekable()) {
 | 
						|
    // Set the duration > 0.0 so SegmentInfo will write out the duration. When
 | 
						|
    // the muxer is done writing we will set the correct duration and have
 | 
						|
    // SegmentInfo upadte it.
 | 
						|
    segment_info_.set_duration(1.0);
 | 
						|
 | 
						|
    if (!seek_head_.Write(writer_header_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!seek_head_.AddSeekEntry(kMkvInfo, MaxOffset()))
 | 
						|
    return false;
 | 
						|
  if (!segment_info_.Write(writer_header_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!seek_head_.AddSeekEntry(kMkvTracks, MaxOffset()))
 | 
						|
    return false;
 | 
						|
  if (!tracks_.Write(writer_header_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (chapters_.Count() > 0) {
 | 
						|
    if (!seek_head_.AddSeekEntry(kMkvChapters, MaxOffset()))
 | 
						|
      return false;
 | 
						|
    if (!chapters_.Write(writer_header_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) {
 | 
						|
    if (!chunk_writer_header_)
 | 
						|
      return false;
 | 
						|
 | 
						|
    chunk_writer_header_->Close();
 | 
						|
  }
 | 
						|
 | 
						|
  header_written_ = true;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
// Here we are testing whether to create a new cluster, given a frame
 | 
						|
// having time frame_timestamp_ns.
 | 
						|
//
 | 
						|
int Segment::TestFrame(uint64 track_number, uint64 frame_timestamp_ns,
 | 
						|
                       bool is_key) const {
 | 
						|
  if (force_new_cluster_)
 | 
						|
    return 1;
 | 
						|
 | 
						|
  // If no clusters have been created yet, then create a new cluster
 | 
						|
  // and write this frame immediately, in the new cluster.  This path
 | 
						|
  // should only be followed once, the first time we attempt to write
 | 
						|
  // a frame.
 | 
						|
 | 
						|
  if (cluster_list_size_ <= 0)
 | 
						|
    return 1;
 | 
						|
 | 
						|
  // There exists at least one cluster. We must compare the frame to
 | 
						|
  // the last cluster, in order to determine whether the frame is
 | 
						|
  // written to the existing cluster, or that a new cluster should be
 | 
						|
  // created.
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 frame_timecode = frame_timestamp_ns / timecode_scale;
 | 
						|
 | 
						|
  const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
  const uint64 last_cluster_timecode = last_cluster->timecode();
 | 
						|
 | 
						|
  // For completeness we test for the case when the frame's timecode
 | 
						|
  // is less than the cluster's timecode.  Although in principle that
 | 
						|
  // is allowed, this muxer doesn't actually write clusters like that,
 | 
						|
  // so this indicates a bug somewhere in our algorithm.
 | 
						|
 | 
						|
  if (frame_timecode < last_cluster_timecode)  // should never happen
 | 
						|
    return -1;
 | 
						|
 | 
						|
  // If the frame has a timestamp significantly larger than the last
 | 
						|
  // cluster (in Matroska, cluster-relative timestamps are serialized
 | 
						|
  // using a 16-bit signed integer), then we cannot write this frame
 | 
						|
  // to that cluster, and so we must create a new cluster.
 | 
						|
 | 
						|
  const int64 delta_timecode = frame_timecode - last_cluster_timecode;
 | 
						|
 | 
						|
  if (delta_timecode > kMaxBlockTimecode)
 | 
						|
    return 2;
 | 
						|
 | 
						|
  // We decide to create a new cluster when we have a video keyframe.
 | 
						|
  // This will flush queued (audio) frames, and write the keyframe
 | 
						|
  // immediately, in the newly-created cluster.
 | 
						|
 | 
						|
  if (is_key && tracks_.TrackIsVideo(track_number))
 | 
						|
    return 1;
 | 
						|
 | 
						|
  // Create a new cluster if we have accumulated too many frames
 | 
						|
  // already, where "too many" is defined as "the total time of frames
 | 
						|
  // in the cluster exceeds a threshold".
 | 
						|
 | 
						|
  const uint64 delta_ns = delta_timecode * timecode_scale;
 | 
						|
 | 
						|
  if (max_cluster_duration_ > 0 && delta_ns >= max_cluster_duration_)
 | 
						|
    return 1;
 | 
						|
 | 
						|
  // This is similar to the case above, with the difference that a new
 | 
						|
  // cluster is created when the size of the current cluster exceeds a
 | 
						|
  // threshold.
 | 
						|
 | 
						|
  const uint64 cluster_size = last_cluster->payload_size();
 | 
						|
 | 
						|
  if (max_cluster_size_ > 0 && cluster_size >= max_cluster_size_)
 | 
						|
    return 1;
 | 
						|
 | 
						|
  // There's no need to create a new cluster, so emit this frame now.
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::MakeNewCluster(uint64 frame_timestamp_ns) {
 | 
						|
  const int32 new_size = cluster_list_size_ + 1;
 | 
						|
 | 
						|
  if (new_size > cluster_list_capacity_) {
 | 
						|
    // Add more clusters.
 | 
						|
    const int32 new_capacity =
 | 
						|
        (cluster_list_capacity_ <= 0) ? 1 : cluster_list_capacity_ * 2;
 | 
						|
    Cluster** const clusters =
 | 
						|
        new (std::nothrow) Cluster* [new_capacity];  // NOLINT
 | 
						|
    if (!clusters)
 | 
						|
      return false;
 | 
						|
 | 
						|
    for (int32 i = 0; i < cluster_list_size_; ++i) {
 | 
						|
      clusters[i] = cluster_list_[i];
 | 
						|
    }
 | 
						|
 | 
						|
    delete[] cluster_list_;
 | 
						|
 | 
						|
    cluster_list_ = clusters;
 | 
						|
    cluster_list_capacity_ = new_capacity;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!WriteFramesLessThan(frame_timestamp_ns))
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (mode_ == kFile) {
 | 
						|
    if (cluster_list_size_ > 0) {
 | 
						|
      // Update old cluster's size
 | 
						|
      Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
 | 
						|
      if (!old_cluster || !old_cluster->Finalize())
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (output_cues_)
 | 
						|
      new_cuepoint_ = true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (chunking_ && cluster_list_size_ > 0) {
 | 
						|
    chunk_writer_cluster_->Close();
 | 
						|
    chunk_count_++;
 | 
						|
 | 
						|
    if (!UpdateChunkName("chk", &chunk_name_))
 | 
						|
      return false;
 | 
						|
    if (!chunk_writer_cluster_->Open(chunk_name_))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
  const uint64 frame_timecode = frame_timestamp_ns / timecode_scale;
 | 
						|
 | 
						|
  uint64 cluster_timecode = frame_timecode;
 | 
						|
 | 
						|
  if (frames_size_ > 0) {
 | 
						|
    const Frame* const f = frames_[0];  // earliest queued frame
 | 
						|
    const uint64 ns = f->timestamp();
 | 
						|
    const uint64 tc = ns / timecode_scale;
 | 
						|
 | 
						|
    if (tc < cluster_timecode)
 | 
						|
      cluster_timecode = tc;
 | 
						|
  }
 | 
						|
 | 
						|
  Cluster*& cluster = cluster_list_[cluster_list_size_];
 | 
						|
  const int64 offset = MaxOffset();
 | 
						|
  cluster = new (std::nothrow) Cluster(cluster_timecode, offset);  // NOLINT
 | 
						|
  if (!cluster)
 | 
						|
    return false;
 | 
						|
 | 
						|
  if (!cluster->Init(writer_cluster_))
 | 
						|
    return false;
 | 
						|
 | 
						|
  cluster_list_size_ = new_size;
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::DoNewClusterProcessing(uint64 track_number,
 | 
						|
                                     uint64 frame_timestamp_ns, bool is_key) {
 | 
						|
  for (;;) {
 | 
						|
    // Based on the characteristics of the current frame and current
 | 
						|
    // cluster, decide whether to create a new cluster.
 | 
						|
    const int result = TestFrame(track_number, frame_timestamp_ns, is_key);
 | 
						|
    if (result < 0)  // error
 | 
						|
      return false;
 | 
						|
 | 
						|
    // Always set force_new_cluster_ to false after TestFrame.
 | 
						|
    force_new_cluster_ = false;
 | 
						|
 | 
						|
    // A non-zero result means create a new cluster.
 | 
						|
    if (result > 0 && !MakeNewCluster(frame_timestamp_ns))
 | 
						|
      return false;
 | 
						|
 | 
						|
    // Write queued (audio) frames.
 | 
						|
    const int frame_count = WriteFramesAll();
 | 
						|
    if (frame_count < 0)  // error
 | 
						|
      return false;
 | 
						|
 | 
						|
    // Write the current frame to the current cluster (if TestFrame
 | 
						|
    // returns 0) or to a newly created cluster (TestFrame returns 1).
 | 
						|
    if (result <= 1)
 | 
						|
      return true;
 | 
						|
 | 
						|
    // TestFrame returned 2, which means there was a large time
 | 
						|
    // difference between the cluster and the frame itself.  Do the
 | 
						|
    // test again, comparing the frame to the new cluster.
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::CheckHeaderInfo() {
 | 
						|
  if (!header_written_) {
 | 
						|
    if (!WriteSegmentHeader())
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (!seek_head_.AddSeekEntry(kMkvCluster, MaxOffset()))
 | 
						|
      return false;
 | 
						|
 | 
						|
    if (output_cues_ && cues_track_ == 0) {
 | 
						|
      // Check for a video track
 | 
						|
      for (uint32 i = 0; i < tracks_.track_entries_size(); ++i) {
 | 
						|
        const Track* const track = tracks_.GetTrackByIndex(i);
 | 
						|
        if (!track)
 | 
						|
          return false;
 | 
						|
 | 
						|
        if (tracks_.TrackIsVideo(track->number())) {
 | 
						|
          cues_track_ = track->number();
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Set first track found
 | 
						|
      if (cues_track_ == 0) {
 | 
						|
        const Track* const track = tracks_.GetTrackByIndex(0);
 | 
						|
        if (!track)
 | 
						|
          return false;
 | 
						|
 | 
						|
        cues_track_ = track->number();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::UpdateChunkName(const char* ext, char** name) const {
 | 
						|
  if (!name || !ext)
 | 
						|
    return false;
 | 
						|
 | 
						|
  char ext_chk[64];
 | 
						|
#ifdef _MSC_VER
 | 
						|
  sprintf_s(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
 | 
						|
#else
 | 
						|
  snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
 | 
						|
#endif
 | 
						|
 | 
						|
  const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1;
 | 
						|
  char* const str = new (std::nothrow) char[length];  // NOLINT
 | 
						|
  if (!str)
 | 
						|
    return false;
 | 
						|
 | 
						|
#ifdef _MSC_VER
 | 
						|
  strcpy_s(str, length - strlen(ext_chk), chunking_base_name_);
 | 
						|
  strcat_s(str, length, ext_chk);
 | 
						|
#else
 | 
						|
  strcpy(str, chunking_base_name_);
 | 
						|
  strcat(str, ext_chk);
 | 
						|
#endif
 | 
						|
 | 
						|
  delete[] * name;
 | 
						|
  *name = str;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
int64 Segment::MaxOffset() {
 | 
						|
  if (!writer_header_)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  int64 offset = writer_header_->Position() - payload_pos_;
 | 
						|
 | 
						|
  if (chunking_) {
 | 
						|
    for (int32 i = 0; i < cluster_list_size_; ++i) {
 | 
						|
      Cluster* const cluster = cluster_list_[i];
 | 
						|
      offset += cluster->Size();
 | 
						|
    }
 | 
						|
 | 
						|
    if (writer_cues_)
 | 
						|
      offset += writer_cues_->Position();
 | 
						|
  }
 | 
						|
 | 
						|
  return offset;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::QueueFrame(Frame* frame) {
 | 
						|
  const int32 new_size = frames_size_ + 1;
 | 
						|
 | 
						|
  if (new_size > frames_capacity_) {
 | 
						|
    // Add more frames.
 | 
						|
    const int32 new_capacity = (!frames_capacity_) ? 2 : frames_capacity_ * 2;
 | 
						|
 | 
						|
    if (new_capacity < 1)
 | 
						|
      return false;
 | 
						|
 | 
						|
    Frame** const frames = new (std::nothrow) Frame* [new_capacity];  // NOLINT
 | 
						|
    if (!frames)
 | 
						|
      return false;
 | 
						|
 | 
						|
    for (int32 i = 0; i < frames_size_; ++i) {
 | 
						|
      frames[i] = frames_[i];
 | 
						|
    }
 | 
						|
 | 
						|
    delete[] frames_;
 | 
						|
    frames_ = frames;
 | 
						|
    frames_capacity_ = new_capacity;
 | 
						|
  }
 | 
						|
 | 
						|
  frames_[frames_size_++] = frame;
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
int Segment::WriteFramesAll() {
 | 
						|
  if (frames_ == NULL)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  if (cluster_list_size_ < 1)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
 | 
						|
  if (!cluster)
 | 
						|
    return -1;
 | 
						|
 | 
						|
  const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
 | 
						|
  for (int32 i = 0; i < frames_size_; ++i) {
 | 
						|
    Frame*& frame = frames_[i];
 | 
						|
    const uint64 frame_timestamp = frame->timestamp();  // ns
 | 
						|
    const uint64 frame_timecode = frame_timestamp / timecode_scale;
 | 
						|
 | 
						|
    if (frame->discard_padding() > 0) {
 | 
						|
      if (!cluster->AddFrameWithDiscardPadding(
 | 
						|
              frame->frame(), frame->length(), frame->discard_padding(),
 | 
						|
              frame->track_number(), frame_timecode, frame->is_key())) {
 | 
						|
        return -1;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      if (!cluster->AddFrame(frame->frame(), frame->length(),
 | 
						|
                             frame->track_number(), frame_timecode,
 | 
						|
                             frame->is_key())) {
 | 
						|
        return -1;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (new_cuepoint_ && cues_track_ == frame->track_number()) {
 | 
						|
      if (!AddCuePoint(frame_timestamp, cues_track_))
 | 
						|
        return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (frame_timestamp > last_timestamp_)
 | 
						|
      last_timestamp_ = frame_timestamp;
 | 
						|
 | 
						|
    delete frame;
 | 
						|
    frame = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  const int result = frames_size_;
 | 
						|
  frames_size_ = 0;
 | 
						|
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
bool Segment::WriteFramesLessThan(uint64 timestamp) {
 | 
						|
  // Check |cluster_list_size_| to see if this is the first cluster. If it is
 | 
						|
  // the first cluster the audio frames that are less than the first video
 | 
						|
  // timesatmp will be written in a later step.
 | 
						|
  if (frames_size_ > 0 && cluster_list_size_ > 0) {
 | 
						|
    if (!frames_)
 | 
						|
      return false;
 | 
						|
 | 
						|
    Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
 | 
						|
    if (!cluster)
 | 
						|
      return false;
 | 
						|
 | 
						|
    const uint64 timecode_scale = segment_info_.timecode_scale();
 | 
						|
    int32 shift_left = 0;
 | 
						|
 | 
						|
    // TODO(fgalligan): Change this to use the durations of frames instead of
 | 
						|
    // the next frame's start time if the duration is accurate.
 | 
						|
    for (int32 i = 1; i < frames_size_; ++i) {
 | 
						|
      const Frame* const frame_curr = frames_[i];
 | 
						|
 | 
						|
      if (frame_curr->timestamp() > timestamp)
 | 
						|
        break;
 | 
						|
 | 
						|
      const Frame* const frame_prev = frames_[i - 1];
 | 
						|
      const uint64 frame_timestamp = frame_prev->timestamp();
 | 
						|
      const uint64 frame_timecode = frame_timestamp / timecode_scale;
 | 
						|
      const int64 discard_padding = frame_prev->discard_padding();
 | 
						|
 | 
						|
      if (discard_padding > 0) {
 | 
						|
        if (!cluster->AddFrameWithDiscardPadding(
 | 
						|
                frame_prev->frame(), frame_prev->length(), discard_padding,
 | 
						|
                frame_prev->track_number(), frame_timecode,
 | 
						|
                frame_prev->is_key())) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        if (!cluster->AddFrame(frame_prev->frame(), frame_prev->length(),
 | 
						|
                               frame_prev->track_number(), frame_timecode,
 | 
						|
                               frame_prev->is_key())) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) {
 | 
						|
        if (!AddCuePoint(frame_timestamp, cues_track_))
 | 
						|
          return false;
 | 
						|
      }
 | 
						|
 | 
						|
      ++shift_left;
 | 
						|
      if (frame_timestamp > last_timestamp_)
 | 
						|
        last_timestamp_ = frame_timestamp;
 | 
						|
 | 
						|
      delete frame_prev;
 | 
						|
    }
 | 
						|
 | 
						|
    if (shift_left > 0) {
 | 
						|
      if (shift_left >= frames_size_)
 | 
						|
        return false;
 | 
						|
 | 
						|
      const int32 new_frames_size = frames_size_ - shift_left;
 | 
						|
      for (int32 i = 0; i < new_frames_size; ++i) {
 | 
						|
        frames_[i] = frames_[i + shift_left];
 | 
						|
      }
 | 
						|
 | 
						|
      frames_size_ = new_frames_size;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mkvmuxer
 |