[Scummvm-git-logs] scummvm master -> 234e1cac4968eec2fb89df55b9a43808cf5b4295

sev- noreply at scummvm.org
Sun Mar 5 20:29:23 UTC 2023


This automated email contains information about 35 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
95e80c01bd VIDEO: Initial import of MKV parser
6f7d132fe9 VIDEO: Fix warnings
a4ab5b991f VIDEO: Added skeleton of VideoPlayer interface for MKV decoder
115231e59d CONFIGURE: Added detection for libvpx
53163f427a DEVTOOLS: Added libvpx support to create_project
fe62ed883d VIDEO: Implement ScummVM-compatible MkvReader and remove generic one
32055ab0b2 SLUDGE: Plugged in movie player
d2adb5b2a6 VIDEO: Initial work on MKV video initialization
91c0463145 SLUDGE: Close resources for now
c27f9c34ef VIDEO: Fixed bug in MkvReader::Length() and enhanced debug log messages
3afa3e4bd3 VIDEO: More work on MKV decoder
6f3f9ce973 VIDEO: Make MKVDecoder compilable
2f7d4acacd SLUDGE: Initial code of the movie player loop
233fb9b121 VIDEO: Playback code for MKV videos
32159c9549 VIDEO: Fix MKV decoder initialization
2cd44e2a79 SLUDGE: Rename constants and flag movie playing
ca8859c4cd VIDEO: MKV: Move video track initialising to a separate class
4613ab0fd9 VIDEO: MKV: Move audio track to separate class
b3dfca3cb1 VIDEO: YUV420 conversion for MKV decoder
0357ccc5d9 VIDEO: Moved video frame decoding to subclass in mkv decoder
2b15031f55 VIDEO: Fix MKV frames navigation. First visuals
7f8c9504f5 VIDEO: Simplified MKV parser loop
0485b06f0b SLUDGE: Change movie playing status to none once its done playing
5a086b1883 VIDEO: Fix a wrong if condition in MKV Decoder
2b05c1b925 VIDEO: Start working on audio playback in MKV Decoder
b0e2b8e02a VIDEO: Remove unneeded variables/leftovers from theora in MKV Decoder
50fac579fe SLUDGE: Add decoder.start() to actually start playing audio
66b21a5020 VIDEO: Add working sound support in MKV parser
b783a064e4 JANITORIAL: Cleanup MKV Decoder
492002291a SLUDGE: Remove old imported code
434d82a9bb SLUDGE: Only scale if required
a121c1034a SLUDGE: Close resources properly in stopMovie()
5351c8b33d SLUDGE: Convert unnecessary warnings to debugs
35c775113a JANITORIAL: Rename variables to match SCUMMVM's coding style
234e1cac49 SLUDGE: Skip movie incase scummvm isn't built with libvpx as a dependancy


Commit: 95e80c01bde5510608b087b3d0dd291270a7148f
    https://github.com/scummvm/scummvm/commit/95e80c01bde5510608b087b3d0dd291270a7148f
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Initial import of MKV parser

Taken from 9690075dccb7cbe25201bc74a467cdc6e3f41e7b from
https://github.com/webmproject/libwebm

Changed paths:
  A video/mkv/AUTHORS.TXT
  A video/mkv/LICENSE.TXT
  A video/mkv/PATENTS.TXT
  A video/mkv/README.libwebm
  A video/mkv/mkvparser.cpp
  A video/mkv/mkvparser.h
  A video/mkv/mkvreader.cpp
  A video/mkv/mkvreader.h
  A video/mkv/webmids.h
    video/module.mk


diff --git a/video/mkv/AUTHORS.TXT b/video/mkv/AUTHORS.TXT
new file mode 100644
index 00000000000..9686ac13eb0
--- /dev/null
+++ b/video/mkv/AUTHORS.TXT
@@ -0,0 +1,4 @@
+# Names should be added to this file like so:
+# Name or Organization <email address>
+
+Google Inc.
diff --git a/video/mkv/LICENSE.TXT b/video/mkv/LICENSE.TXT
new file mode 100644
index 00000000000..7a6f99547d4
--- /dev/null
+++ b/video/mkv/LICENSE.TXT
@@ -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/video/mkv/PATENTS.TXT b/video/mkv/PATENTS.TXT
new file mode 100644
index 00000000000..caedf607e95
--- /dev/null
+++ b/video/mkv/PATENTS.TXT
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+------------------------------------
+
+"These implementations" means the copyrightable works that implement the WebM
+codecs 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 these implementations of WebM, 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 these implementations of WebM. This grant does not include claims
+that would be infringed only as a consequence of further modification of these
+implementations. If you or your agent or exclusive licensee institute or order
+or agree to the institution of patent litigation or any other patent
+enforcement activity against any entity (including a cross-claim or
+counterclaim in a lawsuit) alleging that any of these implementations of WebM
+or any code incorporated within any of these implementations of WebM
+constitute direct or contributory patent infringement, or inducement of
+patent infringement, then any patent rights granted to you under this License
+for these implementations of WebM shall terminate as of the date such
+litigation is filed.
diff --git a/video/mkv/README.libwebm b/video/mkv/README.libwebm
new file mode 100644
index 00000000000..3406f80dbd3
--- /dev/null
+++ b/video/mkv/README.libwebm
@@ -0,0 +1,143 @@
+Building Libwebm
+
+To build libwebm you must first create project files. To do this run cmake
+and pass it the path to your libwebm repo.
+
+Makefile.unix can be used as a fallback on systems that cmake does not
+support.
+
+
+CMake Basics
+
+To generate project/make files for the default toolchain on your system simply
+run cmake with the path to the libwebm repo:
+
+$ cmake path/to/libwebm
+
+On Windows the above command will produce Visual Studio project files for the
+newest Visual Studio detected on the system. On Mac OS X and Linux systems, the
+above command will produce a makefile.
+
+To control what types of projects are generated the -G parameter is added to
+the cmake command line. This argument must be followed by the name of a
+generator. Running cmake with the --help argument will list the available
+generators for your system.
+
+On Mac OS X you would run the following command to generate Xcode projects:
+
+$ cmake path/to/libwebm -G Xcode
+
+On a Windows box you would run the following command to generate Visual Studio
+2013 projects:
+
+$ cmake path/to/libwebm -G "Visual Studio 12"
+
+To generate 64-bit Windows Visual Studio 2013 projects:
+
+$ cmake path/to/libwebm "Visual Studio 12 Win64"
+
+
+CMake Makefiles: Debugging and Optimization
+
+Unlike Visual Studio and Xcode projects, the build configuration for make builds
+is controlled when you run cmake. The following examples demonstrate various
+build configurations.
+
+Omitting the build type produces makefiles that use build flags containing
+neither optimization nor debug flags:
+$ cmake path/to/libwebm
+
+A makefile using release (optimized) flags is produced like this:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=release
+
+A release build with debug info can be produced as well:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=relwithdebinfo
+
+And your standard debug build will be produced using:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=debug
+
+
+Tests
+
+To enable libwebm tests add -DENABLE_TESTS=ON CMake generation command line. For
+example:
+
+$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON
+
+Libwebm tests depend on googletest. By default googletest is expected to be a
+sibling directory of the Libwebm repository. To change that, update your CMake
+command to be similar to the following:
+
+$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON \
+  -DGTEST_SRC_DIR=/path/to/googletest
+
+The tests rely upon the LIBWEBM_TEST_DATA_PATH environment variable to locate
+test input. The following example demonstrates running the muxer tests from the
+build directory:
+
+$ LIBWEBM_TEST_DATA_PATH=path/to/libwebm/testing/testdata ./mkvmuxer_tests
+
+Note: Libwebm Googletest integration was built with googletest from
+      https://github.com/google/googletest.git at git revision
+      ddb8012eb48bc203aa93dcc2b22c1db516302b29.
+
+
+CMake Include-what-you-use integration
+
+Include-what-you-use is an analysis tool that helps ensure libwebm includes the
+C/C++ header files actually in use. To enable the integration support
+ENABLE_IWYU must be turned on at cmake run time:
+
+$ cmake path/to/libwebm -G "Unix Makefiles" -DENABLE_IWYU=ON
+
+This adds the iwyu target to the build. To run include-what-you-use:
+
+$ make iwyu
+
+The following requirements must be met for ENABLE_IWYU to enable the iwyu
+target:
+
+1. include-what-you-use and iwyu_tool.py must be in your PATH.
+2. A python interpreter must be on the system and available to CMake.
+
+The values of the following variables are used to determine if the requirements
+have been met. Values to the right of the equals sign are what a successful run
+might look like:
+  iwyu_path=/path/to/iwyu_tool.py
+  iwyu_tool_path=/path/to/include-what-you-use
+  PYTHONINTERP_FOUND=TRUE
+
+An empty PYTHONINTERP_FOUND, or iwyu_path/iwyu_tool_path suffixed with NOTFOUND
+are failures.
+
+For Include-what-you-use setup instructions, see:
+https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/InstructionsForUsers.md
+
+If, when building the iwyu target, compile errors reporting failures loading
+standard include files occur, one solution can be found here:
+https://github.com/include-what-you-use/include-what-you-use/issues/100
+
+
+CMake cross compile
+To cross compile libwebm for Windows using mingw-w64 run cmake with the
+following arguments:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+  path/to/libwebm
+
+Note1: As of this writing googletest will not build via mingw-w64 without
+disabling pthreads.
+googletest hash: d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0
+
+To build with tests when using mingw-w64 use the following arguments when
+running CMake:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+  -DENABLE_TESTS=ON -Dgtest_disable_pthreads=ON path/to/libwebm
+
+Note2: i686-w64-mingw32 is the default compiler. This can be controlled using
+the MINGW_PREFIX variable:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+  -DMINGW_PREFIX=x86_64-w64-mingw32 path/to/libwebm
+
diff --git a/video/mkv/mkvparser.cpp b/video/mkv/mkvparser.cpp
new file mode 100644
index 00000000000..323cd61d7d8
--- /dev/null
+++ b/video/mkv/mkvparser.cpp
@@ -0,0 +1,8075 @@
+// 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 "video/mkv/mkvparser.h"
+
+#if defined(_MSC_VER) && _MSC_VER < 1800
+#include <float.h>  // _isnan() / _finite()
+#define MSC_COMPAT
+#endif
+
+#include <cassert>
+#include <cfloat>
+#include <climits>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <new>
+
+#include "video/mkv/webmids.h"
+
+namespace mkvparser {
+const long long kStringElementSizeLimit = 20 * 1000 * 1000;
+const float MasteringMetadata::kValueNotPresent = FLT_MAX;
+const long long Colour::kValueNotPresent = LLONG_MAX;
+const float Projection::kValueNotPresent = FLT_MAX;
+
+#ifdef MSC_COMPAT
+inline bool isnan(double val) { return !!_isnan(val); }
+inline bool isinf(double val) { return !_finite(val); }
+#else
+inline bool isnan(double val) { return std::isnan(val); }
+inline bool isinf(double val) { return std::isinf(val); }
+#endif  // MSC_COMPAT
+
+template <typename Type>
+Type* SafeArrayAlloc(unsigned long long num_elements,
+                     unsigned long long element_size) {
+  if (num_elements == 0 || element_size == 0)
+    return NULL;
+
+  const size_t kMaxAllocSize = 0x80000000;  // 2GiB
+  const unsigned long long num_bytes = num_elements * element_size;
+  if (element_size > (kMaxAllocSize / num_elements))
+    return NULL;
+  if (num_bytes != static_cast<size_t>(num_bytes))
+    return NULL;
+
+  return new (std::nothrow) Type[static_cast<size_t>(num_bytes)];
+}
+
+void GetVersion(int& major, int& minor, int& build, int& revision) {
+  major = 1;
+  minor = 0;
+  build = 0;
+  revision = 30;
+}
+
+long long ReadUInt(IMkvReader* pReader, long long pos, long& len) {
+  if (!pReader || pos < 0)
+    return E_FILE_FORMAT_INVALID;
+
+  len = 1;
+  unsigned char b;
+  int 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;
+  }
+
+  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;
+}
+
+// Reads an EBML ID and returns it.
+// An ID must at least 1 byte long, cannot exceed 4, and its value must be
+// greater than 0.
+// See known EBML values and EBMLMaxIDLength:
+// http://www.matroska.org/technical/specs/index.html
+// Returns the ID, or a value less than 0 to report an error while reading the
+// ID.
+long long ReadID(IMkvReader* pReader, long long pos, long& len) {
+  if (pReader == NULL || pos < 0)
+    return E_FILE_FORMAT_INVALID;
+
+  // Read the first byte. The length in bytes of the ID is determined by
+  // finding the first set bit in the first byte of the ID.
+  unsigned char temp_byte = 0;
+  int read_status = pReader->Read(pos, 1, &temp_byte);
+
+  if (read_status < 0)
+    return E_FILE_FORMAT_INVALID;
+  else if (read_status > 0)  // No data to read.
+    return E_BUFFER_NOT_FULL;
+
+  if (temp_byte == 0)  // ID length > 8 bytes; invalid file.
+    return E_FILE_FORMAT_INVALID;
+
+  int bit_pos = 0;
+  const int kMaxIdLengthInBytes = 4;
+  const int kCheckByte = 0x80;
+
+  // Find the first bit that's set.
+  bool found_bit = false;
+  for (; bit_pos < kMaxIdLengthInBytes; ++bit_pos) {
+    if ((kCheckByte >> bit_pos) & temp_byte) {
+      found_bit = true;
+      break;
+    }
+  }
+
+  if (!found_bit) {
+    // The value is too large to be a valid ID.
+    return E_FILE_FORMAT_INVALID;
+  }
+
+  // Read the remaining bytes of the ID (if any).
+  const int id_length = bit_pos + 1;
+  long long ebml_id = temp_byte;
+  for (int i = 1; i < id_length; ++i) {
+    ebml_id <<= 8;
+    read_status = pReader->Read(pos + i, 1, &temp_byte);
+
+    if (read_status < 0)
+      return E_FILE_FORMAT_INVALID;
+    else if (read_status > 0)
+      return E_BUFFER_NOT_FULL;
+
+    ebml_id |= temp_byte;
+  }
+
+  len = id_length;
+  return ebml_id;
+}
+
+long long GetUIntLength(IMkvReader* pReader, long long pos, long& len) {
+  if (!pReader || pos < 0)
+    return E_FILE_FORMAT_INVALID;
+
+  long long total, available;
+
+  int status = pReader->Length(&total, &available);
+  if (status < 0 || (total >= 0 && available > total))
+    return E_FILE_FORMAT_INVALID;
+
+  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;
+
+  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
+}
+
+// TODO(vigneshv): This function assumes that unsigned values never have their
+// high bit set.
+long long UnserializeUInt(IMkvReader* pReader, long long pos, long long size) {
+  if (!pReader || pos < 0 || (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 UnserializeFloat(IMkvReader* pReader, long long pos, long long size_,
+                      double& result) {
+  if (!pReader || pos < 0 || ((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 {
+    union {
+      double d;
+      unsigned long long dd;
+    };
+
+    dd = 0;
+
+    for (int i = 0;;) {
+      dd |= buf[i];
+
+      if (++i >= 8)
+        break;
+
+      dd <<= 8;
+    }
+
+    result = d;
+  }
+
+  if (mkvparser::isinf(result) || mkvparser::isnan(result))
+    return E_FILE_FORMAT_INVALID;
+
+  return 0;
+}
+
+long UnserializeInt(IMkvReader* pReader, long long pos, long long size,
+                    long long& result_ref) {
+  if (!pReader || pos < 0 || size < 1 || size > 8)
+    return E_FILE_FORMAT_INVALID;
+
+  signed char first_byte = 0;
+  const long status = pReader->Read(pos, 1, (unsigned char*)&first_byte);
+
+  if (status < 0)
+    return status;
+
+  unsigned long long result = first_byte;
+  ++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;
+  }
+
+  result_ref = static_cast<long long>(result);
+  return 0;
+}
+
+long UnserializeString(IMkvReader* pReader, long long pos, long long size,
+                       char*& str) {
+  delete[] str;
+  str = NULL;
+
+  if (size >= LONG_MAX || size < 0 || size > kStringElementSizeLimit)
+    return E_FILE_FORMAT_INVALID;
+
+  // +1 for '\0' terminator
+  const long required_size = static_cast<long>(size) + 1;
+
+  str = SafeArrayAlloc<char>(1, required_size);
+  if (str == NULL)
+    return E_FILE_FORMAT_INVALID;
+
+  unsigned char* const buf = reinterpret_cast<unsigned char*>(str);
+
+  const long status = pReader->Read(pos, static_cast<long>(size), buf);
+
+  if (status) {
+    delete[] str;
+    str = NULL;
+
+    return status;
+  }
+
+  str[required_size - 1] = '\0';
+  return 0;
+}
+
+long 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 = ReadID(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 || len < 1 || len > 8) {
+    // Invalid: Negative payload size, negative or 0 length integer, or integer
+    // larger than 64 bits (libwebm cannot handle them).
+    return E_FILE_FORMAT_INVALID;
+  }
+
+  // Avoid rolling over pos when very close to LLONG_MAX.
+  const unsigned long long rollover_check =
+      static_cast<unsigned long long>(pos) + len;
+  if (rollover_check > LLONG_MAX)
+    return E_FILE_FORMAT_INVALID;
+
+  pos += len;  // consume length of size
+
+  // pos now designates payload
+
+  if (stop >= 0 && pos > stop)
+    return E_FILE_FORMAT_INVALID;
+
+  return 0;  // success
+}
+
+bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id,
+           long long& val) {
+  if (!pReader || pos < 0)
+    return false;
+
+  long long total = 0;
+  long long available = 0;
+
+  const long status = pReader->Length(&total, &available);
+  if (status < 0 || (total >= 0 && available > total))
+    return false;
+
+  long len = 0;
+
+  const long long id = ReadID(pReader, pos, len);
+  if (id < 0 || (available - pos) > len)
+    return false;
+
+  if (static_cast<unsigned long>(id) != expected_id)
+    return false;
+
+  pos += len;  // consume id
+
+  const long long size = ReadUInt(pReader, pos, len);
+  if (size < 0 || size > 8 || len < 1 || len > 8 || (available - pos) > len)
+    return false;
+
+  pos += len;  // consume length of size of payload
+
+  val = UnserializeUInt(pReader, pos, size);
+  if (val < 0)
+    return false;
+
+  pos += size;  // consume size of payload
+
+  return true;
+}
+
+bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id,
+           unsigned char*& buf, size_t& buflen) {
+  if (!pReader || pos < 0)
+    return false;
+
+  long long total = 0;
+  long long available = 0;
+
+  long status = pReader->Length(&total, &available);
+  if (status < 0 || (total >= 0 && available > total))
+    return false;
+
+  long len = 0;
+  const long long id = ReadID(pReader, pos, len);
+  if (id < 0 || (available - pos) > len)
+    return false;
+
+  if (static_cast<unsigned long>(id) != expected_id)
+    return false;
+
+  pos += len;  // consume id
+
+  const long long size = ReadUInt(pReader, pos, len);
+  if (size < 0 || len <= 0 || len > 8 || (available - pos) > len)
+    return false;
+
+  unsigned long long rollover_check =
+      static_cast<unsigned long long>(pos) + len;
+  if (rollover_check > LLONG_MAX)
+    return false;
+
+  pos += len;  // consume length of size of payload
+
+  rollover_check = static_cast<unsigned long long>(pos) + size;
+  if (rollover_check > LLONG_MAX)
+    return false;
+
+  if ((pos + size) > available)
+    return false;
+
+  if (size >= LONG_MAX)
+    return false;
+
+  const long buflen_ = static_cast<long>(size);
+
+  buf = SafeArrayAlloc<unsigned char>(1, buflen_);
+  if (!buf)
+    return false;
+
+  status = pReader->Read(pos, buflen_, buf);
+  if (status != 0)
+    return false;
+
+  buflen = buflen_;
+
+  pos += size;  // consume size of payload
+  return true;
+}
+
+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) {
+  if (!pReader)
+    return E_FILE_FORMAT_INVALID;
+
+  long long total, available;
+
+  long status = pReader->Length(&total, &available);
+
+  if (status < 0)  // error
+    return status;
+
+  pos = 0;
+
+  // Scan until we find what looks like the first byte of the EBML header.
+  const long long kMaxScanBytes = (available >= 1024) ? 1024 : available;
+  const unsigned char kEbmlByte0 = 0x1A;
+  unsigned char scan_byte = 0;
+
+  while (pos < kMaxScanBytes) {
+    status = pReader->Read(pos, 1, &scan_byte);
+
+    if (status < 0)  // error
+      return status;
+    else if (status > 0)
+      return E_BUFFER_NOT_FULL;
+
+    if (scan_byte == kEbmlByte0)
+      break;
+
+    ++pos;
+  }
+
+  long len = 0;
+  const long long ebml_id = ReadID(pReader, pos, len);
+
+  if (ebml_id == E_BUFFER_NOT_FULL)
+    return E_BUFFER_NOT_FULL;
+
+  if (len != 4 || ebml_id != libwebm::kMkvEBML)
+    return E_FILE_FORMAT_INVALID;
+
+  // Move read pos forward to the EBML header size field.
+  pos += 4;
+
+  // Read length of size field.
+  long long result = GetUIntLength(pReader, pos, len);
+
+  if (result < 0)  // error
+    return E_FILE_FORMAT_INVALID;
+  else if (result > 0)  // need more data
+    return E_BUFFER_NOT_FULL;
+
+  if (len < 1 || len > 8)
+    return E_FILE_FORMAT_INVALID;
+
+  if ((total >= 0) && ((total - pos) < len))
+    return E_FILE_FORMAT_INVALID;
+
+  if ((available - pos) < len)
+    return pos + len;  // try again later
+
+  // Read 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;
+
+  const long long 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)
+      return E_FILE_FORMAT_INVALID;
+
+    if (id == libwebm::kMkvEBMLVersion) {
+      m_version = UnserializeUInt(pReader, pos, size);
+
+      if (m_version <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvEBMLReadVersion) {
+      m_readVersion = UnserializeUInt(pReader, pos, size);
+
+      if (m_readVersion <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvEBMLMaxIDLength) {
+      m_maxIdLength = UnserializeUInt(pReader, pos, size);
+
+      if (m_maxIdLength <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvEBMLMaxSizeLength) {
+      m_maxSizeLength = UnserializeUInt(pReader, pos, size);
+
+      if (m_maxSizeLength <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDocType) {
+      if (m_docType)
+        return E_FILE_FORMAT_INVALID;
+
+      status = UnserializeString(pReader, pos, size, m_docType);
+
+      if (status)  // error
+        return status;
+    } else if (id == libwebm::kMkvDocTypeVersion) {
+      m_docTypeVersion = UnserializeUInt(pReader, pos, size);
+
+      if (m_docTypeVersion <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDocTypeReadVersion) {
+      m_docTypeReadVersion = UnserializeUInt(pReader, pos, size);
+
+      if (m_docTypeReadVersion <= 0)
+        return E_FILE_FORMAT_INVALID;
+    }
+
+    pos += size;
+  }
+
+  if (pos != end)
+    return E_FILE_FORMAT_INVALID;
+
+  // Make sure DocType, DocTypeReadVersion, and DocTypeVersion are valid.
+  if (m_docType == NULL || m_docTypeReadVersion <= 0 || m_docTypeVersion <= 0)
+    return E_FILE_FORMAT_INVALID;
+
+  // Make sure EBMLMaxIDLength and EBMLMaxSizeLength are valid.
+  if (m_maxIdLength <= 0 || m_maxIdLength > 4 || m_maxSizeLength <= 0 ||
+      m_maxSizeLength > 8)
+    return E_FILE_FORMAT_INVALID;
+
+  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_pTags(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++;
+    delete p;
+  }
+
+  delete[] m_clusters;
+
+  delete m_pTracks;
+  delete m_pInfo;
+  delete m_pCues;
+  delete m_pChapters;
+  delete m_pTags;
+  delete m_pSeekHead;
+}
+
+long long Segment::CreateInstance(IMkvReader* pReader, long long pos,
+                                  Segment*& pSegment) {
+  if (pReader == NULL || pos < 0)
+    return E_PARSE_FAILED;
+
+  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 = ReadID(pReader, pos, len);
+
+    if (id < 0)
+      return E_FILE_FORMAT_INVALID;
+
+    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 == libwebm::kMkvSegment) {
+      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, pos, size);
+      if (pSegment == NULL)
+        return E_PARSE_FAILED;
+
+      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;
+
+  if (total > 0 && available > total)
+    return E_FILE_FORMAT_INVALID;
+
+  const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+  if ((segment_stop >= 0 && total >= 0 && segment_stop > total) ||
+      (segment_stop >= 0 && m_pos > segment_stop)) {
+    return E_FILE_FORMAT_INVALID;
+  }
+
+  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;
+
+    // Avoid rolling over pos when very close to LLONG_MAX.
+    unsigned long long rollover_check = pos + 1ULL;
+    if (rollover_check > LLONG_MAX)
+      return E_FILE_FORMAT_INVALID;
+
+    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) {
+      // MkvReader doesn't have enough data to satisfy this read attempt.
+      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 = ReadID(m_pReader, idpos, len);
+
+    if (id < 0)
+      return E_FILE_FORMAT_INVALID;
+
+    if (id == libwebm::kMkvCluster)
+      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) {
+      // MkvReader doesn't have enough data to satisfy this read attempt.
+      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 || len < 1 || len > 8) {
+      // TODO(tomfinegan): ReadUInt should return an error when len is < 1 or
+      // len > 8 is true instead of checking this _everywhere_.
+      return size;
+    }
+
+    pos += len;  // consume length of size of element
+
+    // Avoid rolling over pos when very close to LLONG_MAX.
+    rollover_check = static_cast<unsigned long long>(pos) + size;
+    if (rollover_check > LLONG_MAX)
+      return E_FILE_FORMAT_INVALID;
+
+    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 == libwebm::kMkvInfo) {
+      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 == libwebm::kMkvTracks) {
+      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 == libwebm::kMkvCues) {
+      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 == libwebm::kMkvSeekHead) {
+      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 == libwebm::kMkvChapters) {
+      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;
+      }
+    } else if (id == libwebm::kMkvTags) {
+      if (m_pTags == NULL) {
+        m_pTags = new (std::nothrow)
+            Tags(this, pos, size, element_start, element_size);
+
+        if (m_pTags == NULL)
+          return -1;
+
+        const long status = m_pTags->Parse();
+
+        if (status)
+          return status;
+      }
+    }
+
+    m_pos = pos + size;  // consume payload
+  }
+
+  if (segment_stop >= 0 && m_pos > segment_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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;
+
+  if (total >= 0 && avail > total)
+    return E_FILE_FORMAT_INVALID;
+
+  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)
+      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 = ReadID(m_pReader, idpos, len);
+
+    if (id < 0)
+      return E_FILE_FORMAT_INVALID;
+
+    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)
+      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) {
+      // Missing element payload: move on.
+      m_pos = pos;
+      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 == libwebm::kMkvCues) {
+      if (size == unknown_size) {
+        // Cues element of unknown size: Not supported.
+        return E_FILE_FORMAT_INVALID;
+      }
+
+      if (m_pCues == NULL) {
+        const long long element_size = (pos - idpos) + size;
+
+        m_pCues = new (std::nothrow) Cues(this, pos, size, idpos, element_size);
+        if (m_pCues == NULL)
+          return -1;
+      }
+
+      m_pos = pos + size;  // consume payload
+      continue;
+    }
+
+    if (id != libwebm::kMkvCluster) {
+      // Besides the Segment, Libwebm allows only cluster elements of unknown
+      // size. Fail the parse upon encountering a non-cluster element reporting
+      // unknown size.
+      if (size == unknown_size)
+        return E_FILE_FORMAT_INVALID;
+
+      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;
+  }
+
+  if (cluster_off < 0) {
+    // No cluster, die.
+    return E_FILE_FORMAT_INVALID;
+  }
+
+  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) {
+    if (idx >= m_clusterSize)
+      return E_FILE_FORMAT_INVALID;
+
+    Cluster* const pCluster = m_clusters[idx];
+    if (pCluster == NULL || pCluster->m_index >= 0)
+      return E_FILE_FORMAT_INVALID;
+
+    const long long off = pCluster->GetPosition();
+    if (off < 0)
+      return E_FILE_FORMAT_INVALID;
+
+    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
+      if (segment_stop >= 0 && m_pos > segment_stop)
+        return E_FILE_FORMAT_INVALID;
+
+      return 0;  // success
+    }
+  }
+
+  if (status == 0) {  // no entries found
+    if (cluster_size >= 0)
+      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);
+  if (pCluster == NULL)
+    return -1;
+
+  if (!AppendCluster(pCluster)) {
+    delete pCluster;
+    return -1;
+  }
+
+  if (cluster_size >= 0) {
+    pos += cluster_size;
+
+    m_pos = pos;
+
+    if (segment_stop > 0 && m_pos > segment_stop)
+      return E_FILE_FORMAT_INVALID;
+
+    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)
+}
+
+long Segment::DoLoadClusterUnknownSize(long long& pos, long& len) {
+  if (m_pos >= 0 || m_pUnknownSize == NULL)
+    return E_PARSE_FAILED;
+
+  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
+
+  const long long start = m_pUnknownSize->m_element_start;
+  const long long size = m_pUnknownSize->GetElementSize();
+
+  if (size < 0)
+    return E_FILE_FORMAT_INVALID;
+
+  pos = start + size;
+  m_pos = pos;
+
+  m_pUnknownSize = 0;
+
+  return 2;  // continue parsing
+}
+
+bool Segment::AppendCluster(Cluster* pCluster) {
+  if (pCluster == NULL || pCluster->m_index < 0)
+    return false;
+
+  const long count = m_clusterCount + m_clusterPreloadCount;
+
+  long& size = m_clusterSize;
+  const long idx = pCluster->m_index;
+
+  if (size < count || idx != m_clusterCount)
+    return false;
+
+  if (count >= size) {
+    const long n = (size <= 0) ? 2048 : 2 * size;
+
+    Cluster** const qq = new (std::nothrow) Cluster*[n];
+    if (qq == NULL)
+      return false;
+
+    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) {
+    Cluster** const p = m_clusters + m_clusterCount;
+    if (*p == NULL || (*p)->m_index >= 0)
+      return false;
+
+    Cluster** q = p + m_clusterPreloadCount;
+    if (q >= (m_clusters + size))
+      return false;
+
+    for (;;) {
+      Cluster** const qq = q - 1;
+      if ((*qq)->m_index >= 0)
+        return false;
+
+      *q = *qq;
+      q = qq;
+
+      if (q == p)
+        break;
+    }
+  }
+
+  m_clusters[idx] = pCluster;
+  ++m_clusterCount;
+  return true;
+}
+
+bool Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) {
+  if (pCluster == NULL || pCluster->m_index >= 0 || idx < m_clusterCount)
+    return false;
+
+  const long count = m_clusterCount + m_clusterPreloadCount;
+
+  long& size = m_clusterSize;
+  if (size < count)
+    return false;
+
+  if (count >= size) {
+    const long n = (size <= 0) ? 2048 : 2 * size;
+
+    Cluster** const qq = new (std::nothrow) Cluster*[n];
+    if (qq == NULL)
+      return false;
+    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_clusters == NULL)
+    return false;
+
+  Cluster** const p = m_clusters + idx;
+
+  Cluster** q = m_clusters + count;
+  if (q < p || q >= (m_clusters + size))
+    return false;
+
+  while (q > p) {
+    Cluster** const qq = q - 1;
+
+    if ((*qq)->m_index >= 0)
+      return false;
+
+    *q = *qq;
+    q = qq;
+  }
+
+  m_clusters[idx] = pCluster;
+  ++m_clusterPreloadCount;
+  return true;
+}
+
+long Segment::Load() {
+  if (m_clusters != NULL || m_clusterSize != 0 || m_clusterCount != 0)
+    return E_PARSE_FAILED;
+
+  // 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;
+
+  if (m_pInfo == NULL || m_pTracks == NULL)
+    return E_FILE_FORMAT_INVALID;
+
+  for (;;) {
+    const long status = LoadCluster();
+
+    if (status < 0)  // error
+      return status;
+
+    if (status >= 1)  // no more clusters
+      return 0;
+  }
+}
+
+SeekHead::Entry::Entry() : id(0), pos(0), element_start(0), element_size(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 == libwebm::kMkvSeek)
+      ++entry_count;
+    else if (id == libwebm::kMkvVoid)
+      ++void_element_count;
+
+    pos += size;  // consume payload
+
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  if (entry_count > 0) {
+    m_entries = new (std::nothrow) Entry[entry_count];
+
+    if (m_entries == NULL)
+      return -1;
+  }
+
+  if (void_element_count > 0) {
+    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 == libwebm::kMkvSeek && entry_count > 0) {
+      if (ParseEntry(pReader, pos, size, pEntry)) {
+        Entry& e = *pEntry++;
+
+        e.element_start = idpos;
+        e.element_size = (pos + size) - idpos;
+      }
+    } else if (id == libwebm::kMkvVoid && void_element_count > 0) {
+      VoidElement& e = *pVoidElement++;
+
+      e.element_start = idpos;
+      e.element_size = (pos + size) - idpos;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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;
+}
+
+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 = ReadID(m_pReader, idpos, len);
+
+  if (id != libwebm::kMkvCues)
+    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);
+  if (m_pCues == NULL)
+    return -1;
+
+  return 0;  // success
+}
+
+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 = ReadID(pReader, pos, len);
+  if (seekIdId < 0)
+    return false;
+
+  if (seekIdId != libwebm::kMkvSeekID)
+    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;
+
+  pEntry->id = ReadID(pReader, pos, len);  // payload
+
+  if (pEntry->id <= 0)
+    return false;
+
+  if (len != seekIdSize)
+    return false;
+
+  pos += seekIdSize;  // consume SeekID payload
+
+  const long long seekPosId = ReadID(pReader, pos, len);
+
+  if (seekPosId != libwebm::kMkvSeekPosition)
+    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;
+}
+
+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);
+}
+
+bool Cues::Init() const {
+  if (m_cue_points)
+    return true;
+
+  if (m_count != 0 || m_preload_count != 0)
+    return false;
+
+  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 = ReadID(pReader, pos, len);
+    if (id < 0 || (pos + len) > stop) {
+      return false;
+    }
+
+    pos += len;  // consume ID
+
+    const long long size = ReadUInt(pReader, pos, len);
+    if (size < 0 || (pos + len > stop)) {
+      return false;
+    }
+
+    pos += len;  // consume Size field
+    if (pos + size > stop) {
+      return false;
+    }
+
+    if (id == libwebm::kMkvCuePoint) {
+      if (!PreloadCuePoint(cue_points_size, idpos))
+        return false;
+    }
+
+    pos += size;  // skip payload
+  }
+  return true;
+}
+
+bool Cues::PreloadCuePoint(long& cue_points_size, long long pos) const {
+  if (m_count != 0)
+    return false;
+
+  if (m_preload_count >= cue_points_size) {
+    const long n = (cue_points_size <= 0) ? 2048 : 2 * cue_points_size;
+
+    CuePoint** const qq = new (std::nothrow) CuePoint*[n];
+    if (qq == NULL)
+      return false;
+
+    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 (std::nothrow) CuePoint(m_preload_count, pos);
+  if (pCP == NULL)
+    return false;
+
+  m_cue_points[m_preload_count++] = pCP;
+  return true;
+}
+
+bool Cues::LoadCuePoint() const {
+  const long long stop = m_start + m_size;
+
+  if (m_pos >= stop)
+    return false;  // nothing else to do
+
+  if (!Init()) {
+    m_pos = stop;
+    return false;
+  }
+
+  IMkvReader* const pReader = m_pSegment->m_pReader;
+
+  while (m_pos < stop) {
+    const long long idpos = m_pos;
+
+    long len;
+
+    const long long id = ReadID(pReader, m_pos, len);
+    if (id < 0 || (m_pos + len) > stop)
+      return false;
+
+    m_pos += len;  // consume ID
+
+    const long long size = ReadUInt(pReader, m_pos, len);
+    if (size < 0 || (m_pos + len) > stop)
+      return false;
+
+    m_pos += len;  // consume Size field
+    if ((m_pos + size) > stop)
+      return false;
+
+    if (id != libwebm::kMkvCuePoint) {
+      m_pos += size;  // consume payload
+      if (m_pos > stop)
+        return false;
+
+      continue;
+    }
+
+    if (m_preload_count < 1)
+      return false;
+
+    CuePoint* const pCP = m_cue_points[m_count];
+    if (!pCP || (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos)))
+      return false;
+
+    if (!pCP->Load(pReader)) {
+      m_pos = stop;
+      return false;
+    }
+    ++m_count;
+    --m_preload_count;
+
+    m_pos += size;  // consume payload
+    if (m_pos > stop)
+      return false;
+
+    return true;  // yes, we loaded a cue point
+  }
+
+  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 {
+  if (time_ns < 0 || pTrack == NULL || m_cue_points == NULL || 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;
+  if (pCP == NULL)
+    return false;
+
+  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;
+    if (k >= jj)
+      return false;
+
+    CuePoint* const pCP = *k;
+    if (pCP == NULL)
+      return false;
+
+    const long long t = pCP->GetTime(m_pSegment);
+
+    if (t <= time_ns)
+      i = k + 1;
+    else
+      j = k;
+
+    if (i > j)
+      return false;
+  }
+
+  if (i != j || i > jj || i <= ii)
+    return false;
+
+  pCP = *--i;
+
+  if (pCP == NULL || pCP->GetTime(m_pSegment) > time_ns)
+    return false;
+
+  // 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);
+}
+
+const CuePoint* Cues::GetFirst() const {
+  if (m_cue_points == NULL || m_count == 0)
+    return NULL;
+
+  CuePoint* const* const pp = m_cue_points;
+  if (pp == NULL)
+    return NULL;
+
+  CuePoint* const pCP = pp[0];
+  if (pCP == NULL || pCP->GetTimeCode() < 0)
+    return NULL;
+
+  return pCP;
+}
+
+const CuePoint* Cues::GetLast() const {
+  if (m_cue_points == NULL || m_count <= 0)
+    return NULL;
+
+  const long index = m_count - 1;
+
+  CuePoint* const* const pp = m_cue_points;
+  if (pp == NULL)
+    return NULL;
+
+  CuePoint* const pCP = pp[index];
+  if (pCP == NULL || pCP->GetTimeCode() < 0)
+    return NULL;
+
+  return pCP;
+}
+
+const CuePoint* Cues::GetNext(const CuePoint* pCurr) const {
+  if (pCurr == NULL || pCurr->GetTimeCode() < 0 || m_cue_points == NULL ||
+      m_count < 1) {
+    return NULL;
+  }
+
+  long index = pCurr->m_index;
+  if (index >= m_count)
+    return NULL;
+
+  CuePoint* const* const pp = m_cue_points;
+  if (pp == NULL || pp[index] != pCurr)
+    return NULL;
+
+  ++index;
+
+  if (index >= m_count)
+    return NULL;
+
+  CuePoint* const pNext = pp[index];
+
+  if (pNext == NULL || pNext->GetTimeCode() < 0)
+    return NULL;
+
+  return pNext;
+}
+
+const BlockEntry* Cues::GetBlock(const CuePoint* pCP,
+                                 const CuePoint::TrackPosition* pTP) const {
+  if (pCP == NULL || 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);
+  if (pCluster == NULL)
+    return NULL;
+
+  const ptrdiff_t idx = i - m_clusters;
+
+  if (!PreloadCluster(pCluster, idx)) {
+    delete pCluster;
+    return NULL;
+  }
+  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);
+  if (pCluster == NULL)
+    return NULL;
+
+  const ptrdiff_t idx = i - m_clusters;
+
+  if (!PreloadCluster(pCluster, idx)) {
+    delete pCluster;
+    return NULL;
+  }
+  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; }
+
+bool CuePoint::Load(IMkvReader* pReader) {
+  // odbgstream os;
+  // os << "CuePoint::Load(begin): timecode=" << m_timecode << endl;
+
+  if (m_timecode >= 0)  // already loaded
+    return true;
+
+  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 = ReadID(pReader, pos_, len);
+    if (id != libwebm::kMkvCuePoint)
+      return false;
+
+    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 = ReadID(pReader, pos, len);
+    if ((id < 0) || (pos + len > stop)) {
+      return false;
+    }
+
+    pos += len;  // consume ID
+
+    const long long size = ReadUInt(pReader, pos, len);
+    if ((size < 0) || (pos + len > stop)) {
+      return false;
+    }
+
+    pos += len;  // consume Size field
+    if ((pos + size) > stop) {
+      return false;
+    }
+
+    if (id == libwebm::kMkvCueTime)
+      m_timecode = UnserializeUInt(pReader, pos, size);
+
+    else if (id == libwebm::kMkvCueTrackPositions)
+      ++m_track_positions_count;
+
+    pos += size;  // consume payload
+  }
+
+  if (m_timecode < 0 || m_track_positions_count <= 0) {
+    return false;
+  }
+
+  // os << "CuePoint::Load(cont'd): idpos=" << idpos
+  //   << " timecode=" << m_timecode
+  //   << endl;
+
+  m_track_positions = new (std::nothrow) TrackPosition[m_track_positions_count];
+  if (m_track_positions == NULL)
+    return false;
+
+  // Now parse track positions
+
+  TrackPosition* p = m_track_positions;
+  pos = pos_;
+
+  while (pos < stop) {
+    long len;
+
+    const long long id = ReadID(pReader, pos, len);
+    if (id < 0 || (pos + len) > stop)
+      return false;
+
+    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 == libwebm::kMkvCueTrackPositions) {
+      TrackPosition& tp = *p++;
+      if (!tp.Parse(pReader, pos, size)) {
+        return false;
+      }
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return false;
+  }
+
+  assert(size_t(p - m_track_positions) == m_track_positions_count);
+
+  m_element_start = element_start;
+  m_element_size = element_size;
+
+  return true;
+}
+
+bool 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 = ReadID(pReader, pos, len);
+    if ((id < 0) || ((pos + len) > stop)) {
+      return false;
+    }
+
+    pos += len;  // consume ID
+
+    const long long size = ReadUInt(pReader, pos, len);
+    if ((size < 0) || ((pos + len) > stop)) {
+      return false;
+    }
+
+    pos += len;  // consume Size field
+    if ((pos + size) > stop) {
+      return false;
+    }
+
+    if (id == libwebm::kMkvCueTrack)
+      m_track = UnserializeUInt(pReader, pos, size);
+    else if (id == libwebm::kMkvCueClusterPosition)
+      m_pos = UnserializeUInt(pReader, pos, size);
+    else if (id == libwebm::kMkvCueBlockNumber)
+      m_block = UnserializeUInt(pReader, pos, size);
+
+    pos += size;  // consume payload
+  }
+
+  if ((m_pos < 0) || (m_track <= 0)) {
+    return false;
+  }
+
+  return true;
+}
+
+const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const {
+  if (pTrack == NULL) {
+    return NULL;
+  }
+
+  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;
+}
+
+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);
+}
+
+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 = ReadID(m_pReader, pos, len);
+    if (id != libwebm::kMkvCluster)
+      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 = ReadID(m_pReader, idpos, len);
+    if (id < 0)
+      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
+
+    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 == libwebm::kMkvCluster) {
+      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);
+  if (pNext == NULL)
+    return NULL;
+
+  const ptrdiff_t idx_next = i - m_clusters;  // insertion position
+
+  if (!PreloadCluster(pNext, idx_next)) {
+    delete pNext;
+    return NULL;
+  }
+  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 != libwebm::kMkvCluster)
+      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)
+    if (segment_stop >= 0 && pos > segment_stop)
+      return E_FILE_FORMAT_INVALID;
+
+    // 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 = ReadID(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 == libwebm::kMkvCues) {
+      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 (std::nothrow)
+            Cues(this, pos, size, element_start, element_size);
+        if (m_pCues == NULL)
+          return false;
+      }
+
+      pos += size;  // consume payload
+      if (segment_stop >= 0 && pos > segment_stop)
+        return E_FILE_FORMAT_INVALID;
+
+      continue;
+    }
+
+    if (id != libwebm::kMkvCluster) {  // not a Cluster ID
+      if (size == unknown_size)
+        return E_FILE_FORMAT_INVALID;
+
+      pos += size;  // consume payload
+      if (segment_stop >= 0 && pos > segment_stop)
+        return E_FILE_FORMAT_INVALID;
+
+      continue;
+    }
+
+    // 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);
+    if (pNext == NULL)
+      return -1;
+
+    const ptrdiff_t idx_next = i - m_clusters;  // insertion position
+
+    if (!PreloadCluster(pNext, idx_next)) {
+      delete pNext;
+      return -1;
+    }
+    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 = ReadID(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 == libwebm::kMkvCluster || id == libwebm::kMkvCues)
+        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
+      if (segment_stop >= 0 && pos > segment_stop)
+        return E_FILE_FORMAT_INVALID;
+    }  // 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
+  if (segment_stop >= 0 && pos > segment_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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;
+}
+
+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 Tags* Segment::GetTags() const { return m_pTags; }
+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();
+  }
+  delete[] m_editions;
+}
+
+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 == libwebm::kMkvEditionEntry) {
+      status = ParseEdition(pos, size);
+
+      if (status < 0)  // error
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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)
+      continue;
+
+    if (id == libwebm::kMkvChapterAtom) {
+      status = ParseAtom(pReader, pos, size);
+
+      if (status < 0)  // error
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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)  // 0 length payload, skip.
+      continue;
+
+    if (id == libwebm::kMkvChapterDisplay) {
+      status = ParseDisplay(pReader, pos, size);
+
+      if (status < 0)  // error
+        return status;
+    } else if (id == libwebm::kMkvChapterStringUID) {
+      status = UnserializeString(pReader, pos, size, m_string_uid);
+
+      if (status < 0)  // error
+        return status;
+    } else if (id == libwebm::kMkvChapterUID) {
+      long long val;
+      status = UnserializeInt(pReader, pos, size, val);
+
+      if (status < 0)  // error
+        return status;
+
+      m_uid = static_cast<unsigned long long>(val);
+    } else if (id == libwebm::kMkvChapterTimeStart) {
+      const long long val = UnserializeUInt(pReader, pos, size);
+
+      if (val < 0)  // error
+        return static_cast<long>(val);
+
+      m_start_timecode = val;
+    } else if (id == libwebm::kMkvChapterTimeEnd) {
+      const long long val = UnserializeUInt(pReader, pos, size);
+
+      if (val < 0)  // error
+        return static_cast<long>(val);
+
+      m_stop_timecode = val;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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)  // No payload.
+      continue;
+
+    if (id == libwebm::kMkvChapString) {
+      status = UnserializeString(pReader, pos, size, m_string);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvChapLanguage) {
+      status = UnserializeString(pReader, pos, size, m_language);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvChapCountry) {
+      status = UnserializeString(pReader, pos, size, m_country);
+
+      if (status)
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  return 0;
+}
+
+Tags::Tags(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_tags(NULL),
+      m_tags_size(0),
+      m_tags_count(0) {}
+
+Tags::~Tags() {
+  while (m_tags_count > 0) {
+    Tag& t = m_tags[--m_tags_count];
+    t.Clear();
+  }
+  delete[] m_tags;
+}
+
+long Tags::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)
+      return status;
+
+    if (size == 0)  // 0 length tag, read another
+      continue;
+
+    if (id == libwebm::kMkvTag) {
+      status = ParseTag(pos, size);
+
+      if (status < 0)
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  return 0;
+}
+
+int Tags::GetTagCount() const { return m_tags_count; }
+
+const Tags::Tag* Tags::GetTag(int idx) const {
+  if (idx < 0)
+    return NULL;
+
+  if (idx >= m_tags_count)
+    return NULL;
+
+  return m_tags + idx;
+}
+
+bool Tags::ExpandTagsArray() {
+  if (m_tags_size > m_tags_count)
+    return true;  // nothing else to do
+
+  const int size = (m_tags_size == 0) ? 1 : 2 * m_tags_size;
+
+  Tag* const tags = new (std::nothrow) Tag[size];
+
+  if (tags == NULL)
+    return false;
+
+  for (int idx = 0; idx < m_tags_count; ++idx) {
+    m_tags[idx].ShallowCopy(tags[idx]);
+  }
+
+  delete[] m_tags;
+  m_tags = tags;
+
+  m_tags_size = size;
+  return true;
+}
+
+long Tags::ParseTag(long long pos, long long size) {
+  if (!ExpandTagsArray())
+    return -1;
+
+  Tag& t = m_tags[m_tags_count++];
+  t.Init();
+
+  return t.Parse(m_pSegment->m_pReader, pos, size);
+}
+
+Tags::Tag::Tag() {}
+
+Tags::Tag::~Tag() {}
+
+int Tags::Tag::GetSimpleTagCount() const { return m_simple_tags_count; }
+
+const Tags::SimpleTag* Tags::Tag::GetSimpleTag(int index) const {
+  if (index < 0)
+    return NULL;
+
+  if (index >= m_simple_tags_count)
+    return NULL;
+
+  return m_simple_tags + index;
+}
+
+void Tags::Tag::Init() {
+  m_simple_tags = NULL;
+  m_simple_tags_size = 0;
+  m_simple_tags_count = 0;
+}
+
+void Tags::Tag::ShallowCopy(Tag& rhs) const {
+  rhs.m_simple_tags = m_simple_tags;
+  rhs.m_simple_tags_size = m_simple_tags_size;
+  rhs.m_simple_tags_count = m_simple_tags_count;
+}
+
+void Tags::Tag::Clear() {
+  while (m_simple_tags_count > 0) {
+    SimpleTag& d = m_simple_tags[--m_simple_tags_count];
+    d.Clear();
+  }
+
+  delete[] m_simple_tags;
+  m_simple_tags = NULL;
+
+  m_simple_tags_size = 0;
+}
+
+long Tags::Tag::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)
+      return status;
+
+    if (size == 0)  // 0 length tag, read another
+      continue;
+
+    if (id == libwebm::kMkvSimpleTag) {
+      status = ParseSimpleTag(pReader, pos, size);
+
+      if (status < 0)
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  return 0;
+}
+
+long Tags::Tag::ParseSimpleTag(IMkvReader* pReader, long long pos,
+                               long long size) {
+  if (!ExpandSimpleTagsArray())
+    return -1;
+
+  SimpleTag& st = m_simple_tags[m_simple_tags_count++];
+  st.Init();
+
+  return st.Parse(pReader, pos, size);
+}
+
+bool Tags::Tag::ExpandSimpleTagsArray() {
+  if (m_simple_tags_size > m_simple_tags_count)
+    return true;  // nothing else to do
+
+  const int size = (m_simple_tags_size == 0) ? 1 : 2 * m_simple_tags_size;
+
+  SimpleTag* const displays = new (std::nothrow) SimpleTag[size];
+
+  if (displays == NULL)
+    return false;
+
+  for (int idx = 0; idx < m_simple_tags_count; ++idx) {
+    m_simple_tags[idx].ShallowCopy(displays[idx]);
+  }
+
+  delete[] m_simple_tags;
+  m_simple_tags = displays;
+
+  m_simple_tags_size = size;
+  return true;
+}
+
+Tags::SimpleTag::SimpleTag() {}
+
+Tags::SimpleTag::~SimpleTag() {}
+
+const char* Tags::SimpleTag::GetTagName() const { return m_tag_name; }
+
+const char* Tags::SimpleTag::GetTagString() const { return m_tag_string; }
+
+void Tags::SimpleTag::Init() {
+  m_tag_name = NULL;
+  m_tag_string = NULL;
+}
+
+void Tags::SimpleTag::ShallowCopy(SimpleTag& rhs) const {
+  rhs.m_tag_name = m_tag_name;
+  rhs.m_tag_string = m_tag_string;
+}
+
+void Tags::SimpleTag::Clear() {
+  delete[] m_tag_name;
+  m_tag_name = NULL;
+
+  delete[] m_tag_string;
+  m_tag_string = NULL;
+}
+
+long Tags::SimpleTag::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 == libwebm::kMkvTagName) {
+      status = UnserializeString(pReader, pos, size, m_tag_name);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvTagString) {
+      status = UnserializeString(pReader, pos, size, m_tag_string);
+
+      if (status)
+        return status;
+    }
+
+    pos += size;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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 == libwebm::kMkvTimecodeScale) {
+      m_timecodeScale = UnserializeUInt(pReader, pos, size);
+
+      if (m_timecodeScale <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDuration) {
+      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 == libwebm::kMkvMuxingApp) {
+      const long status =
+          UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvWritingApp) {
+      const long status =
+          UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvTitle) {
+      const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8);
+
+      if (status)
+        return status;
+    }
+
+    pos += size;
+
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  const double rollover_check = m_duration * m_timecodeScale;
+  if (rollover_check > static_cast<double>(LLONG_MAX))
+    return E_FILE_FORMAT_INVALID;
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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 == libwebm::kMkvAESSettingsCipherMode) {
+      aes->cipher_mode = UnserializeUInt(pReader, pos, size);
+      if (aes->cipher_mode != 1)
+        return E_FILE_FORMAT_INVALID;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  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 == libwebm::kMkvContentCompression)
+      ++compression_count;
+
+    if (id == libwebm::kMkvContentEncryption)
+      ++encryption_count;
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  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_;
+      compression_entries_ = NULL;
+      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 == libwebm::kMkvContentEncodingOrder) {
+      encoding_order_ = UnserializeUInt(pReader, pos, size);
+    } else if (id == libwebm::kMkvContentEncodingScope) {
+      encoding_scope_ = UnserializeUInt(pReader, pos, size);
+      if (encoding_scope_ < 1)
+        return -1;
+    } else if (id == libwebm::kMkvContentEncodingType) {
+      encoding_type_ = UnserializeUInt(pReader, pos, size);
+    } else if (id == libwebm::kMkvContentCompression) {
+      ContentCompression* const compression =
+          new (std::nothrow) ContentCompression();
+      if (!compression)
+        return -1;
+
+      status = ParseCompressionEntry(pos, size, pReader, compression);
+      if (status) {
+        delete compression;
+        return status;
+      }
+      assert(compression_count > 0);
+      *compression_entries_end_++ = compression;
+    } else if (id == libwebm::kMkvContentEncryption) {
+      ContentEncryption* const encryption =
+          new (std::nothrow) ContentEncryption();
+      if (!encryption)
+        return -1;
+
+      status = ParseEncryptionEntry(pos, size, pReader, encryption);
+      if (status) {
+        delete encryption;
+        return status;
+      }
+      assert(encryption_count > 0);
+      *encryption_entries_end_++ = encryption;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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 == libwebm::kMkvContentCompAlgo) {
+      long long algo = UnserializeUInt(pReader, pos, size);
+      if (algo < 0)
+        return E_FILE_FORMAT_INVALID;
+      compression->algo = algo;
+      valid = true;
+    } else if (id == libwebm::kMkvContentCompSettings) {
+      if (size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      const size_t buflen = static_cast<size_t>(size);
+      unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+      if (buf == NULL)
+        return -1;
+
+      const int read_status =
+          pReader->Read(pos, static_cast<long>(buflen), buf);
+      if (read_status) {
+        delete[] buf;
+        return status;
+      }
+
+      // There should be only one settings element per content compression.
+      if (compression->settings != NULL) {
+        delete[] buf;
+        return E_FILE_FORMAT_INVALID;
+      }
+
+      compression->settings = buf;
+      compression->settings_len = buflen;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  // 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 == libwebm::kMkvContentEncAlgo) {
+      encryption->algo = UnserializeUInt(pReader, pos, size);
+      if (encryption->algo != 5)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvContentEncKeyID) {
+      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);
+      unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+      if (buf == NULL)
+        return -1;
+
+      const int read_status =
+          pReader->Read(pos, static_cast<long>(buflen), buf);
+      if (read_status) {
+        delete[] buf;
+        return status;
+      }
+
+      encryption->key_id = buf;
+      encryption->key_id_len = buflen;
+    } else if (id == libwebm::kMkvContentSignature) {
+      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);
+      unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+      if (buf == NULL)
+        return -1;
+
+      const int read_status =
+          pReader->Read(pos, static_cast<long>(buflen), buf);
+      if (read_status) {
+        delete[] buf;
+        return status;
+      }
+
+      encryption->signature = buf;
+      encryption->signature_len = buflen;
+    } else if (id == libwebm::kMkvContentSigKeyID) {
+      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);
+      unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+      if (buf == NULL)
+        return -1;
+
+      const int read_status =
+          pReader->Read(pos, static_cast<long>(buflen), buf);
+      if (read_status) {
+        delete[] buf;
+        return status;
+      }
+
+      encryption->sig_key_id = buf;
+      encryption->sig_key_id_len = buflen;
+    } else if (id == libwebm::kMkvContentSigAlgo) {
+      encryption->sig_algo = UnserializeUInt(pReader, pos, size);
+    } else if (id == libwebm::kMkvContentSigHashAlgo) {
+      encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size);
+    } else if (id == libwebm::kMkvContentEncAESSettings) {
+      const long status = ParseContentEncAESSettingsEntry(
+          pos, size, pReader, &encryption->aes_settings);
+      if (status)
+        return status;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  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 = SafeArrayAlloc<char>(1, 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 = SafeArrayAlloc<unsigned char>(1, 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 (m_pSegment->DoneParsing()) {
+        pBlockEntry = GetEOS();
+        return 1;
+      }
+
+      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 (m_pSegment->DoneParsing()) {
+        pNextEntry = GetEOS();
+        return 1;
+      }
+
+      // 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 == libwebm::kMkvContentEncoding)
+      ++count;
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  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 == libwebm::kMkvContentEncoding) {
+      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
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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; }
+
+bool PrimaryChromaticity::Parse(IMkvReader* reader, long long read_pos,
+                                long long value_size, bool is_x,
+                                PrimaryChromaticity** chromaticity) {
+  if (!reader)
+    return false;
+
+  if (!*chromaticity)
+    *chromaticity = new PrimaryChromaticity();
+
+  if (!*chromaticity)
+    return false;
+
+  PrimaryChromaticity* pc = *chromaticity;
+  float* value = is_x ? &pc->x : &pc->y;
+
+  double parser_value = 0;
+  const long long parse_status =
+      UnserializeFloat(reader, read_pos, value_size, parser_value);
+
+  // Valid range is [0, 1]. Make sure the double is representable as a float
+  // before casting.
+  if (parse_status < 0 || parser_value < 0.0 || parser_value > 1.0 ||
+      (parser_value > 0.0 && parser_value < FLT_MIN))
+    return false;
+
+  *value = static_cast<float>(parser_value);
+
+  return true;
+}
+
+bool MasteringMetadata::Parse(IMkvReader* reader, long long mm_start,
+                              long long mm_size, MasteringMetadata** mm) {
+  if (!reader || *mm)
+    return false;
+
+  std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata());
+  if (!mm_ptr.get())
+    return false;
+
+  const long long mm_end = mm_start + mm_size;
+  long long read_pos = mm_start;
+
+  while (read_pos < mm_end) {
+    long long child_id = 0;
+    long long child_size = 0;
+
+    const long long status =
+        ParseElementHeader(reader, read_pos, mm_end, child_id, child_size);
+    if (status < 0)
+      return false;
+
+    if (child_id == libwebm::kMkvLuminanceMax) {
+      double value = 0;
+      const long long value_parse_status =
+          UnserializeFloat(reader, read_pos, child_size, value);
+      if (value < -FLT_MAX || value > FLT_MAX ||
+          (value > 0.0 && value < FLT_MIN)) {
+        return false;
+      }
+      mm_ptr->luminance_max = static_cast<float>(value);
+      if (value_parse_status < 0 || mm_ptr->luminance_max < 0.0 ||
+          mm_ptr->luminance_max > 9999.99) {
+        return false;
+      }
+    } else if (child_id == libwebm::kMkvLuminanceMin) {
+      double value = 0;
+      const long long value_parse_status =
+          UnserializeFloat(reader, read_pos, child_size, value);
+      if (value < -FLT_MAX || value > FLT_MAX ||
+          (value > 0.0 && value < FLT_MIN)) {
+        return false;
+      }
+      mm_ptr->luminance_min = static_cast<float>(value);
+      if (value_parse_status < 0 || mm_ptr->luminance_min < 0.0 ||
+          mm_ptr->luminance_min > 999.9999) {
+        return false;
+      }
+    } else {
+      bool is_x = false;
+      PrimaryChromaticity** chromaticity;
+      switch (child_id) {
+        case libwebm::kMkvPrimaryRChromaticityX:
+        case libwebm::kMkvPrimaryRChromaticityY:
+          is_x = child_id == libwebm::kMkvPrimaryRChromaticityX;
+          chromaticity = &mm_ptr->r;
+          break;
+        case libwebm::kMkvPrimaryGChromaticityX:
+        case libwebm::kMkvPrimaryGChromaticityY:
+          is_x = child_id == libwebm::kMkvPrimaryGChromaticityX;
+          chromaticity = &mm_ptr->g;
+          break;
+        case libwebm::kMkvPrimaryBChromaticityX:
+        case libwebm::kMkvPrimaryBChromaticityY:
+          is_x = child_id == libwebm::kMkvPrimaryBChromaticityX;
+          chromaticity = &mm_ptr->b;
+          break;
+        case libwebm::kMkvWhitePointChromaticityX:
+        case libwebm::kMkvWhitePointChromaticityY:
+          is_x = child_id == libwebm::kMkvWhitePointChromaticityX;
+          chromaticity = &mm_ptr->white_point;
+          break;
+        default:
+          return false;
+      }
+      const bool value_parse_status = PrimaryChromaticity::Parse(
+          reader, read_pos, child_size, is_x, chromaticity);
+      if (!value_parse_status)
+        return false;
+    }
+
+    read_pos += child_size;
+    if (read_pos > mm_end)
+      return false;
+  }
+
+  *mm = mm_ptr.release();
+  return true;
+}
+
+bool Colour::Parse(IMkvReader* reader, long long colour_start,
+                   long long colour_size, Colour** colour) {
+  if (!reader || *colour)
+    return false;
+
+  std::unique_ptr<Colour> colour_ptr(new Colour());
+  if (!colour_ptr.get())
+    return false;
+
+  const long long colour_end = colour_start + colour_size;
+  long long read_pos = colour_start;
+
+  while (read_pos < colour_end) {
+    long long child_id = 0;
+    long long child_size = 0;
+
+    const long status =
+        ParseElementHeader(reader, read_pos, colour_end, child_id, child_size);
+    if (status < 0)
+      return false;
+
+    if (child_id == libwebm::kMkvMatrixCoefficients) {
+      colour_ptr->matrix_coefficients =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->matrix_coefficients < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvBitsPerChannel) {
+      colour_ptr->bits_per_channel =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->bits_per_channel < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvChromaSubsamplingHorz) {
+      colour_ptr->chroma_subsampling_horz =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->chroma_subsampling_horz < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvChromaSubsamplingVert) {
+      colour_ptr->chroma_subsampling_vert =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->chroma_subsampling_vert < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvCbSubsamplingHorz) {
+      colour_ptr->cb_subsampling_horz =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->cb_subsampling_horz < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvCbSubsamplingVert) {
+      colour_ptr->cb_subsampling_vert =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->cb_subsampling_vert < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvChromaSitingHorz) {
+      colour_ptr->chroma_siting_horz =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->chroma_siting_horz < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvChromaSitingVert) {
+      colour_ptr->chroma_siting_vert =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->chroma_siting_vert < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvRange) {
+      colour_ptr->range = UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->range < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvTransferCharacteristics) {
+      colour_ptr->transfer_characteristics =
+          UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->transfer_characteristics < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvPrimaries) {
+      colour_ptr->primaries = UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->primaries < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvMaxCLL) {
+      colour_ptr->max_cll = UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->max_cll < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvMaxFALL) {
+      colour_ptr->max_fall = UnserializeUInt(reader, read_pos, child_size);
+      if (colour_ptr->max_fall < 0)
+        return false;
+    } else if (child_id == libwebm::kMkvMasteringMetadata) {
+      if (!MasteringMetadata::Parse(reader, read_pos, child_size,
+                                    &colour_ptr->mastering_metadata))
+        return false;
+    } else {
+      return false;
+    }
+
+    read_pos += child_size;
+    if (read_pos > colour_end)
+      return false;
+  }
+  *colour = colour_ptr.release();
+  return true;
+}
+
+bool Projection::Parse(IMkvReader* reader, long long start, long long size,
+                       Projection** projection) {
+  if (!reader || *projection)
+    return false;
+
+  std::unique_ptr<Projection> projection_ptr(new Projection());
+  if (!projection_ptr.get())
+    return false;
+
+  const long long end = start + size;
+  long long read_pos = start;
+
+  while (read_pos < end) {
+    long long child_id = 0;
+    long long child_size = 0;
+
+    const long long status =
+        ParseElementHeader(reader, read_pos, end, child_id, child_size);
+    if (status < 0)
+      return false;
+
+    if (child_id == libwebm::kMkvProjectionType) {
+      long long projection_type = kTypeNotPresent;
+      projection_type = UnserializeUInt(reader, read_pos, child_size);
+      if (projection_type < 0)
+        return false;
+
+      projection_ptr->type = static_cast<ProjectionType>(projection_type);
+    } else if (child_id == libwebm::kMkvProjectionPrivate) {
+      if (projection_ptr->private_data != NULL)
+        return false;
+      unsigned char* data = SafeArrayAlloc<unsigned char>(1, child_size);
+
+      if (data == NULL)
+        return false;
+
+      const int status =
+          reader->Read(read_pos, static_cast<long>(child_size), data);
+
+      if (status) {
+        delete[] data;
+        return false;
+      }
+
+      projection_ptr->private_data = data;
+      projection_ptr->private_data_length = static_cast<size_t>(child_size);
+    } else {
+      double value = 0;
+      const long long value_parse_status =
+          UnserializeFloat(reader, read_pos, child_size, value);
+      // Make sure value is representable as a float before casting.
+      if (value_parse_status < 0 || value < -FLT_MAX || value > FLT_MAX ||
+          (value > 0.0 && value < FLT_MIN)) {
+        return false;
+      }
+
+      switch (child_id) {
+        case libwebm::kMkvProjectionPoseYaw:
+          projection_ptr->pose_yaw = static_cast<float>(value);
+          break;
+        case libwebm::kMkvProjectionPosePitch:
+          projection_ptr->pose_pitch = static_cast<float>(value);
+          break;
+        case libwebm::kMkvProjectionPoseRoll:
+          projection_ptr->pose_roll = static_cast<float>(value);
+          break;
+        default:
+          return false;
+      }
+    }
+
+    read_pos += child_size;
+    if (read_pos > end)
+      return false;
+  }
+
+  *projection = projection_ptr.release();
+  return true;
+}
+
+VideoTrack::VideoTrack(Segment* pSegment, long long element_start,
+                       long long element_size)
+    : Track(pSegment, element_start, element_size),
+      m_colour_space(NULL),
+      m_colour(NULL),
+      m_projection(NULL) {}
+
+VideoTrack::~VideoTrack() {
+  delete m_colour;
+  delete m_projection;
+}
+
+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;
+  long long display_width = 0;
+  long long display_height = 0;
+  long long display_unit = 0;
+  long long stereo_mode = 0;
+
+  double rate = 0.0;
+  std::unique_ptr<char[]> colour_space_ptr;
+
+  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;
+
+  std::unique_ptr<Colour> colour_ptr;
+  std::unique_ptr<Projection> projection_ptr;
+
+  while (pos < stop) {
+    long long id, size;
+
+    const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+    if (status < 0)  // error
+      return status;
+
+    if (id == libwebm::kMkvPixelWidth) {
+      width = UnserializeUInt(pReader, pos, size);
+
+      if (width <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvPixelHeight) {
+      height = UnserializeUInt(pReader, pos, size);
+
+      if (height <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDisplayWidth) {
+      display_width = UnserializeUInt(pReader, pos, size);
+
+      if (display_width <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDisplayHeight) {
+      display_height = UnserializeUInt(pReader, pos, size);
+
+      if (display_height <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvDisplayUnit) {
+      display_unit = UnserializeUInt(pReader, pos, size);
+
+      if (display_unit < 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvStereoMode) {
+      stereo_mode = UnserializeUInt(pReader, pos, size);
+
+      if (stereo_mode < 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvFrameRate) {
+      const long status = UnserializeFloat(pReader, pos, size, rate);
+
+      if (status < 0)
+        return status;
+
+      if (rate <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvColour) {
+      Colour* colour = NULL;
+      if (!Colour::Parse(pReader, pos, size, &colour)) {
+        return E_FILE_FORMAT_INVALID;
+      } else {
+        colour_ptr.reset(colour);
+      }
+    } else if (id == libwebm::kMkvProjection) {
+      Projection* projection = NULL;
+      if (!Projection::Parse(pReader, pos, size, &projection)) {
+        return E_FILE_FORMAT_INVALID;
+      } else {
+        projection_ptr.reset(projection);
+      }
+    } else if (id == libwebm::kMkvColourSpace) {
+      char* colour_space = NULL;
+      const long status = UnserializeString(pReader, pos, size, colour_space);
+      if (status < 0)
+        return status;
+      colour_space_ptr.reset(colour_space);
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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_display_width = display_width;
+  pTrack->m_display_height = display_height;
+  pTrack->m_display_unit = display_unit;
+  pTrack->m_stereo_mode = stereo_mode;
+  pTrack->m_rate = rate;
+  pTrack->m_colour = colour_ptr.release();
+  pTrack->m_colour_space = colour_space_ptr.release();
+  pTrack->m_projection = projection_ptr.release();
+
+  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);
+
+    pResult = pCluster->GetEntry(this, time_ns);
+
+    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;
+}
+
+Colour* VideoTrack::GetColour() const { return m_colour; }
+
+Projection* VideoTrack::GetProjection() const { return m_projection; }
+
+long long VideoTrack::GetWidth() const { return m_width; }
+
+long long VideoTrack::GetHeight() const { return m_height; }
+
+long long VideoTrack::GetDisplayWidth() const {
+  return m_display_width > 0 ? m_display_width : GetWidth();
+}
+
+long long VideoTrack::GetDisplayHeight() const {
+  return m_display_height > 0 ? m_display_height : GetHeight();
+}
+
+long long VideoTrack::GetDisplayUnit() const { return m_display_unit; }
+
+long long VideoTrack::GetStereoMode() const { return m_stereo_mode; }
+
+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 == libwebm::kMkvSamplingFrequency) {
+      status = UnserializeFloat(pReader, pos, size, rate);
+
+      if (status < 0)
+        return status;
+
+      if (rate <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvChannels) {
+      channels = UnserializeUInt(pReader, pos, size);
+
+      if (channels <= 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvBitDepth) {
+      bit_depth = UnserializeUInt(pReader, pos, size);
+
+      if (bit_depth <= 0)
+        return E_FILE_FORMAT_INVALID;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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 == libwebm::kMkvTrackEntry)
+      ++count;
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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 == libwebm::kMkvTrackEntry) {
+      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;
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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 == libwebm::kMkvVideo) {
+      v.start = start;
+      v.size = size;
+    } else if (id == libwebm::kMkvAudio) {
+      a.start = start;
+      a.size = size;
+    } else if (id == libwebm::kMkvContentEncodings) {
+      e.start = start;
+      e.size = size;
+    } else if (id == libwebm::kMkvTrackUID) {
+      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 == libwebm::kMkvTrackNumber) {
+      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 == libwebm::kMkvTrackType) {
+      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 == libwebm::kMkvName) {
+      const long status =
+          UnserializeString(pReader, pos, size, info.nameAsUTF8);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvLanguage) {
+      const long status = UnserializeString(pReader, pos, size, info.language);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvDefaultDuration) {
+      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 == libwebm::kMkvCodecID) {
+      const long status = UnserializeString(pReader, pos, size, info.codecId);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvFlagLacing) {
+      lacing = UnserializeUInt(pReader, pos, size);
+
+      if ((lacing < 0) || (lacing > 1))
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvCodecPrivate) {
+      delete[] info.codecPrivate;
+      info.codecPrivate = NULL;
+      info.codecPrivateSize = 0;
+
+      const size_t buflen = static_cast<size_t>(size);
+
+      if (buflen) {
+        unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+
+        if (buf == NULL)
+          return -1;
+
+        const int status = pReader->Read(pos, static_cast<long>(buflen), buf);
+
+        if (status) {
+          delete[] buf;
+          return status;
+        }
+
+        info.codecPrivate = buf;
+        info.codecPrivateSize = buflen;
+      }
+    } else if (id == libwebm::kMkvCodecName) {
+      const long status =
+          UnserializeString(pReader, pos, size, info.codecNameAsUTF8);
+
+      if (status)
+        return status;
+    } else if (id == libwebm::kMkvCodecDelay) {
+      info.codecDelay = UnserializeUInt(pReader, pos, size);
+    } else if (id == libwebm::kMkvSeekPreRoll) {
+      info.seekPreRoll = UnserializeUInt(pReader, pos, size);
+    }
+
+    pos += size;  // consume payload
+    if (pos > track_stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != track_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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 (info.type == Track::kMetadata && 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];
+}
+
+long Cluster::Load(long long& pos, long& len) const {
+  if (m_pSegment == NULL)
+    return E_PARSE_FAILED;
+
+  if (m_timecode >= 0)  // at least partially loaded
+    return 0;
+
+  if (m_pos != m_element_start || m_element_size >= 0)
+    return E_PARSE_FAILED;
+
+  IMkvReader* const pReader = m_pSegment->m_pReader;
+  long long total, avail;
+  const int status = pReader->Length(&total, &avail);
+
+  if (status < 0)  // error
+    return status;
+
+  if (total >= 0 && (avail > total || m_pos > total))
+    return E_FILE_FORMAT_INVALID;
+
+  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)
+    return E_BUFFER_NOT_FULL;
+
+  if ((pos + len) > avail)
+    return E_BUFFER_NOT_FULL;
+
+  const long long id_ = ReadID(pReader, pos, len);
+
+  if (id_ < 0)  // error
+    return static_cast<long>(id_);
+
+  if (id_ != libwebm::kMkvCluster)
+    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)
+    return E_BUFFER_NOT_FULL;
+
+  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;
+
+  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
+  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)
+      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 = ReadID(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 == libwebm::kMkvCluster)
+      break;
+
+    if (id == libwebm::kMkvCues)
+      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)
+      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)
+      continue;
+
+    if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
+      return E_FILE_FORMAT_INVALID;
+
+    if (id == libwebm::kMkvTimecode) {
+      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 == libwebm::kMkvBlockGroup) {
+      bBlock = true;
+      break;
+    } else if (id == libwebm::kMkvSimpleBlock) {
+      bBlock = true;
+      break;
+    }
+
+    pos += size;  // consume payload
+    if (cluster_stop >= 0 && pos > cluster_stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (cluster_stop >= 0 && pos > cluster_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  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;
+
+  if (m_pos < m_element_start || m_timecode < 0)
+    return E_PARSE_FAILED;
+
+  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;
+
+  if (total >= 0 && avail > total)
+    return E_FILE_FORMAT_INVALID;
+
+  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)
+      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 = ReadID(pReader, pos, len);
+
+    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 == libwebm::kMkvCluster) || (id == libwebm::kMkvCues)) {
+      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)
+      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)
+      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 == libwebm::kMkvBlockGroup || id == libwebm::kMkvSimpleBlock) {
+          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 == libwebm::kMkvBlockGroup)
+      return this_->ParseBlockGroup(size, pos, len);
+
+    if (id == libwebm::kMkvSimpleBlock)
+      return this_->ParseSimpleBlock(size, pos, len);
+
+    pos += size;  // consume payload
+    if (cluster_stop >= 0 && pos > cluster_stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (m_element_size < 1)
+    return E_FILE_FORMAT_INVALID;
+
+  m_pos = pos;
+  if (cluster_stop >= 0 && m_pos > cluster_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  if (m_entries_count > 0) {
+    const long idx = m_entries_count - 1;
+
+    const BlockEntry* const pLast = m_entries[idx];
+    if (pLast == NULL)
+      return E_PARSE_FAILED;
+
+    const Block* const pBlock = pLast->GetBlock();
+    if (pBlock == NULL)
+      return E_PARSE_FAILED;
+
+    const long long start = pBlock->m_start;
+
+    if ((total >= 0) && (start > total))
+      return E_PARSE_FAILED;  // defend against trucated stream
+
+    const long long size = pBlock->m_size;
+
+    const long long stop = start + size;
+    if (cluster_stop >= 0 && stop > cluster_stop)
+      return E_FILE_FORMAT_INVALID;
+
+    if ((total >= 0) && (stop > total))
+      return E_PARSE_FAILED;  // 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;
+
+  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(libwebm::kMkvSimpleBlock, 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 = ReadID(pReader, pos, len);
+
+    if (id < 0)  // error
+      return static_cast<long>(id);
+
+    if (id == 0)  // not a valid 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 == libwebm::kMkvDiscardPadding) {
+      status = UnserializeInt(pReader, pos, size, discard_padding);
+
+      if (status < 0)  // error
+        return status;
+    }
+
+    if (id != libwebm::kMkvBlock) {
+      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;
+
+    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
+    if (pos > payload_stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  if (pos != payload_stop)
+    return E_FILE_FORMAT_INVALID;
+
+  status = CreateBlock(libwebm::kMkvBlockGroup, 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) {
+  if (!pSegment || off < 0)
+    return NULL;
+
+  const long long element_start = pSegment->m_start + off;
+
+  Cluster* const pCluster =
+      new (std::nothrow) Cluster(pSegment, idx, element_start);
+
+  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) {
+    delete[] m_entries;
+    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; }
+
+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 = ReadID(pReader, pos, len);
+
+    if (id < 0)  // error
+      return static_cast<long>(id);
+
+    if (id != libwebm::kMkvCluster)
+      return E_PARSE_FAILED;
+
+    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 = ReadID(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 == libwebm::kMkvCluster)
+      return 0;  // no entries found
+
+    if (id == libwebm::kMkvCues)
+      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 == libwebm::kMkvBlockGroup)
+      return 1;  // have at least one entry
+
+    if (id == libwebm::kMkvSimpleBlock)
+      return 1;  // have at least one entry
+
+    pos += size;  // consume payload
+    if (cluster_stop >= 0 && pos > cluster_stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+}
+
+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) {
+  if (id != libwebm::kMkvBlockGroup && id != libwebm::kMkvSimpleBlock)
+    return E_PARSE_FAILED;
+
+  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 (std::nothrow) BlockEntry*[m_entries_size];
+    if (m_entries == NULL)
+      return -1;
+
+    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 (std::nothrow) BlockEntry*[entries_size];
+      if (entries == NULL)
+        return -1;
+
+      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 == libwebm::kMkvBlockGroup)
+    return CreateBlockGroup(pos, size, discard_padding);
+  else
+    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 = ReadID(pReader, pos, len);
+    if (id < 0 || (pos + len) > stop)
+      return E_FILE_FORMAT_INVALID;
+
+    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 == libwebm::kMkvBlock) {
+      if (bpos < 0) {  // Block ID
+        bpos = pos;
+        bsize = size;
+      }
+    } else if (id == libwebm::kMkvBlockDuration) {
+      if (size > 8)
+        return E_FILE_FORMAT_INVALID;
+
+      duration = UnserializeUInt(pReader, pos, size);
+
+      if (duration < 0)
+        return E_FILE_FORMAT_INVALID;
+    } else if (id == libwebm::kMkvReferenceBlock) {
+      if (size > 8 || size <= 0)
+        return E_FILE_FORMAT_INVALID;
+      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
+        next = time;
+    }
+
+    pos += size;  // consume payload
+    if (pos > stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+  if (bpos < 0)
+    return E_FILE_FORMAT_INVALID;
+
+  if (pos != stop)
+    return E_FILE_FORMAT_INVALID;
+  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();
+
+  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;
+  }
+}
+
+const BlockEntry* Cluster::GetEntry(const CuePoint& cp,
+                                    const CuePoint::TrackPosition& tp) const {
+  assert(m_pSegment);
+  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;
+  }
+}
+
+BlockEntry::BlockEntry(Cluster* p, long idx) : m_pCluster(p), m_index(idx) {}
+BlockEntry::~BlockEntry() {}
+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;
+}
+
+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 (std::nothrow) Frame[m_frame_count];
+    if (m_frames == NULL)
+      return -1;
+
+    Frame& f = m_frames[0];
+    f.pos = pos;
+
+    const long long frame_size = stop - pos;
+
+    if (frame_size > LONG_MAX || frame_size <= 0)
+      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
+  if (pos > stop)
+    return E_FILE_FORMAT_INVALID;
+
+  m_frame_count = int(biased_count) + 1;
+
+  m_frames = new (std::nothrow) Frame[m_frame_count];
+  if (m_frames == NULL)
+    return -1;
+
+  if (!m_frames)
+    return E_FILE_FORMAT_INVALID;
+
+  if (lacing == 1) {  // Xiph
+    Frame* pf = m_frames;
+    Frame* const pf_end = pf + m_frame_count;
+
+    long 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);
+      if (pf >= pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      f.pos = 0;  // patch later
+
+      if (frame_size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      f.len = frame_size;
+      size += frame_size;  // contribution of this frame
+
+      --frame_count;
+    }
+
+    if (pf >= pf_end || pos > stop)
+      return E_FILE_FORMAT_INVALID;
+
+    {
+      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 || frame_size <= 0)
+        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);
+
+      if ((pos + f.len) > stop)
+        return E_FILE_FORMAT_INVALID;
+
+      f.pos = pos;
+      pos += f.len;
+    }
+
+    assert(pos == stop);
+    if (pos != stop)
+      return E_FILE_FORMAT_INVALID;
+
+  } else if (lacing == 2) {  // fixed-size lacing
+    if (pos >= stop)
+      return E_FILE_FORMAT_INVALID;
+
+    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 || frame_size <= 0)
+      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);
+      if ((pos + frame_size) > stop)
+        return E_FILE_FORMAT_INVALID;
+
+      Frame& f = *pf++;
+
+      f.pos = pos;
+      f.len = static_cast<long>(frame_size);
+
+      pos += frame_size;
+    }
+
+    assert(pos == stop);
+    if (pos != stop)
+      return E_FILE_FORMAT_INVALID;
+
+  } else {
+    assert(lacing == 3);  // EBML lacing
+
+    if (pos >= stop)
+      return E_FILE_FORMAT_INVALID;
+
+    long 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);
+      if (pf >= pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      const Frame& prev = *pf++;
+      assert(prev.len == frame_size);
+      if (prev.len != frame_size)
+        return E_FILE_FORMAT_INVALID;
+
+      assert(pf < pf_end);
+      if (pf >= pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      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
+      if (pos > stop)
+        return E_FILE_FORMAT_INVALID;
+
+      const long 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);
+      // Check if size + curr.len could overflow.
+      if (size > LLONG_MAX - curr.len) {
+        return E_FILE_FORMAT_INVALID;
+      }
+      size += curr.len;  // contribution of this frame
+
+      --frame_count;
+    }
+
+    // parse last frame
+    if (frame_count > 0) {
+      if (pos > stop || pf >= pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      const Frame& prev = *pf++;
+      assert(prev.len == frame_size);
+      if (prev.len != frame_size)
+        return E_FILE_FORMAT_INVALID;
+
+      if (pf >= pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      Frame& curr = *pf++;
+      if (pf != pf_end)
+        return E_FILE_FORMAT_INVALID;
+
+      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 || frame_size <= 0)
+        return E_FILE_FORMAT_INVALID;
+
+      curr.len = static_cast<long>(frame_size);
+    }
+
+    pf = m_frames;
+    while (pf != pf_end) {
+      Frame& f = *pf++;
+      if ((pos + f.len) > stop)
+        return E_FILE_FORMAT_INVALID;
+
+      f.pos = pos;
+      pos += f.len;
+    }
+
+    if (pos != stop)
+      return E_FILE_FORMAT_INVALID;
+  }
+
+  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);
+
+  // Check if tc0 + m_timecode would overflow.
+  if (tc0 < 0 || LLONG_MAX - tc0 < m_timecode) {
+    return -1;
+  }
+
+  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);
+
+  // Check if tc * scale could overflow.
+  if (tc != 0 && scale > LLONG_MAX / tc) {
+    return -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; }
+
+}  // namespace mkvparser
diff --git a/video/mkv/mkvparser.h b/video/mkv/mkvparser.h
new file mode 100644
index 00000000000..848d01f03ec
--- /dev/null
+++ b/video/mkv/mkvparser.h
@@ -0,0 +1,1147 @@
+// 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_MKVPARSER_H_
+#define MKVPARSER_MKVPARSER_H_
+
+#include <cstddef>
+
+namespace mkvparser {
+
+const int E_PARSE_FAILED = -1;
+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() {}
+};
+
+template <typename Type>
+Type* SafeArrayAlloc(unsigned long long num_elements,
+                     unsigned long long element_size);
+long long GetUIntLength(IMkvReader*, long long, long&);
+long long ReadUInt(IMkvReader*, long long, long&);
+long long ReadID(IMkvReader* pReader, long long pos, long& len);
+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 long size,
+                    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 { return (GetKind() == kBlockEOS); }
+  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_;
+};
+
+struct PrimaryChromaticity {
+  PrimaryChromaticity() : x(0), y(0) {}
+  ~PrimaryChromaticity() {}
+  static bool Parse(IMkvReader* reader, long long read_pos,
+                    long long value_size, bool is_x,
+                    PrimaryChromaticity** chromaticity);
+  float x;
+  float y;
+};
+
+struct MasteringMetadata {
+  static const float kValueNotPresent;
+
+  MasteringMetadata()
+      : r(NULL),
+        g(NULL),
+        b(NULL),
+        white_point(NULL),
+        luminance_max(kValueNotPresent),
+        luminance_min(kValueNotPresent) {}
+  ~MasteringMetadata() {
+    delete r;
+    delete g;
+    delete b;
+    delete white_point;
+  }
+
+  static bool Parse(IMkvReader* reader, long long element_start,
+                    long long element_size,
+                    MasteringMetadata** mastering_metadata);
+
+  PrimaryChromaticity* r;
+  PrimaryChromaticity* g;
+  PrimaryChromaticity* b;
+  PrimaryChromaticity* white_point;
+  float luminance_max;
+  float luminance_min;
+};
+
+struct Colour {
+  static const long long kValueNotPresent;
+
+  // Unless otherwise noted all values assigned upon construction are the
+  // equivalent of unspecified/default.
+  Colour()
+      : matrix_coefficients(kValueNotPresent),
+        bits_per_channel(kValueNotPresent),
+        chroma_subsampling_horz(kValueNotPresent),
+        chroma_subsampling_vert(kValueNotPresent),
+        cb_subsampling_horz(kValueNotPresent),
+        cb_subsampling_vert(kValueNotPresent),
+        chroma_siting_horz(kValueNotPresent),
+        chroma_siting_vert(kValueNotPresent),
+        range(kValueNotPresent),
+        transfer_characteristics(kValueNotPresent),
+        primaries(kValueNotPresent),
+        max_cll(kValueNotPresent),
+        max_fall(kValueNotPresent),
+        mastering_metadata(NULL) {}
+  ~Colour() {
+    delete mastering_metadata;
+    mastering_metadata = NULL;
+  }
+
+  static bool Parse(IMkvReader* reader, long long element_start,
+                    long long element_size, Colour** colour);
+
+  long long matrix_coefficients;
+  long long bits_per_channel;
+  long long chroma_subsampling_horz;
+  long long chroma_subsampling_vert;
+  long long cb_subsampling_horz;
+  long long cb_subsampling_vert;
+  long long chroma_siting_horz;
+  long long chroma_siting_vert;
+  long long range;
+  long long transfer_characteristics;
+  long long primaries;
+  long long max_cll;
+  long long max_fall;
+
+  MasteringMetadata* mastering_metadata;
+};
+
+struct Projection {
+  enum ProjectionType {
+    kTypeNotPresent = -1,
+    kRectangular = 0,
+    kEquirectangular = 1,
+    kCubeMap = 2,
+    kMesh = 3,
+  };
+  static const float kValueNotPresent;
+  Projection()
+      : type(kTypeNotPresent),
+        private_data(NULL),
+        private_data_length(0),
+        pose_yaw(kValueNotPresent),
+        pose_pitch(kValueNotPresent),
+        pose_roll(kValueNotPresent) {}
+  ~Projection() { delete[] private_data; }
+  static bool Parse(IMkvReader* reader, long long element_start,
+                    long long element_size, Projection** projection);
+
+  ProjectionType type;
+  unsigned char* private_data;
+  size_t private_data_length;
+  float pose_yaw;
+  float pose_pitch;
+  float pose_roll;
+};
+
+class VideoTrack : public Track {
+  VideoTrack(const VideoTrack&);
+  VideoTrack& operator=(const VideoTrack&);
+
+  VideoTrack(Segment*, long long element_start, long long element_size);
+
+ public:
+  virtual ~VideoTrack();
+  static long Parse(Segment*, const Info&, long long element_start,
+                    long long element_size, VideoTrack*&);
+
+  long long GetWidth() const;
+  long long GetHeight() const;
+  long long GetDisplayWidth() const;
+  long long GetDisplayHeight() const;
+  long long GetDisplayUnit() const;
+  long long GetStereoMode() const;
+  double GetFrameRate() const;
+
+  bool VetEntry(const BlockEntry*) const;
+  long Seek(long long time_ns, const BlockEntry*&) const;
+
+  Colour* GetColour() const;
+
+  Projection* GetProjection() const;
+
+  const char* GetColourSpace() const { return m_colour_space; }
+
+ private:
+  long long m_width;
+  long long m_height;
+  long long m_display_width;
+  long long m_display_height;
+  long long m_display_unit;
+  long long m_stereo_mode;
+  char* m_colour_space;
+  double m_rate;
+
+  Colour* m_colour;
+  Projection* m_projection;
+};
+
+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 Tags {
+  Tags(const Tags&);
+  Tags& operator=(const Tags&);
+
+ 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;
+
+  Tags(Segment*, long long payload_start, long long payload_size,
+       long long element_start, long long element_size);
+
+  ~Tags();
+
+  long Parse();
+
+  class Tag;
+  class SimpleTag;
+
+  class SimpleTag {
+    friend class Tag;
+    SimpleTag();
+    SimpleTag(const SimpleTag&);
+    ~SimpleTag();
+    SimpleTag& operator=(const SimpleTag&);
+
+   public:
+    const char* GetTagName() const;
+    const char* GetTagString() const;
+
+   private:
+    void Init();
+    void ShallowCopy(SimpleTag&) const;
+    void Clear();
+    long Parse(IMkvReader*, long long pos, long long size);
+
+    char* m_tag_name;
+    char* m_tag_string;
+  };
+
+  class Tag {
+    friend class Tags;
+    Tag();
+    Tag(const Tag&);
+    ~Tag();
+    Tag& operator=(const Tag&);
+
+   public:
+    int GetSimpleTagCount() const;
+    const SimpleTag* GetSimpleTag(int index) const;
+
+   private:
+    void Init();
+    void ShallowCopy(Tag&) const;
+    void Clear();
+    long Parse(IMkvReader*, long long pos, long long size);
+
+    long ParseSimpleTag(IMkvReader*, long long pos, long long size);
+    bool ExpandSimpleTagsArray();
+
+    SimpleTag* m_simple_tags;
+    int m_simple_tags_size;
+    int m_simple_tags_count;
+  };
+
+  int GetTagCount() const;
+  const Tag* GetTag(int index) const;
+
+ private:
+  long ParseTag(long long pos, long long size);
+  bool ExpandTagsArray();
+
+  Tag* m_tags;
+  int m_tags_size;
+  int m_tags_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 {
+    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;
+
+  bool 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
+
+    bool 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;
+
+  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:
+  bool Init() const;
+  bool 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);
+
+  const SeekHead* GetSeekHead() const;
+  const Tracks* GetTracks() const;
+  const SegmentInfo* GetInfo() const;
+  const Cues* GetCues() const;
+  const Chapters* GetChapters() const;
+  const Tags* GetTags() 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;
+  Tags* m_pTags;
+  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&);
+
+  bool AppendCluster(Cluster*);
+  bool 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&);
+};
+
+}  // namespace mkvparser
+
+inline long mkvparser::Segment::LoadCluster() {
+  long long pos;
+  long size;
+
+  return LoadCluster(pos, size);
+}
+
+#endif  // MKVPARSER_MKVPARSER_H_
diff --git a/video/mkv/mkvreader.cpp b/video/mkv/mkvreader.cpp
new file mode 100644
index 00000000000..6bdbfdfad25
--- /dev/null
+++ b/video/mkv/mkvreader.cpp
@@ -0,0 +1,135 @@
+// 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 "video/mkv/mkvreader.h"
+
+#include <sys/types.h>
+
+#include <cassert>
+
+namespace mkvparser {
+
+MkvReader::MkvReader() : m_file(NULL), reader_owns_file_(true) {}
+
+MkvReader::MkvReader(FILE* fp) : m_file(fp), reader_owns_file_(false) {
+  GetFileSize();
+}
+
+MkvReader::~MkvReader() {
+  if (reader_owns_file_)
+    Close();
+  m_file = NULL;
+}
+
+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
+  return !GetFileSize();
+}
+
+bool MkvReader::GetFileSize() {
+  if (m_file == NULL)
+    return false;
+#ifdef _MSC_VER
+  int status = _fseeki64(m_file, 0L, SEEK_END);
+
+  if (status)
+    return false;  // error
+
+  m_length = _ftelli64(m_file);
+#else
+  fseek(m_file, 0L, SEEK_END);
+  m_length = ftell(m_file);
+#endif
+  assert(m_length >= 0);
+
+  if (m_length < 0)
+    return false;
+
+#ifdef _MSC_VER
+  status = _fseeki64(m_file, 0L, SEEK_SET);
+
+  if (status)
+    return false;  // error
+#else
+  fseek(m_file, 0L, SEEK_SET);
+#endif
+
+  return true;
+}
+
+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
+#elif defined(_WIN32)
+  fseeko64(m_file, static_cast<off_t>(offset), SEEK_SET);
+#else
+  fseeko(m_file, static_cast<off_t>(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
+}
+
+}  // namespace mkvparser
diff --git a/video/mkv/mkvreader.h b/video/mkv/mkvreader.h
new file mode 100644
index 00000000000..dd0b54b057c
--- /dev/null
+++ b/video/mkv/mkvreader.h
@@ -0,0 +1,45 @@
+// 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 MKVPARSER_MKVREADER_H_
+#define MKVPARSER_MKVREADER_H_
+
+#include <cstdio>
+
+#include "video/mkv/mkvparser.h"
+
+namespace mkvparser {
+
+class MkvReader : public IMkvReader {
+ public:
+  MkvReader();
+  explicit MkvReader(FILE* fp);
+  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:
+  MkvReader(const MkvReader&);
+  MkvReader& operator=(const MkvReader&);
+
+  // Determines the size of the file. This is called either by the constructor
+  // or by the Open function depending on file ownership. Returns true on
+  // success.
+  bool GetFileSize();
+
+  long long m_length;
+  FILE* m_file;
+  bool reader_owns_file_;
+};
+
+}  // namespace mkvparser
+
+#endif  // MKVPARSER_MKVREADER_H_
diff --git a/video/mkv/webmids.h b/video/mkv/webmids.h
new file mode 100644
index 00000000000..fc0c2081409
--- /dev/null
+++ b/video/mkv/webmids.h
@@ -0,0 +1,193 @@
+// 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 COMMON_WEBMIDS_H_
+#define COMMON_WEBMIDS_H_
+
+namespace libwebm {
+
+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,
+  kMkvTitle = 0x7BA9,
+  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,
+  kMkvColourSpace = 0x2EB524,
+  kMkvFrameRate = 0x2383E3,
+  // end video
+  // colour
+  kMkvColour = 0x55B0,
+  kMkvMatrixCoefficients = 0x55B1,
+  kMkvBitsPerChannel = 0x55B2,
+  kMkvChromaSubsamplingHorz = 0x55B3,
+  kMkvChromaSubsamplingVert = 0x55B4,
+  kMkvCbSubsamplingHorz = 0x55B5,
+  kMkvCbSubsamplingVert = 0x55B6,
+  kMkvChromaSitingHorz = 0x55B7,
+  kMkvChromaSitingVert = 0x55B8,
+  kMkvRange = 0x55B9,
+  kMkvTransferCharacteristics = 0x55BA,
+  kMkvPrimaries = 0x55BB,
+  kMkvMaxCLL = 0x55BC,
+  kMkvMaxFALL = 0x55BD,
+  // mastering metadata
+  kMkvMasteringMetadata = 0x55D0,
+  kMkvPrimaryRChromaticityX = 0x55D1,
+  kMkvPrimaryRChromaticityY = 0x55D2,
+  kMkvPrimaryGChromaticityX = 0x55D3,
+  kMkvPrimaryGChromaticityY = 0x55D4,
+  kMkvPrimaryBChromaticityX = 0x55D5,
+  kMkvPrimaryBChromaticityY = 0x55D6,
+  kMkvWhitePointChromaticityX = 0x55D7,
+  kMkvWhitePointChromaticityY = 0x55D8,
+  kMkvLuminanceMax = 0x55D9,
+  kMkvLuminanceMin = 0x55DA,
+  // end mastering metadata
+  // end colour
+  // projection
+  kMkvProjection = 0x7670,
+  kMkvProjectionType = 0x7671,
+  kMkvProjectionPrivate = 0x7672,
+  kMkvProjectionPoseYaw = 0x7673,
+  kMkvProjectionPosePitch = 0x7674,
+  kMkvProjectionPoseRoll = 0x7675,
+  // end projection
+  // audio
+  kMkvAudio = 0xE1,
+  kMkvSamplingFrequency = 0xB5,
+  kMkvOutputSamplingFrequency = 0x78B5,
+  kMkvChannels = 0x9F,
+  kMkvBitDepth = 0x6264,
+  // end audio
+  // ContentEncodings
+  kMkvContentEncodings = 0x6D80,
+  kMkvContentEncoding = 0x6240,
+  kMkvContentEncodingOrder = 0x5031,
+  kMkvContentEncodingScope = 0x5032,
+  kMkvContentEncodingType = 0x5033,
+  kMkvContentCompression = 0x5034,
+  kMkvContentCompAlgo = 0x4254,
+  kMkvContentCompSettings = 0x4255,
+  kMkvContentEncryption = 0x5035,
+  kMkvContentEncAlgo = 0x47E1,
+  kMkvContentEncKeyID = 0x47E2,
+  kMkvContentSignature = 0x47E3,
+  kMkvContentSigKeyID = 0x47E4,
+  kMkvContentSigAlgo = 0x47E5,
+  kMkvContentSigHashAlgo = 0x47E6,
+  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,
+  // Tags
+  kMkvTags = 0x1254C367,
+  kMkvTag = 0x7373,
+  kMkvSimpleTag = 0x67C8,
+  kMkvTagName = 0x45A3,
+  kMkvTagString = 0x4487
+};
+
+}  // namespace libwebm
+
+#endif  // COMMON_WEBMIDS_H_
diff --git a/video/module.mk b/video/module.mk
index f9125f26b3b..1ee711268f6 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -14,7 +14,9 @@ MODULE_OBJS := \
 	qt_decoder.o \
 	smk_decoder.o \
 	subtitles.o \
-	video_decoder.o
+	video_decoder.o \
+	mkv/mkvparser.o \
+	mkv/mkvreader.o
 
 ifdef USE_BINK
 MODULE_OBJS += \


Commit: 6f7d132fe9faae3c12c801a01ad6f93b527649fe
    https://github.com/scummvm/scummvm/commit/6f7d132fe9faae3c12c801a01ad6f93b527649fe
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Fix warnings

Changed paths:
    video/mkv/mkvparser.cpp
    video/mkv/mkvparser.h


diff --git a/video/mkv/mkvparser.cpp b/video/mkv/mkvparser.cpp
index 323cd61d7d8..feadda1eeb6 100644
--- a/video/mkv/mkvparser.cpp
+++ b/video/mkv/mkvparser.cpp
@@ -293,7 +293,7 @@ long UnserializeInt(IMkvReader* pReader, long long pos, long long size,
     return E_FILE_FORMAT_INVALID;
 
   signed char first_byte = 0;
-  const long status = pReader->Read(pos, 1, (unsigned char*)&first_byte);
+  long status = pReader->Read(pos, 1, (unsigned char*)&first_byte);
 
   if (status < 0)
     return status;
@@ -304,7 +304,7 @@ long UnserializeInt(IMkvReader* pReader, long long pos, long long size,
   for (long i = 1; i < size; ++i) {
     unsigned char b;
 
-    const long status = pReader->Read(pos, 1, &b);
+    status = pReader->Read(pos, 1, &b);
 
     if (status < 0)
       return status;
@@ -821,7 +821,7 @@ long long Segment::ParseHeaders() {
   // inner (level 1) elements.
   long long total, available;
 
-  const int status = m_pReader->Length(&total, &available);
+  long long status = m_pReader->Length(&total, &available);
 
   if (status < 0)  // error
     return status;
@@ -939,7 +939,7 @@ long long Segment::ParseHeaders() {
       if (m_pInfo == NULL)
         return -1;
 
-      const long status = m_pInfo->Parse();
+      status = m_pInfo->Parse();
 
       if (status)
         return status;
@@ -953,7 +953,7 @@ long long Segment::ParseHeaders() {
       if (m_pTracks == NULL)
         return -1;
 
-      const long status = m_pTracks->Parse();
+      status = m_pTracks->Parse();
 
       if (status)
         return status;
@@ -973,7 +973,7 @@ long long Segment::ParseHeaders() {
         if (m_pSeekHead == NULL)
           return -1;
 
-        const long status = m_pSeekHead->Parse();
+        status = m_pSeekHead->Parse();
 
         if (status)
           return status;
@@ -986,7 +986,7 @@ long long Segment::ParseHeaders() {
         if (m_pChapters == NULL)
           return -1;
 
-        const long status = m_pChapters->Parse();
+        status = m_pChapters->Parse();
 
         if (status)
           return status;
@@ -999,7 +999,7 @@ long long Segment::ParseHeaders() {
         if (m_pTags == NULL)
           return -1;
 
-        const long status = m_pTags->Parse();
+        status = m_pTags->Parse();
 
         if (status)
           return status;
@@ -2031,11 +2031,11 @@ bool Cues::Find(long long time_ns, const Track* pTrack, const CuePoint*& pCP,
     if (k >= jj)
       return false;
 
-    CuePoint* const pCP = *k;
-    if (pCP == NULL)
+    CuePoint* const pCP_ = *k;
+    if (pCP_ == NULL)
       return false;
 
-    const long long t = pCP->GetTime(m_pSegment);
+    const long long t = pCP_->GetTime(m_pSegment);
 
     if (t <= time_ns)
       i = k + 1;
@@ -2818,7 +2818,7 @@ long Segment::ParseNext(const Cluster* pCurr, const Cluster*& pResult,
   // pos now points to just beyond the last fully-loaded cluster
 
   for (;;) {
-    const long status = DoParseNext(pResult, pos, len);
+    status = DoParseNext(pResult, pos, len);
 
     if (status <= 1)
       return status;
@@ -3361,7 +3361,7 @@ long Chapters::Edition::Parse(IMkvReader* pReader, long long pos,
   const long long stop = pos + size;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
 
     long status = ParseElementHeader(pReader, pos, stop, id, size);
 
@@ -3494,7 +3494,7 @@ 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 long id;
 
     long status = ParseElementHeader(pReader, pos, stop, id, size);
 
@@ -3647,7 +3647,7 @@ long Chapters::Display::Parse(IMkvReader* pReader, long long pos,
   const long long stop = pos + size;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
 
     long status = ParseElementHeader(pReader, pos, stop, id, size);
 
@@ -3826,7 +3826,7 @@ long Tags::Tag::Parse(IMkvReader* pReader, long long pos, long long size) {
   const long long stop = pos + size;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
 
     long status = ParseElementHeader(pReader, pos, stop, id, size);
 
@@ -3917,7 +3917,7 @@ long Tags::SimpleTag::Parse(IMkvReader* pReader, long long pos,
   const long long stop = pos + size;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
 
     long status = ParseElementHeader(pReader, pos, stop, id, size);
 
@@ -3987,7 +3987,7 @@ long SegmentInfo::Parse() {
   while (pos < stop) {
     long long id, size;
 
-    const long status = ParseElementHeader(pReader, pos, stop, id, size);
+    long status = ParseElementHeader(pReader, pos, stop, id, size);
 
     if (status < 0)  // error
       return status;
@@ -3998,7 +3998,7 @@ long SegmentInfo::Parse() {
       if (m_timecodeScale <= 0)
         return E_FILE_FORMAT_INVALID;
     } else if (id == libwebm::kMkvDuration) {
-      const long status = UnserializeFloat(pReader, pos, size, m_duration);
+      status = UnserializeFloat(pReader, pos, size, m_duration);
 
       if (status < 0)
         return status;
@@ -4006,19 +4006,19 @@ long SegmentInfo::Parse() {
       if (m_duration < 0)
         return E_FILE_FORMAT_INVALID;
     } else if (id == libwebm::kMkvMuxingApp) {
-      const long status =
+      status =
           UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8);
 
       if (status)
         return status;
     } else if (id == libwebm::kMkvWritingApp) {
-      const long status =
+      status =
           UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8);
 
       if (status)
         return status;
     } else if (id == libwebm::kMkvTitle) {
-      const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8);
+      status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8);
 
       if (status)
         return status;
@@ -4167,7 +4167,7 @@ long ContentEncoding::ParseContentEncAESSettingsEntry(
   const long long stop = start + size;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
     const long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -4198,7 +4198,7 @@ long ContentEncoding::ParseContentEncodingEntry(long long start, long long size,
   int encryption_count = 0;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
     const long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -4238,7 +4238,7 @@ long ContentEncoding::ParseContentEncodingEntry(long long start, long long size,
 
   pos = start;
   while (pos < stop) {
-    long long id, size;
+    long long id;
     long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -4301,7 +4301,7 @@ long ContentEncoding::ParseCompressionEntry(long long start, long long size,
   bool valid = false;
 
   while (pos < stop) {
-    long long id, size;
+    long long id;
     const long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -4360,8 +4360,8 @@ long ContentEncoding::ParseEncryptionEntry(long long start, long long size,
   const long long stop = start + size;
 
   while (pos < stop) {
-    long long id, size;
-    const long status = ParseElementHeader(pReader, pos, stop, id, size);
+    long long id;
+    long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
 
@@ -4440,7 +4440,7 @@ long ContentEncoding::ParseEncryptionEntry(long long start, long long size,
     } else if (id == libwebm::kMkvContentSigHashAlgo) {
       encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size);
     } else if (id == libwebm::kMkvContentEncAESSettings) {
-      const long status = ParseContentEncAESSettingsEntry(
+      status = ParseContentEncAESSettingsEntry(
           pos, size, pReader, &encryption->aes_settings);
       if (status)
         return status;
@@ -4920,7 +4920,7 @@ long Track::ParseContentEncodingsEntry(long long start, long long size) {
   // Count ContentEncoding elements.
   int count = 0;
   while (pos < stop) {
-    long long id, size;
+    long long id;
     const long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -4945,7 +4945,7 @@ long Track::ParseContentEncodingsEntry(long long start, long long size) {
 
   pos = start;
   while (pos < stop) {
-    long long id, size;
+    long long id;
     long status = ParseElementHeader(pReader, pos, stop, id, size);
     if (status < 0)  // error
       return status;
@@ -5216,7 +5216,7 @@ bool Projection::Parse(IMkvReader* reader, long long start, long long size,
     long long child_id = 0;
     long long child_size = 0;
 
-    const long long status =
+    long long status =
         ParseElementHeader(reader, read_pos, end, child_id, child_size);
     if (status < 0)
       return false;
@@ -5236,7 +5236,7 @@ bool Projection::Parse(IMkvReader* reader, long long start, long long size,
       if (data == NULL)
         return false;
 
-      const int status =
+      status =
           reader->Read(read_pos, static_cast<long>(child_size), data);
 
       if (status) {
@@ -5328,7 +5328,7 @@ long VideoTrack::Parse(Segment* pSegment, const Info& info,
   while (pos < stop) {
     long long id, size;
 
-    const long status = ParseElementHeader(pReader, pos, stop, id, size);
+    long status = ParseElementHeader(pReader, pos, stop, id, size);
 
     if (status < 0)  // error
       return status;
@@ -5364,7 +5364,7 @@ long VideoTrack::Parse(Segment* pSegment, const Info& info,
       if (stereo_mode < 0)
         return E_FILE_FORMAT_INVALID;
     } else if (id == libwebm::kMkvFrameRate) {
-      const long status = UnserializeFloat(pReader, pos, size, rate);
+      status = UnserializeFloat(pReader, pos, size, rate);
 
       if (status < 0)
         return status;
@@ -5387,7 +5387,7 @@ long VideoTrack::Parse(Segment* pSegment, const Info& info,
       }
     } else if (id == libwebm::kMkvColourSpace) {
       char* colour_space = NULL;
-      const long status = UnserializeString(pReader, pos, size, colour_space);
+      status = UnserializeString(pReader, pos, size, colour_space);
       if (status < 0)
         return status;
       colour_space_ptr.reset(colour_space);
@@ -5694,7 +5694,7 @@ long Tracks::Parse() {
 
     long long id, payload_size;
 
-    const long status =
+    long status =
         ParseElementHeader(pReader, pos, stop, id, payload_size);
 
     if (status < 0)  // error
@@ -5712,7 +5712,7 @@ long Tracks::Parse() {
       Track*& pTrack = *m_trackEntriesEnd;
       pTrack = NULL;
 
-      const long status = ParseTrackEntry(pos, payload_size, element_start,
+      status = ParseTrackEntry(pos, payload_size, element_start,
                                           element_size, pTrack);
       if (status)
         return status;
@@ -5774,7 +5774,7 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
   while (pos < track_stop) {
     long long id, size;
 
-    const long status = ParseElementHeader(pReader, pos, track_stop, id, size);
+    long status = ParseElementHeader(pReader, pos, track_stop, id, size);
 
     if (status < 0)  // error
       return status;
@@ -5805,7 +5805,7 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
       while (pos_ != pos_end) {
         unsigned char b;
 
-        const int status = pReader->Read(pos_, 1, &b);
+        status = pReader->Read(pos_, 1, &b);
 
         if (status)
           return status;
@@ -5830,13 +5830,13 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
 
       info.type = static_cast<long>(type);
     } else if (id == libwebm::kMkvName) {
-      const long status =
+      status =
           UnserializeString(pReader, pos, size, info.nameAsUTF8);
 
       if (status)
         return status;
     } else if (id == libwebm::kMkvLanguage) {
-      const long status = UnserializeString(pReader, pos, size, info.language);
+      status = UnserializeString(pReader, pos, size, info.language);
 
       if (status)
         return status;
@@ -5848,7 +5848,7 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
 
       info.defaultDuration = static_cast<unsigned long long>(duration);
     } else if (id == libwebm::kMkvCodecID) {
-      const long status = UnserializeString(pReader, pos, size, info.codecId);
+      status = UnserializeString(pReader, pos, size, info.codecId);
 
       if (status)
         return status;
@@ -5870,7 +5870,7 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
         if (buf == NULL)
           return -1;
 
-        const int status = pReader->Read(pos, static_cast<long>(buflen), buf);
+        status = pReader->Read(pos, static_cast<long>(buflen), buf);
 
         if (status) {
           delete[] buf;
@@ -5881,7 +5881,7 @@ long Tracks::ParseTrackEntry(long long track_start, long long track_size,
         info.codecPrivateSize = buflen;
       }
     } else if (id == libwebm::kMkvCodecName) {
-      const long status =
+      status =
           UnserializeString(pReader, pos, size, info.codecNameAsUTF8);
 
       if (status)
@@ -6094,7 +6094,7 @@ long Cluster::Load(long long& pos, long& len) const {
   if ((pos + len) > avail)
     return E_BUFFER_NOT_FULL;
 
-  const long long size = ReadUInt(pReader, pos, len);
+  long long size = ReadUInt(pReader, pos, len);
 
   if (size < 0)  // error
     return static_cast<long>(cluster_size);
@@ -6104,7 +6104,7 @@ long Cluster::Load(long long& pos, long& len) const {
 
   pos += len;  // consume length of size of element
 
-  const long long unknown_size = (1LL << (7 * len)) - 1;
+  long long unknown_size = (1LL << (7 * len)) - 1;
 
   if (size != unknown_size)
     cluster_size = size;
@@ -6127,7 +6127,7 @@ long Cluster::Load(long long& pos, long& len) const {
       return E_BUFFER_NOT_FULL;
     }
 
-    long long result = GetUIntLength(pReader, pos, len);
+    result = GetUIntLength(pReader, pos, len);
 
     if (result < 0)  // error
       return static_cast<long>(result);
@@ -6182,12 +6182,12 @@ long Cluster::Load(long long& pos, long& len) const {
     if ((pos + len) > avail)
       return E_BUFFER_NOT_FULL;
 
-    const long long size = ReadUInt(pReader, pos, len);
+    size = ReadUInt(pReader, pos, len);
 
     if (size < 0)  // error
       return static_cast<long>(size);
 
-    const long long unknown_size = (1LL << (7 * len)) - 1;
+    unknown_size = (1LL << (7 * len)) - 1;
 
     if (size == unknown_size)
       return E_FILE_FORMAT_INVALID;
@@ -7206,7 +7206,7 @@ long Cluster::CreateBlockGroup(long long start_offset, long long size,
 
     pos += len;  // consume ID
 
-    const long long size = ReadUInt(pReader, pos, len);
+    size = ReadUInt(pReader, pos, len);
     assert(size >= 0);  // TODO
     assert((pos + len) <= stop);
 
diff --git a/video/mkv/mkvparser.h b/video/mkv/mkvparser.h
index 848d01f03ec..7f4dfb79647 100644
--- a/video/mkv/mkvparser.h
+++ b/video/mkv/mkvparser.h
@@ -479,7 +479,7 @@ struct Projection {
     kRectangular = 0,
     kEquirectangular = 1,
     kCubeMap = 2,
-    kMesh = 3,
+    kMesh = 3
   };
   static const float kValueNotPresent;
   Projection()


Commit: a4ab5b991f0f40368d04bfc53fd8a38612eab5d4
    https://github.com/scummvm/scummvm/commit/a4ab5b991f0f40368d04bfc53fd8a38612eab5d4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Added skeleton of VideoPlayer interface for MKV decoder

Changed paths:
  A video/mkv_decoder.cpp
  A video/mkv_decoder.h
    video/module.mk


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
new file mode 100644
index 00000000000..a09e69d605f
--- /dev/null
+++ b/video/mkv_decoder.cpp
@@ -0,0 +1,481 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "video/mkv_decoder.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+#include "graphics/pixelformat.h"
+#include "graphics/yuv_to_rgb.h"
+
+namespace Video {
+
+MKVDecoder::MKVDecoder() {
+	_fileStream = 0;
+
+	_videoTrack = 0;
+	_audioTrack = 0;
+	_hasVideo = _hasAudio = false;
+}
+
+MKVDecoder::~MKVDecoder() {
+	close();
+}
+
+bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
+	close();
+
+	_fileStream = stream;
+
+	// start up Ogg stream synchronization layer
+	ogg_sync_init(&_oggSync);
+
+	// init supporting Vorbis structures needed in header parsing
+	vorbis_info_init(&_vorbisInfo);
+	vorbis_comment vorbisComment;
+	vorbis_comment_init(&vorbisComment);
+
+	// init supporting Theora structures needed in header parsing
+	th_info theoraInfo;
+	th_info_init(&theoraInfo);
+	th_comment theoraComment;
+	th_comment_init(&theoraComment);
+	th_setup_info *theoraSetup = 0;
+
+	uint theoraPackets = 0, vorbisPackets = 0;
+
+	// Ogg file open; parse the headers
+	// Only interested in Vorbis/Theora streams
+	bool foundHeader = false;
+	while (!foundHeader) {
+		int ret = bufferData();
+
+		if (ret == 0)
+			break; // FIXME: Shouldn't this error out?
+
+		while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
+			ogg_stream_state test;
+
+			// is this a mandated initial header? If not, stop parsing
+			if (!ogg_page_bos(&_oggPage)) {
+				// don't leak the page; get it into the appropriate stream
+				queuePage(&_oggPage);
+				foundHeader = true;
+				break;
+			}
+
+			ogg_stream_init(&test, ogg_page_serialno(&_oggPage));
+			ogg_stream_pagein(&test, &_oggPage);
+			ogg_stream_packetout(&test, &_oggPacket);
+
+			// identify the codec: try theora
+			if (theoraPackets == 0 && th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket) >= 0) {
+				// it is theora
+				memcpy(&_theoraOut, &test, sizeof(test));
+				theoraPackets = 1;
+				_hasVideo = true;
+			} else if (vorbisPackets == 0 && vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket) >= 0) {
+				// it is vorbis
+				memcpy(&_vorbisOut, &test, sizeof(test));
+				vorbisPackets = 1;
+				_hasAudio = true;
+			} else {
+				// whatever it is, we don't care about it
+				ogg_stream_clear(&test);
+			}
+		}
+		// fall through to non-bos page parsing
+	}
+
+	// we're expecting more header packets.
+	while ((theoraPackets && theoraPackets < 3) || (vorbisPackets && vorbisPackets < 3)) {
+		int ret;
+
+		// look for further theora headers
+		while (theoraPackets && (theoraPackets < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) {
+			if (ret < 0)
+				error("Error parsing Theora stream headers; corrupt stream?");
+
+			if (!th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket))
+				error("Error parsing Theora stream headers; corrupt stream?");
+
+			theoraPackets++;
+		}
+
+		// look for more vorbis header packets
+		while (vorbisPackets && (vorbisPackets < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) {
+			if (ret < 0)
+				error("Error parsing Vorbis stream headers; corrupt stream?");
+
+			if (vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket))
+				error("Error parsing Vorbis stream headers; corrupt stream?");
+
+			vorbisPackets++;
+
+			if (vorbisPackets == 3)
+				break;
+		}
+
+		// The header pages/packets will arrive before anything else we
+		// care about, or the stream is not obeying spec
+
+		if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
+			queuePage(&_oggPage); // demux into the appropriate stream
+		} else {
+			ret = bufferData(); // someone needs more data
+
+			if (ret == 0)
+				error("End of file while searching for codec headers.");
+		}
+	}
+
+	// And now we have it all. Initialize decoders next
+	if (_hasVideo) {
+		_videoTrack = new VPXVideoTrack(getDefaultHighColorFormat(), theoraInfo, theoraSetup);
+		addTrack(_videoTrack);
+	}
+
+	th_info_clear(&theoraInfo);
+	th_comment_clear(&theoraComment);
+	th_setup_free(theoraSetup);
+
+	if (_hasAudio) {
+		_audioTrack = new VorbisAudioTrack(getSoundType(), _vorbisInfo);
+
+		// Get enough audio data to start us off
+		while (!_audioTrack->hasAudio()) {
+			// Queue more data
+			bufferData();
+			while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
+				queuePage(&_oggPage);
+
+			queueAudio();
+		}
+
+		addTrack(_audioTrack);
+	}
+
+	vorbis_comment_clear(&vorbisComment);
+
+	return true;
+}
+
+void MKVDecoder::close() {
+	VideoDecoder::close();
+
+	if (!_fileStream)
+		return;
+
+	if (_videoTrack) {
+		ogg_stream_clear(&_theoraOut);
+		_videoTrack = 0;
+	}
+
+	if (_audioTrack) {
+		ogg_stream_clear(&_vorbisOut);
+		_audioTrack = 0;
+	}
+
+	ogg_sync_clear(&_oggSync);
+	vorbis_info_clear(&_vorbisInfo);
+
+	delete _fileStream;
+	_fileStream = 0;
+
+	_hasVideo = _hasAudio = false;
+}
+
+void MKVDecoder::readNextPacket() {
+	// First, let's get our frame
+	if (_hasVideo) {
+		while (!_videoTrack->endOfTrack()) {
+			// theora is one in, one out...
+			if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) {
+				if (_videoTrack->decodePacket(_oggPacket))
+					break;
+			} else if (_theoraOut.e_o_s || _fileStream->eos()) {
+				// If we can't get any more frames, we're done.
+				_videoTrack->setEndOfVideo();
+			} else {
+				// Queue more data
+				bufferData();
+				while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
+					queuePage(&_oggPage);
+			}
+
+			// Update audio if we can
+			queueAudio();
+		}
+	}
+
+	// Then make sure we have enough audio buffered
+	ensureAudioBufferSize();
+}
+
+MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup) {
+	_theoraDecode = th_decode_alloc(&theoraInfo, theoraSetup);
+
+	if (theoraInfo.pixel_fmt != TH_PF_420)
+		error("Only theora YUV420 is supported");
+
+	int postProcessingMax;
+	th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &postProcessingMax, sizeof(postProcessingMax));
+	th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &postProcessingMax, sizeof(postProcessingMax));
+
+	_surface.create(theoraInfo.frame_width, theoraInfo.frame_height, format);
+
+	// Set up a display surface
+	_displaySurface.init(theoraInfo.pic_width, theoraInfo.pic_height, _surface.pitch,
+	                    _surface.getBasePtr(theoraInfo.pic_x, theoraInfo.pic_y), format);
+
+	// Set the frame rate
+	_frameRate = Common::Rational(theoraInfo.fps_numerator, theoraInfo.fps_denominator);
+
+	_endOfVideo = false;
+	_nextFrameStartTime = 0.0;
+	_curFrame = -1;
+}
+
+MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
+	th_decode_free(_theoraDecode);
+
+	_surface.free();
+	_displaySurface.setPixels(0);
+}
+
+bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &oggPacket) {
+	if (th_decode_packetin(_theoraDecode, &oggPacket, 0) == 0) {
+		_curFrame++;
+
+		// Convert YUV data to RGB data
+		th_ycbcr_buffer yuv;
+		th_decode_ycbcr_out(_theoraDecode, yuv);
+		translateYUVtoRGBA(yuv);
+
+		double time = th_granule_time(_theoraDecode, oggPacket.granulepos);
+
+		// We need to calculate when the next frame should be shown
+		// This is all in floating point because that's what the Ogg code gives us
+		// Ogg is a lossy container format, so it doesn't always list the time to the
+		// next frame. In such cases, we need to calculate it ourselves.
+		if (time == -1.0)
+			_nextFrameStartTime += _frameRate.getInverse().toDouble();
+		else
+			_nextFrameStartTime = time;
+
+		return true;
+	}
+
+	return false;
+}
+
+enum TheoraYUVBuffers {
+	kBufferY = 0,
+	kBufferU = 1,
+	kBufferV = 2
+};
+
+void MKVDecoder::VPXVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) {
+	// Width and height of all buffers have to be divisible by 2.
+	assert((YUVBuffer[kBufferY].width & 1) == 0);
+	assert((YUVBuffer[kBufferY].height & 1) == 0);
+	assert((YUVBuffer[kBufferU].width & 1) == 0);
+	assert((YUVBuffer[kBufferV].width & 1) == 0);
+
+	// UV images have to have a quarter of the Y image resolution
+	assert(YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1);
+	assert(YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1);
+	assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1);
+	assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1);
+
+	YUVToRGBMan.convert420(&_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
+}
+
+static vorbis_info *info = 0;
+
+MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo) :
+		AudioTrack(soundType) {
+	vorbis_synthesis_init(&_vorbisDSP, &vorbisInfo);
+	vorbis_block_init(&_vorbisDSP, &_vorbisBlock);
+	info = &vorbisInfo;
+
+	_audStream = Audio::makeQueuingAudioStream(vorbisInfo.rate, vorbisInfo.channels != 1);
+
+	_audioBufferFill = 0;
+	_audioBuffer = 0;
+	_endOfAudio = false;
+}
+
+MKVDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
+	vorbis_dsp_clear(&_vorbisDSP);
+	vorbis_block_clear(&_vorbisBlock);
+	delete _audStream;
+	free(_audioBuffer);
+}
+
+Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
+	return _audStream;
+}
+
+#define AUDIOFD_FRAGSIZE 10240
+
+#ifndef USE_TREMOR
+static double rint(double v) {
+	return floor(v + 0.5);
+}
+#endif
+
+bool MKVDecoder::VorbisAudioTrack::decodeSamples() {
+#ifdef USE_TREMOR
+	ogg_int32_t **pcm;
+#else
+	float **pcm;
+#endif
+
+	// if there's pending, decoded audio, grab it
+	int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
+
+	if (ret > 0) {
+		if (!_audioBuffer) {
+			_audioBuffer = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t));
+			assert(_audioBuffer);
+		}
+
+		int channels = _audStream->isStereo() ? 2 : 1;
+		int count = _audioBufferFill / 2;
+		int maxsamples = ((AUDIOFD_FRAGSIZE - _audioBufferFill) / channels) >> 1;
+		int i;
+
+		for (i = 0; i < ret && i < maxsamples; i++) {
+			for (int j = 0; j < channels; j++) {
+#ifdef USE_TREMOR
+				int val = CLIP((int)pcm[j][i] >> 9, -32768, 32767);
+#else
+				int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767);
+#endif
+				_audioBuffer[count++] = val;
+			}
+		}
+
+		vorbis_synthesis_read(&_vorbisDSP, i);
+		_audioBufferFill += (i * channels) << 1;
+
+		if (_audioBufferFill == AUDIOFD_FRAGSIZE) {
+			byte flags = Audio::FLAG_16BITS;
+
+			if (_audStream->isStereo())
+				flags |= Audio::FLAG_STEREO;
+
+#ifdef SCUMM_LITTLE_ENDIAN
+			flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+
+			_audStream->queueBuffer((byte *)_audioBuffer, AUDIOFD_FRAGSIZE, DisposeAfterUse::YES, flags);
+
+			// The audio mixer is now responsible for the old audio buffer.
+			// We need to create a new one.
+			_audioBuffer = 0;
+			_audioBufferFill = 0;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
+	return _audStream->numQueuedStreams() > 0;
+}
+
+bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
+	// TODO: 5 is very arbitrary. We probably should do something like QuickTime does.
+	return !_endOfAudio && _audStream->numQueuedStreams() < 5;
+}
+
+void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &oggPacket) {
+	if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) // test for success
+		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
+}
+
+void MKVDecoder::queuePage(ogg_page *page) {
+	if (_hasVideo)
+		ogg_stream_pagein(&_theoraOut, page);
+
+	if (_hasAudio)
+		ogg_stream_pagein(&_vorbisOut, page);
+}
+
+int MKVDecoder::bufferData() {
+	char *buffer = ogg_sync_buffer(&_oggSync, 4096);
+	int bytes = _fileStream->read(buffer, 4096);
+
+	ogg_sync_wrote(&_oggSync, bytes);
+
+	return bytes;
+}
+
+bool MKVDecoder::queueAudio() {
+	if (!_hasAudio)
+		return false;
+
+	bool queuedAudio = false;
+
+	for (;;) {
+		if (_audioTrack->decodeSamples()) {
+			// we queued some pending audio
+			queuedAudio = true;
+		} else if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) {
+			// no pending audio; is there a pending packet to decode?
+			_audioTrack->synthesizePacket(_oggPacket);
+		} else {
+			// we've buffered all we have, break out for now
+			break;
+		}
+	}
+
+	return queuedAudio;
+}
+
+void MKVDecoder::ensureAudioBufferSize() {
+	if (!_hasAudio)
+		return;
+
+	// Force at least some audio to be buffered
+	while (_audioTrack->needsAudio()) {
+		bufferData();
+		while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
+			queuePage(&_oggPage);
+
+		bool queuedAudio = queueAudio();
+		if ((_vorbisOut.e_o_s  || _fileStream->eos()) && !queuedAudio) {
+			_audioTrack->setEndOfAudio();
+			break;
+		}
+	}
+}
+
+} // End of namespace Video
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
new file mode 100644
index 00000000000..3f4329054e4
--- /dev/null
+++ b/video/mkv_decoder.h
@@ -0,0 +1,157 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"	// for USE_VPX
+
+#ifdef USE_VPX
+
+#ifndef VIDEO_MKV_DECODER_H
+#define VIDEO_MKV_DECODER_H
+
+#include "common/rational.h"
+#include "video/video_decoder.h"
+#include "audio/mixer.h"
+#include "graphics/surface.h"
+
+#include <theora/theoradec.h>
+
+#ifdef USE_TREMOR
+#include <tremor/ivorbiscodec.h>
+#else
+#include <vorbis/codec.h>
+#endif
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Audio {
+class AudioStream;
+class QueuingAudioStream;
+}
+
+namespace Video {
+
+/**
+ *
+ * Decoder for MKV/Webm videos.
+ * Video decoder used in engines:
+ *  - sludge
+ */
+class MKVDecoder : public VideoDecoder {
+public:
+	MKVDecoder();
+	virtual ~MKVDecoder();
+
+	/**
+	 * Load a video file
+	 * @param stream  the stream to load
+	 */
+	bool loadStream(Common::SeekableReadStream *stream);
+	void close();
+
+protected:
+	void readNextPacket();
+
+private:
+	class VPXVideoTrack : public VideoTrack {
+	public:
+		VPXVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup);
+		~VPXVideoTrack();
+
+		bool endOfTrack() const { return _endOfVideo; }
+		uint16 getWidth() const { return _displaySurface.w; }
+		uint16 getHeight() const { return _displaySurface.h; }
+		Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; }
+		int getCurFrame() const { return _curFrame; }
+		uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
+		const Graphics::Surface *decodeNextFrame() { return &_displaySurface; }
+
+		bool decodePacket(ogg_packet &oggPacket);
+		void setEndOfVideo() { _endOfVideo = true; }
+
+	private:
+		int _curFrame;
+		bool _endOfVideo;
+		Common::Rational _frameRate;
+		double _nextFrameStartTime;
+
+		Graphics::Surface _surface;
+		Graphics::Surface _displaySurface;
+
+		th_dec_ctx *_theoraDecode;
+
+		void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer);
+	};
+
+	class VorbisAudioTrack : public AudioTrack {
+	public:
+		VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo);
+		~VorbisAudioTrack();
+
+		bool decodeSamples();
+		bool hasAudio() const;
+		bool needsAudio() const;
+		void synthesizePacket(ogg_packet &oggPacket);
+		void setEndOfAudio() { _endOfAudio = true; }
+
+	protected:
+		Audio::AudioStream *getAudioStream() const;
+
+	private:
+		// single audio fragment audio buffering
+		int _audioBufferFill;
+		ogg_int16_t *_audioBuffer;
+
+		Audio::QueuingAudioStream *_audStream;
+
+		vorbis_block _vorbisBlock;
+		vorbis_dsp_state _vorbisDSP;
+
+		bool _endOfAudio;
+	};
+
+	void queuePage(ogg_page *page);
+	int bufferData();
+	bool queueAudio();
+	void ensureAudioBufferSize();
+
+	Common::SeekableReadStream *_fileStream;
+
+	ogg_sync_state _oggSync;
+	ogg_page _oggPage;
+	ogg_packet _oggPacket;
+
+	ogg_stream_state _theoraOut, _vorbisOut;
+	bool _hasVideo, _hasAudio;
+
+	vorbis_info _vorbisInfo;
+
+	VPXVideoTrack *_videoTrack;
+	VorbisAudioTrack *_audioTrack;
+};
+
+} // End of namespace Video
+
+#endif
+
+#endif
diff --git a/video/module.mk b/video/module.mk
index 1ee711268f6..d73f9af0eb1 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -14,9 +14,7 @@ MODULE_OBJS := \
 	qt_decoder.o \
 	smk_decoder.o \
 	subtitles.o \
-	video_decoder.o \
-	mkv/mkvparser.o \
-	mkv/mkvreader.o
+	video_decoder.o
 
 ifdef USE_BINK
 MODULE_OBJS += \
@@ -28,5 +26,12 @@ MODULE_OBJS += \
 	theora_decoder.o
 endif
 
+ifdef USE_VPX
+MODULE_OBJS += \
+	mkv_decoder.o \
+	mkv/mkvparser.o \
+	mkv/mkvreader.o
+endif
+
 # Include common rules
 include $(srcdir)/rules.mk


Commit: 115231e59d7f51d3871478c5fb07501760f13c48
    https://github.com/scummvm/scummvm/commit/115231e59d7f51d3871478c5fb07501760f13c48
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
CONFIGURE: Added detection for libvpx

Changed paths:
    configure


diff --git a/configure b/configure
index a3de4f873ad..425bf464297 100755
--- a/configure
+++ b/configure
@@ -157,6 +157,7 @@ _jpeg=auto
 _png=auto
 _gif=auto
 _theoradec=auto
+_vpx=auto
 _faad=auto
 _fluidsynth=auto
 _fluidlite=auto
@@ -280,6 +281,7 @@ add_feature png "PNG" "_png"
 add_feature gif "GIF" "_gif"
 add_feature theoradec "libtheoradec" "_theoradec"
 add_feature tinygl "TinyGL" "_tinygl"
+add_feature vpx "libvpx" "_vpx"
 add_feature vorbis "Vorbis file support" "_vorbis _tremor"
 add_feature zlib "zlib" "_zlib"
 add_feature lua "lua" "_lua"
@@ -991,6 +993,9 @@ Optional Libraries:
   --with-theoradec-prefix=DIR  prefix where libtheoradec is installed (optional)
   --disable-theoradec      disable Theora decoder [autodetect]
 
+  --with-vpx-prefix=DIR    prefix where libtvpx is installed (optional)
+  --disable-vpx            disable VP8/VP9 decoder [autodetect]
+
   --with-faad-prefix=DIR   prefix where libfaad is installed (optional)
   --disable-faad           disable AAC decoder [autodetect]
 
@@ -1164,6 +1169,8 @@ for ac_option in $@; do
 	--enable-gif)                 _gif=yes               ;;
 	--disable-theoradec)          _theoradec=no          ;;
 	--enable-theoradec)           _theoradec=yes         ;;
+	--disable-vpx)                _vpx=no                ;;
+	--enable-vpx)                 _vpx=yes               ;;
 	--disable-faad)               _faad=no               ;;
 	--enable-faad)                _faad=yes              ;;
 	--disable-fluidsynth)         _fluidsynth=no         ;;
@@ -1321,6 +1328,11 @@ for ac_option in $@; do
 		THEORADEC_CFLAGS="-I$arg/include"
 		THEORADEC_LIBS="-L$arg/lib"
 		;;
+	--with-vpx-prefix=*)
+		arg=`echo $ac_option | cut -d '=' -f 2`
+		VPX_CFLAGS="-I$arg/include"
+		VPX_LIBS="-L$arg/lib"
+		;;
 	--with-faad-prefix=*)
 		arg=`echo $ac_option | cut -d '=' -f 2`
 		FAAD_CFLAGS="-I$arg/include"
@@ -5056,6 +5068,49 @@ if test ! "$_theoradec" = notsupported ; then
 	echo "$_theoradec"
 fi
 
+#
+# Check for VP8/VP9 Decoder
+#
+echocheck "libvpx >= 1.6"
+if test "$_vorbis" = no && test "$_tremor" = no ; then
+	echo "skipping. no vorbis"
+	_vpx=notsupported
+fi
+if test "$_vpx" = auto ; then
+	_vpx=no
+
+	if test "$_pkg_config" = "yes" && $_pkgconfig --exists vpx; then
+		append_var VPX_LIBS "`$_pkgconfig --libs vpx`"
+		append_var VPX_CFLAGS "`$_pkgconfig --cflags vpx`"
+	else
+		append_var VPX_LIBS "-lvpx"
+	fi
+
+	cat > $TMPC << EOF
+#include "vpx/vpx_decoder.h"
+#include "vpx/vp8dx.h"
+int main(void) {
+	vpx_codec_ctx_t codec;
+	vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, NULL, 0);
+
+#if VPX_DECODER_ABI_VERSION >= 10 // Check for 1.6
+#else
+  syntax error
+#endif
+	return 0;
+}
+EOF
+	cc_check $VPX_CFLAGS $VPX_LIBS && _vpx=yes
+fi
+if test "$_vpx" = yes ; then
+	append_var LIBS "$VPX_LIBS -lvpx"
+	append_var INCLUDES "$VPX_CFLAGS"
+fi
+define_in_config_if_yes "$_vpx" 'USE_VPX'
+if test ! "$_vpx" = notsupported ; then
+	echo "$_vpx"
+fi
+
 #
 # Check for the AAC decoder
 #


Commit: 53163f427a1bbb97206896ee8b3a2b2aa3e96eae
    https://github.com/scummvm/scummvm/commit/53163f427a1bbb97206896ee8b3a2b2aa3e96eae
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
DEVTOOLS: Added libvpx support to create_project

Changed paths:
    devtools/create_project/cmake.cpp
    devtools/create_project/create_project.cpp
    devtools/create_project/xcode.cpp


diff --git a/devtools/create_project/cmake.cpp b/devtools/create_project/cmake.cpp
index ef84b674eb0..5907d3445d1 100644
--- a/devtools/create_project/cmake.cpp
+++ b/devtools/create_project/cmake.cpp
@@ -48,6 +48,7 @@ const CMakeProvider::Library *CMakeProvider::getLibraryFromFeature(const char *f
 		{ "vorbis",     "vorbisfile vorbis", kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "vorbisfile vorbis" },
 		{ "tremor",     "vorbisidec",        kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "vorbisidec" },
 		{ "theoradec",  "theoradec",         kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "theoradec"  },
+		{ "vpx",        "vpx",               kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "vpx"        },
 		{ "fluidsynth", "fluidsynth",        kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "fluidsynth" },
 		{ "faad",       "faad2",             kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "faad"       },
 		{ "fribidi",    "fribidi",           kSDLVersionAny, nullptr,        nullptr,    nullptr,                 nullptr,               "fribidi"    },
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 24a605dcab0..a4e1d81f261 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -1071,6 +1071,7 @@ const Feature s_features[] = {
 	{      "faad",        "USE_FAAD", true, false, "AAC support" },
 	{     "mpeg2",       "USE_MPEG2", true, true,  "MPEG-2 support" },
 	{ "theoradec",   "USE_THEORADEC", true, true,  "Theora decoding support" },
+	{       "vpx",         "USE_VPX", true, false, "VP8/VP9 decoding support" },
 	{ "freetype2",   "USE_FREETYPE2", true, true,  "FreeType support" },
 	{      "jpeg",        "USE_JPEG", true, true,  "libjpeg support" },
 	{"fluidsynth",  "USE_FLUIDSYNTH", true, true,  "FluidSynth support" },
diff --git a/devtools/create_project/xcode.cpp b/devtools/create_project/xcode.cpp
index 7de4b7f1e6b..71c5820512e 100644
--- a/devtools/create_project/xcode.cpp
+++ b/devtools/create_project/xcode.cpp
@@ -530,6 +530,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
 	if (CONTAINS_DEFINE(setup.defines, "USE_RETROWAVE")) {
 		DEF_LOCALLIB_STATIC("libretrowave");
 	}
+	if (CONTAINS_DEFINE(setup.defines, "USE_VPX")) {
+		DEF_LOCALLIB_STATIC("libvpx");
+	}
 	if (CONTAINS_DEFINE(setup.defines, "USE_ZLIB")) {
 		DEF_SYSTBD("libz");
 	}
@@ -616,6 +619,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
 	if (CONTAINS_DEFINE(setup.defines, "USE_THEORADEC")) {
 		frameworks_iOS.push_back("libtheoradec.a");
 	}
+	if (CONTAINS_DEFINE(setup.defines, "USE_VPX")) {
+		frameworks_iOS.push_back("libvpx.a");
+	}
 	if (CONTAINS_DEFINE(setup.defines, "USE_MAD")) {
 		frameworks_iOS.push_back("libmad.a");
 	}
@@ -738,6 +744,9 @@ void XcodeProvider::setupFrameworksBuildPhase(const BuildSetup &setup) {
 	if (CONTAINS_DEFINE(setup.defines, "USE_RETROWAVE")) {
 		frameworks_osx.push_back("libretrowave.a");
 	}
+	if (CONTAINS_DEFINE(setup.defines, "USE_VPX")) {
+		frameworks_osx.push_back("libvpx.a");
+	}
 	if (CONTAINS_DEFINE(setup.defines, "USE_ZLIB")) {
 		frameworks_osx.push_back("libz.tbd");
 	}


Commit: fe62ed883d4b801da475e28913a02144d873db55
    https://github.com/scummvm/scummvm/commit/fe62ed883d4b801da475e28913a02144d873db55
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Implement ScummVM-compatible MkvReader and remove generic one

Changed paths:
  R video/mkv/mkvreader.cpp
  R video/mkv/mkvreader.h
    video/mkv_decoder.cpp
    video/module.mk


diff --git a/video/mkv/mkvreader.cpp b/video/mkv/mkvreader.cpp
deleted file mode 100644
index 6bdbfdfad25..00000000000
--- a/video/mkv/mkvreader.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-// 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 "video/mkv/mkvreader.h"
-
-#include <sys/types.h>
-
-#include <cassert>
-
-namespace mkvparser {
-
-MkvReader::MkvReader() : m_file(NULL), reader_owns_file_(true) {}
-
-MkvReader::MkvReader(FILE* fp) : m_file(fp), reader_owns_file_(false) {
-  GetFileSize();
-}
-
-MkvReader::~MkvReader() {
-  if (reader_owns_file_)
-    Close();
-  m_file = NULL;
-}
-
-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
-  return !GetFileSize();
-}
-
-bool MkvReader::GetFileSize() {
-  if (m_file == NULL)
-    return false;
-#ifdef _MSC_VER
-  int status = _fseeki64(m_file, 0L, SEEK_END);
-
-  if (status)
-    return false;  // error
-
-  m_length = _ftelli64(m_file);
-#else
-  fseek(m_file, 0L, SEEK_END);
-  m_length = ftell(m_file);
-#endif
-  assert(m_length >= 0);
-
-  if (m_length < 0)
-    return false;
-
-#ifdef _MSC_VER
-  status = _fseeki64(m_file, 0L, SEEK_SET);
-
-  if (status)
-    return false;  // error
-#else
-  fseek(m_file, 0L, SEEK_SET);
-#endif
-
-  return true;
-}
-
-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
-#elif defined(_WIN32)
-  fseeko64(m_file, static_cast<off_t>(offset), SEEK_SET);
-#else
-  fseeko(m_file, static_cast<off_t>(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
-}
-
-}  // namespace mkvparser
diff --git a/video/mkv/mkvreader.h b/video/mkv/mkvreader.h
deleted file mode 100644
index dd0b54b057c..00000000000
--- a/video/mkv/mkvreader.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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 MKVPARSER_MKVREADER_H_
-#define MKVPARSER_MKVREADER_H_
-
-#include <cstdio>
-
-#include "video/mkv/mkvparser.h"
-
-namespace mkvparser {
-
-class MkvReader : public IMkvReader {
- public:
-  MkvReader();
-  explicit MkvReader(FILE* fp);
-  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:
-  MkvReader(const MkvReader&);
-  MkvReader& operator=(const MkvReader&);
-
-  // Determines the size of the file. This is called either by the constructor
-  // or by the Open function depending on file ownership. Returns true on
-  // success.
-  bool GetFileSize();
-
-  long long m_length;
-  FILE* m_file;
-  bool reader_owns_file_;
-};
-
-}  // namespace mkvparser
-
-#endif  // MKVPARSER_MKVREADER_H_
diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index a09e69d605f..d6e08638302 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -31,8 +31,67 @@
 #include "graphics/pixelformat.h"
 #include "graphics/yuv_to_rgb.h"
 
+#include "video/mkv/mkvparser.h"
+
 namespace Video {
 
+class MkvReader : public mkvparser::IMkvReader {
+public:
+	MkvReader() { _stream = nullptr; }
+	virtual ~MkvReader();
+
+	int Open(Common::SeekableReadStream *stream);
+	void Close();
+
+	virtual int Read(long long position, long length, unsigned char *buffer);
+	virtual int Length(long long *total, long long *available);
+
+private:
+	Common::SeekableReadStream *_stream;
+};
+
+int MkvReader::Open(Common::SeekableReadStream *stream) {
+	_stream = stream;
+
+	return 0;
+}
+
+void MkvReader::Close() {
+	delete _stream;
+
+	_stream = nullptr;
+}
+
+int MkvReader::Read(long long position, long length, unsigned char *buffer) {
+	if (!_stream)
+		return -1;
+
+	if (position > _stream->size() || position < 0)
+		return -1;
+
+	if (length <= 0)
+		return -1;
+
+	_stream->seek(position);
+	if (_stream->read(buffer, length) < length)
+		return -1;
+
+	return 0;
+}
+
+int MkvReader::Length(long long *total, long long *available) {
+	if (!_stream)
+		return -1;
+
+	if (*total)
+		*total = _stream->size();
+
+	if (*available)
+		*available = _stream->size();
+
+	return 0;
+}
+
 MKVDecoder::MKVDecoder() {
 	_fileStream = 0;
 
diff --git a/video/module.mk b/video/module.mk
index d73f9af0eb1..06e1d6e7275 100644
--- a/video/module.mk
+++ b/video/module.mk
@@ -29,8 +29,7 @@ endif
 ifdef USE_VPX
 MODULE_OBJS += \
 	mkv_decoder.o \
-	mkv/mkvparser.o \
-	mkv/mkvreader.o
+	mkv/mkvparser.o
 endif
 
 # Include common rules


Commit: 32055ab0b2df10a71024cdf4fc3b71e1f7e97a8e
    https://github.com/scummvm/scummvm/commit/32055ab0b2df10a71024cdf4fc3b71e1f7e97a8e
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Plugged in movie player

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 689aba34349..165c8e13928 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -19,9 +19,15 @@
  *
  */
 
+#include "sludge/sludge.h"
+#include "sludge/fileset.h"
 #include "sludge/movie.h"
+#include "sludge/newfatal.h"
 #include "sludge/sound.h"
 
+#include "common/substream.h"
+#include "video/mkv_decoder.h"
+
 namespace Sludge {
 
 // sound_openal.cpp
@@ -317,9 +323,20 @@ ALuint feedAudio(void *userdata, ALubyte *data, ALuint length) {
 #endif
 
 int playMovie(int fileNumber) {
+	uint fsize;
+	if (!(fsize = g_sludge->_resMan->openFileFromNum(fileNumber)))
+		return fatal("playMovie(): Can't open movie");
+
+	Video::MKVDecoder decoder;
+
+	Common::SeekableReadStream *stream = g_sludge->_resMan->getData();
+	Common::SeekableSubReadStream video(stream, stream->pos(), stream->pos() + fsize);
+
+	decoder.loadStream(&video);
+
 #if 0
 	if (specialSettings & SPECIAL_SILENT)
-	return 0;
+		return 0;
 
 	if (movieIsPlaying) return 0;
 


Commit: d2adb5b2a609528042b20abb0b171f5625d8d87d
    https://github.com/scummvm/scummvm/commit/d2adb5b2a609528042b20abb0b171f5625d8d87d
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Initial work on MKV video initialization

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index d6e08638302..954ea9eafdb 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -33,12 +33,13 @@
 
 #include "video/mkv/mkvparser.h"
 
-namespace Video {
+namespace mkvparser  {
 
 class MkvReader : public mkvparser::IMkvReader {
 public:
 	MkvReader() { _stream = nullptr; }
-	virtual ~MkvReader();
+	MkvReader(Common::SeekableReadStream *stream, uint size = 0);
+	virtual ~MkvReader() { Close(); }
 
 	int Open(Common::SeekableReadStream *stream);
 	void Close();
@@ -48,8 +49,18 @@ public:
 
 private:
 	Common::SeekableReadStream *_stream;
+	uint _size;
 };
 
+MkvReader::MkvReader(Common::SeekableReadStream *stream, uint size) {
+	_stream = stream;
+
+	if (size == 0)
+		size = _stream->size();
+
+	_size = size;
+}
+
 int MkvReader::Open(Common::SeekableReadStream *stream) {
 	_stream = stream;
 
@@ -84,20 +95,27 @@ int MkvReader::Length(long long *total, long long *available) {
 		return -1;
 
 	if (*total)
-		*total = _stream->size();
+		*total = _size;
 
 	if (*available)
-		*available = _stream->size();
+		*available = _size;
 
 	return 0;
 }
 
+} // end of namespace mkvparser
+
+namespace Video {
+
 MKVDecoder::MKVDecoder() {
 	_fileStream = 0;
 
 	_videoTrack = 0;
 	_audioTrack = 0;
 	_hasVideo = _hasAudio = false;
+
+	_codec = nullptr;
+	_reader = nullptr;
 }
 
 MKVDecoder::~MKVDecoder() {
@@ -109,162 +127,205 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	_fileStream = stream;
 
-	// start up Ogg stream synchronization layer
-	ogg_sync_init(&_oggSync);
+	_codec = new vpx_codec_ctx_t;
+	_reader = new mkvparser::MkvReader(stream);
 
-	// init supporting Vorbis structures needed in header parsing
-	vorbis_info_init(&_vorbisInfo);
-	vorbis_comment vorbisComment;
-	vorbis_comment_init(&vorbisComment);
-
-	// init supporting Theora structures needed in header parsing
-	th_info theoraInfo;
-	th_info_init(&theoraInfo);
-	th_comment theoraComment;
-	th_comment_init(&theoraComment);
-	th_setup_info *theoraSetup = 0;
-
-	uint theoraPackets = 0, vorbisPackets = 0;
-
-	// Ogg file open; parse the headers
-	// Only interested in Vorbis/Theora streams
-	bool foundHeader = false;
-	while (!foundHeader) {
-		int ret = bufferData();
-
-		if (ret == 0)
-			break; // FIXME: Shouldn't this error out?
-
-		while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
-			ogg_stream_state test;
-
-			// is this a mandated initial header? If not, stop parsing
-			if (!ogg_page_bos(&_oggPage)) {
-				// don't leak the page; get it into the appropriate stream
-				queuePage(&_oggPage);
-				foundHeader = true;
-				break;
-			}
+	long long pos = 0;
 
-			ogg_stream_init(&test, ogg_page_serialno(&_oggPage));
-			ogg_stream_pagein(&test, &_oggPage);
-			ogg_stream_packetout(&test, &_oggPacket);
-
-			// identify the codec: try theora
-			if (theoraPackets == 0 && th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket) >= 0) {
-				// it is theora
-				memcpy(&_theoraOut, &test, sizeof(test));
-				theoraPackets = 1;
-				_hasVideo = true;
-			} else if (vorbisPackets == 0 && vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket) >= 0) {
-				// it is vorbis
-				memcpy(&_vorbisOut, &test, sizeof(test));
-				vorbisPackets = 1;
-				_hasAudio = true;
-			} else {
-				// whatever it is, we don't care about it
-				ogg_stream_clear(&test);
-			}
-		}
-		// fall through to non-bos page parsing
+	mkvparser::EBMLHeader ebmlHeader;
+
+	ebmlHeader.Parse(_reader, pos);
+
+	mkvparser::Segment *pSegment;
+
+	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
+	if (ret) {
+		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed.");
 	}
 
-	// we're expecting more header packets.
-	while ((theoraPackets && theoraPackets < 3) || (vorbisPackets && vorbisPackets < 3)) {
-		int ret;
+	ret = pSegment->Load();
+	if (ret < 0) {
+		error("MKVDecoder::loadStream(): Segment::Load() failed.\n");
+	}
 
-		// look for further theora headers
-		while (theoraPackets && (theoraPackets < 3) && (ret = ogg_stream_packetout(&_theoraOut, &_oggPacket))) {
-			if (ret < 0)
-				error("Error parsing Theora stream headers; corrupt stream?");
+	const mkvparser::Tracks *pTracks = pSegment->GetTracks();
 
-			if (!th_decode_headerin(&theoraInfo, &theoraComment, &theoraSetup, &_oggPacket))
-				error("Error parsing Theora stream headers; corrupt stream?");
+	unsigned long i = 0;
+	const unsigned long j = pTracks->GetTracksCount();
 
-			theoraPackets++;
-		}
+	warning("Number of tracks: %ld", j);
 
-		// look for more vorbis header packets
-		while (vorbisPackets && (vorbisPackets < 3) && (ret = ogg_stream_packetout(&_vorbisOut, &_oggPacket))) {
-			if (ret < 0)
-				error("Error parsing Vorbis stream headers; corrupt stream?");
+	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
+	int videoTrack = -1;
+	int audioTrack = -1;
+	long long audioBitDepth;
+	double audioSampleRate;
+	ogg_packet oggPacket;
+	vorbis_info vorbisInfo;
+	vorbis_comment vorbisComment;
+	vorbis_block vorbisBlock;
 
-			if (vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &_oggPacket))
-				error("Error parsing Vorbis stream headers; corrupt stream?");
+#if 0
+	while (i != j) {
+		const Track *const pTrack = pTracks->GetTrackByIndex(i++);
 
-			vorbisPackets++;
+		if (pTrack == NULL)
+		continue;
 
-			if (vorbisPackets == 3)
-				break;
-		}
+		const long long trackType = pTrack->GetType();
+		//const unsigned long long trackUid = pTrack->GetUid();
+		//const char* pTrackName = pTrack->GetNameAsUTF8();
 
-		// The header pages/packets will arrive before anything else we
-		// care about, or the stream is not obeying spec
+		if (trackType == VIDEO_TRACK && videoTrack < 0) {
+			videoTrack = pTrack->GetNumber();
+			const VideoTrack *const pVideoTrack =
+			static_cast<const VideoTrack *>(pTrack);
 
-		if (ogg_sync_pageout(&_oggSync, &_oggPage) > 0) {
-			queuePage(&_oggPage); // demux into the appropriate stream
-		} else {
-			ret = bufferData(); // someone needs more data
+			const long long width = pVideoTrack->GetWidth();
+			const long long height = pVideoTrack->GetHeight();
+
+			const double rate = pVideoTrack->GetFrameRate();
 
-			if (ret == 0)
-				error("End of file while searching for codec headers.");
+			if (rate > 0)
+				Init_Special_Timer(rate);
+
+			movieAspect = (float)width / height;
 		}
-	}
 
-	// And now we have it all. Initialize decoders next
-	if (_hasVideo) {
-		_videoTrack = new VPXVideoTrack(getDefaultHighColorFormat(), theoraInfo, theoraSetup);
-		addTrack(_videoTrack);
-	}
+		if (trackType == AUDIO_TRACK && audioTrack < 0) {
+			audioTrack = pTrack->GetNumber();
+			const AudioTrack *const pAudioTrack =
+			static_cast<const AudioTrack *>(pTrack);
+
+			audioChannels = pAudioTrack->GetChannels();
+			audioBitDepth = pAudioTrack->GetBitDepth();
+			audioSampleRate = pAudioTrack->GetSamplingRate();
 
-	th_info_clear(&theoraInfo);
-	th_comment_clear(&theoraComment);
-	th_setup_free(theoraSetup);
+			uint audioHeaderSize;
+			const byte *audioHeader = pAudioTrack->GetCodecPrivate(audioHeaderSize);
 
-	if (_hasAudio) {
-		_audioTrack = new VorbisAudioTrack(getSoundType(), _vorbisInfo);
+			if (audioHeaderSize < 1) {
+				warning("Strange audio track in movie.");
+				audioTrack = -1;
+				continue;
+			}
 
-		// Get enough audio data to start us off
-		while (!_audioTrack->hasAudio()) {
-			// Queue more data
-			bufferData();
-			while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
-				queuePage(&_oggPage);
+			byte *p = (byte *)audioHeader;
 
-			queueAudio();
-		}
+			uint count = *p++ + 1;
+			if (count != 3) {
+				warning("Strange audio track in movie.");
+				audioTrack = -1;
+				continue;
+			}
+
+			uint64_t sizes[3], total;
+
+			int i = 0;
+			total = 0;
+			while (--count) {
+				sizes[i] = xiph_lace_value(&p);
+				total += sizes[i];
+				i += 1;
+			}
+			sizes[i] = audioHeaderSize - total - (p - audioHeader);
+
+			// initialize vorbis
+			vorbis_info_init(&vorbisInfo);
+			vorbis_comment_init(&vorbisComment);
+			memset(&vorbisDspState, 0, sizeof(vorbisDspState));
+			memset(&vorbisBlock, 0, sizeof(vorbisBlock));
+
+			oggPacket.e_o_s = false;
+			oggPacket.granulepos = 0;
+			oggPacket.packetno = 0;
+			int r;
+			for (int i = 0; i < 3; i++) {
+				oggPacket.packet = p;
+				oggPacket.bytes = sizes[i];
+				oggPacket.b_o_s = oggPacket.packetno == 0;
+				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
+				if (r)
+				fprintf(stderr, "vorbis_synthesis_headerin failed, error: %d", r);
+				oggPacket.packetno++;
+				p += sizes[i];
+			}
 
-		addTrack(_audioTrack);
+			r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
+			if (r)
+			fprintf(stderr, "vorbis_synthesis_init failed, error: %d", r);
+			r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
+			if (r)
+			fprintf(stderr, "vorbis_block_init failed, error: %d", r);
+
+			ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
+			movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
+
+			fprintf(stderr, "Movie sound inited.\n");
+			audio_queue_init(&audioQ);
+			audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
+			audioNsBuffered = 0;
+			audioBufferLen = audioChannels * audioSampleRate;
+		}
 	}
 
-	vorbis_comment_clear(&vorbisComment);
+	if (videoTrack < 0)
+	fatal("Movie error: No video in movie file.");
 
-	return true;
-}
+	if (audioTrack < 0)
+	fatal("Movie error: No sound found.");
 
-void MKVDecoder::close() {
-	VideoDecoder::close();
+	video_queue_init(&videoQ);
 
-	if (!_fileStream)
-		return;
+	const unsigned long clusterCount = pSegment->GetCount();
 
-	if (_videoTrack) {
-		ogg_stream_clear(&_theoraOut);
-		_videoTrack = 0;
+	if (clusterCount == 0) {
+		fatal("Movie error: Segment has no clusters.\n");
 	}
 
-	if (_audioTrack) {
-		ogg_stream_clear(&_vorbisOut);
-		_audioTrack = 0;
+	/* Initialize video codec */
+	if (vpx_codec_dec_init(&codec, interface, NULL, 0))
+	die_codec(&codec, "Failed to initialize decoder for movie.");
+
+	byte *frame = new byte[256 * 1024];
+	if (! checkNew(frame)) return false;
+
+	const mkvparser::Cluster *pCluster = pSegment->GetFirst();
+
+	setMovieViewport();
+
+	movieIsPlaying = playing;
+	movieIsEnding = 0;
+
+	//const long long timeCode = pCluster->GetTimeCode();
+	long long time_ns = pCluster->GetTime();
+
+	const BlockEntry *pBlockEntry = pCluster->GetFirst();
+
+	if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
+		pCluster = pSegment->GetNext(pCluster);
+		if ((pCluster == NULL) || pCluster->EOS()) {
+			fatal("Error: No movie found in the movie file.");
+		}
+		pBlockEntry = pCluster->GetFirst();
 	}
+	const Block *pBlock = pBlockEntry->GetBlock();
+	long long trackNum = pBlock->GetTrackNumber();
+	unsigned long tn = static_cast<unsigned long>(trackNum);
+	const Track *pTrack = pTracks->GetTrackByNumber(tn);
+	long long trackType = pTrack->GetType();
+	int frameCount = pBlock->GetFrameCount();
+	time_ns = pBlock->GetTime(pCluster);
+#endif
 
-	ogg_sync_clear(&_oggSync);
-	vorbis_info_clear(&_vorbisInfo);
+	return true;
+}
 
-	delete _fileStream;
-	_fileStream = 0;
+void MKVDecoder::close() {
+	VideoDecoder::close();
 
-	_hasVideo = _hasAudio = false;
+	delete _codec;
+	delete _reader;
 }
 
 void MKVDecoder::readNextPacket() {
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 3f4329054e4..1adc6d925c9 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -40,6 +40,10 @@
 #include <vorbis/codec.h>
 #endif
 
+#include <vpx/vpx_decoder.h>
+#include <vpx/vp8dx.h>
+
+
 namespace Common {
 class SeekableReadStream;
 }
@@ -49,8 +53,14 @@ class AudioStream;
 class QueuingAudioStream;
 }
 
+namespace mkvparser {
+class MkvReader;
+}
+
 namespace Video {
 
+class MkvReader;
+
 /**
  *
  * Decoder for MKV/Webm videos.
@@ -148,6 +158,9 @@ private:
 
 	VPXVideoTrack *_videoTrack;
 	VorbisAudioTrack *_audioTrack;
+
+	vpx_codec_ctx_t *_codec;
+	mkvparser::MkvReader *_reader;
 };
 
 } // End of namespace Video


Commit: 91c046314580c84bf7e2ca81dd90080be06f0a80
    https://github.com/scummvm/scummvm/commit/91c046314580c84bf7e2ca81dd90080be06f0a80
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Close resources for now

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 165c8e13928..848194e0564 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -334,6 +334,9 @@ int playMovie(int fileNumber) {
 
 	decoder.loadStream(&video);
 
+	g_sludge->_resMan->finishAccess();
+	setResourceForFatal(-1);
+
 #if 0
 	if (specialSettings & SPECIAL_SILENT)
 		return 0;


Commit: c27f9c34ef73440db03f0cdaddd03cce820ce3e4
    https://github.com/scummvm/scummvm/commit/c27f9c34ef73440db03f0cdaddd03cce820ce3e4
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Fixed bug in MkvReader::Length() and enhanced debug log messages

Changed paths:
    video/mkv_decoder.cpp


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 954ea9eafdb..948721daf41 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -38,7 +38,7 @@ namespace mkvparser  {
 class MkvReader : public mkvparser::IMkvReader {
 public:
 	MkvReader() { _stream = nullptr; }
-	MkvReader(Common::SeekableReadStream *stream, uint size = 0);
+	MkvReader(Common::SeekableReadStream *stream);
 	virtual ~MkvReader() { Close(); }
 
 	int Open(Common::SeekableReadStream *stream);
@@ -49,16 +49,10 @@ public:
 
 private:
 	Common::SeekableReadStream *_stream;
-	uint _size;
 };
 
-MkvReader::MkvReader(Common::SeekableReadStream *stream, uint size) {
+MkvReader::MkvReader(Common::SeekableReadStream *stream) {
 	_stream = stream;
-
-	if (size == 0)
-		size = _stream->size();
-
-	_size = size;
 }
 
 int MkvReader::Open(Common::SeekableReadStream *stream) {
@@ -68,8 +62,6 @@ int MkvReader::Open(Common::SeekableReadStream *stream) {
 }
 
 void MkvReader::Close() {
-	delete _stream;
-
 	_stream = nullptr;
 }
 
@@ -94,11 +86,11 @@ int MkvReader::Length(long long *total, long long *available) {
 	if (!_stream)
 		return -1;
 
-	if (*total)
-		*total = _size;
+	if (total)
+		*total = _stream->size();
 
-	if (*available)
-		*available = _size;
+	if (available)
+		*available = _stream->size();
 
 	return 0;
 }
@@ -140,12 +132,12 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
 	if (ret) {
-		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed.");
+		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%ld).", ret);
 	}
 
 	ret = pSegment->Load();
 	if (ret < 0) {
-		error("MKVDecoder::loadStream(): Segment::Load() failed.\n");
+		error("MKVDecoder::loadStream(): Segment::Load() failed (%ld).", ret);
 	}
 
 	const mkvparser::Tracks *pTracks = pSegment->GetTracks();


Commit: 3afa3e4bd3eb36ac48194e573800ce1e7e6896af
    https://github.com/scummvm/scummvm/commit/3afa3e4bd3eb36ac48194e573800ce1e7e6896af
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: More work on MKV decoder

Changed paths:
    video/mkv_decoder.cpp


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 948721daf41..769b8dd137e 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -132,12 +132,12 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
 	if (ret) {
-		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%ld).", ret);
+		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%lld).", ret);
 	}
 
 	ret = pSegment->Load();
 	if (ret < 0) {
-		error("MKVDecoder::loadStream(): Segment::Load() failed (%ld).", ret);
+		error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
 	}
 
 	const mkvparser::Tracks *pTracks = pSegment->GetTracks();
@@ -159,10 +159,10 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 #if 0
 	while (i != j) {
-		const Track *const pTrack = pTracks->GetTrackByIndex(i++);
+		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
 
 		if (pTrack == NULL)
-		continue;
+			continue;
 
 		const long long trackType = pTrack->GetType();
 		//const unsigned long long trackUid = pTrack->GetUid();
@@ -170,8 +170,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 		if (trackType == VIDEO_TRACK && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
-			const VideoTrack *const pVideoTrack =
-			static_cast<const VideoTrack *>(pTrack);
+			const VideoTrack *const pVideoTrack = static_cast<const VideoTrack *>(pTrack);
 
 			const long long width = pVideoTrack->GetWidth();
 			const long long height = pVideoTrack->GetHeight();
@@ -186,8 +185,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 		if (trackType == AUDIO_TRACK && audioTrack < 0) {
 			audioTrack = pTrack->GetNumber();
-			const AudioTrack *const pAudioTrack =
-			static_cast<const AudioTrack *>(pTrack);
+			const AudioTrack *const pAudioTrack = static_cast<const AudioTrack *>(pTrack);
 
 			audioChannels = pAudioTrack->GetChannels();
 			audioBitDepth = pAudioTrack->GetBitDepth();
@@ -211,16 +209,16 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 				continue;
 			}
 
-			uint64_t sizes[3], total;
+			uint64 sizes[3], total;
 
-			int i = 0;
+			int l = 0;
 			total = 0;
 			while (--count) {
-				sizes[i] = xiph_lace_value(&p);
+				sizes[l] = xiph_lace_value(&p);
 				total += sizes[i];
-				i += 1;
+				l += 1;
 			}
-			sizes[i] = audioHeaderSize - total - (p - audioHeader);
+			sizes[l] = audioHeaderSize - total - (p - audioHeader);
 
 			// initialize vorbis
 			vorbis_info_init(&vorbisInfo);
@@ -232,28 +230,28 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			oggPacket.granulepos = 0;
 			oggPacket.packetno = 0;
 			int r;
-			for (int i = 0; i < 3; i++) {
+			for (int s = 0; s < 3; s++) {
 				oggPacket.packet = p;
-				oggPacket.bytes = sizes[i];
+				oggPacket.bytes = sizes[s];
 				oggPacket.b_o_s = oggPacket.packetno == 0;
 				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
 				if (r)
-				fprintf(stderr, "vorbis_synthesis_headerin failed, error: %d", r);
+					warning("vorbis_synthesis_headerin failed, error: %d", r);
 				oggPacket.packetno++;
-				p += sizes[i];
+				p += sizes[s];
 			}
 
 			r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
 			if (r)
-			fprintf(stderr, "vorbis_synthesis_init failed, error: %d", r);
+				warning("vorbis_synthesis_init failed, error: %d", r);
 			r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
 			if (r)
-			fprintf(stderr, "vorbis_block_init failed, error: %d", r);
+				warning("vorbis_block_init failed, error: %d", r);
 
 			ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
 			movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
 
-			fprintf(stderr, "Movie sound inited.\n");
+			debug(1, "Movie sound inited.");
 			audio_queue_init(&audioQ);
 			audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
 			audioNsBuffered = 0;
@@ -262,10 +260,10 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	}
 
 	if (videoTrack < 0)
-	fatal("Movie error: No video in movie file.");
+		error("Movie error: No video in movie file.");
 
 	if (audioTrack < 0)
-	fatal("Movie error: No sound found.");
+		error("Movie error: No sound found.");
 
 	video_queue_init(&videoQ);
 
@@ -277,15 +275,14 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	/* Initialize video codec */
 	if (vpx_codec_dec_init(&codec, interface, NULL, 0))
-	die_codec(&codec, "Failed to initialize decoder for movie.");
+		die_codec(&codec, "Failed to initialize decoder for movie.");
 
 	byte *frame = new byte[256 * 1024];
-	if (! checkNew(frame)) return false;
+	if (!checkNew(frame))
+		return false;
 
 	const mkvparser::Cluster *pCluster = pSegment->GetFirst();
 
-	setMovieViewport();
-
 	movieIsPlaying = playing;
 	movieIsEnding = 0;
 


Commit: 6f3f9ce973c8ddefadef392b7106d58c463e1b71
    https://github.com/scummvm/scummvm/commit/6f3f9ce973c8ddefadef392b7106d58c463e1b71
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Make MKVDecoder compilable

Changed paths:
    video/mkv_decoder.cpp


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 769b8dd137e..8cb543b4bfa 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -24,6 +24,7 @@
 
 #include "audio/audiostream.h"
 #include "audio/decoders/raw.h"
+#include "common/debug.h"
 #include "common/stream.h"
 #include "common/system.h"
 #include "common/textconsole.h"
@@ -114,6 +115,33 @@ MKVDecoder::~MKVDecoder() {
 	close();
 }
 
+long long audioChannels;
+
+static uint64 xiph_lace_value(byte **np) {
+	uint64 lace;
+	uint64 value;
+	byte *p = *np;
+
+	lace = *p++;
+	value = lace;
+	while (lace == 255) {
+		lace = *p++;
+		value += lace;
+	}
+
+	*np = p;
+
+	return value;
+}
+
+vorbis_dsp_state vorbisDspState;
+int64 audioNsPerByte;
+int64 audioNsPlayed;
+int64 audioNsBuffered;
+int64 audioBufferLen;
+bool movieSoundPlaying = false;
+int movieAudioIndex;
+
 bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	close();
 
@@ -157,7 +185,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	vorbis_comment vorbisComment;
 	vorbis_block vorbisBlock;
 
-#if 0
 	while (i != j) {
 		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
 
@@ -168,31 +195,32 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		//const unsigned long long trackUid = pTrack->GetUid();
 		//const char* pTrackName = pTrack->GetNameAsUTF8();
 
-		if (trackType == VIDEO_TRACK && videoTrack < 0) {
+		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
-			const VideoTrack *const pVideoTrack = static_cast<const VideoTrack *>(pTrack);
+			const mkvparser::VideoTrack *const pVideoTrack = static_cast<const mkvparser::VideoTrack *>(pTrack);
 
 			const long long width = pVideoTrack->GetWidth();
 			const long long height = pVideoTrack->GetHeight();
 
 			const double rate = pVideoTrack->GetFrameRate();
 
+#if 0
 			if (rate > 0)
-				Init_Special_Timer(rate);
-
-			movieAspect = (float)width / height;
+				Init_Special_Timer(rate); // TODO
+#endif
+			warning("VideoTrack: %lld x %lld @ %g fps", width, height, rate);
 		}
 
-		if (trackType == AUDIO_TRACK && audioTrack < 0) {
+		if (trackType == mkvparser::Track::kAudio && audioTrack < 0) {
 			audioTrack = pTrack->GetNumber();
-			const AudioTrack *const pAudioTrack = static_cast<const AudioTrack *>(pTrack);
+			const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
 			audioChannels = pAudioTrack->GetChannels();
 			audioBitDepth = pAudioTrack->GetBitDepth();
 			audioSampleRate = pAudioTrack->GetSamplingRate();
 
-			uint audioHeaderSize;
-			const byte *audioHeader = pAudioTrack->GetCodecPrivate(audioHeaderSize);
+			size_t audioHeaderSize;
+			byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
 
 			if (audioHeaderSize < 1) {
 				warning("Strange audio track in movie.");
@@ -200,7 +228,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 				continue;
 			}
 
-			byte *p = (byte *)audioHeader;
+			byte *p = audioHeader;
 
 			uint count = *p++ + 1;
 			if (count != 3) {
@@ -248,11 +276,15 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			if (r)
 				warning("vorbis_block_init failed, error: %d", r);
 
+#if 0
 			ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
 			movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
+#endif
 
 			debug(1, "Movie sound inited.");
+#if 0
 			audio_queue_init(&audioQ);
+#endif
 			audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
 			audioNsBuffered = 0;
 			audioBufferLen = audioChannels * audioSampleRate;
@@ -265,47 +297,53 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	if (audioTrack < 0)
 		error("Movie error: No sound found.");
 
+#if 0
 	video_queue_init(&videoQ);
+#endif
 
 	const unsigned long clusterCount = pSegment->GetCount();
 
 	if (clusterCount == 0) {
-		fatal("Movie error: Segment has no clusters.\n");
+		error("Movie error: Segment has no clusters.\n");
 	}
 
 	/* Initialize video codec */
-	if (vpx_codec_dec_init(&codec, interface, NULL, 0))
-		die_codec(&codec, "Failed to initialize decoder for movie.");
+	if (vpx_codec_dec_init(_codec, &vpx_codec_vp8_dx_algo, NULL, 0))
+		error("Failed to initialize decoder for movie.");
 
 	byte *frame = new byte[256 * 1024];
-	if (!checkNew(frame))
+	if (!frame)
 		return false;
 
 	const mkvparser::Cluster *pCluster = pSegment->GetFirst();
 
+#if 0
 	movieIsPlaying = playing;
 	movieIsEnding = 0;
+#endif
 
 	//const long long timeCode = pCluster->GetTimeCode();
 	long long time_ns = pCluster->GetTime();
 
-	const BlockEntry *pBlockEntry = pCluster->GetFirst();
+	const mkvparser::BlockEntry *pBlockEntry;
+	if (pCluster->GetFirst(pBlockEntry))
+		error("pCluster::GetFirst() failed");
 
 	if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
 		pCluster = pSegment->GetNext(pCluster);
 		if ((pCluster == NULL) || pCluster->EOS()) {
-			fatal("Error: No movie found in the movie file.");
+			error("Error: No movie found in the movie file.");
 		}
-		pBlockEntry = pCluster->GetFirst();
+		if (pCluster->GetFirst(pBlockEntry))
+			error("oCluster::GetFirst() failed");
 	}
-	const Block *pBlock = pBlockEntry->GetBlock();
+	const mkvparser::Block *pBlock = pBlockEntry->GetBlock();
 	long long trackNum = pBlock->GetTrackNumber();
 	unsigned long tn = static_cast<unsigned long>(trackNum);
-	const Track *pTrack = pTracks->GetTrackByNumber(tn);
+	const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(tn);
 	long long trackType = pTrack->GetType();
 	int frameCount = pBlock->GetFrameCount();
 	time_ns = pBlock->GetTime(pCluster);
-#endif
 
 	return true;
 }


Commit: 2f7d4acacdefde8a503b8eb03f159a824751d1e9
    https://github.com/scummvm/scummvm/commit/2f7d4acacdefde8a503b8eb03f159a824751d1e9
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Initial code of the movie player loop

Changed paths:
    engines/sludge/graphics.h
    engines/sludge/movie.cpp


diff --git a/engines/sludge/graphics.h b/engines/sludge/graphics.h
index 08e15e82e99..7d3acaa15d0 100644
--- a/engines/sludge/graphics.h
+++ b/engines/sludge/graphics.h
@@ -200,6 +200,9 @@ public:
 	void blur_loadSettings(Common::SeekableReadStream *stream);
 	bool blur_createSettings(int numParams, VariableStack *&stack);
 
+	uint getWinWidth() { return _winWidth; }
+	uint getWinHeight() { return _winHeight; }
+
 private:
 	SludgeEngine *_vm;
 
diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 848194e0564..47676ec835c 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -20,7 +20,9 @@
  */
 
 #include "sludge/sludge.h"
+#include "sludge/event.h"
 #include "sludge/fileset.h"
+#include "sludge/graphics.h"
 #include "sludge/movie.h"
 #include "sludge/newfatal.h"
 #include "sludge/sound.h"
@@ -334,6 +336,31 @@ int playMovie(int fileNumber) {
 
 	decoder.loadStream(&video);
 
+
+	while (movieIsPlaying) {
+		g_sludge->_evtMan->checkInput();
+		if (g_sludge->_evtMan->quit())
+			break;
+
+		g_sludge->_evtMan->handleInput();
+
+		if (decoder.isVideoLoaded()) {
+			if (decoder.endOfVideo()) {
+				// Movie complete, so unload the movie
+				break;
+			} else if (decoder.needsUpdate()) {
+				const Graphics::Surface *s = decoder.decodeNextFrame();
+				if (s) {
+					// Transfer the next frame
+					assert(s->format.bytesPerPixel == 4);
+
+					g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, MIN<uint32>(s->w, g_sludge->_gfxMan->getWinWidth()), MIN<uint32>(s->h, g_sludge->_gfxMan->getWinHeight()));
+					g_system->updateScreen();
+				}
+			}
+		}
+	}
+
 	g_sludge->_resMan->finishAccess();
 	setResourceForFatal(-1);
 


Commit: 233fb9b1210d0f8c328b33bce8afcf50c500ea6a
    https://github.com/scummvm/scummvm/commit/233fb9b1210d0f8c328b33bce8afcf50c500ea6a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Playback code for MKV videos

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 8cb543b4bfa..5eeb03dbc55 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -106,9 +106,6 @@ MKVDecoder::MKVDecoder() {
 	_videoTrack = 0;
 	_audioTrack = 0;
 	_hasVideo = _hasAudio = false;
-
-	_codec = nullptr;
-	_reader = nullptr;
 }
 
 MKVDecoder::~MKVDecoder() {
@@ -145,6 +142,8 @@ int movieAudioIndex;
 bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	close();
 
+	warning("MKVDecoder::loadStream()");
+
 	_fileStream = stream;
 
 	_codec = new vpx_codec_ctx_t;
@@ -156,8 +155,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	ebmlHeader.Parse(_reader, pos);
 
-	mkvparser::Segment *pSegment;
-
 	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
 	if (ret) {
 		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%lld).", ret);
@@ -168,32 +165,30 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
 	}
 
-	const mkvparser::Tracks *pTracks = pSegment->GetTracks();
+	_tracks = pSegment->GetTracks();
 
 	unsigned long i = 0;
-	const unsigned long j = pTracks->GetTracksCount();
+	const unsigned long j = _tracks->GetTracksCount();
 
 	warning("Number of tracks: %ld", j);
 
 	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
-	int videoTrack = -1;
-	int audioTrack = -1;
+	videoTrack = -1;
+	audioTrack = -1;
 	long long audioBitDepth;
 	double audioSampleRate;
-	ogg_packet oggPacket;
 	vorbis_info vorbisInfo;
 	vorbis_comment vorbisComment;
-	vorbis_block vorbisBlock;
 
 	while (i != j) {
-		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
+		const mkvparser::Track *const pTrack = _tracks->GetTrackByIndex(i++);
 
 		if (pTrack == NULL)
 			continue;
 
 		const long long trackType = pTrack->GetType();
 		//const unsigned long long trackUid = pTrack->GetUid();
-		//const char* pTrackName = pTrack->GetNameAsUTF8();
+		//const char* _trackName = pTrack->GetNameAsUTF8();
 
 		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
@@ -254,18 +249,18 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			memset(&vorbisDspState, 0, sizeof(vorbisDspState));
 			memset(&vorbisBlock, 0, sizeof(vorbisBlock));
 
-			oggPacket.e_o_s = false;
-			oggPacket.granulepos = 0;
-			oggPacket.packetno = 0;
+			_oggPacket.e_o_s = false;
+			_oggPacket.granulepos = 0;
+			_oggPacket.packetno = 0;
 			int r;
 			for (int s = 0; s < 3; s++) {
-				oggPacket.packet = p;
-				oggPacket.bytes = sizes[s];
-				oggPacket.b_o_s = oggPacket.packetno == 0;
-				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
+				_oggPacket.packet = p;
+				_oggPacket.bytes = sizes[s];
+				_oggPacket.b_o_s = _oggPacket.packetno == 0;
+				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &_oggPacket);
 				if (r)
 					warning("vorbis_synthesis_headerin failed, error: %d", r);
-				oggPacket.packetno++;
+				_oggPacket.packetno++;
 				p += sizes[s];
 			}
 
@@ -311,39 +306,31 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	if (vpx_codec_dec_init(_codec, &vpx_codec_vp8_dx_algo, NULL, 0))
 		error("Failed to initialize decoder for movie.");
 
-	byte *frame = new byte[256 * 1024];
+	frame = new byte[256 * 1024];
 	if (!frame)
 		return false;
 
-	const mkvparser::Cluster *pCluster = pSegment->GetFirst();
+	_cluster = pSegment->GetFirst();
 
 #if 0
 	movieIsPlaying = playing;
 	movieIsEnding = 0;
 #endif
 
-	//const long long timeCode = pCluster->GetTimeCode();
-	long long time_ns = pCluster->GetTime();
+	//const long long timeCode = _cluster->GetTimeCode();
+	long long time_ns = _cluster->GetTime();
 
-	const mkvparser::BlockEntry *pBlockEntry;
-	if (pCluster->GetFirst(pBlockEntry))
-		error("pCluster::GetFirst() failed");
+	if (_cluster->GetFirst(pBlockEntry))
+		error("_cluster::GetFirst() failed");
 
 	if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-		pCluster = pSegment->GetNext(pCluster);
-		if ((pCluster == NULL) || pCluster->EOS()) {
+		_cluster = pSegment->GetNext(_cluster);
+		if ((_cluster == NULL) || _cluster->EOS()) {
 			error("Error: No movie found in the movie file.");
 		}
-		if (pCluster->GetFirst(pBlockEntry))
-			error("oCluster::GetFirst() failed");
+		if (_cluster->GetFirst(pBlockEntry))
+			error("_cluster::GetFirst() failed");
 	}
-	const mkvparser::Block *pBlock = pBlockEntry->GetBlock();
-	long long trackNum = pBlock->GetTrackNumber();
-	unsigned long tn = static_cast<unsigned long>(trackNum);
-	const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(tn);
-	long long trackType = pTrack->GetType();
-	int frameCount = pBlock->GetFrameCount();
-	time_ns = pBlock->GetTime(pCluster);
 
 	return true;
 }
@@ -356,26 +343,206 @@ void MKVDecoder::close() {
 }
 
 void MKVDecoder::readNextPacket() {
+	warning("MKVDecoder::readNextPacket()");
+
+	const mkvparser::Block *pBlock = pBlockEntry->GetBlock();
+	long long trackNum = pBlock->GetTrackNumber();
+	unsigned long tn = static_cast<unsigned long>(trackNum);
+	const mkvparser::Track *pTrack = _tracks->GetTrackByNumber(tn);
+	long long trackType = pTrack->GetType();
+	int frameCount = pBlock->GetFrameCount();
+	long long time_ns = pBlock->GetTime(_cluster);
+
+
 	// First, let's get our frame
-	if (_hasVideo) {
-		while (!_videoTrack->endOfTrack()) {
-			// theora is one in, one out...
-			if (ogg_stream_packetout(&_theoraOut, &_oggPacket) > 0) {
-				if (_videoTrack->decodePacket(_oggPacket))
+	while (_cluster != nullptr && !_cluster->EOS()) {
+		if (frameCounter >= frameCount) {
+
+			int res = _cluster->GetNext(pBlockEntry, pBlockEntry);
+			if  ((res != -1) || pBlockEntry->EOS())
+			{
+				_cluster = pSegment->GetNext(_cluster);
+				if ((_cluster == NULL) || _cluster->EOS()) {
+					_videoTrack->setEndOfVideo();
 					break;
-			} else if (_theoraOut.e_o_s || _fileStream->eos()) {
-				// If we can't get any more frames, we're done.
-				_videoTrack->setEndOfVideo();
-			} else {
-				// Queue more data
-				bufferData();
-				while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
-					queuePage(&_oggPage);
+				}
+				int ret = _cluster->GetFirst(pBlockEntry);
+
+				if (ret == -1)
+					error("MKVDecoder::readNextPacket(): GetFirst() failed");
 			}
+			pBlock  = pBlockEntry->GetBlock();
+			trackNum = pBlock->GetTrackNumber();
+			tn = static_cast<unsigned long>(trackNum);
+			pTrack = _tracks->GetTrackByNumber(tn);
+			trackType = pTrack->GetType();
+			frameCount = pBlock->GetFrameCount();
+			time_ns = pBlock->GetTime(_cluster);
+
+			frameCounter = 0;
+		}
+
+		const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
+		const long size = theFrame.len;
+		//                const long long offset = theFrame.pos;
+
+		if (size > sizeof(frame)) {
+			if (frame)
+				delete[] frame;
+			frame = new unsigned char[size];
+			if (!frame)
+				return;
+		}
+
+		if (trackNum == videoTrack) {
+			theFrame.Read(_reader, frame);
+
+			/* Decode the frame */
+			if (vpx_codec_decode(_codec, frame, size, NULL, 0))
+				error("Failed to decode frame");
+
+			// Let's decode an image frame!
+			vpx_codec_iter_t  iter = NULL;
+			vpx_image_t      *img;
 
-			// Update audio if we can
-			queueAudio();
+			/* Get frame data */
+			while ((img = vpx_codec_get_frame(_codec, &iter))) {
+				if (img->fmt != VPX_IMG_FMT_I420)
+					error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
+
+				unsigned int y;
+#if 0
+				GLubyte *ytex = NULL;
+				GLubyte *utex = NULL;
+				GLubyte *vtex = NULL;
+
+				if (! ytex) {
+					ytex = new GLubyte[img->d_w * img->d_h];
+					utex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
+					vtex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
+					if (!ytex || !utex || !vtex)
+						error("MKVDecoder: Out of memory"
+
+				}
+
+				unsigned char *buf =img->planes[0];
+				for (y = 0; y < img->d_h; y++) {
+					memcpy(ytex + y * img->d_w, buf, img->d_w);
+					buf += img->stride[0];
+				}
+				buf = img->planes[1];
+				for (y = 0; y < img->d_h >> 1; y++) {
+					memcpy(utex + y * (img->d_w >> 1), buf, img->d_w >> 1);
+					buf += img->stride[1];
+				}
+				buf = img->planes[2];
+				for (y = 0; y < img->d_h >> 1; y++) {
+					memcpy(vtex + y * (img->d_w >> 1), buf, img->d_w >> 1);
+					buf += img->stride[2];
+				}
+				video_queue_put(&videoQ, ytex, utex, vtex,
+								img->d_w, img->d_h, time_ns/1000000);
+#endif
+
+
+			}
+		} else if (trackNum == audioTrack) {
+			// Use this Audio Track
+			if (size > 0) {
+				theFrame.Read(_reader, frame);
+				_oggPacket.packet = frame;
+				_oggPacket.bytes = size;
+				_oggPacket.b_o_s = false;
+				_oggPacket.packetno++;
+				_oggPacket.granulepos = -1;
+				if(!vorbis_synthesis(&vorbisBlock, &_oggPacket) ) {
+					if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
+						warning("Vorbis Synthesis block in error");
+
+				} else {
+					warning("Vorbis Synthesis error");
+				}
+
+				float **pcm;
+
+				int numSamples = vorbis_synthesis_pcmout(&vorbisDspState, &pcm);
+
+				if (numSamples > 0) {
+					int word = 2;
+					int sgned = 1;
+					int i, j;
+					long bytespersample=audioChannels * word;
+					//vorbis_fpu_control fpu;
+
+					char *buffer = new char[bytespersample * numSamples];
+					if (!buffer)
+						error("MKVDecoder::readNextPacket(): buffer allocation failed");
+
+					/* a tight loop to pack each size */
+					{
+						int val;
+						if (word == 1) {
+							int off = (sgned ? 0 : 128);
+							//vorbis_fpu_setround(&fpu);
+							for (j = 0; j < numSamples; j++)
+								for (i = 0;i < audioChannels; i++) {
+									val = (int)(pcm[i][j] * 128.f);
+									val = CLIP(val, -128, 127);
+
+									*buffer++ = val + off;
+								}
+							//vorbis_fpu_restore(fpu);
+						} else {
+							int off = (sgned ? 0 : 32768);
+
+							if (sgned) {
+								//vorbis_fpu_setround(&fpu);
+								for (i = 0; i < audioChannels; i++) { /* It's faster in this order */
+									float *src = pcm[i];
+									short *dest = ((short *)buffer) + i;
+									for (j = 0; j < numSamples; j++) {
+										val = (int)(src[j] * 32768.f);
+										val = CLIP(val, -32768, 32767);
+
+										*dest = val;
+										dest += audioChannels;
+									}
+								}
+								//vorbis_fpu_restore(fpu);
+							} else { // unsigned
+								//vorbis_fpu_setround(&fpu);
+
+								for (i = 0; i < audioChannels; i++) {
+									float *src = pcm[i];
+									short *dest = ((short *)buffer) + i;
+									for (j = 0; j < numSamples; j++) {
+										val = (int)(src[j] * 32768.f);
+										val = CLIP(val, -32768, 32767);
+
+										*dest = val + off;
+										dest += audioChannels;
+									}
+								}
+								//vorbis_fpu_restore(fpu);
+							}
+						}
+					}
+
+					vorbis_synthesis_read(&vorbisDspState, numSamples);
+					audioBufferLen = bytespersample * numSamples;
+					//audio_queue_put(&audioQ, buffer, audioBufferLen, time_ns / 1000000);
+
+					//warning("Audio buffered: %lld byte %lld ns",audioBufferLen, audioNsPerByte*audioBufferLen);
+
+					if (!movieSoundPlaying && size > 1) {
+						warning("** starting sound **");
+						//playMovieStream(movieAudioIndex);
+						movieSoundPlaying = true;
+					}
+				}
+			}
 		}
+		++frameCounter;
 	}
 
 	// Then make sure we have enough audio buffered
@@ -413,8 +580,8 @@ MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
 	_displaySurface.setPixels(0);
 }
 
-bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &oggPacket) {
-	if (th_decode_packetin(_theoraDecode, &oggPacket, 0) == 0) {
+bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &_oggPacket) {
+	if (th_decode_packetin(_theoraDecode, &_oggPacket, 0) == 0) {
 		_curFrame++;
 
 		// Convert YUV data to RGB data
@@ -422,7 +589,7 @@ bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &oggPacket) {
 		th_decode_ycbcr_out(_theoraDecode, yuv);
 		translateYUVtoRGBA(yuv);
 
-		double time = th_granule_time(_theoraDecode, oggPacket.granulepos);
+		double time = th_granule_time(_theoraDecode, _oggPacket.granulepos);
 
 		// We need to calculate when the next frame should be shown
 		// This is all in floating point because that's what the Ogg code gives us
@@ -563,8 +730,8 @@ bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
 	return !_endOfAudio && _audStream->numQueuedStreams() < 5;
 }
 
-void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &oggPacket) {
-	if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) // test for success
+void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
+	if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success
 		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
 }
 
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 1adc6d925c9..8e940cec72a 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -55,6 +55,10 @@ class QueuingAudioStream;
 
 namespace mkvparser {
 class MkvReader;
+class Cluster;
+class Tracks;
+class BlockEntry;
+class Segment;
 }
 
 namespace Video {
@@ -161,6 +165,19 @@ private:
 
 	vpx_codec_ctx_t *_codec;
 	mkvparser::MkvReader *_reader;
+
+	const mkvparser::Cluster *_cluster = nullptr;
+	const mkvparser::Tracks *_tracks = nullptr;
+	const mkvparser::BlockEntry *pBlockEntry = nullptr;
+	mkvparser::Segment *pSegment = nullptr;
+
+	byte *frame = nullptr;
+	int frameCounter = 0;
+
+	int videoTrack = -1;
+	int audioTrack = -1;
+
+	vorbis_block vorbisBlock;
 };
 
 } // End of namespace Video


Commit: 32159c95491b7dcd169c01c4149c64641788c9d8
    https://github.com/scummvm/scummvm/commit/32159c95491b7dcd169c01c4149c64641788c9d8
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Fix MKV decoder initialization

Changed paths:
    video/mkv_decoder.cpp


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 5eeb03dbc55..4907ca48877 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -106,6 +106,9 @@ MKVDecoder::MKVDecoder() {
 	_videoTrack = 0;
 	_audioTrack = 0;
 	_hasVideo = _hasAudio = false;
+
+	_codec = nullptr;
+	_reader = nullptr;
 }
 
 MKVDecoder::~MKVDecoder() {
@@ -353,14 +356,12 @@ void MKVDecoder::readNextPacket() {
 	int frameCount = pBlock->GetFrameCount();
 	long long time_ns = pBlock->GetTime(_cluster);
 
-
 	// First, let's get our frame
 	while (_cluster != nullptr && !_cluster->EOS()) {
 		if (frameCounter >= frameCount) {
-
 			int res = _cluster->GetNext(pBlockEntry, pBlockEntry);
-			if  ((res != -1) || pBlockEntry->EOS())
-			{
+
+			if  ((res != -1) || pBlockEntry->EOS()) {
 				_cluster = pSegment->GetNext(_cluster);
 				if ((_cluster == NULL) || _cluster->EOS()) {
 					_videoTrack->setEndOfVideo();


Commit: 2cd44e2a7998731b850f8aa45b6a22faaf513e5b
    https://github.com/scummvm/scummvm/commit/2cd44e2a7998731b850f8aa45b6a22faaf513e5b
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Rename constants and flag movie playing

Changed paths:
    engines/sludge/movie.cpp
    engines/sludge/movie.h


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 47676ec835c..f24a6f430f6 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -39,7 +39,7 @@ int initMovieSound(int f, ALenum format, int audioChannels, ALuint samplerate,
 		ALuint(*callback)(void *userdata, ALubyte *data, ALuint bytes));
 #endif
 
-MovieStates movieIsPlaying = nothing;
+MovieStates movieIsPlaying = kMovieNothing;
 
 int movieIsEnding = 0;
 
@@ -334,8 +334,8 @@ int playMovie(int fileNumber) {
 	Common::SeekableReadStream *stream = g_sludge->_resMan->getData();
 	Common::SeekableSubReadStream video(stream, stream->pos(), stream->pos() + fsize);
 
-	decoder.loadStream(&video);
-
+	if (decoder.loadStream(&video))
+		movieIsPlaying = kMoviePlaying;
 
 	while (movieIsPlaying) {
 		g_sludge->_evtMan->checkInput();
@@ -553,7 +553,7 @@ int playMovie(int fileNumber) {
 
 	setMovieViewport();
 
-	movieIsPlaying = playing;
+	movieIsPlaying = kMoviePlaying;
 	movieIsEnding = 0;
 
 	glDepthMask(GL_FALSE);
@@ -835,7 +835,7 @@ int playMovie(int fileNumber) {
 
 		bool videoUpdated = false;
 		// Get a video frame.
-		if (movieIsPlaying == playing) {
+		if (movieIsPlaying == kMoviePlaying) {
 
 			videoBuffers *vB;
 			// Do we have decoded video waiting?
@@ -892,12 +892,12 @@ int playMovie(int fileNumber) {
 				}
 			} else if (movieIsEnding) {
 				// We have reached the end of the movie.
-				movieIsPlaying = nothing;
+				movieIsPlaying = kMovieNothing;
 			}
 		}
 
 		// Update the screen if there's new video, or if we're paused
-		if (videoUpdated || movieIsPlaying == paused) {
+		if (videoUpdated || movieIsPlaying == kMoviePaused) {
 			// Clear The Screen
 			glClear(GL_COLOR_BUFFER_BIT);
 
@@ -919,7 +919,7 @@ int playMovie(int fileNumber) {
 
 			glUseProgram(0);
 
-			if (movieIsPlaying == paused) {
+			if (movieIsPlaying == kMoviePaused) {
 				pausefade -= 1.0 / 24;
 				if (pausefade < -1.0) pausefade = 1.0;
 
@@ -954,7 +954,7 @@ int playMovie(int fileNumber) {
 	// Cleanup
 	glBindFramebuffer(GL_FRAMEBUFFER, old_fbo);
 
-	movieIsPlaying = nothing;
+	movieIsPlaying = kMovieNothing;
 	for (int i = 0; i < 10; i++)
 		Wait_Frame();
 	huntKillFreeSound(fileNumber);
@@ -1002,22 +1002,22 @@ int playMovie(int fileNumber) {
 
 int stopMovie() {
 	int r = movieIsPlaying;
-	movieIsPlaying = nothing;
+	movieIsPlaying = kMovieNothing;
 	return r;
 }
 
 int pauseMovie() {
 #if 0
-	if (movieIsPlaying == playing) {
+	if (movieIsPlaying == kMoviePlaying) {
 		ALuint source = getSoundSource(movieAudioIndex);
 		if (source) {
 
 			alurePauseSource(source);
 
 		}
-		movieIsPlaying = paused;
+		movieIsPlaying = kMoviePaused;
 		fprintf(stderr, "** Pausing **\n");
-	} else if (movieIsPlaying == paused) {
+	} else if (movieIsPlaying == kMoviePaused) {
 		ALuint source = getSoundSource(movieAudioIndex);
 		if (source) {
 
@@ -1025,7 +1025,7 @@ int pauseMovie() {
 
 		}
 		fprintf(stderr, "** Restarted movie ** sound: %d source: %d\n", movieSoundPlaying, source);
-		movieIsPlaying = playing;
+		movieIsPlaying = kMoviePlaying;
 	}
 #endif
 	return movieIsPlaying;
diff --git a/engines/sludge/movie.h b/engines/sludge/movie.h
index 8a1f5ce44f7..b6058424038 100644
--- a/engines/sludge/movie.h
+++ b/engines/sludge/movie.h
@@ -27,7 +27,9 @@ namespace Sludge {
  movieIsPlaying tracks the state of movie playing
  */
 enum MovieStates {
-	nothing = 0, playing, paused
+	kMovieNothing = 0,
+	kMoviePlaying,
+	kMoviePaused
 };
 extern MovieStates movieIsPlaying;
 


Commit: ca8859c4cd71af492bfeec16edad712482389edc
    https://github.com/scummvm/scummvm/commit/ca8859c4cd71af492bfeec16edad712482389edc
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: MKV: Move video track initialising to a separate class

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 4907ca48877..3d321fe4464 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -168,10 +168,10 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
 	}
 
-	_tracks = pSegment->GetTracks();
+	pTracks = pSegment->GetTracks();
 
 	unsigned long i = 0;
-	const unsigned long j = _tracks->GetTracksCount();
+	const unsigned long j = pTracks->GetTracksCount();
 
 	warning("Number of tracks: %ld", j);
 
@@ -184,7 +184,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	vorbis_comment vorbisComment;
 
 	while (i != j) {
-		const mkvparser::Track *const pTrack = _tracks->GetTrackByIndex(i++);
+		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
 
 		if (pTrack == NULL)
 			continue;
@@ -195,18 +195,16 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
-			const mkvparser::VideoTrack *const pVideoTrack = static_cast<const mkvparser::VideoTrack *>(pTrack);
 
-			const long long width = pVideoTrack->GetWidth();
-			const long long height = pVideoTrack->GetHeight();
+			_videoTrack = new VPXVideoTrack(pTrack);
 
-			const double rate = pVideoTrack->GetFrameRate();
+			addTrack(_videoTrack);
+			//setRate(_videoTrack->getFrameRate());
 
 #if 0
 			if (rate > 0)
 				Init_Special_Timer(rate); // TODO
 #endif
-			warning("VideoTrack: %lld x %lld @ %g fps", width, height, rate);
 		}
 
 		if (trackType == mkvparser::Track::kAudio && audioTrack < 0) {
@@ -351,7 +349,7 @@ void MKVDecoder::readNextPacket() {
 	const mkvparser::Block *pBlock = pBlockEntry->GetBlock();
 	long long trackNum = pBlock->GetTrackNumber();
 	unsigned long tn = static_cast<unsigned long>(trackNum);
-	const mkvparser::Track *pTrack = _tracks->GetTrackByNumber(tn);
+	const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(tn);
 	long long trackType = pTrack->GetType();
 	int frameCount = pBlock->GetFrameCount();
 	long long time_ns = pBlock->GetTime(_cluster);
@@ -375,7 +373,7 @@ void MKVDecoder::readNextPacket() {
 			pBlock  = pBlockEntry->GetBlock();
 			trackNum = pBlock->GetTrackNumber();
 			tn = static_cast<unsigned long>(trackNum);
-			pTrack = _tracks->GetTrackByNumber(tn);
+			pTrack = pTracks->GetTrackByNumber(tn);
 			trackType = pTrack->GetType();
 			frameCount = pBlock->GetFrameCount();
 			time_ns = pBlock->GetTime(_cluster);
@@ -396,6 +394,8 @@ void MKVDecoder::readNextPacket() {
 		}
 
 		if (trackNum == videoTrack) {
+			warning("MKVDecoder::readNextPacket(): video track");
+
 			theFrame.Read(_reader, frame);
 
 			/* Decode the frame */
@@ -448,6 +448,8 @@ void MKVDecoder::readNextPacket() {
 
 			}
 		} else if (trackNum == audioTrack) {
+			warning("MKVDecoder::readNextPacket(): audio track");
+
 			// Use this Audio Track
 			if (size > 0) {
 				theFrame.Read(_reader, frame);
@@ -550,24 +552,17 @@ void MKVDecoder::readNextPacket() {
 	ensureAudioBufferSize();
 }
 
-MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup) {
-	_theoraDecode = th_decode_alloc(&theoraInfo, theoraSetup);
+MKVDecoder::VPXVideoTrack::VPXVideoTrack(const mkvparser::Track *const pTrack) {
+	const mkvparser::VideoTrack *const pVideoTrack = static_cast<const mkvparser::VideoTrack *>(pTrack);
 
-	if (theoraInfo.pixel_fmt != TH_PF_420)
-		error("Only theora YUV420 is supported");
+	const long long width = pVideoTrack->GetWidth();
+	const long long height = pVideoTrack->GetHeight();
 
-	int postProcessingMax;
-	th_decode_ctl(_theoraDecode, TH_DECCTL_GET_PPLEVEL_MAX, &postProcessingMax, sizeof(postProcessingMax));
-	th_decode_ctl(_theoraDecode, TH_DECCTL_SET_PPLEVEL, &postProcessingMax, sizeof(postProcessingMax));
+	const double rate = pVideoTrack->GetFrameRate();
 
-	_surface.create(theoraInfo.frame_width, theoraInfo.frame_height, format);
+	warning("VideoTrack: %lld x %lld @ %g fps", width, height, rate);
 
-	// Set up a display surface
-	_displaySurface.init(theoraInfo.pic_width, theoraInfo.pic_height, _surface.pitch,
-	                    _surface.getBasePtr(theoraInfo.pic_x, theoraInfo.pic_y), format);
-
-	// Set the frame rate
-	_frameRate = Common::Rational(theoraInfo.fps_numerator, theoraInfo.fps_denominator);
+	_frameRate = 10; // FIXME
 
 	_endOfVideo = false;
 	_nextFrameStartTime = 0.0;
@@ -575,8 +570,6 @@ MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, th
 }
 
 MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
-	th_decode_free(_theoraDecode);
-
 	_surface.free();
 	_displaySurface.setPixels(0);
 }
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 8e940cec72a..bff866bc3df 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -56,6 +56,7 @@ class QueuingAudioStream;
 namespace mkvparser {
 class MkvReader;
 class Cluster;
+class Track;
 class Tracks;
 class BlockEntry;
 class Segment;
@@ -89,7 +90,7 @@ protected:
 private:
 	class VPXVideoTrack : public VideoTrack {
 	public:
-		VPXVideoTrack(const Graphics::PixelFormat &format, th_info &theoraInfo, th_setup_info *theoraSetup);
+		VPXVideoTrack(const mkvparser::Track *const pTrack);
 		~VPXVideoTrack();
 
 		bool endOfTrack() const { return _endOfVideo; }
@@ -160,14 +161,14 @@ private:
 
 	vorbis_info _vorbisInfo;
 
-	VPXVideoTrack *_videoTrack;
-	VorbisAudioTrack *_audioTrack;
+	VPXVideoTrack *_videoTrack = nullptr;
+	VorbisAudioTrack *_audioTrack = nullptr;
 
-	vpx_codec_ctx_t *_codec;
-	mkvparser::MkvReader *_reader;
+	vpx_codec_ctx_t *_codec = nullptr;
+	mkvparser::MkvReader *_reader = nullptr;
 
 	const mkvparser::Cluster *_cluster = nullptr;
-	const mkvparser::Tracks *_tracks = nullptr;
+	const mkvparser::Tracks *pTracks = nullptr;
 	const mkvparser::BlockEntry *pBlockEntry = nullptr;
 	mkvparser::Segment *pSegment = nullptr;
 


Commit: 4613ab0fd9cac2ee6355977a1483bcf17d703192
    https://github.com/scummvm/scummvm/commit/4613ab0fd9cac2ee6355977a1483bcf17d703192
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: MKV: Move audio track to separate class

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 3d321fe4464..09efea788e9 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -178,10 +178,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
 	videoTrack = -1;
 	audioTrack = -1;
-	long long audioBitDepth;
-	double audioSampleRate;
-	vorbis_info vorbisInfo;
-	vorbis_comment vorbisComment;
 
 	while (i != j) {
 		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
@@ -209,11 +205,8 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 		if (trackType == mkvparser::Track::kAudio && audioTrack < 0) {
 			audioTrack = pTrack->GetNumber();
-			const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
-			audioChannels = pAudioTrack->GetChannels();
-			audioBitDepth = pAudioTrack->GetBitDepth();
-			audioSampleRate = pAudioTrack->GetSamplingRate();
+			const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
 			size_t audioHeaderSize;
 			byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
@@ -233,57 +226,8 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 				continue;
 			}
 
-			uint64 sizes[3], total;
-
-			int l = 0;
-			total = 0;
-			while (--count) {
-				sizes[l] = xiph_lace_value(&p);
-				total += sizes[i];
-				l += 1;
-			}
-			sizes[l] = audioHeaderSize - total - (p - audioHeader);
-
-			// initialize vorbis
-			vorbis_info_init(&vorbisInfo);
-			vorbis_comment_init(&vorbisComment);
-			memset(&vorbisDspState, 0, sizeof(vorbisDspState));
-			memset(&vorbisBlock, 0, sizeof(vorbisBlock));
-
-			_oggPacket.e_o_s = false;
-			_oggPacket.granulepos = 0;
-			_oggPacket.packetno = 0;
-			int r;
-			for (int s = 0; s < 3; s++) {
-				_oggPacket.packet = p;
-				_oggPacket.bytes = sizes[s];
-				_oggPacket.b_o_s = _oggPacket.packetno == 0;
-				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &_oggPacket);
-				if (r)
-					warning("vorbis_synthesis_headerin failed, error: %d", r);
-				_oggPacket.packetno++;
-				p += sizes[s];
-			}
-
-			r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
-			if (r)
-				warning("vorbis_synthesis_init failed, error: %d", r);
-			r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
-			if (r)
-				warning("vorbis_block_init failed, error: %d", r);
-
-#if 0
-			ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
-			movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
-#endif
-
-			debug(1, "Movie sound inited.");
-#if 0
-			audio_queue_init(&audioQ);
-#endif
-			audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
-			audioNsBuffered = 0;
-			audioBufferLen = audioChannels * audioSampleRate;
+			_audioTrack = new VorbisAudioTrack(pTrack);
+			addTrack(_audioTrack);
 		}
 	}
 
@@ -450,99 +394,17 @@ void MKVDecoder::readNextPacket() {
 		} else if (trackNum == audioTrack) {
 			warning("MKVDecoder::readNextPacket(): audio track");
 
-			// Use this Audio Track
 			if (size > 0) {
-				theFrame.Read(_reader, frame);
-				_oggPacket.packet = frame;
-				_oggPacket.bytes = size;
-				_oggPacket.b_o_s = false;
-				_oggPacket.packetno++;
-				_oggPacket.granulepos = -1;
-				if(!vorbis_synthesis(&vorbisBlock, &_oggPacket) ) {
-					if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
-						warning("Vorbis Synthesis block in error");
-
-				} else {
-					warning("Vorbis Synthesis error");
-				}
-
-				float **pcm;
-
-				int numSamples = vorbis_synthesis_pcmout(&vorbisDspState, &pcm);
-
-				if (numSamples > 0) {
-					int word = 2;
-					int sgned = 1;
-					int i, j;
-					long bytespersample=audioChannels * word;
-					//vorbis_fpu_control fpu;
-
-					char *buffer = new char[bytespersample * numSamples];
-					if (!buffer)
-						error("MKVDecoder::readNextPacket(): buffer allocation failed");
-
-					/* a tight loop to pack each size */
-					{
-						int val;
-						if (word == 1) {
-							int off = (sgned ? 0 : 128);
-							//vorbis_fpu_setround(&fpu);
-							for (j = 0; j < numSamples; j++)
-								for (i = 0;i < audioChannels; i++) {
-									val = (int)(pcm[i][j] * 128.f);
-									val = CLIP(val, -128, 127);
-
-									*buffer++ = val + off;
-								}
-							//vorbis_fpu_restore(fpu);
-						} else {
-							int off = (sgned ? 0 : 32768);
-
-							if (sgned) {
-								//vorbis_fpu_setround(&fpu);
-								for (i = 0; i < audioChannels; i++) { /* It's faster in this order */
-									float *src = pcm[i];
-									short *dest = ((short *)buffer) + i;
-									for (j = 0; j < numSamples; j++) {
-										val = (int)(src[j] * 32768.f);
-										val = CLIP(val, -32768, 32767);
-
-										*dest = val;
-										dest += audioChannels;
-									}
-								}
-								//vorbis_fpu_restore(fpu);
-							} else { // unsigned
-								//vorbis_fpu_setround(&fpu);
-
-								for (i = 0; i < audioChannels; i++) {
-									float *src = pcm[i];
-									short *dest = ((short *)buffer) + i;
-									for (j = 0; j < numSamples; j++) {
-										val = (int)(src[j] * 32768.f);
-										val = CLIP(val, -32768, 32767);
-
-										*dest = val + off;
-										dest += audioChannels;
-									}
-								}
-								//vorbis_fpu_restore(fpu);
-							}
-						}
-					}
-
-					vorbis_synthesis_read(&vorbisDspState, numSamples);
-					audioBufferLen = bytespersample * numSamples;
-					//audio_queue_put(&audioQ, buffer, audioBufferLen, time_ns / 1000000);
+				ogg_packet oggPacket;
 
-					//warning("Audio buffered: %lld byte %lld ns",audioBufferLen, audioNsPerByte*audioBufferLen);
+				theFrame.Read(_reader, frame);
+				oggPacket.packet = frame;
+				oggPacket.bytes = size;
+				oggPacket.b_o_s = false;
+				oggPacket.packetno++;
+				oggPacket.granulepos = -1;
 
-					if (!movieSoundPlaying && size > 1) {
-						warning("** starting sound **");
-						//playMovieStream(movieAudioIndex);
-						movieSoundPlaying = true;
-					}
-				}
+				_audioTrack->decodeSamples(oggPacket);
 			}
 		}
 		++frameCounter;
@@ -624,11 +486,80 @@ void MKVDecoder::VPXVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) {
 
 static vorbis_info *info = 0;
 
-MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo) :
-		AudioTrack(soundType) {
-	vorbis_synthesis_init(&_vorbisDSP, &vorbisInfo);
-	vorbis_block_init(&_vorbisDSP, &_vorbisBlock);
-	info = &vorbisInfo;
+MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTrack) :
+		AudioTrack(Audio::Mixer::kPlainSoundType) {
+
+	long long audioBitDepth;
+	double audioSampleRate;
+	vorbis_info vorbisInfo;
+	vorbis_comment vorbisComment;
+
+	const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
+
+	audioChannels = pAudioTrack->GetChannels();
+	audioBitDepth = pAudioTrack->GetBitDepth();
+	audioSampleRate = pAudioTrack->GetSamplingRate();
+
+	size_t audioHeaderSize;
+	byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
+	byte *p = audioHeader;
+
+	uint count = *p++ + 1;
+
+	uint64 sizes[3], total;
+
+	int l = 0;
+	total = 0;
+	while (--count) {
+		sizes[l] = xiph_lace_value(&p);
+		total += sizes[l];
+		l += 1;
+	}
+	sizes[l] = audioHeaderSize - total - (p - audioHeader);
+
+	// initialize vorbis
+	vorbis_info_init(&vorbisInfo);
+	vorbis_comment_init(&vorbisComment);
+	memset(&vorbisDspState, 0, sizeof(vorbisDspState));
+	memset(&vorbisBlock, 0, sizeof(vorbisBlock));
+
+	ogg_packet oggPacket;
+
+	oggPacket.e_o_s = false;
+	oggPacket.granulepos = 0;
+	oggPacket.packetno = 0;
+	int r;
+	for (int s = 0; s < 3; s++) {
+		oggPacket.packet = p;
+		oggPacket.bytes = sizes[s];
+		oggPacket.b_o_s = oggPacket.packetno == 0;
+		r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
+		if (r)
+			warning("vorbis_synthesis_headerin failed, error: %d", r);
+		oggPacket.packetno++;
+		p += sizes[s];
+	}
+
+	r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
+	if (r)
+		warning("vorbis_synthesis_init failed, error: %d", r);
+	r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
+	if (r)
+		warning("vorbis_block_init failed, error: %d", r);
+
+#if 0
+	ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
+	movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
+#endif
+
+	debug(1, "Movie sound inited.");
+#if 0
+	audio_queue_init(&audioQ);
+#endif
+	audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
+	audioNsBuffered = 0;
+	audioBufferLen = audioChannels * audioSampleRate;
+
 
 	_audStream = Audio::makeQueuingAudioStream(vorbisInfo.rate, vorbisInfo.channels != 1);
 
@@ -639,7 +570,7 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(Audio::Mixer::SoundType soundType
 
 MKVDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
 	vorbis_dsp_clear(&_vorbisDSP);
-	vorbis_block_clear(&_vorbisBlock);
+	vorbis_block_clear(&vorbisBlock);
 	delete _audStream;
 	free(_audioBuffer);
 }
@@ -656,63 +587,93 @@ static double rint(double v) {
 }
 #endif
 
-bool MKVDecoder::VorbisAudioTrack::decodeSamples() {
-#ifdef USE_TREMOR
-	ogg_int32_t **pcm;
-#else
-	float **pcm;
-#endif
+bool MKVDecoder::VorbisAudioTrack::decodeSamples(ogg_packet &oggPacket) {
+	if(!vorbis_synthesis(&vorbisBlock, &oggPacket) ) {
+		if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
+			warning("Vorbis Synthesis block in error");
 
-	// if there's pending, decoded audio, grab it
-	int ret = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
+	} else {
+		warning("Vorbis Synthesis error");
+	}
 
-	if (ret > 0) {
-		if (!_audioBuffer) {
-			_audioBuffer = (ogg_int16_t *)malloc(AUDIOFD_FRAGSIZE * sizeof(ogg_int16_t));
-			assert(_audioBuffer);
-		}
+	float **pcm;
 
-		int channels = _audStream->isStereo() ? 2 : 1;
-		int count = _audioBufferFill / 2;
-		int maxsamples = ((AUDIOFD_FRAGSIZE - _audioBufferFill) / channels) >> 1;
-		int i;
-
-		for (i = 0; i < ret && i < maxsamples; i++) {
-			for (int j = 0; j < channels; j++) {
-#ifdef USE_TREMOR
-				int val = CLIP((int)pcm[j][i] >> 9, -32768, 32767);
-#else
-				int val = CLIP((int)rint(pcm[j][i] * 32767.f), -32768, 32767);
-#endif
-				_audioBuffer[count++] = val;
+	int numSamples = vorbis_synthesis_pcmout(&vorbisDspState, &pcm);
+
+	if (numSamples > 0) {
+		int word = 2;
+		int sgned = 1;
+		int i, j;
+		long bytespersample=audioChannels * word;
+		//vorbis_fpu_control fpu;
+
+		char *buffer = new char[bytespersample * numSamples];
+		if (!buffer)
+			error("MKVDecoder::readNextPacket(): buffer allocation failed");
+
+		/* a tight loop to pack each size */
+		{
+			int val;
+			if (word == 1) {
+				int off = (sgned ? 0 : 128);
+				//vorbis_fpu_setround(&fpu);
+				for (j = 0; j < numSamples; j++)
+					for (i = 0;i < audioChannels; i++) {
+						val = (int)(pcm[i][j] * 128.f);
+						val = CLIP(val, -128, 127);
+
+						*buffer++ = val + off;
+					}
+				//vorbis_fpu_restore(fpu);
+			} else {
+				int off = (sgned ? 0 : 32768);
+
+				if (sgned) {
+					//vorbis_fpu_setround(&fpu);
+					for (i = 0; i < audioChannels; i++) { /* It's faster in this order */
+						float *src = pcm[i];
+						short *dest = ((short *)buffer) + i;
+						for (j = 0; j < numSamples; j++) {
+							val = (int)(src[j] * 32768.f);
+							val = CLIP(val, -32768, 32767);
+
+							*dest = val;
+							dest += audioChannels;
+						}
+					}
+					//vorbis_fpu_restore(fpu);
+				} else { // unsigned
+					//vorbis_fpu_setround(&fpu);
+
+					for (i = 0; i < audioChannels; i++) {
+						float *src = pcm[i];
+						short *dest = ((short *)buffer) + i;
+						for (j = 0; j < numSamples; j++) {
+							val = (int)(src[j] * 32768.f);
+							val = CLIP(val, -32768, 32767);
+
+							*dest = val + off;
+							dest += audioChannels;
+						}
+					}
+					//vorbis_fpu_restore(fpu);
+				}
 			}
 		}
 
-		vorbis_synthesis_read(&_vorbisDSP, i);
-		_audioBufferFill += (i * channels) << 1;
-
-		if (_audioBufferFill == AUDIOFD_FRAGSIZE) {
-			byte flags = Audio::FLAG_16BITS;
+		vorbis_synthesis_read(&vorbisDspState, numSamples);
+		audioBufferLen = bytespersample * numSamples;
+		//audio_queue_put(&audioQ, buffer, audioBufferLen, time_ns / 1000000);
 
-			if (_audStream->isStereo())
-				flags |= Audio::FLAG_STEREO;
+		//warning("Audio buffered: %lld byte %lld ns",audioBufferLen, audioNsPerByte*audioBufferLen);
 
-#ifdef SCUMM_LITTLE_ENDIAN
-			flags |= Audio::FLAG_LITTLE_ENDIAN;
-#endif
-
-			_audStream->queueBuffer((byte *)_audioBuffer, AUDIOFD_FRAGSIZE, DisposeAfterUse::YES, flags);
-
-			// The audio mixer is now responsible for the old audio buffer.
-			// We need to create a new one.
-			_audioBuffer = 0;
-			_audioBufferFill = 0;
+		if (!movieSoundPlaying) {
+			warning("** starting sound **");
+			//playMovieStream(movieAudioIndex);
+			movieSoundPlaying = true;
 		}
-
-		return true;
 	}
 
-	return false;
 }
 
 bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
@@ -725,8 +686,8 @@ bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
 }
 
 void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
-	if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success
-		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
+	if (vorbis_synthesis(&vorbisBlock, &_oggPacket) == 0) // test for success
+		vorbis_synthesis_blockin(&_vorbisDSP, &vorbisBlock);
 }
 
 void MKVDecoder::queuePage(ogg_page *page) {
@@ -752,6 +713,7 @@ bool MKVDecoder::queueAudio() {
 
 	bool queuedAudio = false;
 
+#if 0
 	for (;;) {
 		if (_audioTrack->decodeSamples()) {
 			// we queued some pending audio
@@ -764,6 +726,7 @@ bool MKVDecoder::queueAudio() {
 			break;
 		}
 	}
+#endif
 
 	return queuedAudio;
 }
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index bff866bc3df..12c471e6f5c 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -120,10 +120,10 @@ private:
 
 	class VorbisAudioTrack : public AudioTrack {
 	public:
-		VorbisAudioTrack(Audio::Mixer::SoundType soundType, vorbis_info &vorbisInfo);
+		VorbisAudioTrack(const mkvparser::Track *const pTrack);
 		~VorbisAudioTrack();
 
-		bool decodeSamples();
+		bool decodeSamples(ogg_packet &oggPacket);
 		bool hasAudio() const;
 		bool needsAudio() const;
 		void synthesizePacket(ogg_packet &oggPacket);
@@ -139,7 +139,7 @@ private:
 
 		Audio::QueuingAudioStream *_audStream;
 
-		vorbis_block _vorbisBlock;
+		vorbis_block vorbisBlock;
 		vorbis_dsp_state _vorbisDSP;
 
 		bool _endOfAudio;
@@ -154,7 +154,6 @@ private:
 
 	ogg_sync_state _oggSync;
 	ogg_page _oggPage;
-	ogg_packet _oggPacket;
 
 	ogg_stream_state _theoraOut, _vorbisOut;
 	bool _hasVideo, _hasAudio;
@@ -177,8 +176,6 @@ private:
 
 	int videoTrack = -1;
 	int audioTrack = -1;
-
-	vorbis_block vorbisBlock;
 };
 
 } // End of namespace Video


Commit: b3dfca3cb1441d19a11c096c31844c63bf6389ef
    https://github.com/scummvm/scummvm/commit/b3dfca3cb1441d19a11c096c31844c63bf6389ef
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: YUV420 conversion for MKV decoder

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 09efea788e9..4c43f78940d 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -355,6 +355,9 @@ void MKVDecoder::readNextPacket() {
 				if (img->fmt != VPX_IMG_FMT_I420)
 					error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
 
+
+				YUVToRGBMan.convert420(&_displaySurface, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
+
 				unsigned int y;
 #if 0
 				GLubyte *ytex = NULL;
@@ -437,6 +440,7 @@ MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
 }
 
 bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &_oggPacket) {
+	warning("VPXVideoTrack::decodePacket()");
 	if (th_decode_packetin(_theoraDecode, &_oggPacket, 0) == 0) {
 		_curFrame++;
 
@@ -588,6 +592,8 @@ static double rint(double v) {
 #endif
 
 bool MKVDecoder::VorbisAudioTrack::decodeSamples(ogg_packet &oggPacket) {
+	return true;
+
 	if(!vorbis_synthesis(&vorbisBlock, &oggPacket) ) {
 		if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
 			warning("Vorbis Synthesis block in error");
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 12c471e6f5c..5acadf28162 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -176,6 +176,8 @@ private:
 
 	int videoTrack = -1;
 	int audioTrack = -1;
+
+	Graphics::Surface _displaySurface;
 };
 
 } // End of namespace Video


Commit: 0357ccc5d9fb62240006829c3db11dc53f5e998a
    https://github.com/scummvm/scummvm/commit/0357ccc5d9fb62240006829c3db11dc53f5e998a
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Moved video frame decoding to subclass in mkv decoder

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 4c43f78940d..6cb0d257077 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -107,7 +107,6 @@ MKVDecoder::MKVDecoder() {
 	_audioTrack = 0;
 	_hasVideo = _hasAudio = false;
 
-	_codec = nullptr;
 	_reader = nullptr;
 }
 
@@ -149,7 +148,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	_fileStream = stream;
 
-	_codec = new vpx_codec_ctx_t;
 	_reader = new mkvparser::MkvReader(stream);
 
 	long long pos = 0;
@@ -192,7 +190,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
 
-			_videoTrack = new VPXVideoTrack(pTrack);
+			_videoTrack = new VPXVideoTrack(getDefaultHighColorFormat(), pTrack);
 
 			addTrack(_videoTrack);
 			//setRate(_videoTrack->getFrameRate());
@@ -247,10 +245,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		error("Movie error: Segment has no clusters.\n");
 	}
 
-	/* Initialize video codec */
-	if (vpx_codec_dec_init(_codec, &vpx_codec_vp8_dx_algo, NULL, 0))
-		error("Failed to initialize decoder for movie.");
-
 	frame = new byte[256 * 1024];
 	if (!frame)
 		return false;
@@ -283,7 +277,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 void MKVDecoder::close() {
 	VideoDecoder::close();
 
-	delete _codec;
 	delete _reader;
 }
 
@@ -342,72 +335,14 @@ void MKVDecoder::readNextPacket() {
 
 			theFrame.Read(_reader, frame);
 
-			/* Decode the frame */
-			if (vpx_codec_decode(_codec, frame, size, NULL, 0))
-				error("Failed to decode frame");
-
-			// Let's decode an image frame!
-			vpx_codec_iter_t  iter = NULL;
-			vpx_image_t      *img;
-
-			/* Get frame data */
-			while ((img = vpx_codec_get_frame(_codec, &iter))) {
-				if (img->fmt != VPX_IMG_FMT_I420)
-					error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
-
-
-				YUVToRGBMan.convert420(&_displaySurface, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
-
-				unsigned int y;
-#if 0
-				GLubyte *ytex = NULL;
-				GLubyte *utex = NULL;
-				GLubyte *vtex = NULL;
-
-				if (! ytex) {
-					ytex = new GLubyte[img->d_w * img->d_h];
-					utex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
-					vtex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
-					if (!ytex || !utex || !vtex)
-						error("MKVDecoder: Out of memory"
-
-				}
-
-				unsigned char *buf =img->planes[0];
-				for (y = 0; y < img->d_h; y++) {
-					memcpy(ytex + y * img->d_w, buf, img->d_w);
-					buf += img->stride[0];
-				}
-				buf = img->planes[1];
-				for (y = 0; y < img->d_h >> 1; y++) {
-					memcpy(utex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-					buf += img->stride[1];
-				}
-				buf = img->planes[2];
-				for (y = 0; y < img->d_h >> 1; y++) {
-					memcpy(vtex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-					buf += img->stride[2];
-				}
-				video_queue_put(&videoQ, ytex, utex, vtex,
-								img->d_w, img->d_h, time_ns/1000000);
-#endif
-
-
-			}
+			_videoTrack->decodeFrame(frame, size);
 		} else if (trackNum == audioTrack) {
 			warning("MKVDecoder::readNextPacket(): audio track");
 
 			if (size > 0) {
-				ogg_packet oggPacket;
-
 				theFrame.Read(_reader, frame);
-				oggPacket.packet = frame;
-				oggPacket.bytes = size;
-				oggPacket.b_o_s = false;
-				oggPacket.packetno++;
-				oggPacket.granulepos = -1;
 
-				_audioTrack->decodeSamples(oggPacket);
+				_audioTrack->decodeSamples(frame, size);
 			}
 		}
 		++frameCounter;
@@ -417,7 +352,7 @@ void MKVDecoder::readNextPacket() {
 	ensureAudioBufferSize();
 }
 
-MKVDecoder::VPXVideoTrack::VPXVideoTrack(const mkvparser::Track *const pTrack) {
+MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack) {
 	const mkvparser::VideoTrack *const pVideoTrack = static_cast<const mkvparser::VideoTrack *>(pTrack);
 
 	const long long width = pVideoTrack->GetWidth();
@@ -427,42 +362,82 @@ MKVDecoder::VPXVideoTrack::VPXVideoTrack(const mkvparser::Track *const pTrack) {
 
 	warning("VideoTrack: %lld x %lld @ %g fps", width, height, rate);
 
+	_displaySurface.create(width, height, format);
+
 	_frameRate = 10; // FIXME
 
 	_endOfVideo = false;
 	_nextFrameStartTime = 0.0;
 	_curFrame = -1;
+
+	_codec = new vpx_codec_ctx_t;
+
+	/* Initialize video codec */
+	if (vpx_codec_dec_init(_codec, &vpx_codec_vp8_dx_algo, NULL, 0))
+		error("Failed to initialize decoder for movie.");
 }
 
 MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
-	_surface.free();
-	_displaySurface.setPixels(0);
+	_displaySurface.free();
+	delete _codec;
 }
 
-bool MKVDecoder::VPXVideoTrack::decodePacket(ogg_packet &_oggPacket) {
-	warning("VPXVideoTrack::decodePacket()");
-	if (th_decode_packetin(_theoraDecode, &_oggPacket, 0) == 0) {
-		_curFrame++;
+bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
+
+	/* Decode the frame */
+	if (vpx_codec_decode(_codec, frame, size, NULL, 0))
+		error("Failed to decode frame");
 
-		// Convert YUV data to RGB data
-		th_ycbcr_buffer yuv;
-		th_decode_ycbcr_out(_theoraDecode, yuv);
-		translateYUVtoRGBA(yuv);
+	// Let's decode an image frame!
+	vpx_codec_iter_t  iter = NULL;
+	vpx_image_t      *img;
 
-		double time = th_granule_time(_theoraDecode, _oggPacket.granulepos);
+	/* Get frame data */
+	while ((img = vpx_codec_get_frame(_codec, &iter))) {
+		if (img->fmt != VPX_IMG_FMT_I420)
+			error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
+
+
+		YUVToRGBMan.convert420(&_displaySurface, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
+
+		unsigned int y;
+#if 0
+		GLubyte *ytex = NULL;
+		GLubyte *utex = NULL;
+		GLubyte *vtex = NULL;
+
+		if (! ytex) {
+			ytex = new GLubyte[img->d_w * img->d_h];
+			utex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
+			vtex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
+			if (!ytex || !utex || !vtex)
+				error("MKVDecoder: Out of memory"
+
+		}
+
+		unsigned char *buf =img->planes[0];
+		for (y = 0; y < img->d_h; y++) {
+			memcpy(ytex + y * img->d_w, buf, img->d_w);
+			buf += img->stride[0];
+		}
+		buf = img->planes[1];
+		for (y = 0; y < img->d_h >> 1; y++) {
+			memcpy(utex + y * (img->d_w >> 1), buf, img->d_w >> 1);
+			buf += img->stride[1];
+		}
+		buf = img->planes[2];
+		for (y = 0; y < img->d_h >> 1; y++) {
+			memcpy(vtex + y * (img->d_w >> 1), buf, img->d_w >> 1);
+			buf += img->stride[2];
+		}
+		video_queue_put(&videoQ, ytex, utex, vtex,
+						img->d_w, img->d_h, time_ns/1000000);
+#endif
 
-		// We need to calculate when the next frame should be shown
-		// This is all in floating point because that's what the Ogg code gives us
-		// Ogg is a lossy container format, so it doesn't always list the time to the
-		// next frame. In such cases, we need to calculate it ourselves.
-		if (time == -1.0)
-			_nextFrameStartTime += _frameRate.getInverse().toDouble();
-		else
-			_nextFrameStartTime = time;
 
-		return true;
 	}
 
+
 	return false;
 }
 
@@ -591,9 +566,15 @@ static double rint(double v) {
 }
 #endif
 
-bool MKVDecoder::VorbisAudioTrack::decodeSamples(ogg_packet &oggPacket) {
+bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 	return true;
 
+	oggPacket.packet = frame;
+	oggPacket.bytes = size;
+	oggPacket.b_o_s = false;
+	oggPacket.packetno++;
+	oggPacket.granulepos = -1;
+
 	if(!vorbis_synthesis(&vorbisBlock, &oggPacket) ) {
 		if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
 			warning("Vorbis Synthesis block in error");
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 5acadf28162..9d9711db4c2 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -90,7 +90,7 @@ protected:
 private:
 	class VPXVideoTrack : public VideoTrack {
 	public:
-		VPXVideoTrack(const mkvparser::Track *const pTrack);
+		VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack);
 		~VPXVideoTrack();
 
 		bool endOfTrack() const { return _endOfVideo; }
@@ -101,7 +101,7 @@ private:
 		uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
 		const Graphics::Surface *decodeNextFrame() { return &_displaySurface; }
 
-		bool decodePacket(ogg_packet &oggPacket);
+		bool decodeFrame(byte *frame, long size);
 		void setEndOfVideo() { _endOfVideo = true; }
 
 	private:
@@ -115,6 +115,8 @@ private:
 
 		th_dec_ctx *_theoraDecode;
 
+		vpx_codec_ctx_t *_codec = nullptr;
+
 		void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer);
 	};
 
@@ -123,7 +125,7 @@ private:
 		VorbisAudioTrack(const mkvparser::Track *const pTrack);
 		~VorbisAudioTrack();
 
-		bool decodeSamples(ogg_packet &oggPacket);
+		bool decodeSamples(byte *frame, long size);
 		bool hasAudio() const;
 		bool needsAudio() const;
 		void synthesizePacket(ogg_packet &oggPacket);
@@ -143,6 +145,8 @@ private:
 		vorbis_dsp_state _vorbisDSP;
 
 		bool _endOfAudio;
+
+		ogg_packet oggPacket;
 	};
 
 	void queuePage(ogg_page *page);
@@ -163,7 +167,6 @@ private:
 	VPXVideoTrack *_videoTrack = nullptr;
 	VorbisAudioTrack *_audioTrack = nullptr;
 
-	vpx_codec_ctx_t *_codec = nullptr;
 	mkvparser::MkvReader *_reader = nullptr;
 
 	const mkvparser::Cluster *_cluster = nullptr;


Commit: 2b15031f555e7fda4b6d4c3c1d0b7c0322353593
    https://github.com/scummvm/scummvm/commit/2b15031f555e7fda4b6d4c3c1d0b7c0322353593
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Fix MKV frames navigation. First visuals

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 6cb0d257077..a824f9f184f 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -271,6 +271,14 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			error("_cluster::GetFirst() failed");
 	}
 
+	pBlock = pBlockEntry->GetBlock();
+	trackNum = pBlock->GetTrackNumber();
+	tn = static_cast<unsigned long>(trackNum);
+	pTrack = pTracks->GetTrackByNumber(tn);
+	trackType = pTrack->GetType();
+	frameCount = pBlock->GetFrameCount();
+	time_ns = pBlock->GetTime(_cluster);
+
 	return true;
 }
 
@@ -283,14 +291,6 @@ void MKVDecoder::close() {
 void MKVDecoder::readNextPacket() {
 	warning("MKVDecoder::readNextPacket()");
 
-	const mkvparser::Block *pBlock = pBlockEntry->GetBlock();
-	long long trackNum = pBlock->GetTrackNumber();
-	unsigned long tn = static_cast<unsigned long>(trackNum);
-	const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(tn);
-	long long trackType = pTrack->GetType();
-	int frameCount = pBlock->GetFrameCount();
-	long long time_ns = pBlock->GetTime(_cluster);
-
 	// First, let's get our frame
 	while (_cluster != nullptr && !_cluster->EOS()) {
 		if (frameCounter >= frameCount) {
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 9d9711db4c2..2d725896385 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -58,6 +58,7 @@ class MkvReader;
 class Cluster;
 class Track;
 class Tracks;
+class Block;
 class BlockEntry;
 class Segment;
 }
@@ -180,6 +181,14 @@ private:
 	int videoTrack = -1;
 	int audioTrack = -1;
 
+	const mkvparser::Block *pBlock;
+	long long trackNum;
+	unsigned long tn;
+	const mkvparser::Track *pTrack;
+	long long trackType;
+	int frameCount;
+	long long time_ns;
+
 	Graphics::Surface _displaySurface;
 };
 


Commit: 7f8c9504f54082c264a25ad60df13fb5b9871097
    https://github.com/scummvm/scummvm/commit/7f8c9504f54082c264a25ad60df13fb5b9871097
Author: Eugene Sandulenko (sev at scummvm.org)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Simplified MKV parser loop

Changed paths:
    video/mkv_decoder.cpp


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index a824f9f184f..d5600e34ea9 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -278,6 +278,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	trackType = pTrack->GetType();
 	frameCount = pBlock->GetFrameCount();
 	time_ns = pBlock->GetTime(_cluster);
+	warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
 
 	return true;
 }
@@ -289,64 +290,70 @@ void MKVDecoder::close() {
 }
 
 void MKVDecoder::readNextPacket() {
-	warning("MKVDecoder::readNextPacket()");
+	//warning("MKVDecoder::readNextPacket()");
 
 	// First, let's get our frame
-	while (_cluster != nullptr && !_cluster->EOS()) {
-		if (frameCounter >= frameCount) {
-			int res = _cluster->GetNext(pBlockEntry, pBlockEntry);
-
-			if  ((res != -1) || pBlockEntry->EOS()) {
-				_cluster = pSegment->GetNext(_cluster);
-				if ((_cluster == NULL) || _cluster->EOS()) {
-					_videoTrack->setEndOfVideo();
-					break;
-				}
-				int ret = _cluster->GetFirst(pBlockEntry);
+	if (_cluster == nullptr || _cluster->EOS()) {
+		_videoTrack->setEndOfVideo();
+		return;
+	}
+
+	if (frameCounter >= frameCount) {
+		int res = _cluster->GetNext(pBlockEntry, pBlockEntry);
 
-				if (ret == -1)
-					error("MKVDecoder::readNextPacket(): GetFirst() failed");
+		if  ((res != -1) || pBlockEntry->EOS()) {
+			_cluster = pSegment->GetNext(_cluster);
+			if ((_cluster == NULL) || _cluster->EOS()) {
+				_videoTrack->setEndOfVideo();
+				return;
 			}
-			pBlock  = pBlockEntry->GetBlock();
-			trackNum = pBlock->GetTrackNumber();
-			tn = static_cast<unsigned long>(trackNum);
-			pTrack = pTracks->GetTrackByNumber(tn);
-			trackType = pTrack->GetType();
-			frameCount = pBlock->GetFrameCount();
-			time_ns = pBlock->GetTime(_cluster);
-
-			frameCounter = 0;
+			int ret = _cluster->GetFirst(pBlockEntry);
+
+			if (ret == -1)
+				error("MKVDecoder::readNextPacket(): GetFirst() failed");
 		}
+		pBlock  = pBlockEntry->GetBlock();
+		trackNum = pBlock->GetTrackNumber();
+		tn = static_cast<unsigned long>(trackNum);
+		pTrack = pTracks->GetTrackByNumber(tn);
+		trackType = pTrack->GetType();
+		frameCount = pBlock->GetFrameCount();
+		time_ns = pBlock->GetTime(_cluster);
+		warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
+
+		frameCounter = 0;
+	}
 
-		const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
-		const long size = theFrame.len;
-		//                const long long offset = theFrame.pos;
+	const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
+	const long size = theFrame.len;
+	//                const long long offset = theFrame.pos;
 
-		if (size > sizeof(frame)) {
-			if (frame)
-				delete[] frame;
-			frame = new unsigned char[size];
-			if (!frame)
-				return;
-		}
+	if (size > sizeof(frame)) {
+		if (frame)
+			delete[] frame;
+		frame = new unsigned char[size];
+		if (!frame)
+			return;
+	}
 
-		if (trackNum == videoTrack) {
-			warning("MKVDecoder::readNextPacket(): video track");
+	if (trackNum == videoTrack) {
+		warning("MKVDecoder::readNextPacket(): video track");
 
-			theFrame.Read(_reader, frame);
+		theFrame.Read(_reader, frame);
 
-			_videoTrack->decodeFrame(frame, size);
-		} else if (trackNum == audioTrack) {
-			warning("MKVDecoder::readNextPacket(): audio track");
+		_videoTrack->decodeFrame(frame, size);
+	} else if (trackNum == audioTrack) {
+		warning("MKVDecoder::readNextPacket(): audio track");
 
-			if (size > 0) {
-				theFrame.Read(_reader, frame);
+		if (size > 0) {
+			theFrame.Read(_reader, frame);
 
-				_audioTrack->decodeSamples(frame, size);
-			}
+			//_audioTrack->decodeSamples(frame, size);
 		}
-		++frameCounter;
+	} else {
+		warning("Unprocessed track %d", trackNum);
 	}
+	++frameCounter;
 
 	// Then make sure we have enough audio buffered
 	ensureAudioBufferSize();


Commit: 0485b06f0bde71d9ec67653b9a306c3d0b6a17a8
    https://github.com/scummvm/scummvm/commit/0485b06f0bde71d9ec67653b9a306c3d0b6a17a8
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Change movie playing status to none once its done playing

Changed paths:
    engines/sludge/movie.cpp
    engines/sludge/movie.h


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index f24a6f430f6..9169db2d65c 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -329,6 +329,7 @@ int playMovie(int fileNumber) {
 	if (!(fsize = g_sludge->_resMan->openFileFromNum(fileNumber)))
 		return fatal("playMovie(): Can't open movie");
 
+	warning("In play movie");
 	Video::MKVDecoder decoder;
 
 	Common::SeekableReadStream *stream = g_sludge->_resMan->getData();
@@ -337,6 +338,7 @@ int playMovie(int fileNumber) {
 	if (decoder.loadStream(&video))
 		movieIsPlaying = kMoviePlaying;
 
+	warning("movieIsPlaying %d", movieIsPlaying);
 	while (movieIsPlaying) {
 		g_sludge->_evtMan->checkInput();
 		if (g_sludge->_evtMan->quit())
@@ -346,6 +348,7 @@ int playMovie(int fileNumber) {
 
 		if (decoder.isVideoLoaded()) {
 			if (decoder.endOfVideo()) {
+				warning("End of video");
 				// Movie complete, so unload the movie
 				break;
 			} else if (decoder.needsUpdate()) {
@@ -354,13 +357,19 @@ int playMovie(int fileNumber) {
 					// Transfer the next frame
 					assert(s->format.bytesPerPixel == 4);
 
+					//warning("Copy rect to screen");
+
 					g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, MIN<uint32>(s->w, g_sludge->_gfxMan->getWinWidth()), MIN<uint32>(s->h, g_sludge->_gfxMan->getWinHeight()));
 					g_system->updateScreen();
+				} else {
+					warning("s is false");
 				}
 			}
 		}
 	}
 
+	movieIsPlaying = kMovieNothing;
+
 	g_sludge->_resMan->finishAccess();
 	setResourceForFatal(-1);
 
diff --git a/engines/sludge/movie.h b/engines/sludge/movie.h
index b6058424038..14961618051 100644
--- a/engines/sludge/movie.h
+++ b/engines/sludge/movie.h
@@ -32,6 +32,7 @@ enum MovieStates {
 	kMoviePaused
 };
 extern MovieStates movieIsPlaying;
+extern int movieIsEnding;
 
 int playMovie(int fileNumber);
 int stopMovie();


Commit: 5a086b18836ad6f4ad09231e17aac953821164ea
    https://github.com/scummvm/scummvm/commit/5a086b18836ad6f4ad09231e17aac953821164ea
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Fix a wrong if condition in MKV Decoder

The wrong if condition was causing videos to skip playing after 1/2 frames.

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index d5600e34ea9..1ff64cec032 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -295,13 +295,16 @@ void MKVDecoder::readNextPacket() {
 	// First, let's get our frame
 	if (_cluster == nullptr || _cluster->EOS()) {
 		_videoTrack->setEndOfVideo();
+		warning("EOS");
 		return;
 	}
 
+	//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
+
 	if (frameCounter >= frameCount) {
-		int res = _cluster->GetNext(pBlockEntry, pBlockEntry);
+		_cluster->GetNext(pBlockEntry, pBlockEntry);
 
-		if  ((res != -1) || pBlockEntry->EOS()) {
+		if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
 			_cluster = pSegment->GetNext(_cluster);
 			if ((_cluster == NULL) || _cluster->EOS()) {
 				_videoTrack->setEndOfVideo();
@@ -309,7 +312,7 @@ void MKVDecoder::readNextPacket() {
 			}
 			int ret = _cluster->GetFirst(pBlockEntry);
 
-			if (ret == -1)
+			if (ret < 0)
 				error("MKVDecoder::readNextPacket(): GetFirst() failed");
 		}
 		pBlock  = pBlockEntry->GetBlock();
@@ -326,6 +329,7 @@ void MKVDecoder::readNextPacket() {
 
 	const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
 	const long size = theFrame.len;
+	warning("Size of frame %ld\n", size);
 	//                const long long offset = theFrame.pos;
 
 	if (size > sizeof(frame)) {
@@ -391,6 +395,8 @@ MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
 
 bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 
+	//warning("In within decodeFrame");
+
 	/* Decode the frame */
 	if (vpx_codec_decode(_codec, frame, size, NULL, 0))
 		error("Failed to decode frame");
@@ -538,7 +544,7 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
 #endif
 
-	debug(1, "Movie sound inited.");
+	warning("Movie sound inited.");
 #if 0
 	audio_queue_init(&audioQ);
 #endif
@@ -685,9 +691,6 @@ void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
 }
 
 void MKVDecoder::queuePage(ogg_page *page) {
-	if (_hasVideo)
-		ogg_stream_pagein(&_theoraOut, page);
-
 	if (_hasAudio)
 		ogg_stream_pagein(&_vorbisOut, page);
 }
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 2d725896385..4452f512855 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -160,7 +160,7 @@ private:
 	ogg_sync_state _oggSync;
 	ogg_page _oggPage;
 
-	ogg_stream_state _theoraOut, _vorbisOut;
+	ogg_stream_state _vorbisOut;
 	bool _hasVideo, _hasAudio;
 
 	vorbis_info _vorbisInfo;


Commit: 2b05c1b9256f89cd0d0bf35996b11ef9f547f8cd
    https://github.com/scummvm/scummvm/commit/2b05c1b9256f89cd0d0bf35996b11ef9f547f8cd
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Start working on audio playback in MKV Decoder

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 1ff64cec032..92f947a8758 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -278,7 +278,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	trackType = pTrack->GetType();
 	frameCount = pBlock->GetFrameCount();
 	time_ns = pBlock->GetTime(_cluster);
-	warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
+	//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
 
 	return true;
 }
@@ -322,7 +322,7 @@ void MKVDecoder::readNextPacket() {
 		trackType = pTrack->GetType();
 		frameCount = pBlock->GetFrameCount();
 		time_ns = pBlock->GetTime(_cluster);
-		warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
+		//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
 
 		frameCounter = 0;
 	}
@@ -351,8 +351,7 @@ void MKVDecoder::readNextPacket() {
 
 		if (size > 0) {
 			theFrame.Read(_reader, frame);
-
-			//_audioTrack->decodeSamples(frame, size);
+			_audioTrack->decodeSamples(frame, size);
 		}
 	} else {
 		warning("Unprocessed track %d", trackNum);
@@ -492,6 +491,8 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	audioBitDepth = pAudioTrack->GetBitDepth();
 	audioSampleRate = pAudioTrack->GetSamplingRate();
 
+	warning("audioChannels %d audioBitDepth %d audioSamplerate %f", audioChannels, audioBitDepth, audioSampleRate);
+
 	size_t audioHeaderSize;
 	byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
 	byte *p = audioHeader;
@@ -513,9 +514,9 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	vorbis_info_init(&vorbisInfo);
 	vorbis_comment_init(&vorbisComment);
 	memset(&vorbisDspState, 0, sizeof(vorbisDspState));
-	memset(&vorbisBlock, 0, sizeof(vorbisBlock));
+	memset(&_vorbisBlock, 0, sizeof(_vorbisBlock));
 
-	ogg_packet oggPacket;
+	//ogg_packet oggPacket;
 
 	oggPacket.e_o_s = false;
 	oggPacket.granulepos = 0;
@@ -535,7 +536,7 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
 	if (r)
 		warning("vorbis_synthesis_init failed, error: %d", r);
-	r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
+	r = vorbis_block_init(&vorbisDspState, &_vorbisBlock);
 	if (r)
 		warning("vorbis_block_init failed, error: %d", r);
 
@@ -561,10 +562,10 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 }
 
 MKVDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
-	vorbis_dsp_clear(&_vorbisDSP);
-	vorbis_block_clear(&vorbisBlock);
-	delete _audStream;
-	free(_audioBuffer);
+	//vorbis_dsp_clear(&_vorbisDSP);
+	//vorbis_block_clear(&vorbisBlock);
+	//delete _audStream;
+	//free(_audioBuffer);
 }
 
 Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
@@ -580,16 +581,20 @@ static double rint(double v) {
 #endif
 
 bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
-	return true;
+	//return true;
+
+	ogg_packet packet;
+	packet.packet = frame;
+	packet.bytes = size;
+	packet.b_o_s = false;
+	packet.e_o_s = false;
+	packet.packetno = ++oggPacket.packetno;
+	packet.granulepos = -1;
 
-	oggPacket.packet = frame;
-	oggPacket.bytes = size;
-	oggPacket.b_o_s = false;
-	oggPacket.packetno++;
-	oggPacket.granulepos = -1;
+	warning("oggPacket bytes : %d packno %d\n", oggPacket.bytes, oggPacket.packetno);
 
-	if(!vorbis_synthesis(&vorbisBlock, &oggPacket) ) {
-		if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
+	if(!vorbis_synthesis(&_vorbisBlock, &packet) ) {
+		if (vorbis_synthesis_blockin(&vorbisDspState, &_vorbisBlock))
 			warning("Vorbis Synthesis block in error");
 
 	} else {
@@ -673,7 +678,7 @@ bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 			movieSoundPlaying = true;
 		}
 	}
-
+	return true;
 }
 
 bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
@@ -686,8 +691,9 @@ bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
 }
 
 void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
-	if (vorbis_synthesis(&vorbisBlock, &_oggPacket) == 0) // test for success
-		vorbis_synthesis_blockin(&_vorbisDSP, &vorbisBlock);
+	warning("in synthesizePacket");
+	if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success
+		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
 }
 
 void MKVDecoder::queuePage(ogg_page *page) {
@@ -709,8 +715,7 @@ bool MKVDecoder::queueAudio() {
 		return false;
 
 	bool queuedAudio = false;
-
-#if 0
+/*
 	for (;;) {
 		if (_audioTrack->decodeSamples()) {
 			// we queued some pending audio
@@ -723,7 +728,7 @@ bool MKVDecoder::queueAudio() {
 			break;
 		}
 	}
-#endif
+	*/
 
 	return queuedAudio;
 }
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 4452f512855..822a4ad10ae 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -142,7 +142,7 @@ private:
 
 		Audio::QueuingAudioStream *_audStream;
 
-		vorbis_block vorbisBlock;
+		vorbis_block _vorbisBlock;
 		vorbis_dsp_state _vorbisDSP;
 
 		bool _endOfAudio;


Commit: b0e2b8e02ab46e8f738787da8971bca5165b527c
    https://github.com/scummvm/scummvm/commit/b0e2b8e02ab46e8f738787da8971bca5165b527c
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Remove unneeded variables/leftovers from theora in MKV Decoder

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 92f947a8758..2d01ef7970e 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -114,8 +114,6 @@ MKVDecoder::~MKVDecoder() {
 	close();
 }
 
-long long audioChannels;
-
 static uint64 xiph_lace_value(byte **np) {
 	uint64 lace;
 	uint64 value;
@@ -129,31 +127,19 @@ static uint64 xiph_lace_value(byte **np) {
 	}
 
 	*np = p;
-
 	return value;
 }
 
-vorbis_dsp_state vorbisDspState;
-int64 audioNsPerByte;
-int64 audioNsPlayed;
-int64 audioNsBuffered;
-int64 audioBufferLen;
-bool movieSoundPlaying = false;
-int movieAudioIndex;
-
 bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	close();
 
 	warning("MKVDecoder::loadStream()");
 
 	_fileStream = stream;
-
 	_reader = new mkvparser::MkvReader(stream);
 
 	long long pos = 0;
-
 	mkvparser::EBMLHeader ebmlHeader;
-
 	ebmlHeader.Parse(_reader, pos);
 
 	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
@@ -171,7 +157,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	unsigned long i = 0;
 	const unsigned long j = pTracks->GetTracksCount();
 
-	warning("Number of tracks: %ld", j);
+	debug(1, "Number of tracks: %ld", j);
 
 	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
 	videoTrack = -1;
@@ -184,9 +170,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			continue;
 
 		const long long trackType = pTrack->GetType();
-		//const unsigned long long trackUid = pTrack->GetUid();
-		//const char* _trackName = pTrack->GetNameAsUTF8();
-
 		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
 			videoTrack = pTrack->GetNumber();
 
@@ -235,11 +218,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	if (audioTrack < 0)
 		error("Movie error: No sound found.");
 
-#if 0
-	video_queue_init(&videoQ);
-#endif
-
-	const unsigned long clusterCount = pSegment->GetCount();
+	const unsigned long long clusterCount = pSegment->GetCount();
 
 	if (clusterCount == 0) {
 		error("Movie error: Segment has no clusters.\n");
@@ -274,11 +253,8 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	pBlock = pBlockEntry->GetBlock();
 	trackNum = pBlock->GetTrackNumber();
 	tn = static_cast<unsigned long>(trackNum);
-	pTrack = pTracks->GetTrackByNumber(tn);
-	trackType = pTrack->GetType();
 	frameCount = pBlock->GetFrameCount();
 	time_ns = pBlock->GetTime(_cluster);
-	//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
 
 	return true;
 }
@@ -295,7 +271,6 @@ void MKVDecoder::readNextPacket() {
 	// First, let's get our frame
 	if (_cluster == nullptr || _cluster->EOS()) {
 		_videoTrack->setEndOfVideo();
-		warning("EOS");
 		return;
 	}
 
@@ -317,20 +292,13 @@ void MKVDecoder::readNextPacket() {
 		}
 		pBlock  = pBlockEntry->GetBlock();
 		trackNum = pBlock->GetTrackNumber();
-		tn = static_cast<unsigned long>(trackNum);
-		pTrack = pTracks->GetTrackByNumber(tn);
-		trackType = pTrack->GetType();
 		frameCount = pBlock->GetFrameCount();
 		time_ns = pBlock->GetTime(_cluster);
-		//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
-
 		frameCounter = 0;
 	}
 
 	const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
 	const long size = theFrame.len;
-	warning("Size of frame %ld\n", size);
-	//                const long long offset = theFrame.pos;
 
 	if (size > sizeof(frame)) {
 		if (frame)
@@ -344,7 +312,6 @@ void MKVDecoder::readNextPacket() {
 		warning("MKVDecoder::readNextPacket(): video track");
 
 		theFrame.Read(_reader, frame);
-
 		_videoTrack->decodeFrame(frame, size);
 	} else if (trackNum == audioTrack) {
 		warning("MKVDecoder::readNextPacket(): audio track");
@@ -357,9 +324,6 @@ void MKVDecoder::readNextPacket() {
 		warning("Unprocessed track %d", trackNum);
 	}
 	++frameCounter;
-
-	// Then make sure we have enough audio buffered
-	ensureAudioBufferSize();
 }
 
 MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack) {
@@ -367,10 +331,9 @@ MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, co
 
 	const long long width = pVideoTrack->GetWidth();
 	const long long height = pVideoTrack->GetHeight();
-
 	const double rate = pVideoTrack->GetFrameRate();
 
-	warning("VideoTrack: %lld x %lld @ %g fps", width, height, rate);
+	warning("VideoTrack: %lld x %lld @ %f fps", width, height, rate);
 
 	_displaySurface.create(width, height, format);
 
@@ -403,93 +366,26 @@ bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 	// Let's decode an image frame!
 	vpx_codec_iter_t  iter = NULL;
 	vpx_image_t      *img;
-
 	/* Get frame data */
 	while ((img = vpx_codec_get_frame(_codec, &iter))) {
 		if (img->fmt != VPX_IMG_FMT_I420)
 			error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
 
-
 		YUVToRGBMan.convert420(&_displaySurface, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
-
-		unsigned int y;
-#if 0
-		GLubyte *ytex = NULL;
-		GLubyte *utex = NULL;
-		GLubyte *vtex = NULL;
-
-		if (! ytex) {
-			ytex = new GLubyte[img->d_w * img->d_h];
-			utex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
-			vtex = new GLubyte[(img->d_w >> 1) * (img->d_h >> 1)];
-			if (!ytex || !utex || !vtex)
-				error("MKVDecoder: Out of memory"
-
-		}
-
-		unsigned char *buf =img->planes[0];
-		for (y = 0; y < img->d_h; y++) {
-			memcpy(ytex + y * img->d_w, buf, img->d_w);
-			buf += img->stride[0];
-		}
-		buf = img->planes[1];
-		for (y = 0; y < img->d_h >> 1; y++) {
-			memcpy(utex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-			buf += img->stride[1];
-		}
-		buf = img->planes[2];
-		for (y = 0; y < img->d_h >> 1; y++) {
-			memcpy(vtex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-			buf += img->stride[2];
-		}
-		video_queue_put(&videoQ, ytex, utex, vtex,
-						img->d_w, img->d_h, time_ns/1000000);
-#endif
-
-
 	}
-
-
 	return false;
 }
 
-enum TheoraYUVBuffers {
-	kBufferY = 0,
-	kBufferU = 1,
-	kBufferV = 2
-};
-
-void MKVDecoder::VPXVideoTrack::translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer) {
-	// Width and height of all buffers have to be divisible by 2.
-	assert((YUVBuffer[kBufferY].width & 1) == 0);
-	assert((YUVBuffer[kBufferY].height & 1) == 0);
-	assert((YUVBuffer[kBufferU].width & 1) == 0);
-	assert((YUVBuffer[kBufferV].width & 1) == 0);
-
-	// UV images have to have a quarter of the Y image resolution
-	assert(YUVBuffer[kBufferU].width == YUVBuffer[kBufferY].width >> 1);
-	assert(YUVBuffer[kBufferV].width == YUVBuffer[kBufferY].width >> 1);
-	assert(YUVBuffer[kBufferU].height == YUVBuffer[kBufferY].height >> 1);
-	assert(YUVBuffer[kBufferV].height == YUVBuffer[kBufferY].height >> 1);
-
-	YUVToRGBMan.convert420(&_surface, Graphics::YUVToRGBManager::kScaleITU, YUVBuffer[kBufferY].data, YUVBuffer[kBufferU].data, YUVBuffer[kBufferV].data, YUVBuffer[kBufferY].width, YUVBuffer[kBufferY].height, YUVBuffer[kBufferY].stride, YUVBuffer[kBufferU].stride);
-}
-
-static vorbis_info *info = 0;
-
 MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTrack) :
 		AudioTrack(Audio::Mixer::kPlainSoundType) {
 
-	long long audioBitDepth;
-	double audioSampleRate;
-	vorbis_info vorbisInfo;
 	vorbis_comment vorbisComment;
 
 	const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
-	audioChannels = pAudioTrack->GetChannels();
-	audioBitDepth = pAudioTrack->GetBitDepth();
-	audioSampleRate = pAudioTrack->GetSamplingRate();
+	const long long audioChannels = pAudioTrack->GetChannels();
+	const long long audioBitDepth = pAudioTrack->GetBitDepth();
+	const double audioSampleRate = pAudioTrack->GetSamplingRate();
 
 	warning("audioChannels %d audioBitDepth %d audioSamplerate %f", audioChannels, audioBitDepth, audioSampleRate);
 
@@ -511,13 +407,11 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	sizes[l] = audioHeaderSize - total - (p - audioHeader);
 
 	// initialize vorbis
-	vorbis_info_init(&vorbisInfo);
+	vorbis_info_init(&_vorbisInfo);
 	vorbis_comment_init(&vorbisComment);
-	memset(&vorbisDspState, 0, sizeof(vorbisDspState));
+	memset(&_vorbisDSP, 0, sizeof(_vorbisDSP));
 	memset(&_vorbisBlock, 0, sizeof(_vorbisBlock));
 
-	//ogg_packet oggPacket;
-
 	oggPacket.e_o_s = false;
 	oggPacket.granulepos = 0;
 	oggPacket.packetno = 0;
@@ -526,38 +420,20 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 		oggPacket.packet = p;
 		oggPacket.bytes = sizes[s];
 		oggPacket.b_o_s = oggPacket.packetno == 0;
-		r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
+		r = vorbis_synthesis_headerin(&_vorbisInfo, &vorbisComment, &oggPacket);
 		if (r)
 			warning("vorbis_synthesis_headerin failed, error: %d", r);
 		oggPacket.packetno++;
 		p += sizes[s];
 	}
 
-	r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
-	if (r)
-		warning("vorbis_synthesis_init failed, error: %d", r);
-	r = vorbis_block_init(&vorbisDspState, &_vorbisBlock);
-	if (r)
-		warning("vorbis_block_init failed, error: %d", r);
-
-#if 0
-	ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
-	movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
-#endif
-
-	warning("Movie sound inited.");
-#if 0
-	audio_queue_init(&audioQ);
-#endif
-	audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
-	audioNsBuffered = 0;
-	audioBufferLen = audioChannels * audioSampleRate;
-
-
-	_audStream = Audio::makeQueuingAudioStream(vorbisInfo.rate, vorbisInfo.channels != 1);
+	r = vorbis_synthesis_init(&_vorbisDSP, &_vorbisInfo);
+	if(r)
+		error("vorbis_synthesis_init, error: %d", r);
+	r = vorbis_block_init(&_vorbisDSP, &_vorbisBlock);
+	if(r)
+		error("vorbis_block_init, error: %d", r);
 
-	_audioBufferFill = 0;
-	_audioBuffer = 0;
 	_endOfAudio = false;
 }
 
@@ -572,14 +448,6 @@ Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
 	return _audStream;
 }
 
-#define AUDIOFD_FRAGSIZE 10240
-
-#ifndef USE_TREMOR
-static double rint(double v) {
-	return floor(v + 0.5);
-}
-#endif
-
 bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 	//return true;
 
@@ -609,7 +477,7 @@ bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 		int word = 2;
 		int sgned = 1;
 		int i, j;
-		long bytespersample=audioChannels * word;
+		long bytespersample= audioChannels * word;
 		//vorbis_fpu_control fpu;
 
 		char *buffer = new char[bytespersample * numSamples];
@@ -668,13 +536,8 @@ bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 
 		vorbis_synthesis_read(&vorbisDspState, numSamples);
 		audioBufferLen = bytespersample * numSamples;
-		//audio_queue_put(&audioQ, buffer, audioBufferLen, time_ns / 1000000);
-
-		//warning("Audio buffered: %lld byte %lld ns",audioBufferLen, audioNsPerByte*audioBufferLen);
-
 		if (!movieSoundPlaying) {
 			warning("** starting sound **");
-			//playMovieStream(movieAudioIndex);
 			movieSoundPlaying = true;
 		}
 	}
@@ -696,59 +559,11 @@ void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
 		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
 }
 
-void MKVDecoder::queuePage(ogg_page *page) {
-	if (_hasAudio)
-		ogg_stream_pagein(&_vorbisOut, page);
-}
-
-int MKVDecoder::bufferData() {
-	char *buffer = ogg_sync_buffer(&_oggSync, 4096);
-	int bytes = _fileStream->read(buffer, 4096);
-
-	ogg_sync_wrote(&_oggSync, bytes);
-
-	return bytes;
-}
-
 bool MKVDecoder::queueAudio() {
 	if (!_hasAudio)
 		return false;
 
 	bool queuedAudio = false;
-/*
-	for (;;) {
-		if (_audioTrack->decodeSamples()) {
-			// we queued some pending audio
-			queuedAudio = true;
-		} else if (ogg_stream_packetout(&_vorbisOut, &_oggPacket) > 0) {
-			// no pending audio; is there a pending packet to decode?
-			_audioTrack->synthesizePacket(_oggPacket);
-		} else {
-			// we've buffered all we have, break out for now
-			break;
-		}
-	}
-	*/
-
 	return queuedAudio;
 }
-
-void MKVDecoder::ensureAudioBufferSize() {
-	if (!_hasAudio)
-		return;
-
-	// Force at least some audio to be buffered
-	while (_audioTrack->needsAudio()) {
-		bufferData();
-		while (ogg_sync_pageout(&_oggSync, &_oggPage) > 0)
-			queuePage(&_oggPage);
-
-		bool queuedAudio = queueAudio();
-		if ((_vorbisOut.e_o_s  || _fileStream->eos()) && !queuedAudio) {
-			_audioTrack->setEndOfAudio();
-			break;
-		}
-	}
-}
-
 } // End of namespace Video
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 822a4ad10ae..dd26eea4671 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -114,11 +114,7 @@ private:
 		Graphics::Surface _surface;
 		Graphics::Surface _displaySurface;
 
-		th_dec_ctx *_theoraDecode;
-
 		vpx_codec_ctx_t *_codec = nullptr;
-
-		void translateYUVtoRGBA(th_ycbcr_buffer &YUVBuffer);
 	};
 
 	class VorbisAudioTrack : public AudioTrack {
@@ -145,26 +141,18 @@ private:
 		vorbis_block _vorbisBlock;
 		vorbis_dsp_state _vorbisDSP;
 
+		vorbis_info _vorbisInfo;
 		bool _endOfAudio;
 
 		ogg_packet oggPacket;
 	};
 
-	void queuePage(ogg_page *page);
-	int bufferData();
 	bool queueAudio();
-	void ensureAudioBufferSize();
 
 	Common::SeekableReadStream *_fileStream;
 
-	ogg_sync_state _oggSync;
-	ogg_page _oggPage;
-
-	ogg_stream_state _vorbisOut;
 	bool _hasVideo, _hasAudio;
 
-	vorbis_info _vorbisInfo;
-
 	VPXVideoTrack *_videoTrack = nullptr;
 	VorbisAudioTrack *_audioTrack = nullptr;
 
@@ -184,12 +172,9 @@ private:
 	const mkvparser::Block *pBlock;
 	long long trackNum;
 	unsigned long tn;
-	const mkvparser::Track *pTrack;
 	long long trackType;
 	int frameCount;
 	long long time_ns;
-
-	Graphics::Surface _displaySurface;
 };
 
 } // End of namespace Video


Commit: 50fac579fe9f53cdb44030aa11024167a2d0960c
    https://github.com/scummvm/scummvm/commit/50fac579fe9f53cdb44030aa11024167a2d0960c
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Add decoder.start() to actually start playing audio

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 9169db2d65c..28c73c7e2cd 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -338,6 +338,8 @@ int playMovie(int fileNumber) {
 	if (decoder.loadStream(&video))
 		movieIsPlaying = kMoviePlaying;
 
+	decoder.start();
+
 	warning("movieIsPlaying %d", movieIsPlaying);
 	while (movieIsPlaying) {
 		g_sludge->_evtMan->checkInput();


Commit: 66b21a502021540a18bc34128ad5b34c2f74691d
    https://github.com/scummvm/scummvm/commit/66b21a502021540a18bc34128ad5b34c2f74691d
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
VIDEO: Add working sound support in MKV parser

The solution works by ensuring that we have atleast 10 audioBuffers in the audio stream

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 2d01ef7970e..840f1962d76 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -266,64 +266,62 @@ void MKVDecoder::close() {
 }
 
 void MKVDecoder::readNextPacket() {
-	//warning("MKVDecoder::readNextPacket()");
 
 	// First, let's get our frame
 	if (_cluster == nullptr || _cluster->EOS()) {
 		_videoTrack->setEndOfVideo();
+		if (!_audioTrack->hasAudio())
+			_audioTrack->setEndOfAudio();
 		return;
 	}
 
-	//warning("trackNum: %d frameCounter: %d frameCount: %d, time_ns: %d", tn, frameCounter, frameCount, time_ns);
-
-	if (frameCounter >= frameCount) {
+	while(_audioTrack->needsAudio()){
+		if (frameCounter >= frameCount) {
 		_cluster->GetNext(pBlockEntry, pBlockEntry);
 
-		if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-			_cluster = pSegment->GetNext(_cluster);
-			if ((_cluster == NULL) || _cluster->EOS()) {
-				_videoTrack->setEndOfVideo();
-				return;
+			if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
+				_cluster = pSegment->GetNext(_cluster);
+				if ((_cluster == NULL) || _cluster->EOS()) {
+					_videoTrack->setEndOfVideo();
+					_audioTrack->setEndOfAudio();
+					return;
+				}
+				int ret = _cluster->GetFirst(pBlockEntry);
+				if (ret < 0)
+					error("MKVDecoder::readNextPacket(): GetFirst() failed");
 			}
-			int ret = _cluster->GetFirst(pBlockEntry);
 
-			if (ret < 0)
-				error("MKVDecoder::readNextPacket(): GetFirst() failed");
+			pBlock  = pBlockEntry->GetBlock();
+			trackNum = pBlock->GetTrackNumber();
+			frameCount = pBlock->GetFrameCount();
+			time_ns = pBlock->GetTime(_cluster);
+			frameCounter = 0;
 		}
-		pBlock  = pBlockEntry->GetBlock();
-		trackNum = pBlock->GetTrackNumber();
-		frameCount = pBlock->GetFrameCount();
-		time_ns = pBlock->GetTime(_cluster);
-		frameCounter = 0;
-	}
 
-	const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
-	const long size = theFrame.len;
+		const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
+		const uint32 size = theFrame.len;
 
-	if (size > sizeof(frame)) {
-		if (frame)
-			delete[] frame;
-		frame = new unsigned char[size];
-		if (!frame)
-			return;
-	}
-
-	if (trackNum == videoTrack) {
-		warning("MKVDecoder::readNextPacket(): video track");
-
-		theFrame.Read(_reader, frame);
-		_videoTrack->decodeFrame(frame, size);
-	} else if (trackNum == audioTrack) {
-		warning("MKVDecoder::readNextPacket(): audio track");
+		if (size > sizeof(frame)) {
+			if (frame)
+				delete[] frame;
+			frame = new unsigned char[size];
+			if (!frame)
+				return;
+		}
 
-		if (size > 0) {
+		if (trackNum == videoTrack) {
 			theFrame.Read(_reader, frame);
-			_audioTrack->decodeSamples(frame, size);
+			_videoTrack->decodeFrame(frame, size);
+		} else if (trackNum == audioTrack) {
+			if (size > 0) {
+				theFrame.Read(_reader, frame);
+				queueAudio(size);
+			}
+		} else {
+			warning("Unprocessed track %d", trackNum);
 		}
-	} else {
-		warning("Unprocessed track %d", trackNum);
+		++frameCounter;
 	}
-	++frameCounter;
 }
 
 MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack) {
@@ -366,12 +364,17 @@ bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 	// Let's decode an image frame!
 	vpx_codec_iter_t  iter = NULL;
 	vpx_image_t      *img;
+	Graphics::Surface tmp;
+	tmp.create(getWidth(), getHeight(), getPixelFormat());
+
 	/* Get frame data */
 	while ((img = vpx_codec_get_frame(_codec, &iter))) {
 		if (img->fmt != VPX_IMG_FMT_I420)
 			error("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
 
-		YUVToRGBMan.convert420(&_displaySurface, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
+		YUVToRGBMan.convert420(&tmp, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
+		_displayQueue.push(tmp);
+		//warning("Size of _displayQueue is now %d", _displayQueue.size());
 	}
 	return false;
 }
@@ -434,14 +437,14 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	if(r)
 		error("vorbis_block_init, error: %d", r);
 
+	_audStream = Audio::makeQueuingAudioStream(_vorbisInfo.rate, _vorbisInfo.channels != 1);
 	_endOfAudio = false;
 }
 
 MKVDecoder::VorbisAudioTrack::~VorbisAudioTrack() {
-	//vorbis_dsp_clear(&_vorbisDSP);
-	//vorbis_block_clear(&vorbisBlock);
-	//delete _audStream;
-	//free(_audioBuffer);
+	vorbis_dsp_clear(&_vorbisDSP);
+	vorbis_block_clear(&_vorbisBlock);
+	delete _audStream;
 }
 
 Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
@@ -449,99 +452,44 @@ Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
 }
 
 bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
-	//return true;
-
-	ogg_packet packet;
-	packet.packet = frame;
-	packet.bytes = size;
-	packet.b_o_s = false;
-	packet.e_o_s = false;
-	packet.packetno = ++oggPacket.packetno;
-	packet.granulepos = -1;
-
-	warning("oggPacket bytes : %d packno %d\n", oggPacket.bytes, oggPacket.packetno);
-
-	if(!vorbis_synthesis(&_vorbisBlock, &packet) ) {
-		if (vorbis_synthesis_blockin(&vorbisDspState, &_vorbisBlock))
-			warning("Vorbis Synthesis block in error");
-
-	} else {
-		warning("Vorbis Synthesis error");
-	}
-
 	float **pcm;
 
-	int numSamples = vorbis_synthesis_pcmout(&vorbisDspState, &pcm);
+	int numSamples = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
 
 	if (numSamples > 0) {
-		int word = 2;
-		int sgned = 1;
-		int i, j;
-		long bytespersample= audioChannels * word;
-		//vorbis_fpu_control fpu;
+		uint32 channels = _vorbisInfo.channels;
+		long bytespersample = _vorbisInfo.channels * 2;
 
-		char *buffer = new char[bytespersample * numSamples];
+		char *buffer = (char*)malloc(bytespersample * numSamples * sizeof(char));
 		if (!buffer)
 			error("MKVDecoder::readNextPacket(): buffer allocation failed");
 
-		/* a tight loop to pack each size */
-		{
-			int val;
-			if (word == 1) {
-				int off = (sgned ? 0 : 128);
-				//vorbis_fpu_setround(&fpu);
-				for (j = 0; j < numSamples; j++)
-					for (i = 0;i < audioChannels; i++) {
-						val = (int)(pcm[i][j] * 128.f);
-						val = CLIP(val, -128, 127);
-
-						*buffer++ = val + off;
-					}
-				//vorbis_fpu_restore(fpu);
-			} else {
-				int off = (sgned ? 0 : 32768);
-
-				if (sgned) {
-					//vorbis_fpu_setround(&fpu);
-					for (i = 0; i < audioChannels; i++) { /* It's faster in this order */
-						float *src = pcm[i];
-						short *dest = ((short *)buffer) + i;
-						for (j = 0; j < numSamples; j++) {
-							val = (int)(src[j] * 32768.f);
-							val = CLIP(val, -32768, 32767);
-
-							*dest = val;
-							dest += audioChannels;
-						}
-					}
-					//vorbis_fpu_restore(fpu);
-				} else { // unsigned
-					//vorbis_fpu_setround(&fpu);
-
-					for (i = 0; i < audioChannels; i++) {
-						float *src = pcm[i];
-						short *dest = ((short *)buffer) + i;
-						for (j = 0; j < numSamples; j++) {
-							val = (int)(src[j] * 32768.f);
-							val = CLIP(val, -32768, 32767);
-
-							*dest = val + off;
-							dest += audioChannels;
-						}
-					}
-					//vorbis_fpu_restore(fpu);
-				}
+
+		for (int i = 0; i < channels; i++) { /* It's faster in this order */
+			float *src = pcm[i];
+			short *dest = ((short *)buffer) + i;
+			for (int j = 0; j < numSamples; j++) {
+				int val = rint(src[j] * 32768.f);
+				val = CLIP(val, -32768, 32767);
+				*dest = (short)val;
+				dest += channels;
 			}
 		}
 
-		vorbis_synthesis_read(&vorbisDspState, numSamples);
-		audioBufferLen = bytespersample * numSamples;
-		if (!movieSoundPlaying) {
-			warning("** starting sound **");
-			movieSoundPlaying = true;
-		}
+		byte flags = Audio::FLAG_16BITS;
+
+		if (_audStream->isStereo())
+			flags |= Audio::FLAG_STEREO;
+
+#ifdef SCUMM_LITTLE_ENDIAN
+		flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+		int64 audioBufferLen = bytespersample * numSamples;
+		_audStream->queueBuffer((byte *)buffer, audioBufferLen, DisposeAfterUse::YES, flags);
+		vorbis_synthesis_read(&_vorbisDSP, numSamples);
+		return true;
 	}
-	return true;
+	return false;
 }
 
 bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
@@ -549,21 +497,36 @@ bool MKVDecoder::VorbisAudioTrack::hasAudio() const {
 }
 
 bool MKVDecoder::VorbisAudioTrack::needsAudio() const {
-	// TODO: 5 is very arbitrary. We probably should do something like QuickTime does.
-	return !_endOfAudio && _audStream->numQueuedStreams() < 5;
+	// TODO: 10 is very arbitrary. We probably should do something like QuickTime does.
+	return _audStream->numQueuedStreams() < 10;
 }
 
-void MKVDecoder::VorbisAudioTrack::synthesizePacket(ogg_packet &_oggPacket) {
-	warning("in synthesizePacket");
-	if (vorbis_synthesis(&_vorbisBlock, &_oggPacket) == 0) // test for success
-		vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock);
+bool MKVDecoder::VorbisAudioTrack::synthesizePacket(byte *frame, long size) {
+	bool res = true;
+	oggPacket.packet = frame;
+	oggPacket.bytes = size;
+	oggPacket.b_o_s = false;
+	oggPacket.packetno++;
+	oggPacket.granulepos = -1;
+	if (vorbis_synthesis(&_vorbisBlock, &oggPacket) == 0) { // test for success
+		if(vorbis_synthesis_blockin(&_vorbisDSP, &_vorbisBlock)) {
+			res = false;
+			warning("vorbis_synthesis_blockin failed");
+		}
+	}
+	else {
+		res = false;
+		warning("Vorbis synthesis failed");
+	}
+	return res;
 }
 
-bool MKVDecoder::queueAudio() {
-	if (!_hasAudio)
-		return false;
-
+bool MKVDecoder::queueAudio(long size) {
 	bool queuedAudio = false;
+
+	if (_audioTrack->synthesizePacket(frame, size) && _audioTrack->decodeSamples(frame, size))
+		queuedAudio = true;
+
 	return queuedAudio;
 }
 } // End of namespace Video
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index dd26eea4671..5d1432a64e3 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -28,6 +28,7 @@
 #define VIDEO_MKV_DECODER_H
 
 #include "common/rational.h"
+#include "common/queue.h"
 #include "video/video_decoder.h"
 #include "audio/mixer.h"
 #include "graphics/surface.h"
@@ -94,25 +95,36 @@ private:
 		VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack);
 		~VPXVideoTrack();
 
-		bool endOfTrack() const { return _endOfVideo; }
+		bool endOfTrack() const {
+			if(_endOfVideo && _displayQueue.size())
+				return false;
+		 	return _endOfVideo;
+		 }
 		uint16 getWidth() const { return _displaySurface.w; }
 		uint16 getHeight() const { return _displaySurface.h; }
 		Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; }
 		int getCurFrame() const { return _curFrame; }
 		uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
-		const Graphics::Surface *decodeNextFrame() { return &_displaySurface; }
+		const Graphics::Surface *decodeNextFrame() {
+			if(_displayQueue.size())
+				_surface = _displayQueue.pop();
+			warning("Size of display Queue is %d", _displayQueue.size());
+			return &_surface;
+		}
 
 		bool decodeFrame(byte *frame, long size);
 		void setEndOfVideo() { _endOfVideo = true; }
 
 	private:
 		int _curFrame;
+		int _doneOnce = 0;
 		bool _endOfVideo;
 		Common::Rational _frameRate;
 		double _nextFrameStartTime;
 
 		Graphics::Surface _surface;
 		Graphics::Surface _displaySurface;
+		Common::Queue<Graphics::Surface> _displayQueue;
 
 		vpx_codec_ctx_t *_codec = nullptr;
 	};
@@ -125,7 +137,7 @@ private:
 		bool decodeSamples(byte *frame, long size);
 		bool hasAudio() const;
 		bool needsAudio() const;
-		void synthesizePacket(ogg_packet &oggPacket);
+		bool synthesizePacket(byte *frame, long size);
 		void setEndOfAudio() { _endOfAudio = true; }
 
 	protected:
@@ -147,7 +159,7 @@ private:
 		ogg_packet oggPacket;
 	};
 
-	bool queueAudio();
+	bool queueAudio(long size);
 
 	Common::SeekableReadStream *_fileStream;
 


Commit: b783a064e46006e720f215dfc5285f87330c840f
    https://github.com/scummvm/scummvm/commit/b783a064e46006e720f215dfc5285f87330c840f
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
JANITORIAL: Cleanup MKV Decoder

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 840f1962d76..0108c6fe45e 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -77,7 +77,7 @@ int MkvReader::Read(long long position, long length, unsigned char *buffer) {
 		return -1;
 
 	_stream->seek(position);
-	if (_stream->read(buffer, length) < length)
+	if (_stream->read(buffer, length) < (uint32)length)
 		return -1;
 
 	return 0;
@@ -148,16 +148,16 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	}
 
 	ret = pSegment->Load();
-	if (ret < 0) {
+	if (ret) {
 		error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
 	}
 
 	pTracks = pSegment->GetTracks();
 
-	unsigned long i = 0;
+	uint32 i = 0;
 	const unsigned long j = pTracks->GetTracksCount();
 
-	debug(1, "Number of tracks: %ld", j);
+	debug(1, "Number of tracks: %d", j);
 
 	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
 	videoTrack = -1;
@@ -176,12 +176,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			_videoTrack = new VPXVideoTrack(getDefaultHighColorFormat(), pTrack);
 
 			addTrack(_videoTrack);
-			//setRate(_videoTrack->getFrameRate());
-
-#if 0
-			if (rate > 0)
-				Init_Special_Timer(rate); // TODO
-#endif
 		}
 
 		if (trackType == mkvparser::Track::kAudio && audioTrack < 0) {
@@ -190,7 +184,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
 			size_t audioHeaderSize;
-			byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
+			byte *audioHeader = const_cast<byte *>(pAudioTrack->GetCodecPrivate(audioHeaderSize));
 
 			if (audioHeaderSize < 1) {
 				warning("Strange audio track in movie.");
@@ -230,14 +224,6 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	_cluster = pSegment->GetFirst();
 
-#if 0
-	movieIsPlaying = playing;
-	movieIsEnding = 0;
-#endif
-
-	//const long long timeCode = _cluster->GetTimeCode();
-	long long time_ns = _cluster->GetTime();
-
 	if (_cluster->GetFirst(pBlockEntry))
 		error("_cluster::GetFirst() failed");
 
@@ -252,9 +238,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	pBlock = pBlockEntry->GetBlock();
 	trackNum = pBlock->GetTrackNumber();
-	tn = static_cast<unsigned long>(trackNum);
 	frameCount = pBlock->GetFrameCount();
-	time_ns = pBlock->GetTime(_cluster);
 
 	return true;
 }
@@ -275,7 +259,8 @@ void MKVDecoder::readNextPacket() {
 		return;
 	}
 
-	while(_audioTrack->needsAudio()){
+	// ensure we have enough buffers in the stream
+	while (_audioTrack->needsAudio()) {
 		if (frameCounter >= frameCount) {
 		_cluster->GetNext(pBlockEntry, pBlockEntry);
 
@@ -294,7 +279,6 @@ void MKVDecoder::readNextPacket() {
 			pBlock  = pBlockEntry->GetBlock();
 			trackNum = pBlock->GetTrackNumber();
 			frameCount = pBlock->GetFrameCount();
-			time_ns = pBlock->GetTime(_cluster);
 			frameCounter = 0;
 		}
 
@@ -318,7 +302,7 @@ void MKVDecoder::readNextPacket() {
 				queueAudio(size);
 			}
 		} else {
-			warning("Unprocessed track %d", trackNum);
+			warning("Unprocessed track %lld", trackNum);
 		}
 		++frameCounter;
 	}
@@ -329,14 +313,11 @@ MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, co
 
 	const long long width = pVideoTrack->GetWidth();
 	const long long height = pVideoTrack->GetHeight();
-	const double rate = pVideoTrack->GetFrameRate();
 
-	warning("VideoTrack: %lld x %lld @ %f fps", width, height, rate);
+	debug(1, "VideoTrack: %lld x %lld", width, height);
 
 	_displaySurface.create(width, height, format);
 
-	_frameRate = 10; // FIXME
-
 	_endOfVideo = false;
 	_nextFrameStartTime = 0.0;
 	_curFrame = -1;
@@ -350,9 +331,26 @@ MKVDecoder::VPXVideoTrack::VPXVideoTrack(const Graphics::PixelFormat &format, co
 
 MKVDecoder::VPXVideoTrack::~VPXVideoTrack() {
 	_displaySurface.free();
+	// The last frame is not freed in decodeNextFrame(), clear it hear instead.
+	_surface.free();
 	delete _codec;
 }
 
+bool MKVDecoder::VPXVideoTrack::endOfTrack() const {
+	if (_endOfVideo && _displayQueue.size())
+		return false;
+	return _endOfVideo;
+}
+
+const Graphics::Surface *MKVDecoder::VPXVideoTrack::decodeNextFrame() {
+	if (_displayQueue.size()) {
+		if (_surface.getPixels())
+			_surface.free();
+		_surface = _displayQueue.pop();
+	}
+	return &_surface;
+}
+
 bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 
 	//warning("In within decodeFrame");
@@ -362,8 +360,8 @@ bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 		error("Failed to decode frame");
 
 	// Let's decode an image frame!
-	vpx_codec_iter_t  iter = NULL;
-	vpx_image_t      *img;
+	vpx_codec_iter_t iter = NULL;
+	vpx_image_t *img;
 	Graphics::Surface tmp;
 	tmp.create(getWidth(), getHeight(), getPixelFormat());
 
@@ -374,7 +372,6 @@ bool MKVDecoder::VPXVideoTrack::decodeFrame(byte *frame, long size) {
 
 		YUVToRGBMan.convert420(&tmp, Graphics::YUVToRGBManager::kScaleITU, img->planes[0], img->planes[1], img->planes[2], img->d_w, img->d_h, img->stride[0], img->stride[1]);
 		_displayQueue.push(tmp);
-		//warning("Size of _displayQueue is now %d", _displayQueue.size());
 	}
 	return false;
 }
@@ -390,10 +387,10 @@ MKVDecoder::VorbisAudioTrack::VorbisAudioTrack(const mkvparser::Track *const pTr
 	const long long audioBitDepth = pAudioTrack->GetBitDepth();
 	const double audioSampleRate = pAudioTrack->GetSamplingRate();
 
-	warning("audioChannels %d audioBitDepth %d audioSamplerate %f", audioChannels, audioBitDepth, audioSampleRate);
+	debug(1, "audioChannels %lld audioBitDepth %lld audioSamplerate %f", audioChannels, audioBitDepth, audioSampleRate);
 
 	size_t audioHeaderSize;
-	byte *audioHeader = (byte *)pAudioTrack->GetCodecPrivate(audioHeaderSize);
+	byte *audioHeader = const_cast<byte *>(pAudioTrack->GetCodecPrivate(audioHeaderSize));
 	byte *p = audioHeader;
 
 	uint count = *p++ + 1;
@@ -454,21 +451,20 @@ Audio::AudioStream *MKVDecoder::VorbisAudioTrack::getAudioStream() const {
 bool MKVDecoder::VorbisAudioTrack::decodeSamples(byte *frame, long size) {
 	float **pcm;
 
-	int numSamples = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
+	int32 numSamples = vorbis_synthesis_pcmout(&_vorbisDSP, &pcm);
 
 	if (numSamples > 0) {
-		uint32 channels = _vorbisInfo.channels;
+		int32 channels = _vorbisInfo.channels;
 		long bytespersample = _vorbisInfo.channels * 2;
 
 		char *buffer = (char*)malloc(bytespersample * numSamples * sizeof(char));
 		if (!buffer)
 			error("MKVDecoder::readNextPacket(): buffer allocation failed");
 
-
-		for (int i = 0; i < channels; i++) { /* It's faster in this order */
+		for (int32 i = 0; i < channels; i++) { /* It's faster in this order */
 			float *src = pcm[i];
 			short *dest = ((short *)buffer) + i;
-			for (int j = 0; j < numSamples; j++) {
+			for (int32 j = 0; j < numSamples; j++) {
 				int val = rint(src[j] * 32768.f);
 				val = CLIP(val, -32768, 32767);
 				*dest = (short)val;
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 5d1432a64e3..5984d944b89 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -27,14 +27,11 @@
 #ifndef VIDEO_MKV_DECODER_H
 #define VIDEO_MKV_DECODER_H
 
-#include "common/rational.h"
 #include "common/queue.h"
 #include "video/video_decoder.h"
 #include "audio/mixer.h"
 #include "graphics/surface.h"
 
-#include <theora/theoradec.h>
-
 #ifdef USE_TREMOR
 #include <tremor/ivorbiscodec.h>
 #else
@@ -95,31 +92,19 @@ private:
 		VPXVideoTrack(const Graphics::PixelFormat &format, const mkvparser::Track *const pTrack);
 		~VPXVideoTrack();
 
-		bool endOfTrack() const {
-			if(_endOfVideo && _displayQueue.size())
-				return false;
-		 	return _endOfVideo;
-		 }
+		bool endOfTrack() const;
 		uint16 getWidth() const { return _displaySurface.w; }
 		uint16 getHeight() const { return _displaySurface.h; }
 		Graphics::PixelFormat getPixelFormat() const { return _displaySurface.format; }
 		int getCurFrame() const { return _curFrame; }
 		uint32 getNextFrameStartTime() const { return (uint32)(_nextFrameStartTime * 1000); }
-		const Graphics::Surface *decodeNextFrame() {
-			if(_displayQueue.size())
-				_surface = _displayQueue.pop();
-			warning("Size of display Queue is %d", _displayQueue.size());
-			return &_surface;
-		}
-
+		const Graphics::Surface *decodeNextFrame();
 		bool decodeFrame(byte *frame, long size);
 		void setEndOfVideo() { _endOfVideo = true; }
 
 	private:
 		int _curFrame;
-		int _doneOnce = 0;
 		bool _endOfVideo;
-		Common::Rational _frameRate;
 		double _nextFrameStartTime;
 
 		Graphics::Surface _surface;
@@ -144,10 +129,6 @@ private:
 		Audio::AudioStream *getAudioStream() const;
 
 	private:
-		// single audio fragment audio buffering
-		int _audioBufferFill;
-		ogg_int16_t *_audioBuffer;
-
 		Audio::QueuingAudioStream *_audStream;
 
 		vorbis_block _vorbisBlock;
@@ -183,10 +164,7 @@ private:
 
 	const mkvparser::Block *pBlock;
 	long long trackNum;
-	unsigned long tn;
-	long long trackType;
 	int frameCount;
-	long long time_ns;
 };
 
 } // End of namespace Video


Commit: 492002291a390dc5269497fb096350541b5dc5e4
    https://github.com/scummvm/scummvm/commit/492002291a390dc5269497fb096350541b5dc5e4
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Remove old imported code

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 28c73c7e2cd..c7a02114e00 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -32,304 +32,15 @@
 
 namespace Sludge {
 
-// sound_openal.cpp
-void playMovieStream(int a);
-#if 0
-int initMovieSound(int f, ALenum format, int audioChannels, ALuint samplerate,
-		ALuint(*callback)(void *userdata, ALubyte *data, ALuint bytes));
-#endif
-
 MovieStates movieIsPlaying = kMovieNothing;
-
 int movieIsEnding = 0;
-
 float movieAspect = 1.6F;
-#if 0
-typedef struct audioBuffers {
-	char *buffer;
-	uint size;
-	audioBuffers *next;
-	Uint32 time_ms;
-}audioBuffers;
-
-typedef struct audioQueue {
-	audioBuffers *first, *last;
-	int size;
-	SDL_mutex *mutex;
-	SDL_cond *cond;
-}audioQueue;
-
-audioQueue audioQ;
-
-uint32 movieStartTick, movieCurrentTime;
-
-int64 audioNsPerByte;
-int64 audioNsPlayed;
-int64 audioNsBuffered;
-int64 audioBufferLen;
-bool movieSoundPlaying = false;
-int movieAudioIndex;
-GLuint yTextureName = 0;
-GLuint uTextureName = 0;
-GLuint vTextureName = 0;
-
-typedef struct videoBuffers {
-	GLubyte *ytex;
-	GLubyte *utex;
-	GLubyte *vtex;
-	videoBuffers *next;
-	GLsizei w, h;
-	Uint32 time_ms;
-}videoBuffers;
-
-typedef struct videoQueue {
-	videoBuffers *first, *last;
-	int size;
-	SDL_mutex *mutex;
-	SDL_cond *cond;
-}videoQueue;
-
-videoQueue videoQ;
-
-void audio_queue_init(audioQueue *q) {
-	memset(q, 0, sizeof(audioQueue));
-
-	q->mutex = SDL_CreateMutex();
-	q->cond = SDL_CreateCond();
-
-}
-int audio_queue_put(audioQueue *q, char *buffer, uint size, int64 time_ms) {
-
-	audioBuffers *audioBuf = new audioBuffers;
-	if (!audioBuf)
-	return -1;
-	audioBuf->buffer = buffer;
-	audioBuf->next = NULL;
-	audioBuf->size = size;
-	audioBuf->time_ms = time_ms;
-
-	SDL_LockMutex(q->mutex);
-
-	if (!q->last)
-	q->first = audioBuf;
-	else
-	q->last->next = audioBuf;
-	q->last = audioBuf;
-	q->size ++;
-	SDL_CondSignal(q->cond);
-
-	SDL_UnlockMutex(q->mutex);
-
-	return 0;
-}
-inline static int audio_queue_get(audioQueue *q, char **buffer) {
-	int ret = 0;
-
-	audioBuffers *audioBuf;
-
-	SDL_LockMutex(q->mutex);
-
-	audioBuf = q->first;
-	if (audioBuf) {
-		// Synch video timer to audio
-		Uint32 tick = SDL_GetTicks() + 100;
-		if (ABS((long int)((tick - movieStartTick) - (audioBuf->time_ms))) > 300) {
-			movieStartTick = tick - audioBuf->time_ms;
-		}
-
-		q->first = audioBuf->next;
-		if (!q->first)
-		q->last = NULL;
-		q->size--;
-		*buffer = audioBuf->buffer;
-		ret = audioBuf->size;
-		delete audioBuf;
-	}
-
-	SDL_UnlockMutex(q->mutex);
-
-	return ret;
-}
-
-void video_queue_init(videoQueue *q) {
-	memset(q, 0, sizeof(videoQueue));
-	q->mutex = SDL_CreateMutex();
-	q->cond = SDL_CreateCond();
-}
-int video_queue_put(videoQueue *q, GLubyte *ytex,
-		GLubyte *utex,
-		GLubyte *vtex,
-		GLsizei w, GLsizei h,
-		int64 time_ms) {
-
-	videoBuffers *videoBuf = new videoBuffers;
-	if (!videoBuf)
-	return -1;
-	videoBuf->ytex = ytex;
-	videoBuf->utex = utex;
-	videoBuf->vtex = vtex;
-	videoBuf->next = NULL;
-	videoBuf->w = w;
-	videoBuf->h = h;
-	videoBuf->time_ms = time_ms;
-
-	SDL_LockMutex(q->mutex);
-
-	if (!q->last)
-	q->first = videoBuf;
-	else
-	q->last->next = videoBuf;
-	q->last = videoBuf;
-	q->size ++;
-	SDL_CondSignal(q->cond);
-
-	SDL_UnlockMutex(q->mutex);
-	return 0;
-}
-inline static int video_queue_get(videoQueue *q,
-		GLubyte **ytex,
-		GLubyte **utex,
-		GLubyte **vtex,
-		GLsizei *w, GLsizei *h) {
-	videoBuffers *videoBuf;
-	int ret = 0;
-
-	SDL_LockMutex(q->mutex);
-
-	videoBuf = q->first;
-	if (videoBuf) {
-		q->first = videoBuf->next;
-		if (!q->first)
-		q->last = NULL;
-		q->size--;
-		*ytex = videoBuf->ytex;
-		*utex = videoBuf->utex;
-		*vtex = videoBuf->vtex;
-		*w = videoBuf->w;
-		*h = videoBuf->h;
-		ret = 1;
-		delete videoBuf;
-	}
-
-	SDL_UnlockMutex(q->mutex);
-
-	return ret;
-}
-
-#if 0
-static void die_codec(vpx_codec_ctx_t *ctx, const char *s) {
-	//const char *detail = vpx_codec_error_detail(ctx);
-	fatal(s, vpx_codec_error(ctx));
-}
-#endif
-
-void setMovieViewport() {
-	float realAspect = (float) realWinWidth / realWinHeight;
-
-	int vpHeight, vpWidth, vpOffsetX, vpOffsetY;
-	if (realAspect > movieAspect) {
-		vpHeight = realWinHeight;
-		vpWidth = (int)(realWinHeight * movieAspect);
-		vpOffsetY = 0;
-		vpOffsetX = (realWinWidth - vpWidth) / 2;
-	} else {
-		vpWidth = realWinWidth;
-		vpHeight = (int)((float) realWinWidth / movieAspect);
-		vpOffsetY = (realWinHeight - vpHeight) / 2;
-		vpOffsetX = 0;
-	}
-#if 0
-	glViewport(vpOffsetX, vpOffsetY, vpWidth, vpHeight);
-#endif
-	const GLfloat bPMVMatrix[] = {
-		2.0f / 640.f, .0, .0, .0,
-		.0, -2.0f / 400.f, .0, .0,
-		.0, .0, 1.0f, .0,
-		-1.0, 1.0f, .0, 1.0f
-
-	};
-	for (int i = 0; i < 16; i++) {
-		aPMVMatrix[i] = bPMVMatrix[i];
-	}
-}
-
-static uint64_t xiph_lace_value(byte **np) {
-	uint64_t lace;
-	uint64_t value;
-	byte *p = *np;
-
-	lace = *p++;
-	value = lace;
-	while (lace == 255) {
-		lace = *p++;
-		value += lace;
-	}
-
-	*np = p;
-
-	return value;
-}
-
-vorbis_dsp_state vorbisDspState;
-int64 audioChannels;
-
-bool fakeAudio = false;
-
-// send audio to audio device...
-ALuint feedAudio(void *userdata, ALubyte *data, ALuint length) {
-	static char *buffer = NULL;
-	static uint bufOffset = 0;
-	static uint bufSize = 0;
-
-	ALuint got = 0;
-	int bufLen;
-
-	if (! buffer) {
-		bufSize = audio_queue_get(&audioQ, &buffer);
-		bufOffset = 0;
-		if (bufSize <= 0) {
-			bufSize = 0;
-			buffer = NULL;
-			if (! got) {
-				got = audioChannels * 2;
-				memset(data, 0, got);
-				fprintf(stderr, "Faking audio...\n");
-				fakeAudio = true;
-			}
-//				SDL_CondSignal(audioQ.cond);
-			return got;
-		}
-	}
-
-	fakeAudio = false;
-
-	if (length > bufSize - bufOffset)
-	bufLen = bufSize - bufOffset;
-	else
-	bufLen = length;
-
-	memcpy(data, buffer + bufOffset, bufLen);
-
-	bufOffset += bufLen;
-	length -= bufLen;
-	got += bufLen;
-
-	if (bufSize <= bufOffset) {
-		buffer = NULL;
-		delete [] buffer;
-	}
-//	fprintf (stderr, "Sending %d bytes of audio.\n", got);
-
-	return got;
-}
-#endif
 
 int playMovie(int fileNumber) {
 	uint fsize;
 	if (!(fsize = g_sludge->_resMan->openFileFromNum(fileNumber)))
 		return fatal("playMovie(): Can't open movie");
 
-	warning("In play movie");
 	Video::MKVDecoder decoder;
 
 	Common::SeekableReadStream *stream = g_sludge->_resMan->getData();
@@ -358,9 +69,6 @@ int playMovie(int fileNumber) {
 				if (s) {
 					// Transfer the next frame
 					assert(s->format.bytesPerPixel == 4);
-
-					//warning("Copy rect to screen");
-
 					g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, MIN<uint32>(s->w, g_sludge->_gfxMan->getWinWidth()), MIN<uint32>(s->h, g_sludge->_gfxMan->getWinHeight()));
 					g_system->updateScreen();
 				} else {
@@ -374,640 +82,6 @@ int playMovie(int fileNumber) {
 
 	g_sludge->_resMan->finishAccess();
 	setResourceForFatal(-1);
-
-#if 0
-	if (specialSettings & SPECIAL_SILENT)
-		return 0;
-
-	if (movieIsPlaying) return 0;
-
-	movieSoundPlaying = false;
-
-	vpx_codec_ctx_t codec;
-
-	float pausefade = 1.0;
-
-	using namespace mkvparser;
-
-	MkvReader reader;
-
-	if (reader.Open(fileNumber)) {
-		warning(ERROR_MOVIE_ODDNESS);
-		return 0;
-	}
-
-	int64 pos = 0;
-
-	EBMLHeader ebmlHeader;
-
-	ebmlHeader.Parse(&reader, pos);
-
-	mkvparser::Segment *pSegment;
-
-	int64 ret = mkvparser::Segment::CreateInstance(&reader, pos, pSegment);
-	if (ret) {
-		fatal("Movie error: Segment::CreateInstance() failed.\n");
-	}
-
-	ret = pSegment->Load();
-	if (ret < 0) {
-		fatal("Movie error: Segment::Load() failed.\n");
-	}
-
-	//const SegmentInfo* const pSegmentInfo = pSegment->GetInfo();
-	//const int64 timeCodeScale = pSegmentInfo->GetTimeCodeScale();
-	//const int64 duration_ns = pSegmentInfo->GetDuration();
-	//const char* const pTitle = pSegmentInfo->GetTitleAsUTF8();
-	//const char* const pMuxingApp = pSegmentInfo->GetMuxingAppAsUTF8();
-	//const char* const pWritingApp = pSegmentInfo->GetWritingAppAsUTF8();
-
-	const mkvparser::Tracks *pTracks = pSegment->GetTracks();
-
-	uint32 i = 0;
-	const uint32 j = pTracks->GetTracksCount();
-
-	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
-	int videoTrack = -1;
-	int audioTrack = -1;
-	int64 audioBitDepth;
-	double audioSampleRate;
-	ogg_packet oggPacket;
-	vorbis_info vorbisInfo;
-	vorbis_comment vorbisComment;
-	vorbis_block vorbisBlock;
-
-	while (i != j) {
-		const Track *const pTrack = pTracks->GetTrackByIndex(i++);
-
-		if (pTrack == NULL)
-		continue;
-
-		const int64 trackType = pTrack->GetType();
-		//const unsigned int64 trackUid = pTrack->GetUid();
-		//const char* pTrackName = pTrack->GetNameAsUTF8();
-
-		if (trackType == VIDEO_TRACK && videoTrack < 0) {
-			videoTrack = pTrack->GetNumber();
-			const VideoTrack *const pVideoTrack =
-			static_cast<const VideoTrack *>(pTrack);
-
-			const int64 width = pVideoTrack->GetWidth();
-			const int64 height = pVideoTrack->GetHeight();
-
-			const double rate = pVideoTrack->GetFrameRate();
-
-			if (rate > 0)
-			Init_Special_Timer(rate);
-
-			movieAspect = (float)width / height;
-		}
-
-		if (trackType == AUDIO_TRACK && audioTrack < 0) {
-			audioTrack = pTrack->GetNumber();
-			const AudioTrack *const pAudioTrack =
-			static_cast<const AudioTrack *>(pTrack);
-
-			audioChannels = pAudioTrack->GetChannels();
-			audioBitDepth = pAudioTrack->GetBitDepth();
-			audioSampleRate = pAudioTrack->GetSamplingRate();
-
-			uint audioHeaderSize;
-			const byte *audioHeader = pAudioTrack->GetCodecPrivate(audioHeaderSize);
-
-			if (audioHeaderSize < 1) {
-				warning("Strange audio track in movie.");
-				audioTrack = -1;
-				continue;
-			}
-
-			byte *p = (byte *)audioHeader;
-
-			uint count = *p++ + 1;
-			if (count != 3) {
-				warning("Strange audio track in movie.");
-				audioTrack = -1;
-				continue;
-			}
-
-			uint64_t sizes[3], total;
-
-			int i = 0;
-			total = 0;
-			while (--count) {
-				sizes[i] = xiph_lace_value(&p);
-				total += sizes[i];
-				i += 1;
-			}
-			sizes[i] = audioHeaderSize - total - (p - audioHeader);
-
-			// initialize vorbis
-			vorbis_info_init(&vorbisInfo);
-			vorbis_comment_init(&vorbisComment);
-			memset(&vorbisDspState, 0, sizeof(vorbisDspState));
-			memset(&vorbisBlock, 0, sizeof(vorbisBlock));
-
-			oggPacket.e_o_s = false;
-			oggPacket.granulepos = 0;
-			oggPacket.packetno = 0;
-			int r;
-			for (int i = 0; i < 3; i++) {
-				oggPacket.packet = p;
-				oggPacket.bytes = sizes[i];
-				oggPacket.b_o_s = oggPacket.packetno == 0;
-				r = vorbis_synthesis_headerin(&vorbisInfo, &vorbisComment, &oggPacket);
-				if (r)
-				fprintf(stderr, "vorbis_synthesis_headerin failed, error: %d", r);
-				oggPacket.packetno++;
-				p += sizes[i];
-			}
-
-			r = vorbis_synthesis_init(&vorbisDspState, &vorbisInfo);
-			if (r)
-			fprintf(stderr, "vorbis_synthesis_init failed, error: %d", r);
-			r = vorbis_block_init(&vorbisDspState, &vorbisBlock);
-			if (r)
-			fprintf(stderr, "vorbis_block_init failed, error: %d", r);
-
-			ALenum audioFormat = alureGetSampleFormat(audioChannels, 16, 0);
-			movieAudioIndex = initMovieSound(fileNumber, audioFormat, audioChannels, (ALuint) audioSampleRate, feedAudio);
-
-			fprintf(stderr, "Movie sound inited.\n");
-			audio_queue_init(&audioQ);
-			audioNsPerByte = (1000000000 / audioSampleRate) / (audioChannels * 2);
-			audioNsBuffered = 0;
-			audioBufferLen = audioChannels * audioSampleRate;
-		}
-	}
-
-	if (videoTrack < 0)
-	fatal("Movie error: No video in movie file.");
-
-	if (audioTrack < 0)
-	fatal("Movie error: No sound found.");
-
-	video_queue_init(&videoQ);
-
-	const unsigned long clusterCount = pSegment->GetCount();
-
-	if (clusterCount == 0) {
-		fatal("Movie error: Segment has no clusters.\n");
-	}
-
-	/* Initialize video codec */
-	if (vpx_codec_dec_init(&codec, interface, NULL, 0))
-	die_codec(&codec, "Failed to initialize decoder for movie.");
-
-	byte *frame = new byte[256 * 1024];
-	if (! checkNew(frame)) return false;
-
-	const mkvparser::Cluster *pCluster = pSegment->GetFirst();
-
-	setMovieViewport();
-
-	movieIsPlaying = kMoviePlaying;
-	movieIsEnding = 0;
-
-	glDepthMask(GL_FALSE);
-	glDisable(GL_DEPTH_TEST);
-
-	//const int64 timeCode = pCluster->GetTimeCode();
-	int64 time_ns = pCluster->GetTime();
-
-	const BlockEntry *pBlockEntry = pCluster->GetFirst();
-
-	if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-		pCluster = pSegment->GetNext(pCluster);
-		if ((pCluster == NULL) || pCluster->EOS()) {
-			fatal("Error: No movie found in the movie file.");
-		}
-		pBlockEntry = pCluster->GetFirst();
-	}
-	const Block *pBlock = pBlockEntry->GetBlock();
-	int64 trackNum = pBlock->GetTrackNumber();
-	unsigned long tn = static_cast<unsigned long>(trackNum);
-	const Track *pTrack = pTracks->GetTrackByNumber(tn);
-	int64 trackType = pTrack->GetType();
-	int frameCount = pBlock->GetFrameCount();
-	time_ns = pBlock->GetTime(pCluster);
-
-	const GLfloat texCoords[] = {
-		0.0, 0.0,
-		1.0, 0.0,
-		0.0, 1.0,
-		1.0, 1.0
-	};
-
-	const GLfloat vertices[] = {
-		0.0, 0.0, 0.1,
-		640.0, 0.0, 0.1,
-		0.0, 400.0, 0.1,
-		640.0, 400.0, 0.1
-	};
-
-	const GLfloat vertices1[] = {
-		7.0, 7.0, 0.1,
-		17.0, 7.0, 0.1,
-		7.0, 29.0, 0.1,
-		17.0, 29.0, 0.1
-	};
-
-	const GLfloat vertices2[] = {
-		27.0, 7.0, 0.1,
-		37.0, 7.0, 0.1,
-		27.0, 29.0, 0.1,
-		37.0, 29.0, 0.1
-	};
-
-	const GLfloat vertices3[] = {
-		5.0, 5.0, 0.1,
-		15.0, 5.0, 0.1,
-		5.0, 27.0, 0.1,
-		15.0, 27.0, 0.1
-	};
-
-	const GLfloat vertices4[] = {
-		25.0, 5.0, 0.1,
-		35.0, 5.0, 0.1,
-		25.0, 27.0, 0.1,
-		35.0, 27.0, 0.1
-	};
-
-	int frameCounter = 0;
-
-	movieStartTick = SDL_GetTicks();
-
-	while (movieIsPlaying) {
-
-		checkInput();
-		if (weAreDoneSoQuit)
-		break;
-		handleInput();
-
-		if (movieIsPlaying && (! movieIsEnding) && (videoQ.size < 100 || audioQ.size < 100)) {
-			// Decode a frame!
-
-			if ((pCluster != NULL) && !pCluster->EOS()) {
-
-				if (frameCounter >= frameCount) {
-
-					pBlockEntry = pCluster->GetNext(pBlockEntry);
-					if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-						pCluster = pSegment->GetNext(pCluster);
-						if ((pCluster == NULL) || pCluster->EOS()) {
-							goto movieHasEnded;
-						}
-						pBlockEntry = pCluster->GetFirst();
-					}
-					pBlock = pBlockEntry->GetBlock();
-					trackNum = pBlock->GetTrackNumber();
-					tn = static_cast<unsigned long>(trackNum);
-					pTrack = pTracks->GetTrackByNumber(tn);
-					trackType = pTrack->GetType();
-					frameCount = pBlock->GetFrameCount();
-					time_ns = pBlock->GetTime(pCluster);
-
-					frameCounter = 0;
-				}
-
-				const Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
-				const long size = theFrame.len;
-				//                const int64 offset = theFrame.pos;
-
-				if (size > sizeof(frame)) {
-					if (frame) delete [] frame;
-					frame = new byte[size];
-					if (! checkNew(frame)) return 0;
-				}
-				/*
-				 fprintf (stderr, "Block :%s,%s,%15lld\n",
-				 (trackType == VIDEO_TRACK) ? "V" : "A",
-				 pBlock->IsKey() ? "I" : "P",
-				 time_ns);
-				 */
-
-				if (trackNum == videoTrack) {
-
-					theFrame.Read(&reader, frame);
-
-					/* Decode the frame */
-					if (vpx_codec_decode(&codec, frame, size, NULL, 0))
-					die_codec(&codec, "Failed to decode frame");
-
-					// Let's decode an image frame!
-					vpx_codec_iter_t iter = NULL;
-					vpx_image_t *img;
-					/* Get frame data */
-					while ((img = vpx_codec_get_frame(&codec, &iter))) {
-						if (img->fmt != VPX_IMG_FMT_I420)
-						fatal("Movie error. The movie is not in I420 colour format, which is the only one I can hanlde at the moment.");
-
-						uint y;
-
-						GLubyte *ytex = NULL;
-						GLubyte *utex = NULL;
-						GLubyte *vtex = NULL;
-
-						if (! ytex) {
-							ytex = new GLubyte [img->d_w * img->d_h];
-							utex = new GLubyte [(img->d_w >> 1) * (img->d_h >> 1)];
-							vtex = new GLubyte [(img->d_w >> 1) * (img->d_h >> 1)];
-							if (!ytex || !utex || !vtex)
-							fatal(ERROR_OUT_OF_MEMORY);
-
-						}
-
-						byte *buf = img->planes[0];
-						for (y = 0; y < img->d_h; y++) {
-							memcpy(ytex + y * img->d_w, buf, img->d_w);
-							buf += img->stride[0];
-						}
-						buf = img->planes[1];
-						for (y = 0; y < img->d_h >> 1; y++) {
-							memcpy(utex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-							buf += img->stride[1];
-						}
-						buf = img->planes[2];
-						for (y = 0; y < img->d_h >> 1; y++) {
-							memcpy(vtex + y * (img->d_w >> 1), buf, img->d_w >> 1);
-							buf += img->stride[2];
-						}
-
-						video_queue_put(&videoQ, ytex, utex, vtex,
-								img->d_w, img->d_h, time_ns / 1000000);
-
-					}
-
-				} else if (trackNum == audioTrack) {
-					// Use this Audio Track
-					if (size > 0) {
-						theFrame.Read(&reader, frame);
-						oggPacket.packet = frame;
-						oggPacket.bytes = size;
-						oggPacket.b_o_s = false;
-						oggPacket.packetno++;
-						oggPacket.granulepos = -1;
-						if (! vorbis_synthesis(&vorbisBlock, &oggPacket)) {
-							if (vorbis_synthesis_blockin(&vorbisDspState, &vorbisBlock))
-							fprintf(stderr, "Vorbis Synthesis block in error.\n");
-
-						} else {
-							fprintf(stderr, "Vorbis Synthesis error.\n");
-						}
-
-						float **pcm;
-
-						int numSamples = vorbis_synthesis_pcmout(&vorbisDspState, &pcm);
-
-						if (numSamples > 0) {
-							int word = 2;
-							int sgned = 1;
-							int i, j;
-							long bytespersample = audioChannels * word;
-							vorbis_fpu_control fpu;
-
-							char *buffer = new char[bytespersample * numSamples];
-							if (! checkNew(buffer)) return false;
-
-							/* a tight loop to pack each size */
-							{
-								int val;
-								if (word == 1) {
-									int off = (sgned ? 0 : 128);
-									vorbis_fpu_setround(&fpu);
-									for (j = 0; j < numSamples; j++)
-									for (i = 0; i < audioChannels; i++) {
-										val = vorbis_ftoi(pcm[i][j] * 128.f);
-										if (val > 127)val = 127;
-										else if (val < -128)val = -128;
-										*buffer++ = val + off;
-									}
-									vorbis_fpu_restore(fpu);
-								} else {
-									int off = (sgned ? 0 : 32768);
-
-									if (sgned) {
-
-										vorbis_fpu_setround(&fpu);
-										for (i = 0; i < audioChannels; i++) { /* It's faster in this order */
-											float *src = pcm[i];
-											int16 *dest = ((int16 *)buffer) + i;
-											for (j = 0; j < numSamples; j++) {
-												val = vorbis_ftoi(src[j] * 32768.f);
-												if (val > 32767)val = 32767;
-												else if (val < -32768)val = -32768;
-												*dest = val;
-												dest += audioChannels;
-											}
-										}
-										vorbis_fpu_restore(fpu);
-
-									} else {
-
-										vorbis_fpu_setround(&fpu);
-										for (i = 0; i < audioChannels; i++) {
-											float *src = pcm[i];
-											int16 *dest = ((int16 *)buffer) + i;
-											for (j = 0; j < numSamples; j++) {
-												val = vorbis_ftoi(src[j] * 32768.f);
-												if (val > 32767)val = 32767;
-												else if (val < -32768)val = -32768;
-												*dest = val + off;
-												dest += audioChannels;
-											}
-										}
-										vorbis_fpu_restore(fpu);
-
-									}
-
-								}
-							}
-
-							vorbis_synthesis_read(&vorbisDspState, numSamples);
-							audioBufferLen = bytespersample * numSamples;
-							audio_queue_put(&audioQ, buffer, audioBufferLen, time_ns / 1000000);
-
-							//fprintf (stderr, "Audio buffered: %lld byte %lld ns\n",audioBufferLen, audioNsPerByte*audioBufferLen);
-
-							if (! movieSoundPlaying && size > 1) {
-								fprintf(stderr, "** starting sound ** \n");
-								playMovieStream(movieAudioIndex);
-								movieSoundPlaying = true;
-							}
-						}
-					}
-				}
-				frameCounter++;
-
-			} else {
-				movieHasEnded:
-				movieIsEnding = 1;
-			}
-		}
-
-		bool videoUpdated = false;
-		// Get a video frame.
-		if (movieIsPlaying == kMoviePlaying) {
-
-			videoBuffers *vB;
-			// Do we have decoded video waiting?
-			if (vB = videoQ.first) {
-				Uint32 tick = SDL_GetTicks() - movieStartTick;
-
-				// Is it time to display the frame yet?
-				if ((tick + 1) < vB->time_ms) {
-					SDL_Delay(1);
-				} else {
-					GLubyte *ytex = NULL;
-					GLubyte *utex = NULL;
-					GLubyte *vtex = NULL;
-					GLsizei w, h;
-
-					if (video_queue_get(&videoQ, &ytex, &utex, &vtex, &w, &h)) {
-
-						if (! yTextureName) glGenTextures(1, &yTextureName);
-						if (! uTextureName) glGenTextures(1, &uTextureName);
-						if (! vTextureName) glGenTextures(1, &vTextureName);
-						glBindTexture(GL_TEXTURE_2D, yTextureName);
-						glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0,
-								GL_ALPHA, GL_UNSIGNED_BYTE, ytex);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-						glBindTexture(GL_TEXTURE_2D, uTextureName);
-						glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w >> 1, h >> 1, 0,
-								GL_ALPHA, GL_UNSIGNED_BYTE, utex);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-						glBindTexture(GL_TEXTURE_2D, vTextureName);
-						glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w >> 1, h >> 1, 0,
-								GL_ALPHA, GL_UNSIGNED_BYTE, vtex);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-						glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-
-						glBindTexture(GL_TEXTURE_2D, yTextureName);
-						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h,
-								GL_ALPHA, GL_UNSIGNED_BYTE, ytex);
-						glBindTexture(GL_TEXTURE_2D, uTextureName);
-						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w >> 1, h >> 1,
-								GL_ALPHA, GL_UNSIGNED_BYTE, utex);
-						glBindTexture(GL_TEXTURE_2D, vTextureName);
-						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w >> 1, h >> 1,
-								GL_ALPHA, GL_UNSIGNED_BYTE, vtex);
-
-						delete [] ytex;
-						delete [] utex;
-						delete [] vtex;
-						ytex = utex = vtex = NULL;
-						videoUpdated = true;
-
-					}
-				}
-			} else if (movieIsEnding) {
-				// We have reached the end of the movie.
-				movieIsPlaying = kMovieNothing;
-			}
-		}
-
-		// Update the screen if there's new video, or if we're paused
-		if (videoUpdated || movieIsPlaying == kMoviePaused) {
-			// Clear The Screen
-			glClear(GL_COLOR_BUFFER_BIT);
-
-			// Display the current frame here
-			if (shader.yuv) {
-				glUseProgram(shader.yuv);
-				glActiveTexture(GL_TEXTURE1);
-				glBindTexture(GL_TEXTURE_2D, uTextureName);
-				glActiveTexture(GL_TEXTURE2);
-				glBindTexture(GL_TEXTURE_2D, vTextureName);
-				glActiveTexture(GL_TEXTURE0);
-			}
-			glBindTexture(GL_TEXTURE_2D, yTextureName);
-			glEnable(GL_BLEND);
-			//glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-
-			setPMVMatrix(shader.yuv);
-			drawQuad(shader.yuv, vertices, 1, texCoords);
-
-			glUseProgram(0);
-
-			if (movieIsPlaying == kMoviePaused) {
-				pausefade -= 1.0 / 24;
-				if (pausefade < -1.0) pausefade = 1.0;
-
-				// Paused.
-				glEnable(GL_BLEND);
-
-				glUseProgram(shader.color);
-
-				setPMVMatrix(shader.color);
-				setPrimaryColor(0.0f, 0.0f, 0.0f, fabs(pausefade));
-				drawQuad(shader.color, vertices1, 0);
-				drawQuad(shader.color, vertices2, 0);
-				setPrimaryColor(1.0f, 1.0f, 1.0f, fabs(pausefade));
-				drawQuad(shader.color, vertices3, 0);
-				drawQuad(shader.color, vertices4, 0);
-
-				glUseProgram(0);
-
-				glDisable(GL_BLEND);
-
-				Wait_Frame();
-			}
-
-			glFlush();
-
-			EGL_SwapBuffers();
-
-		}
-		videoUpdated = false;
-	}
-
-	// Cleanup
-	glBindFramebuffer(GL_FRAMEBUFFER, old_fbo);
-
-	movieIsPlaying = kMovieNothing;
-	for (int i = 0; i < 10; i++)
-		Wait_Frame();
-	huntKillFreeSound(fileNumber);
-
-	if (vpx_codec_destroy(&codec))
-	die_codec(&codec, "Failed to destroy codec");
-
-	vorbis_dsp_clear(&vorbisDspState);
-	vorbis_block_clear(&vorbisBlock);
-	vorbis_comment_clear(&vorbisComment);
-	vorbis_info_clear(&vorbisInfo);
-	delete pSegment;
-	deleteTextures(1, &yTextureName);
-	deleteTextures(1, &uTextureName);
-	deleteTextures(1, &vTextureName);
-	yTextureName = uTextureName = vTextureName = 0;
-
-	// Delete any remaining buffers
-	videoBuffers *vB = videoQ.first;
-	while (vB = videoQ.first) {
-		videoQ.first = vB->next;
-		delete [] vB->ytex;
-		delete [] vB->utex;
-		delete [] vB->vtex;
-		delete vB;
-	}
-	videoQ.size = 0;
-
-	audioBuffers *aB = audioQ.first;
-	while (aB = audioQ.first) {
-		audioQ.first = aB->next;
-		delete [] aB->buffer;
-		delete aB;
-	}
-	audioQ.size = 0;
-
-	Init_Timer();
-
-	glViewport(viewportOffsetX, viewportOffsetY, viewportWidth, viewportHeight);
-
-	setPixelCoords(false);
-#endif
 	return 0;
 }
 


Commit: 434d82a9bb52b0309fd427a1f116bcb6265db018
    https://github.com/scummvm/scummvm/commit/434d82a9bb52b0309fd427a1f116bcb6265db018
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Only scale if required

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index c7a02114e00..ae5d09d2e5d 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -29,6 +29,7 @@
 
 #include "common/substream.h"
 #include "video/mkv_decoder.h"
+#include "graphics/blit.h"
 
 namespace Sludge {
 
@@ -69,7 +70,14 @@ int playMovie(int fileNumber) {
 				if (s) {
 					// Transfer the next frame
 					assert(s->format.bytesPerPixel == 4);
-					g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, MIN<uint32>(s->w, g_sludge->_gfxMan->getWinWidth()), MIN<uint32>(s->h, g_sludge->_gfxMan->getWinHeight()));
+					if (((uint)s->w != g_sludge->_gfxMan->getWinWidth()) || ((uint)s->h != g_sludge->_gfxMan->getWinHeight())) {
+						Graphics::Surface *surf = g_system->lockScreen();
+						Graphics::scaleBlit((byte*)surf->getPixels(), (const byte*)s->getPixels(), surf->pitch, s->pitch,
+						                    g_sludge->_gfxMan->getWinWidth(), g_sludge->_gfxMan->getWinHeight(), s->w, s->h, s->format);
+						g_system->unlockScreen();
+					} else {
+						g_system->copyRectToScreen(s->getPixels(), s->pitch, 0, 0, s->w, s->h);
+					}
 					g_system->updateScreen();
 				} else {
 					warning("s is false");


Commit: a121c1034ae88610526644b71448242514e065dd
    https://github.com/scummvm/scummvm/commit/a121c1034ae88610526644b71448242514e065dd
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Close resources properly in stopMovie()

Changed paths:
    engines/sludge/event.cpp
    engines/sludge/movie.cpp
    engines/sludge/movie.h


diff --git a/engines/sludge/event.cpp b/engines/sludge/event.cpp
index a6680177c8b..cdae7d31aa5 100644
--- a/engines/sludge/event.cpp
+++ b/engines/sludge/event.cpp
@@ -26,6 +26,7 @@
 #include "sludge/graphics.h"
 #include "sludge/freeze.h"
 #include "sludge/function.h"
+#include "sludge/movie.h"
 #include "sludge/newfatal.h"
 #include "sludge/objtypes.h"
 #include "sludge/region.h"
@@ -297,6 +298,8 @@ bool EventManager::handleInput() {
 		}
 
 		if (!tempString.empty()) {
+			if (isMoviePlaying())
+				stopMovie();
 			VariableStack *tempStack = new VariableStack;
 			if (!checkNew(tempStack))
 				return false;
diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index ae5d09d2e5d..1e218072470 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -93,9 +93,17 @@ int playMovie(int fileNumber) {
 	return 0;
 }
 
+MovieStates isMoviePlaying() {
+	return movieIsPlaying;
+}
+
 int stopMovie() {
 	int r = movieIsPlaying;
 	movieIsPlaying = kMovieNothing;
+
+	g_sludge->_resMan->finishAccess();
+	setResourceForFatal(-1);
+
 	return r;
 }
 
diff --git a/engines/sludge/movie.h b/engines/sludge/movie.h
index 14961618051..2a034e9a575 100644
--- a/engines/sludge/movie.h
+++ b/engines/sludge/movie.h
@@ -36,6 +36,7 @@ extern int movieIsEnding;
 
 int playMovie(int fileNumber);
 int stopMovie();
+MovieStates isMoviePlaying();
 int pauseMovie();
 
 } // End of namespace Sludge


Commit: 5351c8b33d8a4b3ea5ea617339d1bf9d2f8acc5b
    https://github.com/scummvm/scummvm/commit/5351c8b33d8a4b3ea5ea617339d1bf9d2f8acc5b
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Convert unnecessary warnings to debugs

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 1e218072470..29d993dba98 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -52,7 +52,7 @@ int playMovie(int fileNumber) {
 
 	decoder.start();
 
-	warning("movieIsPlaying %d", movieIsPlaying);
+	debug(1, "movieIsPlaying %d", movieIsPlaying);
 	while (movieIsPlaying) {
 		g_sludge->_evtMan->checkInput();
 		if (g_sludge->_evtMan->quit())
@@ -62,7 +62,7 @@ int playMovie(int fileNumber) {
 
 		if (decoder.isVideoLoaded()) {
 			if (decoder.endOfVideo()) {
-				warning("End of video");
+				debug(1, "End of video");
 				// Movie complete, so unload the movie
 				break;
 			} else if (decoder.needsUpdate()) {


Commit: 35c775113a80cb4891dae62695576e514a2483dc
    https://github.com/scummvm/scummvm/commit/35c775113a80cb4891dae62695576e514a2483dc
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
JANITORIAL: Rename variables to match SCUMMVM's coding style

Changed paths:
    video/mkv_decoder.cpp
    video/mkv_decoder.h


diff --git a/video/mkv_decoder.cpp b/video/mkv_decoder.cpp
index 0108c6fe45e..fe8c62f0edb 100644
--- a/video/mkv_decoder.cpp
+++ b/video/mkv_decoder.cpp
@@ -142,44 +142,44 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 	mkvparser::EBMLHeader ebmlHeader;
 	ebmlHeader.Parse(_reader, pos);
 
-	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, pSegment);
+	long long ret = mkvparser::Segment::CreateInstance(_reader, pos, _pSegment);
 	if (ret) {
 		error("MKVDecoder::loadStream(): Segment::CreateInstance() failed (%lld).", ret);
 	}
 
-	ret = pSegment->Load();
+	ret = _pSegment->Load();
 	if (ret) {
 		error("MKVDecoder::loadStream(): Segment::Load() failed (%lld).", ret);
 	}
 
-	pTracks = pSegment->GetTracks();
+	_pTracks = _pSegment->GetTracks();
 
 	uint32 i = 0;
-	const unsigned long j = pTracks->GetTracksCount();
+	const unsigned long j = _pTracks->GetTracksCount();
 
 	debug(1, "Number of tracks: %d", j);
 
 	enum {VIDEO_TRACK = 1, AUDIO_TRACK = 2};
-	videoTrack = -1;
-	audioTrack = -1;
+	_vTrack = -1;
+	_aTrack = -1;
 
 	while (i != j) {
-		const mkvparser::Track *const pTrack = pTracks->GetTrackByIndex(i++);
+		const mkvparser::Track *const pTrack = _pTracks->GetTrackByIndex(i++);
 
 		if (pTrack == NULL)
 			continue;
 
 		const long long trackType = pTrack->GetType();
-		if (trackType == mkvparser::Track::kVideo && videoTrack < 0) {
-			videoTrack = pTrack->GetNumber();
+		if (trackType == mkvparser::Track::kVideo && _vTrack < 0) {
+			_vTrack = pTrack->GetNumber();
 
 			_videoTrack = new VPXVideoTrack(getDefaultHighColorFormat(), pTrack);
 
 			addTrack(_videoTrack);
 		}
 
-		if (trackType == mkvparser::Track::kAudio && audioTrack < 0) {
-			audioTrack = pTrack->GetNumber();
+		if (trackType == mkvparser::Track::kAudio && _aTrack < 0) {
+			_aTrack = pTrack->GetNumber();
 
 			const mkvparser::AudioTrack *const pAudioTrack = static_cast<const mkvparser::AudioTrack *>(pTrack);
 
@@ -188,7 +188,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 
 			if (audioHeaderSize < 1) {
 				warning("Strange audio track in movie.");
-				audioTrack = -1;
+				_aTrack = -1;
 				continue;
 			}
 
@@ -197,7 +197,7 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 			uint count = *p++ + 1;
 			if (count != 3) {
 				warning("Strange audio track in movie.");
-				audioTrack = -1;
+				_aTrack = -1;
 				continue;
 			}
 
@@ -206,39 +206,39 @@ bool MKVDecoder::loadStream(Common::SeekableReadStream *stream) {
 		}
 	}
 
-	if (videoTrack < 0)
+	if (_vTrack < 0)
 		error("Movie error: No video in movie file.");
 
-	if (audioTrack < 0)
+	if (_aTrack < 0)
 		error("Movie error: No sound found.");
 
-	const unsigned long long clusterCount = pSegment->GetCount();
+	const unsigned long long clusterCount = _pSegment->GetCount();
 
 	if (clusterCount == 0) {
 		error("Movie error: Segment has no clusters.\n");
 	}
 
-	frame = new byte[256 * 1024];
-	if (!frame)
+	_frame = new byte[256 * 1024];
+	if (!_frame)
 		return false;
 
-	_cluster = pSegment->GetFirst();
+	_cluster = _pSegment->GetFirst();
 
-	if (_cluster->GetFirst(pBlockEntry))
+	if (_cluster->GetFirst(_pBlockEntry))
 		error("_cluster::GetFirst() failed");
 
-	if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-		_cluster = pSegment->GetNext(_cluster);
+	if ((_pBlockEntry == NULL) || _pBlockEntry->EOS()) {
+		_cluster = _pSegment->GetNext(_cluster);
 		if ((_cluster == NULL) || _cluster->EOS()) {
 			error("Error: No movie found in the movie file.");
 		}
-		if (_cluster->GetFirst(pBlockEntry))
+		if (_cluster->GetFirst(_pBlockEntry))
 			error("_cluster::GetFirst() failed");
 	}
 
-	pBlock = pBlockEntry->GetBlock();
-	trackNum = pBlock->GetTrackNumber();
-	frameCount = pBlock->GetFrameCount();
+	_pBlock = _pBlockEntry->GetBlock();
+	_trackNum = _pBlock->GetTrackNumber();
+	_frameCount = _pBlock->GetFrameCount();
 
 	return true;
 }
@@ -261,50 +261,50 @@ void MKVDecoder::readNextPacket() {
 
 	// ensure we have enough buffers in the stream
 	while (_audioTrack->needsAudio()) {
-		if (frameCounter >= frameCount) {
-		_cluster->GetNext(pBlockEntry, pBlockEntry);
+		if (_frameCounter >= _frameCount) {
+		_cluster->GetNext(_pBlockEntry, _pBlockEntry);
 
-			if ((pBlockEntry == NULL) || pBlockEntry->EOS()) {
-				_cluster = pSegment->GetNext(_cluster);
+			if ((_pBlockEntry == NULL) || _pBlockEntry->EOS()) {
+				_cluster = _pSegment->GetNext(_cluster);
 				if ((_cluster == NULL) || _cluster->EOS()) {
 					_videoTrack->setEndOfVideo();
 					_audioTrack->setEndOfAudio();
 					return;
 				}
-				int ret = _cluster->GetFirst(pBlockEntry);
+				int ret = _cluster->GetFirst(_pBlockEntry);
 				if (ret < 0)
 					error("MKVDecoder::readNextPacket(): GetFirst() failed");
 			}
 
-			pBlock  = pBlockEntry->GetBlock();
-			trackNum = pBlock->GetTrackNumber();
-			frameCount = pBlock->GetFrameCount();
-			frameCounter = 0;
+			_pBlock  = _pBlockEntry->GetBlock();
+			_trackNum = _pBlock->GetTrackNumber();
+			_frameCount = _pBlock->GetFrameCount();
+			_frameCounter = 0;
 		}
 
-		const mkvparser::Block::Frame &theFrame = pBlock->GetFrame(frameCounter);
+		const mkvparser::Block::Frame &theFrame = _pBlock->GetFrame(_frameCounter);
 		const uint32 size = theFrame.len;
 
-		if (size > sizeof(frame)) {
-			if (frame)
-				delete[] frame;
-			frame = new unsigned char[size];
-			if (!frame)
+		if (size > sizeof(_frame)) {
+			if (_frame)
+				delete[] _frame;
+			_frame = new unsigned char[size];
+			if (!_frame)
 				return;
 		}
 
-		if (trackNum == videoTrack) {
-			theFrame.Read(_reader, frame);
-			_videoTrack->decodeFrame(frame, size);
-		} else if (trackNum == audioTrack) {
+		if (_trackNum == _vTrack) {
+			theFrame.Read(_reader, _frame);
+			_videoTrack->decodeFrame(_frame, size);
+		} else if (_trackNum == _aTrack) {
 			if (size > 0) {
-				theFrame.Read(_reader, frame);
+				theFrame.Read(_reader, _frame);
 				queueAudio(size);
 			}
 		} else {
-			warning("Unprocessed track %lld", trackNum);
+			warning("Unprocessed track %lld", _trackNum);
 		}
-		++frameCounter;
+		++_frameCounter;
 	}
 }
 
@@ -520,7 +520,7 @@ bool MKVDecoder::VorbisAudioTrack::synthesizePacket(byte *frame, long size) {
 bool MKVDecoder::queueAudio(long size) {
 	bool queuedAudio = false;
 
-	if (_audioTrack->synthesizePacket(frame, size) && _audioTrack->decodeSamples(frame, size))
+	if (_audioTrack->synthesizePacket(_frame, size) && _audioTrack->decodeSamples(_frame, size))
 		queuedAudio = true;
 
 	return queuedAudio;
diff --git a/video/mkv_decoder.h b/video/mkv_decoder.h
index 5984d944b89..be20906f1a9 100644
--- a/video/mkv_decoder.h
+++ b/video/mkv_decoder.h
@@ -152,19 +152,19 @@ private:
 	mkvparser::MkvReader *_reader = nullptr;
 
 	const mkvparser::Cluster *_cluster = nullptr;
-	const mkvparser::Tracks *pTracks = nullptr;
-	const mkvparser::BlockEntry *pBlockEntry = nullptr;
-	mkvparser::Segment *pSegment = nullptr;
+	const mkvparser::Tracks *_pTracks = nullptr;
+	const mkvparser::BlockEntry *_pBlockEntry = nullptr;
+	mkvparser::Segment *_pSegment = nullptr;
 
-	byte *frame = nullptr;
-	int frameCounter = 0;
+	byte *_frame = nullptr;
+	int _frameCounter = 0;
 
-	int videoTrack = -1;
-	int audioTrack = -1;
+	int _vTrack = -1;
+	int _aTrack = -1;
 
-	const mkvparser::Block *pBlock;
-	long long trackNum;
-	int frameCount;
+	const mkvparser::Block *_pBlock;
+	long long _trackNum;
+	int _frameCount;
 };
 
 } // End of namespace Video


Commit: 234e1cac4968eec2fb89df55b9a43808cf5b4295
    https://github.com/scummvm/scummvm/commit/234e1cac4968eec2fb89df55b9a43808cf5b4295
Author: hax0kartik (agarwala.kartik at gmail.com)
Date: 2023-03-05T21:29:03+01:00

Commit Message:
SLUDGE: Skip movie incase scummvm isn't built with libvpx as a dependancy

Changed paths:
    engines/sludge/movie.cpp


diff --git a/engines/sludge/movie.cpp b/engines/sludge/movie.cpp
index 29d993dba98..87100d99678 100644
--- a/engines/sludge/movie.cpp
+++ b/engines/sludge/movie.cpp
@@ -27,6 +27,7 @@
 #include "sludge/newfatal.h"
 #include "sludge/sound.h"
 
+#include "common/scummsys.h"	// for USE_VPX
 #include "common/substream.h"
 #include "video/mkv_decoder.h"
 #include "graphics/blit.h"
@@ -42,8 +43,12 @@ int playMovie(int fileNumber) {
 	if (!(fsize = g_sludge->_resMan->openFileFromNum(fileNumber)))
 		return fatal("playMovie(): Can't open movie");
 
+#if !defined(USE_VPX)
+	warning("Sludge::playMovie - VPX support not compiled in, skipping movie");
+#else
 	Video::MKVDecoder decoder;
 
+
 	Common::SeekableReadStream *stream = g_sludge->_resMan->getData();
 	Common::SeekableSubReadStream video(stream, stream->pos(), stream->pos() + fsize);
 
@@ -85,6 +90,7 @@ int playMovie(int fileNumber) {
 			}
 		}
 	}
+#endif
 
 	movieIsPlaying = kMovieNothing;
 




More information about the Scummvm-git-logs mailing list