Adding libwebm sources to third_party/libwebm.
authorVignesh Venkatasubramanian <vigneshv@google.com>
Mon, 17 Mar 2014 20:30:55 +0000 (13:30 -0700)
committerVignesh Venkatasubramanian <vigneshv@google.com>
Tue, 18 Mar 2014 16:35:37 +0000 (09:35 -0700)
Hash and license details are found in README.webm

Change-Id: I52192abe8eba8ec413f6bccd479e8e2256a8f617

17 files changed:
third_party/libwebm/AUTHORS.TXT [new file with mode: 0644]
third_party/libwebm/LICENSE.TXT [new file with mode: 0644]
third_party/libwebm/PATENTS.TXT [new file with mode: 0644]
third_party/libwebm/README.webm [new file with mode: 0644]
third_party/libwebm/RELEASE.TXT [new file with mode: 0644]
third_party/libwebm/mkvmuxer.cpp [new file with mode: 0644]
third_party/libwebm/mkvmuxer.hpp [new file with mode: 0644]
third_party/libwebm/mkvmuxertypes.hpp [new file with mode: 0644]
third_party/libwebm/mkvmuxerutil.cpp [new file with mode: 0644]
third_party/libwebm/mkvmuxerutil.hpp [new file with mode: 0644]
third_party/libwebm/mkvparser.cpp [new file with mode: 0644]
third_party/libwebm/mkvparser.hpp [new file with mode: 0644]
third_party/libwebm/mkvreader.cpp [new file with mode: 0644]
third_party/libwebm/mkvreader.hpp [new file with mode: 0644]
third_party/libwebm/mkvwriter.cpp [new file with mode: 0644]
third_party/libwebm/mkvwriter.hpp [new file with mode: 0644]
third_party/libwebm/webmids.hpp [new file with mode: 0644]

diff --git a/third_party/libwebm/AUTHORS.TXT b/third_party/libwebm/AUTHORS.TXT
new file mode 100644 (file)
index 0000000..8ab6f79
--- /dev/null
@@ -0,0 +1,4 @@
+# Names should be added to this file like so:\r
+# Name or Organization <email address>\r
+\r
+Google Inc.\r
diff --git a/third_party/libwebm/LICENSE.TXT b/third_party/libwebm/LICENSE.TXT
new file mode 100644 (file)
index 0000000..7a6f995
--- /dev/null
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+
+  * Neither the name of Google nor the names of its contributors may
+    be used to endorse or promote products derived from this software
+    without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/third_party/libwebm/PATENTS.TXT b/third_party/libwebm/PATENTS.TXT
new file mode 100644 (file)
index 0000000..4414d83
--- /dev/null
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the WebM Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer, and otherwise run, modify and propagate the contents of this
+implementation of VP8, where such license applies only to those patent
+claims, both currently owned by Google and acquired in the future,
+licensable by Google that are necessarily infringed by this
+implementation of VP8. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of VP8 or any code incorporated within this
+implementation of VP8 constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of VP8
+shall terminate as of the date such litigation is filed.
diff --git a/third_party/libwebm/README.webm b/third_party/libwebm/README.webm
new file mode 100644 (file)
index 0000000..b13c8cb
--- /dev/null
@@ -0,0 +1,7 @@
+URL: https://chromium.googlesource.com/webm/libwebm
+Version: 630a0e3c338e1b32bddf513a2dad807908d2976a
+License: BSD
+License File: LICENSE.txt
+
+Description:
+libwebm is used to handle WebM container I/O.
diff --git a/third_party/libwebm/RELEASE.TXT b/third_party/libwebm/RELEASE.TXT
new file mode 100644 (file)
index 0000000..a7e9f03
--- /dev/null
@@ -0,0 +1,34 @@
+1.0.0.5\r
+ * Handled case when no duration\r
+ * Handled empty clusters\r
+ * Handled empty clusters when seeking\r
+ * Implemented check lacing bits\r
+\r
+1.0.0.4\r
+ * Made Cues member variables mutables\r
+ * Defined against badly-formatted cue points\r
+ * Segment::GetCluster returns CuePoint too\r
+ * Separated cue-based searches\r
+\r
+1.0.0.3\r
+ * Added Block::GetOffset() to get a frame's offset in a block\r
+ * Changed cluster count type from size_t to long\r
+ * Parsed SeekHead to find cues\r
+ * Allowed seeking beyond end of cluster cache\r
+ * Added not to attempt to reparse cues element\r
+ * Restructured Segment::LoadCluster\r
+ * Marked position of cues without parsing cues element\r
+ * Allowed cue points to be loaded incrementally\r
+ * Implemented to load lazily cue points as they're searched\r
+ * Merged Cues::LoadCuePoint into Cues::Find\r
+ * Lazy init cues\r
+ * Loaded cue point during find\r
+\r
+1.0.0.2\r
+ * added support for Cues element\r
+ * seeking was improved\r
+\r
+1.0.0.1\r
+ * fixed item 141\r
+ * added item 142\r
+ * added this file, RELEASE.TXT, to repository\r
diff --git a/third_party/libwebm/mkvmuxer.cpp b/third_party/libwebm/mkvmuxer.cpp
new file mode 100644 (file)
index 0000000..8ae0dda
--- /dev/null
@@ -0,0 +1,3245 @@
+// 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),
+      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 (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;
+
+  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_);
+
+
+  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 (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),
+      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_));
+  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 (!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;  // error
+
+  // 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
diff --git a/third_party/libwebm/mkvmuxer.hpp b/third_party/libwebm/mkvmuxer.hpp
new file mode 100644 (file)
index 0000000..63a315e
--- /dev/null
@@ -0,0 +1,1403 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVMUXER_HPP
+#define MKVMUXER_HPP
+
+#include "mkvmuxertypes.hpp"
+
+// For a description of the WebM elements see
+// http://www.webmproject.org/code/specs/container/.
+
+namespace mkvparser {
+  class IMkvReader;
+}  // end namespace
+
+namespace mkvmuxer {
+
+class MkvWriter;
+class Segment;
+
+///////////////////////////////////////////////////////////////
+// Interface used by the mkvmuxer to write out the Mkv data.
+class IMkvWriter {
+ public:
+  // Writes out |len| bytes of |buf|. Returns 0 on success.
+  virtual int32 Write(const void* buf, uint32 len) = 0;
+
+  // Returns the offset of the output position from the beginning of the
+  // output.
+  virtual int64 Position() const = 0;
+
+  // Set the current File position. Returns 0 on success.
+  virtual int32 Position(int64 position) = 0;
+
+  // Returns true if the writer is seekable.
+  virtual bool Seekable() const = 0;
+
+  // Element start notification. Called whenever an element identifier is about
+  // to be written to the stream. |element_id| is the element identifier, and
+  // |position| is the location in the WebM stream where the first octet of the
+  // element identifier will be written.
+  // Note: the |MkvId| enumeration in webmids.hpp defines element values.
+  virtual void ElementStartNotify(uint64 element_id, int64 position) = 0;
+
+ protected:
+  IMkvWriter();
+  virtual ~IMkvWriter();
+
+ private:
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(IMkvWriter);
+};
+
+// Writes out the EBML header for a WebM file. This function must be called
+// before any other libwebm writing functions are called.
+bool WriteEbmlHeader(IMkvWriter* writer);
+
+// Copies in Chunk from source to destination between the given byte positions
+bool ChunkedCopy(mkvparser::IMkvReader* source, IMkvWriter* dst,
+                 int64 start, int64 size);
+
+///////////////////////////////////////////////////////////////
+// Class to hold data the will be written to a block.
+class Frame {
+ public:
+  Frame();
+  ~Frame();
+
+  // Copies |frame| data into |frame_|. Returns true on success.
+  bool Init(const uint8* frame, uint64 length);
+
+  // Copies |additional| data into |additional_|. Returns true on success.
+  bool AddAdditionalData(const uint8* additional, uint64 length,
+                         uint64 add_id);
+
+  uint64 add_id() const { return add_id_; }
+  const uint8* additional() const { return additional_; }
+  uint64 additional_length() const { return additional_length_; }
+  void set_duration(uint64 duration) { duration_ = duration; }
+  uint64 duration() const { return duration_; }
+  const uint8* frame() const { return frame_; }
+  void set_is_key(bool key) { is_key_ = key; }
+  bool is_key() const { return is_key_; }
+  uint64 length() const { return length_; }
+  void set_track_number(uint64 track_number) { track_number_ = track_number; }
+  uint64 track_number() const { return track_number_; }
+  void set_timestamp(uint64 timestamp) { timestamp_ = timestamp; }
+  uint64 timestamp() const { return timestamp_; }
+  void set_discard_padding(uint64 discard_padding) {
+    discard_padding_ = discard_padding;
+  }
+  uint64 discard_padding() const { return discard_padding_; }
+
+ private:
+  // Id of the Additional data.
+  uint64 add_id_;
+
+  // Pointer to additional data. Owned by this class.
+  uint8* additional_;
+
+  // Length of the additional data.
+  uint64 additional_length_;
+
+  // Duration of the frame in nanoseconds.
+  uint64 duration_;
+
+  // Pointer to the data. Owned by this class.
+  uint8* frame_;
+
+  // Flag telling if the data should set the key flag of a block.
+  bool is_key_;
+
+  // Length of the data.
+  uint64 length_;
+
+  // Mkv track number the data is associated with.
+  uint64 track_number_;
+
+  // Timestamp of the data in nanoseconds.
+  uint64 timestamp_;
+
+  // Discard padding for the frame.
+  int64 discard_padding_;
+};
+
+///////////////////////////////////////////////////////////////
+// Class to hold one cue point in a Cues element.
+class CuePoint {
+ public:
+  CuePoint();
+  ~CuePoint();
+
+  // Returns the size in bytes for the entire CuePoint element.
+  uint64 Size() const;
+
+  // Output the CuePoint element to the writer. Returns true on success.
+  bool Write(IMkvWriter* writer) const;
+
+  void set_time(uint64 time) { time_ = time; }
+  uint64 time() const { return time_; }
+  void set_track(uint64 track) { track_ = track; }
+  uint64 track() const { return track_; }
+  void set_cluster_pos(uint64 cluster_pos) { cluster_pos_ = cluster_pos; }
+  uint64 cluster_pos() const { return cluster_pos_; }
+  void set_block_number(uint64 block_number) { block_number_ = block_number; }
+  uint64 block_number() const { return block_number_; }
+  void set_output_block_number(bool output_block_number) {
+    output_block_number_ = output_block_number;
+  }
+  bool output_block_number() const { return output_block_number_; }
+
+ private:
+  // Returns the size in bytes for the payload of the CuePoint element.
+  uint64 PayloadSize() const;
+
+  // Absolute timecode according to the segment time base.
+  uint64 time_;
+
+  // The Track element associated with the CuePoint.
+  uint64 track_;
+
+  // The position of the Cluster containing the Block.
+  uint64 cluster_pos_;
+
+  // Number of the Block within the Cluster, starting from 1.
+  uint64 block_number_;
+
+  // If true the muxer will write out the block number for the cue if the
+  // block number is different than the default of 1. Default is set to true.
+  bool output_block_number_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(CuePoint);
+};
+
+///////////////////////////////////////////////////////////////
+// Cues element.
+class Cues {
+ public:
+  Cues();
+  ~Cues();
+
+  // Adds a cue point to the Cues element. Returns true on success.
+  bool AddCue(CuePoint* cue);
+
+  // Returns the cue point by index. Returns NULL if there is no cue point
+  // match.
+  CuePoint* GetCueByIndex(int32 index) const;
+
+  // Returns the total size of the Cues element
+  uint64 Size();
+
+  // Output the Cues element to the writer. Returns true on success.
+  bool Write(IMkvWriter* writer) const;
+
+  int32 cue_entries_size() const { return cue_entries_size_; }
+  void set_output_block_number(bool output_block_number) {
+    output_block_number_ = output_block_number;
+  }
+  bool output_block_number() const { return output_block_number_; }
+
+ private:
+  // Number of allocated elements in |cue_entries_|.
+  int32 cue_entries_capacity_;
+
+  // Number of CuePoints in |cue_entries_|.
+  int32 cue_entries_size_;
+
+  // CuePoint list.
+  CuePoint** cue_entries_;
+
+  // If true the muxer will write out the block number for the cue if the
+  // block number is different than the default of 1. Default is set to true.
+  bool output_block_number_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cues);
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncAESSettings element
+class ContentEncAESSettings {
+ public:
+  enum {
+    kCTR = 1
+  };
+
+  ContentEncAESSettings();
+  ~ContentEncAESSettings() {}
+
+  // Returns the size in bytes for the ContentEncAESSettings element.
+  uint64 Size() const;
+
+  // Writes out the ContentEncAESSettings element to |writer|. Returns true on
+  // success.
+  bool Write(IMkvWriter* writer) const;
+
+  uint64 cipher_mode() const { return cipher_mode_; }
+
+ private:
+  // Returns the size in bytes for the payload of the ContentEncAESSettings
+  // element.
+  uint64 PayloadSize() const;
+
+  // Sub elements
+  uint64 cipher_mode_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncAESSettings);
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+// Elements used to describe if the track data has been encrypted or
+// compressed with zlib or header stripping.
+// Currently only whole frames can be encrypted with AES. This dictates that
+// ContentEncodingOrder will be 0, ContentEncodingScope will be 1,
+// ContentEncodingType will be 1, and ContentEncAlgo will be 5.
+class ContentEncoding {
+ public:
+  ContentEncoding();
+  ~ContentEncoding();
+
+  // Sets the content encryption id. Copies |length| bytes from |id| to
+  // |enc_key_id_|. Returns true on success.
+  bool SetEncryptionID(const uint8* id, uint64 length);
+
+  // Returns the size in bytes for the ContentEncoding element.
+  uint64 Size() const;
+
+  // Writes out the ContentEncoding element to |writer|. Returns true on
+  // success.
+  bool Write(IMkvWriter* writer) const;
+
+  uint64 enc_algo() const { return enc_algo_; }
+  uint64 encoding_order() const { return encoding_order_; }
+  uint64 encoding_scope() const { return encoding_scope_; }
+  uint64 encoding_type() const { return encoding_type_; }
+  ContentEncAESSettings* enc_aes_settings() { return &enc_aes_settings_; }
+
+ private:
+  // Returns the size in bytes for the encoding elements.
+  uint64 EncodingSize(uint64 compresion_size, uint64 encryption_size) const;
+
+  // Returns the size in bytes for the encryption elements.
+  uint64 EncryptionSize() const;
+
+  // Track element names
+  uint64 enc_algo_;
+  uint8* enc_key_id_;
+  uint64 encoding_order_;
+  uint64 encoding_scope_;
+  uint64 encoding_type_;
+
+  // ContentEncAESSettings element.
+  ContentEncAESSettings enc_aes_settings_;
+
+  // Size of the ContentEncKeyID data in bytes.
+  uint64 enc_key_id_length_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
+};
+
+///////////////////////////////////////////////////////////////
+// Track element.
+class Track {
+ public:
+  // The |seed| parameter is used to synthesize a UID for the track.
+  explicit Track(unsigned int* seed);
+  virtual ~Track();
+
+  // Adds a ContentEncoding element to the Track. Returns true on success.
+  virtual bool AddContentEncoding();
+
+  // Returns the ContentEncoding by index. Returns NULL if there is no
+  // ContentEncoding match.
+  ContentEncoding* GetContentEncodingByIndex(uint32 index) const;
+
+  // Returns the size in bytes for the payload of the Track element.
+  virtual uint64 PayloadSize() const;
+
+  // Returns the size in bytes of the Track element.
+  virtual uint64 Size() const;
+
+  // Output the Track element to the writer. Returns true on success.
+  virtual bool Write(IMkvWriter* writer) const;
+
+  // Sets the CodecPrivate element of the Track element. Copies |length|
+  // bytes from |codec_private| to |codec_private_|. Returns true on success.
+  bool SetCodecPrivate(const uint8* codec_private, uint64 length);
+
+  void set_codec_id(const char* codec_id);
+  const char* codec_id() const { return codec_id_; }
+  const uint8* codec_private() const { return codec_private_; }
+  void set_language(const char* language);
+  const char* language() const { return language_; }
+  void set_max_block_additional_id(uint64 max_block_additional_id) {
+    max_block_additional_id_ = max_block_additional_id;
+  }
+  uint64 max_block_additional_id() const { return max_block_additional_id_; }
+  void set_name(const char* name);
+  const char* name() const { return name_; }
+  void set_number(uint64 number) { number_ = number; }
+  uint64 number() const { return number_; }
+  void set_type(uint64 type) { type_ = type; }
+  uint64 type() const { return type_; }
+  void set_uid(uint64 uid) { uid_ = uid; }
+  uint64 uid() const { return uid_; }
+  void set_codec_delay(uint64 codec_delay) { codec_delay_ = codec_delay; }
+  uint64 codec_delay() const { return codec_delay_; }
+  void set_seek_pre_roll(uint64 seek_pre_roll) {
+    seek_pre_roll_ = seek_pre_roll;
+  }
+  uint64 seek_pre_roll() const { return seek_pre_roll_; }
+
+  uint64 codec_private_length() const { return codec_private_length_; }
+  uint32 content_encoding_entries_size() const {
+    return content_encoding_entries_size_;
+  }
+
+ private:
+  // Track element names
+  char* codec_id_;
+  uint8* codec_private_;
+  char* language_;
+  uint64 max_block_additional_id_;
+  char* name_;
+  uint64 number_;
+  uint64 type_;
+  uint64 uid_;
+  uint64 codec_delay_;
+  uint64 seek_pre_roll_;
+
+  // Size of the CodecPrivate data in bytes.
+  uint64 codec_private_length_;
+
+  // ContentEncoding element list.
+  ContentEncoding** content_encoding_entries_;
+
+  // Number of ContentEncoding elements added.
+  uint32 content_encoding_entries_size_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track);
+};
+
+///////////////////////////////////////////////////////////////
+// Track that has video specific elements.
+class VideoTrack : public Track {
+ public:
+  // Supported modes for stereo 3D.
+  enum StereoMode {
+    kMono = 0,
+    kSideBySideLeftIsFirst  = 1,
+    kTopBottomRightIsFirst  = 2,
+    kTopBottomLeftIsFirst   = 3,
+    kSideBySideRightIsFirst = 11
+  };
+
+  enum AlphaMode {
+    kNoAlpha = 0,
+    kAlpha  = 1
+  };
+
+  // The |seed| parameter is used to synthesize a UID for the track.
+  explicit VideoTrack(unsigned int* seed);
+  virtual ~VideoTrack();
+
+  // Returns the size in bytes for the payload of the Track element plus the
+  // video specific elements.
+  virtual uint64 PayloadSize() const;
+
+  // Output the VideoTrack element to the writer. Returns true on success.
+  virtual bool Write(IMkvWriter* writer) const;
+
+  // Sets the video's stereo mode. Returns true on success.
+  bool SetStereoMode(uint64 stereo_mode);
+
+  // Sets the video's alpha mode. Returns true on success.
+  bool SetAlphaMode(uint64 alpha_mode);
+
+  void set_display_height(uint64 height) { display_height_ = height; }
+  uint64 display_height() const { return display_height_; }
+  void set_display_width(uint64 width) { display_width_ = width; }
+  uint64 display_width() const { return display_width_; }
+  void set_frame_rate(double frame_rate) { frame_rate_ = frame_rate; }
+  double frame_rate() const { return frame_rate_; }
+  void set_height(uint64 height) { height_ = height; }
+  uint64 height() const { return height_; }
+  uint64 stereo_mode() { return stereo_mode_; }
+  uint64 alpha_mode() { return alpha_mode_; }
+  void set_width(uint64 width) { width_ = width; }
+  uint64 width() const { return width_; }
+
+ private:
+  // Returns the size in bytes of the Video element.
+  uint64 VideoPayloadSize() const;
+
+  // Video track element names.
+  uint64 display_height_;
+  uint64 display_width_;
+  double frame_rate_;
+  uint64 height_;
+  uint64 stereo_mode_;
+  uint64 alpha_mode_;
+  uint64 width_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack);
+};
+
+///////////////////////////////////////////////////////////////
+// Track that has audio specific elements.
+class AudioTrack : public Track {
+ public:
+  // The |seed| parameter is used to synthesize a UID for the track.
+  explicit AudioTrack(unsigned int* seed);
+  virtual ~AudioTrack();
+
+  // Returns the size in bytes for the payload of the Track element plus the
+  // audio specific elements.
+  virtual uint64 PayloadSize() const;
+
+  // Output the AudioTrack element to the writer. Returns true on success.
+  virtual bool Write(IMkvWriter* writer) const;
+
+  void set_bit_depth(uint64 bit_depth) { bit_depth_ = bit_depth; }
+  uint64 bit_depth() const { return bit_depth_; }
+  void set_channels(uint64 channels) { channels_ = channels; }
+  uint64 channels() const { return channels_; }
+  void set_sample_rate(double sample_rate) { sample_rate_ = sample_rate; }
+  double sample_rate() const { return sample_rate_; }
+
+ private:
+  // Audio track element names.
+  uint64 bit_depth_;
+  uint64 channels_;
+  double sample_rate_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(AudioTrack);
+};
+
+///////////////////////////////////////////////////////////////
+// Tracks element
+class Tracks {
+ public:
+  // Audio and video type defined by the Matroska specs.
+  enum {
+    kVideo = 0x1,
+    kAudio = 0x2
+  };
+  // Opus, Vorbis, VP8, and VP9 codec ids defined by the Matroska specs.
+  static const char kOpusCodecId[];
+  static const char kVorbisCodecId[];
+  static const char kVp8CodecId[];
+  static const char kVp9CodecId[];
+
+  Tracks();
+  ~Tracks();
+
+  // Adds a Track element to the Tracks object. |track| will be owned and
+  // deleted by the Tracks object. Returns true on success. |number| is the
+  // number to use for the track. |number| must be >= 0. If |number| == 0
+  // then the muxer will decide on the track number.
+  bool AddTrack(Track* track, int32 number);
+
+  // Returns the track by index. Returns NULL if there is no track match.
+  const Track* GetTrackByIndex(uint32 idx) const;
+
+  // Search the Tracks and return the track that matches |tn|. Returns NULL
+  // if there is no track match.
+  Track* GetTrackByNumber(uint64 track_number) const;
+
+  // Returns true if the track number is an audio track.
+  bool TrackIsAudio(uint64 track_number) const;
+
+  // Returns true if the track number is a video track.
+  bool TrackIsVideo(uint64 track_number) const;
+
+  // Output the Tracks element to the writer. Returns true on success.
+  bool Write(IMkvWriter* writer) const;
+
+  uint32 track_entries_size() const { return track_entries_size_; }
+
+ private:
+  // Track element list.
+  Track** track_entries_;
+
+  // Number of Track elements added.
+  uint32 track_entries_size_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks);
+};
+
+///////////////////////////////////////////////////////////////
+// Chapter element
+//
+class Chapter {
+ public:
+  // Set the identifier for this chapter.  (This corresponds to the
+  // Cue Identifier line in WebVTT.)
+  // TODO(matthewjheaney): the actual serialization of this item in
+  // MKV is pending.
+  bool set_id(const char* id);
+
+  // Converts the nanosecond start and stop times of this chapter to
+  // their corresponding timecode values, and stores them that way.
+  void set_time(const Segment& segment,
+                uint64 start_time_ns,
+                uint64 end_time_ns);
+
+  // Sets the uid for this chapter. Primarily used to enable
+  // deterministic output from the muxer.
+  void set_uid(const uint64 uid) { uid_ = uid; }
+
+  // Add a title string to this chapter, per the semantics described
+  // here:
+  //  http://www.matroska.org/technical/specs/index.html
+  //
+  // The title ("chapter string") is a UTF-8 string.
+  //
+  // The language has ISO 639-2 representation, described here:
+  //  http://www.loc.gov/standards/iso639-2/englangn.html
+  //  http://www.loc.gov/standards/iso639-2/php/English_list.php
+  // If you specify NULL as the language value, this implies
+  // English ("eng").
+  //
+  // The country value corresponds to the codes listed here:
+  //  http://www.iana.org/domains/root/db/
+  //
+  // The function returns false if the string could not be allocated.
+  bool add_string(const char* title,
+                  const char* language,
+                  const char* country);
+
+ private:
+  friend class Chapters;
+
+  // For storage of chapter titles that differ by language.
+  class Display {
+   public:
+    // Establish representation invariant for new Display object.
+    void Init();
+
+    // Reclaim resources, in anticipation of destruction.
+    void Clear();
+
+    // Copies the title to the |title_| member.  Returns false on
+    // error.
+    bool set_title(const char* title);
+
+    // Copies the language to the |language_| member.  Returns false
+    // on error.
+    bool set_language(const char* language);
+
+    // Copies the country to the |country_| member.  Returns false on
+    // error.
+    bool set_country(const char* country);
+
+    // If |writer| is non-NULL, serialize the Display sub-element of
+    // the Atom into the stream.  Returns the Display element size on
+    // success, 0 if error.
+    uint64 WriteDisplay(IMkvWriter* writer) const;
+
+   private:
+    char* title_;
+    char* language_;
+    char* country_;
+  };
+
+  Chapter();
+  ~Chapter();
+
+  // Establish the representation invariant for a newly-created
+  // Chapter object.  The |seed| parameter is used to create the UID
+  // for this chapter atom.
+  void Init(unsigned int* seed);
+
+  // Copies this Chapter object to a different one.  This is used when
+  // expanding a plain array of Chapter objects (see Chapters).
+  void ShallowCopy(Chapter* dst) const;
+
+  // Reclaim resources used by this Chapter object, pending its
+  // destruction.
+  void Clear();
+
+  // If there is no storage remaining on the |displays_| array for a
+  // new display object, creates a new, longer array and copies the
+  // existing Display objects to the new array.  Returns false if the
+  // array cannot be expanded.
+  bool ExpandDisplaysArray();
+
+  // If |writer| is non-NULL, serialize the Atom sub-element into the
+  // stream.  Returns the total size of the element on success, 0 if
+  // error.
+  uint64 WriteAtom(IMkvWriter* writer) const;
+
+  // The string identifier for this chapter (corresponds to WebVTT cue
+  // identifier).
+  char* id_;
+
+  // Start timecode of the chapter.
+  uint64 start_timecode_;
+
+  // Stop timecode of the chapter.
+  uint64 end_timecode_;
+
+  // The binary identifier for this chapter.
+  uint64 uid_;
+
+  // The Atom element can contain multiple Display sub-elements, as
+  // the same logical title can be rendered in different languages.
+  Display* displays_;
+
+  // The physical length (total size) of the |displays_| array.
+  int displays_size_;
+
+  // The logical length (number of active elements) on the |displays_|
+  // array.
+  int displays_count_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter);
+};
+
+///////////////////////////////////////////////////////////////
+// Chapters element
+//
+class Chapters {
+ public:
+  Chapters();
+  ~Chapters();
+
+  Chapter* AddChapter(unsigned int* seed);
+
+  // Returns the number of chapters that have been added.
+  int Count() const;
+
+  // Output the Chapters element to the writer. Returns true on success.
+  bool Write(IMkvWriter* writer) const;
+
+ private:
+  // Expands the chapters_ array if there is not enough space to contain
+  // another chapter object.  Returns true on success.
+  bool ExpandChaptersArray();
+
+  // If |writer| is non-NULL, serialize the Edition sub-element of the
+  // Chapters element into the stream.  Returns the Edition element
+  // size on success, 0 if error.
+  uint64 WriteEdition(IMkvWriter* writer) const;
+
+  // Total length of the chapters_ array.
+  int chapters_size_;
+
+  // Number of active chapters on the chapters_ array.
+  int chapters_count_;
+
+  // Array for storage of chapter objects.
+  Chapter* chapters_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters);
+};
+
+///////////////////////////////////////////////////////////////
+// Cluster element
+//
+// Notes:
+//  |Init| must be called before any other method in this class.
+class Cluster {
+ public:
+  Cluster(uint64 timecode, int64 cues_pos);
+  ~Cluster();
+
+  // |timecode| is the absolute timecode of the cluster. |cues_pos| is the
+  // position for the cluster within the segment that should be written in
+  // the cues element.
+  bool Init(IMkvWriter* ptr_writer);
+
+  // Adds a frame to be output in the file. The frame is written out through
+  // |writer_| if successful. Returns true on success.
+  // Inputs:
+  //   frame: Pointer to the data
+  //   length: Length of the data
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.  The range of allowed values is [1, 126].
+  //   timecode:     Absolute (not relative to cluster) timestamp of the
+  //                 frame, expressed in timecode units.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrame(const uint8* frame,
+                uint64 length,
+                uint64 track_number,
+                uint64 timecode,  // timecode units (absolute)
+                bool is_key);
+
+  // Adds a frame to be output in the file. The frame is written out through
+  // |writer_| if successful. Returns true on success.
+  // Inputs:
+  //   frame: Pointer to the data
+  //   length: Length of the data
+  //   additional: Pointer to the additional data
+  //   additional_length: Length of the additional data
+  //   add_id: Value of BlockAddID element
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.  The range of allowed values is [1, 126].
+  //   abs_timecode: Absolute (not relative to cluster) timestamp of the
+  //                 frame, expressed in timecode units.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrameWithAdditional(const uint8* frame,
+                              uint64 length,
+                              const uint8* additional,
+                              uint64 additional_length,
+                              uint64 add_id,
+                              uint64 track_number,
+                              uint64 abs_timecode,
+                              bool is_key);
+
+  // Adds a frame to be output in the file. The frame is written out through
+  // |writer_| if successful. Returns true on success.
+  // Inputs:
+  //   frame: Pointer to the data.
+  //   length: Length of the data.
+  //   discard_padding: DiscardPadding element value.
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.  The range of allowed values is [1, 126].
+  //   abs_timecode: Absolute (not relative to cluster) timestamp of the
+  //                 frame, expressed in timecode units.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrameWithDiscardPadding(const uint8* frame,
+                                  uint64 length,
+                                  int64 discard_padding,
+                                  uint64 track_number,
+                                  uint64 abs_timecode,
+                                  bool is_key);
+
+  // Writes a frame of metadata to the output medium; returns true on
+  // success.
+  // Inputs:
+  //   frame: Pointer to the data
+  //   length: Length of the data
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.  The range of allowed values is [1, 126].
+  //   timecode:     Absolute (not relative to cluster) timestamp of the
+  //                 metadata frame, expressed in timecode units.
+  //   duration:     Duration of metadata frame, in timecode units.
+  //
+  // The metadata frame is written as a block group, with a duration
+  // sub-element but no reference time sub-elements (indicating that
+  // it is considered a keyframe, per Matroska semantics).
+  bool AddMetadata(const uint8* frame,
+                   uint64 length,
+                   uint64 track_number,
+                   uint64 timecode,  // timecode units (absolute)
+                   uint64 duration);  // timecode units
+
+  // Increments the size of the cluster's data in bytes.
+  void AddPayloadSize(uint64 size);
+
+  // Closes the cluster so no more data can be written to it. Will update the
+  // cluster's size if |writer_| is seekable. Returns true on success.
+  bool Finalize();
+
+  // Returns the size in bytes for the entire Cluster element.
+  uint64 Size() const;
+
+  int64 size_position() const { return size_position_; }
+  int32 blocks_added() const { return blocks_added_; }
+  uint64 payload_size() const { return payload_size_; }
+  int64 position_for_cues() const { return position_for_cues_; }
+  uint64 timecode() const { return timecode_; }
+
+ private:
+  //  Signature that matches either of WriteSimpleBlock or WriteMetadataBlock
+  //  in the muxer utilities package.
+  typedef uint64 (*WriteBlock)(IMkvWriter* writer,
+                               const uint8* data,
+                               uint64 length,
+                               uint64 track_number,
+                               int64 timecode,
+                               uint64 generic_arg);
+
+  //  Signature that matches WriteBlockWithAdditional
+  //  in the muxer utilities package.
+  typedef uint64 (*WriteBlockAdditional)(IMkvWriter* writer,
+                                         const uint8* data,
+                                         uint64 length,
+                                         const uint8* additional,
+                                         uint64 add_id,
+                                         uint64 additional_length,
+                                         uint64 track_number,
+                                         int64 timecode,
+                                         uint64 is_key);
+
+  //  Signature that matches WriteBlockWithDiscardPadding
+  //  in the muxer utilities package.
+  typedef uint64 (*WriteBlockDiscardPadding)(IMkvWriter* writer,
+                                             const uint8* data,
+                                             uint64 length,
+                                             int64 discard_padding,
+                                             uint64 track_number,
+                                             int64 timecode,
+                                             uint64 is_key);
+
+  // Utility method that confirms that blocks can still be added, and that the
+  // cluster header has been written. Used by |DoWriteBlock*|. Returns true
+  // when successful.
+  template <typename Type>
+  bool PreWriteBlock(Type* write_function);
+
+  // Utility method used by the |DoWriteBlock*| methods that handles the book
+  // keeping required after each block is written.
+  void PostWriteBlock(uint64 element_size);
+
+  // To simplify things, we require that there be fewer than 127
+  // tracks -- this allows us to serialize the track number value for
+  // a stream using a single byte, per the Matroska encoding.
+  bool IsValidTrackNumber(uint64 track_number) const;
+
+  // Given |abs_timecode|, calculates timecode relative to most recent timecode.
+  // Returns -1 on failure, or a relative timecode.
+  int64 GetRelativeTimecode(int64 abs_timecode) const;
+
+  //  Used to implement AddFrame and AddMetadata.
+  bool DoWriteBlock(const uint8* frame,
+                    uint64 length,
+                    uint64 track_number,
+                    uint64 absolute_timecode,
+                    uint64 generic_arg,
+                    WriteBlock write_block);
+
+  // Used to implement AddFrameWithAdditional
+  bool DoWriteBlockWithAdditional(const uint8* frame,
+                                  uint64 length,
+                                  const uint8* additional,
+                                  uint64 additional_length,
+                                  uint64 add_id,
+                                  uint64 track_number,
+                                  uint64 absolute_timecode,
+                                  uint64 generic_arg,
+                                  WriteBlockAdditional write_block);
+
+  // Used to implement AddFrameWithDiscardPadding
+  bool DoWriteBlockWithDiscardPadding(const uint8* frame,
+                                      uint64 length,
+                                      int64 discard_padding,
+                                      uint64 track_number,
+                                      uint64 absolute_timecode,
+                                      uint64 generic_arg,
+                                      WriteBlockDiscardPadding write_block);
+
+  // Outputs the Cluster header to |writer_|. Returns true on success.
+  bool WriteClusterHeader();
+
+  // Number of blocks added to the cluster.
+  int32 blocks_added_;
+
+  // Flag telling if the cluster has been closed.
+  bool finalized_;
+
+  // Flag telling if the cluster's header has been written.
+  bool header_written_;
+
+  // The size of the cluster elements in bytes.
+  uint64 payload_size_;
+
+  // The file position used for cue points.
+  const int64 position_for_cues_;
+
+  // The file position of the cluster's size element.
+  int64 size_position_;
+
+  // The absolute timecode of the cluster.
+  const uint64 timecode_;
+
+  // Pointer to the writer object. Not owned by this class.
+  IMkvWriter* writer_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cluster);
+};
+
+///////////////////////////////////////////////////////////////
+// SeekHead element
+class SeekHead {
+ public:
+  SeekHead();
+  ~SeekHead();
+
+  // TODO(fgalligan): Change this to reserve a certain size. Then check how
+  // big the seek entry to be added is as not every seek entry will be the
+  // maximum size it could be.
+  // Adds a seek entry to be written out when the element is finalized. |id|
+  // must be the coded mkv element id. |pos| is the file position of the
+  // element. Returns true on success.
+  bool AddSeekEntry(uint32 id, uint64 pos);
+
+  // Writes out SeekHead and SeekEntry elements. Returns true on success.
+  bool Finalize(IMkvWriter* writer) const;
+
+  // Returns the id of the Seek Entry at the given index. Returns -1 if index is
+  // out of range.
+  uint32 GetId(int index) const;
+
+  // Returns the position of the Seek Entry at the given index. Returns -1 if
+  // index is out of range.
+  uint64 GetPosition(int index) const;
+
+  // Sets the Seek Entry id and position at given index.
+  // Returns true on success.
+  bool SetSeekEntry(int index, uint32 id, uint64 position);
+
+  // Reserves space by writing out a Void element which will be updated with
+  // a SeekHead element later. Returns true on success.
+  bool Write(IMkvWriter* writer);
+
+  // We are going to put a cap on the number of Seek Entries.
+  const static int32 kSeekEntryCount = 5;
+
+ private:
+  // Returns the maximum size in bytes of one seek entry.
+  uint64 MaxEntrySize() const;
+
+  // Seek entry id element list.
+  uint32 seek_entry_id_[kSeekEntryCount];
+
+  // Seek entry pos element list.
+  uint64 seek_entry_pos_[kSeekEntryCount];
+
+  // The file position of SeekHead element.
+  int64 start_pos_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SeekHead);
+};
+
+///////////////////////////////////////////////////////////////
+// Segment Information element
+class SegmentInfo {
+ public:
+  SegmentInfo();
+  ~SegmentInfo();
+
+  // Will update the duration if |duration_| is > 0.0. Returns true on success.
+  bool Finalize(IMkvWriter* writer) const;
+
+  // Sets |muxing_app_| and |writing_app_|.
+  bool Init();
+
+  // Output the Segment Information element to the writer. Returns true on
+  // success.
+  bool Write(IMkvWriter* writer);
+
+  void set_duration(double duration) { duration_ = duration; }
+  double duration() const { return duration_; }
+  void set_muxing_app(const char* app);
+  const char* muxing_app() const { return muxing_app_; }
+  void set_timecode_scale(uint64 scale) { timecode_scale_ = scale; }
+  uint64 timecode_scale() const { return timecode_scale_; }
+  void set_writing_app(const char* app);
+  const char* writing_app() const { return writing_app_; }
+
+ private:
+  // Segment Information element names.
+  // Initially set to -1 to signify that a duration has not been set and should
+  // not be written out.
+  double duration_;
+  // Set to libwebm-%d.%d.%d.%d, major, minor, build, revision.
+  char* muxing_app_;
+  uint64 timecode_scale_;
+  // Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision.
+  char* writing_app_;
+
+  // The file position of the duration element.
+  int64 duration_pos_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SegmentInfo);
+};
+
+///////////////////////////////////////////////////////////////
+// This class represents the main segment in a WebM file. Currently only
+// supports one Segment element.
+//
+// Notes:
+//  |Init| must be called before any other method in this class.
+class Segment {
+ public:
+  enum Mode {
+    kLive = 0x1,
+    kFile = 0x2
+  };
+
+  enum CuesPosition {
+    kAfterClusters = 0x0,  // Position Cues after Clusters - Default
+    kBeforeClusters = 0x1  // Position Cues before Clusters
+  };
+
+  const static uint64 kDefaultMaxClusterDuration = 30000000000ULL;
+
+  Segment();
+  ~Segment();
+
+  // Initializes |SegmentInfo| and returns result. Always returns false when
+  // |ptr_writer| is NULL.
+  bool Init(IMkvWriter* ptr_writer);
+
+  // Adds a generic track to the segment.  Returns the newly-allocated
+  // track object (which is owned by the segment) on success, NULL on
+  // error. |number| is the number to use for the track.  |number|
+  // must be >= 0. If |number| == 0 then the muxer will decide on the
+  // track number.
+  Track* AddTrack(int32 number);
+
+  // Adds a Vorbis audio track to the segment. Returns the number of the track
+  // on success, 0 on error. |number| is the number to use for the audio track.
+  // |number| must be >= 0. If |number| == 0 then the muxer will decide on
+  // the track number.
+  uint64 AddAudioTrack(int32 sample_rate, int32 channels, int32 number);
+
+  // Adds an empty chapter to the chapters of this segment.  Returns
+  // non-NULL on success.  After adding the chapter, the caller should
+  // populate its fields via the Chapter member functions.
+  Chapter* AddChapter();
+
+  // Adds a cue point to the Cues element. |timestamp| is the time in
+  // nanoseconds of the cue's time. |track| is the Track of the Cue. This
+  // function must be called after AddFrame to calculate the correct
+  // BlockNumber for the CuePoint. Returns true on success.
+  bool AddCuePoint(uint64 timestamp, uint64 track);
+
+  // Adds a frame to be output in the file. Returns true on success.
+  // Inputs:
+  //   frame: Pointer to the data
+  //   length: Length of the data
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.
+  //   timestamp:    Timestamp of the frame in nanoseconds from 0.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrame(const uint8* frame,
+                uint64 length,
+                uint64 track_number,
+                uint64 timestamp_ns,
+                bool is_key);
+
+  // Writes a frame of metadata to the output medium; returns true on
+  // success.
+  // Inputs:
+  //   frame: Pointer to the data
+  //   length: Length of the data
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.
+  //   timecode:     Absolute timestamp of the metadata frame, expressed
+  //                 in nanosecond units.
+  //   duration:     Duration of metadata frame, in nanosecond units.
+  //
+  // The metadata frame is written as a block group, with a duration
+  // sub-element but no reference time sub-elements (indicating that
+  // it is considered a keyframe, per Matroska semantics).
+  bool AddMetadata(const uint8* frame,
+                   uint64 length,
+                   uint64 track_number,
+                   uint64 timestamp_ns,
+                   uint64 duration_ns);
+
+  // Writes a frame with additional data to the output medium; returns true on
+  // success.
+  // Inputs:
+  //   frame: Pointer to the data.
+  //   length: Length of the data.
+  //   additional: Pointer to additional data.
+  //   additional_length: Length of additional data.
+  //   add_id: Additional ID which identifies the type of additional data.
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.
+  //   timestamp:    Absolute timestamp of the frame, expressed in nanosecond
+  //                 units.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrameWithAdditional(const uint8* frame,
+                              uint64 length,
+                              const uint8* additional,
+                              uint64 additional_length,
+                              uint64 add_id,
+                              uint64 track_number,
+                              uint64 timestamp,
+                              bool is_key);
+
+  // Writes a frame with DiscardPadding to the output medium; returns true on
+  // success.
+  // Inputs:
+  //   frame: Pointer to the data.
+  //   length: Length of the data.
+  //   discard_padding: DiscardPadding element value.
+  //   track_number: Track to add the data to. Value returned by Add track
+  //                 functions.
+  //   timestamp:    Absolute timestamp of the frame, expressed in nanosecond
+  //                 units.
+  //   is_key:       Flag telling whether or not this frame is a key frame.
+  bool AddFrameWithDiscardPadding(const uint8* frame,
+                                  uint64 length,
+                                  int64 discard_padding,
+                                  uint64 track_number,
+                                  uint64 timestamp,
+                                  bool is_key);
+
+  // Writes a Frame to the output medium. Chooses the correct way of writing
+  // the frame (Block vs SimpleBlock) based on the parameters passed.
+  // Inputs:
+  //   frame: frame object
+  bool AddGenericFrame(const Frame* frame);
+
+  // Adds a VP8 video track to the segment. Returns the number of the track on
+  // success, 0 on error. |number| is the number to use for the video track.
+  // |number| must be >= 0. If |number| == 0 then the muxer will decide on
+  // the track number.
+  uint64 AddVideoTrack(int32 width, int32 height, int32 number);
+
+  // This function must be called after Finalize() if you need a copy of the
+  // output with Cues written before the Clusters. It will return false if the
+  // writer is not seekable of if chunking is set to true.
+  // Input parameters:
+  // reader - an IMkvReader object created with the same underlying file of the
+  //          current writer object. Make sure to close the existing writer
+  //          object before creating this so that all the data is properly
+  //          flushed and available for reading.
+  // writer - an IMkvWriter object pointing to a *different* file than the one
+  //          pointed by the current writer object. This file will contain the
+  //          Cues element before the Clusters.
+  bool CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
+                                     IMkvWriter* writer);
+
+  // Sets which track to use for the Cues element. Must have added the track
+  // before calling this function. Returns true on success. |track_number| is
+  // returned by the Add track functions.
+  bool CuesTrack(uint64 track_number);
+
+  // This will force the muxer to create a new Cluster when the next frame is
+  // added.
+  void ForceNewClusterOnNextFrame();
+
+  // Writes out any frames that have not been written out. Finalizes the last
+  // cluster. May update the size and duration of the segment. May output the
+  // Cues element. May finalize the SeekHead element. Returns true on success.
+  bool Finalize();
+
+  // Returns the Cues object.
+  Cues* GetCues() { return &cues_; }
+
+  // Returns the Segment Information object.
+  const SegmentInfo* GetSegmentInfo() const { return &segment_info_; }
+  SegmentInfo* GetSegmentInfo() { return &segment_info_; }
+
+  // Search the Tracks and return the track that matches |track_number|.
+  // Returns NULL if there is no track match.
+  Track* GetTrackByNumber(uint64 track_number) const;
+
+  // Toggles whether to output a cues element.
+  void OutputCues(bool output_cues);
+
+  // Sets if the muxer will output files in chunks or not. |chunking| is a
+  // flag telling whether or not to turn on chunking. |filename| is the base
+  // filename for the chunk files. The header chunk file will be named
+  // |filename|.hdr and the data chunks will be named
+  // |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing
+  // to files so the muxer will use the default MkvWriter class to control
+  // what data is written to what files. Returns true on success.
+  // TODO: Should we change the IMkvWriter Interface to add Open and Close?
+  // That will force the interface to be dependent on files.
+  bool SetChunking(bool chunking, const char* filename);
+
+  bool chunking() const { return chunking_; }
+  uint64 cues_track() const { return cues_track_; }
+  void set_max_cluster_duration(uint64 max_cluster_duration) {
+    max_cluster_duration_ = max_cluster_duration;
+  }
+  uint64 max_cluster_duration() const { return max_cluster_duration_; }
+  void set_max_cluster_size(uint64 max_cluster_size) {
+    max_cluster_size_ = max_cluster_size;
+  }
+  uint64 max_cluster_size() const { return max_cluster_size_; }
+  void set_mode(Mode mode) { mode_ = mode; }
+  Mode mode() const { return mode_; }
+  CuesPosition cues_position() const { return cues_position_; }
+  bool output_cues() const { return output_cues_; }
+  const SegmentInfo* segment_info() const { return &segment_info_; }
+
+ private:
+  // Checks if header information has been output and initialized. If not it
+  // will output the Segment element and initialize the SeekHead elment and
+  // Cues elements.
+  bool CheckHeaderInfo();
+
+  // Sets |name| according to how many chunks have been written. |ext| is the
+  // file extension. |name| must be deleted by the calling app. Returns true
+  // on success.
+  bool UpdateChunkName(const char* ext, char** name) const;
+
+  // Returns the maximum offset within the segment's payload. When chunking
+  // this function is needed to determine offsets of elements within the
+  // chunked files. Returns -1 on error.
+  int64 MaxOffset();
+
+  // Adds the frame to our frame array.
+  bool QueueFrame(Frame* frame);
+
+  // Output all frames that are queued. Returns -1 on error, otherwise
+  // it returns the number of frames written.
+  int WriteFramesAll();
+
+  // Output all frames that are queued that have an end time that is less
+  // then |timestamp|. Returns true on success and if there are no frames
+  // queued.
+  bool WriteFramesLessThan(uint64 timestamp);
+
+  // Outputs the segment header, Segment Information element, SeekHead element,
+  // and Tracks element to |writer_|.
+  bool WriteSegmentHeader();
+
+  // Given a frame with the specified timestamp (nanosecond units) and
+  // keyframe status, determine whether a new cluster should be
+  // created, before writing enqueued frames and the frame itself. The
+  // function returns one of the following values:
+  //  -1 = error: an out-of-order frame was detected
+  //  0 = do not create a new cluster, and write frame to the existing cluster
+  //  1 = create a new cluster, and write frame to that new cluster
+  //  2 = create a new cluster, and re-run test
+  int TestFrame(uint64 track_num, uint64 timestamp_ns, bool key) const;
+
+  // Create a new cluster, using the earlier of the first enqueued
+  // frame, or the indicated time. Returns true on success.
+  bool MakeNewCluster(uint64 timestamp_ns);
+
+  // Checks whether a new cluster needs to be created, and if so
+  // creates a new cluster. Returns false if creation of a new cluster
+  // was necessary but creation was not successful.
+  bool DoNewClusterProcessing(uint64 track_num, uint64 timestamp_ns, bool key);
+
+
+  // Adjusts Cue Point values (to place Cues before Clusters) so that they
+  // reflect the correct offsets.
+  void MoveCuesBeforeClusters();
+
+  // This function recursively computes the correct cluster offsets (this is
+  // done to move the Cues before Clusters). It recursively updates the change
+  // in size (which indicates a change in cluster offset) until no sizes change.
+  // Parameters:
+  // diff - indicates the difference in size of the Cues element that needs to
+  //        accounted for.
+  // index - index in the list of Cues which is currently being adjusted.
+  // cue_size - size of the Cues element.
+  void MoveCuesBeforeClustersHelper(uint64 diff, int index, uint64* cue_size);
+
+  // Seeds the random number generator used to make UIDs.
+  unsigned int seed_;
+
+  // WebM elements
+  Cues cues_;
+  SeekHead seek_head_;
+  SegmentInfo segment_info_;
+  Tracks tracks_;
+  Chapters chapters_;
+
+  // Number of chunks written.
+  int chunk_count_;
+
+  // Current chunk filename.
+  char* chunk_name_;
+
+  // Default MkvWriter object created by this class used for writing clusters
+  // out in separate files.
+  MkvWriter* chunk_writer_cluster_;
+
+  // Default MkvWriter object created by this class used for writing Cues
+  // element out to a file.
+  MkvWriter* chunk_writer_cues_;
+
+  // Default MkvWriter object created by this class used for writing the
+  // Matroska header out to a file.
+  MkvWriter* chunk_writer_header_;
+
+  // Flag telling whether or not the muxer is chunking output to multiple
+  // files.
+  bool chunking_;
+
+  // Base filename for the chunked files.
+  char* chunking_base_name_;
+
+  // File position offset where the Clusters end.
+  int64 cluster_end_offset_;
+
+  // List of clusters.
+  Cluster** cluster_list_;
+
+  // Number of cluster pointers allocated in the cluster list.
+  int32 cluster_list_capacity_;
+
+  // Number of clusters in the cluster list.
+  int32 cluster_list_size_;
+
+  // Indicates whether Cues should be written before or after Clusters
+  CuesPosition cues_position_;
+
+  // Track number that is associated with the cues element for this segment.
+  uint64 cues_track_;
+
+  // Tells the muxer to force a new cluster on the next Block.
+  bool force_new_cluster_;
+
+  // List of stored audio frames. These variables are used to store frames so
+  // the muxer can follow the guideline "Audio blocks that contain the video
+  // key frame's timecode should be in the same cluster as the video key frame
+  // block."
+  Frame** frames_;
+
+  // Number of frame pointers allocated in the frame list.
+  int32 frames_capacity_;
+
+  // Number of frames in the frame list.
+  int32 frames_size_;
+
+  // Flag telling if a video track has been added to the segment.
+  bool has_video_;
+
+  // Flag telling if the segment's header has been written.
+  bool header_written_;
+
+  // Duration of the last block in nanoseconds.
+  uint64 last_block_duration_;
+
+  // Last timestamp in nanoseconds added to a cluster.
+  uint64 last_timestamp_;
+
+  // Maximum time in nanoseconds for a cluster duration. This variable is a
+  // guideline and some clusters may have a longer duration. Default is 30
+  // seconds.
+  uint64 max_cluster_duration_;
+
+  // Maximum size in bytes for a cluster. This variable is a guideline and
+  // some clusters may have a larger size. Default is 0 which signifies that
+  // the muxer will decide the size.
+  uint64 max_cluster_size_;
+
+  // The mode that segment is in. If set to |kLive| the writer must not
+  // seek backwards.
+  Mode mode_;
+
+  // Flag telling the muxer that a new cue point should be added.
+  bool new_cuepoint_;
+
+  // TODO(fgalligan): Should we add support for more than one Cues element?
+  // Flag whether or not the muxer should output a Cues element.
+  bool output_cues_;
+
+  // The file position of the segment's payload.
+  int64 payload_pos_;
+
+  // The file position of the element's size.
+  int64 size_position_;
+
+  // Pointer to the writer objects. Not owned by this class.
+  IMkvWriter* writer_cluster_;
+  IMkvWriter* writer_cues_;
+  IMkvWriter* writer_header_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment);
+};
+
+}  //end namespace mkvmuxer
+
+#endif //MKVMUXER_HPP
diff --git a/third_party/libwebm/mkvmuxertypes.hpp b/third_party/libwebm/mkvmuxertypes.hpp
new file mode 100644 (file)
index 0000000..2c66fd2
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.\r
+//\r
+// Use of this source code is governed by a BSD-style license\r
+// that can be found in the LICENSE file in the root of the source\r
+// tree. An additional intellectual property rights grant can be found\r
+// in the file PATENTS.  All contributing project authors may\r
+// be found in the AUTHORS file in the root of the source tree.\r
+\r
+#ifndef MKVMUXERTYPES_HPP\r
+#define MKVMUXERTYPES_HPP\r
+\r
+// Copied from Chromium basictypes.h\r
+// A macro to disallow the copy constructor and operator= functions\r
+// This should be used in the private: declarations for a class\r
+#define LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TypeName) \\r
+  TypeName(const TypeName&);               \\r
+  void operator=(const TypeName&)\r
+\r
+namespace mkvmuxer {\r
+\r
+typedef unsigned char      uint8;\r
+typedef short              int16;\r
+typedef int                int32;\r
+typedef unsigned int       uint32;\r
+typedef long long          int64;\r
+typedef unsigned long long uint64;\r
+\r
+}  //end namespace mkvmuxer\r
+\r
+#endif // MKVMUXERTYPES_HPP\r
diff --git a/third_party/libwebm/mkvmuxerutil.cpp b/third_party/libwebm/mkvmuxerutil.cpp
new file mode 100644 (file)
index 0000000..96350e9
--- /dev/null
@@ -0,0 +1,713 @@
+// 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 "mkvmuxerutil.hpp"
+
+#ifdef __ANDROID__
+#include <fcntl.h>
+#endif
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#ifdef _MSC_VER
+#define _CRT_RAND_S
+#endif
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#include <new>
+
+#include "mkvwriter.hpp"
+#include "webmids.hpp"
+
+namespace mkvmuxer {
+
+int32 GetCodedUIntSize(uint64 value) {
+  if (value < 0x000000000000007FULL)
+    return 1;
+  else if (value < 0x0000000000003FFFULL)
+    return 2;
+  else if (value < 0x00000000001FFFFFULL)
+    return 3;
+  else if (value < 0x000000000FFFFFFFULL)
+    return 4;
+  else if (value < 0x00000007FFFFFFFFULL)
+    return 5;
+  else if (value < 0x000003FFFFFFFFFFULL)
+    return 6;
+  else if (value < 0x0001FFFFFFFFFFFFULL)
+    return 7;
+  return 8;
+}
+
+int32 GetUIntSize(uint64 value) {
+  if (value < 0x0000000000000100ULL)
+    return 1;
+  else if (value < 0x0000000000010000ULL)
+    return 2;
+  else if (value < 0x0000000001000000ULL)
+    return 3;
+  else if (value < 0x0000000100000000ULL)
+    return 4;
+  else if (value < 0x0000010000000000ULL)
+    return 5;
+  else if (value < 0x0001000000000000ULL)
+    return 6;
+  else if (value < 0x0100000000000000ULL)
+    return 7;
+  return 8;
+}
+
+uint64 EbmlMasterElementSize(uint64 type, uint64 value) {
+  // Size of EBML ID
+  int32 ebml_size = GetUIntSize(type);
+
+  // Datasize
+  ebml_size += GetCodedUIntSize(value);
+
+  return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, int64 value) {
+  return EbmlElementSize(type, static_cast<uint64>(value));
+}
+
+uint64 EbmlElementSize(uint64 type, uint64 value) {
+  // Size of EBML ID
+  int32 ebml_size = GetUIntSize(type);
+
+  // Datasize
+  ebml_size += GetUIntSize(value);
+
+  // Size of Datasize
+  ebml_size++;
+
+  return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, float /* value */ ) {
+  // Size of EBML ID
+  uint64 ebml_size = GetUIntSize(type);
+
+  // Datasize
+  ebml_size += sizeof(float);
+
+  // Size of Datasize
+  ebml_size++;
+
+  return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, const char* value) {
+  if (!value)
+    return 0;
+
+  // Size of EBML ID
+  uint64 ebml_size = GetUIntSize(type);
+
+  // Datasize
+  ebml_size += strlen(value);
+
+  // Size of Datasize
+  ebml_size++;
+
+  return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size) {
+  if (!value)
+    return 0;
+
+  // Size of EBML ID
+  uint64 ebml_size = GetUIntSize(type);
+
+  // Datasize
+  ebml_size += size;
+
+  // Size of Datasize
+  ebml_size += GetCodedUIntSize(size);
+
+  return ebml_size;
+}
+
+int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size) {
+  if (!writer || size < 1 || size > 8)
+    return -1;
+
+  for (int32 i = 1; i <= size; ++i) {
+    const int32 byte_count = size - i;
+    const int32 bit_count = byte_count * 8;
+
+    const int64 bb = value >> bit_count;
+    const uint8 b = static_cast<uint8>(bb);
+
+    const int32 status = writer->Write(&b, 1);
+
+    if (status < 0)
+      return status;
+  }
+
+  return 0;
+}
+
+int32 SerializeFloat(IMkvWriter* writer, float f) {
+  if (!writer)
+    return -1;
+
+  assert(sizeof(uint32) == sizeof(float));
+  // This union is merely used to avoid a reinterpret_cast from float& to
+  // uint32& which will result in violation of strict aliasing.
+  union U32 {
+    uint32 u32;
+    float f;
+  } value;
+  value.f = f;
+
+  for (int32 i = 1; i <= 4; ++i) {
+    const int32 byte_count = 4 - i;
+    const int32 bit_count = byte_count * 8;
+
+    const uint8 byte = static_cast<uint8>(value.u32 >> bit_count);
+
+    const int32 status = writer->Write(&byte, 1);
+
+    if (status < 0)
+      return status;
+  }
+
+  return 0;
+}
+
+int32 WriteUInt(IMkvWriter* writer, uint64 value) {
+  if (!writer)
+    return -1;
+
+  int32 size = GetCodedUIntSize(value);
+
+  return WriteUIntSize(writer, value, size);
+}
+
+int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) {
+  if (!writer || size < 0 || size > 8)
+    return -1;
+
+  if (size > 0) {
+    const uint64 bit = 1LL << (size * 7);
+
+    if (value > (bit - 2))
+      return -1;
+
+    value |= bit;
+  } else {
+    size = 1;
+    int64 bit;
+
+    for (;;) {
+      bit = 1LL << (size * 7);
+      const uint64 max = bit - 2;
+
+      if (value <= max)
+        break;
+
+      ++size;
+    }
+
+    if (size > 8)
+      return false;
+
+    value |= bit;
+  }
+
+  return SerializeInt(writer, value, size);
+}
+
+int32 WriteID(IMkvWriter* writer, uint64 type) {
+  if (!writer)
+    return -1;
+
+  writer->ElementStartNotify(type, writer->Position());
+
+  const int32 size = GetUIntSize(type);
+
+  return SerializeInt(writer, type, size);
+}
+
+bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) {
+  if (!writer)
+    return false;
+
+  if (WriteID(writer, type))
+    return false;
+
+  if (WriteUInt(writer, size))
+    return false;
+
+  return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) {
+  if (!writer)
+    return false;
+
+  if (WriteID(writer, type))
+    return false;
+
+  const uint64 size = GetUIntSize(value);
+  if (WriteUInt(writer, size))
+    return false;
+
+  if (SerializeInt(writer, value, static_cast<int32>(size)))
+    return false;
+
+  return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value) {
+  if (!writer)
+    return false;
+
+  if (WriteID(writer, type))
+    return false;
+
+  if (WriteUInt(writer, 4))
+    return false;
+
+  if (SerializeFloat(writer, value))
+    return false;
+
+  return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value) {
+  if (!writer || !value)
+    return false;
+
+  if (WriteID(writer, type))
+    return false;
+
+  const int32 length = strlen(value);
+  if (WriteUInt(writer, length))
+    return false;
+
+  if (writer->Write(value, length))
+    return false;
+
+  return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer,
+                      uint64 type,
+                      const uint8* value,
+                      uint64 size) {
+  if (!writer || !value || size < 1)
+    return false;
+
+  if (WriteID(writer, type))
+    return false;
+
+  if (WriteUInt(writer, size))
+    return false;
+
+  if (writer->Write(value, static_cast<uint32>(size)))
+    return false;
+
+  return true;
+}
+
+uint64 WriteSimpleBlock(IMkvWriter* writer,
+                        const uint8* data,
+                        uint64 length,
+                        uint64 track_number,
+                        int64 timecode,
+                        uint64 is_key) {
+  if (!writer)
+    return false;
+
+  if (!data || length < 1)
+    return false;
+
+  //  Here we only permit track number values to be no greater than
+  //  126, which the largest value we can store having a Matroska
+  //  integer representation of only 1 byte.
+
+  if (track_number < 1 || track_number > 126)
+    return false;
+
+  //  Technically the timestamp for a block can be less than the
+  //  timestamp for the cluster itself (remember that block timestamp
+  //  is a signed, 16-bit integer).  However, as a simplification we
+  //  only permit non-negative cluster-relative timestamps for blocks.
+
+  if (timecode < 0 || timecode > kMaxBlockTimecode)
+    return false;
+
+  if (WriteID(writer, kMkvSimpleBlock))
+    return 0;
+
+  const int32 size = static_cast<int32>(length) + 4;
+  if (WriteUInt(writer, size))
+    return 0;
+
+  if (WriteUInt(writer, static_cast<uint64>(track_number)))
+    return 0;
+
+  if (SerializeInt(writer, timecode, 2))
+    return 0;
+
+  uint64 flags = 0;
+  if (is_key)
+    flags |= 0x80;
+
+  if (SerializeInt(writer, flags, 1))
+    return 0;
+
+  if (writer->Write(data, static_cast<uint32>(length)))
+    return 0;
+
+  const uint64 element_size =
+    GetUIntSize(kMkvSimpleBlock) + GetCodedUIntSize(size) + 4 + length;
+
+  return element_size;
+}
+
+// We must write the metadata (key)frame as a BlockGroup element,
+// because we need to specify a duration for the frame.  The
+// BlockGroup element comprises the frame itself and its duration,
+// and is laid out as follows:
+//
+//   BlockGroup tag
+//   BlockGroup size
+//     Block tag
+//     Block size
+//     (the frame is the block payload)
+//     Duration tag
+//     Duration size
+//     (duration payload)
+//
+uint64 WriteMetadataBlock(IMkvWriter* writer,
+                          const uint8* data,
+                          uint64 length,
+                          uint64 track_number,
+                          int64 timecode,
+                          uint64 duration) {
+  // We don't backtrack when writing to the stream, so we must
+  // pre-compute the BlockGroup size, by summing the sizes of each
+  // sub-element (the block and the duration).
+
+  // We use a single byte for the track number of the block, which
+  // means the block header is exactly 4 bytes.
+
+  // TODO(matthewjheaney): use EbmlMasterElementSize and WriteEbmlMasterElement
+
+  const uint64 block_payload_size = 4 + length;
+  const int32 block_size = GetCodedUIntSize(block_payload_size);
+  const uint64 block_elem_size = 1 + block_size + block_payload_size;
+
+  const int32 duration_payload_size = GetUIntSize(duration);
+  const int32 duration_size = GetCodedUIntSize(duration_payload_size);
+  const uint64 duration_elem_size = 1 + duration_size + duration_payload_size;
+
+  const uint64 blockg_payload_size = block_elem_size + duration_elem_size;
+  const int32 blockg_size = GetCodedUIntSize(blockg_payload_size);
+  const uint64 blockg_elem_size = 1 + blockg_size + blockg_payload_size;
+
+  if (WriteID(writer, kMkvBlockGroup))  // 1-byte ID size
+    return 0;
+
+  if (WriteUInt(writer, blockg_payload_size))
+    return 0;
+
+  //  Write Block element
+
+  if (WriteID(writer, kMkvBlock))  // 1-byte ID size
+    return 0;
+
+  if (WriteUInt(writer, block_payload_size))
+    return 0;
+
+  // Byte 1 of 4
+
+  if (WriteUInt(writer, track_number))
+    return 0;
+
+  // Bytes 2 & 3 of 4
+
+  if (SerializeInt(writer, timecode, 2))
+    return 0;
+
+  // Byte 4 of 4
+
+  const uint64 flags = 0;
+
+  if (SerializeInt(writer, flags, 1))
+    return 0;
+
+  // Now write the actual frame (of metadata)
+
+  if (writer->Write(data, static_cast<uint32>(length)))
+    return 0;
+
+  // Write Duration element
+
+  if (WriteID(writer, kMkvBlockDuration))  // 1-byte ID size
+    return 0;
+
+  if (WriteUInt(writer, duration_payload_size))
+    return 0;
+
+  if (SerializeInt(writer, duration, duration_payload_size))
+    return 0;
+
+  // Note that we don't write a reference time as part of the block
+  // group; no reference time(s) indicates that this block is a
+  // keyframe.  (Unlike the case for a SimpleBlock element, the header
+  // bits of the Block sub-element of a BlockGroup element do not
+  // indicate keyframe status.  The keyframe status is inferred from
+  // the absence of reference time sub-elements.)
+
+  return blockg_elem_size;
+}
+
+// Writes a WebM BlockGroup with BlockAdditional data. The structure is as
+// follows:
+// Indentation shows sub-levels
+// BlockGroup
+//  Block
+//    Data
+//  BlockAdditions
+//    BlockMore
+//      BlockAddID
+//        1 (Denotes Alpha)
+//      BlockAdditional
+//        Data
+uint64 WriteBlockWithAdditional(IMkvWriter* writer,
+                                const uint8* data,
+                                uint64 length,
+                                const uint8* additional,
+                                uint64 additional_length,
+                                uint64 add_id,
+                                uint64 track_number,
+                                int64 timecode,
+                                uint64 is_key) {
+  if (!data || !additional || length < 1 || additional_length < 1)
+    return 0;
+
+  const uint64 block_payload_size = 4 + length;
+  const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock,
+                                                       block_payload_size) +
+                                 block_payload_size;
+  const uint64 block_additional_elem_size = EbmlElementSize(kMkvBlockAdditional,
+                                                            additional,
+                                                            additional_length);
+  const uint64 block_addid_elem_size = EbmlElementSize(kMkvBlockAddID, add_id);
+
+  const uint64 block_more_payload_size = block_addid_elem_size +
+                                         block_additional_elem_size;
+  const uint64 block_more_elem_size = EbmlMasterElementSize(
+                                          kMkvBlockMore,
+                                          block_more_payload_size) +
+                                      block_more_payload_size;
+  const uint64 block_additions_payload_size = block_more_elem_size;
+  const uint64 block_additions_elem_size = EbmlMasterElementSize(
+                                               kMkvBlockAdditions,
+                                               block_additions_payload_size) +
+                                           block_additions_payload_size;
+  const uint64 block_group_payload_size = block_elem_size +
+                                          block_additions_elem_size;
+  const uint64 block_group_elem_size = EbmlMasterElementSize(
+                                           kMkvBlockGroup,
+                                           block_group_payload_size) +
+                                       block_group_payload_size;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlockGroup,
+                              block_group_payload_size))
+    return 0;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlock, block_payload_size))
+    return 0;
+
+  if (WriteUInt(writer, track_number))
+    return 0;
+
+  if (SerializeInt(writer, timecode, 2))
+    return 0;
+
+  uint64 flags = 0;
+  if (is_key)
+    flags |= 0x80;
+  if (SerializeInt(writer, flags, 1))
+    return 0;
+
+  if (writer->Write(data, static_cast<uint32>(length)))
+    return 0;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlockAdditions,
+                              block_additions_payload_size))
+    return 0;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlockMore, block_more_payload_size))
+    return 0;
+
+  if (!WriteEbmlElement(writer, kMkvBlockAddID, add_id))
+    return 0;
+
+  if (!WriteEbmlElement(writer, kMkvBlockAdditional,
+                        additional, additional_length))
+    return 0;
+
+  return block_group_elem_size;
+}
+
+// Writes a WebM BlockGroup with DiscardPadding. The structure is as follows:
+// Indentation shows sub-levels
+// BlockGroup
+//  Block
+//    Data
+//  DiscardPadding
+uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer,
+                                    const uint8* data,
+                                    uint64 length,
+                                    int64 discard_padding,
+                                    uint64 track_number,
+                                    int64 timecode,
+                                    uint64 is_key) {
+  if (!data || length < 1 || discard_padding <= 0)
+    return 0;
+
+  const uint64 block_payload_size = 4 + length;
+  const uint64 block_elem_size = EbmlMasterElementSize(kMkvBlock,
+                                                       block_payload_size) +
+                                 block_payload_size;
+  const uint64 discard_padding_elem_size = EbmlElementSize(kMkvDiscardPadding,
+                                                           discard_padding);
+  const uint64 block_group_payload_size = block_elem_size +
+                                          discard_padding_elem_size;
+  const uint64 block_group_elem_size = EbmlMasterElementSize(
+                                           kMkvBlockGroup,
+                                           block_group_payload_size) +
+                                       block_group_payload_size;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlockGroup,
+                              block_group_payload_size))
+    return 0;
+
+  if (!WriteEbmlMasterElement(writer, kMkvBlock, block_payload_size))
+    return 0;
+
+  if (WriteUInt(writer, track_number))
+    return 0;
+
+  if (SerializeInt(writer, timecode, 2))
+    return 0;
+
+  uint64 flags = 0;
+  if (is_key)
+    flags |= 0x80;
+  if (SerializeInt(writer, flags, 1))
+    return 0;
+
+  if (writer->Write(data, static_cast<uint32>(length)))
+    return 0;
+
+  if (WriteID(writer, kMkvDiscardPadding))
+    return 0;
+
+  const uint64 size = GetUIntSize(discard_padding);
+  if (WriteUInt(writer, size))
+    return false;
+
+  if (SerializeInt(writer, discard_padding, static_cast<int32>(size)))
+    return false;
+
+  return block_group_elem_size;
+}
+
+uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) {
+  if (!writer)
+    return false;
+
+  // Subtract one for the void ID and the coded size.
+  uint64 void_entry_size = size - 1 - GetCodedUIntSize(size-1);
+  uint64 void_size = EbmlMasterElementSize(kMkvVoid, void_entry_size) +
+                     void_entry_size;
+
+  if (void_size != size)
+    return 0;
+
+  const int64 payload_position = writer->Position();
+  if (payload_position < 0)
+    return 0;
+
+  if (WriteID(writer, kMkvVoid))
+    return 0;
+
+  if (WriteUInt(writer, void_entry_size))
+    return 0;
+
+  const uint8 value = 0;
+  for (int32 i = 0; i < static_cast<int32>(void_entry_size); ++i) {
+    if (writer->Write(&value, 1))
+      return 0;
+  }
+
+  const int64 stop_position = writer->Position();
+  if (stop_position < 0 ||
+      stop_position - payload_position != static_cast<int64>(void_size))
+    return 0;
+
+  return void_size;
+}
+
+void GetVersion(int32* major, int32* minor, int32* build, int32* revision) {
+  *major = 0;
+  *minor = 2;
+  *build = 1;
+  *revision = 0;
+}
+
+}  // namespace mkvmuxer
+
+mkvmuxer::uint64 mkvmuxer::MakeUID(unsigned int* seed) {
+  uint64 uid = 0;
+
+#ifdef __MINGW32__
+  srand(*seed);
+#endif
+
+  for (int i = 0; i < 7; ++i) {  // avoid problems with 8-byte values
+    uid <<= 8;
+
+    // TODO(fgalligan): Move random number generation to platform specific code.
+#ifdef _MSC_VER
+    (void)seed;
+    unsigned int random_value;
+    const errno_t e = rand_s(&random_value);
+    (void)e;
+    const int32 nn  = random_value;
+#elif __ANDROID__
+    int32 temp_num = 1;
+    int fd = open("/dev/urandom", O_RDONLY);
+    if (fd != -1) {
+      read(fd, &temp_num, sizeof(int32));
+      close(fd);
+    }
+    const int32 nn = temp_num;
+#elif defined __MINGW32__
+    const int32 nn = rand();
+#else
+    const int32 nn = rand_r(seed);
+#endif
+    const int32 n = 0xFF & (nn >> 4);  // throw away low-order bits
+
+    uid |= n;
+  }
+
+  return uid;
+}
diff --git a/third_party/libwebm/mkvmuxerutil.hpp b/third_party/libwebm/mkvmuxerutil.hpp
new file mode 100644 (file)
index 0000000..d196ad3
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVMUXERUTIL_HPP
+#define MKVMUXERUTIL_HPP
+
+#include "mkvmuxertypes.hpp"
+
+namespace mkvmuxer {
+
+class IMkvWriter;
+
+const uint64 kEbmlUnknownValue = 0x01FFFFFFFFFFFFFFULL;
+const int64 kMaxBlockTimecode = 0x07FFFLL;
+
+// Writes out |value| in Big Endian order. Returns 0 on success.
+int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size);
+
+// Returns the size in bytes of the element.
+int32 GetUIntSize(uint64 value);
+int32 GetCodedUIntSize(uint64 value);
+uint64 EbmlMasterElementSize(uint64 type, uint64 value);
+uint64 EbmlElementSize(uint64 type, int64 value);
+uint64 EbmlElementSize(uint64 type, uint64 value);
+uint64 EbmlElementSize(uint64 type, float value);
+uint64 EbmlElementSize(uint64 type, const char* value);
+uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size);
+
+// Creates an EBML coded number from |value| and writes it out. The size of
+// the coded number is determined by the value of |value|. |value| must not
+// be in a coded form. Returns 0 on success.
+int32 WriteUInt(IMkvWriter* writer, uint64 value);
+
+// Creates an EBML coded number from |value| and writes it out. The size of
+// the coded number is determined by the value of |size|. |value| must not
+// be in a coded form. Returns 0 on success.
+int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size);
+
+// Output an Mkv master element. Returns true if the element was written.
+bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 value, uint64 size);
+
+// Outputs an Mkv ID, calls |IMkvWriter::ElementStartNotify|, and passes the
+// ID to |SerializeInt|. Returns 0 on success.
+int32 WriteID(IMkvWriter* writer, uint64 type);
+
+// Output an Mkv non-master element. Returns true if the element was written.
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value);
+bool WriteEbmlElement(IMkvWriter* writer,
+                      uint64 type,
+                      const uint8* value,
+                      uint64 size);
+
+// Output an Mkv Simple Block.
+// Inputs:
+//   data:         Pointer to the data.
+//   length:       Length of the data.
+//   track_number: Track to add the data to. Value returned by Add track
+//                  functions.  Only values in the range [1, 126] are
+//                  permitted.
+//   timecode:     Relative timecode of the Block.  Only values in the
+//                  range [0, 2^15) are permitted.
+//   is_key:       Non-zero value specifies that frame is a key frame.
+uint64 WriteSimpleBlock(IMkvWriter* writer,
+                        const uint8* data,
+                        uint64 length,
+                        uint64 track_number,
+                        int64 timecode,
+                        uint64 is_key);
+
+// Output a metadata keyframe, using a Block Group element.
+// Inputs:
+//   data:         Pointer to the (meta)data.
+//   length:       Length of the (meta)data.
+//   track_number: Track to add the data to. Value returned by Add track
+//                  functions.  Only values in the range [1, 126] are
+//                  permitted.
+//   timecode      Timecode of frame, relative to cluster timecode.  Only
+//                  values in the range [0, 2^15) are permitted.
+//   duration_timecode  Duration of frame, using timecode units.
+uint64 WriteMetadataBlock(IMkvWriter* writer,
+                          const uint8* data,
+                          uint64 length,
+                          uint64 track_number,
+                          int64 timecode,
+                          uint64 duration_timecode);
+
+// Output an Mkv Block with BlockAdditional data.
+// Inputs:
+//   data:         Pointer to the data.
+//   length:       Length of the data.
+//   additional:   Pointer to the additional data
+//   additional_length: Length of the additional data.
+//   add_id: Value of BlockAddID element.
+//   track_number: Track to add the data to. Value returned by Add track
+//                  functions.  Only values in the range [1, 126] are
+//                  permitted.
+//   timecode:     Relative timecode of the Block.  Only values in the
+//                  range [0, 2^15) are permitted.
+//   is_key:       Non-zero value specifies that frame is a key frame.
+uint64 WriteBlockWithAdditional(IMkvWriter* writer,
+                                const uint8* data,
+                                uint64 length,
+                                const uint8* additional,
+                                uint64 additional_length,
+                                uint64 add_id,
+                                uint64 track_number,
+                                int64 timecode,
+                                uint64 is_key);
+
+// Output an Mkv Block with a DiscardPadding element.
+// Inputs:
+//   data:            Pointer to the data.
+//   length:          Length of the data.
+//   discard_padding: DiscardPadding value.
+//   track_number:    Track to add the data to. Value returned by Add track
+//                    functions. Only values in the range [1, 126] are
+//                    permitted.
+//   timecode:        Relative timecode of the Block.  Only values in the
+//                    range [0, 2^15) are permitted.
+//   is_key:          Non-zero value specifies that frame is a key frame.
+uint64 WriteBlockWithDiscardPadding(IMkvWriter* writer,
+                                    const uint8* data,
+                                    uint64 length,
+                                    int64 discard_padding,
+                                    uint64 track_number,
+                                    int64 timecode,
+                                    uint64 is_key);
+
+// Output a void element. |size| must be the entire size in bytes that will be
+// void. The function will calculate the size of the void header and subtract
+// it from |size|.
+uint64 WriteVoidElement(IMkvWriter* writer, uint64 size);
+
+// Returns the version number of the muxer in |major|, |minor|, |build|,
+// and |revision|.
+void GetVersion(int32* major, int32* minor, int32* build, int32* revision);
+
+// Returns a random number to be used for UID, using |seed| to seed
+// the random-number generator (see POSIX rand_r() for semantics).
+uint64 MakeUID(unsigned int* seed);
+
+}  //end namespace mkvmuxer
+
+#endif // MKVMUXERUTIL_HPP
diff --git a/third_party/libwebm/mkvparser.cpp b/third_party/libwebm/mkvparser.cpp
new file mode 100644 (file)
index 0000000..b41456a
--- /dev/null
@@ -0,0 +1,9617 @@
+// 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 "mkvparser.hpp"
+#include <cassert>
+#include <cstring>
+#include <new>
+#include <climits>
+
+#ifdef _MSC_VER
+// Disable MSVC warnings that suggest making code non-portable.
+#pragma warning(disable:4996)
+#endif
+
+mkvparser::IMkvReader::~IMkvReader()
+{
+}
+
+void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision)
+{
+    major = 1;
+    minor = 0;
+    build = 0;
+    revision = 27;
+}
+
+long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    int status;
+
+//#ifdef _DEBUG
+//    long long total, available;
+//    status = pReader->Length(&total, &available);
+//    assert(status >= 0);
+//    assert((total < 0) || (available <= total));
+//    assert(pos < available);
+//    assert((available - pos) >= 1);  //assume here max u-int len is 8
+//#endif
+
+    len = 1;
+
+    unsigned char b;
+
+    status = pReader->Read(pos, 1, &b);
+
+    if (status < 0)  //error or underflow
+        return status;
+
+    if (status > 0)  //interpreted as "underflow"
+        return E_BUFFER_NOT_FULL;
+
+    if (b == 0)  //we can't handle u-int values larger than 8 bytes
+        return E_FILE_FORMAT_INVALID;
+
+    unsigned char m = 0x80;
+
+    while (!(b & m))
+    {
+        m >>= 1;
+        ++len;
+    }
+
+//#ifdef _DEBUG
+//    assert((available - pos) >= len);
+//#endif
+
+    long long result = b & (~m);
+    ++pos;
+
+    for (int i = 1; i < len; ++i)
+    {
+        status = pReader->Read(pos, 1, &b);
+
+        if (status < 0)
+        {
+            len = 1;
+            return status;
+        }
+
+        if (status > 0)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result <<= 8;
+        result |= b;
+
+        ++pos;
+    }
+
+    return result;
+}
+
+long long mkvparser::GetUIntLength(
+    IMkvReader* pReader,
+    long long pos,
+    long& len)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    long long total, available;
+
+    int status = pReader->Length(&total, &available);
+    assert(status >= 0);
+    assert((total < 0) || (available <= total));
+
+    len = 1;
+
+    if (pos >= available)
+        return pos;  //too few bytes available
+
+    unsigned char b;
+
+    status = pReader->Read(pos, 1, &b);
+
+    if (status < 0)
+        return status;
+
+    assert(status == 0);
+
+    if (b == 0)  //we can't handle u-int values larger than 8 bytes
+        return E_FILE_FORMAT_INVALID;
+
+    unsigned char m = 0x80;
+
+    while (!(b & m))
+    {
+        m >>= 1;
+        ++len;
+    }
+
+    return 0;  //success
+}
+
+
+long long mkvparser::UnserializeUInt(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    if ((size <= 0) || (size > 8))
+        return E_FILE_FORMAT_INVALID;
+
+    long long result = 0;
+
+    for (long long i = 0; i < size; ++i)
+    {
+        unsigned char b;
+
+        const long status = pReader->Read(pos, 1, &b);
+
+        if (status < 0)
+            return status;
+
+        result <<= 8;
+        result |= b;
+
+        ++pos;
+    }
+
+    return result;
+}
+
+
+long mkvparser::UnserializeFloat(
+    IMkvReader* pReader,
+    long long pos,
+    long long size_,
+    double& result)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    if ((size_ != 4) && (size_ != 8))
+        return E_FILE_FORMAT_INVALID;
+
+    const long size = static_cast<long>(size_);
+
+    unsigned char buf[8];
+
+    const int status = pReader->Read(pos, size, buf);
+
+    if (status < 0)  //error
+        return status;
+
+    if (size == 4)
+    {
+        union
+        {
+            float f;
+            unsigned long ff;
+        };
+
+        ff = 0;
+
+        for (int i = 0;;)
+        {
+            ff |= buf[i];
+
+            if (++i >= 4)
+                break;
+
+            ff <<= 8;
+        }
+
+        result = f;
+    }
+    else
+    {
+        assert(size == 8);
+
+        union
+        {
+            double d;
+            unsigned long long dd;
+        };
+
+        dd = 0;
+
+        for (int i = 0;;)
+        {
+            dd |= buf[i];
+
+            if (++i >= 8)
+                break;
+
+            dd <<= 8;
+        }
+
+        result = d;
+    }
+
+    return 0;
+}
+
+
+long mkvparser::UnserializeInt(
+    IMkvReader* pReader,
+    long long pos,
+    long size,
+    long long& result)
+{
+    assert(pReader);
+    assert(pos >= 0);
+    assert(size > 0);
+    assert(size <= 8);
+
+    {
+        signed char b;
+
+        const long status = pReader->Read(pos, 1, (unsigned char*)&b);
+
+        if (status < 0)
+            return status;
+
+        result = b;
+
+        ++pos;
+    }
+
+    for (long i = 1; i < size; ++i)
+    {
+        unsigned char b;
+
+        const long status = pReader->Read(pos, 1, &b);
+
+        if (status < 0)
+            return status;
+
+        result <<= 8;
+        result |= b;
+
+        ++pos;
+    }
+
+    return 0;  //success
+}
+
+
+long mkvparser::UnserializeString(
+    IMkvReader* pReader,
+    long long pos,
+    long long size_,
+    char*& str)
+{
+    delete[] str;
+    str = NULL;
+
+    if (size_ >= LONG_MAX)  //we need (size+1) chars
+        return E_FILE_FORMAT_INVALID;
+
+    const long size = static_cast<long>(size_);
+
+    str = new (std::nothrow) char[size+1];
+
+    if (str == NULL)
+        return -1;
+
+    unsigned char* const buf = reinterpret_cast<unsigned char*>(str);
+
+    const long status = pReader->Read(pos, size, buf);
+
+    if (status)
+    {
+        delete[] str;
+        str = NULL;
+
+        return status;
+    }
+
+    str[size] = '\0';
+
+    return 0;  //success
+}
+
+
+long mkvparser::ParseElementHeader(
+    IMkvReader* pReader,
+    long long& pos,
+    long long stop,
+    long long& id,
+    long long& size)
+{
+    if ((stop >= 0) && (pos >= stop))
+        return E_FILE_FORMAT_INVALID;
+
+    long len;
+
+    id = ReadUInt(pReader, pos, len);
+
+    if (id < 0)
+        return E_FILE_FORMAT_INVALID;
+
+    pos += len;  //consume id
+
+    if ((stop >= 0) && (pos >= stop))
+        return E_FILE_FORMAT_INVALID;
+
+    size = ReadUInt(pReader, pos, len);
+
+    if (size < 0)
+        return E_FILE_FORMAT_INVALID;
+
+    pos += len;  //consume length of size
+
+    //pos now designates payload
+
+    if ((stop >= 0) && ((pos + size) > stop))
+        return E_FILE_FORMAT_INVALID;
+
+    return 0;  //success
+}
+
+
+bool mkvparser::Match(
+    IMkvReader* pReader,
+    long long& pos,
+    unsigned long id_,
+    long long& val)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    long long total, available;
+
+    const long status = pReader->Length(&total, &available);
+    assert(status >= 0);
+    assert((total < 0) || (available <= total));
+    if (status < 0)
+        return false;
+
+    long len;
+
+    const long long id = ReadUInt(pReader, pos, len);
+    assert(id >= 0);
+    assert(len > 0);
+    assert(len <= 8);
+    assert((pos + len) <= available);
+
+    if ((unsigned long)id != id_)
+        return false;
+
+    pos += len;  //consume id
+
+    const long long size = ReadUInt(pReader, pos, len);
+    assert(size >= 0);
+    assert(size <= 8);
+    assert(len > 0);
+    assert(len <= 8);
+    assert((pos + len) <= available);
+
+    pos += len;  //consume length of size of payload
+
+    val = UnserializeUInt(pReader, pos, size);
+    assert(val >= 0);
+
+    pos += size;  //consume size of payload
+
+    return true;
+}
+
+bool mkvparser::Match(
+    IMkvReader* pReader,
+    long long& pos,
+    unsigned long id_,
+    unsigned char*& buf,
+    size_t& buflen)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    long long total, available;
+
+    long status = pReader->Length(&total, &available);
+    assert(status >= 0);
+    assert((total < 0) || (available <= total));
+    if (status < 0)
+        return false;
+
+    long len;
+    const long long id = ReadUInt(pReader, pos, len);
+    assert(id >= 0);
+    assert(len > 0);
+    assert(len <= 8);
+    assert((pos + len) <= available);
+
+    if ((unsigned long)id != id_)
+        return false;
+
+    pos += len;  //consume id
+
+    const long long size_ = ReadUInt(pReader, pos, len);
+    assert(size_ >= 0);
+    assert(len > 0);
+    assert(len <= 8);
+    assert((pos + len) <= available);
+
+    pos += len;  //consume length of size of payload
+    assert((pos + size_) <= available);
+
+    const long buflen_ = static_cast<long>(size_);
+
+    buf = new (std::nothrow) unsigned char[buflen_];
+    assert(buf);  //TODO
+
+    status = pReader->Read(pos, buflen_, buf);
+    assert(status == 0);  //TODO
+
+    buflen = buflen_;
+
+    pos += size_;  //consume size of payload
+    return true;
+}
+
+
+namespace mkvparser
+{
+
+EBMLHeader::EBMLHeader() :
+    m_docType(NULL)
+{
+    Init();
+}
+
+EBMLHeader::~EBMLHeader()
+{
+    delete[] m_docType;
+}
+
+void EBMLHeader::Init()
+{
+    m_version = 1;
+    m_readVersion = 1;
+    m_maxIdLength = 4;
+    m_maxSizeLength = 8;
+
+    if (m_docType)
+    {
+        delete[] m_docType;
+        m_docType = NULL;
+    }
+
+    m_docTypeVersion = 1;
+    m_docTypeReadVersion = 1;
+}
+
+long long EBMLHeader::Parse(
+    IMkvReader* pReader,
+    long long& pos)
+{
+    assert(pReader);
+
+    long long total, available;
+
+    long status = pReader->Length(&total, &available);
+
+    if (status < 0)  //error
+        return status;
+
+    pos = 0;
+    long long end = (available >= 1024) ? 1024 : available;
+
+    for (;;)
+    {
+        unsigned char b = 0;
+
+        while (pos < end)
+        {
+            status = pReader->Read(pos, 1, &b);
+
+            if (status < 0)  //error
+                return status;
+
+            if (b == 0x1A)
+                break;
+
+            ++pos;
+        }
+
+        if (b != 0x1A)
+        {
+            if (pos >= 1024)
+                return E_FILE_FORMAT_INVALID;  //don't bother looking anymore
+
+            if ((total >= 0) && ((total - available) < 5))
+                return E_FILE_FORMAT_INVALID;
+
+            return available + 5;  //5 = 4-byte ID + 1st byte of size
+        }
+
+        if ((total >= 0) && ((total - pos) < 5))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((available - pos) < 5)
+            return pos + 5;  //try again later
+
+        long len;
+
+        const long long result = ReadUInt(pReader, pos, len);
+
+        if (result < 0)  //error
+            return result;
+
+        if (result == 0x0A45DFA3)  //EBML Header ID
+        {
+            pos += len;  //consume ID
+            break;
+        }
+
+        ++pos;  //throw away just the 0x1A byte, and try again
+    }
+
+    //pos designates start of size field
+
+    //get length of size field
+
+    long len;
+    long long result = GetUIntLength(pReader, pos, len);
+
+    if (result < 0)  //error
+        return result;
+
+    if (result > 0)  //need more data
+        return result;
+
+    assert(len > 0);
+    assert(len <= 8);
+
+    if ((total >= 0) && ((total -  pos) < len))
+        return E_FILE_FORMAT_INVALID;
+
+    if ((available - pos) < len)
+        return pos + len;  //try again later
+
+    //get the EBML header size
+
+    result = ReadUInt(pReader, pos, len);
+
+    if (result < 0)  //error
+        return result;
+
+    pos += len;  //consume size field
+
+    //pos now designates start of payload
+
+    if ((total >= 0) && ((total - pos) < result))
+        return E_FILE_FORMAT_INVALID;
+
+    if ((available - pos) < result)
+        return pos + result;
+
+    end = pos + result;
+
+    Init();
+
+    while (pos < end)
+    {
+        long long id, size;
+
+        status = ParseElementHeader(
+                    pReader,
+                    pos,
+                    end,
+                    id,
+                    size);
+
+        if (status < 0) //error
+            return status;
+
+        if (size == 0)  //weird
+            return E_FILE_FORMAT_INVALID;
+
+        if (id == 0x0286)  //version
+        {
+            m_version = UnserializeUInt(pReader, pos, size);
+
+            if (m_version <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x02F7)  //read version
+        {
+            m_readVersion = UnserializeUInt(pReader, pos, size);
+
+            if (m_readVersion <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x02F2)  //max id length
+        {
+            m_maxIdLength = UnserializeUInt(pReader, pos, size);
+
+            if (m_maxIdLength <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x02F3)  //max size length
+        {
+            m_maxSizeLength = UnserializeUInt(pReader, pos, size);
+
+            if (m_maxSizeLength <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x0282)  //doctype
+        {
+            if (m_docType)
+                return E_FILE_FORMAT_INVALID;
+
+            status = UnserializeString(pReader, pos, size, m_docType);
+
+            if (status)  //error
+                return status;
+        }
+        else if (id == 0x0287)  //doctype version
+        {
+            m_docTypeVersion = UnserializeUInt(pReader, pos, size);
+
+            if (m_docTypeVersion <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x0285)  //doctype read version
+        {
+            m_docTypeReadVersion = UnserializeUInt(pReader, pos, size);
+
+            if (m_docTypeReadVersion <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+
+        pos += size;
+    }
+
+    assert(pos == end);
+    return 0;
+}
+
+
+Segment::Segment(
+    IMkvReader* pReader,
+    long long elem_start,
+    //long long elem_size,
+    long long start,
+    long long size) :
+    m_pReader(pReader),
+    m_element_start(elem_start),
+    //m_element_size(elem_size),
+    m_start(start),
+    m_size(size),
+    m_pos(start),
+    m_pUnknownSize(0),
+    m_pSeekHead(NULL),
+    m_pInfo(NULL),
+    m_pTracks(NULL),
+    m_pCues(NULL),
+    m_pChapters(NULL),
+    m_clusters(NULL),
+    m_clusterCount(0),
+    m_clusterPreloadCount(0),
+    m_clusterSize(0)
+{
+}
+
+
+Segment::~Segment()
+{
+    const long count = m_clusterCount + m_clusterPreloadCount;
+
+    Cluster** i = m_clusters;
+    Cluster** j = m_clusters + count;
+
+    while (i != j)
+    {
+        Cluster* const p = *i++;
+        assert(p);
+
+        delete p;
+    }
+
+    delete[] m_clusters;
+
+    delete m_pTracks;
+    delete m_pInfo;
+    delete m_pCues;
+    delete m_pChapters;
+    delete m_pSeekHead;
+}
+
+
+long long Segment::CreateInstance(
+    IMkvReader* pReader,
+    long long pos,
+    Segment*& pSegment)
+{
+    assert(pReader);
+    assert(pos >= 0);
+
+    pSegment = NULL;
+
+    long long total, available;
+
+    const long status = pReader->Length(&total, &available);
+
+    if (status < 0) //error
+        return status;
+
+    if (available < 0)
+        return -1;
+
+    if ((total >= 0) && (available > total))
+        return -1;
+
+    //I would assume that in practice this loop would execute
+    //exactly once, but we allow for other elements (e.g. Void)
+    //to immediately follow the EBML header.  This is fine for
+    //the source filter case (since the entire file is available),
+    //but in the splitter case over a network we should probably
+    //just give up early.  We could for example decide only to
+    //execute this loop a maximum of, say, 10 times.
+    //TODO:
+    //There is an implied "give up early" by only parsing up
+    //to the available limit.  We do do that, but only if the
+    //total file size is unknown.  We could decide to always
+    //use what's available as our limit (irrespective of whether
+    //we happen to know the total file length).  This would have
+    //as its sense "parse this much of the file before giving up",
+    //which a slightly different sense from "try to parse up to
+    //10 EMBL elements before giving up".
+
+    for (;;)
+    {
+        if ((total >= 0) && (pos >= total))
+            return E_FILE_FORMAT_INVALID;
+
+        //Read ID
+        long len;
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result)  //error, or too few available bytes
+            return result;
+
+        if ((total >= 0) && ((pos + len) > total))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > available)
+            return pos + len;
+
+        const long long idpos = pos;
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0)  //error
+            return id;
+
+        pos += len;  //consume ID
+
+        //Read Size
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result)  //error, or too few available bytes
+            return result;
+
+        if ((total >= 0) && ((pos + len) > total))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > available)
+            return pos + len;
+
+        long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return size;
+
+        pos += len;  //consume length of size of element
+
+        //Pos now points to start of payload
+
+        //Handle "unknown size" for live streaming of webm files.
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (id == 0x08538067)  //Segment ID
+        {
+            if (size == unknown_size)
+                size = -1;
+
+            else if (total < 0)
+                size = -1;
+
+            else if ((pos + size) > total)
+                size = -1;
+
+            pSegment = new (std::nothrow) Segment(
+                                            pReader,
+                                            idpos,
+                                            //elem_size
+                                            pos,
+                                            size);
+
+            if (pSegment == 0)
+                return -1;  //generic error
+
+            return 0;    //success
+        }
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((total >= 0) && ((pos + size) > total))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + size) > available)
+            return pos + size;
+
+        pos += size;  //consume payload
+    }
+}
+
+
+long long Segment::ParseHeaders()
+{
+    //Outermost (level 0) segment object has been constructed,
+    //and pos designates start of payload.  We need to find the
+    //inner (level 1) elements.
+    long long total, available;
+
+    const int status = m_pReader->Length(&total, &available);
+
+    if (status < 0) //error
+        return status;
+
+    assert((total < 0) || (available <= total));
+
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+    assert((segment_stop < 0) || (total < 0) || (segment_stop <= total));
+    assert((segment_stop < 0) || (m_pos <= segment_stop));
+
+    for (;;)
+    {
+        if ((total >= 0) && (m_pos >= total))
+            break;
+
+        if ((segment_stop >= 0) && (m_pos >= segment_stop))
+            break;
+
+        long long pos = m_pos;
+        const long long element_start = pos;
+
+        if ((pos + 1) > available)
+            return (pos + 1);
+
+        long len;
+        long long result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return result;
+
+        if (result > 0)  //underflow (weird)
+            return (pos + 1);
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > available)
+            return pos + len;
+
+        const long long idpos = pos;
+        const long long id = ReadUInt(m_pReader, idpos, len);
+
+        if (id < 0)  //error
+            return id;
+
+        if (id == 0x0F43B675)  //Cluster ID
+            break;
+
+        pos += len;  //consume ID
+
+        if ((pos + 1) > available)
+            return (pos + 1);
+
+        //Read Size
+        result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return result;
+
+        if (result > 0)  //underflow (weird)
+            return (pos + 1);
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > available)
+            return pos + len;
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+
+        if (size < 0)  //error
+            return size;
+
+        pos += len;  //consume length of size of element
+
+        const long long element_size = size + pos - element_start;
+
+        //Pos now points to start of payload
+
+        if ((segment_stop >= 0) && ((pos + size) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        //We read EBML elements either in total or nothing at all.
+
+        if ((pos + size) > available)
+            return pos + size;
+
+        if (id == 0x0549A966)  //Segment Info ID
+        {
+            if (m_pInfo)
+                return E_FILE_FORMAT_INVALID;
+
+            m_pInfo = new (std::nothrow) SegmentInfo(
+                                          this,
+                                          pos,
+                                          size,
+                                          element_start,
+                                          element_size);
+
+            if (m_pInfo == NULL)
+                return -1;
+
+            const long status = m_pInfo->Parse();
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x0654AE6B)  //Tracks ID
+        {
+            if (m_pTracks)
+                return E_FILE_FORMAT_INVALID;
+
+            m_pTracks = new (std::nothrow) Tracks(this,
+                                                  pos,
+                                                  size,
+                                                  element_start,
+                                                  element_size);
+
+            if (m_pTracks == NULL)
+                return -1;
+
+            const long status = m_pTracks->Parse();
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x0C53BB6B)  //Cues ID
+        {
+            if (m_pCues == NULL)
+            {
+                m_pCues = new (std::nothrow) Cues(
+                                                this,
+                                                pos,
+                                                size,
+                                                element_start,
+                                                element_size);
+
+                if (m_pCues == NULL)
+                    return -1;
+            }
+        }
+        else if (id == 0x014D9B74)  //SeekHead ID
+        {
+            if (m_pSeekHead == NULL)
+            {
+                m_pSeekHead = new (std::nothrow) SeekHead(
+                                                    this,
+                                                    pos,
+                                                    size,
+                                                    element_start,
+                                                    element_size);
+
+                if (m_pSeekHead == NULL)
+                    return -1;
+
+                const long status = m_pSeekHead->Parse();
+
+                if (status)
+                    return status;
+            }
+        }
+        else if (id == 0x0043A770)  //Chapters ID
+        {
+            if (m_pChapters == NULL)
+            {
+                m_pChapters = new (std::nothrow) Chapters(
+                                this,
+                                pos,
+                                size,
+                                element_start,
+                                element_size);
+
+                if (m_pChapters == NULL)
+                  return -1;
+
+                const long status = m_pChapters->Parse();
+
+                if (status)
+                  return status;
+            }
+        }
+
+        m_pos = pos + size;  //consume payload
+    }
+
+    assert((segment_stop < 0) || (m_pos <= segment_stop));
+
+    if (m_pInfo == NULL)  //TODO: liberalize this behavior
+        return E_FILE_FORMAT_INVALID;
+
+    if (m_pTracks == NULL)
+        return E_FILE_FORMAT_INVALID;
+
+    return 0;  //success
+}
+
+
+long Segment::LoadCluster(
+    long long& pos,
+    long& len)
+{
+    for (;;)
+    {
+        const long result = DoLoadCluster(pos, len);
+
+        if (result <= 1)
+            return result;
+    }
+}
+
+
+long Segment::DoLoadCluster(
+    long long& pos,
+    long& len)
+{
+    if (m_pos < 0)
+        return DoLoadClusterUnknownSize(pos, len);
+
+    long long total, avail;
+
+    long status = m_pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+    long long cluster_off = -1;   //offset relative to start of segment
+    long long cluster_size = -1;  //size of cluster payload
+
+    for (;;)
+    {
+        if ((total >= 0) && (m_pos >= total))
+            return 1;  //no more clusters
+
+        if ((segment_stop >= 0) && (m_pos >= segment_stop))
+            return 1;  //no more clusters
+
+        pos = m_pos;
+
+        //Read ID
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long idpos = pos;
+        const long long id = ReadUInt(m_pReader, idpos, len);
+
+        if (id < 0)  //error (or underflow)
+            return static_cast<long>(id);
+
+        pos += len;  //consume ID
+
+        //Read Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume length of size of element
+
+        //pos now points to start of payload
+
+        if (size == 0)  //weird
+        {
+            m_pos = pos;
+            continue;
+        }
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+#if 0  //we must handle this to support live webm
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;  //TODO: allow this
+#endif
+
+        if ((segment_stop >= 0) &&
+            (size != unknown_size) &&
+            ((pos + size) > segment_stop))
+        {
+            return E_FILE_FORMAT_INVALID;
+        }
+
+#if 0  //commented-out, to support incremental cluster parsing
+        len = static_cast<long>(size);
+
+        if ((pos + size) > avail)
+            return E_BUFFER_NOT_FULL;
+#endif
+
+        if (id == 0x0C53BB6B)  //Cues ID
+        {
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;  //TODO: liberalize
+
+            if (m_pCues == NULL)
+            {
+                const long long element_size = (pos - idpos) + size;
+
+                m_pCues = new Cues(this,
+                                   pos,
+                                   size,
+                                   idpos,
+                                   element_size);
+                assert(m_pCues);  //TODO
+            }
+
+            m_pos = pos + size;  //consume payload
+            continue;
+        }
+
+        if (id != 0x0F43B675)  //Cluster ID
+        {
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;  //TODO: liberalize
+
+            m_pos = pos + size;  //consume payload
+            continue;
+        }
+
+        //We have a cluster.
+
+        cluster_off = idpos - m_start;  //relative pos
+
+        if (size != unknown_size)
+            cluster_size = size;
+
+        break;
+    }
+
+    assert(cluster_off >= 0);  //have cluster
+
+    long long pos_;
+    long len_;
+
+    status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_);
+
+    if (status < 0) //error, or underflow
+    {
+        pos = pos_;
+        len = len_;
+
+        return status;
+    }
+
+    //status == 0 means "no block entries found"
+    //status > 0 means "found at least one block entry"
+
+    //TODO:
+    //The issue here is that the segment increments its own
+    //pos ptr past the most recent cluster parsed, and then
+    //starts from there to parse the next cluster.  If we
+    //don't know the size of the current cluster, then we
+    //must either parse its payload (as we do below), looking
+    //for the cluster (or cues) ID to terminate the parse.
+    //This isn't really what we want: rather, we really need
+    //a way to create the curr cluster object immediately.
+    //The pity is that cluster::parse can determine its own
+    //boundary, and we largely duplicate that same logic here.
+    //
+    //Maybe we need to get rid of our look-ahead preloading
+    //in source::parse???
+    //
+    //As we're parsing the blocks in the curr cluster
+    //(in cluster::parse), we should have some way to signal
+    //to the segment that we have determined the boundary,
+    //so it can adjust its own segment::m_pos member.
+    //
+    //The problem is that we're asserting in asyncreadinit,
+    //because we adjust the pos down to the curr seek pos,
+    //and the resulting adjusted len is > 2GB.  I'm suspicious
+    //that this is even correct, but even if it is, we can't
+    //be loading that much data in the cache anyway.
+
+    const long idx = m_clusterCount;
+
+    if (m_clusterPreloadCount > 0)
+    {
+        assert(idx < m_clusterSize);
+
+        Cluster* const pCluster = m_clusters[idx];
+        assert(pCluster);
+        assert(pCluster->m_index < 0);
+
+        const long long off = pCluster->GetPosition();
+        assert(off >= 0);
+
+        if (off == cluster_off)  //preloaded already
+        {
+            if (status == 0)  //no entries found
+                return E_FILE_FORMAT_INVALID;
+
+            if (cluster_size >= 0)
+                pos += cluster_size;
+            else
+            {
+                const long long element_size = pCluster->GetElementSize();
+
+                if (element_size <= 0)
+                    return E_FILE_FORMAT_INVALID;  //TODO: handle this case
+
+                pos = pCluster->m_element_start + element_size;
+            }
+
+            pCluster->m_index = idx;  //move from preloaded to loaded
+            ++m_clusterCount;
+            --m_clusterPreloadCount;
+
+            m_pos = pos;  //consume payload
+            assert((segment_stop < 0) || (m_pos <= segment_stop));
+
+            return 0;  //success
+        }
+    }
+
+    if (status == 0)  //no entries found
+    {
+        if (cluster_size < 0)
+            return E_FILE_FORMAT_INVALID;  //TODO: handle this
+
+        pos += cluster_size;
+
+        if ((total >= 0) && (pos >= total))
+        {
+            m_pos = total;
+            return 1;  //no more clusters
+        }
+
+        if ((segment_stop >= 0) && (pos >= segment_stop))
+        {
+            m_pos = segment_stop;
+            return 1;  //no more clusters
+        }
+
+        m_pos = pos;
+        return 2;  //try again
+    }
+
+    //status > 0 means we have an entry
+
+    Cluster* const pCluster = Cluster::Create(this,
+                                              idx,
+                                              cluster_off);
+                                              //element_size);
+    assert(pCluster);
+
+    AppendCluster(pCluster);
+    assert(m_clusters);
+    assert(idx < m_clusterSize);
+    assert(m_clusters[idx] == pCluster);
+
+    if (cluster_size >= 0)
+    {
+        pos += cluster_size;
+
+        m_pos = pos;
+        assert((segment_stop < 0) || (m_pos <= segment_stop));
+
+        return 0;
+    }
+
+    m_pUnknownSize = pCluster;
+    m_pos = -pos;
+
+    return 0;  //partial success, since we have a new cluster
+
+    //status == 0 means "no block entries found"
+
+    //pos designates start of payload
+    //m_pos has NOT been adjusted yet (in case we need to come back here)
+
+#if 0
+
+    if (cluster_size < 0)  //unknown size
+    {
+        const long long payload_pos = pos;  //absolute pos of cluster payload
+
+        for (;;)  //determine cluster size
+        {
+            if ((total >= 0) && (pos >= total))
+                break;
+
+            if ((segment_stop >= 0) && (pos >= segment_stop))
+                break;  //no more clusters
+
+            //Read ID
+
+            if ((pos + 1) > avail)
+            {
+                len = 1;
+                return E_BUFFER_NOT_FULL;
+            }
+
+            long long result = GetUIntLength(m_pReader, pos, len);
+
+            if (result < 0)  //error
+                return static_cast<long>(result);
+
+            if (result > 0)  //weird
+                return E_BUFFER_NOT_FULL;
+
+            if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            if ((pos + len) > avail)
+                return E_BUFFER_NOT_FULL;
+
+            const long long idpos = pos;
+            const long long id = ReadUInt(m_pReader, idpos, len);
+
+            if (id < 0)  //error (or underflow)
+                return static_cast<long>(id);
+
+            //This is the distinguished set of ID's we use to determine
+            //that we have exhausted the sub-element's inside the cluster
+            //whose ID we parsed earlier.
+
+            if (id == 0x0F43B675)  //Cluster ID
+                break;
+
+            if (id == 0x0C53BB6B)  //Cues ID
+                break;
+
+            switch (id)
+            {
+                case 0x20:  //BlockGroup
+                case 0x23:  //Simple Block
+                case 0x67:  //TimeCode
+                case 0x2B:  //PrevSize
+                    break;
+
+                default:
+                    assert(false);
+                    break;
+            }
+
+            pos += len;  //consume ID (of sub-element)
+
+            //Read Size
+
+            if ((pos + 1) > avail)
+            {
+                len = 1;
+                return E_BUFFER_NOT_FULL;
+            }
+
+            result = GetUIntLength(m_pReader, pos, len);
+
+            if (result < 0)  //error
+                return static_cast<long>(result);
+
+            if (result > 0)  //weird
+                return E_BUFFER_NOT_FULL;
+
+            if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            if ((pos + len) > avail)
+                return E_BUFFER_NOT_FULL;
+
+            const long long size = ReadUInt(m_pReader, pos, len);
+
+            if (size < 0)  //error
+                return static_cast<long>(size);
+
+            pos += len;  //consume size field of element
+
+            //pos now points to start of sub-element's payload
+
+            if (size == 0)  //weird
+                continue;
+
+            const long long unknown_size = (1LL << (7 * len)) - 1;
+
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;  //not allowed for sub-elements
+
+            if ((segment_stop >= 0) && ((pos + size) > segment_stop))  //weird
+                return E_FILE_FORMAT_INVALID;
+
+            pos += size;  //consume payload of sub-element
+            assert((segment_stop < 0) || (pos <= segment_stop));
+        }  //determine cluster size
+
+        cluster_size = pos - payload_pos;
+        assert(cluster_size >= 0);
+
+        pos = payload_pos;  //reset and re-parse original cluster
+    }
+
+    if (m_clusterPreloadCount > 0)
+    {
+        assert(idx < m_clusterSize);
+
+        Cluster* const pCluster = m_clusters[idx];
+        assert(pCluster);
+        assert(pCluster->m_index < 0);
+
+        const long long off = pCluster->GetPosition();
+        assert(off >= 0);
+
+        if (off == cluster_off)  //preloaded already
+            return E_FILE_FORMAT_INVALID;  //subtle
+    }
+
+    m_pos = pos + cluster_size;  //consume payload
+    assert((segment_stop < 0) || (m_pos <= segment_stop));
+
+    return 2;     //try to find another cluster
+
+#endif
+
+}
+
+
+long Segment::DoLoadClusterUnknownSize(
+    long long& pos,
+    long& len)
+{
+    assert(m_pos < 0);
+    assert(m_pUnknownSize);
+
+#if 0
+    assert(m_pUnknownSize->GetElementSize() < 0);  //TODO: verify this
+
+    const long long element_start = m_pUnknownSize->m_element_start;
+
+    pos = -m_pos;
+    assert(pos > element_start);
+
+    //We have already consumed the (cluster) ID and size fields.
+    //We just need to consume the blocks and other sub-elements
+    //of this cluster, until we discover the boundary.
+
+    long long total, avail;
+
+    long status = m_pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+    long long element_size = -1;
+
+    for (;;)  //determine cluster size
+    {
+        if ((total >= 0) && (pos >= total))
+        {
+            element_size = total - element_start;
+            assert(element_size > 0);
+
+            break;
+        }
+
+        if ((segment_stop >= 0) && (pos >= segment_stop))
+        {
+            element_size = segment_stop - element_start;
+            assert(element_size > 0);
+
+            break;
+        }
+
+        //Read ID
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long idpos = pos;
+        const long long id = ReadUInt(m_pReader, idpos, len);
+
+        if (id < 0)  //error (or underflow)
+            return static_cast<long>(id);
+
+        //This is the distinguished set of ID's we use to determine
+        //that we have exhausted the sub-element's inside the cluster
+        //whose ID we parsed earlier.
+
+        if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster ID or Cues ID
+        {
+            element_size = pos - element_start;
+            assert(element_size > 0);
+
+            break;
+        }
+
+#ifdef _DEBUG
+        switch (id)
+        {
+            case 0x20:  //BlockGroup
+            case 0x23:  //Simple Block
+            case 0x67:  //TimeCode
+            case 0x2B:  //PrevSize
+                break;
+
+            default:
+                assert(false);
+                break;
+        }
+#endif
+
+        pos += len;  //consume ID (of sub-element)
+
+        //Read Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume size field of element
+
+        //pos now points to start of sub-element's payload
+
+        if (size == 0)  //weird
+            continue;
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;  //not allowed for sub-elements
+
+        if ((segment_stop >= 0) && ((pos + size) > segment_stop))  //weird
+            return E_FILE_FORMAT_INVALID;
+
+        pos += size;  //consume payload of sub-element
+        assert((segment_stop < 0) || (pos <= segment_stop));
+    }  //determine cluster size
+
+    assert(element_size >= 0);
+
+    m_pos = element_start + element_size;
+    m_pUnknownSize = 0;
+
+    return 2;  //continue parsing
+#else
+    const long status = m_pUnknownSize->Parse(pos, len);
+
+    if (status < 0)  //error or underflow
+        return status;
+
+    if (status == 0)  //parsed a block
+        return 2;     //continue parsing
+
+    assert(status > 0);   //nothing left to parse of this cluster
+
+    const long long start = m_pUnknownSize->m_element_start;
+
+    const long long size = m_pUnknownSize->GetElementSize();
+    assert(size >= 0);
+
+    pos = start + size;
+    m_pos = pos;
+
+    m_pUnknownSize = 0;
+
+    return 2;  //continue parsing
+#endif
+}
+
+
+void Segment::AppendCluster(Cluster* pCluster)
+{
+    assert(pCluster);
+    assert(pCluster->m_index >= 0);
+
+    const long count = m_clusterCount + m_clusterPreloadCount;
+
+    long& size = m_clusterSize;
+    assert(size >= count);
+
+    const long idx = pCluster->m_index;
+    assert(idx == m_clusterCount);
+
+    if (count >= size)
+    {
+        const long n = (size <= 0) ? 2048 : 2*size;
+
+        Cluster** const qq = new Cluster*[n];
+        Cluster** q = qq;
+
+        Cluster** p = m_clusters;
+        Cluster** const pp = p + count;
+
+        while (p != pp)
+            *q++ = *p++;
+
+        delete[] m_clusters;
+
+        m_clusters = qq;
+        size = n;
+    }
+
+    if (m_clusterPreloadCount > 0)
+    {
+        assert(m_clusters);
+
+        Cluster** const p = m_clusters + m_clusterCount;
+        assert(*p);
+        assert((*p)->m_index < 0);
+
+        Cluster** q = p + m_clusterPreloadCount;
+        assert(q < (m_clusters + size));
+
+        for (;;)
+        {
+            Cluster** const qq = q - 1;
+            assert((*qq)->m_index < 0);
+
+            *q = *qq;
+            q = qq;
+
+            if (q == p)
+                break;
+        }
+    }
+
+    m_clusters[idx] = pCluster;
+    ++m_clusterCount;
+}
+
+
+void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx)
+{
+    assert(pCluster);
+    assert(pCluster->m_index < 0);
+    assert(idx >= m_clusterCount);
+
+    const long count = m_clusterCount + m_clusterPreloadCount;
+
+    long& size = m_clusterSize;
+    assert(size >= count);
+
+    if (count >= size)
+    {
+        const long n = (size <= 0) ? 2048 : 2*size;
+
+        Cluster** const qq = new Cluster*[n];
+        Cluster** q = qq;
+
+        Cluster** p = m_clusters;
+        Cluster** const pp = p + count;
+
+        while (p != pp)
+            *q++ = *p++;
+
+        delete[] m_clusters;
+
+        m_clusters = qq;
+        size = n;
+    }
+
+    assert(m_clusters);
+
+    Cluster** const p = m_clusters + idx;
+
+    Cluster** q = m_clusters + count;
+    assert(q >= p);
+    assert(q < (m_clusters + size));
+
+    while (q > p)
+    {
+        Cluster** const qq = q - 1;
+        assert((*qq)->m_index < 0);
+
+        *q = *qq;
+        q = qq;
+    }
+
+    m_clusters[idx] = pCluster;
+    ++m_clusterPreloadCount;
+}
+
+
+long Segment::Load()
+{
+    assert(m_clusters == NULL);
+    assert(m_clusterSize == 0);
+    assert(m_clusterCount == 0);
+    //assert(m_size >= 0);
+
+    //Outermost (level 0) segment object has been constructed,
+    //and pos designates start of payload.  We need to find the
+    //inner (level 1) elements.
+
+    const long long header_status = ParseHeaders();
+
+    if (header_status < 0)  //error
+        return static_cast<long>(header_status);
+
+    if (header_status > 0)  //underflow
+        return E_BUFFER_NOT_FULL;
+
+    assert(m_pInfo);
+    assert(m_pTracks);
+
+    for (;;)
+    {
+        const int status = LoadCluster();
+
+        if (status < 0)  //error
+            return status;
+
+        if (status >= 1)  //no more clusters
+            return 0;
+    }
+}
+
+
+SeekHead::SeekHead(
+    Segment* pSegment,
+    long long start,
+    long long size_,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_start(start),
+    m_size(size_),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    m_entries(0),
+    m_entry_count(0),
+    m_void_elements(0),
+    m_void_element_count(0)
+{
+}
+
+
+SeekHead::~SeekHead()
+{
+    delete[] m_entries;
+    delete[] m_void_elements;
+}
+
+
+long SeekHead::Parse()
+{
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long pos = m_start;
+    const long long stop = m_start + m_size;
+
+    //first count the seek head entries
+
+    int entry_count = 0;
+    int void_element_count = 0;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (id == 0x0DBB)  //SeekEntry ID
+            ++entry_count;
+        else if (id == 0x6C)  //Void ID
+            ++void_element_count;
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    m_entries = new (std::nothrow) Entry[entry_count];
+
+    if (m_entries == NULL)
+        return -1;
+
+    m_void_elements = new (std::nothrow) VoidElement[void_element_count];
+
+    if (m_void_elements == NULL)
+        return -1;
+
+    //now parse the entries and void elements
+
+    Entry* pEntry = m_entries;
+    VoidElement* pVoidElement = m_void_elements;
+
+    pos = m_start;
+
+    while (pos < stop)
+    {
+        const long long idpos = pos;
+
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (id == 0x0DBB)  //SeekEntry ID
+        {
+            if (ParseEntry(pReader, pos, size, pEntry))
+            {
+                Entry& e = *pEntry++;
+
+                e.element_start = idpos;
+                e.element_size = (pos + size) - idpos;
+            }
+        }
+        else if (id == 0x6C)  //Void ID
+        {
+            VoidElement& e = *pVoidElement++;
+
+            e.element_start = idpos;
+            e.element_size = (pos + size) - idpos;
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries);
+    assert(count_ >= 0);
+    assert(count_ <= entry_count);
+
+    m_entry_count = static_cast<int>(count_);
+
+    count_ = ptrdiff_t(pVoidElement - m_void_elements);
+    assert(count_ >= 0);
+    assert(count_ <= void_element_count);
+
+    m_void_element_count = static_cast<int>(count_);
+
+    return 0;
+}
+
+
+int SeekHead::GetCount() const
+{
+    return m_entry_count;
+}
+
+const SeekHead::Entry* SeekHead::GetEntry(int idx) const
+{
+    if (idx < 0)
+        return 0;
+
+    if (idx >= m_entry_count)
+        return 0;
+
+    return m_entries + idx;
+}
+
+int SeekHead::GetVoidElementCount() const
+{
+    return m_void_element_count;
+}
+
+const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const
+{
+    if (idx < 0)
+        return 0;
+
+    if (idx >= m_void_element_count)
+        return 0;
+
+    return m_void_elements + idx;
+}
+
+
+#if 0
+void Segment::ParseCues(long long off)
+{
+    if (m_pCues)
+        return;
+
+    //odbgstream os;
+    //os << "Segment::ParseCues (begin)" << endl;
+
+    long long pos = m_start + off;
+    const long long element_start = pos;
+    const long long stop = m_start + m_size;
+
+    long len;
+
+    long long result = GetUIntLength(m_pReader, pos, len);
+    assert(result == 0);
+    assert((pos + len) <= stop);
+
+    const long long idpos = pos;
+
+    const long long id = ReadUInt(m_pReader, idpos, len);
+    assert(id == 0x0C53BB6B);  //Cues ID
+
+    pos += len;  //consume ID
+    assert(pos < stop);
+
+    //Read Size
+
+    result = GetUIntLength(m_pReader, pos, len);
+    assert(result == 0);
+    assert((pos + len) <= stop);
+
+    const long long size = ReadUInt(m_pReader, pos, len);
+    assert(size >= 0);
+
+    pos += len;  //consume length of size of element
+    assert((pos + size) <= stop);
+
+    const long long element_size = size + pos - element_start;
+
+    //Pos now points to start of payload
+
+    m_pCues = new Cues(this, pos, size, element_start, element_size);
+    assert(m_pCues);  //TODO
+
+    //os << "Segment::ParseCues (end)" << endl;
+}
+#else
+long Segment::ParseCues(
+    long long off,
+    long long& pos,
+    long& len)
+{
+    if (m_pCues)
+        return 0;  //success
+
+    if (off < 0)
+        return -1;
+
+    long long total, avail;
+
+    const int status = m_pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    pos = m_start + off;
+
+    if ((total < 0) || (pos >= total))
+        return 1;  //don't bother parsing cues
+
+    const long long element_start = pos;
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+    if ((pos + 1) > avail)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    long long result = GetUIntLength(m_pReader, pos, len);
+
+    if (result < 0)  //error
+        return static_cast<long>(result);
+
+    if (result > 0) //underflow (weird)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + len) > avail)
+        return E_BUFFER_NOT_FULL;
+
+    const long long idpos = pos;
+
+    const long long id = ReadUInt(m_pReader, idpos, len);
+
+    if (id != 0x0C53BB6B)  //Cues ID
+        return E_FILE_FORMAT_INVALID;
+
+    pos += len;  //consume ID
+    assert((segment_stop < 0) || (pos <= segment_stop));
+
+    //Read Size
+
+    if ((pos + 1) > avail)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    result = GetUIntLength(m_pReader, pos, len);
+
+    if (result < 0)  //error
+        return static_cast<long>(result);
+
+    if (result > 0) //underflow (weird)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + len) > avail)
+        return E_BUFFER_NOT_FULL;
+
+    const long long size = ReadUInt(m_pReader, pos, len);
+
+    if (size < 0)  //error
+        return static_cast<long>(size);
+
+    if (size == 0)  //weird, although technically not illegal
+        return 1;   //done
+
+    pos += len;  //consume length of size of element
+    assert((segment_stop < 0) || (pos <= segment_stop));
+
+    //Pos now points to start of payload
+
+    const long long element_stop = pos + size;
+
+    if ((segment_stop >= 0) && (element_stop > segment_stop))
+        return E_FILE_FORMAT_INVALID;
+
+    if ((total >= 0) && (element_stop > total))
+        return 1;  //don't bother parsing anymore
+
+    len = static_cast<long>(size);
+
+    if (element_stop > avail)
+        return E_BUFFER_NOT_FULL;
+
+    const long long element_size = element_stop - element_start;
+
+    m_pCues = new (std::nothrow) Cues(
+                                    this,
+                                    pos,
+                                    size,
+                                    element_start,
+                                    element_size);
+    assert(m_pCues);  //TODO
+
+    return 0;  //success
+}
+#endif
+
+
+#if 0
+void Segment::ParseSeekEntry(
+    long long start,
+    long long size_)
+{
+    long long pos = start;
+
+    const long long stop = start + size_;
+
+    long len;
+
+    const long long seekIdId = ReadUInt(m_pReader, pos, len);
+    //seekIdId;
+    assert(seekIdId == 0x13AB);  //SeekID ID
+    assert((pos + len) <= stop);
+
+    pos += len;  //consume id
+
+    const long long seekIdSize = ReadUInt(m_pReader, pos, len);
+    assert(seekIdSize >= 0);
+    assert((pos + len) <= stop);
+
+    pos += len;  //consume size
+
+    const long long seekId = ReadUInt(m_pReader, pos, len);  //payload
+    assert(seekId >= 0);
+    assert(len == seekIdSize);
+    assert((pos + len) <= stop);
+
+    pos += seekIdSize;  //consume payload
+
+    const long long seekPosId = ReadUInt(m_pReader, pos, len);
+    //seekPosId;
+    assert(seekPosId == 0x13AC);  //SeekPos ID
+    assert((pos + len) <= stop);
+
+    pos += len;  //consume id
+
+    const long long seekPosSize = ReadUInt(m_pReader, pos, len);
+    assert(seekPosSize >= 0);
+    assert((pos + len) <= stop);
+
+    pos += len;  //consume size
+    assert((pos + seekPosSize) <= stop);
+
+    const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize);
+    assert(seekOff >= 0);
+    assert(seekOff < m_size);
+
+    pos += seekPosSize;  //consume payload
+    assert(pos == stop);
+
+    const long long seekPos = m_start + seekOff;
+    assert(seekPos < (m_start + m_size));
+
+    if (seekId == 0x0C53BB6B)  //Cues ID
+        ParseCues(seekOff);
+}
+#else
+bool SeekHead::ParseEntry(
+    IMkvReader* pReader,
+    long long start,
+    long long size_,
+    Entry* pEntry)
+{
+    if (size_ <= 0)
+        return false;
+
+    long long pos = start;
+    const long long stop = start + size_;
+
+    long len;
+
+    //parse the container for the level-1 element ID
+
+    const long long seekIdId = ReadUInt(pReader, pos, len);
+    //seekIdId;
+
+    if (seekIdId != 0x13AB)  //SeekID ID
+        return false;
+
+    if ((pos + len) > stop)
+        return false;
+
+    pos += len;  //consume SeekID id
+
+    const long long seekIdSize = ReadUInt(pReader, pos, len);
+
+    if (seekIdSize <= 0)
+        return false;
+
+    if ((pos + len) > stop)
+        return false;
+
+    pos += len;  //consume size of field
+
+    if ((pos + seekIdSize) > stop)
+        return false;
+
+    //Note that the SeekId payload really is serialized
+    //as a "Matroska integer", not as a plain binary value.
+    //In fact, Matroska requires that ID values in the
+    //stream exactly match the binary representation as listed
+    //in the Matroska specification.
+    //
+    //This parser is more liberal, and permits IDs to have
+    //any width.  (This could make the representation in the stream
+    //different from what's in the spec, but it doesn't matter here,
+    //since we always normalize "Matroska integer" values.)
+
+    pEntry->id = ReadUInt(pReader, pos, len);  //payload
+
+    if (pEntry->id <= 0)
+        return false;
+
+    if (len != seekIdSize)
+        return false;
+
+    pos += seekIdSize;  //consume SeekID payload
+
+    const long long seekPosId = ReadUInt(pReader, pos, len);
+
+    if (seekPosId != 0x13AC)  //SeekPos ID
+        return false;
+
+    if ((pos + len) > stop)
+        return false;
+
+    pos += len;  //consume id
+
+    const long long seekPosSize = ReadUInt(pReader, pos, len);
+
+    if (seekPosSize <= 0)
+        return false;
+
+    if ((pos + len) > stop)
+        return false;
+
+    pos += len;  //consume size
+
+    if ((pos + seekPosSize) > stop)
+        return false;
+
+    pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize);
+
+    if (pEntry->pos < 0)
+        return false;
+
+    pos += seekPosSize;  //consume payload
+
+    if (pos != stop)
+        return false;
+
+    return true;
+}
+#endif
+
+
+Cues::Cues(
+    Segment* pSegment,
+    long long start_,
+    long long size_,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_start(start_),
+    m_size(size_),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    m_cue_points(NULL),
+    m_count(0),
+    m_preload_count(0),
+    m_pos(start_)
+{
+}
+
+
+Cues::~Cues()
+{
+    const long n = m_count + m_preload_count;
+
+    CuePoint** p = m_cue_points;
+    CuePoint** const q = p + n;
+
+    while (p != q)
+    {
+        CuePoint* const pCP = *p++;
+        assert(pCP);
+
+        delete pCP;
+    }
+
+    delete[] m_cue_points;
+}
+
+
+long Cues::GetCount() const
+{
+    if (m_cue_points == NULL)
+        return -1;
+
+    return m_count;  //TODO: really ignore preload count?
+}
+
+
+bool Cues::DoneParsing() const
+{
+    const long long stop = m_start + m_size;
+    return (m_pos >= stop);
+}
+
+
+void Cues::Init() const
+{
+    if (m_cue_points)
+        return;
+
+    assert(m_count == 0);
+    assert(m_preload_count == 0);
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    const long long stop = m_start + m_size;
+    long long pos = m_start;
+
+    long cue_points_size = 0;
+
+    while (pos < stop)
+    {
+        const long long idpos = pos;
+
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume Size field
+        assert((pos + size) <= stop);
+
+        if (id == 0x3B)  //CuePoint ID
+            PreloadCuePoint(cue_points_size, idpos);
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+}
+
+
+void Cues::PreloadCuePoint(
+    long& cue_points_size,
+    long long pos) const
+{
+    assert(m_count == 0);
+
+    if (m_preload_count >= cue_points_size)
+    {
+        const long n = (cue_points_size <= 0) ? 2048 : 2*cue_points_size;
+
+        CuePoint** const qq = new CuePoint*[n];
+        CuePoint** q = qq;  //beginning of target
+
+        CuePoint** p = m_cue_points;                //beginning of source
+        CuePoint** const pp = p + m_preload_count;  //end of source
+
+        while (p != pp)
+            *q++ = *p++;
+
+        delete[] m_cue_points;
+
+        m_cue_points = qq;
+        cue_points_size = n;
+    }
+
+    CuePoint* const pCP = new CuePoint(m_preload_count, pos);
+    m_cue_points[m_preload_count++] = pCP;
+}
+
+
+bool Cues::LoadCuePoint() const
+{
+    //odbgstream os;
+    //os << "Cues::LoadCuePoint" << endl;
+
+    const long long stop = m_start + m_size;
+
+    if (m_pos >= stop)
+        return false;  //nothing else to do
+
+    Init();
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    while (m_pos < stop)
+    {
+        const long long idpos = m_pos;
+
+        long len;
+
+        const long long id = ReadUInt(pReader, m_pos, len);
+        assert(id >= 0);  //TODO
+        assert((m_pos + len) <= stop);
+
+        m_pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, m_pos, len);
+        assert(size >= 0);
+        assert((m_pos + len) <= stop);
+
+        m_pos += len;  //consume Size field
+        assert((m_pos + size) <= stop);
+
+        if (id != 0x3B)  //CuePoint ID
+        {
+            m_pos += size;  //consume payload
+            assert(m_pos <= stop);
+
+            continue;
+        }
+
+        assert(m_preload_count > 0);
+
+        CuePoint* const pCP = m_cue_points[m_count];
+        assert(pCP);
+        assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos));
+        if (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos))
+            return false;
+
+        pCP->Load(pReader);
+        ++m_count;
+        --m_preload_count;
+
+        m_pos += size;  //consume payload
+        assert(m_pos <= stop);
+
+        return true;  //yes, we loaded a cue point
+    }
+
+    //return (m_pos < stop);
+    return false;  //no, we did not load a cue point
+}
+
+
+bool Cues::Find(
+    long long time_ns,
+    const Track* pTrack,
+    const CuePoint*& pCP,
+    const CuePoint::TrackPosition*& pTP) const
+{
+    assert(time_ns >= 0);
+    assert(pTrack);
+
+#if 0
+    LoadCuePoint();  //establish invariant
+
+    assert(m_cue_points);
+    assert(m_count > 0);
+
+    CuePoint** const ii = m_cue_points;
+    CuePoint** i = ii;
+
+    CuePoint** const jj = ii + m_count + m_preload_count;
+    CuePoint** j = jj;
+
+    pCP = *i;
+    assert(pCP);
+
+    if (time_ns <= pCP->GetTime(m_pSegment))
+    {
+        pTP = pCP->Find(pTrack);
+        return (pTP != NULL);
+    }
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[ii, i) <= time_ns
+        //[i, j)  ?
+        //[j, jj) > time_ns
+
+        CuePoint** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        CuePoint* const pCP = *k;
+        assert(pCP);
+
+        pCP->Load(pReader);
+
+        const long long t = pCP->GetTime(m_pSegment);
+
+        if (t <= time_ns)
+            i = k + 1;
+        else
+            j = k;
+
+        assert(i <= j);
+    }
+
+    assert(i == j);
+    assert(i <= jj);
+    assert(i > ii);
+
+    pCP = *--i;
+    assert(pCP);
+    assert(pCP->GetTime(m_pSegment) <= time_ns);
+#else
+    if (m_cue_points == NULL)
+        return false;
+
+    if (m_count == 0)
+        return false;
+
+    CuePoint** const ii = m_cue_points;
+    CuePoint** i = ii;
+
+    CuePoint** const jj = ii + m_count;
+    CuePoint** j = jj;
+
+    pCP = *i;
+    assert(pCP);
+
+    if (time_ns <= pCP->GetTime(m_pSegment))
+    {
+        pTP = pCP->Find(pTrack);
+        return (pTP != NULL);
+    }
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[ii, i) <= time_ns
+        //[i, j)  ?
+        //[j, jj) > time_ns
+
+        CuePoint** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        CuePoint* const pCP = *k;
+        assert(pCP);
+
+        const long long t = pCP->GetTime(m_pSegment);
+
+        if (t <= time_ns)
+            i = k + 1;
+        else
+            j = k;
+
+        assert(i <= j);
+    }
+
+    assert(i == j);
+    assert(i <= jj);
+    assert(i > ii);
+
+    pCP = *--i;
+    assert(pCP);
+    assert(pCP->GetTime(m_pSegment) <= time_ns);
+#endif
+
+    //TODO: here and elsewhere, it's probably not correct to search
+    //for the cue point with this time, and then search for a matching
+    //track.  In principle, the matching track could be on some earlier
+    //cue point, and with our current algorithm, we'd miss it.  To make
+    //this bullet-proof, we'd need to create a secondary structure,
+    //with a list of cue points that apply to a track, and then search
+    //that track-based structure for a matching cue point.
+
+    pTP = pCP->Find(pTrack);
+    return (pTP != NULL);
+}
+
+
+#if 0
+bool Cues::FindNext(
+    long long time_ns,
+    const Track* pTrack,
+    const CuePoint*& pCP,
+    const CuePoint::TrackPosition*& pTP) const
+{
+    pCP = 0;
+    pTP = 0;
+
+    if (m_count == 0)
+        return false;
+
+    assert(m_cue_points);
+
+    const CuePoint* const* const ii = m_cue_points;
+    const CuePoint* const* i = ii;
+
+    const CuePoint* const* const jj = ii + m_count;
+    const CuePoint* const* j = jj;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[ii, i) <= time_ns
+        //[i, j)  ?
+        //[j, jj) > time_ns
+
+        const CuePoint* const* const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        pCP = *k;
+        assert(pCP);
+
+        const long long t = pCP->GetTime(m_pSegment);
+
+        if (t <= time_ns)
+            i = k + 1;
+        else
+            j = k;
+
+        assert(i <= j);
+    }
+
+    assert(i == j);
+    assert(i <= jj);
+
+    if (i >= jj)  //time_ns is greater than max cue point
+        return false;
+
+    pCP = *i;
+    assert(pCP);
+    assert(pCP->GetTime(m_pSegment) > time_ns);
+
+    pTP = pCP->Find(pTrack);
+    return (pTP != NULL);
+}
+#endif
+
+
+const CuePoint* Cues::GetFirst() const
+{
+    if (m_cue_points == NULL)
+        return NULL;
+
+    if (m_count == 0)
+        return NULL;
+
+#if 0
+    LoadCuePoint();  //init cues
+
+    const size_t count = m_count + m_preload_count;
+
+    if (count == 0)  //weird
+        return NULL;
+#endif
+
+    CuePoint* const* const pp = m_cue_points;
+    assert(pp);
+
+    CuePoint* const pCP = pp[0];
+    assert(pCP);
+    assert(pCP->GetTimeCode() >= 0);
+
+    return pCP;
+}
+
+
+const CuePoint* Cues::GetLast() const
+{
+    if (m_cue_points == NULL)
+        return NULL;
+
+    if (m_count <= 0)
+        return NULL;
+
+#if 0
+    LoadCuePoint();  //init cues
+
+    const size_t count = m_count + m_preload_count;
+
+    if (count == 0)  //weird
+        return NULL;
+
+    const size_t index = count - 1;
+
+    CuePoint* const* const pp = m_cue_points;
+    assert(pp);
+
+    CuePoint* const pCP = pp[index];
+    assert(pCP);
+
+    pCP->Load(m_pSegment->m_pReader);
+    assert(pCP->GetTimeCode() >= 0);
+#else
+    const long index = m_count - 1;
+
+    CuePoint* const* const pp = m_cue_points;
+    assert(pp);
+
+    CuePoint* const pCP = pp[index];
+    assert(pCP);
+    assert(pCP->GetTimeCode() >= 0);
+#endif
+
+    return pCP;
+}
+
+
+const CuePoint* Cues::GetNext(const CuePoint* pCurr) const
+{
+    if (pCurr == NULL)
+        return NULL;
+
+    assert(pCurr->GetTimeCode() >= 0);
+    assert(m_cue_points);
+    assert(m_count >= 1);
+
+#if 0
+    const size_t count = m_count + m_preload_count;
+
+    size_t index = pCurr->m_index;
+    assert(index < count);
+
+    CuePoint* const* const pp = m_cue_points;
+    assert(pp);
+    assert(pp[index] == pCurr);
+
+    ++index;
+
+    if (index >= count)
+        return NULL;
+
+    CuePoint* const pNext = pp[index];
+    assert(pNext);
+
+    pNext->Load(m_pSegment->m_pReader);
+#else
+    long index = pCurr->m_index;
+    assert(index < m_count);
+
+    CuePoint* const* const pp = m_cue_points;
+    assert(pp);
+    assert(pp[index] == pCurr);
+
+    ++index;
+
+    if (index >= m_count)
+        return NULL;
+
+    CuePoint* const pNext = pp[index];
+    assert(pNext);
+    assert(pNext->GetTimeCode() >= 0);
+#endif
+
+    return pNext;
+}
+
+
+const BlockEntry* Cues::GetBlock(
+    const CuePoint* pCP,
+    const CuePoint::TrackPosition* pTP) const
+{
+    if (pCP == NULL)
+        return NULL;
+
+    if (pTP == NULL)
+        return NULL;
+
+    return m_pSegment->GetBlock(*pCP, *pTP);
+}
+
+
+const BlockEntry* Segment::GetBlock(
+    const CuePoint& cp,
+    const CuePoint::TrackPosition& tp)
+{
+    Cluster** const ii = m_clusters;
+    Cluster** i = ii;
+
+    const long count = m_clusterCount + m_clusterPreloadCount;
+
+    Cluster** const jj = ii + count;
+    Cluster** j = jj;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[ii, i) < pTP->m_pos
+        //[i, j) ?
+        //[j, jj)  > pTP->m_pos
+
+        Cluster** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        Cluster* const pCluster = *k;
+        assert(pCluster);
+
+        //const long long pos_ = pCluster->m_pos;
+        //assert(pos_);
+        //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+        const long long pos = pCluster->GetPosition();
+        assert(pos >= 0);
+
+        if (pos < tp.m_pos)
+            i = k + 1;
+        else if (pos > tp.m_pos)
+            j = k;
+        else
+            return pCluster->GetEntry(cp, tp);
+    }
+
+    assert(i == j);
+    //assert(Cluster::HasBlockEntries(this, tp.m_pos));
+
+    Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1);
+    assert(pCluster);
+
+    const ptrdiff_t idx = i - m_clusters;
+
+    PreloadCluster(pCluster, idx);
+    assert(m_clusters);
+    assert(m_clusterPreloadCount > 0);
+    assert(m_clusters[idx] == pCluster);
+
+    return pCluster->GetEntry(cp, tp);
+}
+
+
+const Cluster* Segment::FindOrPreloadCluster(long long requested_pos)
+{
+    if (requested_pos < 0)
+        return 0;
+
+    Cluster** const ii = m_clusters;
+    Cluster** i = ii;
+
+    const long count = m_clusterCount + m_clusterPreloadCount;
+
+    Cluster** const jj = ii + count;
+    Cluster** j = jj;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[ii, i) < pTP->m_pos
+        //[i, j) ?
+        //[j, jj)  > pTP->m_pos
+
+        Cluster** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        Cluster* const pCluster = *k;
+        assert(pCluster);
+
+        //const long long pos_ = pCluster->m_pos;
+        //assert(pos_);
+        //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+        const long long pos = pCluster->GetPosition();
+        assert(pos >= 0);
+
+        if (pos < requested_pos)
+            i = k + 1;
+        else if (pos > requested_pos)
+            j = k;
+        else
+            return pCluster;
+    }
+
+    assert(i == j);
+    //assert(Cluster::HasBlockEntries(this, tp.m_pos));
+
+    Cluster* const pCluster = Cluster::Create(
+                                this,
+                                -1,
+                                requested_pos);
+                                //-1);
+    assert(pCluster);
+
+    const ptrdiff_t idx = i - m_clusters;
+
+    PreloadCluster(pCluster, idx);
+    assert(m_clusters);
+    assert(m_clusterPreloadCount > 0);
+    assert(m_clusters[idx] == pCluster);
+
+    return pCluster;
+}
+
+
+CuePoint::CuePoint(long idx, long long pos) :
+    m_element_start(0),
+    m_element_size(0),
+    m_index(idx),
+    m_timecode(-1 * pos),
+    m_track_positions(NULL),
+    m_track_positions_count(0)
+{
+    assert(pos > 0);
+}
+
+
+CuePoint::~CuePoint()
+{
+    delete[] m_track_positions;
+}
+
+
+void CuePoint::Load(IMkvReader* pReader)
+{
+    //odbgstream os;
+    //os << "CuePoint::Load(begin): timecode=" << m_timecode << endl;
+
+    if (m_timecode >= 0)  //already loaded
+        return;
+
+    assert(m_track_positions == NULL);
+    assert(m_track_positions_count == 0);
+
+    long long pos_ = -m_timecode;
+    const long long element_start = pos_;
+
+    long long stop;
+
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos_, len);
+        assert(id == 0x3B);  //CuePoint ID
+        if (id != 0x3B)
+            return;
+
+        pos_ += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos_, len);
+        assert(size >= 0);
+
+        pos_ += len;  //consume Size field
+        //pos_ now points to start of payload
+
+        stop = pos_ + size;
+    }
+
+    const long long element_size = stop - element_start;
+
+    long long pos = pos_;
+
+    //First count number of track positions
+
+    while (pos < stop)
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume Size field
+        assert((pos + size) <= stop);
+
+        if (id == 0x33)  //CueTime ID
+            m_timecode = UnserializeUInt(pReader, pos, size);
+
+        else if (id == 0x37) //CueTrackPosition(s) ID
+            ++m_track_positions_count;
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(m_timecode >= 0);
+    assert(m_track_positions_count > 0);
+
+    //os << "CuePoint::Load(cont'd): idpos=" << idpos
+    //   << " timecode=" << m_timecode
+    //   << endl;
+
+    m_track_positions = new TrackPosition[m_track_positions_count];
+
+    //Now parse track positions
+
+    TrackPosition* p = m_track_positions;
+    pos = pos_;
+
+    while (pos < stop)
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume Size field
+        assert((pos + size) <= stop);
+
+        if (id == 0x37) //CueTrackPosition(s) ID
+        {
+            TrackPosition& tp = *p++;
+            tp.Parse(pReader, pos, size);
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(size_t(p - m_track_positions) == m_track_positions_count);
+
+    m_element_start = element_start;
+    m_element_size = element_size;
+}
+
+
+
+void CuePoint::TrackPosition::Parse(
+    IMkvReader* pReader,
+    long long start_,
+    long long size_)
+{
+    const long long stop = start_ + size_;
+    long long pos = start_;
+
+    m_track = -1;
+    m_pos = -1;
+    m_block = 1;  //default
+
+    while (pos < stop)
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume Size field
+        assert((pos + size) <= stop);
+
+        if (id == 0x77)  //CueTrack ID
+            m_track = UnserializeUInt(pReader, pos, size);
+
+        else if (id == 0x71)  //CueClusterPos ID
+            m_pos = UnserializeUInt(pReader, pos, size);
+
+        else if (id == 0x1378)  //CueBlockNumber
+            m_block = UnserializeUInt(pReader, pos, size);
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(m_pos >= 0);
+    assert(m_track > 0);
+    //assert(m_block > 0);
+}
+
+
+const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const
+{
+    assert(pTrack);
+
+    const long long n = pTrack->GetNumber();
+
+    const TrackPosition* i = m_track_positions;
+    const TrackPosition* const j = i + m_track_positions_count;
+
+    while (i != j)
+    {
+        const TrackPosition& p = *i++;
+
+        if (p.m_track == n)
+            return &p;
+    }
+
+    return NULL;  //no matching track number found
+}
+
+
+long long CuePoint::GetTimeCode() const
+{
+    return m_timecode;
+}
+
+long long CuePoint::GetTime(const Segment* pSegment) const
+{
+    assert(pSegment);
+    assert(m_timecode >= 0);
+
+    const SegmentInfo* const pInfo = pSegment->GetInfo();
+    assert(pInfo);
+
+    const long long scale = pInfo->GetTimeCodeScale();
+    assert(scale >= 1);
+
+    const long long time = scale * m_timecode;
+
+    return time;
+}
+
+
+#if 0
+long long Segment::Unparsed() const
+{
+    if (m_size < 0)
+        return LLONG_MAX;
+
+    const long long stop = m_start + m_size;
+
+    const long long result = stop - m_pos;
+    assert(result >= 0);
+
+    return result;
+}
+#else
+bool Segment::DoneParsing() const
+{
+    if (m_size < 0)
+    {
+        long long total, avail;
+
+        const int status = m_pReader->Length(&total, &avail);
+
+        if (status < 0)  //error
+            return true;  //must assume done
+
+        if (total < 0)
+            return false;  //assume live stream
+
+        return (m_pos >= total);
+    }
+
+    const long long stop = m_start + m_size;
+
+    return (m_pos >= stop);
+}
+#endif
+
+
+const Cluster* Segment::GetFirst() const
+{
+    if ((m_clusters == NULL) || (m_clusterCount <= 0))
+       return &m_eos;
+
+    Cluster* const pCluster = m_clusters[0];
+    assert(pCluster);
+
+    return pCluster;
+}
+
+
+const Cluster* Segment::GetLast() const
+{
+    if ((m_clusters == NULL) || (m_clusterCount <= 0))
+        return &m_eos;
+
+    const long idx = m_clusterCount - 1;
+
+    Cluster* const pCluster = m_clusters[idx];
+    assert(pCluster);
+
+    return pCluster;
+}
+
+
+unsigned long Segment::GetCount() const
+{
+    return m_clusterCount;
+}
+
+
+const Cluster* Segment::GetNext(const Cluster* pCurr)
+{
+    assert(pCurr);
+    assert(pCurr != &m_eos);
+    assert(m_clusters);
+
+    long idx =  pCurr->m_index;
+
+    if (idx >= 0)
+    {
+        assert(m_clusterCount > 0);
+        assert(idx < m_clusterCount);
+        assert(pCurr == m_clusters[idx]);
+
+        ++idx;
+
+        if (idx >= m_clusterCount)
+            return &m_eos;  //caller will LoadCluster as desired
+
+        Cluster* const pNext = m_clusters[idx];
+        assert(pNext);
+        assert(pNext->m_index >= 0);
+        assert(pNext->m_index == idx);
+
+        return pNext;
+    }
+
+    assert(m_clusterPreloadCount > 0);
+
+    long long pos = pCurr->m_element_start;
+
+    assert(m_size >= 0);  //TODO
+    const long long stop = m_start + m_size;  //end of segment
+
+    {
+        long len;
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+        assert(result == 0);
+        assert((pos + len) <= stop);  //TODO
+        if (result != 0)
+            return NULL;
+
+        const long long id = ReadUInt(m_pReader, pos, len);
+        assert(id == 0x0F43B675);  //Cluster ID
+        if (id != 0x0F43B675)
+            return NULL;
+
+        pos += len;  //consume ID
+
+        //Read Size
+        result = GetUIntLength(m_pReader, pos, len);
+        assert(result == 0);  //TODO
+        assert((pos + len) <= stop);  //TODO
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+        assert(size > 0);  //TODO
+        //assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
+
+        pos += len;  //consume length of size of element
+        assert((pos + size) <= stop);  //TODO
+
+        //Pos now points to start of payload
+
+        pos += size;  //consume payload
+    }
+
+    long long off_next = 0;
+
+    while (pos < stop)
+    {
+        long len;
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+        assert(result == 0);
+        assert((pos + len) <= stop);  //TODO
+        if (result != 0)
+            return NULL;
+
+        const long long idpos = pos;  //pos of next (potential) cluster
+
+        const long long id = ReadUInt(m_pReader, idpos, len);
+        assert(id > 0);  //TODO
+
+        pos += len;  //consume ID
+
+        //Read Size
+        result = GetUIntLength(m_pReader, pos, len);
+        assert(result == 0);  //TODO
+        assert((pos + len) <= stop);  //TODO
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+        assert(size >= 0);  //TODO
+
+        pos += len;  //consume length of size of element
+        assert((pos + size) <= stop);  //TODO
+
+        //Pos now points to start of payload
+
+        if (size == 0)  //weird
+            continue;
+
+        if (id == 0x0F43B675)  //Cluster ID
+        {
+            const long long off_next_ = idpos - m_start;
+
+            long long pos_;
+            long len_;
+
+            const long status = Cluster::HasBlockEntries(
+                                    this,
+                                    off_next_,
+                                    pos_,
+                                    len_);
+
+            assert(status >= 0);
+
+            if (status > 0)
+            {
+                off_next = off_next_;
+                break;
+            }
+        }
+
+        pos += size;  //consume payload
+    }
+
+    if (off_next <= 0)
+        return 0;
+
+    Cluster** const ii = m_clusters + m_clusterCount;
+    Cluster** i = ii;
+
+    Cluster** const jj = ii + m_clusterPreloadCount;
+    Cluster** j = jj;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[0, i) < pos_next
+        //[i, j) ?
+        //[j, jj)  > pos_next
+
+        Cluster** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        Cluster* const pNext = *k;
+        assert(pNext);
+        assert(pNext->m_index < 0);
+
+        //const long long pos_ = pNext->m_pos;
+        //assert(pos_);
+        //pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+        pos = pNext->GetPosition();
+
+        if (pos < off_next)
+            i = k + 1;
+        else if (pos > off_next)
+            j = k;
+        else
+            return pNext;
+    }
+
+    assert(i == j);
+
+    Cluster* const pNext = Cluster::Create(this,
+                                          -1,
+                                          off_next);
+    assert(pNext);
+
+    const ptrdiff_t idx_next = i - m_clusters;  //insertion position
+
+    PreloadCluster(pNext, idx_next);
+    assert(m_clusters);
+    assert(idx_next < m_clusterSize);
+    assert(m_clusters[idx_next] == pNext);
+
+    return pNext;
+}
+
+
+long Segment::ParseNext(
+    const Cluster* pCurr,
+    const Cluster*& pResult,
+    long long& pos,
+    long& len)
+{
+    assert(pCurr);
+    assert(!pCurr->EOS());
+    assert(m_clusters);
+
+    pResult = 0;
+
+    if (pCurr->m_index >= 0)  //loaded (not merely preloaded)
+    {
+        assert(m_clusters[pCurr->m_index] == pCurr);
+
+        const long next_idx = pCurr->m_index + 1;
+
+        if (next_idx < m_clusterCount)
+        {
+            pResult = m_clusters[next_idx];
+            return 0;  //success
+        }
+
+        //curr cluster is last among loaded
+
+        const long result = LoadCluster(pos, len);
+
+        if (result < 0)  //error or underflow
+            return result;
+
+        if (result > 0)  //no more clusters
+        {
+            //pResult = &m_eos;
+            return 1;
+        }
+
+        pResult = GetLast();
+        return 0;  //success
+    }
+
+    assert(m_pos > 0);
+
+    long long total, avail;
+
+    long status = m_pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+    //interrogate curr cluster
+
+    pos = pCurr->m_element_start;
+
+    if (pCurr->m_element_size >= 0)
+        pos += pCurr->m_element_size;
+    else
+    {
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(m_pReader, pos, len);
+
+        if (id != 0x0F43B675)  //weird: not Cluster ID
+            return -1;
+
+        pos += len;  //consume ID
+
+        //Read Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+
+        if (size < 0) //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume size field
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)          //TODO: should never happen
+            return E_FILE_FORMAT_INVALID;  //TODO: resolve this
+
+        //assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
+
+        if ((segment_stop >= 0) && ((pos + size) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        //Pos now points to start of payload
+
+        pos += size;  //consume payload (that is, the current cluster)
+        assert((segment_stop < 0) || (pos <= segment_stop));
+
+        //By consuming the payload, we are assuming that the curr
+        //cluster isn't interesting.  That is, we don't bother checking
+        //whether the payload of the curr cluster is less than what
+        //happens to be available (obtained via IMkvReader::Length).
+        //Presumably the caller has already dispensed with the current
+        //cluster, and really does want the next cluster.
+    }
+
+    //pos now points to just beyond the last fully-loaded cluster
+
+    for (;;)
+    {
+        const long status = DoParseNext(pResult, pos, len);
+
+        if (status <= 1)
+            return status;
+    }
+}
+
+
+long Segment::DoParseNext(
+    const Cluster*& pResult,
+    long long& pos,
+    long& len)
+{
+    long long total, avail;
+
+    long status = m_pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+    //Parse next cluster.  This is strictly a parsing activity.
+    //Creation of a new cluster object happens later, after the
+    //parsing is done.
+
+    long long off_next = 0;
+    long long cluster_size = -1;
+
+    for (;;)
+    {
+        if ((total >= 0) && (pos >= total))
+            return 1;  //EOF
+
+        if ((segment_stop >= 0) && (pos >= segment_stop))
+            return 1;  //EOF
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long idpos = pos;             //absolute
+        const long long idoff = pos - m_start;   //relative
+
+        const long long id = ReadUInt(m_pReader, idpos, len);  //absolute
+
+        if (id < 0)  //error
+            return static_cast<long>(id);
+
+        if (id == 0)  //weird
+            return -1;  //generic error
+
+        pos += len;  //consume ID
+
+        //Read Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(m_pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(m_pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume length of size of element
+
+        //Pos now points to start of payload
+
+        if (size == 0)  //weird
+            continue;
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if ((segment_stop >= 0) &&
+            (size != unknown_size) &&
+            ((pos + size) > segment_stop))
+        {
+            return E_FILE_FORMAT_INVALID;
+        }
+
+        if (id == 0x0C53BB6B)  //Cues ID
+        {
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;
+
+            const long long element_stop = pos + size;
+
+            if ((segment_stop >= 0) && (element_stop > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            const long long element_start = idpos;
+            const long long element_size = element_stop - element_start;
+
+            if (m_pCues == NULL)
+            {
+                m_pCues = new Cues(this,
+                                    pos,
+                                    size,
+                                    element_start,
+                                    element_size);
+                assert(m_pCues);  //TODO
+            }
+
+            pos += size;  //consume payload
+            assert((segment_stop < 0) || (pos <= segment_stop));
+
+            continue;
+        }
+
+        if (id != 0x0F43B675)  //not a Cluster ID
+        {
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;
+
+            pos += size;  //consume payload
+            assert((segment_stop < 0) || (pos <= segment_stop));
+
+            continue;
+        }
+
+#if 0 //this is commented-out to support incremental cluster parsing
+        len = static_cast<long>(size);
+
+        if (element_stop > avail)
+            return E_BUFFER_NOT_FULL;
+#endif
+
+        //We have a cluster.
+
+        off_next = idoff;
+
+        if (size != unknown_size)
+            cluster_size = size;
+
+        break;
+    }
+
+    assert(off_next > 0);  //have cluster
+
+    //We have parsed the next cluster.
+    //We have not created a cluster object yet.  What we need
+    //to do now is determine whether it has already be preloaded
+    //(in which case, an object for this cluster has already been
+    //created), and if not, create a new cluster object.
+
+    Cluster** const ii = m_clusters + m_clusterCount;
+    Cluster** i = ii;
+
+    Cluster** const jj = ii + m_clusterPreloadCount;
+    Cluster** j = jj;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[0, i) < pos_next
+        //[i, j) ?
+        //[j, jj)  > pos_next
+
+        Cluster** const k = i + (j - i) / 2;
+        assert(k < jj);
+
+        const Cluster* const pNext = *k;
+        assert(pNext);
+        assert(pNext->m_index < 0);
+
+        pos = pNext->GetPosition();
+        assert(pos >= 0);
+
+        if (pos < off_next)
+            i = k + 1;
+        else if (pos > off_next)
+            j = k;
+        else
+        {
+            pResult = pNext;
+            return 0;  //success
+        }
+    }
+
+    assert(i == j);
+
+    long long pos_;
+    long len_;
+
+    status = Cluster::HasBlockEntries(this, off_next, pos_, len_);
+
+    if (status < 0)  //error or underflow
+    {
+        pos = pos_;
+        len = len_;
+
+        return status;
+    }
+
+    if (status > 0)  //means "found at least one block entry"
+    {
+        Cluster* const pNext = Cluster::Create(this,
+                                                -1,   //preloaded
+                                                off_next);
+                                                //element_size);
+        assert(pNext);
+
+        const ptrdiff_t idx_next = i - m_clusters;  //insertion position
+
+        PreloadCluster(pNext, idx_next);
+        assert(m_clusters);
+        assert(idx_next < m_clusterSize);
+        assert(m_clusters[idx_next] == pNext);
+
+        pResult = pNext;
+        return 0;  //success
+    }
+
+    //status == 0 means "no block entries found"
+
+    if (cluster_size < 0)  //unknown size
+    {
+        const long long payload_pos = pos;  //absolute pos of cluster payload
+
+        for (;;)  //determine cluster size
+        {
+            if ((total >= 0) && (pos >= total))
+                break;
+
+            if ((segment_stop >= 0) && (pos >= segment_stop))
+                break;  //no more clusters
+
+            //Read ID
+
+            if ((pos + 1) > avail)
+            {
+                len = 1;
+                return E_BUFFER_NOT_FULL;
+            }
+
+            long long result = GetUIntLength(m_pReader, pos, len);
+
+            if (result < 0)  //error
+                return static_cast<long>(result);
+
+            if (result > 0)  //weird
+                return E_BUFFER_NOT_FULL;
+
+            if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            if ((pos + len) > avail)
+                return E_BUFFER_NOT_FULL;
+
+            const long long idpos = pos;
+            const long long id = ReadUInt(m_pReader, idpos, len);
+
+            if (id < 0)  //error (or underflow)
+                return static_cast<long>(id);
+
+            //This is the distinguished set of ID's we use to determine
+            //that we have exhausted the sub-element's inside the cluster
+            //whose ID we parsed earlier.
+
+            if (id == 0x0F43B675)  //Cluster ID
+                break;
+
+            if (id == 0x0C53BB6B)  //Cues ID
+                break;
+
+            pos += len;  //consume ID (of sub-element)
+
+            //Read Size
+
+            if ((pos + 1) > avail)
+            {
+                len = 1;
+                return E_BUFFER_NOT_FULL;
+            }
+
+            result = GetUIntLength(m_pReader, pos, len);
+
+            if (result < 0)  //error
+                return static_cast<long>(result);
+
+            if (result > 0)  //weird
+                return E_BUFFER_NOT_FULL;
+
+            if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            if ((pos + len) > avail)
+                return E_BUFFER_NOT_FULL;
+
+            const long long size = ReadUInt(m_pReader, pos, len);
+
+            if (size < 0)  //error
+                return static_cast<long>(size);
+
+            pos += len;  //consume size field of element
+
+            //pos now points to start of sub-element's payload
+
+            if (size == 0)  //weird
+                continue;
+
+            const long long unknown_size = (1LL << (7 * len)) - 1;
+
+            if (size == unknown_size)
+                return E_FILE_FORMAT_INVALID;  //not allowed for sub-elements
+
+            if ((segment_stop >= 0) && ((pos + size) > segment_stop))  //weird
+                return E_FILE_FORMAT_INVALID;
+
+            pos += size;  //consume payload of sub-element
+            assert((segment_stop < 0) || (pos <= segment_stop));
+        }  //determine cluster size
+
+        cluster_size = pos - payload_pos;
+        assert(cluster_size >= 0);  //TODO: handle cluster_size = 0
+
+        pos = payload_pos;  //reset and re-parse original cluster
+    }
+
+    pos += cluster_size;  //consume payload
+    assert((segment_stop < 0) || (pos <= segment_stop));
+
+    return 2;             //try to find a cluster that follows next
+}
+
+
+const Cluster* Segment::FindCluster(long long time_ns) const
+{
+    if ((m_clusters == NULL) || (m_clusterCount <= 0))
+        return &m_eos;
+
+    {
+        Cluster* const pCluster = m_clusters[0];
+        assert(pCluster);
+        assert(pCluster->m_index == 0);
+
+        if (time_ns <= pCluster->GetTime())
+            return pCluster;
+    }
+
+    //Binary search of cluster array
+
+    long i = 0;
+    long j = m_clusterCount;
+
+    while (i < j)
+    {
+        //INVARIANT:
+        //[0, i) <= time_ns
+        //[i, j) ?
+        //[j, m_clusterCount)  > time_ns
+
+        const long k = i + (j - i) / 2;
+        assert(k < m_clusterCount);
+
+        Cluster* const pCluster = m_clusters[k];
+        assert(pCluster);
+        assert(pCluster->m_index == k);
+
+        const long long t = pCluster->GetTime();
+
+        if (t <= time_ns)
+            i = k + 1;
+        else
+            j = k;
+
+        assert(i <= j);
+    }
+
+    assert(i == j);
+    assert(i > 0);
+    assert(i <= m_clusterCount);
+
+    const long k = i - 1;
+
+    Cluster* const pCluster = m_clusters[k];
+    assert(pCluster);
+    assert(pCluster->m_index == k);
+    assert(pCluster->GetTime() <= time_ns);
+
+    return pCluster;
+}
+
+
+#if 0
+const BlockEntry* Segment::Seek(
+    long long time_ns,
+    const Track* pTrack) const
+{
+    assert(pTrack);
+
+    if ((m_clusters == NULL) || (m_clusterCount <= 0))
+        return pTrack->GetEOS();
+
+    Cluster** const i = m_clusters;
+    assert(i);
+
+    {
+        Cluster* const pCluster = *i;
+        assert(pCluster);
+        assert(pCluster->m_index == 0);  //m_clusterCount > 0
+        assert(pCluster->m_pSegment == this);
+
+        if (time_ns <= pCluster->GetTime())
+            return pCluster->GetEntry(pTrack);
+    }
+
+    Cluster** const j = i + m_clusterCount;
+
+    if (pTrack->GetType() == 2)  //audio
+    {
+        //TODO: we could decide to use cues for this, as we do for video.
+        //But we only use it for video because looking around for a keyframe
+        //can get expensive.  Audio doesn't require anything special so a
+        //straight cluster search is good enough (we assume).
+
+        Cluster** lo = i;
+        Cluster** hi = j;
+
+        while (lo < hi)
+        {
+            //INVARIANT:
+            //[i, lo) <= time_ns
+            //[lo, hi) ?
+            //[hi, j)  > time_ns
+
+            Cluster** const mid = lo + (hi - lo) / 2;
+            assert(mid < hi);
+
+            Cluster* const pCluster = *mid;
+            assert(pCluster);
+            assert(pCluster->m_index == long(mid - m_clusters));
+            assert(pCluster->m_pSegment == this);
+
+            const long long t = pCluster->GetTime();
+
+            if (t <= time_ns)
+                lo = mid + 1;
+            else
+                hi = mid;
+
+            assert(lo <= hi);
+        }
+
+        assert(lo == hi);
+        assert(lo > i);
+        assert(lo <= j);
+
+        while (lo > i)
+        {
+            Cluster* const pCluster = *--lo;
+            assert(pCluster);
+            assert(pCluster->GetTime() <= time_ns);
+
+            const BlockEntry* const pBE = pCluster->GetEntry(pTrack);
+
+            if ((pBE != 0) && !pBE->EOS())
+                return pBE;
+
+            //landed on empty cluster (no entries)
+        }
+
+        return pTrack->GetEOS();  //weird
+    }
+
+    assert(pTrack->GetType() == 1);  //video
+
+    Cluster** lo = i;
+    Cluster** hi = j;
+
+    while (lo < hi)
+    {
+        //INVARIANT:
+        //[i, lo) <= time_ns
+        //[lo, hi) ?
+        //[hi, j)  > time_ns
+
+        Cluster** const mid = lo + (hi - lo) / 2;
+        assert(mid < hi);
+
+        Cluster* const pCluster = *mid;
+        assert(pCluster);
+
+        const long long t = pCluster->GetTime();
+
+        if (t <= time_ns)
+            lo = mid + 1;
+        else
+            hi = mid;
+
+        assert(lo <= hi);
+    }
+
+    assert(lo == hi);
+    assert(lo > i);
+    assert(lo <= j);
+
+    Cluster* pCluster = *--lo;
+    assert(pCluster);
+    assert(pCluster->GetTime() <= time_ns);
+
+    {
+        const BlockEntry* const pBE = pCluster->GetEntry(pTrack, time_ns);
+
+        if ((pBE != 0) && !pBE->EOS())  //found a keyframe
+            return pBE;
+    }
+
+    const VideoTrack* const pVideo = static_cast<const VideoTrack*>(pTrack);
+
+    while (lo != i)
+    {
+        pCluster = *--lo;
+        assert(pCluster);
+        assert(pCluster->GetTime() <= time_ns);
+
+        const BlockEntry* const pBlockEntry = pCluster->GetMaxKey(pVideo);
+
+        if ((pBlockEntry != 0) && !pBlockEntry->EOS())
+            return pBlockEntry;
+    }
+
+    //weird: we're on the first cluster, but no keyframe found
+    //should never happen but we must return something anyway
+
+    return pTrack->GetEOS();
+}
+#endif
+
+
+#if 0
+bool Segment::SearchCues(
+    long long time_ns,
+    Track* pTrack,
+    Cluster*& pCluster,
+    const BlockEntry*& pBlockEntry,
+    const CuePoint*& pCP,
+    const CuePoint::TrackPosition*& pTP)
+{
+    if (pTrack->GetType() != 1)  //not video
+        return false;  //TODO: for now, just handle video stream
+
+    if (m_pCues == NULL)
+        return false;
+
+    if (!m_pCues->Find(time_ns, pTrack, pCP, pTP))
+        return false;  //weird
+
+    assert(pCP);
+    assert(pTP);
+    assert(pTP->m_track == pTrack->GetNumber());
+
+    //We have the cue point and track position we want,
+    //so we now need to search for the cluster having
+    //the indicated position.
+
+    return GetCluster(pCP, pTP, pCluster, pBlockEntry);
+}
+#endif
+
+
+const Tracks* Segment::GetTracks() const
+{
+    return m_pTracks;
+}
+
+
+const SegmentInfo* Segment::GetInfo() const
+{
+    return m_pInfo;
+}
+
+
+const Cues* Segment::GetCues() const
+{
+    return m_pCues;
+}
+
+
+const Chapters* Segment::GetChapters() const
+{
+  return m_pChapters;
+}
+
+
+const SeekHead* Segment::GetSeekHead() const
+{
+    return m_pSeekHead;
+}
+
+
+long long Segment::GetDuration() const
+{
+    assert(m_pInfo);
+    return m_pInfo->GetDuration();
+}
+
+
+Chapters::Chapters(
+    Segment* pSegment,
+    long long payload_start,
+    long long payload_size,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_start(payload_start),
+    m_size(payload_size),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    m_editions(NULL),
+    m_editions_size(0),
+    m_editions_count(0)
+{
+}
+
+
+Chapters::~Chapters()
+{
+    while (m_editions_count > 0)
+    {
+        Edition& e = m_editions[--m_editions_count];
+        e.Clear();
+    }
+}
+
+
+long Chapters::Parse()
+{
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long pos = m_start;  // payload start
+    const long long stop = pos + m_size;  // payload stop
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        long status = ParseElementHeader(
+                        pReader,
+                        pos,
+                        stop,
+                        id,
+                        size);
+
+        if (status < 0)  // error
+            return status;
+
+        if (size == 0)  // weird
+            continue;
+
+        if (id == 0x05B9)  // EditionEntry ID
+        {
+            status = ParseEdition(pos, size);
+
+            if (status < 0)  // error
+                return status;
+        }
+
+        pos += size;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+    return 0;
+}
+
+
+int Chapters::GetEditionCount() const
+{
+    return m_editions_count;
+}
+
+
+const Chapters::Edition* Chapters::GetEdition(int idx) const
+{
+    if (idx < 0)
+        return NULL;
+
+    if (idx >= m_editions_count)
+        return NULL;
+
+    return m_editions + idx;
+}
+
+
+bool Chapters::ExpandEditionsArray()
+{
+    if (m_editions_size > m_editions_count)
+        return true;  // nothing else to do
+
+    const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size;
+
+    Edition* const editions = new (std::nothrow) Edition[size];
+
+    if (editions == NULL)
+        return false;
+
+    for (int idx = 0; idx < m_editions_count; ++idx)
+    {
+        m_editions[idx].ShallowCopy(editions[idx]);
+    }
+
+    delete[] m_editions;
+    m_editions = editions;
+
+    m_editions_size = size;
+    return true;
+}
+
+
+long Chapters::ParseEdition(
+    long long pos,
+    long long size)
+{
+    if (!ExpandEditionsArray())
+        return -1;
+
+    Edition& e = m_editions[m_editions_count++];
+    e.Init();
+
+    return e.Parse(m_pSegment->m_pReader, pos, size);
+}
+
+
+Chapters::Edition::Edition()
+{
+}
+
+
+Chapters::Edition::~Edition()
+{
+}
+
+
+int Chapters::Edition::GetAtomCount() const
+{
+    return m_atoms_count;
+}
+
+
+const Chapters::Atom* Chapters::Edition::GetAtom(int index) const
+{
+    if (index < 0)
+        return NULL;
+
+    if (index >= m_atoms_count)
+        return NULL;
+
+    return m_atoms + index;
+}
+
+
+void Chapters::Edition::Init()
+{
+    m_atoms = NULL;
+    m_atoms_size = 0;
+    m_atoms_count = 0;
+}
+
+
+void Chapters::Edition::ShallowCopy(Edition& rhs) const
+{
+    rhs.m_atoms = m_atoms;
+    rhs.m_atoms_size = m_atoms_size;
+    rhs.m_atoms_count = m_atoms_count;
+}
+
+
+void Chapters::Edition::Clear()
+{
+    while (m_atoms_count > 0)
+    {
+        Atom& a = m_atoms[--m_atoms_count];
+        a.Clear();
+    }
+
+    delete[] m_atoms;
+    m_atoms = NULL;
+
+    m_atoms_size = 0;
+}
+
+
+long Chapters::Edition::Parse(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    const long long stop = pos + size;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        long status = ParseElementHeader(
+                        pReader,
+                        pos,
+                        stop,
+                        id,
+                        size);
+
+        if (status < 0)  // error
+            return status;
+
+        if (size == 0)  // weird
+            continue;
+
+        if (id == 0x36)  // Atom ID
+        {
+            status = ParseAtom(pReader, pos, size);
+
+            if (status < 0)  // error
+                return status;
+        }
+
+        pos += size;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+    return 0;
+}
+
+
+long Chapters::Edition::ParseAtom(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    if (!ExpandAtomsArray())
+        return -1;
+
+    Atom& a = m_atoms[m_atoms_count++];
+    a.Init();
+
+    return a.Parse(pReader, pos, size);
+}
+
+
+bool Chapters::Edition::ExpandAtomsArray()
+{
+    if (m_atoms_size > m_atoms_count)
+        return true;  // nothing else to do
+
+    const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size;
+
+    Atom* const atoms = new (std::nothrow) Atom[size];
+
+    if (atoms == NULL)
+        return false;
+
+    for (int idx = 0; idx < m_atoms_count; ++idx)
+    {
+        m_atoms[idx].ShallowCopy(atoms[idx]);
+    }
+
+    delete[] m_atoms;
+    m_atoms = atoms;
+
+    m_atoms_size = size;
+    return true;
+}
+
+
+Chapters::Atom::Atom()
+{
+}
+
+
+Chapters::Atom::~Atom()
+{
+}
+
+
+unsigned long long Chapters::Atom::GetUID() const
+{
+    return m_uid;
+}
+
+
+const char* Chapters::Atom::GetStringUID() const
+{
+    return m_string_uid;
+}
+
+
+long long Chapters::Atom::GetStartTimecode() const
+{
+    return m_start_timecode;
+}
+
+
+long long Chapters::Atom::GetStopTimecode() const
+{
+    return m_stop_timecode;
+}
+
+
+long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const
+{
+    return GetTime(pChapters, m_start_timecode);
+}
+
+
+long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const
+{
+    return GetTime(pChapters, m_stop_timecode);
+}
+
+
+int Chapters::Atom::GetDisplayCount() const
+{
+    return m_displays_count;
+}
+
+
+const Chapters::Display* Chapters::Atom::GetDisplay(int index) const
+{
+    if (index < 0)
+        return NULL;
+
+    if (index >= m_displays_count)
+        return NULL;
+
+    return m_displays + index;
+}
+
+
+void Chapters::Atom::Init()
+{
+    m_string_uid = NULL;
+    m_uid = 0;
+    m_start_timecode = -1;
+    m_stop_timecode = -1;
+
+    m_displays = NULL;
+    m_displays_size = 0;
+    m_displays_count = 0;
+}
+
+
+void Chapters::Atom::ShallowCopy(Atom& rhs) const
+{
+    rhs.m_string_uid = m_string_uid;
+    rhs.m_uid = m_uid;
+    rhs.m_start_timecode = m_start_timecode;
+    rhs.m_stop_timecode = m_stop_timecode;
+
+    rhs.m_displays = m_displays;
+    rhs.m_displays_size = m_displays_size;
+    rhs.m_displays_count = m_displays_count;
+}
+
+
+void Chapters::Atom::Clear()
+{
+    delete[] m_string_uid;
+    m_string_uid = NULL;
+
+    while (m_displays_count > 0)
+    {
+        Display& d = m_displays[--m_displays_count];
+        d.Clear();
+    }
+
+    delete[] m_displays;
+    m_displays = NULL;
+
+    m_displays_size = 0;
+}
+
+
+long Chapters::Atom::Parse(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    const long long stop = pos + size;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        long status = ParseElementHeader(
+                        pReader,
+                        pos,
+                        stop,
+                        id,
+                        size);
+
+        if (status < 0)  // error
+            return status;
+
+        if (size == 0)  // weird
+            continue;
+
+        if (id == 0x00)  // Display ID
+        {
+            status = ParseDisplay(pReader, pos, size);
+
+            if (status < 0)  // error
+                return status;
+        }
+        else if (id == 0x1654)  // StringUID ID
+        {
+            status = UnserializeString(pReader, pos, size, m_string_uid);
+
+            if (status < 0)  // error
+                return status;
+        }
+        else if (id == 0x33C4)  // UID ID
+        {
+            const long long val = UnserializeUInt(pReader, pos, size);
+
+            if (val < 0)  // error
+                return static_cast<long>(val);
+
+            m_uid = val;
+        }
+        else if (id == 0x11)  // TimeStart ID
+        {
+            const long long val = UnserializeUInt(pReader, pos, size);
+
+            if (val < 0)  // error
+                return static_cast<long>(val);
+
+            m_start_timecode = val;
+        }
+        else if (id == 0x12)  // TimeEnd ID
+        {
+            const long long val = UnserializeUInt(pReader, pos, size);
+
+            if (val < 0)  // error
+                return static_cast<long>(val);
+
+            m_stop_timecode = val;
+        }
+
+        pos += size;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+    return 0;
+}
+
+
+long long Chapters::Atom::GetTime(
+    const Chapters* pChapters,
+    long long timecode)
+{
+    if (pChapters == NULL)
+        return -1;
+
+    Segment* const pSegment = pChapters->m_pSegment;
+
+    if (pSegment == NULL)  // weird
+        return -1;
+
+    const SegmentInfo* const pInfo = pSegment->GetInfo();
+
+    if (pInfo == NULL)
+        return -1;
+
+    const long long timecode_scale = pInfo->GetTimeCodeScale();
+
+    if (timecode_scale < 1)  // weird
+        return -1;
+
+    if (timecode < 0)
+        return -1;
+
+    const long long result = timecode_scale * timecode;
+
+    return result;
+}
+
+
+long Chapters::Atom::ParseDisplay(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    if (!ExpandDisplaysArray())
+        return -1;
+
+    Display& d = m_displays[m_displays_count++];
+    d.Init();
+
+    return d.Parse(pReader, pos, size);
+}
+
+
+bool Chapters::Atom::ExpandDisplaysArray()
+{
+    if (m_displays_size > m_displays_count)
+        return true;  // nothing else to do
+
+    const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size;
+
+    Display* const displays = new (std::nothrow) Display[size];
+
+    if (displays == NULL)
+        return false;
+
+    for (int idx = 0; idx < m_displays_count; ++idx)
+    {
+        m_displays[idx].ShallowCopy(displays[idx]);
+    }
+
+    delete[] m_displays;
+    m_displays = displays;
+
+    m_displays_size = size;
+    return true;
+}
+
+
+Chapters::Display::Display()
+{
+}
+
+
+Chapters::Display::~Display()
+{
+}
+
+
+const char* Chapters::Display::GetString() const
+{
+    return m_string;
+}
+
+
+const char* Chapters::Display::GetLanguage() const
+{
+    return m_language;
+}
+
+
+const char* Chapters::Display::GetCountry() const
+{
+    return m_country;
+}
+
+
+void Chapters::Display::Init()
+{
+    m_string = NULL;
+    m_language = NULL;
+    m_country = NULL;
+}
+
+
+void Chapters::Display::ShallowCopy(Display& rhs) const
+{
+    rhs.m_string = m_string;
+    rhs.m_language = m_language;
+    rhs.m_country = m_country;
+}
+
+
+void Chapters::Display::Clear()
+{
+    delete[] m_string;
+    m_string = NULL;
+
+    delete[] m_language;
+    m_language = NULL;
+
+    delete[] m_country;
+    m_country = NULL;
+}
+
+
+long Chapters::Display::Parse(
+    IMkvReader* pReader,
+    long long pos,
+    long long size)
+{
+    const long long stop = pos + size;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        long status = ParseElementHeader(
+                        pReader,
+                        pos,
+                        stop,
+                        id,
+                        size);
+
+        if (status < 0)  // error
+            return status;
+
+        if (size == 0)  // weird
+            continue;
+
+        if (id == 0x05)  // ChapterString ID
+        {
+            status = UnserializeString(pReader, pos, size, m_string);
+
+            if (status)
+              return status;
+        }
+        else if (id == 0x037C)  // ChapterLanguage ID
+        {
+            status = UnserializeString(pReader, pos, size, m_language);
+
+            if (status)
+              return status;
+        }
+        else if (id == 0x037E)  // ChapterCountry ID
+        {
+            status = UnserializeString(pReader, pos, size, m_country);
+
+            if (status)
+              return status;
+        }
+
+        pos += size;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+    return 0;
+}
+
+
+SegmentInfo::SegmentInfo(
+    Segment* pSegment,
+    long long start,
+    long long size_,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_start(start),
+    m_size(size_),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    m_pMuxingAppAsUTF8(NULL),
+    m_pWritingAppAsUTF8(NULL),
+    m_pTitleAsUTF8(NULL)
+{
+}
+
+SegmentInfo::~SegmentInfo()
+{
+    delete[] m_pMuxingAppAsUTF8;
+    m_pMuxingAppAsUTF8 = NULL;
+
+    delete[] m_pWritingAppAsUTF8;
+    m_pWritingAppAsUTF8 = NULL;
+
+    delete[] m_pTitleAsUTF8;
+    m_pTitleAsUTF8 = NULL;
+}
+
+
+long SegmentInfo::Parse()
+{
+    assert(m_pMuxingAppAsUTF8 == NULL);
+    assert(m_pWritingAppAsUTF8 == NULL);
+    assert(m_pTitleAsUTF8 == NULL);
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long pos = m_start;
+    const long long stop = m_start + m_size;
+
+    m_timecodeScale = 1000000;
+    m_duration = -1;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (id == 0x0AD7B1)  //Timecode Scale
+        {
+            m_timecodeScale = UnserializeUInt(pReader, pos, size);
+
+            if (m_timecodeScale <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x0489)  //Segment duration
+        {
+            const long status = UnserializeFloat(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    m_duration);
+
+            if (status < 0)
+                return status;
+
+            if (m_duration < 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x0D80)  //MuxingApp
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    m_pMuxingAppAsUTF8);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x1741)  //WritingApp
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    m_pWritingAppAsUTF8);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x3BA9)  //Title
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    m_pTitleAsUTF8);
+
+            if (status)
+                return status;
+        }
+
+        pos += size;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    return 0;
+}
+
+
+long long SegmentInfo::GetTimeCodeScale() const
+{
+    return m_timecodeScale;
+}
+
+
+long long SegmentInfo::GetDuration() const
+{
+    if (m_duration < 0)
+        return -1;
+
+    assert(m_timecodeScale >= 1);
+
+    const double dd = double(m_duration) * double(m_timecodeScale);
+    const long long d = static_cast<long long>(dd);
+
+    return d;
+}
+
+const char* SegmentInfo::GetMuxingAppAsUTF8() const
+{
+    return m_pMuxingAppAsUTF8;
+}
+
+
+const char* SegmentInfo::GetWritingAppAsUTF8() const
+{
+    return m_pWritingAppAsUTF8;
+}
+
+const char* SegmentInfo::GetTitleAsUTF8() const
+{
+    return m_pTitleAsUTF8;
+}
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+ContentEncoding::ContentCompression::ContentCompression()
+    : algo(0),
+      settings(NULL),
+      settings_len(0) {
+}
+
+ContentEncoding::ContentCompression::~ContentCompression() {
+  delete [] settings;
+}
+
+ContentEncoding::ContentEncryption::ContentEncryption()
+    : algo(0),
+      key_id(NULL),
+      key_id_len(0),
+      signature(NULL),
+      signature_len(0),
+      sig_key_id(NULL),
+      sig_key_id_len(0),
+      sig_algo(0),
+      sig_hash_algo(0) {
+}
+
+ContentEncoding::ContentEncryption::~ContentEncryption() {
+  delete [] key_id;
+  delete [] signature;
+  delete [] sig_key_id;
+}
+
+ContentEncoding::ContentEncoding()
+    : compression_entries_(NULL),
+      compression_entries_end_(NULL),
+      encryption_entries_(NULL),
+      encryption_entries_end_(NULL),
+      encoding_order_(0),
+      encoding_scope_(1),
+      encoding_type_(0) {
+}
+
+ContentEncoding::~ContentEncoding() {
+  ContentCompression** comp_i = compression_entries_;
+  ContentCompression** const comp_j = compression_entries_end_;
+
+  while (comp_i != comp_j) {
+    ContentCompression* const comp = *comp_i++;
+    delete comp;
+  }
+
+  delete [] compression_entries_;
+
+  ContentEncryption** enc_i = encryption_entries_;
+  ContentEncryption** const enc_j = encryption_entries_end_;
+
+  while (enc_i != enc_j) {
+    ContentEncryption* const enc = *enc_i++;
+    delete enc;
+  }
+
+  delete [] encryption_entries_;
+}
+
+
+const ContentEncoding::ContentCompression*
+ContentEncoding::GetCompressionByIndex(unsigned long idx) const {
+  const ptrdiff_t count = compression_entries_end_ - compression_entries_;
+  assert(count >= 0);
+
+  if (idx >= static_cast<unsigned long>(count))
+    return NULL;
+
+  return compression_entries_[idx];
+}
+
+unsigned long ContentEncoding::GetCompressionCount() const {
+  const ptrdiff_t count = compression_entries_end_ - compression_entries_;
+  assert(count >= 0);
+
+  return static_cast<unsigned long>(count);
+}
+
+const ContentEncoding::ContentEncryption*
+ContentEncoding::GetEncryptionByIndex(unsigned long idx) const {
+  const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
+  assert(count >= 0);
+
+  if (idx >= static_cast<unsigned long>(count))
+    return NULL;
+
+  return encryption_entries_[idx];
+}
+
+unsigned long ContentEncoding::GetEncryptionCount() const {
+  const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
+  assert(count >= 0);
+
+  return static_cast<unsigned long>(count);
+}
+
+long ContentEncoding::ParseContentEncAESSettingsEntry(
+    long long start,
+    long long size,
+    IMkvReader* pReader,
+    ContentEncAESSettings* aes) {
+  assert(pReader);
+  assert(aes);
+
+  long long pos = start;
+  const long long stop = start + size;
+
+  while (pos < stop) {
+    long long id, size;
+    const long status = ParseElementHeader(pReader,
+                                           pos,
+                                           stop,
+                                           id,
+                                           size);
+    if (status < 0)  //error
+      return status;
+
+    if (id == 0x7E8) {
+      // AESSettingsCipherMode
+      aes->cipher_mode = UnserializeUInt(pReader, pos, size);
+      if (aes->cipher_mode != 1)
+        return E_FILE_FORMAT_INVALID;
+    }
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  return 0;
+}
+
+long ContentEncoding::ParseContentEncodingEntry(long long start,
+                                                long long size,
+                                                IMkvReader* pReader) {
+  assert(pReader);
+
+  long long pos = start;
+  const long long stop = start + size;
+
+  // Count ContentCompression and ContentEncryption elements.
+  int compression_count = 0;
+  int encryption_count = 0;
+
+  while (pos < stop) {
+    long long id, size;
+    const long status = ParseElementHeader(pReader,
+                                           pos,
+                                           stop,
+                                           id,
+                                           size);
+    if (status < 0)  //error
+      return status;
+
+    if (id == 0x1034)  // ContentCompression ID
+      ++compression_count;
+
+    if (id == 0x1035)  // ContentEncryption ID
+      ++encryption_count;
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  if (compression_count <= 0 && encryption_count <= 0)
+    return -1;
+
+  if (compression_count > 0) {
+    compression_entries_ =
+        new (std::nothrow) ContentCompression*[compression_count];
+    if (!compression_entries_)
+      return -1;
+    compression_entries_end_ = compression_entries_;
+  }
+
+  if (encryption_count > 0) {
+    encryption_entries_ =
+        new (std::nothrow) ContentEncryption*[encryption_count];
+    if (!encryption_entries_) {
+      delete [] compression_entries_;
+      return -1;
+    }
+    encryption_entries_end_ = encryption_entries_;
+  }
+
+  pos = start;
+  while (pos < stop) {
+    long long id, size;
+    long status = ParseElementHeader(pReader,
+                                     pos,
+                                     stop,
+                                     id,
+                                     size);
+    if (status < 0)  //error
+      return status;
+
+    if (id == 0x1031) {
+      // ContentEncodingOrder
+      encoding_order_ = UnserializeUInt(pReader, pos, size);
+    } else if (id == 0x1032) {
+      // ContentEncodingScope
+      encoding_scope_ = UnserializeUInt(pReader, pos, size);
+      if (encoding_scope_ < 1)
+        return -1;
+    } else if (id == 0x1033) {
+      // ContentEncodingType
+      encoding_type_ = UnserializeUInt(pReader, pos, size);
+    } else if (id == 0x1034) {
+      // ContentCompression ID
+      ContentCompression* const compression =
+        new (std::nothrow) ContentCompression();
+      if (!compression)
+        return -1;
+
+      status = ParseCompressionEntry(pos, size, pReader, compression);
+      if (status) {
+        delete compression;
+        return status;
+      }
+      *compression_entries_end_++ = compression;
+    } else if (id == 0x1035) {
+      // ContentEncryption ID
+      ContentEncryption* const encryption =
+          new (std::nothrow) ContentEncryption();
+      if (!encryption)
+        return -1;
+
+      status = ParseEncryptionEntry(pos, size, pReader, encryption);
+      if (status) {
+        delete encryption;
+        return status;
+      }
+      *encryption_entries_end_++ = encryption;
+    }
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  assert(pos == stop);
+  return 0;
+}
+
+long ContentEncoding::ParseCompressionEntry(
+    long long start,
+    long long size,
+    IMkvReader* pReader,
+    ContentCompression* compression) {
+  assert(pReader);
+  assert(compression);
+
+  long long pos = start;
+  const long long stop = start + size;
+
+  bool valid = false;
+
+  while (pos < stop) {
+    long long id, size;
+    const long status = ParseElementHeader(pReader,
+                                           pos,
+                                           stop,
+                                           id,
+                                           size);
+    if (status < 0)  //error
+      return status;
+
+    if (id == 0x254) {
+      // ContentCompAlgo
+      long long algo = UnserializeUInt(pReader, pos, size);
+      if (algo < 0)
+        return E_FILE_FORMAT_INVALID;
+      compression->algo = algo;
+      valid = true;
+    } else if (id == 0x255) {
+      // ContentCompSettings
+      if (size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      const size_t buflen = static_cast<size_t>(size);
+      typedef unsigned char* buf_t;
+      const buf_t buf = new (std::nothrow) unsigned char[buflen];
+      if (buf == NULL)
+        return -1;
+
+      const int read_status = pReader->Read(pos, buflen, buf);
+      if (read_status) {
+        delete [] buf;
+        return status;
+      }
+
+      compression->settings = buf;
+      compression->settings_len = buflen;
+    }
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  // ContentCompAlgo is mandatory
+  if (!valid)
+    return E_FILE_FORMAT_INVALID;
+
+  return 0;
+}
+
+long ContentEncoding::ParseEncryptionEntry(
+    long long start,
+    long long size,
+    IMkvReader* pReader,
+    ContentEncryption* encryption) {
+  assert(pReader);
+  assert(encryption);
+
+  long long pos = start;
+  const long long stop = start + size;
+
+  while (pos < stop) {
+    long long id, size;
+    const long status = ParseElementHeader(pReader,
+                                           pos,
+                                           stop,
+                                           id,
+                                           size);
+    if (status < 0)  //error
+      return status;
+
+    if (id == 0x7E1) {
+      // ContentEncAlgo
+      encryption->algo = UnserializeUInt(pReader, pos, size);
+      if (encryption->algo != 5)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == 0x7E2) {
+      // ContentEncKeyID
+      delete[] encryption->key_id;
+      encryption->key_id = NULL;
+      encryption->key_id_len = 0;
+
+      if (size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      const size_t buflen = static_cast<size_t>(size);
+      typedef unsigned char* buf_t;
+      const buf_t buf = new (std::nothrow) unsigned char[buflen];
+      if (buf == NULL)
+        return -1;
+
+      const int read_status = pReader->Read(pos, buflen, buf);
+      if (read_status) {
+        delete [] buf;
+        return status;
+      }
+
+      encryption->key_id = buf;
+      encryption->key_id_len = buflen;
+    } else if (id == 0x7E3) {
+      // ContentSignature
+      delete[] encryption->signature;
+      encryption->signature = NULL;
+      encryption->signature_len = 0;
+
+      if (size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      const size_t buflen = static_cast<size_t>(size);
+      typedef unsigned char* buf_t;
+      const buf_t buf = new (std::nothrow) unsigned char[buflen];
+      if (buf == NULL)
+        return -1;
+
+      const int read_status = pReader->Read(pos, buflen, buf);
+      if (read_status) {
+        delete [] buf;
+        return status;
+      }
+
+      encryption->signature = buf;
+      encryption->signature_len = buflen;
+    } else if (id == 0x7E4) {
+      // ContentSigKeyID
+      delete[] encryption->sig_key_id;
+      encryption->sig_key_id = NULL;
+      encryption->sig_key_id_len = 0;
+
+      if (size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      const size_t buflen = static_cast<size_t>(size);
+      typedef unsigned char* buf_t;
+      const buf_t buf = new (std::nothrow) unsigned char[buflen];
+      if (buf == NULL)
+        return -1;
+
+      const int read_status = pReader->Read(pos, buflen, buf);
+      if (read_status) {
+        delete [] buf;
+        return status;
+      }
+
+      encryption->sig_key_id = buf;
+      encryption->sig_key_id_len = buflen;
+    } else if (id == 0x7E5) {
+      // ContentSigAlgo
+      encryption->sig_algo = UnserializeUInt(pReader, pos, size);
+    } else if (id == 0x7E6) {
+      // ContentSigHashAlgo
+      encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size);
+    } else if (id == 0x7E7) {
+      // ContentEncAESSettings
+      const long status = ParseContentEncAESSettingsEntry(
+          pos,
+          size,
+          pReader,
+          &encryption->aes_settings);
+      if (status)
+        return status;
+    }
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  return 0;
+}
+
+Track::Track(
+    Segment* pSegment,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    content_encoding_entries_(NULL),
+    content_encoding_entries_end_(NULL)
+{
+}
+
+Track::~Track()
+{
+    Info& info = const_cast<Info&>(m_info);
+    info.Clear();
+
+    ContentEncoding** i = content_encoding_entries_;
+    ContentEncoding** const j = content_encoding_entries_end_;
+
+    while (i != j) {
+        ContentEncoding* const encoding = *i++;
+        delete encoding;
+    }
+
+    delete [] content_encoding_entries_;
+}
+
+long Track::Create(
+    Segment* pSegment,
+    const Info& info,
+    long long element_start,
+    long long element_size,
+    Track*& pResult)
+{
+    if (pResult)
+        return -1;
+
+    Track* const pTrack = new (std::nothrow) Track(pSegment,
+                                                   element_start,
+                                                   element_size);
+
+    if (pTrack == NULL)
+        return -1;  //generic error
+
+    const int status = info.Copy(pTrack->m_info);
+
+    if (status)  // error
+    {
+        delete pTrack;
+        return status;
+    }
+
+    pResult = pTrack;
+    return 0;  //success
+}
+
+Track::Info::Info():
+    uid(0),
+    defaultDuration(0),
+    codecDelay(0),
+    seekPreRoll(0),
+    nameAsUTF8(NULL),
+    language(NULL),
+    codecId(NULL),
+    codecNameAsUTF8(NULL),
+    codecPrivate(NULL),
+    codecPrivateSize(0),
+    lacing(false)
+{
+}
+
+Track::Info::~Info()
+{
+    Clear();
+}
+
+void Track::Info::Clear()
+{
+    delete[] nameAsUTF8;
+    nameAsUTF8 = NULL;
+
+    delete[] language;
+    language = NULL;
+
+    delete[] codecId;
+    codecId = NULL;
+
+    delete[] codecPrivate;
+    codecPrivate = NULL;
+    codecPrivateSize = 0;
+
+    delete[] codecNameAsUTF8;
+    codecNameAsUTF8 = NULL;
+}
+
+int Track::Info::CopyStr(char* Info::*str, Info& dst_) const
+{
+    if (str == static_cast<char* Info::*>(NULL))
+        return -1;
+
+    char*& dst = dst_.*str;
+
+    if (dst)  //should be NULL already
+        return -1;
+
+    const char* const src = this->*str;
+
+    if (src == NULL)
+        return 0;
+
+    const size_t len = strlen(src);
+
+    dst = new (std::nothrow) char[len+1];
+
+    if (dst == NULL)
+        return -1;
+
+    strcpy(dst, src);
+
+    return 0;
+}
+
+
+int Track::Info::Copy(Info& dst) const
+{
+    if (&dst == this)
+        return 0;
+
+    dst.type = type;
+    dst.number = number;
+    dst.defaultDuration = defaultDuration;
+    dst.codecDelay = codecDelay;
+    dst.seekPreRoll = seekPreRoll;
+    dst.uid = uid;
+    dst.lacing = lacing;
+    dst.settings = settings;
+
+    //We now copy the string member variables from src to dst.
+    //This involves memory allocation so in principle the operation
+    //can fail (indeed, that's why we have Info::Copy), so we must
+    //report this to the caller.  An error return from this function
+    //therefore implies that the copy was only partially successful.
+
+    if (int status = CopyStr(&Info::nameAsUTF8, dst))
+        return status;
+
+    if (int status = CopyStr(&Info::language, dst))
+        return status;
+
+    if (int status = CopyStr(&Info::codecId, dst))
+        return status;
+
+    if (int status = CopyStr(&Info::codecNameAsUTF8, dst))
+        return status;
+
+    if (codecPrivateSize > 0)
+    {
+        if (codecPrivate == NULL)
+            return -1;
+
+        if (dst.codecPrivate)
+            return -1;
+
+        if (dst.codecPrivateSize != 0)
+            return -1;
+
+        dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize];
+
+        if (dst.codecPrivate == NULL)
+            return -1;
+
+        memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize);
+        dst.codecPrivateSize = codecPrivateSize;
+    }
+
+    return 0;
+}
+
+const BlockEntry* Track::GetEOS() const
+{
+    return &m_eos;
+}
+
+long Track::GetType() const
+{
+    return m_info.type;
+}
+
+long Track::GetNumber() const
+{
+    return m_info.number;
+}
+
+unsigned long long Track::GetUid() const
+{
+    return m_info.uid;
+}
+
+const char* Track::GetNameAsUTF8() const
+{
+    return m_info.nameAsUTF8;
+}
+
+const char* Track::GetLanguage() const
+{
+    return m_info.language;
+}
+
+const char* Track::GetCodecNameAsUTF8() const
+{
+    return m_info.codecNameAsUTF8;
+}
+
+
+const char* Track::GetCodecId() const
+{
+    return m_info.codecId;
+}
+
+const unsigned char* Track::GetCodecPrivate(size_t& size) const
+{
+    size = m_info.codecPrivateSize;
+    return m_info.codecPrivate;
+}
+
+
+bool Track::GetLacing() const
+{
+    return m_info.lacing;
+}
+
+unsigned long long Track::GetDefaultDuration() const
+{
+    return m_info.defaultDuration;
+}
+
+unsigned long long Track::GetCodecDelay() const
+{
+    return m_info.codecDelay;
+}
+
+unsigned long long Track::GetSeekPreRoll() const
+{
+    return m_info.seekPreRoll;
+}
+
+long Track::GetFirst(const BlockEntry*& pBlockEntry) const
+{
+    const Cluster* pCluster = m_pSegment->GetFirst();
+
+    for (int i = 0; ; )
+    {
+        if (pCluster == NULL)
+        {
+            pBlockEntry = GetEOS();
+            return 1;
+        }
+
+        if (pCluster->EOS())
+        {
+#if 0
+            if (m_pSegment->Unparsed() <= 0)  //all clusters have been loaded
+            {
+                pBlockEntry = GetEOS();
+                return 1;
+            }
+#else
+            if (m_pSegment->DoneParsing())
+            {
+                pBlockEntry = GetEOS();
+                return 1;
+            }
+#endif
+
+            pBlockEntry = 0;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long status = pCluster->GetFirst(pBlockEntry);
+
+        if (status < 0)  //error
+            return status;
+
+        if (pBlockEntry == 0)  //empty cluster
+        {
+            pCluster = m_pSegment->GetNext(pCluster);
+            continue;
+        }
+
+        for (;;)
+        {
+            const Block* const pBlock = pBlockEntry->GetBlock();
+            assert(pBlock);
+
+            const long long tn = pBlock->GetTrackNumber();
+
+            if ((tn == m_info.number) && VetEntry(pBlockEntry))
+                return 0;
+
+            const BlockEntry* pNextEntry;
+
+            status = pCluster->GetNext(pBlockEntry, pNextEntry);
+
+            if (status < 0)  //error
+                return status;
+
+            if (pNextEntry == 0)
+                break;
+
+            pBlockEntry = pNextEntry;
+        }
+
+        ++i;
+
+        if (i >= 100)
+            break;
+
+        pCluster = m_pSegment->GetNext(pCluster);
+    }
+
+    //NOTE: if we get here, it means that we didn't find a block with
+    //a matching track number.  We interpret that as an error (which
+    //might be too conservative).
+
+    pBlockEntry = GetEOS();  //so we can return a non-NULL value
+    return 1;
+}
+
+
+long Track::GetNext(
+    const BlockEntry* pCurrEntry,
+    const BlockEntry*& pNextEntry) const
+{
+    assert(pCurrEntry);
+    assert(!pCurrEntry->EOS());  //?
+
+    const Block* const pCurrBlock = pCurrEntry->GetBlock();
+    assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number);
+    if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number)
+        return -1;
+
+    const Cluster* pCluster = pCurrEntry->GetCluster();
+    assert(pCluster);
+    assert(!pCluster->EOS());
+
+    long status = pCluster->GetNext(pCurrEntry, pNextEntry);
+
+    if (status < 0)  //error
+        return status;
+
+    for (int i = 0; ; )
+    {
+        while (pNextEntry)
+        {
+            const Block* const pNextBlock = pNextEntry->GetBlock();
+            assert(pNextBlock);
+
+            if (pNextBlock->GetTrackNumber() == m_info.number)
+                return 0;
+
+            pCurrEntry = pNextEntry;
+
+            status = pCluster->GetNext(pCurrEntry, pNextEntry);
+
+            if (status < 0) //error
+                return status;
+        }
+
+        pCluster = m_pSegment->GetNext(pCluster);
+
+        if (pCluster == NULL)
+        {
+            pNextEntry = GetEOS();
+            return 1;
+        }
+
+        if (pCluster->EOS())
+        {
+#if 0
+            if (m_pSegment->Unparsed() <= 0)   //all clusters have been loaded
+            {
+                pNextEntry = GetEOS();
+                return 1;
+            }
+#else
+            if (m_pSegment->DoneParsing())
+            {
+                pNextEntry = GetEOS();
+                return 1;
+            }
+#endif
+
+            //TODO: there is a potential O(n^2) problem here: we tell the
+            //caller to (pre)load another cluster, which he does, but then he
+            //calls GetNext again, which repeats the same search.  This is
+            //a pathological case, since the only way it can happen is if
+            //there exists a long sequence of clusters none of which contain a
+            // block from this track.  One way around this problem is for the
+            //caller to be smarter when he loads another cluster: don't call
+            //us back until you have a cluster that contains a block from this
+            //track. (Of course, that's not cheap either, since our caller
+            //would have to scan the each cluster as it's loaded, so that
+            //would just push back the problem.)
+
+            pNextEntry = NULL;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        status = pCluster->GetFirst(pNextEntry);
+
+        if (status < 0)  //error
+            return status;
+
+        if (pNextEntry == NULL)  //empty cluster
+            continue;
+
+        ++i;
+
+        if (i >= 100)
+            break;
+    }
+
+    //NOTE: if we get here, it means that we didn't find a block with
+    //a matching track number after lots of searching, so we give
+    //up trying.
+
+    pNextEntry = GetEOS();  //so we can return a non-NULL value
+    return 1;
+}
+
+bool Track::VetEntry(const BlockEntry* pBlockEntry) const
+{
+    assert(pBlockEntry);
+    const Block* const pBlock = pBlockEntry->GetBlock();
+    assert(pBlock);
+    assert(pBlock->GetTrackNumber() == m_info.number);
+    if (!pBlock || pBlock->GetTrackNumber() != m_info.number)
+        return false;
+
+    // This function is used during a seek to determine whether the
+    // frame is a valid seek target.  This default function simply
+    // returns true, which means all frames are valid seek targets.
+    // It gets overridden by the VideoTrack class, because only video
+    // keyframes can be used as seek target.
+
+    return true;
+}
+
+long Track::Seek(
+    long long time_ns,
+    const BlockEntry*& pResult) const
+{
+    const long status = GetFirst(pResult);
+
+    if (status < 0)  //buffer underflow, etc
+        return status;
+
+    assert(pResult);
+
+    if (pResult->EOS())
+        return 0;
+
+    const Cluster* pCluster = pResult->GetCluster();
+    assert(pCluster);
+    assert(pCluster->GetIndex() >= 0);
+
+    if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
+        return 0;
+
+    Cluster** const clusters = m_pSegment->m_clusters;
+    assert(clusters);
+
+    const long count = m_pSegment->GetCount();  //loaded only, not preloaded
+    assert(count > 0);
+
+    Cluster** const i = clusters + pCluster->GetIndex();
+    assert(i);
+    assert(*i == pCluster);
+    assert(pCluster->GetTime() <= time_ns);
+
+    Cluster** const j = clusters + count;
+
+    Cluster** lo = i;
+    Cluster** hi = j;
+
+    while (lo < hi)
+    {
+        //INVARIANT:
+        //[i, lo) <= time_ns
+        //[lo, hi) ?
+        //[hi, j)  > time_ns
+
+        Cluster** const mid = lo + (hi - lo) / 2;
+        assert(mid < hi);
+
+        pCluster = *mid;
+        assert(pCluster);
+        assert(pCluster->GetIndex() >= 0);
+        assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
+
+        const long long t = pCluster->GetTime();
+
+        if (t <= time_ns)
+            lo = mid + 1;
+        else
+            hi = mid;
+
+        assert(lo <= hi);
+    }
+
+    assert(lo == hi);
+    assert(lo > i);
+    assert(lo <= j);
+
+    while (lo > i)
+    {
+        pCluster = *--lo;
+        assert(pCluster);
+        assert(pCluster->GetTime() <= time_ns);
+
+        pResult = pCluster->GetEntry(this);
+
+        if ((pResult != 0) && !pResult->EOS())
+            return 0;
+
+        //landed on empty cluster (no entries)
+    }
+
+    pResult = GetEOS();  //weird
+    return 0;
+}
+
+const ContentEncoding*
+Track::GetContentEncodingByIndex(unsigned long idx) const {
+  const ptrdiff_t count =
+      content_encoding_entries_end_ - content_encoding_entries_;
+  assert(count >= 0);
+
+  if (idx >= static_cast<unsigned long>(count))
+    return NULL;
+
+  return content_encoding_entries_[idx];
+}
+
+unsigned long Track::GetContentEncodingCount() const {
+  const ptrdiff_t count =
+      content_encoding_entries_end_ - content_encoding_entries_;
+  assert(count >= 0);
+
+  return static_cast<unsigned long>(count);
+}
+
+long Track::ParseContentEncodingsEntry(long long start, long long size) {
+  IMkvReader* const pReader = m_pSegment->m_pReader;
+  assert(pReader);
+
+  long long pos = start;
+  const long long stop = start + size;
+
+  // Count ContentEncoding elements.
+  int count = 0;
+  while (pos < stop) {
+    long long id, size;
+    const long status = ParseElementHeader(pReader,
+                                           pos,
+                                           stop,
+                                           id,
+                                           size);
+    if (status < 0)  //error
+      return status;
+
+
+    //pos now designates start of element
+    if (id == 0x2240)  // ContentEncoding ID
+      ++count;
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  if (count <= 0)
+    return -1;
+
+  content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count];
+  if (!content_encoding_entries_)
+    return -1;
+
+  content_encoding_entries_end_ = content_encoding_entries_;
+
+  pos = start;
+  while (pos < stop) {
+    long long id, size;
+    long status = ParseElementHeader(pReader,
+                                     pos,
+                                     stop,
+                                     id,
+                                     size);
+    if (status < 0)  //error
+      return status;
+
+    //pos now designates start of element
+    if (id == 0x2240) { // ContentEncoding ID
+      ContentEncoding* const content_encoding =
+          new (std::nothrow) ContentEncoding();
+      if (!content_encoding)
+        return -1;
+
+      status = content_encoding->ParseContentEncodingEntry(pos,
+                                                           size,
+                                                           pReader);
+      if (status) {
+        delete content_encoding;
+        return status;
+      }
+
+      *content_encoding_entries_end_++ = content_encoding;
+    }
+
+    pos += size;  //consume payload
+    assert(pos <= stop);
+  }
+
+  assert(pos == stop);
+
+  return 0;
+}
+
+Track::EOSBlock::EOSBlock() :
+    BlockEntry(NULL, LONG_MIN)
+{
+}
+
+BlockEntry::Kind Track::EOSBlock::GetKind() const
+{
+    return kBlockEOS;
+}
+
+
+const Block* Track::EOSBlock::GetBlock() const
+{
+    return NULL;
+}
+
+
+VideoTrack::VideoTrack(
+    Segment* pSegment,
+    long long element_start,
+    long long element_size) :
+    Track(pSegment, element_start, element_size)
+{
+}
+
+
+long VideoTrack::Parse(
+    Segment* pSegment,
+    const Info& info,
+    long long element_start,
+    long long element_size,
+    VideoTrack*& pResult)
+{
+    if (pResult)
+        return -1;
+
+    if (info.type != Track::kVideo)
+        return -1;
+
+    long long width = 0;
+    long long height = 0;
+    double rate = 0.0;
+
+    IMkvReader* const pReader = pSegment->m_pReader;
+
+    const Settings& s = info.settings;
+    assert(s.start >= 0);
+    assert(s.size >= 0);
+
+    long long pos = s.start;
+    assert(pos >= 0);
+
+    const long long stop = pos + s.size;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (id == 0x30)  //pixel width
+        {
+            width = UnserializeUInt(pReader, pos, size);
+
+            if (width <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x3A)  //pixel height
+        {
+            height = UnserializeUInt(pReader, pos, size);
+
+            if (height <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x0383E3)  //frame rate
+        {
+            const long status = UnserializeFloat(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    rate);
+
+            if (status < 0)
+                return status;
+
+            if (rate <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    VideoTrack* const pTrack = new (std::nothrow) VideoTrack(pSegment,
+                                                             element_start,
+                                                             element_size);
+
+    if (pTrack == NULL)
+        return -1;  //generic error
+
+    const int status = info.Copy(pTrack->m_info);
+
+    if (status)  // error
+    {
+        delete pTrack;
+        return status;
+    }
+
+    pTrack->m_width = width;
+    pTrack->m_height = height;
+    pTrack->m_rate = rate;
+
+    pResult = pTrack;
+    return 0;  //success
+}
+
+
+bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const
+{
+    return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey();
+}
+
+long VideoTrack::Seek(
+    long long time_ns,
+    const BlockEntry*& pResult) const
+{
+    const long status = GetFirst(pResult);
+
+    if (status < 0)  //buffer underflow, etc
+        return status;
+
+    assert(pResult);
+
+    if (pResult->EOS())
+        return 0;
+
+    const Cluster* pCluster = pResult->GetCluster();
+    assert(pCluster);
+    assert(pCluster->GetIndex() >= 0);
+
+    if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
+        return 0;
+
+    Cluster** const clusters = m_pSegment->m_clusters;
+    assert(clusters);
+
+    const long count = m_pSegment->GetCount();  //loaded only, not pre-loaded
+    assert(count > 0);
+
+    Cluster** const i = clusters + pCluster->GetIndex();
+    assert(i);
+    assert(*i == pCluster);
+    assert(pCluster->GetTime() <= time_ns);
+
+    Cluster** const j = clusters + count;
+
+    Cluster** lo = i;
+    Cluster** hi = j;
+
+    while (lo < hi)
+    {
+        //INVARIANT:
+        //[i, lo) <= time_ns
+        //[lo, hi) ?
+        //[hi, j)  > time_ns
+
+        Cluster** const mid = lo + (hi - lo) / 2;
+        assert(mid < hi);
+
+        pCluster = *mid;
+        assert(pCluster);
+        assert(pCluster->GetIndex() >= 0);
+        assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
+
+        const long long t = pCluster->GetTime();
+
+        if (t <= time_ns)
+            lo = mid + 1;
+        else
+            hi = mid;
+
+        assert(lo <= hi);
+    }
+
+    assert(lo == hi);
+    assert(lo > i);
+    assert(lo <= j);
+
+    pCluster = *--lo;
+    assert(pCluster);
+    assert(pCluster->GetTime() <= time_ns);
+
+    pResult = pCluster->GetEntry(this, time_ns);
+
+    if ((pResult != 0) && !pResult->EOS())  //found a keyframe
+        return 0;
+
+    while (lo != i)
+    {
+        pCluster = *--lo;
+        assert(pCluster);
+        assert(pCluster->GetTime() <= time_ns);
+
+#if 0
+        //TODO:
+        //We need to handle the case when a cluster
+        //contains multiple keyframes.  Simply returning
+        //the largest keyframe on the cluster isn't
+        //good enough.
+        pResult = pCluster->GetMaxKey(this);
+#else
+        pResult = pCluster->GetEntry(this, time_ns);
+#endif
+
+        if ((pResult != 0) && !pResult->EOS())
+            return 0;
+    }
+
+    //weird: we're on the first cluster, but no keyframe found
+    //should never happen but we must return something anyway
+
+    pResult = GetEOS();
+    return 0;
+}
+
+
+long long VideoTrack::GetWidth() const
+{
+    return m_width;
+}
+
+
+long long VideoTrack::GetHeight() const
+{
+    return m_height;
+}
+
+
+double VideoTrack::GetFrameRate() const
+{
+    return m_rate;
+}
+
+
+AudioTrack::AudioTrack(
+    Segment* pSegment,
+    long long element_start,
+    long long element_size) :
+    Track(pSegment, element_start, element_size)
+{
+}
+
+
+long AudioTrack::Parse(
+    Segment* pSegment,
+    const Info& info,
+    long long element_start,
+    long long element_size,
+    AudioTrack*& pResult)
+{
+    if (pResult)
+        return -1;
+
+    if (info.type != Track::kAudio)
+        return -1;
+
+    IMkvReader* const pReader = pSegment->m_pReader;
+
+    const Settings& s = info.settings;
+    assert(s.start >= 0);
+    assert(s.size >= 0);
+
+    long long pos = s.start;
+    assert(pos >= 0);
+
+    const long long stop = pos + s.size;
+
+    double rate = 8000.0;  // MKV default
+    long long channels = 1;
+    long long bit_depth = 0;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (id == 0x35)  //Sample Rate
+        {
+            status = UnserializeFloat(pReader, pos, size, rate);
+
+            if (status < 0)
+                return status;
+
+            if (rate <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x1F)  //Channel Count
+        {
+            channels = UnserializeUInt(pReader, pos, size);
+
+            if (channels <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x2264)  //Bit Depth
+        {
+            bit_depth = UnserializeUInt(pReader, pos, size);
+
+            if (bit_depth <= 0)
+                return E_FILE_FORMAT_INVALID;
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    AudioTrack* const pTrack = new (std::nothrow) AudioTrack(pSegment,
+                                                             element_start,
+                                                             element_size);
+
+    if (pTrack == NULL)
+        return -1;  //generic error
+
+    const int status = info.Copy(pTrack->m_info);
+
+    if (status)
+    {
+        delete pTrack;
+        return status;
+    }
+
+    pTrack->m_rate = rate;
+    pTrack->m_channels = channels;
+    pTrack->m_bitDepth = bit_depth;
+
+    pResult = pTrack;
+    return 0;  //success
+}
+
+
+double AudioTrack::GetSamplingRate() const
+{
+    return m_rate;
+}
+
+
+long long AudioTrack::GetChannels() const
+{
+    return m_channels;
+}
+
+long long AudioTrack::GetBitDepth() const
+{
+    return m_bitDepth;
+}
+
+Tracks::Tracks(
+    Segment* pSegment,
+    long long start,
+    long long size_,
+    long long element_start,
+    long long element_size) :
+    m_pSegment(pSegment),
+    m_start(start),
+    m_size(size_),
+    m_element_start(element_start),
+    m_element_size(element_size),
+    m_trackEntries(NULL),
+    m_trackEntriesEnd(NULL)
+{
+}
+
+
+long Tracks::Parse()
+{
+    assert(m_trackEntries == NULL);
+    assert(m_trackEntriesEnd == NULL);
+
+    const long long stop = m_start + m_size;
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    int count = 0;
+    long long pos = m_start;
+
+    while (pos < stop)
+    {
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (size == 0)  //weird
+            continue;
+
+        if (id == 0x2E)  //TrackEntry ID
+            ++count;
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    if (count <= 0)
+        return 0;  //success
+
+    m_trackEntries = new (std::nothrow) Track*[count];
+
+    if (m_trackEntries == NULL)
+        return -1;
+
+    m_trackEntriesEnd = m_trackEntries;
+
+    pos = m_start;
+
+    while (pos < stop)
+    {
+        const long long element_start = pos;
+
+        long long id, payload_size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                stop,
+                                id,
+                                payload_size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (payload_size == 0)  //weird
+            continue;
+
+        const long long payload_stop = pos + payload_size;
+        assert(payload_stop <= stop);  //checked in ParseElement
+
+        const long long element_size = payload_stop - element_start;
+
+        if (id == 0x2E)  //TrackEntry ID
+        {
+            Track*& pTrack = *m_trackEntriesEnd;
+            pTrack = NULL;
+
+            const long status = ParseTrackEntry(
+                                    pos,
+                                    payload_size,
+                                    element_start,
+                                    element_size,
+                                    pTrack);
+
+            if (status)
+                return status;
+
+            if (pTrack)
+                ++m_trackEntriesEnd;
+        }
+
+        pos = payload_stop;
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+
+    return 0;  //success
+}
+
+
+unsigned long Tracks::GetTracksCount() const
+{
+    const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries;
+    assert(result >= 0);
+
+    return static_cast<unsigned long>(result);
+}
+
+long Tracks::ParseTrackEntry(
+    long long track_start,
+    long long track_size,
+    long long element_start,
+    long long element_size,
+    Track*& pResult) const
+{
+    if (pResult)
+        return -1;
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long pos = track_start;
+    const long long track_stop = track_start + track_size;
+
+    Track::Info info;
+
+    info.type = 0;
+    info.number = 0;
+    info.uid = 0;
+    info.defaultDuration = 0;
+
+    Track::Settings v;
+    v.start = -1;
+    v.size = -1;
+
+    Track::Settings a;
+    a.start = -1;
+    a.size = -1;
+
+    Track::Settings e;  //content_encodings_settings;
+    e.start = -1;
+    e.size = -1;
+
+    long long lacing = 1;  //default is true
+
+    while (pos < track_stop)
+    {
+        long long id, size;
+
+        const long status = ParseElementHeader(
+                                pReader,
+                                pos,
+                                track_stop,
+                                id,
+                                size);
+
+        if (status < 0)  //error
+            return status;
+
+        if (size < 0)
+            return E_FILE_FORMAT_INVALID;
+
+        const long long start = pos;
+
+        if (id == 0x60)  // VideoSettings ID
+        {
+            v.start = start;
+            v.size = size;
+        }
+        else if (id == 0x61)  // AudioSettings ID
+        {
+            a.start = start;
+            a.size = size;
+        }
+        else if (id == 0x2D80) // ContentEncodings ID
+        {
+            e.start = start;
+            e.size = size;
+        }
+        else if (id == 0x33C5)  //Track UID
+        {
+            if (size > 8)
+                return E_FILE_FORMAT_INVALID;
+
+            info.uid = 0;
+
+            long long pos_ = start;
+            const long long pos_end = start + size;
+
+            while (pos_ != pos_end)
+            {
+                unsigned char b;
+
+                const int status = pReader->Read(pos_, 1, &b);
+
+                if (status)
+                    return status;
+
+                info.uid <<= 8;
+                info.uid |= b;
+
+                ++pos_;
+            }
+        }
+        else if (id == 0x57)  //Track Number
+        {
+            const long long num = UnserializeUInt(pReader, pos, size);
+
+            if ((num <= 0) || (num > 127))
+                return E_FILE_FORMAT_INVALID;
+
+            info.number = static_cast<long>(num);
+        }
+        else if (id == 0x03)  //Track Type
+        {
+            const long long type = UnserializeUInt(pReader, pos, size);
+
+            if ((type <= 0) || (type > 254))
+                return E_FILE_FORMAT_INVALID;
+
+            info.type = static_cast<long>(type);
+        }
+        else if (id == 0x136E)  //Track Name
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    info.nameAsUTF8);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x02B59C)  //Track Language
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    info.language);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x03E383)  //Default Duration
+        {
+            const long long duration = UnserializeUInt(pReader, pos, size);
+
+            if (duration < 0)
+                return E_FILE_FORMAT_INVALID;
+
+            info.defaultDuration = static_cast<unsigned long long>(duration);
+        }
+        else if (id == 0x06)  //CodecID
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    info.codecId);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x1C)  //lacing
+        {
+            lacing = UnserializeUInt(pReader, pos, size);
+
+            if ((lacing < 0) || (lacing > 1))
+                return E_FILE_FORMAT_INVALID;
+        }
+        else if (id == 0x23A2)  //Codec Private
+        {
+            delete[] info.codecPrivate;
+            info.codecPrivate = NULL;
+            info.codecPrivateSize = 0;
+
+            const size_t buflen = static_cast<size_t>(size);
+
+            if (buflen)
+            {
+                typedef unsigned char* buf_t;
+
+                const buf_t buf = new (std::nothrow) unsigned char[buflen];
+
+                if (buf == NULL)
+                    return -1;
+
+                const int status = pReader->Read(pos, buflen, buf);
+
+                if (status)
+                {
+                    delete[] buf;
+                    return status;
+                }
+
+                info.codecPrivate = buf;
+                info.codecPrivateSize = buflen;
+            }
+        }
+        else if (id == 0x058688)  //Codec Name
+        {
+            const long status = UnserializeString(
+                                    pReader,
+                                    pos,
+                                    size,
+                                    info.codecNameAsUTF8);
+
+            if (status)
+                return status;
+        }
+        else if (id == 0x16AA)  //Codec Delay
+        {
+            info.codecDelay = UnserializeUInt(pReader, pos, size);
+
+        }
+        else if (id == 0x16BB) //Seek Pre Roll
+        {
+            info.seekPreRoll = UnserializeUInt(pReader, pos, size);
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= track_stop);
+    }
+
+    assert(pos == track_stop);
+
+    if (info.number <= 0)  //not specified
+        return E_FILE_FORMAT_INVALID;
+
+    if (GetTrackByNumber(info.number))
+        return E_FILE_FORMAT_INVALID;
+
+    if (info.type <= 0)  //not specified
+        return E_FILE_FORMAT_INVALID;
+
+    info.lacing = (lacing > 0) ? true : false;
+
+    if (info.type == Track::kVideo)
+    {
+        if (v.start < 0)
+            return E_FILE_FORMAT_INVALID;
+
+        if (a.start >= 0)
+            return E_FILE_FORMAT_INVALID;
+
+        info.settings = v;
+
+        VideoTrack* pTrack = NULL;
+
+        const long status = VideoTrack::Parse(m_pSegment,
+                                              info,
+                                              element_start,
+                                              element_size,
+                                              pTrack);
+
+        if (status)
+            return status;
+
+        pResult = pTrack;
+        assert(pResult);
+
+        if (e.start >= 0)
+            pResult->ParseContentEncodingsEntry(e.start, e.size);
+    }
+    else if (info.type == Track::kAudio)
+    {
+        if (a.start < 0)
+            return E_FILE_FORMAT_INVALID;
+
+        if (v.start >= 0)
+            return E_FILE_FORMAT_INVALID;
+
+        info.settings = a;
+
+        AudioTrack* pTrack = NULL;
+
+        const long status = AudioTrack::Parse(m_pSegment,
+                                              info,
+                                              element_start,
+                                              element_size,
+                                              pTrack);
+
+        if (status)
+            return status;
+
+        pResult = pTrack;
+        assert(pResult);
+
+        if (e.start >= 0)
+            pResult->ParseContentEncodingsEntry(e.start, e.size);
+    }
+    else
+    {
+        // neither video nor audio - probably metadata or subtitles
+
+        if (a.start >= 0)
+            return E_FILE_FORMAT_INVALID;
+
+        if (v.start >= 0)
+            return E_FILE_FORMAT_INVALID;
+
+        if (e.start >= 0)
+            return E_FILE_FORMAT_INVALID;
+
+        info.settings.start = -1;
+        info.settings.size = 0;
+
+        Track* pTrack = NULL;
+
+        const long status = Track::Create(m_pSegment,
+                                          info,
+                                          element_start,
+                                          element_size,
+                                          pTrack);
+
+        if (status)
+            return status;
+
+        pResult = pTrack;
+        assert(pResult);
+    }
+
+    return 0;  //success
+}
+
+
+Tracks::~Tracks()
+{
+    Track** i = m_trackEntries;
+    Track** const j = m_trackEntriesEnd;
+
+    while (i != j)
+    {
+        Track* const pTrack = *i++;
+        delete pTrack;
+    }
+
+    delete[] m_trackEntries;
+}
+
+const Track* Tracks::GetTrackByNumber(long tn) const
+{
+    if (tn < 0)
+        return NULL;
+
+    Track** i = m_trackEntries;
+    Track** const j = m_trackEntriesEnd;
+
+    while (i != j)
+    {
+        Track* const pTrack = *i++;
+
+        if (pTrack == NULL)
+            continue;
+
+        if (tn == pTrack->GetNumber())
+            return pTrack;
+    }
+
+    return NULL;  //not found
+}
+
+
+const Track* Tracks::GetTrackByIndex(unsigned long idx) const
+{
+    const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries;
+
+    if (idx >= static_cast<unsigned long>(count))
+         return NULL;
+
+    return m_trackEntries[idx];
+}
+
+#if 0
+long long Cluster::Unparsed() const
+{
+    if (m_timecode < 0)  //not even partially loaded
+        return LLONG_MAX;
+
+    assert(m_pos >= m_element_start);
+    //assert(m_element_size > m_size);
+
+    const long long element_stop = m_element_start + m_element_size;
+    assert(m_pos <= element_stop);
+
+    const long long result = element_stop - m_pos;
+    assert(result >= 0);
+
+    return result;
+}
+#endif
+
+
+long Cluster::Load(long long& pos, long& len) const
+{
+    assert(m_pSegment);
+    assert(m_pos >= m_element_start);
+
+    if (m_timecode >= 0)  //at least partially loaded
+        return 0;
+
+    assert(m_pos == m_element_start);
+    assert(m_element_size < 0);
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long total, avail;
+
+    const int status = pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+    assert((total < 0) || (m_pos <= total));  //TODO: verify this
+
+    pos = m_pos;
+
+    long long cluster_size = -1;
+
+    {
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error or underflow
+            return static_cast<long>(result);
+
+        if (result > 0)  //underflow (weird)
+            return E_BUFFER_NOT_FULL;
+
+        //if ((pos + len) > segment_stop)
+        //    return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id_ = ReadUInt(pReader, pos, len);
+
+        if (id_ < 0)  //error
+            return static_cast<long>(id_);
+
+        if (id_ != 0x0F43B675)  //Cluster ID
+            return E_FILE_FORMAT_INVALID;
+
+        pos += len;  //consume id
+
+        //read cluster size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        //if ((pos + len) > segment_stop)
+        //    return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(cluster_size);
+
+        if (size == 0)
+            return E_FILE_FORMAT_INVALID;  //TODO: verify this
+
+        pos += len;  //consume length of size of element
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size != unknown_size)
+            cluster_size = size;
+    }
+
+    //pos points to start of payload
+
+#if 0
+    len = static_cast<long>(size_);
+
+    if (cluster_stop > avail)
+        return E_BUFFER_NOT_FULL;
+#endif
+
+    long long timecode = -1;
+    long long new_pos = -1;
+    bool bBlock = false;
+
+    long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size;
+
+    for (;;)
+    {
+        if ((cluster_stop >= 0) && (pos >= cluster_stop))
+            break;
+
+        //Parse ID
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0) //error
+            return static_cast<long>(id);
+
+        if (id == 0)
+            return E_FILE_FORMAT_INVALID;
+
+        //This is the distinguished set of ID's we use to determine
+        //that we have exhausted the sub-element's inside the cluster
+        //whose ID we parsed earlier.
+
+        if (id == 0x0F43B675)  //Cluster ID
+            break;
+
+        if (id == 0x0C53BB6B)  //Cues ID
+            break;
+
+        pos += len;  //consume ID field
+
+        //Parse Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;
+
+        pos += len;  //consume size field
+
+        if ((cluster_stop >= 0) && (pos > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        //pos now points to start of payload
+
+        if (size == 0)  //weird
+            continue;
+
+        if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if (id == 0x67)  //TimeCode ID
+        {
+            len = static_cast<long>(size);
+
+            if ((pos + size) > avail)
+                return E_BUFFER_NOT_FULL;
+
+            timecode = UnserializeUInt(pReader, pos, size);
+
+            if (timecode < 0)  //error (or underflow)
+                return static_cast<long>(timecode);
+
+            new_pos = pos + size;
+
+            if (bBlock)
+                break;
+        }
+        else if (id == 0x20)  //BlockGroup ID
+        {
+            bBlock = true;
+            break;
+        }
+        else if (id == 0x23)  //SimpleBlock ID
+        {
+            bBlock = true;
+            break;
+        }
+
+        pos += size;  //consume payload
+        assert((cluster_stop < 0) || (pos <= cluster_stop));
+    }
+
+    assert((cluster_stop < 0) || (pos <= cluster_stop));
+
+    if (timecode < 0)  //no timecode found
+        return E_FILE_FORMAT_INVALID;
+
+    if (!bBlock)
+        return E_FILE_FORMAT_INVALID;
+
+    m_pos = new_pos;  //designates position just beyond timecode payload
+    m_timecode = timecode;  // m_timecode >= 0 means we're partially loaded
+
+    if (cluster_size >= 0)
+        m_element_size = cluster_stop - m_element_start;
+
+    return 0;
+}
+
+
+long Cluster::Parse(long long& pos, long& len) const
+{
+    long status = Load(pos, len);
+
+    if (status < 0)
+        return status;
+
+    assert(m_pos >= m_element_start);
+    assert(m_timecode >= 0);
+    //assert(m_size > 0);
+    //assert(m_element_size > m_size);
+
+    const long long cluster_stop =
+        (m_element_size < 0) ? -1 : m_element_start + m_element_size;
+
+    if ((cluster_stop >= 0) && (m_pos >= cluster_stop))
+        return 1;  //nothing else to do
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long total, avail;
+
+    status = pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    pos = m_pos;
+
+    for (;;)
+    {
+        if ((cluster_stop >= 0) && (pos >= cluster_stop))
+            break;
+
+        if ((total >= 0) && (pos >= total))
+        {
+            if (m_element_size < 0)
+                m_element_size = pos - m_element_start;
+
+            break;
+        }
+
+        //Parse ID
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0) //error
+            return static_cast<long>(id);
+
+        if (id == 0)  //weird
+            return E_FILE_FORMAT_INVALID;
+
+        //This is the distinguished set of ID's we use to determine
+        //that we have exhausted the sub-element's inside the cluster
+        //whose ID we parsed earlier.
+
+        if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster or Cues ID
+        {
+            if (m_element_size < 0)
+                m_element_size = pos - m_element_start;
+
+            break;
+        }
+
+        pos += len;  //consume ID field
+
+        //Parse Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;
+
+        pos += len;  //consume size field
+
+        if ((cluster_stop >= 0) && (pos > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        //pos now points to start of payload
+
+        if (size == 0)  //weird
+            continue;
+
+        //const long long block_start = pos;
+        const long long block_stop = pos + size;
+
+        if (cluster_stop >= 0)
+        {
+            if (block_stop > cluster_stop)
+            {
+                if ((id == 0x20) || (id == 0x23))
+                    return E_FILE_FORMAT_INVALID;
+
+                pos = cluster_stop;
+                break;
+            }
+        }
+        else if ((total >= 0) && (block_stop > total))
+        {
+            m_element_size = total - m_element_start;
+            pos = total;
+            break;
+        }
+        else if (block_stop > avail)
+        {
+            len = static_cast<long>(size);
+            return E_BUFFER_NOT_FULL;
+        }
+
+        Cluster* const this_ = const_cast<Cluster*>(this);
+
+        if (id == 0x20)  //BlockGroup
+            return this_->ParseBlockGroup(size, pos, len);
+
+        if (id == 0x23)  //SimpleBlock
+            return this_->ParseSimpleBlock(size, pos, len);
+
+        pos += size;  //consume payload
+        assert((cluster_stop < 0) || (pos <= cluster_stop));
+    }
+
+    assert(m_element_size > 0);
+
+    m_pos = pos;
+    assert((cluster_stop < 0) || (m_pos <= cluster_stop));
+
+    if (m_entries_count > 0)
+    {
+        const long idx = m_entries_count - 1;
+
+        const BlockEntry* const pLast = m_entries[idx];
+        assert(pLast);
+
+        const Block* const pBlock = pLast->GetBlock();
+        assert(pBlock);
+
+        const long long start = pBlock->m_start;
+
+        if ((total >= 0) && (start > total))
+            return -1;  //defend against trucated stream
+
+        const long long size = pBlock->m_size;
+
+        const long long stop = start + size;
+        assert((cluster_stop < 0) || (stop <= cluster_stop));
+
+        if ((total >= 0) && (stop > total))
+            return -1;  //defend against trucated stream
+    }
+
+    return 1;  //no more entries
+}
+
+
+long Cluster::ParseSimpleBlock(
+    long long block_size,
+    long long& pos,
+    long& len)
+{
+    const long long block_start = pos;
+    const long long block_stop = pos + block_size;
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long total, avail;
+
+    long status = pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    //parse track number
+
+    if ((pos + 1) > avail)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    long long result = GetUIntLength(pReader, pos, len);
+
+    if (result < 0)  //error
+        return static_cast<long>(result);
+
+    if (result > 0)  //weird
+        return E_BUFFER_NOT_FULL;
+
+    if ((pos + len) > block_stop)
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + len) > avail)
+        return E_BUFFER_NOT_FULL;
+
+    const long long track = ReadUInt(pReader, pos, len);
+
+    if (track < 0) //error
+        return static_cast<long>(track);
+
+    if (track == 0)
+        return E_FILE_FORMAT_INVALID;
+
+#if 0
+    //TODO(matthewjheaney)
+    //This turned out to be too conservative.  The problem is that
+    //if we see a track header in the tracks element with an unsupported
+    //track type, we throw that track header away, so it is not present
+    //in the track map.  But even though we don't understand the track
+    //header, there are still blocks in the cluster with that track
+    //number.  It was our decision to ignore that track header, so it's
+    //up to us to deal with blocks associated with that track -- we
+    //cannot simply report an error since technically there's nothing
+    //wrong with the file.
+    //
+    //For now we go ahead and finish the parse, creating a block entry
+    //for this block.  This is somewhat wasteful, because without a
+    //track header there's nothing you can do with the block. What
+    //we really need here is a special return value that indicates to
+    //the caller that he should ignore this particular block, and
+    //continue parsing.
+
+    const Tracks* const pTracks = m_pSegment->GetTracks();
+    assert(pTracks);
+
+    const long tn = static_cast<long>(track);
+
+    const Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+    if (pTrack == NULL)
+        return E_FILE_FORMAT_INVALID;
+#endif
+
+    pos += len;  //consume track number
+
+    if ((pos + 2) > block_stop)
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + 2) > avail)
+    {
+        len = 2;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    pos += 2;  //consume timecode
+
+    if ((pos + 1) > block_stop)
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + 1) > avail)
+    {
+        len = 1;
+        return E_BUFFER_NOT_FULL;
+    }
+
+    unsigned char flags;
+
+    status = pReader->Read(pos, 1, &flags);
+
+    if (status < 0)  //error or underflow
+    {
+        len = 1;
+        return status;
+    }
+
+    ++pos;  //consume flags byte
+    assert(pos <= avail);
+
+    if (pos >= block_stop)
+        return E_FILE_FORMAT_INVALID;
+
+    const int lacing = int(flags & 0x06) >> 1;
+
+    if ((lacing != 0) && (block_stop > avail))
+    {
+        len = static_cast<long>(block_stop - pos);
+        return E_BUFFER_NOT_FULL;
+    }
+
+    status = CreateBlock(0x23,  //simple block id
+                         block_start, block_size,
+                         0);  //DiscardPadding
+
+    if (status != 0)
+        return status;
+
+    m_pos = block_stop;
+
+    return 0;  //success
+}
+
+
+long Cluster::ParseBlockGroup(
+    long long payload_size,
+    long long& pos,
+    long& len)
+{
+    const long long payload_start = pos;
+    const long long payload_stop = pos + payload_size;
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long total, avail;
+
+    long status = pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    if ((total >= 0) && (payload_stop > total))
+        return E_FILE_FORMAT_INVALID;
+
+    if (payload_stop > avail)
+    {
+         len = static_cast<long>(payload_size);
+         return E_BUFFER_NOT_FULL;
+    }
+
+    long long discard_padding = 0;
+
+    while (pos < payload_stop)
+    {
+        //parse sub-block element ID
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((pos + len) > payload_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0) //error
+            return static_cast<long>(id);
+
+        if (id == 0)  //not a value ID
+            return E_FILE_FORMAT_INVALID;
+
+        pos += len;  //consume ID field
+
+        //Parse Size
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((pos + len) > payload_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume size field
+
+        //pos now points to start of sub-block group payload
+
+        if (pos > payload_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if (size == 0)  //weird
+            continue;
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;
+
+        if (id == 0x35A2)  //DiscardPadding
+        {
+            result = GetUIntLength(pReader, pos, len);
+
+            if (result < 0)  //error
+                return static_cast<long>(result);
+
+            status = UnserializeInt(pReader, pos, len, discard_padding);
+
+            if (status < 0)  //error
+                return status;
+        }
+
+        if (id != 0x21)  //sub-part of BlockGroup is not a Block
+        {
+            pos += size;  //consume sub-part of block group
+
+            if (pos > payload_stop)
+                return E_FILE_FORMAT_INVALID;
+
+            continue;
+        }
+
+        const long long block_stop = pos + size;
+
+        if (block_stop > payload_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        //parse track number
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((pos + len) > block_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long track = ReadUInt(pReader, pos, len);
+
+        if (track < 0) //error
+            return static_cast<long>(track);
+
+        if (track == 0)
+            return E_FILE_FORMAT_INVALID;
+
+#if 0
+        //TODO(matthewjheaney)
+        //This turned out to be too conservative.  The problem is that
+        //if we see a track header in the tracks element with an unsupported
+        //track type, we throw that track header away, so it is not present
+        //in the track map.  But even though we don't understand the track
+        //header, there are still blocks in the cluster with that track
+        //number.  It was our decision to ignore that track header, so it's
+        //up to us to deal with blocks associated with that track -- we
+        //cannot simply report an error since technically there's nothing
+        //wrong with the file.
+        //
+        //For now we go ahead and finish the parse, creating a block entry
+        //for this block.  This is somewhat wasteful, because without a
+        //track header there's nothing you can do with the block. What
+        //we really need here is a special return value that indicates to
+        //the caller that he should ignore this particular block, and
+        //continue parsing.
+
+        const Tracks* const pTracks = m_pSegment->GetTracks();
+        assert(pTracks);
+
+        const long tn = static_cast<long>(track);
+
+        const Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+        if (pTrack == NULL)
+            return E_FILE_FORMAT_INVALID;
+#endif
+
+        pos += len;  //consume track number
+
+        if ((pos + 2) > block_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + 2) > avail)
+        {
+            len = 2;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        pos += 2;  //consume timecode
+
+        if ((pos + 1) > block_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        unsigned char flags;
+
+        status = pReader->Read(pos, 1, &flags);
+
+        if (status < 0)  //error or underflow
+        {
+            len = 1;
+            return status;
+        }
+
+        ++pos;  //consume flags byte
+        assert(pos <= avail);
+
+        if (pos >= block_stop)
+            return E_FILE_FORMAT_INVALID;
+
+        const int lacing = int(flags & 0x06) >> 1;
+
+        if ((lacing != 0) && (block_stop > avail))
+        {
+            len = static_cast<long>(block_stop - pos);
+            return E_BUFFER_NOT_FULL;
+        }
+
+        pos = block_stop;  //consume block-part of block group
+        assert(pos <= payload_stop);
+    }
+
+    assert(pos == payload_stop);
+
+    status = CreateBlock(0x20,  //BlockGroup ID
+                         payload_start, payload_size,
+                         discard_padding);
+    if (status != 0)
+        return status;
+
+    m_pos = payload_stop;
+
+    return 0;  //success
+}
+
+
+long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const
+{
+    assert(m_pos >= m_element_start);
+
+    pEntry = NULL;
+
+    if (index < 0)
+        return -1;  //generic error
+
+    if (m_entries_count < 0)
+        return E_BUFFER_NOT_FULL;
+
+    assert(m_entries);
+    assert(m_entries_size > 0);
+    assert(m_entries_count <= m_entries_size);
+
+    if (index < m_entries_count)
+    {
+        pEntry = m_entries[index];
+        assert(pEntry);
+
+        return 1;  //found entry
+    }
+
+    if (m_element_size < 0)        //we don't know cluster end yet
+        return E_BUFFER_NOT_FULL;  //underflow
+
+    const long long element_stop = m_element_start + m_element_size;
+
+    if (m_pos >= element_stop)
+        return 0;  //nothing left to parse
+
+    return E_BUFFER_NOT_FULL;  //underflow, since more remains to be parsed
+}
+
+
+Cluster* Cluster::Create(
+    Segment* pSegment,
+    long idx,
+    long long off)
+    //long long element_size)
+{
+    assert(pSegment);
+    assert(off >= 0);
+
+    const long long element_start = pSegment->m_start + off;
+
+    Cluster* const pCluster = new Cluster(pSegment,
+                                          idx,
+                                          element_start);
+                                          //element_size);
+    assert(pCluster);
+
+    return pCluster;
+}
+
+
+Cluster::Cluster() :
+    m_pSegment(NULL),
+    m_element_start(0),
+    m_index(0),
+    m_pos(0),
+    m_element_size(0),
+    m_timecode(0),
+    m_entries(NULL),
+    m_entries_size(0),
+    m_entries_count(0)  //means "no entries"
+{
+}
+
+
+Cluster::Cluster(
+    Segment* pSegment,
+    long idx,
+    long long element_start
+    /* long long element_size */ ) :
+    m_pSegment(pSegment),
+    m_element_start(element_start),
+    m_index(idx),
+    m_pos(element_start),
+    m_element_size(-1 /* element_size */ ),
+    m_timecode(-1),
+    m_entries(NULL),
+    m_entries_size(0),
+    m_entries_count(-1)  //means "has not been parsed yet"
+{
+}
+
+
+Cluster::~Cluster()
+{
+    if (m_entries_count <= 0)
+        return;
+
+    BlockEntry** i = m_entries;
+    BlockEntry** const j = m_entries + m_entries_count;
+
+    while (i != j)
+    {
+         BlockEntry* p = *i++;
+         assert(p);
+
+         delete p;
+    }
+
+    delete[] m_entries;
+}
+
+
+bool Cluster::EOS() const
+{
+    return (m_pSegment == NULL);
+}
+
+
+long Cluster::GetIndex() const
+{
+    return m_index;
+}
+
+
+long long Cluster::GetPosition() const
+{
+    const long long pos = m_element_start - m_pSegment->m_start;
+    assert(pos >= 0);
+
+    return pos;
+}
+
+
+long long Cluster::GetElementSize() const
+{
+    return m_element_size;
+}
+
+
+#if 0
+bool Cluster::HasBlockEntries(
+    const Segment* pSegment,
+    long long off)  //relative to start of segment payload
+{
+    assert(pSegment);
+    assert(off >= 0);  //relative to segment
+
+    IMkvReader* const pReader = pSegment->m_pReader;
+
+    long long pos = pSegment->m_start + off;  //absolute
+    long long size;
+
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        (void)id;
+        assert(id >= 0);
+        assert(id == 0x0F43B675);  //Cluster ID
+
+        pos += len;  //consume id
+
+        size = ReadUInt(pReader, pos, len);
+        assert(size > 0);
+
+        pos += len;  //consume size
+
+        //pos now points to start of payload
+    }
+
+    const long long stop = pos + size;
+
+    while (pos < stop)
+    {
+        long len;
+
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume id
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume size
+
+        if (id == 0x20)  //BlockGroup ID
+            return true;
+
+        if (id == 0x23)  //SimpleBlock ID
+            return true;
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    return false;
+}
+#endif
+
+
+long Cluster::HasBlockEntries(
+    const Segment* pSegment,
+    long long off,  //relative to start of segment payload
+    long long& pos,
+    long& len)
+{
+    assert(pSegment);
+    assert(off >= 0);  //relative to segment
+
+    IMkvReader* const pReader = pSegment->m_pReader;
+
+    long long total, avail;
+
+    long status = pReader->Length(&total, &avail);
+
+    if (status < 0)  //error
+        return status;
+
+    assert((total < 0) || (avail <= total));
+
+    pos = pSegment->m_start + off;  //absolute
+
+    if ((total >= 0) && (pos >= total))
+        return 0;  //we don't even have a complete cluster
+
+    const long long segment_stop =
+        (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size;
+
+    long long cluster_stop = -1;  //interpreted later to mean "unknown size"
+
+    {
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //need more data
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((total >= 0) && ((pos + len) > total))
+            return 0;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0)  //error
+            return static_cast<long>(id);
+
+        if (id != 0x0F43B675)  //weird: not cluster ID
+            return -1;         //generic error
+
+        pos += len;  //consume Cluster ID field
+
+        //read size field
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //weird
+            return E_BUFFER_NOT_FULL;
+
+        if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((total >= 0) && ((pos + len) > total))
+            return 0;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        if (size == 0)
+            return 0;  //cluster does not have entries
+
+        pos += len;  //consume size field
+
+        //pos now points to start of payload
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size != unknown_size)
+        {
+            cluster_stop = pos + size;
+            assert(cluster_stop >= 0);
+
+            if ((segment_stop >= 0) && (cluster_stop > segment_stop))
+                return E_FILE_FORMAT_INVALID;
+
+            if ((total >= 0) && (cluster_stop > total))
+                //return E_FILE_FORMAT_INVALID;  //too conservative
+                return 0;  //cluster does not have any entries
+        }
+    }
+
+    for (;;)
+    {
+        if ((cluster_stop >= 0) && (pos >= cluster_stop))
+            return 0;  //no entries detected
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        long long result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //need more data
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long id = ReadUInt(pReader, pos, len);
+
+        if (id < 0)  //error
+            return static_cast<long>(id);
+
+        //This is the distinguished set of ID's we use to determine
+        //that we have exhausted the sub-element's inside the cluster
+        //whose ID we parsed earlier.
+
+        if (id == 0x0F43B675)  //Cluster ID
+            return 0;  //no entries found
+
+        if (id == 0x0C53BB6B)  //Cues ID
+            return 0;  //no entries found
+
+        pos += len;  //consume id field
+
+        if ((cluster_stop >= 0) && (pos >= cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        //read size field
+
+        if ((pos + 1) > avail)
+        {
+            len = 1;
+            return E_BUFFER_NOT_FULL;
+        }
+
+        result = GetUIntLength(pReader, pos, len);
+
+        if (result < 0)  //error
+            return static_cast<long>(result);
+
+        if (result > 0)  //underflow
+            return E_BUFFER_NOT_FULL;
+
+        if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > avail)
+            return E_BUFFER_NOT_FULL;
+
+        const long long size = ReadUInt(pReader, pos, len);
+
+        if (size < 0)  //error
+            return static_cast<long>(size);
+
+        pos += len;  //consume size field
+
+        //pos now points to start of payload
+
+        if ((cluster_stop >= 0) && (pos > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if (size == 0)  //weird
+            continue;
+
+        const long long unknown_size = (1LL << (7 * len)) - 1;
+
+        if (size == unknown_size)
+            return E_FILE_FORMAT_INVALID;  //not supported inside cluster
+
+        if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
+            return E_FILE_FORMAT_INVALID;
+
+        if (id == 0x20)  //BlockGroup ID
+            return 1;    //have at least one entry
+
+        if (id == 0x23)  //SimpleBlock ID
+            return 1;    //have at least one entry
+
+        pos += size;  //consume payload
+        assert((cluster_stop < 0) || (pos <= cluster_stop));
+    }
+}
+
+
+long long Cluster::GetTimeCode() const
+{
+    long long pos;
+    long len;
+
+    const long status = Load(pos, len);
+
+    if (status < 0) //error
+        return status;
+
+    return m_timecode;
+}
+
+
+long long Cluster::GetTime() const
+{
+    const long long tc = GetTimeCode();
+
+    if (tc < 0)
+        return tc;
+
+    const SegmentInfo* const pInfo = m_pSegment->GetInfo();
+    assert(pInfo);
+
+    const long long scale = pInfo->GetTimeCodeScale();
+    assert(scale >= 1);
+
+    const long long t = m_timecode * scale;
+
+    return t;
+}
+
+
+long long Cluster::GetFirstTime() const
+{
+    const BlockEntry* pEntry;
+
+    const long status = GetFirst(pEntry);
+
+    if (status < 0)  //error
+        return status;
+
+    if (pEntry == NULL)  //empty cluster
+        return GetTime();
+
+    const Block* const pBlock = pEntry->GetBlock();
+    assert(pBlock);
+
+    return pBlock->GetTime(this);
+}
+
+
+long long Cluster::GetLastTime() const
+{
+    const BlockEntry* pEntry;
+
+    const long status = GetLast(pEntry);
+
+    if (status < 0)  //error
+        return status;
+
+    if (pEntry == NULL)  //empty cluster
+        return GetTime();
+
+    const Block* const pBlock = pEntry->GetBlock();
+    assert(pBlock);
+
+    return pBlock->GetTime(this);
+}
+
+
+long Cluster::CreateBlock(
+    long long id,
+    long long pos,   //absolute pos of payload
+    long long size,
+    long long discard_padding)
+{
+    assert((id == 0x20) || (id == 0x23));  //BlockGroup or SimpleBlock
+
+    if (m_entries_count < 0)  //haven't parsed anything yet
+    {
+        assert(m_entries == NULL);
+        assert(m_entries_size == 0);
+
+        m_entries_size = 1024;
+        m_entries = new BlockEntry*[m_entries_size];
+
+        m_entries_count = 0;
+    }
+    else
+    {
+        assert(m_entries);
+        assert(m_entries_size > 0);
+        assert(m_entries_count <= m_entries_size);
+
+        if (m_entries_count >= m_entries_size)
+        {
+            const long entries_size = 2 * m_entries_size;
+
+            BlockEntry** const entries = new BlockEntry*[entries_size];
+            assert(entries);
+
+            BlockEntry** src = m_entries;
+            BlockEntry** const src_end = src + m_entries_count;
+
+            BlockEntry** dst = entries;
+
+            while (src != src_end)
+                *dst++ = *src++;
+
+            delete[] m_entries;
+
+            m_entries = entries;
+            m_entries_size = entries_size;
+        }
+    }
+
+    if (id == 0x20)  //BlockGroup ID
+        return CreateBlockGroup(pos, size, discard_padding);
+    else  //SimpleBlock ID
+        return CreateSimpleBlock(pos, size);
+}
+
+
+long Cluster::CreateBlockGroup(
+    long long start_offset,
+    long long size,
+    long long discard_padding)
+{
+    assert(m_entries);
+    assert(m_entries_size > 0);
+    assert(m_entries_count >= 0);
+    assert(m_entries_count < m_entries_size);
+
+    IMkvReader* const pReader = m_pSegment->m_pReader;
+
+    long long pos = start_offset;
+    const long long stop = start_offset + size;
+
+    //For WebM files, there is a bias towards previous reference times
+    //(in order to support alt-ref frames, which refer back to the previous
+    //keyframe).  Normally a 0 value is not possible, but here we tenatively
+    //allow 0 as the value of a reference frame, with the interpretation
+    //that this is a "previous" reference time.
+
+    long long prev = 1;  //nonce
+    long long next = 0;  //nonce
+    long long duration = -1;  //really, this is unsigned
+
+    long long bpos = -1;
+    long long bsize = -1;
+
+    while (pos < stop)
+    {
+        long len;
+        const long long id = ReadUInt(pReader, pos, len);
+        assert(id >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume ID
+
+        const long long size = ReadUInt(pReader, pos, len);
+        assert(size >= 0);  //TODO
+        assert((pos + len) <= stop);
+
+        pos += len;  //consume size
+
+        if (id == 0x21) //Block ID
+        {
+            if (bpos < 0) //Block ID
+            {
+                bpos = pos;
+                bsize = size;
+            }
+        }
+        else if (id == 0x1B)  //Duration ID
+        {
+            assert(size <= 8);
+
+            duration = UnserializeUInt(pReader, pos, size);
+            assert(duration >= 0);  //TODO
+        }
+        else if (id == 0x7B)  //ReferenceBlock
+        {
+            assert(size <= 8);
+            const long size_ = static_cast<long>(size);
+
+            long long time;
+
+            long status = UnserializeInt(pReader, pos, size_, time);
+            assert(status == 0);
+            if (status != 0)
+                return -1;
+
+            if (time <= 0)  //see note above
+                prev = time;
+            else  //weird
+                next = time;
+        }
+
+        pos += size;  //consume payload
+        assert(pos <= stop);
+    }
+
+    assert(pos == stop);
+    assert(bpos >= 0);
+    assert(bsize >= 0);
+
+    const long idx = m_entries_count;
+
+    BlockEntry** const ppEntry = m_entries + idx;
+    BlockEntry*& pEntry = *ppEntry;
+
+    pEntry = new (std::nothrow) BlockGroup(
+                                  this,
+                                  idx,
+                                  bpos,
+                                  bsize,
+                                  prev,
+                                  next,
+                                  duration,
+                                  discard_padding);
+
+    if (pEntry == NULL)
+        return -1;  //generic error
+
+    BlockGroup* const p = static_cast<BlockGroup*>(pEntry);
+
+    const long status = p->Parse();
+
+    if (status == 0)  //success
+    {
+        ++m_entries_count;
+        return 0;
+    }
+
+    delete pEntry;
+    pEntry = 0;
+
+    return status;
+}
+
+
+
+long Cluster::CreateSimpleBlock(
+    long long st,
+    long long sz)
+{
+    assert(m_entries);
+    assert(m_entries_size > 0);
+    assert(m_entries_count >= 0);
+    assert(m_entries_count < m_entries_size);
+
+    const long idx = m_entries_count;
+
+    BlockEntry** const ppEntry = m_entries + idx;
+    BlockEntry*& pEntry = *ppEntry;
+
+    pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz);
+
+    if (pEntry == NULL)
+        return -1;  //generic error
+
+    SimpleBlock* const p = static_cast<SimpleBlock*>(pEntry);
+
+    const long status = p->Parse();
+
+    if (status == 0)
+    {
+        ++m_entries_count;
+        return 0;
+    }
+
+    delete pEntry;
+    pEntry = 0;
+
+    return status;
+}
+
+
+long Cluster::GetFirst(const BlockEntry*& pFirst) const
+{
+    if (m_entries_count <= 0)
+    {
+        long long pos;
+        long len;
+
+        const long status = Parse(pos, len);
+
+        if (status < 0)  //error
+        {
+            pFirst = NULL;
+            return status;
+        }
+
+        if (m_entries_count <= 0)  //empty cluster
+        {
+            pFirst = NULL;
+            return 0;
+        }
+    }
+
+    assert(m_entries);
+
+    pFirst = m_entries[0];
+    assert(pFirst);
+
+    return 0;  //success
+}
+
+long Cluster::GetLast(const BlockEntry*& pLast) const
+{
+    for (;;)
+    {
+        long long pos;
+        long len;
+
+        const long status = Parse(pos, len);
+
+        if (status < 0)  //error
+        {
+            pLast = NULL;
+            return status;
+        }
+
+        if (status > 0)  //no new block
+            break;
+    }
+
+    if (m_entries_count <= 0)
+    {
+        pLast = NULL;
+        return 0;
+    }
+
+    assert(m_entries);
+
+    const long idx = m_entries_count - 1;
+
+    pLast = m_entries[idx];
+    assert(pLast);
+
+    return 0;
+}
+
+
+long Cluster::GetNext(
+    const BlockEntry* pCurr,
+    const BlockEntry*& pNext) const
+{
+    assert(pCurr);
+    assert(m_entries);
+    assert(m_entries_count > 0);
+
+    size_t idx = pCurr->GetIndex();
+    assert(idx < size_t(m_entries_count));
+    assert(m_entries[idx] == pCurr);
+
+    ++idx;
+
+    if (idx >= size_t(m_entries_count))
+    {
+        long long pos;
+        long len;
+
+        const long status = Parse(pos, len);
+
+        if (status < 0)  //error
+        {
+            pNext = NULL;
+            return status;
+        }
+
+        if (status > 0)
+        {
+            pNext = NULL;
+            return 0;
+        }
+
+        assert(m_entries);
+        assert(m_entries_count > 0);
+        assert(idx < size_t(m_entries_count));
+    }
+
+    pNext = m_entries[idx];
+    assert(pNext);
+
+    return 0;
+}
+
+
+long Cluster::GetEntryCount() const
+{
+    return m_entries_count;
+}
+
+
+const BlockEntry* Cluster::GetEntry(
+    const Track* pTrack,
+    long long time_ns) const
+{
+    assert(pTrack);
+
+    if (m_pSegment == NULL)  //this is the special EOS cluster
+        return pTrack->GetEOS();
+
+#if 0
+
+    LoadBlockEntries();
+
+    if ((m_entries == NULL) || (m_entries_count <= 0))
+        return NULL;  //return EOS here?
+
+    const BlockEntry* pResult = pTrack->GetEOS();
+
+    BlockEntry** i = m_entries;
+    assert(i);
+
+    BlockEntry** const j = i + m_entries_count;
+
+    while (i != j)
+    {
+        const BlockEntry* const pEntry = *i++;
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if (pBlock->GetTrackNumber() != pTrack->GetNumber())
+            continue;
+
+        if (pTrack->VetEntry(pEntry))
+        {
+            if (time_ns < 0)  //just want first candidate block
+                return pEntry;
+
+            const long long ns = pBlock->GetTime(this);
+
+            if (ns > time_ns)
+                break;
+
+            pResult = pEntry;
+        }
+        else if (time_ns >= 0)
+        {
+            const long long ns = pBlock->GetTime(this);
+
+            if (ns > time_ns)
+                break;
+        }
+    }
+
+    return pResult;
+
+#else
+
+    const BlockEntry* pResult = pTrack->GetEOS();
+
+    long index = 0;
+
+    for (;;)
+    {
+        if (index >= m_entries_count)
+        {
+            long long pos;
+            long len;
+
+            const long status = Parse(pos, len);
+            assert(status >= 0);
+
+            if (status > 0)  //completely parsed, and no more entries
+                return pResult;
+
+            if (status < 0)  //should never happen
+                return 0;
+
+            assert(m_entries);
+            assert(index < m_entries_count);
+        }
+
+        const BlockEntry* const pEntry = m_entries[index];
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if (pBlock->GetTrackNumber() != pTrack->GetNumber())
+        {
+            ++index;
+            continue;
+        }
+
+        if (pTrack->VetEntry(pEntry))
+        {
+            if (time_ns < 0)  //just want first candidate block
+                return pEntry;
+
+            const long long ns = pBlock->GetTime(this);
+
+            if (ns > time_ns)
+                return pResult;
+
+            pResult = pEntry;  //have a candidate
+        }
+        else if (time_ns >= 0)
+        {
+            const long long ns = pBlock->GetTime(this);
+
+            if (ns > time_ns)
+                return pResult;
+        }
+
+        ++index;
+    }
+
+#endif
+}
+
+
+const BlockEntry*
+Cluster::GetEntry(
+    const CuePoint& cp,
+    const CuePoint::TrackPosition& tp) const
+{
+    assert(m_pSegment);
+
+#if 0
+
+    LoadBlockEntries();
+
+    if (m_entries == NULL)
+        return NULL;
+
+    const long long count = m_entries_count;
+
+    if (count <= 0)
+        return NULL;
+
+    const long long tc = cp.GetTimeCode();
+
+    if ((tp.m_block > 0) && (tp.m_block <= count))
+    {
+        const size_t block = static_cast<size_t>(tp.m_block);
+        const size_t index = block - 1;
+
+        const BlockEntry* const pEntry = m_entries[index];
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if ((pBlock->GetTrackNumber() == tp.m_track) &&
+            (pBlock->GetTimeCode(this) == tc))
+        {
+            return pEntry;
+        }
+    }
+
+    const BlockEntry* const* i = m_entries;
+    const BlockEntry* const* const j = i + count;
+
+    while (i != j)
+    {
+#ifdef _DEBUG
+        const ptrdiff_t idx = i - m_entries;
+        idx;
+#endif
+
+        const BlockEntry* const pEntry = *i++;
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if (pBlock->GetTrackNumber() != tp.m_track)
+            continue;
+
+        const long long tc_ = pBlock->GetTimeCode(this);
+        assert(tc_ >= 0);
+
+        if (tc_ < tc)
+            continue;
+
+        if (tc_ > tc)
+            return NULL;
+
+        const Tracks* const pTracks = m_pSegment->GetTracks();
+        assert(pTracks);
+
+        const long tn = static_cast<long>(tp.m_track);
+        const Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+        if (pTrack == NULL)
+            return NULL;
+
+        const long long type = pTrack->GetType();
+
+        if (type == 2)  //audio
+            return pEntry;
+
+        if (type != 1)  //not video
+            return NULL;
+
+        if (!pBlock->IsKey())
+            return NULL;
+
+        return pEntry;
+    }
+
+    return NULL;
+
+#else
+
+    const long long tc = cp.GetTimeCode();
+
+    if (tp.m_block > 0)
+    {
+        const long block = static_cast<long>(tp.m_block);
+        const long index = block - 1;
+
+        while (index >= m_entries_count)
+        {
+            long long pos;
+            long len;
+
+            const long status = Parse(pos, len);
+
+            if (status < 0)  //TODO: can this happen?
+                return NULL;
+
+            if (status > 0)  //nothing remains to be parsed
+                return NULL;
+        }
+
+        const BlockEntry* const pEntry = m_entries[index];
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if ((pBlock->GetTrackNumber() == tp.m_track) &&
+            (pBlock->GetTimeCode(this) == tc))
+        {
+            return pEntry;
+        }
+    }
+
+    long index = 0;
+
+    for (;;)
+    {
+        if (index >= m_entries_count)
+        {
+            long long pos;
+            long len;
+
+            const long status = Parse(pos, len);
+
+            if (status < 0)  //TODO: can this happen?
+                return NULL;
+
+            if (status > 0)  //nothing remains to be parsed
+                return NULL;
+
+            assert(m_entries);
+            assert(index < m_entries_count);
+        }
+
+        const BlockEntry* const pEntry = m_entries[index];
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if (pBlock->GetTrackNumber() != tp.m_track)
+        {
+            ++index;
+            continue;
+        }
+
+        const long long tc_ = pBlock->GetTimeCode(this);
+
+        if (tc_ < tc)
+        {
+            ++index;
+            continue;
+        }
+
+        if (tc_ > tc)
+            return NULL;
+
+        const Tracks* const pTracks = m_pSegment->GetTracks();
+        assert(pTracks);
+
+        const long tn = static_cast<long>(tp.m_track);
+        const Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+        if (pTrack == NULL)
+            return NULL;
+
+        const long long type = pTrack->GetType();
+
+        if (type == 2)  //audio
+            return pEntry;
+
+        if (type != 1)  //not video
+            return NULL;
+
+        if (!pBlock->IsKey())
+            return NULL;
+
+        return pEntry;
+    }
+
+#endif
+
+}
+
+
+#if 0
+const BlockEntry* Cluster::GetMaxKey(const VideoTrack* pTrack) const
+{
+    assert(pTrack);
+
+    if (m_pSegment == NULL)  //EOS
+        return pTrack->GetEOS();
+
+    LoadBlockEntries();
+
+    if ((m_entries == NULL) || (m_entries_count <= 0))
+        return pTrack->GetEOS();
+
+    BlockEntry** i = m_entries + m_entries_count;
+    BlockEntry** const j = m_entries;
+
+    while (i != j)
+    {
+        const BlockEntry* const pEntry = *--i;
+        assert(pEntry);
+        assert(!pEntry->EOS());
+
+        const Block* const pBlock = pEntry->GetBlock();
+        assert(pBlock);
+
+        if (pBlock->GetTrackNumber() != pTrack->GetNumber())
+            continue;
+
+        if (pBlock->IsKey())
+            return pEntry;
+    }
+
+    return pTrack->GetEOS();  //no satisfactory block found
+}
+#endif
+
+
+BlockEntry::BlockEntry(Cluster* p, long idx) :
+    m_pCluster(p),
+    m_index(idx)
+{
+}
+
+
+BlockEntry::~BlockEntry()
+{
+}
+
+
+bool BlockEntry::EOS() const
+{
+    return (GetKind() == kBlockEOS);
+}
+
+
+const Cluster* BlockEntry::GetCluster() const
+{
+    return m_pCluster;
+}
+
+
+long BlockEntry::GetIndex() const
+{
+    return m_index;
+}
+
+
+SimpleBlock::SimpleBlock(
+    Cluster* pCluster,
+    long idx,
+    long long start,
+    long long size) :
+    BlockEntry(pCluster, idx),
+    m_block(start, size, 0)
+{
+}
+
+
+long SimpleBlock::Parse()
+{
+    return m_block.Parse(m_pCluster);
+}
+
+
+BlockEntry::Kind SimpleBlock::GetKind() const
+{
+    return kBlockSimple;
+}
+
+
+const Block* SimpleBlock::GetBlock() const
+{
+    return &m_block;
+}
+
+
+BlockGroup::BlockGroup(
+    Cluster* pCluster,
+    long idx,
+    long long block_start,
+    long long block_size,
+    long long prev,
+    long long next,
+    long long duration,
+    long long discard_padding) :
+    BlockEntry(pCluster, idx),
+    m_block(block_start, block_size, discard_padding),
+    m_prev(prev),
+    m_next(next),
+    m_duration(duration)
+{
+}
+
+
+long BlockGroup::Parse()
+{
+    const long status = m_block.Parse(m_pCluster);
+
+    if (status)
+        return status;
+
+    m_block.SetKey((m_prev > 0) && (m_next <= 0));
+
+    return 0;
+}
+
+
+#if 0
+void BlockGroup::ParseBlock(long long start, long long size)
+{
+    IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader;
+
+    Block* const pBlock = new Block(start, size, pReader);
+    assert(pBlock);  //TODO
+
+    //TODO: the Matroska spec says you have multiple blocks within the
+    //same block group, with blocks ranked by priority (the flag bits).
+
+    assert(m_pBlock == NULL);
+    m_pBlock = pBlock;
+}
+#endif
+
+
+BlockEntry::Kind BlockGroup::GetKind() const
+{
+    return kBlockGroup;
+}
+
+
+const Block* BlockGroup::GetBlock() const
+{
+    return &m_block;
+}
+
+
+long long BlockGroup::GetPrevTimeCode() const
+{
+    return m_prev;
+}
+
+
+long long BlockGroup::GetNextTimeCode() const
+{
+    return m_next;
+}
+
+long long BlockGroup::GetDurationTimeCode() const
+{
+    return m_duration;
+}
+
+Block::Block(long long start, long long size_, long long discard_padding) :
+    m_start(start),
+    m_size(size_),
+    m_track(0),
+    m_timecode(-1),
+    m_flags(0),
+    m_frames(NULL),
+    m_frame_count(-1),
+    m_discard_padding(discard_padding)
+{
+}
+
+
+Block::~Block()
+{
+    delete[] m_frames;
+}
+
+
+long Block::Parse(const Cluster* pCluster)
+{
+    if (pCluster == NULL)
+        return -1;
+
+    if (pCluster->m_pSegment == NULL)
+        return -1;
+
+    assert(m_start >= 0);
+    assert(m_size >= 0);
+    assert(m_track <= 0);
+    assert(m_frames == NULL);
+    assert(m_frame_count <= 0);
+
+    long long pos = m_start;
+    const long long stop = m_start + m_size;
+
+    long len;
+
+    IMkvReader* const pReader = pCluster->m_pSegment->m_pReader;
+
+    m_track = ReadUInt(pReader, pos, len);
+
+    if (m_track <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+    if ((pos + len) > stop)
+        return E_FILE_FORMAT_INVALID;
+
+    pos += len;  //consume track number
+
+    if ((stop - pos) < 2)
+        return E_FILE_FORMAT_INVALID;
+
+    long status;
+    long long value;
+
+    status = UnserializeInt(pReader, pos, 2, value);
+
+    if (status)
+        return E_FILE_FORMAT_INVALID;
+
+    if (value < SHRT_MIN)
+        return E_FILE_FORMAT_INVALID;
+
+    if (value > SHRT_MAX)
+        return E_FILE_FORMAT_INVALID;
+
+    m_timecode = static_cast<short>(value);
+
+    pos += 2;
+
+    if ((stop - pos) <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+    status = pReader->Read(pos, 1, &m_flags);
+
+    if (status)
+        return E_FILE_FORMAT_INVALID;
+
+    const int lacing = int(m_flags & 0x06) >> 1;
+
+    ++pos;  //consume flags byte
+
+    if (lacing == 0)  //no lacing
+    {
+        if (pos > stop)
+            return E_FILE_FORMAT_INVALID;
+
+        m_frame_count = 1;
+        m_frames = new Frame[m_frame_count];
+
+        Frame& f = m_frames[0];
+        f.pos = pos;
+
+        const long long frame_size = stop - pos;
+
+        if (frame_size > LONG_MAX)
+            return E_FILE_FORMAT_INVALID;
+
+        f.len = static_cast<long>(frame_size);
+
+        return 0;  //success
+    }
+
+    if (pos >= stop)
+        return E_FILE_FORMAT_INVALID;
+
+    unsigned char biased_count;
+
+    status = pReader->Read(pos, 1, &biased_count);
+
+    if (status)
+        return E_FILE_FORMAT_INVALID;
+
+    ++pos;  //consume frame count
+    assert(pos <= stop);
+
+    m_frame_count = int(biased_count) + 1;
+
+    m_frames = new Frame[m_frame_count];
+    assert(m_frames);
+
+    if (lacing == 1)  //Xiph
+    {
+        Frame* pf = m_frames;
+        Frame* const pf_end = pf + m_frame_count;
+
+        long size = 0;
+        int frame_count = m_frame_count;
+
+        while (frame_count > 1)
+        {
+            long frame_size = 0;
+
+            for (;;)
+            {
+                unsigned char val;
+
+                if (pos >= stop)
+                    return E_FILE_FORMAT_INVALID;
+
+                status = pReader->Read(pos, 1, &val);
+
+                if (status)
+                    return E_FILE_FORMAT_INVALID;
+
+                ++pos;  //consume xiph size byte
+
+                frame_size += val;
+
+                if (val < 255)
+                    break;
+            }
+
+            Frame& f = *pf++;
+            assert(pf < pf_end);
+
+            f.pos = 0;  //patch later
+
+            f.len = frame_size;
+            size += frame_size;  //contribution of this frame
+
+            --frame_count;
+        }
+
+        assert(pf < pf_end);
+        assert(pos <= stop);
+
+        {
+            Frame& f = *pf++;
+
+            if (pf != pf_end)
+                return E_FILE_FORMAT_INVALID;
+
+            f.pos = 0;  //patch later
+
+            const long long total_size = stop - pos;
+
+            if (total_size < size)
+                return E_FILE_FORMAT_INVALID;
+
+            const long long frame_size = total_size - size;
+
+            if (frame_size > LONG_MAX)
+                return E_FILE_FORMAT_INVALID;
+
+            f.len = static_cast<long>(frame_size);
+        }
+
+        pf = m_frames;
+        while (pf != pf_end)
+        {
+            Frame& f = *pf++;
+            assert((pos + f.len) <= stop);
+
+            f.pos = pos;
+            pos += f.len;
+        }
+
+        assert(pos == stop);
+    }
+    else if (lacing == 2)  //fixed-size lacing
+    {
+        const long long total_size = stop - pos;
+
+        if ((total_size % m_frame_count) != 0)
+            return E_FILE_FORMAT_INVALID;
+
+        const long long frame_size = total_size / m_frame_count;
+
+        if (frame_size > LONG_MAX)
+            return E_FILE_FORMAT_INVALID;
+
+        Frame* pf = m_frames;
+        Frame* const pf_end = pf + m_frame_count;
+
+        while (pf != pf_end)
+        {
+            assert((pos + frame_size) <= stop);
+
+            Frame& f = *pf++;
+
+            f.pos = pos;
+            f.len = static_cast<long>(frame_size);
+
+            pos += frame_size;
+        }
+
+        assert(pos == stop);
+    }
+    else
+    {
+        assert(lacing == 3);  //EBML lacing
+
+        if (pos >= stop)
+            return E_FILE_FORMAT_INVALID;
+
+        long size = 0;
+        int frame_count = m_frame_count;
+
+        long long frame_size = ReadUInt(pReader, pos, len);
+
+        if (frame_size < 0)
+            return E_FILE_FORMAT_INVALID;
+
+        if (frame_size > LONG_MAX)
+            return E_FILE_FORMAT_INVALID;
+
+        if ((pos + len) > stop)
+            return E_FILE_FORMAT_INVALID;
+
+        pos += len; //consume length of size of first frame
+
+        if ((pos + frame_size) > stop)
+            return E_FILE_FORMAT_INVALID;
+
+        Frame* pf = m_frames;
+        Frame* const pf_end = pf + m_frame_count;
+
+        {
+            Frame& curr = *pf;
+
+            curr.pos = 0;  //patch later
+
+            curr.len = static_cast<long>(frame_size);
+            size += curr.len;  //contribution of this frame
+        }
+
+        --frame_count;
+
+        while (frame_count > 1)
+        {
+            if (pos >= stop)
+                return E_FILE_FORMAT_INVALID;
+
+            assert(pf < pf_end);
+
+            const Frame& prev = *pf++;
+            assert(prev.len == frame_size);
+            if (prev.len != frame_size)
+                return E_FILE_FORMAT_INVALID;
+
+            assert(pf < pf_end);
+
+            Frame& curr = *pf;
+
+            curr.pos = 0;  //patch later
+
+            const long long delta_size_ = ReadUInt(pReader, pos, len);
+
+            if (delta_size_ < 0)
+                return E_FILE_FORMAT_INVALID;
+
+            if ((pos + len) > stop)
+                return E_FILE_FORMAT_INVALID;
+
+            pos += len;  //consume length of (delta) size
+            assert(pos <= stop);
+
+            const int exp = 7*len - 1;
+            const long long bias = (1LL << exp) - 1LL;
+            const long long delta_size = delta_size_ - bias;
+
+            frame_size += delta_size;
+
+            if (frame_size < 0)
+                return E_FILE_FORMAT_INVALID;
+
+            if (frame_size > LONG_MAX)
+                return E_FILE_FORMAT_INVALID;
+
+            curr.len = static_cast<long>(frame_size);
+            size += curr.len;  //contribution of this frame
+
+            --frame_count;
+        }
+
+        {
+            assert(pos <= stop);
+            assert(pf < pf_end);
+
+            const Frame& prev = *pf++;
+            assert(prev.len == frame_size);
+            if (prev.len != frame_size)
+                return E_FILE_FORMAT_INVALID;
+
+            assert(pf < pf_end);
+
+            Frame& curr = *pf++;
+            assert(pf == pf_end);
+
+            curr.pos = 0;  //patch later
+
+            const long long total_size = stop - pos;
+
+            if (total_size < size)
+                return E_FILE_FORMAT_INVALID;
+
+            frame_size = total_size - size;
+
+            if (frame_size > LONG_MAX)
+                return E_FILE_FORMAT_INVALID;
+
+            curr.len = static_cast<long>(frame_size);
+        }
+
+        pf = m_frames;
+        while (pf != pf_end)
+        {
+            Frame& f = *pf++;
+            assert((pos + f.len) <= stop);
+
+            f.pos = pos;
+            pos += f.len;
+        }
+
+        assert(pos == stop);
+    }
+
+    return 0;  //success
+}
+
+
+long long Block::GetTimeCode(const Cluster* pCluster) const
+{
+    if (pCluster == 0)
+        return m_timecode;
+
+    const long long tc0 = pCluster->GetTimeCode();
+    assert(tc0 >= 0);
+
+    const long long tc = tc0 + m_timecode;
+
+    return tc;  //unscaled timecode units
+}
+
+
+long long Block::GetTime(const Cluster* pCluster) const
+{
+    assert(pCluster);
+
+    const long long tc = GetTimeCode(pCluster);
+
+    const Segment* const pSegment = pCluster->m_pSegment;
+    const SegmentInfo* const pInfo = pSegment->GetInfo();
+    assert(pInfo);
+
+    const long long scale = pInfo->GetTimeCodeScale();
+    assert(scale >= 1);
+
+    const long long ns = tc * scale;
+
+    return ns;
+}
+
+
+long long Block::GetTrackNumber() const
+{
+    return m_track;
+}
+
+
+bool Block::IsKey() const
+{
+    return ((m_flags & static_cast<unsigned char>(1 << 7)) != 0);
+}
+
+
+void Block::SetKey(bool bKey)
+{
+    if (bKey)
+        m_flags |= static_cast<unsigned char>(1 << 7);
+    else
+        m_flags &= 0x7F;
+}
+
+
+bool Block::IsInvisible() const
+{
+    return bool(int(m_flags & 0x08) != 0);
+}
+
+
+Block::Lacing Block::GetLacing() const
+{
+    const int value = int(m_flags & 0x06) >> 1;
+    return static_cast<Lacing>(value);
+}
+
+
+int Block::GetFrameCount() const
+{
+    return m_frame_count;
+}
+
+
+const Block::Frame& Block::GetFrame(int idx) const
+{
+    assert(idx >= 0);
+    assert(idx < m_frame_count);
+
+    const Frame& f = m_frames[idx];
+    assert(f.pos > 0);
+    assert(f.len > 0);
+
+    return f;
+}
+
+
+long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const
+{
+    assert(pReader);
+    assert(buf);
+
+    const long status = pReader->Read(pos, len, buf);
+    return status;
+}
+
+long long Block::GetDiscardPadding() const
+{
+    return m_discard_padding;
+}
+
+}  //end namespace mkvparser
diff --git a/third_party/libwebm/mkvparser.hpp b/third_party/libwebm/mkvparser.hpp
new file mode 100644 (file)
index 0000000..7184d26
--- /dev/null
@@ -0,0 +1,1079 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVPARSER_HPP
+#define MKVPARSER_HPP
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstddef>
+
+namespace mkvparser
+{
+
+const int E_FILE_FORMAT_INVALID = -2;
+const int E_BUFFER_NOT_FULL = -3;
+
+class IMkvReader
+{
+public:
+    virtual int Read(long long pos, long len, unsigned char* buf) = 0;
+    virtual int Length(long long* total, long long* available) = 0;
+protected:
+    virtual ~IMkvReader();
+};
+
+long long GetUIntLength(IMkvReader*, long long, long&);
+long long ReadUInt(IMkvReader*, long long, long&);
+long long UnserializeUInt(IMkvReader*, long long pos, long long size);
+
+long UnserializeFloat(IMkvReader*, long long pos, long long size, double&);
+long UnserializeInt(IMkvReader*, long long pos, long len, long long& result);
+
+long UnserializeString(
+        IMkvReader*,
+        long long pos,
+        long long size,
+        char*& str);
+
+long ParseElementHeader(
+    IMkvReader* pReader,
+    long long& pos,  //consume id and size fields
+    long long stop,  //if you know size of element's parent
+    long long& id,
+    long long& size);
+
+bool Match(IMkvReader*, long long&, unsigned long, long long&);
+bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&);
+
+void GetVersion(int& major, int& minor, int& build, int& revision);
+
+struct EBMLHeader
+{
+    EBMLHeader();
+    ~EBMLHeader();
+    long long m_version;
+    long long m_readVersion;
+    long long m_maxIdLength;
+    long long m_maxSizeLength;
+    char* m_docType;
+    long long m_docTypeVersion;
+    long long m_docTypeReadVersion;
+
+    long long Parse(IMkvReader*, long long&);
+    void Init();
+};
+
+
+class Segment;
+class Track;
+class Cluster;
+
+class Block
+{
+    Block(const Block&);
+    Block& operator=(const Block&);
+
+public:
+    const long long m_start;
+    const long long m_size;
+
+    Block(long long start, long long size, long long discard_padding);
+    ~Block();
+
+    long Parse(const Cluster*);
+
+    long long GetTrackNumber() const;
+    long long GetTimeCode(const Cluster*) const;  //absolute, but not scaled
+    long long GetTime(const Cluster*) const;      //absolute, and scaled (ns)
+    bool IsKey() const;
+    void SetKey(bool);
+    bool IsInvisible() const;
+
+    enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml };
+    Lacing GetLacing() const;
+
+    int GetFrameCount() const;  //to index frames: [0, count)
+
+    struct Frame
+    {
+        long long pos;  //absolute offset
+        long len;
+
+        long Read(IMkvReader*, unsigned char*) const;
+    };
+
+    const Frame& GetFrame(int frame_index) const;
+
+    long long GetDiscardPadding() const;
+
+private:
+    long long m_track;   //Track::Number()
+    short m_timecode;  //relative to cluster
+    unsigned char m_flags;
+
+    Frame* m_frames;
+    int m_frame_count;
+
+protected:
+    const long long m_discard_padding;
+};
+
+
+class BlockEntry
+{
+    BlockEntry(const BlockEntry&);
+    BlockEntry& operator=(const BlockEntry&);
+
+protected:
+    BlockEntry(Cluster*, long index);
+
+public:
+    virtual ~BlockEntry();
+
+    bool EOS() const;
+    const Cluster* GetCluster() const;
+    long GetIndex() const;
+    virtual const Block* GetBlock() const = 0;
+
+    enum Kind { kBlockEOS, kBlockSimple, kBlockGroup };
+    virtual Kind GetKind() const = 0;
+
+protected:
+    Cluster* const m_pCluster;
+    const long m_index;
+
+};
+
+
+class SimpleBlock : public BlockEntry
+{
+    SimpleBlock(const SimpleBlock&);
+    SimpleBlock& operator=(const SimpleBlock&);
+
+public:
+    SimpleBlock(Cluster*, long index, long long start, long long size);
+    long Parse();
+
+    Kind GetKind() const;
+    const Block* GetBlock() const;
+
+protected:
+    Block m_block;
+
+};
+
+
+class BlockGroup : public BlockEntry
+{
+    BlockGroup(const BlockGroup&);
+    BlockGroup& operator=(const BlockGroup&);
+
+public:
+    BlockGroup(
+        Cluster*,
+        long index,
+        long long block_start, //absolute pos of block's payload
+        long long block_size,  //size of block's payload
+        long long prev,
+        long long next,
+        long long duration,
+        long long discard_padding);
+
+    long Parse();
+
+    Kind GetKind() const;
+    const Block* GetBlock() const;
+
+    long long GetPrevTimeCode() const;  //relative to block's time
+    long long GetNextTimeCode() const;  //as above
+    long long GetDurationTimeCode() const;
+
+private:
+    Block m_block;
+    const long long m_prev;
+    const long long m_next;
+    const long long m_duration;
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+// Elements used to describe if the track data has been encrypted or
+// compressed with zlib or header stripping.
+class ContentEncoding {
+public:
+    enum {
+      kCTR = 1
+    };
+
+    ContentEncoding();
+    ~ContentEncoding();
+
+    // ContentCompression element names
+    struct ContentCompression {
+        ContentCompression();
+        ~ContentCompression();
+
+        unsigned long long algo;
+        unsigned char* settings;
+        long long settings_len;
+    };
+
+    // ContentEncAESSettings element names
+    struct ContentEncAESSettings {
+      ContentEncAESSettings() : cipher_mode(kCTR) {}
+      ~ContentEncAESSettings() {}
+
+      unsigned long long cipher_mode;
+    };
+
+    // ContentEncryption element names
+    struct ContentEncryption {
+        ContentEncryption();
+        ~ContentEncryption();
+
+        unsigned long long algo;
+        unsigned char* key_id;
+        long long key_id_len;
+        unsigned char* signature;
+        long long signature_len;
+        unsigned char* sig_key_id;
+        long long sig_key_id_len;
+        unsigned long long sig_algo;
+        unsigned long long sig_hash_algo;
+
+        ContentEncAESSettings aes_settings;
+    };
+
+    // Returns ContentCompression represented by |idx|. Returns NULL if |idx|
+    // is out of bounds.
+    const ContentCompression* GetCompressionByIndex(unsigned long idx) const;
+
+    // Returns number of ContentCompression elements in this ContentEncoding
+    // element.
+    unsigned long GetCompressionCount() const;
+
+    // Parses the ContentCompression element from |pReader|. |start| is the
+    // starting offset of the ContentCompression payload. |size| is the size in
+    // bytes of the ContentCompression payload. |compression| is where the parsed
+    // values will be stored.
+    long ParseCompressionEntry(long long start,
+                               long long size,
+                               IMkvReader* pReader,
+                               ContentCompression* compression);
+
+    // Returns ContentEncryption represented by |idx|. Returns NULL if |idx|
+    // is out of bounds.
+    const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const;
+
+    // Returns number of ContentEncryption elements in this ContentEncoding
+    // element.
+    unsigned long GetEncryptionCount() const;
+
+    // Parses the ContentEncAESSettings element from |pReader|. |start| is the
+    // starting offset of the ContentEncAESSettings payload. |size| is the
+    // size in bytes of the ContentEncAESSettings payload. |encryption| is
+    // where the parsed values will be stored.
+    long ParseContentEncAESSettingsEntry(long long start,
+                                         long long size,
+                                         IMkvReader* pReader,
+                                         ContentEncAESSettings* aes);
+
+    // Parses the ContentEncoding element from |pReader|. |start| is the
+    // starting offset of the ContentEncoding payload. |size| is the size in
+    // bytes of the ContentEncoding payload. Returns true on success.
+    long ParseContentEncodingEntry(long long start,
+                                   long long size,
+                                   IMkvReader* pReader);
+
+    // Parses the ContentEncryption element from |pReader|. |start| is the
+    // starting offset of the ContentEncryption payload. |size| is the size in
+    // bytes of the ContentEncryption payload. |encryption| is where the parsed
+    // values will be stored.
+    long ParseEncryptionEntry(long long start,
+                              long long size,
+                              IMkvReader* pReader,
+                              ContentEncryption* encryption);
+
+    unsigned long long encoding_order() const { return encoding_order_; }
+    unsigned long long encoding_scope() const { return encoding_scope_; }
+    unsigned long long encoding_type() const { return encoding_type_; }
+
+private:
+    // Member variables for list of ContentCompression elements.
+    ContentCompression** compression_entries_;
+    ContentCompression** compression_entries_end_;
+
+    // Member variables for list of ContentEncryption elements.
+    ContentEncryption** encryption_entries_;
+    ContentEncryption** encryption_entries_end_;
+
+    // ContentEncoding element names
+    unsigned long long encoding_order_;
+    unsigned long long encoding_scope_;
+    unsigned long long encoding_type_;
+
+    // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
+    ContentEncoding(const ContentEncoding&);
+    ContentEncoding& operator=(const ContentEncoding&);
+};
+
+class Track
+{
+    Track(const Track&);
+    Track& operator=(const Track&);
+
+public:
+    class Info;
+    static long Create(
+        Segment*,
+        const Info&,
+        long long element_start,
+        long long element_size,
+        Track*&);
+
+    enum Type {
+        kVideo = 1,
+        kAudio = 2,
+        kSubtitle = 0x11,
+        kMetadata = 0x21
+     };
+
+    Segment* const m_pSegment;
+    const long long m_element_start;
+    const long long m_element_size;
+    virtual ~Track();
+
+    long GetType() const;
+    long GetNumber() const;
+    unsigned long long GetUid() const;
+    const char* GetNameAsUTF8() const;
+    const char* GetLanguage() const;
+    const char* GetCodecNameAsUTF8() const;
+    const char* GetCodecId() const;
+    const unsigned char* GetCodecPrivate(size_t&) const;
+    bool GetLacing() const;
+    unsigned long long GetDefaultDuration() const;
+    unsigned long long GetCodecDelay() const;
+    unsigned long long GetSeekPreRoll() const;
+
+    const BlockEntry* GetEOS() const;
+
+    struct Settings
+    {
+        long long start;
+        long long size;
+    };
+
+    class Info
+    {
+    public:
+        Info();
+        ~Info();
+        int Copy(Info&) const;
+        void Clear();
+        long type;
+        long number;
+        unsigned long long uid;
+        unsigned long long defaultDuration;
+        unsigned long long codecDelay;
+        unsigned long long seekPreRoll;
+        char* nameAsUTF8;
+        char* language;
+        char* codecId;
+        char* codecNameAsUTF8;
+        unsigned char* codecPrivate;
+        size_t codecPrivateSize;
+        bool lacing;
+        Settings settings;
+
+    private:
+        Info(const Info&);
+        Info& operator=(const Info&);
+        int CopyStr(char* Info::*str, Info&) const;
+    };
+
+    long GetFirst(const BlockEntry*&) const;
+    long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const;
+    virtual bool VetEntry(const BlockEntry*) const;
+    virtual long Seek(long long time_ns, const BlockEntry*&) const;
+
+    const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const;
+    unsigned long GetContentEncodingCount() const;
+
+    long ParseContentEncodingsEntry(long long start, long long size);
+
+protected:
+    Track(
+        Segment*,
+        long long element_start,
+        long long element_size);
+
+    Info m_info;
+
+    class EOSBlock : public BlockEntry
+    {
+    public:
+        EOSBlock();
+
+        Kind GetKind() const;
+        const Block* GetBlock() const;
+    };
+
+    EOSBlock m_eos;
+
+private:
+    ContentEncoding** content_encoding_entries_;
+    ContentEncoding** content_encoding_entries_end_;
+};
+
+
+class VideoTrack : public Track
+{
+    VideoTrack(const VideoTrack&);
+    VideoTrack& operator=(const VideoTrack&);
+
+    VideoTrack(
+        Segment*,
+        long long element_start,
+        long long element_size);
+
+public:
+    static long Parse(
+        Segment*,
+        const Info&,
+        long long element_start,
+        long long element_size,
+        VideoTrack*&);
+
+    long long GetWidth() const;
+    long long GetHeight() const;
+    double GetFrameRate() const;
+
+    bool VetEntry(const BlockEntry*) const;
+    long Seek(long long time_ns, const BlockEntry*&) const;
+
+private:
+    long long m_width;
+    long long m_height;
+    double m_rate;
+
+};
+
+
+class AudioTrack : public Track
+{
+    AudioTrack(const AudioTrack&);
+    AudioTrack& operator=(const AudioTrack&);
+
+    AudioTrack(
+        Segment*,
+        long long element_start,
+        long long element_size);
+public:
+    static long Parse(
+        Segment*,
+        const Info&,
+        long long element_start,
+        long long element_size,
+        AudioTrack*&);
+
+    double GetSamplingRate() const;
+    long long GetChannels() const;
+    long long GetBitDepth() const;
+
+private:
+    double m_rate;
+    long long m_channels;
+    long long m_bitDepth;
+};
+
+
+class Tracks
+{
+    Tracks(const Tracks&);
+    Tracks& operator=(const Tracks&);
+
+public:
+    Segment* const m_pSegment;
+    const long long m_start;
+    const long long m_size;
+    const long long m_element_start;
+    const long long m_element_size;
+
+    Tracks(
+        Segment*,
+        long long start,
+        long long size,
+        long long element_start,
+        long long element_size);
+
+    ~Tracks();
+
+    long Parse();
+
+    unsigned long GetTracksCount() const;
+
+    const Track* GetTrackByNumber(long tn) const;
+    const Track* GetTrackByIndex(unsigned long idx) const;
+
+private:
+    Track** m_trackEntries;
+    Track** m_trackEntriesEnd;
+
+    long ParseTrackEntry(
+        long long payload_start,
+        long long payload_size,
+        long long element_start,
+        long long element_size,
+        Track*&) const;
+
+};
+
+
+class Chapters
+{
+    Chapters(const Chapters&);
+    Chapters& operator=(const Chapters&);
+
+public:
+    Segment* const m_pSegment;
+    const long long m_start;
+    const long long m_size;
+    const long long m_element_start;
+    const long long m_element_size;
+
+    Chapters(
+        Segment*,
+        long long payload_start,
+        long long payload_size,
+        long long element_start,
+        long long element_size);
+
+    ~Chapters();
+
+    long Parse();
+
+    class Atom;
+    class Edition;
+
+    class Display
+    {
+        friend class Atom;
+        Display();
+        Display(const Display&);
+        ~Display();
+        Display& operator=(const Display&);
+    public:
+        const char* GetString() const;
+        const char* GetLanguage() const;
+        const char* GetCountry() const;
+    private:
+        void Init();
+        void ShallowCopy(Display&) const;
+        void Clear();
+        long Parse(IMkvReader*, long long pos, long long size);
+
+        char* m_string;
+        char* m_language;
+        char* m_country;
+    };
+
+    class Atom
+    {
+        friend class Edition;
+        Atom();
+        Atom(const Atom&);
+        ~Atom();
+        Atom& operator=(const Atom&);
+    public:
+        unsigned long long GetUID() const;
+        const char* GetStringUID() const;
+
+        long long GetStartTimecode() const;
+        long long GetStopTimecode() const;
+
+        long long GetStartTime(const Chapters*) const;
+        long long GetStopTime(const Chapters*) const;
+
+        int GetDisplayCount() const;
+        const Display* GetDisplay(int index) const;
+    private:
+        void Init();
+        void ShallowCopy(Atom&) const;
+        void Clear();
+        long Parse(IMkvReader*, long long pos, long long size);
+        static long long GetTime(const Chapters*, long long timecode);
+
+        long ParseDisplay(IMkvReader*, long long pos, long long size);
+        bool ExpandDisplaysArray();
+
+        char* m_string_uid;
+        unsigned long long m_uid;
+        long long m_start_timecode;
+        long long m_stop_timecode;
+
+        Display* m_displays;
+        int m_displays_size;
+        int m_displays_count;
+    };
+
+    class Edition
+    {
+        friend class Chapters;
+        Edition();
+        Edition(const Edition&);
+        ~Edition();
+        Edition& operator=(const Edition&);
+    public:
+        int GetAtomCount() const;
+        const Atom* GetAtom(int index) const;
+    private:
+        void Init();
+        void ShallowCopy(Edition&) const;
+        void Clear();
+        long Parse(IMkvReader*, long long pos, long long size);
+
+        long ParseAtom(IMkvReader*, long long pos, long long size);
+        bool ExpandAtomsArray();
+
+        Atom* m_atoms;
+        int m_atoms_size;
+        int m_atoms_count;
+    };
+
+    int GetEditionCount() const;
+    const Edition* GetEdition(int index) const;
+
+private:
+    long ParseEdition(long long pos, long long size);
+    bool ExpandEditionsArray();
+
+    Edition* m_editions;
+    int m_editions_size;
+    int m_editions_count;
+
+};
+
+
+class SegmentInfo
+{
+    SegmentInfo(const SegmentInfo&);
+    SegmentInfo& operator=(const SegmentInfo&);
+
+public:
+    Segment* const m_pSegment;
+    const long long m_start;
+    const long long m_size;
+    const long long m_element_start;
+    const long long m_element_size;
+
+    SegmentInfo(
+        Segment*,
+        long long start,
+        long long size,
+        long long element_start,
+        long long element_size);
+
+    ~SegmentInfo();
+
+    long Parse();
+
+    long long GetTimeCodeScale() const;
+    long long GetDuration() const;  //scaled
+    const char* GetMuxingAppAsUTF8() const;
+    const char* GetWritingAppAsUTF8() const;
+    const char* GetTitleAsUTF8() const;
+
+private:
+    long long m_timecodeScale;
+    double m_duration;
+    char* m_pMuxingAppAsUTF8;
+    char* m_pWritingAppAsUTF8;
+    char* m_pTitleAsUTF8;
+};
+
+
+class SeekHead
+{
+    SeekHead(const SeekHead&);
+    SeekHead& operator=(const SeekHead&);
+
+public:
+    Segment* const m_pSegment;
+    const long long m_start;
+    const long long m_size;
+    const long long m_element_start;
+    const long long m_element_size;
+
+    SeekHead(
+        Segment*,
+        long long start,
+        long long size,
+        long long element_start,
+        long long element_size);
+
+    ~SeekHead();
+
+    long Parse();
+
+    struct Entry
+    {
+        //the SeekHead entry payload
+        long long id;
+        long long pos;
+
+        //absolute pos of SeekEntry ID
+        long long element_start;
+
+        //SeekEntry ID size + size size + payload
+        long long element_size;
+    };
+
+    int GetCount() const;
+    const Entry* GetEntry(int idx) const;
+
+    struct VoidElement
+    {
+        //absolute pos of Void ID
+        long long element_start;
+
+        //ID size + size size + payload size
+        long long element_size;
+    };
+
+    int GetVoidElementCount() const;
+    const VoidElement* GetVoidElement(int idx) const;
+
+private:
+    Entry* m_entries;
+    int m_entry_count;
+
+    VoidElement* m_void_elements;
+    int m_void_element_count;
+
+    static bool ParseEntry(
+        IMkvReader*,
+        long long pos,  //payload
+        long long size,
+        Entry*);
+
+};
+
+class Cues;
+class CuePoint
+{
+    friend class Cues;
+
+    CuePoint(long, long long);
+    ~CuePoint();
+
+    CuePoint(const CuePoint&);
+    CuePoint& operator=(const CuePoint&);
+
+public:
+    long long m_element_start;
+    long long m_element_size;
+
+    void Load(IMkvReader*);
+
+    long long GetTimeCode() const;      //absolute but unscaled
+    long long GetTime(const Segment*) const;  //absolute and scaled (ns units)
+
+    struct TrackPosition
+    {
+        long long m_track;
+        long long m_pos;  //of cluster
+        long long m_block;
+        //codec_state  //defaults to 0
+        //reference = clusters containing req'd referenced blocks
+        //  reftime = timecode of the referenced block
+
+        void Parse(IMkvReader*, long long, long long);
+    };
+
+    const TrackPosition* Find(const Track*) const;
+
+private:
+    const long m_index;
+    long long m_timecode;
+    TrackPosition* m_track_positions;
+    size_t m_track_positions_count;
+
+};
+
+
+class Cues
+{
+    friend class Segment;
+
+    Cues(
+        Segment*,
+        long long start,
+        long long size,
+        long long element_start,
+        long long element_size);
+    ~Cues();
+
+    Cues(const Cues&);
+    Cues& operator=(const Cues&);
+
+public:
+    Segment* const m_pSegment;
+    const long long m_start;
+    const long long m_size;
+    const long long m_element_start;
+    const long long m_element_size;
+
+    bool Find(  //lower bound of time_ns
+        long long time_ns,
+        const Track*,
+        const CuePoint*&,
+        const CuePoint::TrackPosition*&) const;
+
+#if 0
+    bool FindNext(  //upper_bound of time_ns
+        long long time_ns,
+        const Track*,
+        const CuePoint*&,
+        const CuePoint::TrackPosition*&) const;
+#endif
+
+    const CuePoint* GetFirst() const;
+    const CuePoint* GetLast() const;
+    const CuePoint* GetNext(const CuePoint*) const;
+
+    const BlockEntry* GetBlock(
+                        const CuePoint*,
+                        const CuePoint::TrackPosition*) const;
+
+    bool LoadCuePoint() const;
+    long GetCount() const;  //loaded only
+    //long GetTotal() const;  //loaded + preloaded
+    bool DoneParsing() const;
+
+private:
+    void Init() const;
+    void PreloadCuePoint(long&, long long) const;
+
+    mutable CuePoint** m_cue_points;
+    mutable long m_count;
+    mutable long m_preload_count;
+    mutable long long m_pos;
+
+};
+
+
+class Cluster
+{
+    friend class Segment;
+
+    Cluster(const Cluster&);
+    Cluster& operator=(const Cluster&);
+
+public:
+    Segment* const m_pSegment;
+
+public:
+    static Cluster* Create(
+        Segment*,
+        long index,       //index in segment
+        long long off);   //offset relative to segment
+        //long long element_size);
+
+    Cluster();  //EndOfStream
+    ~Cluster();
+
+    bool EOS() const;
+
+    long long GetTimeCode() const;   //absolute, but not scaled
+    long long GetTime() const;       //absolute, and scaled (nanosecond units)
+    long long GetFirstTime() const;  //time (ns) of first (earliest) block
+    long long GetLastTime() const;   //time (ns) of last (latest) block
+
+    long GetFirst(const BlockEntry*&) const;
+    long GetLast(const BlockEntry*&) const;
+    long GetNext(const BlockEntry* curr, const BlockEntry*& next) const;
+
+    const BlockEntry* GetEntry(const Track*, long long ns = -1) const;
+    const BlockEntry* GetEntry(
+        const CuePoint&,
+        const CuePoint::TrackPosition&) const;
+    //const BlockEntry* GetMaxKey(const VideoTrack*) const;
+
+//    static bool HasBlockEntries(const Segment*, long long);
+
+    static long HasBlockEntries(
+            const Segment*,
+            long long idoff,
+            long long& pos,
+            long& size);
+
+    long GetEntryCount() const;
+
+    long Load(long long& pos, long& size) const;
+
+    long Parse(long long& pos, long& size) const;
+    long GetEntry(long index, const mkvparser::BlockEntry*&) const;
+
+protected:
+    Cluster(
+        Segment*,
+        long index,
+        long long element_start);
+        //long long element_size);
+
+public:
+    const long long m_element_start;
+    long long GetPosition() const;  //offset relative to segment
+
+    long GetIndex() const;
+    long long GetElementSize() const;
+    //long long GetPayloadSize() const;
+
+    //long long Unparsed() const;
+
+private:
+    long m_index;
+    mutable long long m_pos;
+    //mutable long long m_size;
+    mutable long long m_element_size;
+    mutable long long m_timecode;
+    mutable BlockEntry** m_entries;
+    mutable long m_entries_size;
+    mutable long m_entries_count;
+
+    long ParseSimpleBlock(long long, long long&, long&);
+    long ParseBlockGroup(long long, long long&, long&);
+
+    long CreateBlock(long long id, long long pos, long long size,
+                     long long discard_padding);
+    long CreateBlockGroup(long long start_offset, long long size,
+                          long long discard_padding);
+    long CreateSimpleBlock(long long, long long);
+
+};
+
+
+class Segment
+{
+    friend class Cues;
+    friend class Track;
+    friend class VideoTrack;
+
+    Segment(const Segment&);
+    Segment& operator=(const Segment&);
+
+private:
+    Segment(
+        IMkvReader*,
+        long long elem_start,
+        //long long elem_size,
+        long long pos,
+        long long size);
+
+public:
+    IMkvReader* const m_pReader;
+    const long long m_element_start;
+    //const long long m_element_size;
+    const long long m_start;  //posn of segment payload
+    const long long m_size;   //size of segment payload
+    Cluster m_eos;  //TODO: make private?
+
+    static long long CreateInstance(IMkvReader*, long long, Segment*&);
+    ~Segment();
+
+    long Load();  //loads headers and all clusters
+
+    //for incremental loading
+    //long long Unparsed() const;
+    bool DoneParsing() const;
+    long long ParseHeaders();  //stops when first cluster is found
+    //long FindNextCluster(long long& pos, long& size) const;
+    long LoadCluster(long long& pos, long& size);  //load one cluster
+    long LoadCluster();
+
+    long ParseNext(
+            const Cluster* pCurr,
+            const Cluster*& pNext,
+            long long& pos,
+            long& size);
+
+#if 0
+    //This pair parses one cluster, but only changes the state of the
+    //segment object when the cluster is actually added to the index.
+    long ParseCluster(long long& cluster_pos, long long& new_pos) const;
+    bool AddCluster(long long cluster_pos, long long new_pos);
+#endif
+
+    const SeekHead* GetSeekHead() const;
+    const Tracks* GetTracks() const;
+    const SegmentInfo* GetInfo() const;
+    const Cues* GetCues() const;
+    const Chapters* GetChapters() const;
+
+    long long GetDuration() const;
+
+    unsigned long GetCount() const;
+    const Cluster* GetFirst() const;
+    const Cluster* GetLast() const;
+    const Cluster* GetNext(const Cluster*);
+
+    const Cluster* FindCluster(long long time_nanoseconds) const;
+    //const BlockEntry* Seek(long long time_nanoseconds, const Track*) const;
+
+    const Cluster* FindOrPreloadCluster(long long pos);
+
+    long ParseCues(
+        long long cues_off,  //offset relative to start of segment
+        long long& parse_pos,
+        long& parse_len);
+
+private:
+
+    long long m_pos;  //absolute file posn; what has been consumed so far
+    Cluster* m_pUnknownSize;
+
+    SeekHead* m_pSeekHead;
+    SegmentInfo* m_pInfo;
+    Tracks* m_pTracks;
+    Cues* m_pCues;
+    Chapters* m_pChapters;
+    Cluster** m_clusters;
+    long m_clusterCount;         //number of entries for which m_index >= 0
+    long m_clusterPreloadCount;  //number of entries for which m_index < 0
+    long m_clusterSize;          //array size
+
+    long DoLoadCluster(long long&, long&);
+    long DoLoadClusterUnknownSize(long long&, long&);
+    long DoParseNext(const Cluster*&, long long&, long&);
+
+    void AppendCluster(Cluster*);
+    void PreloadCluster(Cluster*, ptrdiff_t);
+
+    //void ParseSeekHead(long long pos, long long size);
+    //void ParseSeekEntry(long long pos, long long size);
+    //void ParseCues(long long);
+
+    const BlockEntry* GetBlock(
+        const CuePoint&,
+        const CuePoint::TrackPosition&);
+
+};
+
+}  //end namespace mkvparser
+
+inline long mkvparser::Segment::LoadCluster()
+{
+    long long pos;
+    long size;
+
+    return LoadCluster(pos, size);
+}
+
+#endif  //MKVPARSER_HPP
diff --git a/third_party/libwebm/mkvreader.cpp b/third_party/libwebm/mkvreader.cpp
new file mode 100644 (file)
index 0000000..cb3567f
--- /dev/null
@@ -0,0 +1,128 @@
+// Copyright (c) 2010 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 "mkvreader.hpp"
+
+#include <cassert>
+
+namespace mkvparser
+{
+
+MkvReader::MkvReader() :
+    m_file(NULL)
+{
+}
+
+MkvReader::~MkvReader()
+{
+    Close();
+}
+
+int MkvReader::Open(const char* fileName)
+{
+    if (fileName == NULL)
+        return -1;
+
+    if (m_file)
+        return -1;
+
+#ifdef _MSC_VER
+    const errno_t e = fopen_s(&m_file, fileName, "rb");
+
+    if (e)
+        return -1;  //error
+#else
+    m_file = fopen(fileName, "rb");
+
+    if (m_file == NULL)
+        return -1;
+#endif
+
+#ifdef _MSC_VER
+    int status = _fseeki64(m_file, 0L, SEEK_END);
+
+    if (status)
+        return -1;  //error
+
+    m_length = _ftelli64(m_file);
+#else
+    fseek(m_file, 0L, SEEK_END);
+    m_length = ftell(m_file);
+#endif
+    assert(m_length >= 0);
+
+#ifdef _MSC_VER
+    status = _fseeki64(m_file, 0L, SEEK_SET);
+
+    if (status)
+        return -1;  //error
+#else
+    fseek(m_file, 0L, SEEK_SET);
+#endif
+
+    return 0;
+}
+
+void MkvReader::Close()
+{
+    if (m_file != NULL)
+    {
+        fclose(m_file);
+        m_file = NULL;
+    }
+}
+
+int MkvReader::Length(long long* total, long long* available)
+{
+    if (m_file == NULL)
+        return -1;
+
+    if (total)
+        *total = m_length;
+
+    if (available)
+        *available = m_length;
+
+    return 0;
+}
+
+int MkvReader::Read(long long offset, long len, unsigned char* buffer)
+{
+    if (m_file == NULL)
+        return -1;
+
+    if (offset < 0)
+        return -1;
+
+    if (len < 0)
+        return -1;
+
+    if (len == 0)
+        return 0;
+
+    if (offset >= m_length)
+        return -1;
+
+#ifdef _MSC_VER
+    const int status = _fseeki64(m_file, offset, SEEK_SET);
+
+    if (status)
+        return -1;  //error
+#else
+    fseek(m_file, offset, SEEK_SET);
+#endif
+
+    const size_t size = fread(buffer, 1, len, m_file);
+
+    if (size < size_t(len))
+        return -1;  //error
+
+    return 0;  //success
+}
+
+}  //end namespace mkvparser
diff --git a/third_party/libwebm/mkvreader.hpp b/third_party/libwebm/mkvreader.hpp
new file mode 100644 (file)
index 0000000..adcc29f
--- /dev/null
@@ -0,0 +1,38 @@
+// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVREADER_HPP
+#define MKVREADER_HPP
+
+#include "mkvparser.hpp"
+#include <cstdio>
+
+namespace mkvparser
+{
+
+class MkvReader : public IMkvReader
+{
+    MkvReader(const MkvReader&);
+    MkvReader& operator=(const MkvReader&);
+public:
+    MkvReader();
+    virtual ~MkvReader();
+
+    int Open(const char*);
+    void Close();
+
+    virtual int Read(long long position, long length, unsigned char* buffer);
+    virtual int Length(long long* total, long long* available);
+private:
+    long long m_length;
+    FILE* m_file;
+};
+
+}  //end namespace mkvparser
+
+#endif //MKVREADER_HPP
diff --git a/third_party/libwebm/mkvwriter.cpp b/third_party/libwebm/mkvwriter.cpp
new file mode 100644 (file)
index 0000000..8de89a4
--- /dev/null
@@ -0,0 +1,97 @@
+// 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 "mkvwriter.hpp"
+
+#ifdef _MSC_VER
+#include <share.h>  // for _SH_DENYWR
+#endif
+
+#include <new>
+
+namespace mkvmuxer {
+
+MkvWriter::MkvWriter() : file_(NULL), writer_owns_file_(true) {
+}
+
+MkvWriter::MkvWriter(FILE* fp): file_(fp), writer_owns_file_(false) {
+}
+
+MkvWriter::~MkvWriter() {
+  Close();
+}
+
+int32 MkvWriter::Write(const void* buffer, uint32 length) {
+  if (!file_)
+    return -1;
+
+  if (length == 0)
+    return 0;
+
+  if (buffer == NULL)
+    return -1;
+
+  const size_t bytes_written = fwrite(buffer, 1, length, file_);
+
+  return (bytes_written == length) ? 0 : -1;
+}
+
+bool MkvWriter::Open(const char* filename) {
+  if (filename == NULL)
+    return false;
+
+  if (file_)
+    return false;
+
+#ifdef _MSC_VER
+  file_ = _fsopen(filename, "wb", _SH_DENYWR);
+#else
+  file_ = fopen(filename, "wb");
+#endif
+  if (file_ == NULL)
+    return false;
+  return true;
+}
+
+void MkvWriter::Close() {
+  if (file_ && writer_owns_file_) {
+    fclose(file_);
+  }
+  file_ = NULL;
+}
+
+int64 MkvWriter::Position() const {
+  if (!file_)
+    return 0;
+
+#ifdef _MSC_VER
+    return _ftelli64(file_);
+#else
+    return ftell(file_);
+#endif
+}
+
+int32 MkvWriter::Position(int64 position) {
+  if (!file_)
+    return -1;
+
+#ifdef _MSC_VER
+    return _fseeki64(file_, position, SEEK_SET);
+#else
+    return fseek(file_, position, SEEK_SET);
+#endif
+}
+
+bool MkvWriter::Seekable() const {
+  return true;
+}
+
+void MkvWriter::ElementStartNotify(uint64, int64) {
+}
+
+}  // namespace mkvmuxer
diff --git a/third_party/libwebm/mkvwriter.hpp b/third_party/libwebm/mkvwriter.hpp
new file mode 100644 (file)
index 0000000..524e0f7
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVWRITER_HPP
+#define MKVWRITER_HPP
+
+#include <stdio.h>
+
+#include "mkvmuxer.hpp"
+#include "mkvmuxertypes.hpp"
+
+namespace mkvmuxer {
+
+// Default implementation of the IMkvWriter interface on Windows.
+class MkvWriter : public IMkvWriter {
+ public:
+  MkvWriter();
+  MkvWriter(FILE* fp);
+  virtual ~MkvWriter();
+
+  // IMkvWriter interface
+  virtual int64 Position() const;
+  virtual int32 Position(int64 position);
+  virtual bool Seekable() const;
+  virtual int32 Write(const void* buffer, uint32 length);
+  virtual void ElementStartNotify(uint64 element_id, int64 position);
+
+  // Creates and opens a file for writing. |filename| is the name of the file
+  // to open. This function will overwrite the contents of |filename|. Returns
+  // true on success.
+  bool Open(const char* filename);
+
+  // Closes an opened file.
+  void Close();
+
+ private:
+  // File handle to output file.
+  FILE* file_;
+  bool writer_owns_file_;
+
+  LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter);
+};
+
+}  //end namespace mkvmuxer
+
+#endif // MKVWRITER_HPP
diff --git a/third_party/libwebm/webmids.hpp b/third_party/libwebm/webmids.hpp
new file mode 100644 (file)
index 0000000..65fab96
--- /dev/null
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS.  All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef WEBMIDS_HPP
+#define WEBMIDS_HPP
+
+namespace mkvmuxer {
+
+enum MkvId {
+  kMkvEBML                    = 0x1A45DFA3,
+  kMkvEBMLVersion             = 0x4286,
+  kMkvEBMLReadVersion         = 0x42F7,
+  kMkvEBMLMaxIDLength         = 0x42F2,
+  kMkvEBMLMaxSizeLength       = 0x42F3,
+  kMkvDocType                 = 0x4282,
+  kMkvDocTypeVersion          = 0x4287,
+  kMkvDocTypeReadVersion      = 0x4285,
+  kMkvVoid                    = 0xEC,
+  kMkvSignatureSlot           = 0x1B538667,
+  kMkvSignatureAlgo           = 0x7E8A,
+  kMkvSignatureHash           = 0x7E9A,
+  kMkvSignaturePublicKey      = 0x7EA5,
+  kMkvSignature               = 0x7EB5,
+  kMkvSignatureElements       = 0x7E5B,
+  kMkvSignatureElementList    = 0x7E7B,
+  kMkvSignedElement           = 0x6532,
+  //segment
+  kMkvSegment                 = 0x18538067,
+  //Meta Seek Information
+  kMkvSeekHead                = 0x114D9B74,
+  kMkvSeek                    = 0x4DBB,
+  kMkvSeekID                  = 0x53AB,
+  kMkvSeekPosition            = 0x53AC,
+  //Segment Information
+  kMkvInfo                    = 0x1549A966,
+  kMkvTimecodeScale           = 0x2AD7B1,
+  kMkvDuration                = 0x4489,
+  kMkvDateUTC                 = 0x4461,
+  kMkvMuxingApp               = 0x4D80,
+  kMkvWritingApp              = 0x5741,
+  //Cluster
+  kMkvCluster                 = 0x1F43B675,
+  kMkvTimecode                = 0xE7,
+  kMkvPrevSize                = 0xAB,
+  kMkvBlockGroup              = 0xA0,
+  kMkvBlock                   = 0xA1,
+  kMkvBlockDuration           = 0x9B,
+  kMkvReferenceBlock          = 0xFB,
+  kMkvLaceNumber              = 0xCC,
+  kMkvSimpleBlock             = 0xA3,
+  kMkvBlockAdditions          = 0x75A1,
+  kMkvBlockMore               = 0xA6,
+  kMkvBlockAddID              = 0xEE,
+  kMkvBlockAdditional         = 0xA5,
+  kMkvDiscardPadding          = 0x75A2,
+  //Track
+  kMkvTracks                  = 0x1654AE6B,
+  kMkvTrackEntry              = 0xAE,
+  kMkvTrackNumber             = 0xD7,
+  kMkvTrackUID                = 0x73C5,
+  kMkvTrackType               = 0x83,
+  kMkvFlagEnabled             = 0xB9,
+  kMkvFlagDefault             = 0x88,
+  kMkvFlagForced              = 0x55AA,
+  kMkvFlagLacing              = 0x9C,
+  kMkvDefaultDuration         = 0x23E383,
+  kMkvMaxBlockAdditionID      = 0x55EE,
+  kMkvName                    = 0x536E,
+  kMkvLanguage                = 0x22B59C,
+  kMkvCodecID                 = 0x86,
+  kMkvCodecPrivate            = 0x63A2,
+  kMkvCodecName               = 0x258688,
+  kMkvCodecDelay              = 0x56AA,
+  kMkvSeekPreRoll             = 0x56BB,
+  //video
+  kMkvVideo                   = 0xE0,
+  kMkvFlagInterlaced          = 0x9A,
+  kMkvStereoMode              = 0x53B8,
+  kMkvAlphaMode               = 0x53C0,
+  kMkvPixelWidth              = 0xB0,
+  kMkvPixelHeight             = 0xBA,
+  kMkvPixelCropBottom         = 0x54AA,
+  kMkvPixelCropTop            = 0x54BB,
+  kMkvPixelCropLeft           = 0x54CC,
+  kMkvPixelCropRight          = 0x54DD,
+  kMkvDisplayWidth            = 0x54B0,
+  kMkvDisplayHeight           = 0x54BA,
+  kMkvDisplayUnit             = 0x54B2,
+  kMkvAspectRatioType         = 0x54B3,
+  kMkvFrameRate               = 0x2383E3,
+  //end video
+  //audio
+  kMkvAudio                   = 0xE1,
+  kMkvSamplingFrequency       = 0xB5,
+  kMkvOutputSamplingFrequency = 0x78B5,
+  kMkvChannels                = 0x9F,
+  kMkvBitDepth                = 0x6264,
+  //end audio
+  //ContentEncodings
+  kMkvContentEncodings        = 0x6D80,
+  kMkvContentEncoding         = 0x6240,
+  kMkvContentEncodingOrder    = 0x5031,
+  kMkvContentEncodingScope    = 0x5032,
+  kMkvContentEncodingType     = 0x5033,
+  kMkvContentEncryption       = 0x5035,
+  kMkvContentEncAlgo          = 0x47E1,
+  kMkvContentEncKeyID         = 0x47E2,
+  kMkvContentEncAESSettings   = 0x47E7,
+  kMkvAESSettingsCipherMode   = 0x47E8,
+  kMkvAESSettingsCipherInitData = 0x47E9,
+  //end ContentEncodings
+  //Cueing Data
+  kMkvCues                    = 0x1C53BB6B,
+  kMkvCuePoint                = 0xBB,
+  kMkvCueTime                 = 0xB3,
+  kMkvCueTrackPositions       = 0xB7,
+  kMkvCueTrack                = 0xF7,
+  kMkvCueClusterPosition      = 0xF1,
+  kMkvCueBlockNumber          = 0x5378,
+  //Chapters
+  kMkvChapters                = 0x1043A770,
+  kMkvEditionEntry            = 0x45B9,
+  kMkvChapterAtom             = 0xB6,
+  kMkvChapterUID              = 0x73C4,
+  kMkvChapterStringUID        = 0x5654,
+  kMkvChapterTimeStart        = 0x91,
+  kMkvChapterTimeEnd          = 0x92,
+  kMkvChapterDisplay          = 0x80,
+  kMkvChapString              = 0x85,
+  kMkvChapLanguage            = 0x437C,
+  kMkvChapCountry             = 0x437E
+};
+
+}  // end namespace mkvmuxer
+
+#endif // WEBMIDS_HPP