[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