From 5c14a3f035660273354ad5045d6e8663464949ea Mon Sep 17 00:00:00 2001 From: Tom Finegan Date: Thu, 23 Jan 2014 10:57:06 -0800 Subject: [PATCH] mkvparser/mkvreader/sample: CRLF -> LF Change-Id: Id4a5bea411d104289548b276d0e996352cc816b1 --- mkvparser.cpp | 19178 ++++++++++++++++++++++++------------------------ mkvparser.hpp | 2158 +++--- mkvreader.cpp | 256 +- mkvreader.hpp | 76 +- sample.cpp | 584 +- 5 files changed, 11126 insertions(+), 11126 deletions(-) diff --git a/mkvparser.cpp b/mkvparser.cpp index a63c2fa..f264055 100644 --- a/mkvparser.cpp +++ b/mkvparser.cpp @@ -1,9589 +1,9589 @@ -// Copyright (c) 2012 The WebM project authors. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. - -#include "mkvparser.hpp" -#include -#include -#include -#include - -mkvparser::IMkvReader::~IMkvReader() -{ -} - -void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) -{ - major = 1; - minor = 0; - build = 0; - revision = 27; -} - -long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len) -{ - assert(pReader); - assert(pos >= 0); - - int status; - -//#ifdef _DEBUG -// long long total, available; -// status = pReader->Length(&total, &available); -// assert(status >= 0); -// assert((total < 0) || (available <= total)); -// assert(pos < available); -// assert((available - pos) >= 1); //assume here max u-int len is 8 -//#endif - - len = 1; - - unsigned char b; - - status = pReader->Read(pos, 1, &b); - - if (status < 0) //error or underflow - return status; - - if (status > 0) //interpreted as "underflow" - return E_BUFFER_NOT_FULL; - - if (b == 0) //we can't handle u-int values larger than 8 bytes - return E_FILE_FORMAT_INVALID; - - unsigned char m = 0x80; - - while (!(b & m)) - { - m >>= 1; - ++len; - } - -//#ifdef _DEBUG -// assert((available - pos) >= len); -//#endif - - long long result = b & (~m); - ++pos; - - for (int i = 1; i < len; ++i) - { - status = pReader->Read(pos, 1, &b); - - if (status < 0) - { - len = 1; - return status; - } - - if (status > 0) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result <<= 8; - result |= b; - - ++pos; - } - - return result; -} - -long long mkvparser::GetUIntLength( - IMkvReader* pReader, - long long pos, - long& len) -{ - assert(pReader); - assert(pos >= 0); - - long long total, available; - - int status = pReader->Length(&total, &available); - assert(status >= 0); - assert((total < 0) || (available <= total)); - - len = 1; - - if (pos >= available) - return pos; //too few bytes available - - unsigned char b; - - status = pReader->Read(pos, 1, &b); - - if (status < 0) - return status; - - assert(status == 0); - - if (b == 0) //we can't handle u-int values larger than 8 bytes - return E_FILE_FORMAT_INVALID; - - unsigned char m = 0x80; - - while (!(b & m)) - { - m >>= 1; - ++len; - } - - return 0; //success -} - - -long long mkvparser::UnserializeUInt( - IMkvReader* pReader, - long long pos, - long long size) -{ - assert(pReader); - assert(pos >= 0); - - if ((size <= 0) || (size > 8)) - return E_FILE_FORMAT_INVALID; - - long long result = 0; - - for (long long i = 0; i < size; ++i) - { - unsigned char b; - - const long status = pReader->Read(pos, 1, &b); - - if (status < 0) - return status; - - result <<= 8; - result |= b; - - ++pos; - } - - return result; -} - - -long mkvparser::UnserializeFloat( - IMkvReader* pReader, - long long pos, - long long size_, - double& result) -{ - assert(pReader); - assert(pos >= 0); - - if ((size_ != 4) && (size_ != 8)) - return E_FILE_FORMAT_INVALID; - - const long size = static_cast(size_); - - unsigned char buf[8]; - - const int status = pReader->Read(pos, size, buf); - - if (status < 0) //error - return status; - - if (size == 4) - { - union - { - float f; - unsigned long ff; - }; - - ff = 0; - - for (int i = 0;;) - { - ff |= buf[i]; - - if (++i >= 4) - break; - - ff <<= 8; - } - - result = f; - } - else - { - assert(size == 8); - - union - { - double d; - unsigned long long dd; - }; - - dd = 0; - - for (int i = 0;;) - { - dd |= buf[i]; - - if (++i >= 8) - break; - - dd <<= 8; - } - - result = d; - } - - return 0; -} - - -long mkvparser::UnserializeInt( - IMkvReader* pReader, - long long pos, - long size, - long long& result) -{ - assert(pReader); - assert(pos >= 0); - assert(size > 0); - assert(size <= 8); - - { - signed char b; - - const long status = pReader->Read(pos, 1, (unsigned char*)&b); - - if (status < 0) - return status; - - result = b; - - ++pos; - } - - for (long i = 1; i < size; ++i) - { - unsigned char b; - - const long status = pReader->Read(pos, 1, &b); - - if (status < 0) - return status; - - result <<= 8; - result |= b; - - ++pos; - } - - return 0; //success -} - - -long mkvparser::UnserializeString( - IMkvReader* pReader, - long long pos, - long long size_, - char*& str) -{ - delete[] str; - str = NULL; - - if (size_ >= LONG_MAX) //we need (size+1) chars - return E_FILE_FORMAT_INVALID; - - const long size = static_cast(size_); - - str = new (std::nothrow) char[size+1]; - - if (str == NULL) - return -1; - - unsigned char* const buf = reinterpret_cast(str); - - const long status = pReader->Read(pos, size, buf); - - if (status) - { - delete[] str; - str = NULL; - - return status; - } - - str[size] = '\0'; - - return 0; //success -} - - -long mkvparser::ParseElementHeader( - IMkvReader* pReader, - long long& pos, - long long stop, - long long& id, - long long& size) -{ - if ((stop >= 0) && (pos >= stop)) - return E_FILE_FORMAT_INVALID; - - long len; - - id = ReadUInt(pReader, pos, len); - - if (id < 0) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume id - - if ((stop >= 0) && (pos >= stop)) - return E_FILE_FORMAT_INVALID; - - size = ReadUInt(pReader, pos, len); - - if (size < 0) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume length of size - - //pos now designates payload - - if ((stop >= 0) && ((pos + size) > stop)) - return E_FILE_FORMAT_INVALID; - - return 0; //success -} - - -bool mkvparser::Match( - IMkvReader* pReader, - long long& pos, - unsigned long id_, - long long& val) -{ - assert(pReader); - assert(pos >= 0); - - long long total, available; - - const long status = pReader->Length(&total, &available); - assert(status >= 0); - assert((total < 0) || (available <= total)); - - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); - assert(len > 0); - assert(len <= 8); - assert((pos + len) <= available); - - if ((unsigned long)id != id_) - return false; - - pos += len; //consume id - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); - assert(size <= 8); - assert(len > 0); - assert(len <= 8); - assert((pos + len) <= available); - - pos += len; //consume length of size of payload - - val = UnserializeUInt(pReader, pos, size); - assert(val >= 0); - - pos += size; //consume size of payload - - return true; -} - -bool mkvparser::Match( - IMkvReader* pReader, - long long& pos, - unsigned long id_, - unsigned char*& buf, - size_t& buflen) -{ - assert(pReader); - assert(pos >= 0); - - long long total, available; - - long status = pReader->Length(&total, &available); - assert(status >= 0); - assert((total < 0) || (available <= total)); - - long len; - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); - assert(len > 0); - assert(len <= 8); - assert((pos + len) <= available); - - if ((unsigned long)id != id_) - return false; - - pos += len; //consume id - - const long long size_ = ReadUInt(pReader, pos, len); - assert(size_ >= 0); - assert(len > 0); - assert(len <= 8); - assert((pos + len) <= available); - - pos += len; //consume length of size of payload - assert((pos + size_) <= available); - - const long buflen_ = static_cast(size_); - - buf = new (std::nothrow) unsigned char[buflen_]; - assert(buf); //TODO - - status = pReader->Read(pos, buflen_, buf); - assert(status == 0); //TODO - - buflen = buflen_; - - pos += size_; //consume size of payload - return true; -} - - -namespace mkvparser -{ - -EBMLHeader::EBMLHeader() : - m_docType(NULL) -{ - Init(); -} - -EBMLHeader::~EBMLHeader() -{ - delete[] m_docType; -} - -void EBMLHeader::Init() -{ - m_version = 1; - m_readVersion = 1; - m_maxIdLength = 4; - m_maxSizeLength = 8; - - if (m_docType) - { - delete[] m_docType; - m_docType = NULL; - } - - m_docTypeVersion = 1; - m_docTypeReadVersion = 1; -} - -long long EBMLHeader::Parse( - IMkvReader* pReader, - long long& pos) -{ - assert(pReader); - - long long total, available; - - long status = pReader->Length(&total, &available); - - if (status < 0) //error - return status; - - pos = 0; - long long end = (available >= 1024) ? 1024 : available; - - for (;;) - { - unsigned char b = 0; - - while (pos < end) - { - status = pReader->Read(pos, 1, &b); - - if (status < 0) //error - return status; - - if (b == 0x1A) - break; - - ++pos; - } - - if (b != 0x1A) - { - if (pos >= 1024) - return E_FILE_FORMAT_INVALID; //don't bother looking anymore - - if ((total >= 0) && ((total - available) < 5)) - return E_FILE_FORMAT_INVALID; - - return available + 5; //5 = 4-byte ID + 1st byte of size - } - - if ((total >= 0) && ((total - pos) < 5)) - return E_FILE_FORMAT_INVALID; - - if ((available - pos) < 5) - return pos + 5; //try again later - - long len; - - const long long result = ReadUInt(pReader, pos, len); - - if (result < 0) //error - return result; - - if (result == 0x0A45DFA3) //EBML Header ID - { - pos += len; //consume ID - break; - } - - ++pos; //throw away just the 0x1A byte, and try again - } - - //pos designates start of size field - - //get length of size field - - long len; - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return result; - - if (result > 0) //need more data - return result; - - assert(len > 0); - assert(len <= 8); - - if ((total >= 0) && ((total - pos) < len)) - return E_FILE_FORMAT_INVALID; - - if ((available - pos) < len) - return pos + len; //try again later - - //get the EBML header size - - result = ReadUInt(pReader, pos, len); - - if (result < 0) //error - return result; - - pos += len; //consume size field - - //pos now designates start of payload - - if ((total >= 0) && ((total - pos) < result)) - return E_FILE_FORMAT_INVALID; - - if ((available - pos) < result) - return pos + result; - - end = pos + result; - - Init(); - - while (pos < end) - { - long long id, size; - - status = ParseElementHeader( - pReader, - pos, - end, - id, - size); - - if (status < 0) //error - return status; - - if (size == 0) //weird - return E_FILE_FORMAT_INVALID; - - if (id == 0x0286) //version - { - m_version = UnserializeUInt(pReader, pos, size); - - if (m_version <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x02F7) //read version - { - m_readVersion = UnserializeUInt(pReader, pos, size); - - if (m_readVersion <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x02F2) //max id length - { - m_maxIdLength = UnserializeUInt(pReader, pos, size); - - if (m_maxIdLength <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x02F3) //max size length - { - m_maxSizeLength = UnserializeUInt(pReader, pos, size); - - if (m_maxSizeLength <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x0282) //doctype - { - if (m_docType) - return E_FILE_FORMAT_INVALID; - - status = UnserializeString(pReader, pos, size, m_docType); - - if (status) //error - return status; - } - else if (id == 0x0287) //doctype version - { - m_docTypeVersion = UnserializeUInt(pReader, pos, size); - - if (m_docTypeVersion <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x0285) //doctype read version - { - m_docTypeReadVersion = UnserializeUInt(pReader, pos, size); - - if (m_docTypeReadVersion <= 0) - return E_FILE_FORMAT_INVALID; - } - - pos += size; - } - - assert(pos == end); - return 0; -} - - -Segment::Segment( - IMkvReader* pReader, - long long elem_start, - //long long elem_size, - long long start, - long long size) : - m_pReader(pReader), - m_element_start(elem_start), - //m_element_size(elem_size), - m_start(start), - m_size(size), - m_pos(start), - m_pUnknownSize(0), - m_pSeekHead(NULL), - m_pInfo(NULL), - m_pTracks(NULL), - m_pCues(NULL), - m_pChapters(NULL), - m_clusters(NULL), - m_clusterCount(0), - m_clusterPreloadCount(0), - m_clusterSize(0) -{ -} - - -Segment::~Segment() -{ - const long count = m_clusterCount + m_clusterPreloadCount; - - Cluster** i = m_clusters; - Cluster** j = m_clusters + count; - - while (i != j) - { - Cluster* const p = *i++; - assert(p); - - delete p; - } - - delete[] m_clusters; - - delete m_pTracks; - delete m_pInfo; - delete m_pCues; - delete m_pChapters; - delete m_pSeekHead; -} - - -long long Segment::CreateInstance( - IMkvReader* pReader, - long long pos, - Segment*& pSegment) -{ - assert(pReader); - assert(pos >= 0); - - pSegment = NULL; - - long long total, available; - - const long status = pReader->Length(&total, &available); - - if (status < 0) //error - return status; - - if (available < 0) - return -1; - - if ((total >= 0) && (available > total)) - return -1; - - //I would assume that in practice this loop would execute - //exactly once, but we allow for other elements (e.g. Void) - //to immediately follow the EBML header. This is fine for - //the source filter case (since the entire file is available), - //but in the splitter case over a network we should probably - //just give up early. We could for example decide only to - //execute this loop a maximum of, say, 10 times. - //TODO: - //There is an implied "give up early" by only parsing up - //to the available limit. We do do that, but only if the - //total file size is unknown. We could decide to always - //use what's available as our limit (irrespective of whether - //we happen to know the total file length). This would have - //as its sense "parse this much of the file before giving up", - //which a slightly different sense from "try to parse up to - //10 EMBL elements before giving up". - - for (;;) - { - if ((total >= 0) && (pos >= total)) - return E_FILE_FORMAT_INVALID; - - //Read ID - long len; - long long result = GetUIntLength(pReader, pos, len); - - if (result) //error, or too few available bytes - return result; - - if ((total >= 0) && ((pos + len) > total)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > available) - return pos + len; - - const long long idpos = pos; - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return id; - - pos += len; //consume ID - - //Read Size - - result = GetUIntLength(pReader, pos, len); - - if (result) //error, or too few available bytes - return result; - - if ((total >= 0) && ((pos + len) > total)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > available) - return pos + len; - - long long size = ReadUInt(pReader, pos, len); - - if (size < 0) //error - return size; - - pos += len; //consume length of size of element - - //Pos now points to start of payload - - //Handle "unknown size" for live streaming of webm files. - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (id == 0x08538067) //Segment ID - { - if (size == unknown_size) - size = -1; - - else if (total < 0) - size = -1; - - else if ((pos + size) > total) - size = -1; - - pSegment = new (std::nothrow) Segment( - pReader, - idpos, - //elem_size - pos, - size); - - if (pSegment == 0) - return -1; //generic error - - return 0; //success - } - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - if ((total >= 0) && ((pos + size) > total)) - return E_FILE_FORMAT_INVALID; - - if ((pos + size) > available) - return pos + size; - - pos += size; //consume payload - } -} - - -long long Segment::ParseHeaders() -{ - //Outermost (level 0) segment object has been constructed, - //and pos designates start of payload. We need to find the - //inner (level 1) elements. - long long total, available; - - const int status = m_pReader->Length(&total, &available); - - if (status < 0) //error - return status; - - assert((total < 0) || (available <= total)); - - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - assert((segment_stop < 0) || (total < 0) || (segment_stop <= total)); - assert((segment_stop < 0) || (m_pos <= segment_stop)); - - for (;;) - { - if ((total >= 0) && (m_pos >= total)) - break; - - if ((segment_stop >= 0) && (m_pos >= segment_stop)) - break; - - long long pos = m_pos; - const long long element_start = pos; - - if ((pos + 1) > available) - return (pos + 1); - - long len; - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return result; - - if (result > 0) //underflow (weird) - return (pos + 1); - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > available) - return pos + len; - - const long long idpos = pos; - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id < 0) //error - return id; - - if (id == 0x0F43B675) //Cluster ID - break; - - pos += len; //consume ID - - if ((pos + 1) > available) - return (pos + 1); - - //Read Size - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return result; - - if (result > 0) //underflow (weird) - return (pos + 1); - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > available) - return pos + len; - - const long long size = ReadUInt(m_pReader, pos, len); - - if (size < 0) //error - return size; - - pos += len; //consume length of size of element - - const long long element_size = size + pos - element_start; - - //Pos now points to start of payload - - if ((segment_stop >= 0) && ((pos + size) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - //We read EBML elements either in total or nothing at all. - - if ((pos + size) > available) - return pos + size; - - if (id == 0x0549A966) //Segment Info ID - { - if (m_pInfo) - return E_FILE_FORMAT_INVALID; - - m_pInfo = new (std::nothrow) SegmentInfo( - this, - pos, - size, - element_start, - element_size); - - if (m_pInfo == NULL) - return -1; - - const long status = m_pInfo->Parse(); - - if (status) - return status; - } - else if (id == 0x0654AE6B) //Tracks ID - { - if (m_pTracks) - return E_FILE_FORMAT_INVALID; - - m_pTracks = new (std::nothrow) Tracks(this, - pos, - size, - element_start, - element_size); - - if (m_pTracks == NULL) - return -1; - - const long status = m_pTracks->Parse(); - - if (status) - return status; - } - else if (id == 0x0C53BB6B) //Cues ID - { - if (m_pCues == NULL) - { - m_pCues = new (std::nothrow) Cues( - this, - pos, - size, - element_start, - element_size); - - if (m_pCues == NULL) - return -1; - } - } - else if (id == 0x014D9B74) //SeekHead ID - { - if (m_pSeekHead == NULL) - { - m_pSeekHead = new (std::nothrow) SeekHead( - this, - pos, - size, - element_start, - element_size); - - if (m_pSeekHead == NULL) - return -1; - - const long status = m_pSeekHead->Parse(); - - if (status) - return status; - } - } - else if (id == 0x0043A770) //Chapters ID - { - if (m_pChapters == NULL) - { - m_pChapters = new (std::nothrow) Chapters( - this, - pos, - size, - element_start, - element_size); - - if (m_pChapters == NULL) - return -1; - - const long status = m_pChapters->Parse(); - - if (status) - return status; - } - } - - m_pos = pos + size; //consume payload - } - - assert((segment_stop < 0) || (m_pos <= segment_stop)); - - if (m_pInfo == NULL) //TODO: liberalize this behavior - return E_FILE_FORMAT_INVALID; - - if (m_pTracks == NULL) - return E_FILE_FORMAT_INVALID; - - return 0; //success -} - - -long Segment::LoadCluster( - long long& pos, - long& len) -{ - for (;;) - { - const long result = DoLoadCluster(pos, len); - - if (result <= 1) - return result; - } -} - - -long Segment::DoLoadCluster( - long long& pos, - long& len) -{ - if (m_pos < 0) - return DoLoadClusterUnknownSize(pos, len); - - long long total, avail; - - long status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - - long long cluster_off = -1; //offset relative to start of segment - long long cluster_size = -1; //size of cluster payload - - for (;;) - { - if ((total >= 0) && (m_pos >= total)) - return 1; //no more clusters - - if ((segment_stop >= 0) && (m_pos >= segment_stop)) - return 1; //no more clusters - - pos = m_pos; - - //Read ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id < 0) //error (or underflow) - return static_cast(id); - - pos += len; //consume ID - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume length of size of element - - //pos now points to start of payload - - if (size == 0) //weird - { - m_pos = pos; - continue; - } - - const long long unknown_size = (1LL << (7 * len)) - 1; - -#if 0 //we must handle this to support live webm - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //TODO: allow this -#endif - - if ((segment_stop >= 0) && - (size != unknown_size) && - ((pos + size) > segment_stop)) - { - return E_FILE_FORMAT_INVALID; - } - -#if 0 //commented-out, to support incremental cluster parsing - len = static_cast(size); - - if ((pos + size) > avail) - return E_BUFFER_NOT_FULL; -#endif - - if (id == 0x0C53BB6B) //Cues ID - { - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //TODO: liberalize - - if (m_pCues == NULL) - { - const long long element_size = (pos - idpos) + size; - - m_pCues = new Cues(this, - pos, - size, - idpos, - element_size); - assert(m_pCues); //TODO - } - - m_pos = pos + size; //consume payload - continue; - } - - if (id != 0x0F43B675) //Cluster ID - { - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //TODO: liberalize - - m_pos = pos + size; //consume payload - continue; - } - - //We have a cluster. - - cluster_off = idpos - m_start; //relative pos - - if (size != unknown_size) - cluster_size = size; - - break; - } - - assert(cluster_off >= 0); //have cluster - - long long pos_; - long len_; - - status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_); - - if (status < 0) //error, or underflow - { - pos = pos_; - len = len_; - - return status; - } - - //status == 0 means "no block entries found" - //status > 0 means "found at least one block entry" - - //TODO: - //The issue here is that the segment increments its own - //pos ptr past the most recent cluster parsed, and then - //starts from there to parse the next cluster. If we - //don't know the size of the current cluster, then we - //must either parse its payload (as we do below), looking - //for the cluster (or cues) ID to terminate the parse. - //This isn't really what we want: rather, we really need - //a way to create the curr cluster object immediately. - //The pity is that cluster::parse can determine its own - //boundary, and we largely duplicate that same logic here. - // - //Maybe we need to get rid of our look-ahead preloading - //in source::parse??? - // - //As we're parsing the blocks in the curr cluster - //(in cluster::parse), we should have some way to signal - //to the segment that we have determined the boundary, - //so it can adjust its own segment::m_pos member. - // - //The problem is that we're asserting in asyncreadinit, - //because we adjust the pos down to the curr seek pos, - //and the resulting adjusted len is > 2GB. I'm suspicious - //that this is even correct, but even if it is, we can't - //be loading that much data in the cache anyway. - - const long idx = m_clusterCount; - - if (m_clusterPreloadCount > 0) - { - assert(idx < m_clusterSize); - - Cluster* const pCluster = m_clusters[idx]; - assert(pCluster); - assert(pCluster->m_index < 0); - - const long long off = pCluster->GetPosition(); - assert(off >= 0); - - if (off == cluster_off) //preloaded already - { - if (status == 0) //no entries found - return E_FILE_FORMAT_INVALID; - - if (cluster_size >= 0) - pos += cluster_size; - else - { - const long long element_size = pCluster->GetElementSize(); - - if (element_size <= 0) - return E_FILE_FORMAT_INVALID; //TODO: handle this case - - pos = pCluster->m_element_start + element_size; - } - - pCluster->m_index = idx; //move from preloaded to loaded - ++m_clusterCount; - --m_clusterPreloadCount; - - m_pos = pos; //consume payload - assert((segment_stop < 0) || (m_pos <= segment_stop)); - - return 0; //success - } - } - - if (status == 0) //no entries found - { - if (cluster_size < 0) - return E_FILE_FORMAT_INVALID; //TODO: handle this - - pos += cluster_size; - - if ((total >= 0) && (pos >= total)) - { - m_pos = total; - return 1; //no more clusters - } - - if ((segment_stop >= 0) && (pos >= segment_stop)) - { - m_pos = segment_stop; - return 1; //no more clusters - } - - m_pos = pos; - return 2; //try again - } - - //status > 0 means we have an entry - - Cluster* const pCluster = Cluster::Create(this, - idx, - cluster_off); - //element_size); - assert(pCluster); - - AppendCluster(pCluster); - assert(m_clusters); - assert(idx < m_clusterSize); - assert(m_clusters[idx] == pCluster); - - if (cluster_size >= 0) - { - pos += cluster_size; - - m_pos = pos; - assert((segment_stop < 0) || (m_pos <= segment_stop)); - - return 0; - } - - m_pUnknownSize = pCluster; - m_pos = -pos; - - return 0; //partial success, since we have a new cluster - - //status == 0 means "no block entries found" - - //pos designates start of payload - //m_pos has NOT been adjusted yet (in case we need to come back here) - -#if 0 - - if (cluster_size < 0) //unknown size - { - const long long payload_pos = pos; //absolute pos of cluster payload - - for (;;) //determine cluster size - { - if ((total >= 0) && (pos >= total)) - break; - - if ((segment_stop >= 0) && (pos >= segment_stop)) - break; //no more clusters - - //Read ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id < 0) //error (or underflow) - return static_cast(id); - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if (id == 0x0F43B675) //Cluster ID - break; - - if (id == 0x0C53BB6B) //Cues ID - break; - - switch (id) - { - case 0x20: //BlockGroup - case 0x23: //Simple Block - case 0x67: //TimeCode - case 0x2B: //PrevSize - break; - - default: - assert(false); - break; - } - - pos += len; //consume ID (of sub-element) - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field of element - - //pos now points to start of sub-element's payload - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //not allowed for sub-elements - - if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird - return E_FILE_FORMAT_INVALID; - - pos += size; //consume payload of sub-element - assert((segment_stop < 0) || (pos <= segment_stop)); - } //determine cluster size - - cluster_size = pos - payload_pos; - assert(cluster_size >= 0); - - pos = payload_pos; //reset and re-parse original cluster - } - - if (m_clusterPreloadCount > 0) - { - assert(idx < m_clusterSize); - - Cluster* const pCluster = m_clusters[idx]; - assert(pCluster); - assert(pCluster->m_index < 0); - - const long long off = pCluster->GetPosition(); - assert(off >= 0); - - if (off == cluster_off) //preloaded already - return E_FILE_FORMAT_INVALID; //subtle - } - - m_pos = pos + cluster_size; //consume payload - assert((segment_stop < 0) || (m_pos <= segment_stop)); - - return 2; //try to find another cluster - -#endif - -} - - -long Segment::DoLoadClusterUnknownSize( - long long& pos, - long& len) -{ - assert(m_pos < 0); - assert(m_pUnknownSize); - -#if 0 - assert(m_pUnknownSize->GetElementSize() < 0); //TODO: verify this - - const long long element_start = m_pUnknownSize->m_element_start; - - pos = -m_pos; - assert(pos > element_start); - - //We have already consumed the (cluster) ID and size fields. - //We just need to consume the blocks and other sub-elements - //of this cluster, until we discover the boundary. - - long long total, avail; - - long status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - - long long element_size = -1; - - for (;;) //determine cluster size - { - if ((total >= 0) && (pos >= total)) - { - element_size = total - element_start; - assert(element_size > 0); - - break; - } - - if ((segment_stop >= 0) && (pos >= segment_stop)) - { - element_size = segment_stop - element_start; - assert(element_size > 0); - - break; - } - - //Read ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id < 0) //error (or underflow) - return static_cast(id); - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster ID or Cues ID - { - element_size = pos - element_start; - assert(element_size > 0); - - break; - } - -#ifdef _DEBUG - switch (id) - { - case 0x20: //BlockGroup - case 0x23: //Simple Block - case 0x67: //TimeCode - case 0x2B: //PrevSize - break; - - default: - assert(false); - break; - } -#endif - - pos += len; //consume ID (of sub-element) - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field of element - - //pos now points to start of sub-element's payload - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //not allowed for sub-elements - - if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird - return E_FILE_FORMAT_INVALID; - - pos += size; //consume payload of sub-element - assert((segment_stop < 0) || (pos <= segment_stop)); - } //determine cluster size - - assert(element_size >= 0); - - m_pos = element_start + element_size; - m_pUnknownSize = 0; - - return 2; //continue parsing -#else - const long status = m_pUnknownSize->Parse(pos, len); - - if (status < 0) //error or underflow - return status; - - if (status == 0) //parsed a block - return 2; //continue parsing - - assert(status > 0); //nothing left to parse of this cluster - - const long long start = m_pUnknownSize->m_element_start; - - const long long size = m_pUnknownSize->GetElementSize(); - assert(size >= 0); - - pos = start + size; - m_pos = pos; - - m_pUnknownSize = 0; - - return 2; //continue parsing -#endif -} - - -void Segment::AppendCluster(Cluster* pCluster) -{ - assert(pCluster); - assert(pCluster->m_index >= 0); - - const long count = m_clusterCount + m_clusterPreloadCount; - - long& size = m_clusterSize; - assert(size >= count); - - const long idx = pCluster->m_index; - assert(idx == m_clusterCount); - - if (count >= size) - { - const long n = (size <= 0) ? 2048 : 2*size; - - Cluster** const qq = new Cluster*[n]; - Cluster** q = qq; - - Cluster** p = m_clusters; - Cluster** const pp = p + count; - - while (p != pp) - *q++ = *p++; - - delete[] m_clusters; - - m_clusters = qq; - size = n; - } - - if (m_clusterPreloadCount > 0) - { - assert(m_clusters); - - Cluster** const p = m_clusters + m_clusterCount; - assert(*p); - assert((*p)->m_index < 0); - - Cluster** q = p + m_clusterPreloadCount; - assert(q < (m_clusters + size)); - - for (;;) - { - Cluster** const qq = q - 1; - assert((*qq)->m_index < 0); - - *q = *qq; - q = qq; - - if (q == p) - break; - } - } - - m_clusters[idx] = pCluster; - ++m_clusterCount; -} - - -void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) -{ - assert(pCluster); - assert(pCluster->m_index < 0); - assert(idx >= m_clusterCount); - - const long count = m_clusterCount + m_clusterPreloadCount; - - long& size = m_clusterSize; - assert(size >= count); - - if (count >= size) - { - const long n = (size <= 0) ? 2048 : 2*size; - - Cluster** const qq = new Cluster*[n]; - Cluster** q = qq; - - Cluster** p = m_clusters; - Cluster** const pp = p + count; - - while (p != pp) - *q++ = *p++; - - delete[] m_clusters; - - m_clusters = qq; - size = n; - } - - assert(m_clusters); - - Cluster** const p = m_clusters + idx; - - Cluster** q = m_clusters + count; - assert(q >= p); - assert(q < (m_clusters + size)); - - while (q > p) - { - Cluster** const qq = q - 1; - assert((*qq)->m_index < 0); - - *q = *qq; - q = qq; - } - - m_clusters[idx] = pCluster; - ++m_clusterPreloadCount; -} - - -long Segment::Load() -{ - assert(m_clusters == NULL); - assert(m_clusterSize == 0); - assert(m_clusterCount == 0); - //assert(m_size >= 0); - - //Outermost (level 0) segment object has been constructed, - //and pos designates start of payload. We need to find the - //inner (level 1) elements. - - const long long header_status = ParseHeaders(); - - if (header_status < 0) //error - return static_cast(header_status); - - if (header_status > 0) //underflow - return E_BUFFER_NOT_FULL; - - assert(m_pInfo); - assert(m_pTracks); - - for (;;) - { - const int status = LoadCluster(); - - if (status < 0) //error - return status; - - if (status >= 1) //no more clusters - return 0; - } -} - - -SeekHead::SeekHead( - Segment* pSegment, - long long start, - long long size_, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_start(start), - m_size(size_), - m_element_start(element_start), - m_element_size(element_size), - m_entries(0), - m_entry_count(0), - m_void_elements(0), - m_void_element_count(0) -{ -} - - -SeekHead::~SeekHead() -{ - delete[] m_entries; - delete[] m_void_elements; -} - - -long SeekHead::Parse() -{ - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long pos = m_start; - const long long stop = m_start + m_size; - - //first count the seek head entries - - int entry_count = 0; - int void_element_count = 0; - - while (pos < stop) - { - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (id == 0x0DBB) //SeekEntry ID - ++entry_count; - else if (id == 0x6C) //Void ID - ++void_element_count; - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - m_entries = new (std::nothrow) Entry[entry_count]; - - if (m_entries == NULL) - return -1; - - m_void_elements = new (std::nothrow) VoidElement[void_element_count]; - - if (m_void_elements == NULL) - return -1; - - //now parse the entries and void elements - - Entry* pEntry = m_entries; - VoidElement* pVoidElement = m_void_elements; - - pos = m_start; - - while (pos < stop) - { - const long long idpos = pos; - - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (id == 0x0DBB) //SeekEntry ID - { - if (ParseEntry(pReader, pos, size, pEntry)) - { - Entry& e = *pEntry++; - - e.element_start = idpos; - e.element_size = (pos + size) - idpos; - } - } - else if (id == 0x6C) //Void ID - { - VoidElement& e = *pVoidElement++; - - e.element_start = idpos; - e.element_size = (pos + size) - idpos; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries); - assert(count_ >= 0); - assert(count_ <= entry_count); - - m_entry_count = static_cast(count_); - - count_ = ptrdiff_t(pVoidElement - m_void_elements); - assert(count_ >= 0); - assert(count_ <= void_element_count); - - m_void_element_count = static_cast(count_); - - return 0; -} - - -int SeekHead::GetCount() const -{ - return m_entry_count; -} - -const SeekHead::Entry* SeekHead::GetEntry(int idx) const -{ - if (idx < 0) - return 0; - - if (idx >= m_entry_count) - return 0; - - return m_entries + idx; -} - -int SeekHead::GetVoidElementCount() const -{ - return m_void_element_count; -} - -const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const -{ - if (idx < 0) - return 0; - - if (idx >= m_void_element_count) - return 0; - - return m_void_elements + idx; -} - - -#if 0 -void Segment::ParseCues(long long off) -{ - if (m_pCues) - return; - - //odbgstream os; - //os << "Segment::ParseCues (begin)" << endl; - - long long pos = m_start + off; - const long long element_start = pos; - const long long stop = m_start + m_size; - - long len; - - long long result = GetUIntLength(m_pReader, pos, len); - assert(result == 0); - assert((pos + len) <= stop); - - const long long idpos = pos; - - const long long id = ReadUInt(m_pReader, idpos, len); - assert(id == 0x0C53BB6B); //Cues ID - - pos += len; //consume ID - assert(pos < stop); - - //Read Size - - result = GetUIntLength(m_pReader, pos, len); - assert(result == 0); - assert((pos + len) <= stop); - - const long long size = ReadUInt(m_pReader, pos, len); - assert(size >= 0); - - pos += len; //consume length of size of element - assert((pos + size) <= stop); - - const long long element_size = size + pos - element_start; - - //Pos now points to start of payload - - m_pCues = new Cues(this, pos, size, element_start, element_size); - assert(m_pCues); //TODO - - //os << "Segment::ParseCues (end)" << endl; -} -#else -long Segment::ParseCues( - long long off, - long long& pos, - long& len) -{ - if (m_pCues) - return 0; //success - - if (off < 0) - return -1; - - long long total, avail; - - const int status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - pos = m_start + off; - - if ((total < 0) || (pos >= total)) - return 1; //don't bother parsing cues - - const long long element_start = pos; - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //underflow (weird) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; - - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id != 0x0C53BB6B) //Cues ID - return E_FILE_FORMAT_INVALID; - - pos += len; //consume ID - assert((segment_stop < 0) || (pos <= segment_stop)); - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(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(size); - - if (element_stop > avail) - return E_BUFFER_NOT_FULL; - - const long long element_size = element_stop - element_start; - - m_pCues = new (std::nothrow) Cues( - this, - pos, - size, - element_start, - element_size); - assert(m_pCues); //TODO - - return 0; //success -} -#endif - - -#if 0 -void Segment::ParseSeekEntry( - long long start, - long long size_) -{ - long long pos = start; - - const long long stop = start + size_; - - long len; - - const long long seekIdId = ReadUInt(m_pReader, pos, len); - //seekIdId; - assert(seekIdId == 0x13AB); //SeekID ID - assert((pos + len) <= stop); - - pos += len; //consume id - - const long long seekIdSize = ReadUInt(m_pReader, pos, len); - assert(seekIdSize >= 0); - assert((pos + len) <= stop); - - pos += len; //consume size - - const long long seekId = ReadUInt(m_pReader, pos, len); //payload - assert(seekId >= 0); - assert(len == seekIdSize); - assert((pos + len) <= stop); - - pos += seekIdSize; //consume payload - - const long long seekPosId = ReadUInt(m_pReader, pos, len); - //seekPosId; - assert(seekPosId == 0x13AC); //SeekPos ID - assert((pos + len) <= stop); - - pos += len; //consume id - - const long long seekPosSize = ReadUInt(m_pReader, pos, len); - assert(seekPosSize >= 0); - assert((pos + len) <= stop); - - pos += len; //consume size - assert((pos + seekPosSize) <= stop); - - const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize); - assert(seekOff >= 0); - assert(seekOff < m_size); - - pos += seekPosSize; //consume payload - assert(pos == stop); - - const long long seekPos = m_start + seekOff; - assert(seekPos < (m_start + m_size)); - - if (seekId == 0x0C53BB6B) //Cues ID - ParseCues(seekOff); -} -#else -bool SeekHead::ParseEntry( - IMkvReader* pReader, - long long start, - long long size_, - Entry* pEntry) -{ - if (size_ <= 0) - return false; - - long long pos = start; - const long long stop = start + size_; - - long len; - - //parse the container for the level-1 element ID - - const long long seekIdId = ReadUInt(pReader, pos, len); - //seekIdId; - - if (seekIdId != 0x13AB) //SeekID ID - return false; - - if ((pos + len) > stop) - return false; - - pos += len; //consume SeekID id - - const long long seekIdSize = ReadUInt(pReader, pos, len); - - if (seekIdSize <= 0) - return false; - - if ((pos + len) > stop) - return false; - - pos += len; //consume size of field - - if ((pos + seekIdSize) > stop) - return false; - - //Note that the SeekId payload really is serialized - //as a "Matroska integer", not as a plain binary value. - //In fact, Matroska requires that ID values in the - //stream exactly match the binary representation as listed - //in the Matroska specification. - // - //This parser is more liberal, and permits IDs to have - //any width. (This could make the representation in the stream - //different from what's in the spec, but it doesn't matter here, - //since we always normalize "Matroska integer" values.) - - pEntry->id = ReadUInt(pReader, pos, len); //payload - - if (pEntry->id <= 0) - return false; - - if (len != seekIdSize) - return false; - - pos += seekIdSize; //consume SeekID payload - - const long long seekPosId = ReadUInt(pReader, pos, len); - - if (seekPosId != 0x13AC) //SeekPos ID - return false; - - if ((pos + len) > stop) - return false; - - pos += len; //consume id - - const long long seekPosSize = ReadUInt(pReader, pos, len); - - if (seekPosSize <= 0) - return false; - - if ((pos + len) > stop) - return false; - - pos += len; //consume size - - if ((pos + seekPosSize) > stop) - return false; - - pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize); - - if (pEntry->pos < 0) - return false; - - pos += seekPosSize; //consume payload - - if (pos != stop) - return false; - - return true; -} -#endif - - -Cues::Cues( - Segment* pSegment, - long long start_, - long long size_, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_start(start_), - m_size(size_), - m_element_start(element_start), - m_element_size(element_size), - m_cue_points(NULL), - m_count(0), - m_preload_count(0), - m_pos(start_) -{ -} - - -Cues::~Cues() -{ - const long n = m_count + m_preload_count; - - CuePoint** p = m_cue_points; - CuePoint** const q = p + n; - - while (p != q) - { - CuePoint* const pCP = *p++; - assert(pCP); - - delete pCP; - } - - delete[] m_cue_points; -} - - -long Cues::GetCount() const -{ - if (m_cue_points == NULL) - return -1; - - return m_count; //TODO: really ignore preload count? -} - - -bool Cues::DoneParsing() const -{ - const long long stop = m_start + m_size; - return (m_pos >= stop); -} - - -void Cues::Init() const -{ - if (m_cue_points) - return; - - assert(m_count == 0); - assert(m_preload_count == 0); - - IMkvReader* const pReader = m_pSegment->m_pReader; - - const long long stop = m_start + m_size; - long long pos = m_start; - - long cue_points_size = 0; - - while (pos < stop) - { - const long long idpos = pos; - - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume ID - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); - assert((pos + len) <= stop); - - pos += len; //consume Size field - assert((pos + size) <= stop); - - if (id == 0x3B) //CuePoint ID - PreloadCuePoint(cue_points_size, idpos); - - pos += size; //consume payload - assert(pos <= stop); - } -} - - -void Cues::PreloadCuePoint( - long& cue_points_size, - long long pos) const -{ - assert(m_count == 0); - - if (m_preload_count >= cue_points_size) - { - const long n = (cue_points_size <= 0) ? 2048 : 2*cue_points_size; - - CuePoint** const qq = new CuePoint*[n]; - CuePoint** q = qq; //beginning of target - - CuePoint** p = m_cue_points; //beginning of source - CuePoint** const pp = p + m_preload_count; //end of source - - while (p != pp) - *q++ = *p++; - - delete[] m_cue_points; - - m_cue_points = qq; - cue_points_size = n; - } - - CuePoint* const pCP = new CuePoint(m_preload_count, pos); - m_cue_points[m_preload_count++] = pCP; -} - - -bool Cues::LoadCuePoint() const -{ - //odbgstream os; - //os << "Cues::LoadCuePoint" << endl; - - const long long stop = m_start + m_size; - - if (m_pos >= stop) - return false; //nothing else to do - - Init(); - - IMkvReader* const pReader = m_pSegment->m_pReader; - - while (m_pos < stop) - { - const long long idpos = m_pos; - - long len; - - const long long id = ReadUInt(pReader, m_pos, len); - assert(id >= 0); //TODO - assert((m_pos + len) <= stop); - - m_pos += len; //consume ID - - const long long size = ReadUInt(pReader, m_pos, len); - assert(size >= 0); - assert((m_pos + len) <= stop); - - m_pos += len; //consume Size field - assert((m_pos + size) <= stop); - - if (id != 0x3B) //CuePoint ID - { - m_pos += size; //consume payload - assert(m_pos <= stop); - - continue; - } - - assert(m_preload_count > 0); - - CuePoint* const pCP = m_cue_points[m_count]; - assert(pCP); - assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos)); - - pCP->Load(pReader); - ++m_count; - --m_preload_count; - - m_pos += size; //consume payload - assert(m_pos <= stop); - - return true; //yes, we loaded a cue point - } - - //return (m_pos < stop); - return false; //no, we did not load a cue point -} - - -bool Cues::Find( - long long time_ns, - const Track* pTrack, - const CuePoint*& pCP, - const CuePoint::TrackPosition*& pTP) const -{ - assert(time_ns >= 0); - assert(pTrack); - -#if 0 - LoadCuePoint(); //establish invariant - - assert(m_cue_points); - assert(m_count > 0); - - CuePoint** const ii = m_cue_points; - CuePoint** i = ii; - - CuePoint** const jj = ii + m_count + m_preload_count; - CuePoint** j = jj; - - pCP = *i; - assert(pCP); - - if (time_ns <= pCP->GetTime(m_pSegment)) - { - pTP = pCP->Find(pTrack); - return (pTP != NULL); - } - - IMkvReader* const pReader = m_pSegment->m_pReader; - - while (i < j) - { - //INVARIANT: - //[ii, i) <= time_ns - //[i, j) ? - //[j, jj) > time_ns - - CuePoint** const k = i + (j - i) / 2; - assert(k < jj); - - CuePoint* const pCP = *k; - assert(pCP); - - pCP->Load(pReader); - - const long long t = pCP->GetTime(m_pSegment); - - if (t <= time_ns) - i = k + 1; - else - j = k; - - assert(i <= j); - } - - assert(i == j); - assert(i <= jj); - assert(i > ii); - - pCP = *--i; - assert(pCP); - assert(pCP->GetTime(m_pSegment) <= time_ns); -#else - if (m_cue_points == NULL) - return false; - - if (m_count == 0) - return false; - - CuePoint** const ii = m_cue_points; - CuePoint** i = ii; - - CuePoint** const jj = ii + m_count; - CuePoint** j = jj; - - pCP = *i; - assert(pCP); - - if (time_ns <= pCP->GetTime(m_pSegment)) - { - pTP = pCP->Find(pTrack); - return (pTP != NULL); - } - - while (i < j) - { - //INVARIANT: - //[ii, i) <= time_ns - //[i, j) ? - //[j, jj) > time_ns - - CuePoint** const k = i + (j - i) / 2; - assert(k < jj); - - CuePoint* const pCP = *k; - assert(pCP); - - const long long t = pCP->GetTime(m_pSegment); - - if (t <= time_ns) - i = k + 1; - else - j = k; - - assert(i <= j); - } - - assert(i == j); - assert(i <= jj); - assert(i > ii); - - pCP = *--i; - assert(pCP); - assert(pCP->GetTime(m_pSegment) <= time_ns); -#endif - - //TODO: here and elsewhere, it's probably not correct to search - //for the cue point with this time, and then search for a matching - //track. In principle, the matching track could be on some earlier - //cue point, and with our current algorithm, we'd miss it. To make - //this bullet-proof, we'd need to create a secondary structure, - //with a list of cue points that apply to a track, and then search - //that track-based structure for a matching cue point. - - pTP = pCP->Find(pTrack); - return (pTP != NULL); -} - - -#if 0 -bool Cues::FindNext( - long long time_ns, - const Track* pTrack, - const CuePoint*& pCP, - const CuePoint::TrackPosition*& pTP) const -{ - pCP = 0; - pTP = 0; - - if (m_count == 0) - return false; - - assert(m_cue_points); - - const CuePoint* const* const ii = m_cue_points; - const CuePoint* const* i = ii; - - const CuePoint* const* const jj = ii + m_count; - const CuePoint* const* j = jj; - - while (i < j) - { - //INVARIANT: - //[ii, i) <= time_ns - //[i, j) ? - //[j, jj) > time_ns - - const CuePoint* const* const k = i + (j - i) / 2; - assert(k < jj); - - pCP = *k; - assert(pCP); - - const long long t = pCP->GetTime(m_pSegment); - - if (t <= time_ns) - i = k + 1; - else - j = k; - - assert(i <= j); - } - - assert(i == j); - assert(i <= jj); - - if (i >= jj) //time_ns is greater than max cue point - return false; - - pCP = *i; - assert(pCP); - assert(pCP->GetTime(m_pSegment) > time_ns); - - pTP = pCP->Find(pTrack); - return (pTP != NULL); -} -#endif - - -const CuePoint* Cues::GetFirst() const -{ - if (m_cue_points == NULL) - return NULL; - - if (m_count == 0) - return NULL; - -#if 0 - LoadCuePoint(); //init cues - - const size_t count = m_count + m_preload_count; - - if (count == 0) //weird - return NULL; -#endif - - CuePoint* const* const pp = m_cue_points; - assert(pp); - - CuePoint* const pCP = pp[0]; - assert(pCP); - assert(pCP->GetTimeCode() >= 0); - - return pCP; -} - - -const CuePoint* Cues::GetLast() const -{ - if (m_cue_points == NULL) - return NULL; - - if (m_count <= 0) - return NULL; - -#if 0 - LoadCuePoint(); //init cues - - const size_t count = m_count + m_preload_count; - - if (count == 0) //weird - return NULL; - - const size_t index = count - 1; - - CuePoint* const* const pp = m_cue_points; - assert(pp); - - CuePoint* const pCP = pp[index]; - assert(pCP); - - pCP->Load(m_pSegment->m_pReader); - assert(pCP->GetTimeCode() >= 0); -#else - const long index = m_count - 1; - - CuePoint* const* const pp = m_cue_points; - assert(pp); - - CuePoint* const pCP = pp[index]; - assert(pCP); - assert(pCP->GetTimeCode() >= 0); -#endif - - return pCP; -} - - -const CuePoint* Cues::GetNext(const CuePoint* pCurr) const -{ - if (pCurr == NULL) - return NULL; - - assert(pCurr->GetTimeCode() >= 0); - assert(m_cue_points); - assert(m_count >= 1); - -#if 0 - const size_t count = m_count + m_preload_count; - - size_t index = pCurr->m_index; - assert(index < count); - - CuePoint* const* const pp = m_cue_points; - assert(pp); - assert(pp[index] == pCurr); - - ++index; - - if (index >= count) - return NULL; - - CuePoint* const pNext = pp[index]; - assert(pNext); - - pNext->Load(m_pSegment->m_pReader); -#else - long index = pCurr->m_index; - assert(index < m_count); - - CuePoint* const* const pp = m_cue_points; - assert(pp); - assert(pp[index] == pCurr); - - ++index; - - if (index >= m_count) - return NULL; - - CuePoint* const pNext = pp[index]; - assert(pNext); - assert(pNext->GetTimeCode() >= 0); -#endif - - return pNext; -} - - -const BlockEntry* Cues::GetBlock( - const CuePoint* pCP, - const CuePoint::TrackPosition* pTP) const -{ - if (pCP == NULL) - return NULL; - - if (pTP == NULL) - return NULL; - - return m_pSegment->GetBlock(*pCP, *pTP); -} - - -const BlockEntry* Segment::GetBlock( - const CuePoint& cp, - const CuePoint::TrackPosition& tp) -{ - Cluster** const ii = m_clusters; - Cluster** i = ii; - - const long count = m_clusterCount + m_clusterPreloadCount; - - Cluster** const jj = ii + count; - Cluster** j = jj; - - while (i < j) - { - //INVARIANT: - //[ii, i) < pTP->m_pos - //[i, j) ? - //[j, jj) > pTP->m_pos - - Cluster** const k = i + (j - i) / 2; - assert(k < jj); - - Cluster* const pCluster = *k; - assert(pCluster); - - //const long long pos_ = pCluster->m_pos; - //assert(pos_); - //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); - - const long long pos = pCluster->GetPosition(); - assert(pos >= 0); - - if (pos < tp.m_pos) - i = k + 1; - else if (pos > tp.m_pos) - j = k; - else - return pCluster->GetEntry(cp, tp); - } - - assert(i == j); - //assert(Cluster::HasBlockEntries(this, tp.m_pos)); - - Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1); - assert(pCluster); - - const ptrdiff_t idx = i - m_clusters; - - PreloadCluster(pCluster, idx); - assert(m_clusters); - assert(m_clusterPreloadCount > 0); - assert(m_clusters[idx] == pCluster); - - return pCluster->GetEntry(cp, tp); -} - - -const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) -{ - if (requested_pos < 0) - return 0; - - Cluster** const ii = m_clusters; - Cluster** i = ii; - - const long count = m_clusterCount + m_clusterPreloadCount; - - Cluster** const jj = ii + count; - Cluster** j = jj; - - while (i < j) - { - //INVARIANT: - //[ii, i) < pTP->m_pos - //[i, j) ? - //[j, jj) > pTP->m_pos - - Cluster** const k = i + (j - i) / 2; - assert(k < jj); - - Cluster* const pCluster = *k; - assert(pCluster); - - //const long long pos_ = pCluster->m_pos; - //assert(pos_); - //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); - - const long long pos = pCluster->GetPosition(); - assert(pos >= 0); - - if (pos < requested_pos) - i = k + 1; - else if (pos > requested_pos) - j = k; - else - return pCluster; - } - - assert(i == j); - //assert(Cluster::HasBlockEntries(this, tp.m_pos)); - - Cluster* const pCluster = Cluster::Create( - this, - -1, - requested_pos); - //-1); - assert(pCluster); - - const ptrdiff_t idx = i - m_clusters; - - PreloadCluster(pCluster, idx); - assert(m_clusters); - assert(m_clusterPreloadCount > 0); - assert(m_clusters[idx] == pCluster); - - return pCluster; -} - - -CuePoint::CuePoint(long idx, long long pos) : - m_element_start(0), - m_element_size(0), - m_index(idx), - m_timecode(-1 * pos), - m_track_positions(NULL), - m_track_positions_count(0) -{ - assert(pos > 0); -} - - -CuePoint::~CuePoint() -{ - delete[] m_track_positions; -} - - -void CuePoint::Load(IMkvReader* pReader) -{ - //odbgstream os; - //os << "CuePoint::Load(begin): timecode=" << m_timecode << endl; - - if (m_timecode >= 0) //already loaded - return; - - assert(m_track_positions == NULL); - assert(m_track_positions_count == 0); - - long long pos_ = -m_timecode; - const long long element_start = pos_; - - long long stop; - - { - long len; - - const long long id = ReadUInt(pReader, pos_, len); - assert(id == 0x3B); //CuePoint ID - //assert((pos + len) <= stop); - - pos_ += len; //consume ID - - const long long size = ReadUInt(pReader, pos_, len); - assert(size >= 0); - //assert((pos + len) <= stop); - - pos_ += len; //consume Size field - //assert((pos + size) <= stop); - - //pos_ now points to start of payload - - stop = pos_ + size; - } - - const long long element_size = stop - element_start; - - long long pos = pos_; - - //First count number of track positions - - while (pos < stop) - { - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume ID - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); - assert((pos + len) <= stop); - - pos += len; //consume Size field - assert((pos + size) <= stop); - - if (id == 0x33) //CueTime ID - m_timecode = UnserializeUInt(pReader, pos, size); - - else if (id == 0x37) //CueTrackPosition(s) ID - ++m_track_positions_count; - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(m_timecode >= 0); - assert(m_track_positions_count > 0); - - //os << "CuePoint::Load(cont'd): idpos=" << idpos - // << " timecode=" << m_timecode - // << endl; - - m_track_positions = new TrackPosition[m_track_positions_count]; - - //Now parse track positions - - TrackPosition* p = m_track_positions; - pos = pos_; - - while (pos < stop) - { - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume ID - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); - assert((pos + len) <= stop); - - pos += len; //consume Size field - assert((pos + size) <= stop); - - if (id == 0x37) //CueTrackPosition(s) ID - { - TrackPosition& tp = *p++; - tp.Parse(pReader, pos, size); - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(size_t(p - m_track_positions) == m_track_positions_count); - - m_element_start = element_start; - m_element_size = element_size; -} - - - -void CuePoint::TrackPosition::Parse( - IMkvReader* pReader, - long long start_, - long long size_) -{ - const long long stop = start_ + size_; - long long pos = start_; - - m_track = -1; - m_pos = -1; - m_block = 1; //default - - while (pos < stop) - { - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume ID - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); - assert((pos + len) <= stop); - - pos += len; //consume Size field - assert((pos + size) <= stop); - - if (id == 0x77) //CueTrack ID - m_track = UnserializeUInt(pReader, pos, size); - - else if (id == 0x71) //CueClusterPos ID - m_pos = UnserializeUInt(pReader, pos, size); - - else if (id == 0x1378) //CueBlockNumber - m_block = UnserializeUInt(pReader, pos, size); - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(m_pos >= 0); - assert(m_track > 0); - //assert(m_block > 0); -} - - -const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const -{ - assert(pTrack); - - const long long n = pTrack->GetNumber(); - - const TrackPosition* i = m_track_positions; - const TrackPosition* const j = i + m_track_positions_count; - - while (i != j) - { - const TrackPosition& p = *i++; - - if (p.m_track == n) - return &p; - } - - return NULL; //no matching track number found -} - - -long long CuePoint::GetTimeCode() const -{ - return m_timecode; -} - -long long CuePoint::GetTime(const Segment* pSegment) const -{ - assert(pSegment); - assert(m_timecode >= 0); - - const SegmentInfo* const pInfo = pSegment->GetInfo(); - assert(pInfo); - - const long long scale = pInfo->GetTimeCodeScale(); - assert(scale >= 1); - - const long long time = scale * m_timecode; - - return time; -} - - -#if 0 -long long Segment::Unparsed() const -{ - if (m_size < 0) - return LLONG_MAX; - - const long long stop = m_start + m_size; - - const long long result = stop - m_pos; - assert(result >= 0); - - return result; -} -#else -bool Segment::DoneParsing() const -{ - if (m_size < 0) - { - long long total, avail; - - const int status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return true; //must assume done - - if (total < 0) - return false; //assume live stream - - return (m_pos >= total); - } - - const long long stop = m_start + m_size; - - return (m_pos >= stop); -} -#endif - - -const Cluster* Segment::GetFirst() const -{ - if ((m_clusters == NULL) || (m_clusterCount <= 0)) - return &m_eos; - - Cluster* const pCluster = m_clusters[0]; - assert(pCluster); - - return pCluster; -} - - -const Cluster* Segment::GetLast() const -{ - if ((m_clusters == NULL) || (m_clusterCount <= 0)) - return &m_eos; - - const long idx = m_clusterCount - 1; - - Cluster* const pCluster = m_clusters[idx]; - assert(pCluster); - - return pCluster; -} - - -unsigned long Segment::GetCount() const -{ - return m_clusterCount; -} - - -const Cluster* Segment::GetNext(const Cluster* pCurr) -{ - assert(pCurr); - assert(pCurr != &m_eos); - assert(m_clusters); - - long idx = pCurr->m_index; - - if (idx >= 0) - { - assert(m_clusterCount > 0); - assert(idx < m_clusterCount); - assert(pCurr == m_clusters[idx]); - - ++idx; - - if (idx >= m_clusterCount) - return &m_eos; //caller will LoadCluster as desired - - Cluster* const pNext = m_clusters[idx]; - assert(pNext); - assert(pNext->m_index >= 0); - assert(pNext->m_index == idx); - - return pNext; - } - - assert(m_clusterPreloadCount > 0); - - long long pos = pCurr->m_element_start; - - assert(m_size >= 0); //TODO - const long long stop = m_start + m_size; //end of segment - - { - long len; - - long long result = GetUIntLength(m_pReader, pos, len); - assert(result == 0); //TODO - assert((pos + len) <= stop); //TODO - - const long long id = ReadUInt(m_pReader, pos, len); - assert(id == 0x0F43B675); //Cluster ID //TODO - - pos += len; //consume ID - - //Read Size - result = GetUIntLength(m_pReader, pos, len); - assert(result == 0); //TODO - assert((pos + len) <= stop); //TODO - - const long long size = ReadUInt(m_pReader, pos, len); - assert(size > 0); //TODO - //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); //TODO - assert((pos + len) <= stop); //TODO - - const long long idpos = pos; //pos of next (potential) cluster - - const long long id = ReadUInt(m_pReader, idpos, len); - assert(id > 0); //TODO - - pos += len; //consume ID - - //Read Size - result = GetUIntLength(m_pReader, pos, len); - assert(result == 0); //TODO - assert((pos + len) <= stop); //TODO - - const long long size = ReadUInt(m_pReader, pos, len); - assert(size >= 0); //TODO - - pos += len; //consume length of size of element - assert((pos + size) <= stop); //TODO - - //Pos now points to start of payload - - if (size == 0) //weird - continue; - - if (id == 0x0F43B675) //Cluster ID - { - const long long off_next_ = idpos - m_start; - - long long pos_; - long len_; - - const long status = Cluster::HasBlockEntries( - this, - off_next_, - pos_, - len_); - - assert(status >= 0); - - if (status > 0) - { - off_next = off_next_; - break; - } - } - - pos += size; //consume payload - } - - if (off_next <= 0) - return 0; - - Cluster** const ii = m_clusters + m_clusterCount; - Cluster** i = ii; - - Cluster** const jj = ii + m_clusterPreloadCount; - Cluster** j = jj; - - while (i < j) - { - //INVARIANT: - //[0, i) < pos_next - //[i, j) ? - //[j, jj) > pos_next - - Cluster** const k = i + (j - i) / 2; - assert(k < jj); - - Cluster* const pNext = *k; - assert(pNext); - assert(pNext->m_index < 0); - - //const long long pos_ = pNext->m_pos; - //assert(pos_); - //pos = pos_ * ((pos_ < 0) ? -1 : 1); - - pos = pNext->GetPosition(); - - if (pos < off_next) - i = k + 1; - else if (pos > off_next) - j = k; - else - return pNext; - } - - assert(i == j); - - Cluster* const pNext = Cluster::Create(this, - -1, - off_next); - assert(pNext); - - const ptrdiff_t idx_next = i - m_clusters; //insertion position - - PreloadCluster(pNext, idx_next); - assert(m_clusters); - assert(idx_next < m_clusterSize); - assert(m_clusters[idx_next] == pNext); - - return pNext; -} - - -long Segment::ParseNext( - const Cluster* pCurr, - const Cluster*& pResult, - long long& pos, - long& len) -{ - assert(pCurr); - assert(!pCurr->EOS()); - assert(m_clusters); - - pResult = 0; - - if (pCurr->m_index >= 0) //loaded (not merely preloaded) - { - assert(m_clusters[pCurr->m_index] == pCurr); - - const long next_idx = pCurr->m_index + 1; - - if (next_idx < m_clusterCount) - { - pResult = m_clusters[next_idx]; - return 0; //success - } - - //curr cluster is last among loaded - - const long result = LoadCluster(pos, len); - - if (result < 0) //error or underflow - return result; - - if (result > 0) //no more clusters - { - //pResult = &m_eos; - return 1; - } - - pResult = GetLast(); - return 0; //success - } - - assert(m_pos > 0); - - long long total, avail; - - long status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - - //interrogate curr cluster - - pos = pCurr->m_element_start; - - if (pCurr->m_element_size >= 0) - pos += pCurr->m_element_size; - else - { - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(m_pReader, pos, len); - - if (id != 0x0F43B675) //weird: not Cluster ID - return -1; - - pos += len; //consume ID - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) //TODO: should never happen - return E_FILE_FORMAT_INVALID; //TODO: resolve this - - //assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); - - if ((segment_stop >= 0) && ((pos + size) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - //Pos now points to start of payload - - pos += size; //consume payload (that is, the current cluster) - assert((segment_stop < 0) || (pos <= segment_stop)); - - //By consuming the payload, we are assuming that the curr - //cluster isn't interesting. That is, we don't bother checking - //whether the payload of the curr cluster is less than what - //happens to be available (obtained via IMkvReader::Length). - //Presumably the caller has already dispensed with the current - //cluster, and really does want the next cluster. - } - - //pos now points to just beyond the last fully-loaded cluster - - for (;;) - { - const long status = DoParseNext(pResult, pos, len); - - if (status <= 1) - return status; - } -} - - -long Segment::DoParseNext( - const Cluster*& pResult, - long long& pos, - long& len) -{ - long long total, avail; - - long status = m_pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; - - //Parse next cluster. This is strictly a parsing activity. - //Creation of a new cluster object happens later, after the - //parsing is done. - - long long off_next = 0; - long long cluster_size = -1; - - for (;;) - { - if ((total >= 0) && (pos >= total)) - return 1; //EOF - - if ((segment_stop >= 0) && (pos >= segment_stop)) - return 1; //EOF - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; //absolute - const long long idoff = pos - m_start; //relative - - const long long id = ReadUInt(m_pReader, idpos, len); //absolute - - if (id < 0) //error - return static_cast(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(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(size); - - pos += len; //consume length of size of element - - //Pos now points to start of payload - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if ((segment_stop >= 0) && - (size != unknown_size) && - ((pos + size) > segment_stop)) - { - return E_FILE_FORMAT_INVALID; - } - - if (id == 0x0C53BB6B) //Cues ID - { - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - const long long element_stop = pos + size; - - if ((segment_stop >= 0) && (element_stop > segment_stop)) - return E_FILE_FORMAT_INVALID; - - const long long element_start = idpos; - const long long element_size = element_stop - element_start; - - if (m_pCues == NULL) - { - m_pCues = new Cues(this, - pos, - size, - element_start, - element_size); - assert(m_pCues); //TODO - } - - pos += size; //consume payload - assert((segment_stop < 0) || (pos <= segment_stop)); - - continue; - } - - if (id != 0x0F43B675) //not a Cluster ID - { - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - pos += size; //consume payload - assert((segment_stop < 0) || (pos <= segment_stop)); - - continue; - } - -#if 0 //this is commented-out to support incremental cluster parsing - len = static_cast(size); - - if (element_stop > avail) - return E_BUFFER_NOT_FULL; -#endif - - //We have a cluster. - - off_next = idoff; - - if (size != unknown_size) - cluster_size = size; - - break; - } - - assert(off_next > 0); //have cluster - - //We have parsed the next cluster. - //We have not created a cluster object yet. What we need - //to do now is determine whether it has already be preloaded - //(in which case, an object for this cluster has already been - //created), and if not, create a new cluster object. - - Cluster** const ii = m_clusters + m_clusterCount; - Cluster** i = ii; - - Cluster** const jj = ii + m_clusterPreloadCount; - Cluster** j = jj; - - while (i < j) - { - //INVARIANT: - //[0, i) < pos_next - //[i, j) ? - //[j, jj) > pos_next - - Cluster** const k = i + (j - i) / 2; - assert(k < jj); - - const Cluster* const pNext = *k; - assert(pNext); - assert(pNext->m_index < 0); - - pos = pNext->GetPosition(); - assert(pos >= 0); - - if (pos < off_next) - i = k + 1; - else if (pos > off_next) - j = k; - else - { - pResult = pNext; - return 0; //success - } - } - - assert(i == j); - - long long pos_; - long len_; - - status = Cluster::HasBlockEntries(this, off_next, pos_, len_); - - if (status < 0) //error or underflow - { - pos = pos_; - len = len_; - - return status; - } - - if (status > 0) //means "found at least one block entry" - { - Cluster* const pNext = Cluster::Create(this, - -1, //preloaded - off_next); - //element_size); - assert(pNext); - - const ptrdiff_t idx_next = i - m_clusters; //insertion position - - PreloadCluster(pNext, idx_next); - assert(m_clusters); - assert(idx_next < m_clusterSize); - assert(m_clusters[idx_next] == pNext); - - pResult = pNext; - return 0; //success - } - - //status == 0 means "no block entries found" - - if (cluster_size < 0) //unknown size - { - const long long payload_pos = pos; //absolute pos of cluster payload - - for (;;) //determine cluster size - { - if ((total >= 0) && (pos >= total)) - break; - - if ((segment_stop >= 0) && (pos >= segment_stop)) - break; //no more clusters - - //Read ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long idpos = pos; - const long long id = ReadUInt(m_pReader, idpos, len); - - if (id < 0) //error (or underflow) - return static_cast(id); - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if (id == 0x0F43B675) //Cluster ID - break; - - if (id == 0x0C53BB6B) //Cues ID - break; - - pos += len; //consume ID (of sub-element) - - //Read Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(m_pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field of element - - //pos now points to start of sub-element's payload - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //not allowed for sub-elements - - if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird - return E_FILE_FORMAT_INVALID; - - pos += size; //consume payload of sub-element - assert((segment_stop < 0) || (pos <= segment_stop)); - } //determine cluster size - - cluster_size = pos - payload_pos; - assert(cluster_size >= 0); //TODO: handle cluster_size = 0 - - pos = payload_pos; //reset and re-parse original cluster - } - - pos += cluster_size; //consume payload - assert((segment_stop < 0) || (pos <= segment_stop)); - - return 2; //try to find a cluster that follows next -} - - -const Cluster* Segment::FindCluster(long long time_ns) const -{ - if ((m_clusters == NULL) || (m_clusterCount <= 0)) - return &m_eos; - - { - Cluster* const pCluster = m_clusters[0]; - assert(pCluster); - assert(pCluster->m_index == 0); - - if (time_ns <= pCluster->GetTime()) - return pCluster; - } - - //Binary search of cluster array - - long i = 0; - long j = m_clusterCount; - - while (i < j) - { - //INVARIANT: - //[0, i) <= time_ns - //[i, j) ? - //[j, m_clusterCount) > time_ns - - const long k = i + (j - i) / 2; - assert(k < m_clusterCount); - - Cluster* const pCluster = m_clusters[k]; - assert(pCluster); - assert(pCluster->m_index == k); - - const long long t = pCluster->GetTime(); - - if (t <= time_ns) - i = k + 1; - else - j = k; - - assert(i <= j); - } - - assert(i == j); - assert(i > 0); - assert(i <= m_clusterCount); - - const long k = i - 1; - - Cluster* const pCluster = m_clusters[k]; - assert(pCluster); - assert(pCluster->m_index == k); - assert(pCluster->GetTime() <= time_ns); - - return pCluster; -} - - -#if 0 -const BlockEntry* Segment::Seek( - long long time_ns, - const Track* pTrack) const -{ - assert(pTrack); - - if ((m_clusters == NULL) || (m_clusterCount <= 0)) - return pTrack->GetEOS(); - - Cluster** const i = m_clusters; - assert(i); - - { - Cluster* const pCluster = *i; - assert(pCluster); - assert(pCluster->m_index == 0); //m_clusterCount > 0 - assert(pCluster->m_pSegment == this); - - if (time_ns <= pCluster->GetTime()) - return pCluster->GetEntry(pTrack); - } - - Cluster** const j = i + m_clusterCount; - - if (pTrack->GetType() == 2) //audio - { - //TODO: we could decide to use cues for this, as we do for video. - //But we only use it for video because looking around for a keyframe - //can get expensive. Audio doesn't require anything special so a - //straight cluster search is good enough (we assume). - - Cluster** lo = i; - Cluster** hi = j; - - while (lo < hi) - { - //INVARIANT: - //[i, lo) <= time_ns - //[lo, hi) ? - //[hi, j) > time_ns - - Cluster** const mid = lo + (hi - lo) / 2; - assert(mid < hi); - - Cluster* const pCluster = *mid; - assert(pCluster); - assert(pCluster->m_index == long(mid - m_clusters)); - assert(pCluster->m_pSegment == this); - - const long long t = pCluster->GetTime(); - - if (t <= time_ns) - lo = mid + 1; - else - hi = mid; - - assert(lo <= hi); - } - - assert(lo == hi); - assert(lo > i); - assert(lo <= j); - - while (lo > i) - { - Cluster* const pCluster = *--lo; - assert(pCluster); - assert(pCluster->GetTime() <= time_ns); - - const BlockEntry* const pBE = pCluster->GetEntry(pTrack); - - if ((pBE != 0) && !pBE->EOS()) - return pBE; - - //landed on empty cluster (no entries) - } - - return pTrack->GetEOS(); //weird - } - - assert(pTrack->GetType() == 1); //video - - Cluster** lo = i; - Cluster** hi = j; - - while (lo < hi) - { - //INVARIANT: - //[i, lo) <= time_ns - //[lo, hi) ? - //[hi, j) > time_ns - - Cluster** const mid = lo + (hi - lo) / 2; - assert(mid < hi); - - Cluster* const pCluster = *mid; - assert(pCluster); - - const long long t = pCluster->GetTime(); - - if (t <= time_ns) - lo = mid + 1; - else - hi = mid; - - assert(lo <= hi); - } - - assert(lo == hi); - assert(lo > i); - assert(lo <= j); - - Cluster* pCluster = *--lo; - assert(pCluster); - assert(pCluster->GetTime() <= time_ns); - - { - const BlockEntry* const pBE = pCluster->GetEntry(pTrack, time_ns); - - if ((pBE != 0) && !pBE->EOS()) //found a keyframe - return pBE; - } - - const VideoTrack* const pVideo = static_cast(pTrack); - - while (lo != i) - { - pCluster = *--lo; - assert(pCluster); - assert(pCluster->GetTime() <= time_ns); - - const BlockEntry* const pBlockEntry = pCluster->GetMaxKey(pVideo); - - if ((pBlockEntry != 0) && !pBlockEntry->EOS()) - return pBlockEntry; - } - - //weird: we're on the first cluster, but no keyframe found - //should never happen but we must return something anyway - - return pTrack->GetEOS(); -} -#endif - - -#if 0 -bool Segment::SearchCues( - long long time_ns, - Track* pTrack, - Cluster*& pCluster, - const BlockEntry*& pBlockEntry, - const CuePoint*& pCP, - const CuePoint::TrackPosition*& pTP) -{ - if (pTrack->GetType() != 1) //not video - return false; //TODO: for now, just handle video stream - - if (m_pCues == NULL) - return false; - - if (!m_pCues->Find(time_ns, pTrack, pCP, pTP)) - return false; //weird - - assert(pCP); - assert(pTP); - assert(pTP->m_track == pTrack->GetNumber()); - - //We have the cue point and track position we want, - //so we now need to search for the cluster having - //the indicated position. - - return GetCluster(pCP, pTP, pCluster, pBlockEntry); -} -#endif - - -const Tracks* Segment::GetTracks() const -{ - return m_pTracks; -} - - -const SegmentInfo* Segment::GetInfo() const -{ - return m_pInfo; -} - - -const Cues* Segment::GetCues() const -{ - return m_pCues; -} - - -const Chapters* Segment::GetChapters() const -{ - return m_pChapters; -} - - -const SeekHead* Segment::GetSeekHead() const -{ - return m_pSeekHead; -} - - -long long Segment::GetDuration() const -{ - assert(m_pInfo); - return m_pInfo->GetDuration(); -} - - -Chapters::Chapters( - Segment* pSegment, - long long payload_start, - long long payload_size, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_start(payload_start), - m_size(payload_size), - m_element_start(element_start), - m_element_size(element_size), - m_editions(NULL), - m_editions_size(0), - m_editions_count(0) -{ -} - - -Chapters::~Chapters() -{ - while (m_editions_count > 0) - { - Edition& e = m_editions[--m_editions_count]; - e.Clear(); - } -} - - -long Chapters::Parse() -{ - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long pos = m_start; // payload start - const long long stop = pos + m_size; // payload stop - - while (pos < stop) - { - long long id, size; - - long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) // error - return status; - - if (size == 0) // weird - continue; - - if (id == 0x05B9) // EditionEntry ID - { - status = ParseEdition(pos, size); - - if (status < 0) // error - return status; - } - - pos += size; - assert(pos <= stop); - } - - assert(pos == stop); - return 0; -} - - -int Chapters::GetEditionCount() const -{ - return m_editions_count; -} - - -const Chapters::Edition* Chapters::GetEdition(int idx) const -{ - if (idx < 0) - return NULL; - - if (idx >= m_editions_count) - return NULL; - - return m_editions + idx; -} - - -bool Chapters::ExpandEditionsArray() -{ - if (m_editions_size > m_editions_count) - return true; // nothing else to do - - const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size; - - Edition* const editions = new (std::nothrow) Edition[size]; - - if (editions == NULL) - return false; - - for (int idx = 0; idx < m_editions_count; ++idx) - { - m_editions[idx].ShallowCopy(editions[idx]); - } - - delete[] m_editions; - m_editions = editions; - - m_editions_size = size; - return true; -} - - -long Chapters::ParseEdition( - long long pos, - long long size) -{ - if (!ExpandEditionsArray()) - return -1; - - Edition& e = m_editions[m_editions_count++]; - e.Init(); - - return e.Parse(m_pSegment->m_pReader, pos, size); -} - - -Chapters::Edition::Edition() -{ -} - - -Chapters::Edition::~Edition() -{ -} - - -int Chapters::Edition::GetAtomCount() const -{ - return m_atoms_count; -} - - -const Chapters::Atom* Chapters::Edition::GetAtom(int index) const -{ - if (index < 0) - return NULL; - - if (index >= m_atoms_count) - return NULL; - - return m_atoms + index; -} - - -void Chapters::Edition::Init() -{ - m_atoms = NULL; - m_atoms_size = 0; - m_atoms_count = 0; -} - - -void Chapters::Edition::ShallowCopy(Edition& rhs) const -{ - rhs.m_atoms = m_atoms; - rhs.m_atoms_size = m_atoms_size; - rhs.m_atoms_count = m_atoms_count; -} - - -void Chapters::Edition::Clear() -{ - while (m_atoms_count > 0) - { - Atom& a = m_atoms[--m_atoms_count]; - a.Clear(); - } - - delete[] m_atoms; - m_atoms = NULL; - - m_atoms_size = 0; -} - - -long Chapters::Edition::Parse( - IMkvReader* pReader, - long long pos, - long long size) -{ - const long long stop = pos + size; - - while (pos < stop) - { - long long id, size; - - long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) // error - return status; - - if (size == 0) // weird - continue; - - if (id == 0x36) // Atom ID - { - status = ParseAtom(pReader, pos, size); - - if (status < 0) // error - return status; - } - - pos += size; - assert(pos <= stop); - } - - assert(pos == stop); - return 0; -} - - -long Chapters::Edition::ParseAtom( - IMkvReader* pReader, - long long pos, - long long size) -{ - if (!ExpandAtomsArray()) - return -1; - - Atom& a = m_atoms[m_atoms_count++]; - a.Init(); - - return a.Parse(pReader, pos, size); -} - - -bool Chapters::Edition::ExpandAtomsArray() -{ - if (m_atoms_size > m_atoms_count) - return true; // nothing else to do - - const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size; - - Atom* const atoms = new (std::nothrow) Atom[size]; - - if (atoms == NULL) - return false; - - for (int idx = 0; idx < m_atoms_count; ++idx) - { - m_atoms[idx].ShallowCopy(atoms[idx]); - } - - delete[] m_atoms; - m_atoms = atoms; - - m_atoms_size = size; - return true; -} - - -Chapters::Atom::Atom() -{ -} - - -Chapters::Atom::~Atom() -{ -} - - -unsigned long long Chapters::Atom::GetUID() const -{ - return m_uid; -} - - -const char* Chapters::Atom::GetStringUID() const -{ - return m_string_uid; -} - - -long long Chapters::Atom::GetStartTimecode() const -{ - return m_start_timecode; -} - - -long long Chapters::Atom::GetStopTimecode() const -{ - return m_stop_timecode; -} - - -long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const -{ - return GetTime(pChapters, m_start_timecode); -} - - -long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const -{ - return GetTime(pChapters, m_stop_timecode); -} - - -int Chapters::Atom::GetDisplayCount() const -{ - return m_displays_count; -} - - -const Chapters::Display* Chapters::Atom::GetDisplay(int index) const -{ - if (index < 0) - return NULL; - - if (index >= m_displays_count) - return NULL; - - return m_displays + index; -} - - -void Chapters::Atom::Init() -{ - m_string_uid = NULL; - m_uid = 0; - m_start_timecode = -1; - m_stop_timecode = -1; - - m_displays = NULL; - m_displays_size = 0; - m_displays_count = 0; -} - - -void Chapters::Atom::ShallowCopy(Atom& rhs) const -{ - rhs.m_string_uid = m_string_uid; - rhs.m_uid = m_uid; - rhs.m_start_timecode = m_start_timecode; - rhs.m_stop_timecode = m_stop_timecode; - - rhs.m_displays = m_displays; - rhs.m_displays_size = m_displays_size; - rhs.m_displays_count = m_displays_count; -} - - -void Chapters::Atom::Clear() -{ - delete[] m_string_uid; - m_string_uid = NULL; - - while (m_displays_count > 0) - { - Display& d = m_displays[--m_displays_count]; - d.Clear(); - } - - delete[] m_displays; - m_displays = NULL; - - m_displays_size = 0; -} - - -long Chapters::Atom::Parse( - IMkvReader* pReader, - long long pos, - long long size) -{ - const long long stop = pos + size; - - while (pos < stop) - { - long long id, size; - - long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) // error - return status; - - if (size == 0) // weird - continue; - - if (id == 0x00) // Display ID - { - status = ParseDisplay(pReader, pos, size); - - if (status < 0) // error - return status; - } - else if (id == 0x1654) // StringUID ID - { - status = UnserializeString(pReader, pos, size, m_string_uid); - - if (status < 0) // error - return status; - } - else if (id == 0x33C4) // UID ID - { - const long long val = UnserializeUInt(pReader, pos, size); - - if (val < 0) // error - return static_cast(val); - - m_uid = val; - } - else if (id == 0x11) // TimeStart ID - { - const long long val = UnserializeUInt(pReader, pos, size); - - if (val < 0) // error - return static_cast(val); - - m_start_timecode = val; - } - else if (id == 0x12) // TimeEnd ID - { - const long long val = UnserializeUInt(pReader, pos, size); - - if (val < 0) // error - return static_cast(val); - - m_stop_timecode = val; - } - - pos += size; - assert(pos <= stop); - } - - assert(pos == stop); - return 0; -} - - -long long Chapters::Atom::GetTime( - const Chapters* pChapters, - long long timecode) -{ - if (pChapters == NULL) - return -1; - - Segment* const pSegment = pChapters->m_pSegment; - - if (pSegment == NULL) // weird - return -1; - - const SegmentInfo* const pInfo = pSegment->GetInfo(); - - if (pInfo == NULL) - return -1; - - const long long timecode_scale = pInfo->GetTimeCodeScale(); - - if (timecode_scale < 1) // weird - return -1; - - if (timecode < 0) - return -1; - - const long long result = timecode_scale * timecode; - - return result; -} - - -long Chapters::Atom::ParseDisplay( - IMkvReader* pReader, - long long pos, - long long size) -{ - if (!ExpandDisplaysArray()) - return -1; - - Display& d = m_displays[m_displays_count++]; - d.Init(); - - return d.Parse(pReader, pos, size); -} - - -bool Chapters::Atom::ExpandDisplaysArray() -{ - if (m_displays_size > m_displays_count) - return true; // nothing else to do - - const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size; - - Display* const displays = new (std::nothrow) Display[size]; - - if (displays == NULL) - return false; - - for (int idx = 0; idx < m_displays_count; ++idx) - { - m_displays[idx].ShallowCopy(displays[idx]); - } - - delete[] m_displays; - m_displays = displays; - - m_displays_size = size; - return true; -} - - -Chapters::Display::Display() -{ -} - - -Chapters::Display::~Display() -{ -} - - -const char* Chapters::Display::GetString() const -{ - return m_string; -} - - -const char* Chapters::Display::GetLanguage() const -{ - return m_language; -} - - -const char* Chapters::Display::GetCountry() const -{ - return m_country; -} - - -void Chapters::Display::Init() -{ - m_string = NULL; - m_language = NULL; - m_country = NULL; -} - - -void Chapters::Display::ShallowCopy(Display& rhs) const -{ - rhs.m_string = m_string; - rhs.m_language = m_language; - rhs.m_country = m_country; -} - - -void Chapters::Display::Clear() -{ - delete[] m_string; - m_string = NULL; - - delete[] m_language; - m_language = NULL; - - delete[] m_country; - m_country = NULL; -} - - -long Chapters::Display::Parse( - IMkvReader* pReader, - long long pos, - long long size) -{ - const long long stop = pos + size; - - while (pos < stop) - { - long long id, size; - - long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) // error - return status; - - if (size == 0) // weird - continue; - - if (id == 0x05) // ChapterString ID - { - status = UnserializeString(pReader, pos, size, m_string); - - if (status) - return status; - } - else if (id == 0x037C) // ChapterLanguage ID - { - status = UnserializeString(pReader, pos, size, m_language); - - if (status) - return status; - } - else if (id == 0x037E) // ChapterCountry ID - { - status = UnserializeString(pReader, pos, size, m_country); - - if (status) - return status; - } - - pos += size; - assert(pos <= stop); - } - - assert(pos == stop); - return 0; -} - - -SegmentInfo::SegmentInfo( - Segment* pSegment, - long long start, - long long size_, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_start(start), - m_size(size_), - m_element_start(element_start), - m_element_size(element_size), - m_pMuxingAppAsUTF8(NULL), - m_pWritingAppAsUTF8(NULL), - m_pTitleAsUTF8(NULL) -{ -} - -SegmentInfo::~SegmentInfo() -{ - delete[] m_pMuxingAppAsUTF8; - m_pMuxingAppAsUTF8 = NULL; - - delete[] m_pWritingAppAsUTF8; - m_pWritingAppAsUTF8 = NULL; - - delete[] m_pTitleAsUTF8; - m_pTitleAsUTF8 = NULL; -} - - -long SegmentInfo::Parse() -{ - assert(m_pMuxingAppAsUTF8 == NULL); - assert(m_pWritingAppAsUTF8 == NULL); - assert(m_pTitleAsUTF8 == NULL); - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long pos = m_start; - const long long stop = m_start + m_size; - - m_timecodeScale = 1000000; - m_duration = -1; - - while (pos < stop) - { - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (id == 0x0AD7B1) //Timecode Scale - { - m_timecodeScale = UnserializeUInt(pReader, pos, size); - - if (m_timecodeScale <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x0489) //Segment duration - { - const long status = UnserializeFloat( - pReader, - pos, - size, - m_duration); - - if (status < 0) - return status; - - if (m_duration < 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x0D80) //MuxingApp - { - const long status = UnserializeString( - pReader, - pos, - size, - m_pMuxingAppAsUTF8); - - if (status) - return status; - } - else if (id == 0x1741) //WritingApp - { - const long status = UnserializeString( - pReader, - pos, - size, - m_pWritingAppAsUTF8); - - if (status) - return status; - } - else if (id == 0x3BA9) //Title - { - const long status = UnserializeString( - pReader, - pos, - size, - m_pTitleAsUTF8); - - if (status) - return status; - } - - pos += size; - assert(pos <= stop); - } - - assert(pos == stop); - - return 0; -} - - -long long SegmentInfo::GetTimeCodeScale() const -{ - return m_timecodeScale; -} - - -long long SegmentInfo::GetDuration() const -{ - if (m_duration < 0) - return -1; - - assert(m_timecodeScale >= 1); - - const double dd = double(m_duration) * double(m_timecodeScale); - const long long d = static_cast(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(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(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(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(count); -} - -long ContentEncoding::ParseContentEncAESSettingsEntry( - long long start, - long long size, - IMkvReader* pReader, - ContentEncAESSettings* aes) { - assert(pReader); - assert(aes); - - long long pos = start; - const long long stop = start + size; - - while (pos < stop) { - long long id, size; - const long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - if (id == 0x7E8) { - // AESSettingsCipherMode - aes->cipher_mode = UnserializeUInt(pReader, pos, size); - if (aes->cipher_mode != 1) - return E_FILE_FORMAT_INVALID; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - return 0; -} - -long ContentEncoding::ParseContentEncodingEntry(long long start, - long long size, - IMkvReader* pReader) { - assert(pReader); - - long long pos = start; - const long long stop = start + size; - - // Count ContentCompression and ContentEncryption elements. - int compression_count = 0; - int encryption_count = 0; - - while (pos < stop) { - long long id, size; - const long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - if (id == 0x1034) // ContentCompression ID - ++compression_count; - - if (id == 0x1035) // ContentEncryption ID - ++encryption_count; - - pos += size; //consume payload - assert(pos <= stop); - } - - if (compression_count <= 0 && encryption_count <= 0) - return -1; - - if (compression_count > 0) { - compression_entries_ = - new (std::nothrow) ContentCompression*[compression_count]; - if (!compression_entries_) - return -1; - compression_entries_end_ = compression_entries_; - } - - if (encryption_count > 0) { - encryption_entries_ = - new (std::nothrow) ContentEncryption*[encryption_count]; - if (!encryption_entries_) { - delete [] compression_entries_; - return -1; - } - encryption_entries_end_ = encryption_entries_; - } - - pos = start; - while (pos < stop) { - long long id, size; - long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - if (id == 0x1031) { - // ContentEncodingOrder - encoding_order_ = UnserializeUInt(pReader, pos, size); - } else if (id == 0x1032) { - // ContentEncodingScope - encoding_scope_ = UnserializeUInt(pReader, pos, size); - if (encoding_scope_ < 1) - return -1; - } else if (id == 0x1033) { - // ContentEncodingType - encoding_type_ = UnserializeUInt(pReader, pos, size); - } else if (id == 0x1034) { - // ContentCompression ID - ContentCompression* const compression = - new (std::nothrow) ContentCompression(); - if (!compression) - return -1; - - status = ParseCompressionEntry(pos, size, pReader, compression); - if (status) { - delete compression; - return status; - } - *compression_entries_end_++ = compression; - } else if (id == 0x1035) { - // ContentEncryption ID - ContentEncryption* const encryption = - new (std::nothrow) ContentEncryption(); - if (!encryption) - return -1; - - status = ParseEncryptionEntry(pos, size, pReader, encryption); - if (status) { - delete encryption; - return status; - } - *encryption_entries_end_++ = encryption; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - return 0; -} - -long ContentEncoding::ParseCompressionEntry( - long long start, - long long size, - IMkvReader* pReader, - ContentCompression* compression) { - assert(pReader); - assert(compression); - - long long pos = start; - const long long stop = start + size; - - bool valid = false; - - while (pos < stop) { - long long id, size; - const long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - if (id == 0x254) { - // ContentCompAlgo - long long algo = UnserializeUInt(pReader, pos, size); - if (algo < 0) - return E_FILE_FORMAT_INVALID; - compression->algo = algo; - valid = true; - } else if (id == 0x255) { - // ContentCompSettings - if (size <= 0) - return E_FILE_FORMAT_INVALID; - - const size_t buflen = static_cast(size); - typedef unsigned char* buf_t; - const buf_t buf = new (std::nothrow) unsigned char[buflen]; - if (buf == NULL) - return -1; - - const int read_status = pReader->Read(pos, buflen, buf); - if (read_status) { - delete [] buf; - return status; - } - - compression->settings = buf; - compression->settings_len = buflen; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - // ContentCompAlgo is mandatory - if (!valid) - return E_FILE_FORMAT_INVALID; - - return 0; -} - -long ContentEncoding::ParseEncryptionEntry( - long long start, - long long size, - IMkvReader* pReader, - ContentEncryption* encryption) { - assert(pReader); - assert(encryption); - - long long pos = start; - const long long stop = start + size; - - while (pos < stop) { - long long id, size; - const long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - if (id == 0x7E1) { - // ContentEncAlgo - encryption->algo = UnserializeUInt(pReader, pos, size); - if (encryption->algo != 5) - return E_FILE_FORMAT_INVALID; - } else if (id == 0x7E2) { - // ContentEncKeyID - delete[] encryption->key_id; - encryption->key_id = NULL; - encryption->key_id_len = 0; - - if (size <= 0) - return E_FILE_FORMAT_INVALID; - - const size_t buflen = static_cast(size); - typedef unsigned char* buf_t; - const buf_t buf = new (std::nothrow) unsigned char[buflen]; - if (buf == NULL) - return -1; - - const int read_status = pReader->Read(pos, buflen, buf); - if (read_status) { - delete [] buf; - return status; - } - - encryption->key_id = buf; - encryption->key_id_len = buflen; - } else if (id == 0x7E3) { - // ContentSignature - delete[] encryption->signature; - encryption->signature = NULL; - encryption->signature_len = 0; - - if (size <= 0) - return E_FILE_FORMAT_INVALID; - - const size_t buflen = static_cast(size); - typedef unsigned char* buf_t; - const buf_t buf = new (std::nothrow) unsigned char[buflen]; - if (buf == NULL) - return -1; - - const int read_status = pReader->Read(pos, buflen, buf); - if (read_status) { - delete [] buf; - return status; - } - - encryption->signature = buf; - encryption->signature_len = buflen; - } else if (id == 0x7E4) { - // ContentSigKeyID - delete[] encryption->sig_key_id; - encryption->sig_key_id = NULL; - encryption->sig_key_id_len = 0; - - if (size <= 0) - return E_FILE_FORMAT_INVALID; - - const size_t buflen = static_cast(size); - typedef unsigned char* buf_t; - const buf_t buf = new (std::nothrow) unsigned char[buflen]; - if (buf == NULL) - return -1; - - const int read_status = pReader->Read(pos, buflen, buf); - if (read_status) { - delete [] buf; - return status; - } - - encryption->sig_key_id = buf; - encryption->sig_key_id_len = buflen; - } else if (id == 0x7E5) { - // ContentSigAlgo - encryption->sig_algo = UnserializeUInt(pReader, pos, size); - } else if (id == 0x7E6) { - // ContentSigHashAlgo - encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size); - } else if (id == 0x7E7) { - // ContentEncAESSettings - const long status = ParseContentEncAESSettingsEntry( - pos, - size, - pReader, - &encryption->aes_settings); - if (status) - return status; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - return 0; -} - -Track::Track( - Segment* pSegment, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_element_start(element_start), - m_element_size(element_size), - content_encoding_entries_(NULL), - content_encoding_entries_end_(NULL) -{ -} - -Track::~Track() -{ - Info& info = const_cast(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(): - nameAsUTF8(NULL), - language(NULL), - codecId(NULL), - codecNameAsUTF8(NULL), - codecPrivate(NULL), - codecPrivateSize(0), - codecDelay(0), - seekPreRoll(0) -{ -} - -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(NULL)) - return -1; - - char*& dst = dst_.*str; - - if (dst) //should be NULL already - return -1; - - const char* const src = this->*str; - - if (src == NULL) - return 0; - - const size_t len = strlen(src); - - dst = new (std::nothrow) char[len+1]; - - if (dst == NULL) - return -1; - - strcpy(dst, src); - - return 0; -} - - -int Track::Info::Copy(Info& dst) const -{ - if (&dst == this) - return 0; - - dst.type = type; - dst.number = number; - dst.defaultDuration = defaultDuration; - dst.codecDelay = codecDelay; - dst.seekPreRoll = seekPreRoll; - dst.uid = uid; - dst.lacing = lacing; - dst.settings = settings; - - //We now copy the string member variables from src to dst. - //This involves memory allocation so in principle the operation - //can fail (indeed, that's why we have Info::Copy), so we must - //report this to the caller. An error return from this function - //therefore implies that the copy was only partially successful. - - if (int status = CopyStr(&Info::nameAsUTF8, dst)) - return status; - - if (int status = CopyStr(&Info::language, dst)) - return status; - - if (int status = CopyStr(&Info::codecId, dst)) - return status; - - if (int status = CopyStr(&Info::codecNameAsUTF8, dst)) - return status; - - if (codecPrivateSize > 0) - { - if (codecPrivate == NULL) - return -1; - - if (dst.codecPrivate) - return -1; - - if (dst.codecPrivateSize != 0) - return -1; - - dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize]; - - if (dst.codecPrivate == NULL) - return -1; - - memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize); - dst.codecPrivateSize = codecPrivateSize; - } - - return 0; -} - -const BlockEntry* Track::GetEOS() const -{ - return &m_eos; -} - -long Track::GetType() const -{ - return m_info.type; -} - -long Track::GetNumber() const -{ - return m_info.number; -} - -unsigned long long Track::GetUid() const -{ - return m_info.uid; -} - -const char* Track::GetNameAsUTF8() const -{ - return m_info.nameAsUTF8; -} - -const char* Track::GetLanguage() const -{ - return m_info.language; -} - -const char* Track::GetCodecNameAsUTF8() const -{ - return m_info.codecNameAsUTF8; -} - - -const char* Track::GetCodecId() const -{ - return m_info.codecId; -} - -const unsigned char* Track::GetCodecPrivate(size_t& size) const -{ - size = m_info.codecPrivateSize; - return m_info.codecPrivate; -} - - -bool Track::GetLacing() const -{ - return m_info.lacing; -} - -unsigned long long Track::GetDefaultDuration() const -{ - return m_info.defaultDuration; -} - -unsigned long long Track::GetCodecDelay() const -{ - return m_info.codecDelay; -} - -unsigned long long Track::GetSeekPreRoll() const -{ - return m_info.seekPreRoll; -} - -long Track::GetFirst(const BlockEntry*& pBlockEntry) const -{ - const Cluster* pCluster = m_pSegment->GetFirst(); - - for (int i = 0; ; ) - { - if (pCluster == NULL) - { - pBlockEntry = GetEOS(); - return 1; - } - - if (pCluster->EOS()) - { -#if 0 - if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded - { - pBlockEntry = GetEOS(); - return 1; - } -#else - if (m_pSegment->DoneParsing()) - { - pBlockEntry = GetEOS(); - return 1; - } -#endif - - pBlockEntry = 0; - return E_BUFFER_NOT_FULL; - } - - long status = pCluster->GetFirst(pBlockEntry); - - if (status < 0) //error - return status; - - if (pBlockEntry == 0) //empty cluster - { - pCluster = m_pSegment->GetNext(pCluster); - continue; - } - - for (;;) - { - const Block* const pBlock = pBlockEntry->GetBlock(); - assert(pBlock); - - const long long tn = pBlock->GetTrackNumber(); - - if ((tn == m_info.number) && VetEntry(pBlockEntry)) - return 0; - - const BlockEntry* pNextEntry; - - status = pCluster->GetNext(pBlockEntry, pNextEntry); - - if (status < 0) //error - return status; - - if (pNextEntry == 0) - break; - - pBlockEntry = pNextEntry; - } - - ++i; - - if (i >= 100) - break; - - pCluster = m_pSegment->GetNext(pCluster); - } - - //NOTE: if we get here, it means that we didn't find a block with - //a matching track number. We interpret that as an error (which - //might be too conservative). - - pBlockEntry = GetEOS(); //so we can return a non-NULL value - return 1; -} - - -long Track::GetNext( - const BlockEntry* pCurrEntry, - const BlockEntry*& pNextEntry) const -{ - assert(pCurrEntry); - assert(!pCurrEntry->EOS()); //? - - const Block* const pCurrBlock = pCurrEntry->GetBlock(); - assert(pCurrBlock->GetTrackNumber() == m_info.number); - - const Cluster* pCluster = pCurrEntry->GetCluster(); - assert(pCluster); - assert(!pCluster->EOS()); - - long status = pCluster->GetNext(pCurrEntry, pNextEntry); - - if (status < 0) //error - return status; - - for (int i = 0; ; ) - { - while (pNextEntry) - { - const Block* const pNextBlock = pNextEntry->GetBlock(); - assert(pNextBlock); - - if (pNextBlock->GetTrackNumber() == m_info.number) - return 0; - - pCurrEntry = pNextEntry; - - status = pCluster->GetNext(pCurrEntry, pNextEntry); - - if (status < 0) //error - return status; - } - - pCluster = m_pSegment->GetNext(pCluster); - - if (pCluster == NULL) - { - pNextEntry = GetEOS(); - return 1; - } - - if (pCluster->EOS()) - { -#if 0 - if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded - { - pNextEntry = GetEOS(); - return 1; - } -#else - if (m_pSegment->DoneParsing()) - { - pNextEntry = GetEOS(); - return 1; - } -#endif - - //TODO: there is a potential O(n^2) problem here: we tell the - //caller to (pre)load another cluster, which he does, but then he - //calls GetNext again, which repeats the same search. This is - //a pathological case, since the only way it can happen is if - //there exists a long sequence of clusters none of which contain a - // block from this track. One way around this problem is for the - //caller to be smarter when he loads another cluster: don't call - //us back until you have a cluster that contains a block from this - //track. (Of course, that's not cheap either, since our caller - //would have to scan the each cluster as it's loaded, so that - //would just push back the problem.) - - pNextEntry = NULL; - return E_BUFFER_NOT_FULL; - } - - status = pCluster->GetFirst(pNextEntry); - - if (status < 0) //error - return status; - - if (pNextEntry == NULL) //empty cluster - continue; - - ++i; - - if (i >= 100) - break; - } - - //NOTE: if we get here, it means that we didn't find a block with - //a matching track number after lots of searching, so we give - //up trying. - - pNextEntry = GetEOS(); //so we can return a non-NULL value - return 1; -} - -bool Track::VetEntry(const BlockEntry* pBlockEntry) const -{ - assert(pBlockEntry); - const Block* const pBlock = pBlockEntry->GetBlock(); - assert(pBlock); - assert(pBlock->GetTrackNumber() == m_info.number); - - // 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(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(count); -} - -long Track::ParseContentEncodingsEntry(long long start, long long size) { - IMkvReader* const pReader = m_pSegment->m_pReader; - assert(pReader); - - long long pos = start; - const long long stop = start + size; - - // Count ContentEncoding elements. - int count = 0; - while (pos < stop) { - long long id, size; - const long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - - //pos now designates start of element - if (id == 0x2240) // ContentEncoding ID - ++count; - - pos += size; //consume payload - assert(pos <= stop); - } - - if (count <= 0) - return -1; - - content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count]; - if (!content_encoding_entries_) - return -1; - - content_encoding_entries_end_ = content_encoding_entries_; - - pos = start; - while (pos < stop) { - long long id, size; - long status = ParseElementHeader(pReader, - pos, - stop, - id, - size); - if (status < 0) //error - return status; - - //pos now designates start of element - if (id == 0x2240) { // ContentEncoding ID - ContentEncoding* const content_encoding = - new (std::nothrow) ContentEncoding(); - if (!content_encoding) - return -1; - - status = content_encoding->ParseContentEncodingEntry(pos, - size, - pReader); - if (status) { - delete content_encoding; - return status; - } - - *content_encoding_entries_end_++ = content_encoding; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - return 0; -} - -Track::EOSBlock::EOSBlock() : - BlockEntry(NULL, LONG_MIN) -{ -} - -BlockEntry::Kind Track::EOSBlock::GetKind() const -{ - return kBlockEOS; -} - - -const Block* Track::EOSBlock::GetBlock() const -{ - return NULL; -} - - -VideoTrack::VideoTrack( - Segment* pSegment, - long long element_start, - long long element_size) : - Track(pSegment, element_start, element_size) -{ -} - - -long VideoTrack::Parse( - Segment* pSegment, - const Info& info, - long long element_start, - long long element_size, - VideoTrack*& pResult) -{ - if (pResult) - return -1; - - if (info.type != Track::kVideo) - return -1; - - long long width = 0; - long long height = 0; - double rate = 0.0; - - IMkvReader* const pReader = pSegment->m_pReader; - - const Settings& s = info.settings; - assert(s.start >= 0); - assert(s.size >= 0); - - long long pos = s.start; - assert(pos >= 0); - - const long long stop = pos + s.size; - - while (pos < stop) - { - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (id == 0x30) //pixel width - { - width = UnserializeUInt(pReader, pos, size); - - if (width <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x3A) //pixel height - { - height = UnserializeUInt(pReader, pos, size); - - if (height <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x0383E3) //frame rate - { - const long status = UnserializeFloat( - pReader, - pos, - size, - rate); - - if (status < 0) - return status; - - if (rate <= 0) - return E_FILE_FORMAT_INVALID; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - VideoTrack* const pTrack = new (std::nothrow) VideoTrack(pSegment, - element_start, - element_size); - - if (pTrack == NULL) - return -1; //generic error - - const int status = info.Copy(pTrack->m_info); - - if (status) // error - { - delete pTrack; - return status; - } - - pTrack->m_width = width; - pTrack->m_height = height; - pTrack->m_rate = rate; - - pResult = pTrack; - return 0; //success -} - - -bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const -{ - return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey(); -} - -long VideoTrack::Seek( - long long time_ns, - const BlockEntry*& pResult) const -{ - const long status = GetFirst(pResult); - - if (status < 0) //buffer underflow, etc - return status; - - assert(pResult); - - if (pResult->EOS()) - return 0; - - const Cluster* pCluster = pResult->GetCluster(); - assert(pCluster); - assert(pCluster->GetIndex() >= 0); - - if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) - return 0; - - Cluster** const clusters = m_pSegment->m_clusters; - assert(clusters); - - const long count = m_pSegment->GetCount(); //loaded only, not pre-loaded - assert(count > 0); - - Cluster** const i = clusters + pCluster->GetIndex(); - assert(i); - assert(*i == pCluster); - assert(pCluster->GetTime() <= time_ns); - - Cluster** const j = clusters + count; - - Cluster** lo = i; - Cluster** hi = j; - - while (lo < hi) - { - //INVARIANT: - //[i, lo) <= time_ns - //[lo, hi) ? - //[hi, j) > time_ns - - Cluster** const mid = lo + (hi - lo) / 2; - assert(mid < hi); - - pCluster = *mid; - assert(pCluster); - assert(pCluster->GetIndex() >= 0); - assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); - - const long long t = pCluster->GetTime(); - - if (t <= time_ns) - lo = mid + 1; - else - hi = mid; - - assert(lo <= hi); - } - - assert(lo == hi); - assert(lo > i); - assert(lo <= j); - - pCluster = *--lo; - assert(pCluster); - assert(pCluster->GetTime() <= time_ns); - - pResult = pCluster->GetEntry(this, time_ns); - - if ((pResult != 0) && !pResult->EOS()) //found a keyframe - return 0; - - while (lo != i) - { - pCluster = *--lo; - assert(pCluster); - assert(pCluster->GetTime() <= time_ns); - -#if 0 - //TODO: - //We need to handle the case when a cluster - //contains multiple keyframes. Simply returning - //the largest keyframe on the cluster isn't - //good enough. - pResult = pCluster->GetMaxKey(this); -#else - pResult = pCluster->GetEntry(this, time_ns); -#endif - - if ((pResult != 0) && !pResult->EOS()) - return 0; - } - - //weird: we're on the first cluster, but no keyframe found - //should never happen but we must return something anyway - - pResult = GetEOS(); - return 0; -} - - -long long VideoTrack::GetWidth() const -{ - return m_width; -} - - -long long VideoTrack::GetHeight() const -{ - return m_height; -} - - -double VideoTrack::GetFrameRate() const -{ - return m_rate; -} - - -AudioTrack::AudioTrack( - Segment* pSegment, - long long element_start, - long long element_size) : - Track(pSegment, element_start, element_size) -{ -} - - -long AudioTrack::Parse( - Segment* pSegment, - const Info& info, - long long element_start, - long long element_size, - AudioTrack*& pResult) -{ - if (pResult) - return -1; - - if (info.type != Track::kAudio) - return -1; - - IMkvReader* const pReader = pSegment->m_pReader; - - const Settings& s = info.settings; - assert(s.start >= 0); - assert(s.size >= 0); - - long long pos = s.start; - assert(pos >= 0); - - const long long stop = pos + s.size; - - double rate = 8000.0; // MKV default - long long channels = 1; - long long bit_depth = 0; - - while (pos < stop) - { - long long id, size; - - long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (id == 0x35) //Sample Rate - { - status = UnserializeFloat(pReader, pos, size, rate); - - if (status < 0) - return status; - - if (rate <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x1F) //Channel Count - { - channels = UnserializeUInt(pReader, pos, size); - - if (channels <= 0) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x2264) //Bit Depth - { - bit_depth = UnserializeUInt(pReader, pos, size); - - if (bit_depth <= 0) - return E_FILE_FORMAT_INVALID; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - AudioTrack* const pTrack = new (std::nothrow) AudioTrack(pSegment, - element_start, - element_size); - - if (pTrack == NULL) - return -1; //generic error - - const int status = info.Copy(pTrack->m_info); - - if (status) - { - delete pTrack; - return status; - } - - pTrack->m_rate = rate; - pTrack->m_channels = channels; - pTrack->m_bitDepth = bit_depth; - - pResult = pTrack; - return 0; //success -} - - -double AudioTrack::GetSamplingRate() const -{ - return m_rate; -} - - -long long AudioTrack::GetChannels() const -{ - return m_channels; -} - -long long AudioTrack::GetBitDepth() const -{ - return m_bitDepth; -} - -Tracks::Tracks( - Segment* pSegment, - long long start, - long long size_, - long long element_start, - long long element_size) : - m_pSegment(pSegment), - m_start(start), - m_size(size_), - m_element_start(element_start), - m_element_size(element_size), - m_trackEntries(NULL), - m_trackEntriesEnd(NULL) -{ -} - - -long Tracks::Parse() -{ - assert(m_trackEntries == NULL); - assert(m_trackEntriesEnd == NULL); - - const long long stop = m_start + m_size; - IMkvReader* const pReader = m_pSegment->m_pReader; - - int count = 0; - long long pos = m_start; - - while (pos < stop) - { - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - size); - - if (status < 0) //error - return status; - - if (size == 0) //weird - continue; - - if (id == 0x2E) //TrackEntry ID - ++count; - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - - if (count <= 0) - return 0; //success - - m_trackEntries = new (std::nothrow) Track*[count]; - - if (m_trackEntries == NULL) - return -1; - - m_trackEntriesEnd = m_trackEntries; - - pos = m_start; - - while (pos < stop) - { - const long long element_start = pos; - - long long id, payload_size; - - const long status = ParseElementHeader( - pReader, - pos, - stop, - id, - payload_size); - - if (status < 0) //error - return status; - - if (payload_size == 0) //weird - continue; - - const long long payload_stop = pos + payload_size; - assert(payload_stop <= stop); //checked in ParseElement - - const long long element_size = payload_stop - element_start; - - if (id == 0x2E) //TrackEntry ID - { - Track*& pTrack = *m_trackEntriesEnd; - pTrack = NULL; - - const long status = ParseTrackEntry( - pos, - payload_size, - element_start, - element_size, - pTrack); - - if (status) - return status; - - if (pTrack) - ++m_trackEntriesEnd; - } - - pos = payload_stop; - assert(pos <= stop); - } - - assert(pos == stop); - - return 0; //success -} - - -unsigned long Tracks::GetTracksCount() const -{ - const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; - assert(result >= 0); - - return static_cast(result); -} - -long Tracks::ParseTrackEntry( - long long track_start, - long long track_size, - long long element_start, - long long element_size, - Track*& pResult) const -{ - if (pResult) - return -1; - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long pos = track_start; - const long long track_stop = track_start + track_size; - - Track::Info info; - - info.type = 0; - info.number = 0; - info.uid = 0; - info.defaultDuration = 0; - - Track::Settings v; - v.start = -1; - v.size = -1; - - Track::Settings a; - a.start = -1; - a.size = -1; - - Track::Settings e; //content_encodings_settings; - e.start = -1; - e.size = -1; - - long long lacing = 1; //default is true - - while (pos < track_stop) - { - long long id, size; - - const long status = ParseElementHeader( - pReader, - pos, - track_stop, - id, - size); - - if (status < 0) //error - return status; - - if (size < 0) - return E_FILE_FORMAT_INVALID; - - const long long start = pos; - - if (id == 0x60) // VideoSettings ID - { - v.start = start; - v.size = size; - } - else if (id == 0x61) // AudioSettings ID - { - a.start = start; - a.size = size; - } - else if (id == 0x2D80) // ContentEncodings ID - { - e.start = start; - e.size = size; - } - else if (id == 0x33C5) //Track UID - { - if (size > 8) - return E_FILE_FORMAT_INVALID; - - info.uid = 0; - - long long pos_ = start; - const long long pos_end = start + size; - - while (pos_ != pos_end) - { - unsigned char b; - - const int status = pReader->Read(pos_, 1, &b); - - if (status) - return status; - - info.uid <<= 8; - info.uid |= b; - - ++pos_; - } - } - else if (id == 0x57) //Track Number - { - const long long num = UnserializeUInt(pReader, pos, size); - - if ((num <= 0) || (num > 127)) - return E_FILE_FORMAT_INVALID; - - info.number = static_cast(num); - } - else if (id == 0x03) //Track Type - { - const long long type = UnserializeUInt(pReader, pos, size); - - if ((type <= 0) || (type > 254)) - return E_FILE_FORMAT_INVALID; - - info.type = static_cast(type); - } - else if (id == 0x136E) //Track Name - { - const long status = UnserializeString( - pReader, - pos, - size, - info.nameAsUTF8); - - if (status) - return status; - } - else if (id == 0x02B59C) //Track Language - { - const long status = UnserializeString( - pReader, - pos, - size, - info.language); - - if (status) - return status; - } - else if (id == 0x03E383) //Default Duration - { - const long long duration = UnserializeUInt(pReader, pos, size); - - if (duration < 0) - return E_FILE_FORMAT_INVALID; - - info.defaultDuration = static_cast(duration); - } - else if (id == 0x06) //CodecID - { - const long status = UnserializeString( - pReader, - pos, - size, - info.codecId); - - if (status) - return status; - } - else if (id == 0x1C) //lacing - { - lacing = UnserializeUInt(pReader, pos, size); - - if ((lacing < 0) || (lacing > 1)) - return E_FILE_FORMAT_INVALID; - } - else if (id == 0x23A2) //Codec Private - { - delete[] info.codecPrivate; - info.codecPrivate = NULL; - info.codecPrivateSize = 0; - - const size_t buflen = static_cast(size); - - if (buflen) - { - typedef unsigned char* buf_t; - - const buf_t buf = new (std::nothrow) unsigned char[buflen]; - - if (buf == NULL) - return -1; - - const int status = pReader->Read(pos, buflen, buf); - - if (status) - { - delete[] buf; - return status; - } - - info.codecPrivate = buf; - info.codecPrivateSize = buflen; - } - } - else if (id == 0x058688) //Codec Name - { - const long status = UnserializeString( - pReader, - pos, - size, - info.codecNameAsUTF8); - - if (status) - return status; - } - else if (id == 0x16AA) //Codec Delay - { - info.codecDelay = UnserializeUInt(pReader, pos, size); - - } - else if (id == 0x16BB) //Seek Pre Roll - { - info.seekPreRoll = UnserializeUInt(pReader, pos, size); - } - - pos += size; //consume payload - assert(pos <= track_stop); - } - - assert(pos == track_stop); - - if (info.number <= 0) //not specified - return E_FILE_FORMAT_INVALID; - - if (GetTrackByNumber(info.number)) - return E_FILE_FORMAT_INVALID; - - if (info.type <= 0) //not specified - return E_FILE_FORMAT_INVALID; - - info.lacing = (lacing > 0) ? true : false; - - if (info.type == Track::kVideo) - { - if (v.start < 0) - return E_FILE_FORMAT_INVALID; - - if (a.start >= 0) - return E_FILE_FORMAT_INVALID; - - info.settings = v; - - VideoTrack* pTrack = NULL; - - const long status = VideoTrack::Parse(m_pSegment, - info, - element_start, - element_size, - pTrack); - - if (status) - return status; - - pResult = pTrack; - assert(pResult); - - if (e.start >= 0) - pResult->ParseContentEncodingsEntry(e.start, e.size); - } - else if (info.type == Track::kAudio) - { - if (a.start < 0) - return E_FILE_FORMAT_INVALID; - - if (v.start >= 0) - return E_FILE_FORMAT_INVALID; - - info.settings = a; - - AudioTrack* pTrack = NULL; - - const long status = AudioTrack::Parse(m_pSegment, - info, - element_start, - element_size, - pTrack); - - if (status) - return status; - - pResult = pTrack; - assert(pResult); - - if (e.start >= 0) - pResult->ParseContentEncodingsEntry(e.start, e.size); - } - else - { - // neither video nor audio - probably metadata or subtitles - - if (a.start >= 0) - return E_FILE_FORMAT_INVALID; - - if (v.start >= 0) - return E_FILE_FORMAT_INVALID; - - if (e.start >= 0) - return E_FILE_FORMAT_INVALID; - - info.settings.start = -1; - info.settings.size = 0; - - Track* pTrack = NULL; - - const long status = Track::Create(m_pSegment, - info, - element_start, - element_size, - pTrack); - - if (status) - return status; - - pResult = pTrack; - assert(pResult); - } - - return 0; //success -} - - -Tracks::~Tracks() -{ - Track** i = m_trackEntries; - Track** const j = m_trackEntriesEnd; - - while (i != j) - { - Track* const pTrack = *i++; - delete pTrack; - } - - delete[] m_trackEntries; -} - -const Track* Tracks::GetTrackByNumber(long tn) const -{ - if (tn < 0) - return NULL; - - Track** i = m_trackEntries; - Track** const j = m_trackEntriesEnd; - - while (i != j) - { - Track* const pTrack = *i++; - - if (pTrack == NULL) - continue; - - if (tn == pTrack->GetNumber()) - return pTrack; - } - - return NULL; //not found -} - - -const Track* Tracks::GetTrackByIndex(unsigned long idx) const -{ - const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries; - - if (idx >= static_cast(count)) - return NULL; - - return m_trackEntries[idx]; -} - -#if 0 -long long Cluster::Unparsed() const -{ - if (m_timecode < 0) //not even partially loaded - return LLONG_MAX; - - assert(m_pos >= m_element_start); - //assert(m_element_size > m_size); - - const long long element_stop = m_element_start + m_element_size; - assert(m_pos <= element_stop); - - const long long result = element_stop - m_pos; - assert(result >= 0); - - return result; -} -#endif - - -long Cluster::Load(long long& pos, long& len) const -{ - assert(m_pSegment); - assert(m_pos >= m_element_start); - - if (m_timecode >= 0) //at least partially loaded - return 0; - - assert(m_pos == m_element_start); - assert(m_element_size < 0); - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long total, avail; - - const int status = pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - assert((total < 0) || (m_pos <= total)); //TODO: verify this - - pos = m_pos; - - long long cluster_size = -1; - - { - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error or underflow - return static_cast(result); - - if (result > 0) //underflow (weird) - return E_BUFFER_NOT_FULL; - - //if ((pos + len) > segment_stop) - // return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id_ = ReadUInt(pReader, pos, len); - - if (id_ < 0) //error - return static_cast(id_); - - if (id_ != 0x0F43B675) //Cluster ID - return E_FILE_FORMAT_INVALID; - - pos += len; //consume id - - //read cluster size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - //if ((pos + len) > segment_stop) - // return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long size = ReadUInt(pReader, pos, len); - - if (size < 0) //error - return static_cast(cluster_size); - - if (size == 0) - return E_FILE_FORMAT_INVALID; //TODO: verify this - - pos += len; //consume length of size of element - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size != unknown_size) - cluster_size = size; - } - - //pos points to start of payload - -#if 0 - len = static_cast(size_); - - if (cluster_stop > avail) - return E_BUFFER_NOT_FULL; -#endif - - long long timecode = -1; - long long new_pos = -1; - bool bBlock = false; - - long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size; - - for (;;) - { - if ((cluster_stop >= 0) && (pos >= cluster_stop)) - break; - - //Parse ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return static_cast(id); - - if (id == 0) - return E_FILE_FORMAT_INVALID; - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if (id == 0x0F43B675) //Cluster ID - break; - - if (id == 0x0C53BB6B) //Cues ID - break; - - pos += len; //consume ID field - - //Parse Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long size = ReadUInt(pReader, pos, len); - - if (size < 0) //error - return static_cast(size); - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume size field - - if ((cluster_stop >= 0) && (pos > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - //pos now points to start of payload - - if (size == 0) //weird - continue; - - if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if (id == 0x67) //TimeCode ID - { - len = static_cast(size); - - if ((pos + size) > avail) - return E_BUFFER_NOT_FULL; - - timecode = UnserializeUInt(pReader, pos, size); - - if (timecode < 0) //error (or underflow) - return static_cast(timecode); - - new_pos = pos + size; - - if (bBlock) - break; - } - else if (id == 0x20) //BlockGroup ID - { - bBlock = true; - break; - } - else if (id == 0x23) //SimpleBlock ID - { - bBlock = true; - break; - } - - pos += size; //consume payload - assert((cluster_stop < 0) || (pos <= cluster_stop)); - } - - assert((cluster_stop < 0) || (pos <= cluster_stop)); - - if (timecode < 0) //no timecode found - return E_FILE_FORMAT_INVALID; - - if (!bBlock) - return E_FILE_FORMAT_INVALID; - - m_pos = new_pos; //designates position just beyond timecode payload - m_timecode = timecode; // m_timecode >= 0 means we're partially loaded - - if (cluster_size >= 0) - m_element_size = cluster_stop - m_element_start; - - return 0; -} - - -long Cluster::Parse(long long& pos, long& len) const -{ - long status = Load(pos, len); - - if (status < 0) - return status; - - assert(m_pos >= m_element_start); - assert(m_timecode >= 0); - //assert(m_size > 0); - //assert(m_element_size > m_size); - - const long long cluster_stop = - (m_element_size < 0) ? -1 : m_element_start + m_element_size; - - if ((cluster_stop >= 0) && (m_pos >= cluster_stop)) - return 1; //nothing else to do - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long total, avail; - - status = pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - pos = m_pos; - - for (;;) - { - if ((cluster_stop >= 0) && (pos >= cluster_stop)) - break; - - if ((total >= 0) && (pos >= total)) - { - if (m_element_size < 0) - m_element_size = pos - m_element_start; - - break; - } - - //Parse ID - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return static_cast(id); - - if (id == 0) //weird - return E_FILE_FORMAT_INVALID; - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster or Cues ID - { - if (m_element_size < 0) - m_element_size = pos - m_element_start; - - break; - } - - pos += len; //consume ID field - - //Parse Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long size = ReadUInt(pReader, pos, len); - - if (size < 0) //error - return static_cast(size); - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume size field - - if ((cluster_stop >= 0) && (pos > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - //pos now points to start of payload - - if (size == 0) //weird - continue; - - //const long long block_start = pos; - const long long block_stop = pos + size; - - if (cluster_stop >= 0) - { - if (block_stop > cluster_stop) - { - if ((id == 0x20) || (id == 0x23)) - return E_FILE_FORMAT_INVALID; - - pos = cluster_stop; - break; - } - } - else if ((total >= 0) && (block_stop > total)) - { - m_element_size = total - m_element_start; - pos = total; - break; - } - else if (block_stop > avail) - { - len = static_cast(size); - return E_BUFFER_NOT_FULL; - } - - Cluster* const this_ = const_cast(this); - - if (id == 0x20) //BlockGroup - return this_->ParseBlockGroup(size, pos, len); - - if (id == 0x23) //SimpleBlock - return this_->ParseSimpleBlock(size, pos, len); - - pos += size; //consume payload - assert((cluster_stop < 0) || (pos <= cluster_stop)); - } - - assert(m_element_size > 0); - - m_pos = pos; - assert((cluster_stop < 0) || (m_pos <= cluster_stop)); - - if (m_entries_count > 0) - { - const long idx = m_entries_count - 1; - - const BlockEntry* const pLast = m_entries[idx]; - assert(pLast); - - const Block* const pBlock = pLast->GetBlock(); - assert(pBlock); - - const long long start = pBlock->m_start; - - if ((total >= 0) && (start > total)) - return -1; //defend against trucated stream - - const long long size = pBlock->m_size; - - const long long stop = start + size; - assert((cluster_stop < 0) || (stop <= cluster_stop)); - - if ((total >= 0) && (stop > total)) - return -1; //defend against trucated stream - } - - return 1; //no more entries -} - - -long Cluster::ParseSimpleBlock( - long long block_size, - long long& pos, - long& len) -{ - const long long block_start = pos; - const long long block_stop = pos + block_size; - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long total, avail; - - long status = pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - //parse track number - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(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(track); - - if (track == 0) - return E_FILE_FORMAT_INVALID; - -#if 0 - //TODO(matthewjheaney) - //This turned out to be too conservative. The problem is that - //if we see a track header in the tracks element with an unsupported - //track type, we throw that track header away, so it is not present - //in the track map. But even though we don't understand the track - //header, there are still blocks in the cluster with that track - //number. It was our decision to ignore that track header, so it's - //up to us to deal with blocks associated with that track -- we - //cannot simply report an error since technically there's nothing - //wrong with the file. - // - //For now we go ahead and finish the parse, creating a block entry - //for this block. This is somewhat wasteful, because without a - //track header there's nothing you can do with the block. What - //we really need here is a special return value that indicates to - //the caller that he should ignore this particular block, and - //continue parsing. - - const Tracks* const pTracks = m_pSegment->GetTracks(); - assert(pTracks); - - const long tn = static_cast(track); - - const Track* const pTrack = pTracks->GetTrackByNumber(tn); - - if (pTrack == NULL) - return E_FILE_FORMAT_INVALID; -#endif - - pos += len; //consume track number - - if ((pos + 2) > block_stop) - return E_FILE_FORMAT_INVALID; - - if ((pos + 2) > avail) - { - len = 2; - return E_BUFFER_NOT_FULL; - } - - pos += 2; //consume timecode - - if ((pos + 1) > block_stop) - return E_FILE_FORMAT_INVALID; - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - unsigned char flags; - - status = pReader->Read(pos, 1, &flags); - - if (status < 0) //error or underflow - { - len = 1; - return status; - } - - ++pos; //consume flags byte - assert(pos <= avail); - - if (pos >= block_stop) - return E_FILE_FORMAT_INVALID; - - const int lacing = int(flags & 0x06) >> 1; - - if ((lacing != 0) && (block_stop > avail)) - { - len = static_cast(block_stop - pos); - return E_BUFFER_NOT_FULL; - } - - status = CreateBlock(0x23, //simple block id - block_start, block_size, - 0); //DiscardPadding - - if (status != 0) - return status; - - m_pos = block_stop; - - return 0; //success -} - - -long Cluster::ParseBlockGroup( - long long payload_size, - long long& pos, - long& len) -{ - const long long payload_start = pos; - const long long payload_stop = pos + payload_size; - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long total, avail; - - long status = pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - if ((total >= 0) && (payload_stop > total)) - return E_FILE_FORMAT_INVALID; - - if (payload_stop > avail) - { - len = static_cast(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(result); - - if (result > 0) //weird - return E_BUFFER_NOT_FULL; - - if ((pos + len) > payload_stop) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return static_cast(id); - - if (id == 0) //not a value ID - return E_FILE_FORMAT_INVALID; - - pos += len; //consume ID field - - //Parse Size - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field - - //pos now points to start of sub-block group payload - - if (pos > payload_stop) - return E_FILE_FORMAT_INVALID; - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; - - if (id == 0x35A2) //DiscardPadding - { - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - status = UnserializeInt(pReader, pos, len, discard_padding); - - if (status < 0) //error - return status; - } - - if (id != 0x21) //sub-part of BlockGroup is not a Block - { - pos += size; //consume sub-part of block group - - if (pos > payload_stop) - return E_FILE_FORMAT_INVALID; - - continue; - } - - const long long block_stop = pos + size; - - if (block_stop > payload_stop) - return E_FILE_FORMAT_INVALID; - - //parse track number - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(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(track); - - if (track == 0) - return E_FILE_FORMAT_INVALID; - -#if 0 - //TODO(matthewjheaney) - //This turned out to be too conservative. The problem is that - //if we see a track header in the tracks element with an unsupported - //track type, we throw that track header away, so it is not present - //in the track map. But even though we don't understand the track - //header, there are still blocks in the cluster with that track - //number. It was our decision to ignore that track header, so it's - //up to us to deal with blocks associated with that track -- we - //cannot simply report an error since technically there's nothing - //wrong with the file. - // - //For now we go ahead and finish the parse, creating a block entry - //for this block. This is somewhat wasteful, because without a - //track header there's nothing you can do with the block. What - //we really need here is a special return value that indicates to - //the caller that he should ignore this particular block, and - //continue parsing. - - const Tracks* const pTracks = m_pSegment->GetTracks(); - assert(pTracks); - - const long tn = static_cast(track); - - const Track* const pTrack = pTracks->GetTrackByNumber(tn); - - if (pTrack == NULL) - return E_FILE_FORMAT_INVALID; -#endif - - pos += len; //consume track number - - if ((pos + 2) > block_stop) - return E_FILE_FORMAT_INVALID; - - if ((pos + 2) > avail) - { - len = 2; - return E_BUFFER_NOT_FULL; - } - - pos += 2; //consume timecode - - if ((pos + 1) > block_stop) - return E_FILE_FORMAT_INVALID; - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - unsigned char flags; - - status = pReader->Read(pos, 1, &flags); - - if (status < 0) //error or underflow - { - len = 1; - return status; - } - - ++pos; //consume flags byte - assert(pos <= avail); - - if (pos >= block_stop) - return E_FILE_FORMAT_INVALID; - - const int lacing = int(flags & 0x06) >> 1; - - if ((lacing != 0) && (block_stop > avail)) - { - len = static_cast(block_stop - pos); - return E_BUFFER_NOT_FULL; - } - - pos = block_stop; //consume block-part of block group - assert(pos <= payload_stop); - } - - assert(pos == payload_stop); - - status = CreateBlock(0x20, //BlockGroup ID - payload_start, payload_size, - discard_padding); - if (status != 0) - return status; - - m_pos = payload_stop; - - return 0; //success -} - - -long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const -{ - assert(m_pos >= m_element_start); - - pEntry = NULL; - - if (index < 0) - return -1; //generic error - - if (m_entries_count < 0) - return E_BUFFER_NOT_FULL; - - assert(m_entries); - assert(m_entries_size > 0); - assert(m_entries_count <= m_entries_size); - - if (index < m_entries_count) - { - pEntry = m_entries[index]; - assert(pEntry); - - return 1; //found entry - } - - if (m_element_size < 0) //we don't know cluster end yet - return E_BUFFER_NOT_FULL; //underflow - - const long long element_stop = m_element_start + m_element_size; - - if (m_pos >= element_stop) - return 0; //nothing left to parse - - return E_BUFFER_NOT_FULL; //underflow, since more remains to be parsed -} - - -Cluster* Cluster::Create( - Segment* pSegment, - long idx, - long long off) - //long long element_size) -{ - assert(pSegment); - assert(off >= 0); - - const long long element_start = pSegment->m_start + off; - - Cluster* const pCluster = new Cluster(pSegment, - idx, - element_start); - //element_size); - assert(pCluster); - - return pCluster; -} - - -Cluster::Cluster() : - m_pSegment(NULL), - m_element_start(0), - m_index(0), - m_pos(0), - m_element_size(0), - m_timecode(0), - m_entries(NULL), - m_entries_size(0), - m_entries_count(0) //means "no entries" -{ -} - - -Cluster::Cluster( - Segment* pSegment, - long idx, - long long element_start - /* long long element_size */ ) : - m_pSegment(pSegment), - m_element_start(element_start), - m_index(idx), - m_pos(element_start), - m_element_size(-1 /* element_size */ ), - m_timecode(-1), - m_entries(NULL), - m_entries_size(0), - m_entries_count(-1) //means "has not been parsed yet" -{ -} - - -Cluster::~Cluster() -{ - if (m_entries_count <= 0) - return; - - BlockEntry** i = m_entries; - BlockEntry** const j = m_entries + m_entries_count; - - while (i != j) - { - BlockEntry* p = *i++; - assert(p); - - delete p; - } - - delete[] m_entries; -} - - -bool Cluster::EOS() const -{ - return (m_pSegment == NULL); -} - - -long Cluster::GetIndex() const -{ - return m_index; -} - - -long long Cluster::GetPosition() const -{ - const long long pos = m_element_start - m_pSegment->m_start; - assert(pos >= 0); - - return pos; -} - - -long long Cluster::GetElementSize() const -{ - return m_element_size; -} - - -#if 0 -bool Cluster::HasBlockEntries( - const Segment* pSegment, - long long off) //relative to start of segment payload -{ - assert(pSegment); - assert(off >= 0); //relative to segment - - IMkvReader* const pReader = pSegment->m_pReader; - - long long pos = pSegment->m_start + off; //absolute - long long size; - - { - long len; - - const long long id = ReadUInt(pReader, pos, len); - (void)id; - assert(id >= 0); - assert(id == 0x0F43B675); //Cluster ID - - pos += len; //consume id - - size = ReadUInt(pReader, pos, len); - assert(size > 0); - - pos += len; //consume size - - //pos now points to start of payload - } - - const long long stop = pos + size; - - while (pos < stop) - { - long len; - - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume id - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume size - - if (id == 0x20) //BlockGroup ID - return true; - - if (id == 0x23) //SimpleBlock ID - return true; - - pos += size; //consume payload - assert(pos <= stop); - } - - return false; -} -#endif - - -long Cluster::HasBlockEntries( - const Segment* pSegment, - long long off, //relative to start of segment payload - long long& pos, - long& len) -{ - assert(pSegment); - assert(off >= 0); //relative to segment - - IMkvReader* const pReader = pSegment->m_pReader; - - long long total, avail; - - long status = pReader->Length(&total, &avail); - - if (status < 0) //error - return status; - - assert((total < 0) || (avail <= total)); - - pos = pSegment->m_start + off; //absolute - - if ((total >= 0) && (pos >= total)) - return 0; //we don't even have a complete cluster - - const long long segment_stop = - (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size; - - long long cluster_stop = -1; //interpreted later to mean "unknown size" - - { - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - long long result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(result); - - if (result > 0) //need more data - return E_BUFFER_NOT_FULL; - - if ((segment_stop >= 0) && ((pos + len) > segment_stop)) - return E_FILE_FORMAT_INVALID; - - if ((total >= 0) && ((pos + len) > total)) - return 0; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return static_cast(id); - - if (id != 0x0F43B675) //weird: not cluster ID - return -1; //generic error - - pos += len; //consume Cluster ID field - - //read size field - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(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(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(result); - - if (result > 0) //need more data - return E_BUFFER_NOT_FULL; - - if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > avail) - return E_BUFFER_NOT_FULL; - - const long long id = ReadUInt(pReader, pos, len); - - if (id < 0) //error - return static_cast(id); - - //This is the distinguished set of ID's we use to determine - //that we have exhausted the sub-element's inside the cluster - //whose ID we parsed earlier. - - if (id == 0x0F43B675) //Cluster ID - return 0; //no entries found - - if (id == 0x0C53BB6B) //Cues ID - return 0; //no entries found - - pos += len; //consume id field - - if ((cluster_stop >= 0) && (pos >= cluster_stop)) - return E_FILE_FORMAT_INVALID; - - //read size field - - if ((pos + 1) > avail) - { - len = 1; - return E_BUFFER_NOT_FULL; - } - - result = GetUIntLength(pReader, pos, len); - - if (result < 0) //error - return static_cast(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(size); - - pos += len; //consume size field - - //pos now points to start of payload - - if ((cluster_stop >= 0) && (pos > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if (size == 0) //weird - continue; - - const long long unknown_size = (1LL << (7 * len)) - 1; - - if (size == unknown_size) - return E_FILE_FORMAT_INVALID; //not supported inside cluster - - if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) - return E_FILE_FORMAT_INVALID; - - if (id == 0x20) //BlockGroup ID - return 1; //have at least one entry - - if (id == 0x23) //SimpleBlock ID - return 1; //have at least one entry - - pos += size; //consume payload - assert((cluster_stop < 0) || (pos <= cluster_stop)); - } -} - - -long long Cluster::GetTimeCode() const -{ - long long pos; - long len; - - const long status = Load(pos, len); - - if (status < 0) //error - return status; - - return m_timecode; -} - - -long long Cluster::GetTime() const -{ - const long long tc = GetTimeCode(); - - if (tc < 0) - return tc; - - const SegmentInfo* const pInfo = m_pSegment->GetInfo(); - assert(pInfo); - - const long long scale = pInfo->GetTimeCodeScale(); - assert(scale >= 1); - - const long long t = m_timecode * scale; - - return t; -} - - -long long Cluster::GetFirstTime() const -{ - const BlockEntry* pEntry; - - const long status = GetFirst(pEntry); - - if (status < 0) //error - return status; - - if (pEntry == NULL) //empty cluster - return GetTime(); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - return pBlock->GetTime(this); -} - - -long long Cluster::GetLastTime() const -{ - const BlockEntry* pEntry; - - const long status = GetLast(pEntry); - - if (status < 0) //error - return status; - - if (pEntry == NULL) //empty cluster - return GetTime(); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - return pBlock->GetTime(this); -} - - -long Cluster::CreateBlock( - long long id, - long long pos, //absolute pos of payload - long long size, - long long discard_padding) -{ - assert((id == 0x20) || (id == 0x23)); //BlockGroup or SimpleBlock - - if (m_entries_count < 0) //haven't parsed anything yet - { - assert(m_entries == NULL); - assert(m_entries_size == 0); - - m_entries_size = 1024; - m_entries = new BlockEntry*[m_entries_size]; - - m_entries_count = 0; - } - else - { - assert(m_entries); - assert(m_entries_size > 0); - assert(m_entries_count <= m_entries_size); - - if (m_entries_count >= m_entries_size) - { - const long entries_size = 2 * m_entries_size; - - BlockEntry** const entries = new BlockEntry*[entries_size]; - assert(entries); - - BlockEntry** src = m_entries; - BlockEntry** const src_end = src + m_entries_count; - - BlockEntry** dst = entries; - - while (src != src_end) - *dst++ = *src++; - - delete[] m_entries; - - m_entries = entries; - m_entries_size = entries_size; - } - } - - if (id == 0x20) //BlockGroup ID - return CreateBlockGroup(pos, size, discard_padding); - else //SimpleBlock ID - return CreateSimpleBlock(pos, size); -} - - -long Cluster::CreateBlockGroup( - long long start_offset, - long long size, - long long discard_padding) -{ - assert(m_entries); - assert(m_entries_size > 0); - assert(m_entries_count >= 0); - assert(m_entries_count < m_entries_size); - - IMkvReader* const pReader = m_pSegment->m_pReader; - - long long pos = start_offset; - const long long stop = start_offset + size; - - //For WebM files, there is a bias towards previous reference times - //(in order to support alt-ref frames, which refer back to the previous - //keyframe). Normally a 0 value is not possible, but here we tenatively - //allow 0 as the value of a reference frame, with the interpretation - //that this is a "previous" reference time. - - long long prev = 1; //nonce - long long next = 0; //nonce - long long duration = -1; //really, this is unsigned - - long long bpos = -1; - long long bsize = -1; - - while (pos < stop) - { - long len; - const long long id = ReadUInt(pReader, pos, len); - assert(id >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume ID - - const long long size = ReadUInt(pReader, pos, len); - assert(size >= 0); //TODO - assert((pos + len) <= stop); - - pos += len; //consume size - - if (id == 0x21) //Block ID - { - if (bpos < 0) //Block ID - { - bpos = pos; - bsize = size; - } - } - else if (id == 0x1B) //Duration ID - { - assert(size <= 8); - - duration = UnserializeUInt(pReader, pos, size); - assert(duration >= 0); //TODO - } - else if (id == 0x7B) //ReferenceBlock - { - assert(size <= 8); - const long size_ = static_cast(size); - - long long time; - - long status = UnserializeInt(pReader, pos, size_, time); - assert(status == 0); //TODO - - if (time <= 0) //see note above - prev = time; - else //weird - next = time; - } - - pos += size; //consume payload - assert(pos <= stop); - } - - assert(pos == stop); - assert(bpos >= 0); - assert(bsize >= 0); - - const long idx = m_entries_count; - - BlockEntry** const ppEntry = m_entries + idx; - BlockEntry*& pEntry = *ppEntry; - - pEntry = new (std::nothrow) BlockGroup( - this, - idx, - bpos, - bsize, - prev, - next, - duration, - discard_padding); - - if (pEntry == NULL) - return -1; //generic error - - BlockGroup* const p = static_cast(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(pEntry); - - const long status = p->Parse(); - - if (status == 0) - { - ++m_entries_count; - return 0; - } - - delete pEntry; - pEntry = 0; - - return status; -} - - -long Cluster::GetFirst(const BlockEntry*& pFirst) const -{ - if (m_entries_count <= 0) - { - long long pos; - long len; - - const long status = Parse(pos, len); - - if (status < 0) //error - { - pFirst = NULL; - return status; - } - - if (m_entries_count <= 0) //empty cluster - { - pFirst = NULL; - return 0; - } - } - - assert(m_entries); - - pFirst = m_entries[0]; - assert(pFirst); - - return 0; //success -} - -long Cluster::GetLast(const BlockEntry*& pLast) const -{ - for (;;) - { - long long pos; - long len; - - const long status = Parse(pos, len); - - if (status < 0) //error - { - pLast = NULL; - return status; - } - - if (status > 0) //no new block - break; - } - - if (m_entries_count <= 0) - { - pLast = NULL; - return 0; - } - - assert(m_entries); - - const long idx = m_entries_count - 1; - - pLast = m_entries[idx]; - assert(pLast); - - return 0; -} - - -long Cluster::GetNext( - const BlockEntry* pCurr, - const BlockEntry*& pNext) const -{ - assert(pCurr); - assert(m_entries); - assert(m_entries_count > 0); - - size_t idx = pCurr->GetIndex(); - assert(idx < size_t(m_entries_count)); - assert(m_entries[idx] == pCurr); - - ++idx; - - if (idx >= size_t(m_entries_count)) - { - long long pos; - long len; - - const long status = Parse(pos, len); - - if (status < 0) //error - { - pNext = NULL; - return status; - } - - if (status > 0) - { - pNext = NULL; - return 0; - } - - assert(m_entries); - assert(m_entries_count > 0); - assert(idx < size_t(m_entries_count)); - } - - pNext = m_entries[idx]; - assert(pNext); - - return 0; -} - - -long Cluster::GetEntryCount() const -{ - return m_entries_count; -} - - -const BlockEntry* Cluster::GetEntry( - const Track* pTrack, - long long time_ns) const -{ - assert(pTrack); - - if (m_pSegment == NULL) //this is the special EOS cluster - return pTrack->GetEOS(); - -#if 0 - - LoadBlockEntries(); - - if ((m_entries == NULL) || (m_entries_count <= 0)) - return NULL; //return EOS here? - - const BlockEntry* pResult = pTrack->GetEOS(); - - BlockEntry** i = m_entries; - assert(i); - - BlockEntry** const j = i + m_entries_count; - - while (i != j) - { - const BlockEntry* const pEntry = *i++; - assert(pEntry); - assert(!pEntry->EOS()); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - if (pBlock->GetTrackNumber() != pTrack->GetNumber()) - continue; - - if (pTrack->VetEntry(pEntry)) - { - if (time_ns < 0) //just want first candidate block - return pEntry; - - const long long ns = pBlock->GetTime(this); - - if (ns > time_ns) - break; - - pResult = pEntry; - } - else if (time_ns >= 0) - { - const long long ns = pBlock->GetTime(this); - - if (ns > time_ns) - break; - } - } - - return pResult; - -#else - - const BlockEntry* pResult = pTrack->GetEOS(); - - long index = 0; - - for (;;) - { - if (index >= m_entries_count) - { - long long pos; - long len; - - const long status = Parse(pos, len); - assert(status >= 0); - - if (status > 0) //completely parsed, and no more entries - return pResult; - - if (status < 0) //should never happen - return 0; - - assert(m_entries); - assert(index < m_entries_count); - } - - const BlockEntry* const pEntry = m_entries[index]; - assert(pEntry); - assert(!pEntry->EOS()); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - if (pBlock->GetTrackNumber() != pTrack->GetNumber()) - { - ++index; - continue; - } - - if (pTrack->VetEntry(pEntry)) - { - if (time_ns < 0) //just want first candidate block - return pEntry; - - const long long ns = pBlock->GetTime(this); - - if (ns > time_ns) - return pResult; - - pResult = pEntry; //have a candidate - } - else if (time_ns >= 0) - { - const long long ns = pBlock->GetTime(this); - - if (ns > time_ns) - return pResult; - } - - ++index; - } - -#endif -} - - -const BlockEntry* -Cluster::GetEntry( - const CuePoint& cp, - const CuePoint::TrackPosition& tp) const -{ - assert(m_pSegment); - -#if 0 - - LoadBlockEntries(); - - if (m_entries == NULL) - return NULL; - - const long long count = m_entries_count; - - if (count <= 0) - return NULL; - - const long long tc = cp.GetTimeCode(); - - if ((tp.m_block > 0) && (tp.m_block <= count)) - { - const size_t block = static_cast(tp.m_block); - const size_t index = block - 1; - - const BlockEntry* const pEntry = m_entries[index]; - assert(pEntry); - assert(!pEntry->EOS()); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - if ((pBlock->GetTrackNumber() == tp.m_track) && - (pBlock->GetTimeCode(this) == tc)) - { - return pEntry; - } - } - - const BlockEntry* const* i = m_entries; - const BlockEntry* const* const j = i + count; - - while (i != j) - { -#ifdef _DEBUG - const ptrdiff_t idx = i - m_entries; - idx; -#endif - - const BlockEntry* const pEntry = *i++; - assert(pEntry); - assert(!pEntry->EOS()); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - if (pBlock->GetTrackNumber() != tp.m_track) - continue; - - const long long tc_ = pBlock->GetTimeCode(this); - assert(tc_ >= 0); - - if (tc_ < tc) - continue; - - if (tc_ > tc) - return NULL; - - const Tracks* const pTracks = m_pSegment->GetTracks(); - assert(pTracks); - - const long tn = static_cast(tp.m_track); - const Track* const pTrack = pTracks->GetTrackByNumber(tn); - - if (pTrack == NULL) - return NULL; - - const long long type = pTrack->GetType(); - - if (type == 2) //audio - return pEntry; - - if (type != 1) //not video - return NULL; - - if (!pBlock->IsKey()) - return NULL; - - return pEntry; - } - - return NULL; - -#else - - const long long tc = cp.GetTimeCode(); - - if (tp.m_block > 0) - { - const long block = static_cast(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(tp.m_track); - const Track* const pTrack = pTracks->GetTrackByNumber(tn); - - if (pTrack == NULL) - return NULL; - - const long long type = pTrack->GetType(); - - if (type == 2) //audio - return pEntry; - - if (type != 1) //not video - return NULL; - - if (!pBlock->IsKey()) - return NULL; - - return pEntry; - } - -#endif - -} - - -#if 0 -const BlockEntry* Cluster::GetMaxKey(const VideoTrack* pTrack) const -{ - assert(pTrack); - - if (m_pSegment == NULL) //EOS - return pTrack->GetEOS(); - - LoadBlockEntries(); - - if ((m_entries == NULL) || (m_entries_count <= 0)) - return pTrack->GetEOS(); - - BlockEntry** i = m_entries + m_entries_count; - BlockEntry** const j = m_entries; - - while (i != j) - { - const BlockEntry* const pEntry = *--i; - assert(pEntry); - assert(!pEntry->EOS()); - - const Block* const pBlock = pEntry->GetBlock(); - assert(pBlock); - - if (pBlock->GetTrackNumber() != pTrack->GetNumber()) - continue; - - if (pBlock->IsKey()) - return pEntry; - } - - return pTrack->GetEOS(); //no satisfactory block found -} -#endif - - -BlockEntry::BlockEntry(Cluster* p, long idx) : - m_pCluster(p), - m_index(idx) -{ -} - - -BlockEntry::~BlockEntry() -{ -} - - -bool BlockEntry::EOS() const -{ - return (GetKind() == kBlockEOS); -} - - -const Cluster* BlockEntry::GetCluster() const -{ - return m_pCluster; -} - - -long BlockEntry::GetIndex() const -{ - return m_index; -} - - -SimpleBlock::SimpleBlock( - Cluster* pCluster, - long idx, - long long start, - long long size) : - BlockEntry(pCluster, idx), - m_block(start, size, 0) -{ -} - - -long SimpleBlock::Parse() -{ - return m_block.Parse(m_pCluster); -} - - -BlockEntry::Kind SimpleBlock::GetKind() const -{ - return kBlockSimple; -} - - -const Block* SimpleBlock::GetBlock() const -{ - return &m_block; -} - - -BlockGroup::BlockGroup( - Cluster* pCluster, - long idx, - long long block_start, - long long block_size, - long long prev, - long long next, - long long duration, - long long discard_padding) : - BlockEntry(pCluster, idx), - m_block(block_start, block_size, discard_padding), - m_prev(prev), - m_next(next), - m_duration(duration) -{ -} - - -long BlockGroup::Parse() -{ - const long status = m_block.Parse(m_pCluster); - - if (status) - return status; - - m_block.SetKey((m_prev > 0) && (m_next <= 0)); - - return 0; -} - - -#if 0 -void BlockGroup::ParseBlock(long long start, long long size) -{ - IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; - - Block* const pBlock = new Block(start, size, pReader); - assert(pBlock); //TODO - - //TODO: the Matroska spec says you have multiple blocks within the - //same block group, with blocks ranked by priority (the flag bits). - - assert(m_pBlock == NULL); - m_pBlock = pBlock; -} -#endif - - -BlockEntry::Kind BlockGroup::GetKind() const -{ - return kBlockGroup; -} - - -const Block* BlockGroup::GetBlock() const -{ - return &m_block; -} - - -long long BlockGroup::GetPrevTimeCode() const -{ - return m_prev; -} - - -long long BlockGroup::GetNextTimeCode() const -{ - return m_next; -} - -long long BlockGroup::GetDurationTimeCode() const -{ - return m_duration; -} - -Block::Block(long long start, long long size_, long long discard_padding) : - m_start(start), - m_size(size_), - m_track(0), - m_timecode(-1), - m_flags(0), - m_frames(NULL), - m_frame_count(-1), - m_discard_padding(discard_padding) -{ -} - - -Block::~Block() -{ - delete[] m_frames; -} - - -long Block::Parse(const Cluster* pCluster) -{ - if (pCluster == NULL) - return -1; - - if (pCluster->m_pSegment == NULL) - return -1; - - assert(m_start >= 0); - assert(m_size >= 0); - assert(m_track <= 0); - assert(m_frames == NULL); - assert(m_frame_count <= 0); - - long long pos = m_start; - const long long stop = m_start + m_size; - - long len; - - IMkvReader* const pReader = pCluster->m_pSegment->m_pReader; - - m_track = ReadUInt(pReader, pos, len); - - if (m_track <= 0) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > stop) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume track number - - if ((stop - pos) < 2) - return E_FILE_FORMAT_INVALID; - - long status; - long long value; - - status = UnserializeInt(pReader, pos, 2, value); - - if (status) - return E_FILE_FORMAT_INVALID; - - if (value < SHRT_MIN) - return E_FILE_FORMAT_INVALID; - - if (value > SHRT_MAX) - return E_FILE_FORMAT_INVALID; - - m_timecode = static_cast(value); - - pos += 2; - - if ((stop - pos) <= 0) - return E_FILE_FORMAT_INVALID; - - status = pReader->Read(pos, 1, &m_flags); - - if (status) - return E_FILE_FORMAT_INVALID; - - const int lacing = int(m_flags & 0x06) >> 1; - - ++pos; //consume flags byte - - if (lacing == 0) //no lacing - { - if (pos > stop) - return E_FILE_FORMAT_INVALID; - - m_frame_count = 1; - m_frames = new Frame[m_frame_count]; - - Frame& f = m_frames[0]; - f.pos = pos; - - const long long frame_size = stop - pos; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - f.len = static_cast(frame_size); - - return 0; //success - } - - if (pos >= stop) - return E_FILE_FORMAT_INVALID; - - unsigned char biased_count; - - status = pReader->Read(pos, 1, &biased_count); - - if (status) - return E_FILE_FORMAT_INVALID; - - ++pos; //consume frame count - assert(pos <= stop); - - m_frame_count = int(biased_count) + 1; - - m_frames = new Frame[m_frame_count]; - assert(m_frames); - - if (lacing == 1) //Xiph - { - Frame* pf = m_frames; - Frame* const pf_end = pf + m_frame_count; - - long size = 0; - int frame_count = m_frame_count; - - while (frame_count > 1) - { - long frame_size = 0; - - for (;;) - { - unsigned char val; - - if (pos >= stop) - return E_FILE_FORMAT_INVALID; - - status = pReader->Read(pos, 1, &val); - - if (status) - return E_FILE_FORMAT_INVALID; - - ++pos; //consume xiph size byte - - frame_size += val; - - if (val < 255) - break; - } - - Frame& f = *pf++; - assert(pf < pf_end); - - f.pos = 0; //patch later - - f.len = frame_size; - size += frame_size; //contribution of this frame - - --frame_count; - } - - assert(pf < pf_end); - assert(pos <= stop); - - { - Frame& f = *pf++; - - if (pf != pf_end) - return E_FILE_FORMAT_INVALID; - - f.pos = 0; //patch later - - const long long total_size = stop - pos; - - if (total_size < size) - return E_FILE_FORMAT_INVALID; - - const long long frame_size = total_size - size; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - f.len = static_cast(frame_size); - } - - pf = m_frames; - while (pf != pf_end) - { - Frame& f = *pf++; - assert((pos + f.len) <= stop); - - f.pos = pos; - pos += f.len; - } - - assert(pos == stop); - } - else if (lacing == 2) //fixed-size lacing - { - const long long total_size = stop - pos; - - if ((total_size % m_frame_count) != 0) - return E_FILE_FORMAT_INVALID; - - const long long frame_size = total_size / m_frame_count; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - Frame* pf = m_frames; - Frame* const pf_end = pf + m_frame_count; - - while (pf != pf_end) - { - assert((pos + frame_size) <= stop); - - Frame& f = *pf++; - - f.pos = pos; - f.len = static_cast(frame_size); - - pos += frame_size; - } - - assert(pos == stop); - } - else - { - assert(lacing == 3); //EBML lacing - - if (pos >= stop) - return E_FILE_FORMAT_INVALID; - - long size = 0; - int frame_count = m_frame_count; - - long long frame_size = ReadUInt(pReader, pos, len); - - if (frame_size < 0) - return E_FILE_FORMAT_INVALID; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > stop) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume length of size of first frame - - if ((pos + frame_size) > stop) - return E_FILE_FORMAT_INVALID; - - Frame* pf = m_frames; - Frame* const pf_end = pf + m_frame_count; - - { - Frame& curr = *pf; - - curr.pos = 0; //patch later - - curr.len = static_cast(frame_size); - size += curr.len; //contribution of this frame - } - - --frame_count; - - while (frame_count > 1) - { - if (pos >= stop) - return E_FILE_FORMAT_INVALID; - - assert(pf < pf_end); - - const Frame& prev = *pf++; - assert(prev.len == frame_size); - - assert(pf < pf_end); - - Frame& curr = *pf; - - curr.pos = 0; //patch later - - const long long delta_size_ = ReadUInt(pReader, pos, len); - - if (delta_size_ < 0) - return E_FILE_FORMAT_INVALID; - - if ((pos + len) > stop) - return E_FILE_FORMAT_INVALID; - - pos += len; //consume length of (delta) size - assert(pos <= stop); - - const int exp = 7*len - 1; - const long long bias = (1LL << exp) - 1LL; - const long long delta_size = delta_size_ - bias; - - frame_size += delta_size; - - if (frame_size < 0) - return E_FILE_FORMAT_INVALID; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - curr.len = static_cast(frame_size); - size += curr.len; //contribution of this frame - - --frame_count; - } - - { - assert(pos <= stop); - assert(pf < pf_end); - - const Frame& prev = *pf++; - assert(prev.len == frame_size); - - assert(pf < pf_end); - - Frame& curr = *pf++; - assert(pf == pf_end); - - curr.pos = 0; //patch later - - const long long total_size = stop - pos; - - if (total_size < size) - return E_FILE_FORMAT_INVALID; - - frame_size = total_size - size; - - if (frame_size > LONG_MAX) - return E_FILE_FORMAT_INVALID; - - curr.len = static_cast(frame_size); - } - - pf = m_frames; - while (pf != pf_end) - { - Frame& f = *pf++; - assert((pos + f.len) <= stop); - - f.pos = pos; - pos += f.len; - } - - assert(pos == stop); - } - - return 0; //success -} - - -long long Block::GetTimeCode(const Cluster* pCluster) const -{ - if (pCluster == 0) - return m_timecode; - - const long long tc0 = pCluster->GetTimeCode(); - assert(tc0 >= 0); - - const long long tc = tc0 + m_timecode; - - return tc; //unscaled timecode units -} - - -long long Block::GetTime(const Cluster* pCluster) const -{ - assert(pCluster); - - const long long tc = GetTimeCode(pCluster); - - const Segment* const pSegment = pCluster->m_pSegment; - const SegmentInfo* const pInfo = pSegment->GetInfo(); - assert(pInfo); - - const long long scale = pInfo->GetTimeCodeScale(); - assert(scale >= 1); - - const long long ns = tc * scale; - - return ns; -} - - -long long Block::GetTrackNumber() const -{ - return m_track; -} - - -bool Block::IsKey() const -{ - return ((m_flags & static_cast(1 << 7)) != 0); -} - - -void Block::SetKey(bool bKey) -{ - if (bKey) - m_flags |= static_cast(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(value); -} - - -int Block::GetFrameCount() const -{ - return m_frame_count; -} - - -const Block::Frame& Block::GetFrame(int idx) const -{ - assert(idx >= 0); - assert(idx < m_frame_count); - - const Frame& f = m_frames[idx]; - assert(f.pos > 0); - assert(f.len > 0); - - return f; -} - - -long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const -{ - assert(pReader); - assert(buf); - - const long status = pReader->Read(pos, len, buf); - return status; -} - -long long Block::GetDiscardPadding() const -{ - return m_discard_padding; -} - -} //end namespace mkvparser +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvparser.hpp" +#include +#include +#include +#include + +mkvparser::IMkvReader::~IMkvReader() +{ +} + +void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) +{ + major = 1; + minor = 0; + build = 0; + revision = 27; +} + +long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len) +{ + assert(pReader); + assert(pos >= 0); + + int status; + +//#ifdef _DEBUG +// long long total, available; +// status = pReader->Length(&total, &available); +// assert(status >= 0); +// assert((total < 0) || (available <= total)); +// assert(pos < available); +// assert((available - pos) >= 1); //assume here max u-int len is 8 +//#endif + + len = 1; + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) //error or underflow + return status; + + if (status > 0) //interpreted as "underflow" + return E_BUFFER_NOT_FULL; + + if (b == 0) //we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) + { + m >>= 1; + ++len; + } + +//#ifdef _DEBUG +// assert((available - pos) >= len); +//#endif + + long long result = b & (~m); + ++pos; + + for (int i = 1; i < len; ++i) + { + status = pReader->Read(pos, 1, &b); + + if (status < 0) + { + len = 1; + return status; + } + + if (status > 0) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +long long mkvparser::GetUIntLength( + IMkvReader* pReader, + long long pos, + long& len) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + int status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + + len = 1; + + if (pos >= available) + return pos; //too few bytes available + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + assert(status == 0); + + if (b == 0) //we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) + { + m >>= 1; + ++len; + } + + return 0; //success +} + + +long long mkvparser::UnserializeUInt( + IMkvReader* pReader, + long long pos, + long long size) +{ + assert(pReader); + assert(pos >= 0); + + if ((size <= 0) || (size > 8)) + return E_FILE_FORMAT_INVALID; + + long long result = 0; + + for (long long i = 0; i < size; ++i) + { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + + +long mkvparser::UnserializeFloat( + IMkvReader* pReader, + long long pos, + long long size_, + double& result) +{ + assert(pReader); + assert(pos >= 0); + + if ((size_ != 4) && (size_ != 8)) + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + unsigned char buf[8]; + + const int status = pReader->Read(pos, size, buf); + + if (status < 0) //error + return status; + + if (size == 4) + { + union + { + float f; + unsigned long ff; + }; + + ff = 0; + + for (int i = 0;;) + { + ff |= buf[i]; + + if (++i >= 4) + break; + + ff <<= 8; + } + + result = f; + } + else + { + assert(size == 8); + + union + { + double d; + unsigned long long dd; + }; + + dd = 0; + + for (int i = 0;;) + { + dd |= buf[i]; + + if (++i >= 8) + break; + + dd <<= 8; + } + + result = d; + } + + return 0; +} + + +long mkvparser::UnserializeInt( + IMkvReader* pReader, + long long pos, + long size, + long long& result) +{ + assert(pReader); + assert(pos >= 0); + assert(size > 0); + assert(size <= 8); + + { + signed char b; + + const long status = pReader->Read(pos, 1, (unsigned char*)&b); + + if (status < 0) + return status; + + result = b; + + ++pos; + } + + for (long i = 1; i < size; ++i) + { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return 0; //success +} + + +long mkvparser::UnserializeString( + IMkvReader* pReader, + long long pos, + long long size_, + char*& str) +{ + delete[] str; + str = NULL; + + if (size_ >= LONG_MAX) //we need (size+1) chars + return E_FILE_FORMAT_INVALID; + + const long size = static_cast(size_); + + str = new (std::nothrow) char[size+1]; + + if (str == NULL) + return -1; + + unsigned char* const buf = reinterpret_cast(str); + + const long status = pReader->Read(pos, size, buf); + + if (status) + { + delete[] str; + str = NULL; + + return status; + } + + str[size] = '\0'; + + return 0; //success +} + + +long mkvparser::ParseElementHeader( + IMkvReader* pReader, + long long& pos, + long long stop, + long long& id, + long long& size) +{ + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + long len; + + id = ReadUInt(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume id + + if ((stop >= 0) && (pos >= stop)) + return E_FILE_FORMAT_INVALID; + + size = ReadUInt(pReader, pos, len); + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of size + + //pos now designates payload + + if ((stop >= 0) && ((pos + size) > stop)) + return E_FILE_FORMAT_INVALID; + + return 0; //success +} + + +bool mkvparser::Match( + IMkvReader* pReader, + long long& pos, + unsigned long id_, + long long& val) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + const long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; //consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert(size <= 8); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; //consume length of size of payload + + val = UnserializeUInt(pReader, pos, size); + assert(val >= 0); + + pos += size; //consume size of payload + + return true; +} + +bool mkvparser::Match( + IMkvReader* pReader, + long long& pos, + unsigned long id_, + unsigned char*& buf, + size_t& buflen) +{ + assert(pReader); + assert(pos >= 0); + + long long total, available; + + long status = pReader->Length(&total, &available); + assert(status >= 0); + assert((total < 0) || (available <= total)); + + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + if ((unsigned long)id != id_) + return false; + + pos += len; //consume id + + const long long size_ = ReadUInt(pReader, pos, len); + assert(size_ >= 0); + assert(len > 0); + assert(len <= 8); + assert((pos + len) <= available); + + pos += len; //consume length of size of payload + assert((pos + size_) <= available); + + const long buflen_ = static_cast(size_); + + buf = new (std::nothrow) unsigned char[buflen_]; + assert(buf); //TODO + + status = pReader->Read(pos, buflen_, buf); + assert(status == 0); //TODO + + buflen = buflen_; + + pos += size_; //consume size of payload + return true; +} + + +namespace mkvparser +{ + +EBMLHeader::EBMLHeader() : + m_docType(NULL) +{ + Init(); +} + +EBMLHeader::~EBMLHeader() +{ + delete[] m_docType; +} + +void EBMLHeader::Init() +{ + m_version = 1; + m_readVersion = 1; + m_maxIdLength = 4; + m_maxSizeLength = 8; + + if (m_docType) + { + delete[] m_docType; + m_docType = NULL; + } + + m_docTypeVersion = 1; + m_docTypeReadVersion = 1; +} + +long long EBMLHeader::Parse( + IMkvReader* pReader, + long long& pos) +{ + assert(pReader); + + long long total, available; + + long status = pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + pos = 0; + long long end = (available >= 1024) ? 1024 : available; + + for (;;) + { + unsigned char b = 0; + + while (pos < end) + { + status = pReader->Read(pos, 1, &b); + + if (status < 0) //error + return status; + + if (b == 0x1A) + break; + + ++pos; + } + + if (b != 0x1A) + { + if (pos >= 1024) + return E_FILE_FORMAT_INVALID; //don't bother looking anymore + + if ((total >= 0) && ((total - available) < 5)) + return E_FILE_FORMAT_INVALID; + + return available + 5; //5 = 4-byte ID + 1st byte of size + } + + if ((total >= 0) && ((total - pos) < 5)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < 5) + return pos + 5; //try again later + + long len; + + const long long result = ReadUInt(pReader, pos, len); + + if (result < 0) //error + return result; + + if (result == 0x0A45DFA3) //EBML Header ID + { + pos += len; //consume ID + break; + } + + ++pos; //throw away just the 0x1A byte, and try again + } + + //pos designates start of size field + + //get length of size field + + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //need more data + return result; + + assert(len > 0); + assert(len <= 8); + + if ((total >= 0) && ((total - pos) < len)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < len) + return pos + len; //try again later + + //get the EBML header size + + result = ReadUInt(pReader, pos, len); + + if (result < 0) //error + return result; + + pos += len; //consume size field + + //pos now designates start of payload + + if ((total >= 0) && ((total - pos) < result)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < result) + return pos + result; + + end = pos + result; + + Init(); + + while (pos < end) + { + long long id, size; + + status = ParseElementHeader( + pReader, + pos, + end, + id, + size); + + if (status < 0) //error + return status; + + if (size == 0) //weird + return E_FILE_FORMAT_INVALID; + + if (id == 0x0286) //version + { + m_version = UnserializeUInt(pReader, pos, size); + + if (m_version <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F7) //read version + { + m_readVersion = UnserializeUInt(pReader, pos, size); + + if (m_readVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F2) //max id length + { + m_maxIdLength = UnserializeUInt(pReader, pos, size); + + if (m_maxIdLength <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x02F3) //max size length + { + m_maxSizeLength = UnserializeUInt(pReader, pos, size); + + if (m_maxSizeLength <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0282) //doctype + { + if (m_docType) + return E_FILE_FORMAT_INVALID; + + status = UnserializeString(pReader, pos, size, m_docType); + + if (status) //error + return status; + } + else if (id == 0x0287) //doctype version + { + m_docTypeVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0285) //doctype read version + { + m_docTypeReadVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeReadVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; + } + + assert(pos == end); + return 0; +} + + +Segment::Segment( + IMkvReader* pReader, + long long elem_start, + //long long elem_size, + long long start, + long long size) : + m_pReader(pReader), + m_element_start(elem_start), + //m_element_size(elem_size), + m_start(start), + m_size(size), + m_pos(start), + m_pUnknownSize(0), + m_pSeekHead(NULL), + m_pInfo(NULL), + m_pTracks(NULL), + m_pCues(NULL), + m_pChapters(NULL), + m_clusters(NULL), + m_clusterCount(0), + m_clusterPreloadCount(0), + m_clusterSize(0) +{ +} + + +Segment::~Segment() +{ + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** i = m_clusters; + Cluster** j = m_clusters + count; + + while (i != j) + { + Cluster* const p = *i++; + assert(p); + + delete p; + } + + delete[] m_clusters; + + delete m_pTracks; + delete m_pInfo; + delete m_pCues; + delete m_pChapters; + delete m_pSeekHead; +} + + +long long Segment::CreateInstance( + IMkvReader* pReader, + long long pos, + Segment*& pSegment) +{ + assert(pReader); + assert(pos >= 0); + + pSegment = NULL; + + long long total, available; + + const long status = pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + if (available < 0) + return -1; + + if ((total >= 0) && (available > total)) + return -1; + + //I would assume that in practice this loop would execute + //exactly once, but we allow for other elements (e.g. Void) + //to immediately follow the EBML header. This is fine for + //the source filter case (since the entire file is available), + //but in the splitter case over a network we should probably + //just give up early. We could for example decide only to + //execute this loop a maximum of, say, 10 times. + //TODO: + //There is an implied "give up early" by only parsing up + //to the available limit. We do do that, but only if the + //total file size is unknown. We could decide to always + //use what's available as our limit (irrespective of whether + //we happen to know the total file length). This would have + //as its sense "parse this much of the file before giving up", + //which a slightly different sense from "try to parse up to + //10 EMBL elements before giving up". + + for (;;) + { + if ((total >= 0) && (pos >= total)) + return E_FILE_FORMAT_INVALID; + + //Read ID + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result) //error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return id; + + pos += len; //consume ID + + //Read Size + + result = GetUIntLength(pReader, pos, len); + + if (result) //error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return size; + + pos += len; //consume length of size of element + + //Pos now points to start of payload + + //Handle "unknown size" for live streaming of webm files. + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (id == 0x08538067) //Segment ID + { + if (size == unknown_size) + size = -1; + + else if (total < 0) + size = -1; + + else if ((pos + size) > total) + size = -1; + + pSegment = new (std::nothrow) Segment( + pReader, + idpos, + //elem_size + pos, + size); + + if (pSegment == 0) + return -1; //generic error + + return 0; //success + } + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + size) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + size) > available) + return pos + size; + + pos += size; //consume payload + } +} + + +long long Segment::ParseHeaders() +{ + //Outermost (level 0) segment object has been constructed, + //and pos designates start of payload. We need to find the + //inner (level 1) elements. + long long total, available; + + const int status = m_pReader->Length(&total, &available); + + if (status < 0) //error + return status; + + assert((total < 0) || (available <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + assert((segment_stop < 0) || (total < 0) || (segment_stop <= total)); + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + for (;;) + { + if ((total >= 0) && (m_pos >= total)) + break; + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + break; + + long long pos = m_pos; + const long long element_start = pos; + + if ((pos + 1) > available) + return (pos + 1); + + long len; + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error + return id; + + if (id == 0x0F43B675) //Cluster ID + break; + + pos += len; //consume ID + + if ((pos + 1) > available) + return (pos + 1); + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return result; + + if (result > 0) //underflow (weird) + return (pos + 1); + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) //error + return size; + + pos += len; //consume length of size of element + + const long long element_size = size + pos - element_start; + + //Pos now points to start of payload + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + //We read EBML elements either in total or nothing at all. + + if ((pos + size) > available) + return pos + size; + + if (id == 0x0549A966) //Segment Info ID + { + if (m_pInfo) + return E_FILE_FORMAT_INVALID; + + m_pInfo = new (std::nothrow) SegmentInfo( + this, + pos, + size, + element_start, + element_size); + + if (m_pInfo == NULL) + return -1; + + const long status = m_pInfo->Parse(); + + if (status) + return status; + } + else if (id == 0x0654AE6B) //Tracks ID + { + if (m_pTracks) + return E_FILE_FORMAT_INVALID; + + m_pTracks = new (std::nothrow) Tracks(this, + pos, + size, + element_start, + element_size); + + if (m_pTracks == NULL) + return -1; + + const long status = m_pTracks->Parse(); + + if (status) + return status; + } + else if (id == 0x0C53BB6B) //Cues ID + { + if (m_pCues == NULL) + { + m_pCues = new (std::nothrow) Cues( + this, + pos, + size, + element_start, + element_size); + + if (m_pCues == NULL) + return -1; + } + } + else if (id == 0x014D9B74) //SeekHead ID + { + if (m_pSeekHead == NULL) + { + m_pSeekHead = new (std::nothrow) SeekHead( + this, + pos, + size, + element_start, + element_size); + + if (m_pSeekHead == NULL) + return -1; + + const long status = m_pSeekHead->Parse(); + + if (status) + return status; + } + } + else if (id == 0x0043A770) //Chapters ID + { + if (m_pChapters == NULL) + { + m_pChapters = new (std::nothrow) Chapters( + this, + pos, + size, + element_start, + element_size); + + if (m_pChapters == NULL) + return -1; + + const long status = m_pChapters->Parse(); + + if (status) + return status; + } + } + + m_pos = pos + size; //consume payload + } + + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + if (m_pInfo == NULL) //TODO: liberalize this behavior + return E_FILE_FORMAT_INVALID; + + if (m_pTracks == NULL) + return E_FILE_FORMAT_INVALID; + + return 0; //success +} + + +long Segment::LoadCluster( + long long& pos, + long& len) +{ + for (;;) + { + const long result = DoLoadCluster(pos, len); + + if (result <= 1) + return result; + } +} + + +long Segment::DoLoadCluster( + long long& pos, + long& len) +{ + if (m_pos < 0) + return DoLoadClusterUnknownSize(pos, len); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long cluster_off = -1; //offset relative to start of segment + long long cluster_size = -1; //size of cluster payload + + for (;;) + { + if ((total >= 0) && (m_pos >= total)) + return 1; //no more clusters + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + return 1; //no more clusters + + pos = m_pos; + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + pos += len; //consume ID + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume length of size of element + + //pos now points to start of payload + + if (size == 0) //weird + { + m_pos = pos; + continue; + } + + const long long unknown_size = (1LL << (7 * len)) - 1; + +#if 0 //we must handle this to support live webm + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: allow this +#endif + + if ((segment_stop >= 0) && + (size != unknown_size) && + ((pos + size) > segment_stop)) + { + return E_FILE_FORMAT_INVALID; + } + +#if 0 //commented-out, to support incremental cluster parsing + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; +#endif + + if (id == 0x0C53BB6B) //Cues ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: liberalize + + if (m_pCues == NULL) + { + const long long element_size = (pos - idpos) + size; + + m_pCues = new Cues(this, + pos, + size, + idpos, + element_size); + assert(m_pCues); //TODO + } + + m_pos = pos + size; //consume payload + continue; + } + + if (id != 0x0F43B675) //Cluster ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //TODO: liberalize + + m_pos = pos + size; //consume payload + continue; + } + + //We have a cluster. + + cluster_off = idpos - m_start; //relative pos + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(cluster_off >= 0); //have cluster + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_); + + if (status < 0) //error, or underflow + { + pos = pos_; + len = len_; + + return status; + } + + //status == 0 means "no block entries found" + //status > 0 means "found at least one block entry" + + //TODO: + //The issue here is that the segment increments its own + //pos ptr past the most recent cluster parsed, and then + //starts from there to parse the next cluster. If we + //don't know the size of the current cluster, then we + //must either parse its payload (as we do below), looking + //for the cluster (or cues) ID to terminate the parse. + //This isn't really what we want: rather, we really need + //a way to create the curr cluster object immediately. + //The pity is that cluster::parse can determine its own + //boundary, and we largely duplicate that same logic here. + // + //Maybe we need to get rid of our look-ahead preloading + //in source::parse??? + // + //As we're parsing the blocks in the curr cluster + //(in cluster::parse), we should have some way to signal + //to the segment that we have determined the boundary, + //so it can adjust its own segment::m_pos member. + // + //The problem is that we're asserting in asyncreadinit, + //because we adjust the pos down to the curr seek pos, + //and the resulting adjusted len is > 2GB. I'm suspicious + //that this is even correct, but even if it is, we can't + //be loading that much data in the cache anyway. + + const long idx = m_clusterCount; + + if (m_clusterPreloadCount > 0) + { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) //preloaded already + { + if (status == 0) //no entries found + return E_FILE_FORMAT_INVALID; + + if (cluster_size >= 0) + pos += cluster_size; + else + { + const long long element_size = pCluster->GetElementSize(); + + if (element_size <= 0) + return E_FILE_FORMAT_INVALID; //TODO: handle this case + + pos = pCluster->m_element_start + element_size; + } + + pCluster->m_index = idx; //move from preloaded to loaded + ++m_clusterCount; + --m_clusterPreloadCount; + + m_pos = pos; //consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; //success + } + } + + if (status == 0) //no entries found + { + if (cluster_size < 0) + return E_FILE_FORMAT_INVALID; //TODO: handle this + + pos += cluster_size; + + if ((total >= 0) && (pos >= total)) + { + m_pos = total; + return 1; //no more clusters + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) + { + m_pos = segment_stop; + return 1; //no more clusters + } + + m_pos = pos; + return 2; //try again + } + + //status > 0 means we have an entry + + Cluster* const pCluster = Cluster::Create(this, + idx, + cluster_off); + //element_size); + assert(pCluster); + + AppendCluster(pCluster); + assert(m_clusters); + assert(idx < m_clusterSize); + assert(m_clusters[idx] == pCluster); + + if (cluster_size >= 0) + { + pos += cluster_size; + + m_pos = pos; + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 0; + } + + m_pUnknownSize = pCluster; + m_pos = -pos; + + return 0; //partial success, since we have a new cluster + + //status == 0 means "no block entries found" + + //pos designates start of payload + //m_pos has NOT been adjusted yet (in case we need to come back here) + +#if 0 + + if (cluster_size < 0) //unknown size + { + const long long payload_pos = pos; //absolute pos of cluster payload + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; //no more clusters + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); + + pos = payload_pos; //reset and re-parse original cluster + } + + if (m_clusterPreloadCount > 0) + { + assert(idx < m_clusterSize); + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + assert(pCluster->m_index < 0); + + const long long off = pCluster->GetPosition(); + assert(off >= 0); + + if (off == cluster_off) //preloaded already + return E_FILE_FORMAT_INVALID; //subtle + } + + m_pos = pos + cluster_size; //consume payload + assert((segment_stop < 0) || (m_pos <= segment_stop)); + + return 2; //try to find another cluster + +#endif + +} + + +long Segment::DoLoadClusterUnknownSize( + long long& pos, + long& len) +{ + assert(m_pos < 0); + assert(m_pUnknownSize); + +#if 0 + assert(m_pUnknownSize->GetElementSize() < 0); //TODO: verify this + + const long long element_start = m_pUnknownSize->m_element_start; + + pos = -m_pos; + assert(pos > element_start); + + //We have already consumed the (cluster) ID and size fields. + //We just need to consume the blocks and other sub-elements + //of this cluster, until we discover the boundary. + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long element_size = -1; + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + { + element_size = total - element_start; + assert(element_size > 0); + + break; + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) + { + element_size = segment_stop - element_start; + assert(element_size > 0); + + break; + } + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster ID or Cues ID + { + element_size = pos - element_start; + assert(element_size > 0); + + break; + } + +#ifdef _DEBUG + switch (id) + { + case 0x20: //BlockGroup + case 0x23: //Simple Block + case 0x67: //TimeCode + case 0x2B: //PrevSize + break; + + default: + assert(false); + break; + } +#endif + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + assert(element_size >= 0); + + m_pos = element_start + element_size; + m_pUnknownSize = 0; + + return 2; //continue parsing +#else + const long status = m_pUnknownSize->Parse(pos, len); + + if (status < 0) //error or underflow + return status; + + if (status == 0) //parsed a block + return 2; //continue parsing + + assert(status > 0); //nothing left to parse of this cluster + + const long long start = m_pUnknownSize->m_element_start; + + const long long size = m_pUnknownSize->GetElementSize(); + assert(size >= 0); + + pos = start + size; + m_pos = pos; + + m_pUnknownSize = 0; + + return 2; //continue parsing +#endif +} + + +void Segment::AppendCluster(Cluster* pCluster) +{ + assert(pCluster); + assert(pCluster->m_index >= 0); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + const long idx = pCluster->m_index; + assert(idx == m_clusterCount); + + if (count >= size) + { + const long n = (size <= 0) ? 2048 : 2*size; + + Cluster** const qq = new Cluster*[n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + if (m_clusterPreloadCount > 0) + { + assert(m_clusters); + + Cluster** const p = m_clusters + m_clusterCount; + assert(*p); + assert((*p)->m_index < 0); + + Cluster** q = p + m_clusterPreloadCount; + assert(q < (m_clusters + size)); + + for (;;) + { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + + if (q == p) + break; + } + } + + m_clusters[idx] = pCluster; + ++m_clusterCount; +} + + +void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) +{ + assert(pCluster); + assert(pCluster->m_index < 0); + assert(idx >= m_clusterCount); + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + assert(size >= count); + + if (count >= size) + { + const long n = (size <= 0) ? 2048 : 2*size; + + Cluster** const qq = new Cluster*[n]; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + assert(m_clusters); + + Cluster** const p = m_clusters + idx; + + Cluster** q = m_clusters + count; + assert(q >= p); + assert(q < (m_clusters + size)); + + while (q > p) + { + Cluster** const qq = q - 1; + assert((*qq)->m_index < 0); + + *q = *qq; + q = qq; + } + + m_clusters[idx] = pCluster; + ++m_clusterPreloadCount; +} + + +long Segment::Load() +{ + assert(m_clusters == NULL); + assert(m_clusterSize == 0); + assert(m_clusterCount == 0); + //assert(m_size >= 0); + + //Outermost (level 0) segment object has been constructed, + //and pos designates start of payload. We need to find the + //inner (level 1) elements. + + const long long header_status = ParseHeaders(); + + if (header_status < 0) //error + return static_cast(header_status); + + if (header_status > 0) //underflow + return E_BUFFER_NOT_FULL; + + assert(m_pInfo); + assert(m_pTracks); + + for (;;) + { + const int status = LoadCluster(); + + if (status < 0) //error + return status; + + if (status >= 1) //no more clusters + return 0; + } +} + + +SeekHead::SeekHead( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_entries(0), + m_entry_count(0), + m_void_elements(0), + m_void_element_count(0) +{ +} + + +SeekHead::~SeekHead() +{ + delete[] m_entries; + delete[] m_void_elements; +} + + +long SeekHead::Parse() +{ + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + //first count the seek head entries + + int entry_count = 0; + int void_element_count = 0; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0DBB) //SeekEntry ID + ++entry_count; + else if (id == 0x6C) //Void ID + ++void_element_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + m_entries = new (std::nothrow) Entry[entry_count]; + + if (m_entries == NULL) + return -1; + + m_void_elements = new (std::nothrow) VoidElement[void_element_count]; + + if (m_void_elements == NULL) + return -1; + + //now parse the entries and void elements + + Entry* pEntry = m_entries; + VoidElement* pVoidElement = m_void_elements; + + pos = m_start; + + while (pos < stop) + { + const long long idpos = pos; + + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0DBB) //SeekEntry ID + { + if (ParseEntry(pReader, pos, size, pEntry)) + { + Entry& e = *pEntry++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + } + else if (id == 0x6C) //Void ID + { + VoidElement& e = *pVoidElement++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries); + assert(count_ >= 0); + assert(count_ <= entry_count); + + m_entry_count = static_cast(count_); + + count_ = ptrdiff_t(pVoidElement - m_void_elements); + assert(count_ >= 0); + assert(count_ <= void_element_count); + + m_void_element_count = static_cast(count_); + + return 0; +} + + +int SeekHead::GetCount() const +{ + return m_entry_count; +} + +const SeekHead::Entry* SeekHead::GetEntry(int idx) const +{ + if (idx < 0) + return 0; + + if (idx >= m_entry_count) + return 0; + + return m_entries + idx; +} + +int SeekHead::GetVoidElementCount() const +{ + return m_void_element_count; +} + +const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const +{ + if (idx < 0) + return 0; + + if (idx >= m_void_element_count) + return 0; + + return m_void_elements + idx; +} + + +#if 0 +void Segment::ParseCues(long long off) +{ + if (m_pCues) + return; + + //odbgstream os; + //os << "Segment::ParseCues (begin)" << endl; + + long long pos = m_start + off; + const long long element_start = pos; + const long long stop = m_start + m_size; + + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id == 0x0C53BB6B); //Cues ID + + pos += len; //consume ID + assert(pos < stop); + + //Read Size + + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); + + pos += len; //consume length of size of element + assert((pos + size) <= stop); + + const long long element_size = size + pos - element_start; + + //Pos now points to start of payload + + m_pCues = new Cues(this, pos, size, element_start, element_size); + assert(m_pCues); //TODO + + //os << "Segment::ParseCues (end)" << endl; +} +#else +long Segment::ParseCues( + long long off, + long long& pos, + long& len) +{ + if (m_pCues) + return 0; //success + + if (off < 0) + return -1; + + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_start + off; + + if ((total < 0) || (pos >= total)) + return 1; //don't bother parsing cues + + const long long element_start = pos; + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id != 0x0C53BB6B) //Cues ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume ID + assert((segment_stop < 0) || (pos <= segment_stop)); + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(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(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; + + const long long element_size = element_stop - element_start; + + m_pCues = new (std::nothrow) Cues( + this, + pos, + size, + element_start, + element_size); + assert(m_pCues); //TODO + + return 0; //success +} +#endif + + +#if 0 +void Segment::ParseSeekEntry( + long long start, + long long size_) +{ + long long pos = start; + + const long long stop = start + size_; + + long len; + + const long long seekIdId = ReadUInt(m_pReader, pos, len); + //seekIdId; + assert(seekIdId == 0x13AB); //SeekID ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekIdSize = ReadUInt(m_pReader, pos, len); + assert(seekIdSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + + const long long seekId = ReadUInt(m_pReader, pos, len); //payload + assert(seekId >= 0); + assert(len == seekIdSize); + assert((pos + len) <= stop); + + pos += seekIdSize; //consume payload + + const long long seekPosId = ReadUInt(m_pReader, pos, len); + //seekPosId; + assert(seekPosId == 0x13AC); //SeekPos ID + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long seekPosSize = ReadUInt(m_pReader, pos, len); + assert(seekPosSize >= 0); + assert((pos + len) <= stop); + + pos += len; //consume size + assert((pos + seekPosSize) <= stop); + + const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize); + assert(seekOff >= 0); + assert(seekOff < m_size); + + pos += seekPosSize; //consume payload + assert(pos == stop); + + const long long seekPos = m_start + seekOff; + assert(seekPos < (m_start + m_size)); + + if (seekId == 0x0C53BB6B) //Cues ID + ParseCues(seekOff); +} +#else +bool SeekHead::ParseEntry( + IMkvReader* pReader, + long long start, + long long size_, + Entry* pEntry) +{ + if (size_ <= 0) + return false; + + long long pos = start; + const long long stop = start + size_; + + long len; + + //parse the container for the level-1 element ID + + const long long seekIdId = ReadUInt(pReader, pos, len); + //seekIdId; + + if (seekIdId != 0x13AB) //SeekID ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume SeekID id + + const long long seekIdSize = ReadUInt(pReader, pos, len); + + if (seekIdSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume size of field + + if ((pos + seekIdSize) > stop) + return false; + + //Note that the SeekId payload really is serialized + //as a "Matroska integer", not as a plain binary value. + //In fact, Matroska requires that ID values in the + //stream exactly match the binary representation as listed + //in the Matroska specification. + // + //This parser is more liberal, and permits IDs to have + //any width. (This could make the representation in the stream + //different from what's in the spec, but it doesn't matter here, + //since we always normalize "Matroska integer" values.) + + pEntry->id = ReadUInt(pReader, pos, len); //payload + + if (pEntry->id <= 0) + return false; + + if (len != seekIdSize) + return false; + + pos += seekIdSize; //consume SeekID payload + + const long long seekPosId = ReadUInt(pReader, pos, len); + + if (seekPosId != 0x13AC) //SeekPos ID + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume id + + const long long seekPosSize = ReadUInt(pReader, pos, len); + + if (seekPosSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; //consume size + + if ((pos + seekPosSize) > stop) + return false; + + pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize); + + if (pEntry->pos < 0) + return false; + + pos += seekPosSize; //consume payload + + if (pos != stop) + return false; + + return true; +} +#endif + + +Cues::Cues( + Segment* pSegment, + long long start_, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start_), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_cue_points(NULL), + m_count(0), + m_preload_count(0), + m_pos(start_) +{ +} + + +Cues::~Cues() +{ + const long n = m_count + m_preload_count; + + CuePoint** p = m_cue_points; + CuePoint** const q = p + n; + + while (p != q) + { + CuePoint* const pCP = *p++; + assert(pCP); + + delete pCP; + } + + delete[] m_cue_points; +} + + +long Cues::GetCount() const +{ + if (m_cue_points == NULL) + return -1; + + return m_count; //TODO: really ignore preload count? +} + + +bool Cues::DoneParsing() const +{ + const long long stop = m_start + m_size; + return (m_pos >= stop); +} + + +void Cues::Init() const +{ + if (m_cue_points) + return; + + assert(m_count == 0); + assert(m_preload_count == 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + const long long stop = m_start + m_size; + long long pos = m_start; + + long cue_points_size = 0; + + while (pos < stop) + { + const long long idpos = pos; + + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x3B) //CuePoint ID + PreloadCuePoint(cue_points_size, idpos); + + pos += size; //consume payload + assert(pos <= stop); + } +} + + +void Cues::PreloadCuePoint( + long& cue_points_size, + long long pos) const +{ + assert(m_count == 0); + + if (m_preload_count >= cue_points_size) + { + const long n = (cue_points_size <= 0) ? 2048 : 2*cue_points_size; + + CuePoint** const qq = new CuePoint*[n]; + CuePoint** q = qq; //beginning of target + + CuePoint** p = m_cue_points; //beginning of source + CuePoint** const pp = p + m_preload_count; //end of source + + while (p != pp) + *q++ = *p++; + + delete[] m_cue_points; + + m_cue_points = qq; + cue_points_size = n; + } + + CuePoint* const pCP = new CuePoint(m_preload_count, pos); + m_cue_points[m_preload_count++] = pCP; +} + + +bool Cues::LoadCuePoint() const +{ + //odbgstream os; + //os << "Cues::LoadCuePoint" << endl; + + const long long stop = m_start + m_size; + + if (m_pos >= stop) + return false; //nothing else to do + + Init(); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (m_pos < stop) + { + const long long idpos = m_pos; + + long len; + + const long long id = ReadUInt(pReader, m_pos, len); + assert(id >= 0); //TODO + assert((m_pos + len) <= stop); + + m_pos += len; //consume ID + + const long long size = ReadUInt(pReader, m_pos, len); + assert(size >= 0); + assert((m_pos + len) <= stop); + + m_pos += len; //consume Size field + assert((m_pos + size) <= stop); + + if (id != 0x3B) //CuePoint ID + { + m_pos += size; //consume payload + assert(m_pos <= stop); + + continue; + } + + assert(m_preload_count > 0); + + CuePoint* const pCP = m_cue_points[m_count]; + assert(pCP); + assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos)); + + pCP->Load(pReader); + ++m_count; + --m_preload_count; + + m_pos += size; //consume payload + assert(m_pos <= stop); + + return true; //yes, we loaded a cue point + } + + //return (m_pos < stop); + return false; //no, we did not load a cue point +} + + +bool Cues::Find( + long long time_ns, + const Track* pTrack, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const +{ + assert(time_ns >= 0); + assert(pTrack); + +#if 0 + LoadCuePoint(); //establish invariant + + assert(m_cue_points); + assert(m_count > 0); + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count + m_preload_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) + { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + pCP->Load(pReader); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#else + if (m_cue_points == NULL) + return false; + + if (m_count == 0) + return false; + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count; + CuePoint** j = jj; + + pCP = *i; + assert(pCP); + + if (time_ns <= pCP->GetTime(m_pSegment)) + { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + assert(k < jj); + + CuePoint* const pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + assert(i > ii); + + pCP = *--i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) <= time_ns); +#endif + + //TODO: here and elsewhere, it's probably not correct to search + //for the cue point with this time, and then search for a matching + //track. In principle, the matching track could be on some earlier + //cue point, and with our current algorithm, we'd miss it. To make + //this bullet-proof, we'd need to create a secondary structure, + //with a list of cue points that apply to a track, and then search + //that track-based structure for a matching cue point. + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} + + +#if 0 +bool Cues::FindNext( + long long time_ns, + const Track* pTrack, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const +{ + pCP = 0; + pTP = 0; + + if (m_count == 0) + return false; + + assert(m_cue_points); + + const CuePoint* const* const ii = m_cue_points; + const CuePoint* const* i = ii; + + const CuePoint* const* const jj = ii + m_count; + const CuePoint* const* j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + const CuePoint* const* const k = i + (j - i) / 2; + assert(k < jj); + + pCP = *k; + assert(pCP); + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i <= jj); + + if (i >= jj) //time_ns is greater than max cue point + return false; + + pCP = *i; + assert(pCP); + assert(pCP->GetTime(m_pSegment) > time_ns); + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} +#endif + + +const CuePoint* Cues::GetFirst() const +{ + if (m_cue_points == NULL) + return NULL; + + if (m_count == 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; +#endif + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[0]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); + + return pCP; +} + + +const CuePoint* Cues::GetLast() const +{ + if (m_cue_points == NULL) + return NULL; + + if (m_count <= 0) + return NULL; + +#if 0 + LoadCuePoint(); //init cues + + const size_t count = m_count + m_preload_count; + + if (count == 0) //weird + return NULL; + + const size_t index = count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + + pCP->Load(m_pSegment->m_pReader); + assert(pCP->GetTimeCode() >= 0); +#else + const long index = m_count - 1; + + CuePoint* const* const pp = m_cue_points; + assert(pp); + + CuePoint* const pCP = pp[index]; + assert(pCP); + assert(pCP->GetTimeCode() >= 0); +#endif + + return pCP; +} + + +const CuePoint* Cues::GetNext(const CuePoint* pCurr) const +{ + if (pCurr == NULL) + return NULL; + + assert(pCurr->GetTimeCode() >= 0); + assert(m_cue_points); + assert(m_count >= 1); + +#if 0 + const size_t count = m_count + m_preload_count; + + size_t index = pCurr->m_index; + assert(index < count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + + pNext->Load(m_pSegment->m_pReader); +#else + long index = pCurr->m_index; + assert(index < m_count); + + CuePoint* const* const pp = m_cue_points; + assert(pp); + assert(pp[index] == pCurr); + + ++index; + + if (index >= m_count) + return NULL; + + CuePoint* const pNext = pp[index]; + assert(pNext); + assert(pNext->GetTimeCode() >= 0); +#endif + + return pNext; +} + + +const BlockEntry* Cues::GetBlock( + const CuePoint* pCP, + const CuePoint::TrackPosition* pTP) const +{ + if (pCP == NULL) + return NULL; + + if (pTP == NULL) + return NULL; + + return m_pSegment->GetBlock(*pCP, *pTP); +} + + +const BlockEntry* Segment::GetBlock( + const CuePoint& cp, + const CuePoint::TrackPosition& tp) +{ + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + //const long long pos_ = pCluster->m_pos; + //assert(pos_); + //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < tp.m_pos) + i = k + 1; + else if (pos > tp.m_pos) + j = k; + else + return pCluster->GetEntry(cp, tp); + } + + assert(i == j); + //assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster->GetEntry(cp, tp); +} + + +const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) +{ + if (requested_pos < 0) + return 0; + + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + //const long long pos_ = pCluster->m_pos; + //assert(pos_); + //const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < requested_pos) + i = k + 1; + else if (pos > requested_pos) + j = k; + else + return pCluster; + } + + assert(i == j); + //assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create( + this, + -1, + requested_pos); + //-1); + assert(pCluster); + + const ptrdiff_t idx = i - m_clusters; + + PreloadCluster(pCluster, idx); + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster; +} + + +CuePoint::CuePoint(long idx, long long pos) : + m_element_start(0), + m_element_size(0), + m_index(idx), + m_timecode(-1 * pos), + m_track_positions(NULL), + m_track_positions_count(0) +{ + assert(pos > 0); +} + + +CuePoint::~CuePoint() +{ + delete[] m_track_positions; +} + + +void CuePoint::Load(IMkvReader* pReader) +{ + //odbgstream os; + //os << "CuePoint::Load(begin): timecode=" << m_timecode << endl; + + if (m_timecode >= 0) //already loaded + return; + + assert(m_track_positions == NULL); + assert(m_track_positions_count == 0); + + long long pos_ = -m_timecode; + const long long element_start = pos_; + + long long stop; + + { + long len; + + const long long id = ReadUInt(pReader, pos_, len); + assert(id == 0x3B); //CuePoint ID + //assert((pos + len) <= stop); + + pos_ += len; //consume ID + + const long long size = ReadUInt(pReader, pos_, len); + assert(size >= 0); + //assert((pos + len) <= stop); + + pos_ += len; //consume Size field + //assert((pos + size) <= stop); + + //pos_ now points to start of payload + + stop = pos_ + size; + } + + const long long element_size = stop - element_start; + + long long pos = pos_; + + //First count number of track positions + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x33) //CueTime ID + m_timecode = UnserializeUInt(pReader, pos, size); + + else if (id == 0x37) //CueTrackPosition(s) ID + ++m_track_positions_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(m_timecode >= 0); + assert(m_track_positions_count > 0); + + //os << "CuePoint::Load(cont'd): idpos=" << idpos + // << " timecode=" << m_timecode + // << endl; + + m_track_positions = new TrackPosition[m_track_positions_count]; + + //Now parse track positions + + TrackPosition* p = m_track_positions; + pos = pos_; + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x37) //CueTrackPosition(s) ID + { + TrackPosition& tp = *p++; + tp.Parse(pReader, pos, size); + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(size_t(p - m_track_positions) == m_track_positions_count); + + m_element_start = element_start; + m_element_size = element_size; +} + + + +void CuePoint::TrackPosition::Parse( + IMkvReader* pReader, + long long start_, + long long size_) +{ + const long long stop = start_ + size_; + long long pos = start_; + + m_track = -1; + m_pos = -1; + m_block = 1; //default + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; //consume Size field + assert((pos + size) <= stop); + + if (id == 0x77) //CueTrack ID + m_track = UnserializeUInt(pReader, pos, size); + + else if (id == 0x71) //CueClusterPos ID + m_pos = UnserializeUInt(pReader, pos, size); + + else if (id == 0x1378) //CueBlockNumber + m_block = UnserializeUInt(pReader, pos, size); + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(m_pos >= 0); + assert(m_track > 0); + //assert(m_block > 0); +} + + +const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const +{ + assert(pTrack); + + const long long n = pTrack->GetNumber(); + + const TrackPosition* i = m_track_positions; + const TrackPosition* const j = i + m_track_positions_count; + + while (i != j) + { + const TrackPosition& p = *i++; + + if (p.m_track == n) + return &p; + } + + return NULL; //no matching track number found +} + + +long long CuePoint::GetTimeCode() const +{ + return m_timecode; +} + +long long CuePoint::GetTime(const Segment* pSegment) const +{ + assert(pSegment); + assert(m_timecode >= 0); + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long time = scale * m_timecode; + + return time; +} + + +#if 0 +long long Segment::Unparsed() const +{ + if (m_size < 0) + return LLONG_MAX; + + const long long stop = m_start + m_size; + + const long long result = stop - m_pos; + assert(result >= 0); + + return result; +} +#else +bool Segment::DoneParsing() const +{ + if (m_size < 0) + { + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return true; //must assume done + + if (total < 0) + return false; //assume live stream + + return (m_pos >= total); + } + + const long long stop = m_start + m_size; + + return (m_pos >= stop); +} +#endif + + +const Cluster* Segment::GetFirst() const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + + return pCluster; +} + + +const Cluster* Segment::GetLast() const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + const long idx = m_clusterCount - 1; + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + + return pCluster; +} + + +unsigned long Segment::GetCount() const +{ + return m_clusterCount; +} + + +const Cluster* Segment::GetNext(const Cluster* pCurr) +{ + assert(pCurr); + assert(pCurr != &m_eos); + assert(m_clusters); + + long idx = pCurr->m_index; + + if (idx >= 0) + { + assert(m_clusterCount > 0); + assert(idx < m_clusterCount); + assert(pCurr == m_clusters[idx]); + + ++idx; + + if (idx >= m_clusterCount) + return &m_eos; //caller will LoadCluster as desired + + Cluster* const pNext = m_clusters[idx]; + assert(pNext); + assert(pNext->m_index >= 0); + assert(pNext->m_index == idx); + + return pNext; + } + + assert(m_clusterPreloadCount > 0); + + long long pos = pCurr->m_element_start; + + assert(m_size >= 0); //TODO + const long long stop = m_start + m_size; //end of segment + + { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); //TODO + assert((pos + len) <= stop); //TODO + + const long long id = ReadUInt(m_pReader, pos, len); + assert(id == 0x0F43B675); //Cluster ID //TODO + + pos += len; //consume ID + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); //TODO + assert((pos + len) <= stop); //TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size > 0); //TODO + //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); //TODO + assert((pos + len) <= stop); //TODO + + const long long idpos = pos; //pos of next (potential) cluster + + const long long id = ReadUInt(m_pReader, idpos, len); + assert(id > 0); //TODO + + pos += len; //consume ID + + //Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); //TODO + assert((pos + len) <= stop); //TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); //TODO + + pos += len; //consume length of size of element + assert((pos + size) <= stop); //TODO + + //Pos now points to start of payload + + if (size == 0) //weird + continue; + + if (id == 0x0F43B675) //Cluster ID + { + const long long off_next_ = idpos - m_start; + + long long pos_; + long len_; + + const long status = Cluster::HasBlockEntries( + this, + off_next_, + pos_, + len_); + + assert(status >= 0); + + if (status > 0) + { + off_next = off_next_; + break; + } + } + + pos += size; //consume payload + } + + if (off_next <= 0) + return 0; + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + //const long long pos_ = pNext->m_pos; + //assert(pos_); + //pos = pos_ * ((pos_ < 0) ? -1 : 1); + + pos = pNext->GetPosition(); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + return pNext; + } + + assert(i == j); + + Cluster* const pNext = Cluster::Create(this, + -1, + off_next); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; //insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + return pNext; +} + + +long Segment::ParseNext( + const Cluster* pCurr, + const Cluster*& pResult, + long long& pos, + long& len) +{ + assert(pCurr); + assert(!pCurr->EOS()); + assert(m_clusters); + + pResult = 0; + + if (pCurr->m_index >= 0) //loaded (not merely preloaded) + { + assert(m_clusters[pCurr->m_index] == pCurr); + + const long next_idx = pCurr->m_index + 1; + + if (next_idx < m_clusterCount) + { + pResult = m_clusters[next_idx]; + return 0; //success + } + + //curr cluster is last among loaded + + const long result = LoadCluster(pos, len); + + if (result < 0) //error or underflow + return result; + + if (result > 0) //no more clusters + { + //pResult = &m_eos; + return 1; + } + + pResult = GetLast(); + return 0; //success + } + + assert(m_pos > 0); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + //interrogate curr cluster + + pos = pCurr->m_element_start; + + if (pCurr->m_element_size >= 0) + pos += pCurr->m_element_size; + else + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(m_pReader, pos, len); + + if (id != 0x0F43B675) //weird: not Cluster ID + return -1; + + pos += len; //consume ID + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) //TODO: should never happen + return E_FILE_FORMAT_INVALID; //TODO: resolve this + + //assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + //Pos now points to start of payload + + pos += size; //consume payload (that is, the current cluster) + assert((segment_stop < 0) || (pos <= segment_stop)); + + //By consuming the payload, we are assuming that the curr + //cluster isn't interesting. That is, we don't bother checking + //whether the payload of the curr cluster is less than what + //happens to be available (obtained via IMkvReader::Length). + //Presumably the caller has already dispensed with the current + //cluster, and really does want the next cluster. + } + + //pos now points to just beyond the last fully-loaded cluster + + for (;;) + { + const long status = DoParseNext(pResult, pos, len); + + if (status <= 1) + return status; + } +} + + +long Segment::DoParseNext( + const Cluster*& pResult, + long long& pos, + long& len) +{ + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + //Parse next cluster. This is strictly a parsing activity. + //Creation of a new cluster object happens later, after the + //parsing is done. + + long long off_next = 0; + long long cluster_size = -1; + + for (;;) + { + if ((total >= 0) && (pos >= total)) + return 1; //EOF + + if ((segment_stop >= 0) && (pos >= segment_stop)) + return 1; //EOF + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; //absolute + const long long idoff = pos - m_start; //relative + + const long long id = ReadUInt(m_pReader, idpos, len); //absolute + + if (id < 0) //error + return static_cast(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(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(size); + + pos += len; //consume length of size of element + + //Pos now points to start of payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if ((segment_stop >= 0) && + (size != unknown_size) && + ((pos + size) > segment_stop)) + { + return E_FILE_FORMAT_INVALID; + } + + if (id == 0x0C53BB6B) //Cues ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + const long long element_start = idpos; + const long long element_size = element_stop - element_start; + + if (m_pCues == NULL) + { + m_pCues = new Cues(this, + pos, + size, + element_start, + element_size); + assert(m_pCues); //TODO + } + + pos += size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + + if (id != 0x0F43B675) //not a Cluster ID + { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + continue; + } + +#if 0 //this is commented-out to support incremental cluster parsing + len = static_cast(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + //We have a cluster. + + off_next = idoff; + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(off_next > 0); //have cluster + + //We have parsed the next cluster. + //We have not created a cluster object yet. What we need + //to do now is determine whether it has already be preloaded + //(in which case, an object for this cluster has already been + //created), and if not, create a new cluster object. + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) + { + //INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + const Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + pos = pNext->GetPosition(); + assert(pos >= 0); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + { + pResult = pNext; + return 0; //success + } + } + + assert(i == j); + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, off_next, pos_, len_); + + if (status < 0) //error or underflow + { + pos = pos_; + len = len_; + + return status; + } + + if (status > 0) //means "found at least one block entry" + { + Cluster* const pNext = Cluster::Create(this, + -1, //preloaded + off_next); + //element_size); + assert(pNext); + + const ptrdiff_t idx_next = i - m_clusters; //insertion position + + PreloadCluster(pNext, idx_next); + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + pResult = pNext; + return 0; //success + } + + //status == 0 means "no block entries found" + + if (cluster_size < 0) //unknown size + { + const long long payload_pos = pos; //absolute pos of cluster payload + + for (;;) //determine cluster size + { + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; //no more clusters + + //Read ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadUInt(m_pReader, idpos, len); + + if (id < 0) //error (or underflow) + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + pos += len; //consume ID (of sub-element) + + //Read Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field of element + + //pos now points to start of sub-element's payload + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) //weird + return E_FILE_FORMAT_INVALID; + + pos += size; //consume payload of sub-element + assert((segment_stop < 0) || (pos <= segment_stop)); + } //determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); //TODO: handle cluster_size = 0 + + pos = payload_pos; //reset and re-parse original cluster + } + + pos += cluster_size; //consume payload + assert((segment_stop < 0) || (pos <= segment_stop)); + + return 2; //try to find a cluster that follows next +} + + +const Cluster* Segment::FindCluster(long long time_ns) const +{ + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + { + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + assert(pCluster->m_index == 0); + + if (time_ns <= pCluster->GetTime()) + return pCluster; + } + + //Binary search of cluster array + + long i = 0; + long j = m_clusterCount; + + while (i < j) + { + //INVARIANT: + //[0, i) <= time_ns + //[i, j) ? + //[j, m_clusterCount) > time_ns + + const long k = i + (j - i) / 2; + assert(k < m_clusterCount); + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i > 0); + assert(i <= m_clusterCount); + + const long k = i - 1; + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + assert(pCluster->GetTime() <= time_ns); + + return pCluster; +} + + +#if 0 +const BlockEntry* Segment::Seek( + long long time_ns, + const Track* pTrack) const +{ + assert(pTrack); + + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return pTrack->GetEOS(); + + Cluster** const i = m_clusters; + assert(i); + + { + Cluster* const pCluster = *i; + assert(pCluster); + assert(pCluster->m_index == 0); //m_clusterCount > 0 + assert(pCluster->m_pSegment == this); + + if (time_ns <= pCluster->GetTime()) + return pCluster->GetEntry(pTrack); + } + + Cluster** const j = i + m_clusterCount; + + if (pTrack->GetType() == 2) //audio + { + //TODO: we could decide to use cues for this, as we do for video. + //But we only use it for video because looking around for a keyframe + //can get expensive. Audio doesn't require anything special so a + //straight cluster search is good enough (we assume). + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + assert(pCluster->m_index == long(mid - m_clusters)); + assert(pCluster->m_pSegment == this); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) + { + Cluster* const pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBE = pCluster->GetEntry(pTrack); + + if ((pBE != 0) && !pBE->EOS()) + return pBE; + + //landed on empty cluster (no entries) + } + + return pTrack->GetEOS(); //weird + } + + assert(pTrack->GetType() == 1); //video + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + Cluster* const pCluster = *mid; + assert(pCluster); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + Cluster* pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + { + const BlockEntry* const pBE = pCluster->GetEntry(pTrack, time_ns); + + if ((pBE != 0) && !pBE->EOS()) //found a keyframe + return pBE; + } + + const VideoTrack* const pVideo = static_cast(pTrack); + + while (lo != i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + const BlockEntry* const pBlockEntry = pCluster->GetMaxKey(pVideo); + + if ((pBlockEntry != 0) && !pBlockEntry->EOS()) + return pBlockEntry; + } + + //weird: we're on the first cluster, but no keyframe found + //should never happen but we must return something anyway + + return pTrack->GetEOS(); +} +#endif + + +#if 0 +bool Segment::SearchCues( + long long time_ns, + Track* pTrack, + Cluster*& pCluster, + const BlockEntry*& pBlockEntry, + const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) +{ + if (pTrack->GetType() != 1) //not video + return false; //TODO: for now, just handle video stream + + if (m_pCues == NULL) + return false; + + if (!m_pCues->Find(time_ns, pTrack, pCP, pTP)) + return false; //weird + + assert(pCP); + assert(pTP); + assert(pTP->m_track == pTrack->GetNumber()); + + //We have the cue point and track position we want, + //so we now need to search for the cluster having + //the indicated position. + + return GetCluster(pCP, pTP, pCluster, pBlockEntry); +} +#endif + + +const Tracks* Segment::GetTracks() const +{ + return m_pTracks; +} + + +const SegmentInfo* Segment::GetInfo() const +{ + return m_pInfo; +} + + +const Cues* Segment::GetCues() const +{ + return m_pCues; +} + + +const Chapters* Segment::GetChapters() const +{ + return m_pChapters; +} + + +const SeekHead* Segment::GetSeekHead() const +{ + return m_pSeekHead; +} + + +long long Segment::GetDuration() const +{ + assert(m_pInfo); + return m_pInfo->GetDuration(); +} + + +Chapters::Chapters( + Segment* pSegment, + long long payload_start, + long long payload_size, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(payload_start), + m_size(payload_size), + m_element_start(element_start), + m_element_size(element_size), + m_editions(NULL), + m_editions_size(0), + m_editions_count(0) +{ +} + + +Chapters::~Chapters() +{ + while (m_editions_count > 0) + { + Edition& e = m_editions[--m_editions_count]; + e.Clear(); + } +} + + +long Chapters::Parse() +{ + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; // payload start + const long long stop = pos + m_size; // payload stop + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05B9) // EditionEntry ID + { + status = ParseEdition(pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +int Chapters::GetEditionCount() const +{ + return m_editions_count; +} + + +const Chapters::Edition* Chapters::GetEdition(int idx) const +{ + if (idx < 0) + return NULL; + + if (idx >= m_editions_count) + return NULL; + + return m_editions + idx; +} + + +bool Chapters::ExpandEditionsArray() +{ + if (m_editions_size > m_editions_count) + return true; // nothing else to do + + const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size; + + Edition* const editions = new (std::nothrow) Edition[size]; + + if (editions == NULL) + return false; + + for (int idx = 0; idx < m_editions_count; ++idx) + { + m_editions[idx].ShallowCopy(editions[idx]); + } + + delete[] m_editions; + m_editions = editions; + + m_editions_size = size; + return true; +} + + +long Chapters::ParseEdition( + long long pos, + long long size) +{ + if (!ExpandEditionsArray()) + return -1; + + Edition& e = m_editions[m_editions_count++]; + e.Init(); + + return e.Parse(m_pSegment->m_pReader, pos, size); +} + + +Chapters::Edition::Edition() +{ +} + + +Chapters::Edition::~Edition() +{ +} + + +int Chapters::Edition::GetAtomCount() const +{ + return m_atoms_count; +} + + +const Chapters::Atom* Chapters::Edition::GetAtom(int index) const +{ + if (index < 0) + return NULL; + + if (index >= m_atoms_count) + return NULL; + + return m_atoms + index; +} + + +void Chapters::Edition::Init() +{ + m_atoms = NULL; + m_atoms_size = 0; + m_atoms_count = 0; +} + + +void Chapters::Edition::ShallowCopy(Edition& rhs) const +{ + rhs.m_atoms = m_atoms; + rhs.m_atoms_size = m_atoms_size; + rhs.m_atoms_count = m_atoms_count; +} + + +void Chapters::Edition::Clear() +{ + while (m_atoms_count > 0) + { + Atom& a = m_atoms[--m_atoms_count]; + a.Clear(); + } + + delete[] m_atoms; + m_atoms = NULL; + + m_atoms_size = 0; +} + + +long Chapters::Edition::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x36) // Atom ID + { + status = ParseAtom(pReader, pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +long Chapters::Edition::ParseAtom( + IMkvReader* pReader, + long long pos, + long long size) +{ + if (!ExpandAtomsArray()) + return -1; + + Atom& a = m_atoms[m_atoms_count++]; + a.Init(); + + return a.Parse(pReader, pos, size); +} + + +bool Chapters::Edition::ExpandAtomsArray() +{ + if (m_atoms_size > m_atoms_count) + return true; // nothing else to do + + const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size; + + Atom* const atoms = new (std::nothrow) Atom[size]; + + if (atoms == NULL) + return false; + + for (int idx = 0; idx < m_atoms_count; ++idx) + { + m_atoms[idx].ShallowCopy(atoms[idx]); + } + + delete[] m_atoms; + m_atoms = atoms; + + m_atoms_size = size; + return true; +} + + +Chapters::Atom::Atom() +{ +} + + +Chapters::Atom::~Atom() +{ +} + + +unsigned long long Chapters::Atom::GetUID() const +{ + return m_uid; +} + + +const char* Chapters::Atom::GetStringUID() const +{ + return m_string_uid; +} + + +long long Chapters::Atom::GetStartTimecode() const +{ + return m_start_timecode; +} + + +long long Chapters::Atom::GetStopTimecode() const +{ + return m_stop_timecode; +} + + +long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const +{ + return GetTime(pChapters, m_start_timecode); +} + + +long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const +{ + return GetTime(pChapters, m_stop_timecode); +} + + +int Chapters::Atom::GetDisplayCount() const +{ + return m_displays_count; +} + + +const Chapters::Display* Chapters::Atom::GetDisplay(int index) const +{ + if (index < 0) + return NULL; + + if (index >= m_displays_count) + return NULL; + + return m_displays + index; +} + + +void Chapters::Atom::Init() +{ + m_string_uid = NULL; + m_uid = 0; + m_start_timecode = -1; + m_stop_timecode = -1; + + m_displays = NULL; + m_displays_size = 0; + m_displays_count = 0; +} + + +void Chapters::Atom::ShallowCopy(Atom& rhs) const +{ + rhs.m_string_uid = m_string_uid; + rhs.m_uid = m_uid; + rhs.m_start_timecode = m_start_timecode; + rhs.m_stop_timecode = m_stop_timecode; + + rhs.m_displays = m_displays; + rhs.m_displays_size = m_displays_size; + rhs.m_displays_count = m_displays_count; +} + + +void Chapters::Atom::Clear() +{ + delete[] m_string_uid; + m_string_uid = NULL; + + while (m_displays_count > 0) + { + Display& d = m_displays[--m_displays_count]; + d.Clear(); + } + + delete[] m_displays; + m_displays = NULL; + + m_displays_size = 0; +} + + +long Chapters::Atom::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x00) // Display ID + { + status = ParseDisplay(pReader, pos, size); + + if (status < 0) // error + return status; + } + else if (id == 0x1654) // StringUID ID + { + status = UnserializeString(pReader, pos, size, m_string_uid); + + if (status < 0) // error + return status; + } + else if (id == 0x33C4) // UID ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_uid = val; + } + else if (id == 0x11) // TimeStart ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_start_timecode = val; + } + else if (id == 0x12) // TimeEnd ID + { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast(val); + + m_stop_timecode = val; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +long long Chapters::Atom::GetTime( + const Chapters* pChapters, + long long timecode) +{ + if (pChapters == NULL) + return -1; + + Segment* const pSegment = pChapters->m_pSegment; + + if (pSegment == NULL) // weird + return -1; + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + + if (pInfo == NULL) + return -1; + + const long long timecode_scale = pInfo->GetTimeCodeScale(); + + if (timecode_scale < 1) // weird + return -1; + + if (timecode < 0) + return -1; + + const long long result = timecode_scale * timecode; + + return result; +} + + +long Chapters::Atom::ParseDisplay( + IMkvReader* pReader, + long long pos, + long long size) +{ + if (!ExpandDisplaysArray()) + return -1; + + Display& d = m_displays[m_displays_count++]; + d.Init(); + + return d.Parse(pReader, pos, size); +} + + +bool Chapters::Atom::ExpandDisplaysArray() +{ + if (m_displays_size > m_displays_count) + return true; // nothing else to do + + const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size; + + Display* const displays = new (std::nothrow) Display[size]; + + if (displays == NULL) + return false; + + for (int idx = 0; idx < m_displays_count; ++idx) + { + m_displays[idx].ShallowCopy(displays[idx]); + } + + delete[] m_displays; + m_displays = displays; + + m_displays_size = size; + return true; +} + + +Chapters::Display::Display() +{ +} + + +Chapters::Display::~Display() +{ +} + + +const char* Chapters::Display::GetString() const +{ + return m_string; +} + + +const char* Chapters::Display::GetLanguage() const +{ + return m_language; +} + + +const char* Chapters::Display::GetCountry() const +{ + return m_country; +} + + +void Chapters::Display::Init() +{ + m_string = NULL; + m_language = NULL; + m_country = NULL; +} + + +void Chapters::Display::ShallowCopy(Display& rhs) const +{ + rhs.m_string = m_string; + rhs.m_language = m_language; + rhs.m_country = m_country; +} + + +void Chapters::Display::Clear() +{ + delete[] m_string; + m_string = NULL; + + delete[] m_language; + m_language = NULL; + + delete[] m_country; + m_country = NULL; +} + + +long Chapters::Display::Parse( + IMkvReader* pReader, + long long pos, + long long size) +{ + const long long stop = pos + size; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == 0x05) // ChapterString ID + { + status = UnserializeString(pReader, pos, size, m_string); + + if (status) + return status; + } + else if (id == 0x037C) // ChapterLanguage ID + { + status = UnserializeString(pReader, pos, size, m_language); + + if (status) + return status; + } + else if (id == 0x037E) // ChapterCountry ID + { + status = UnserializeString(pReader, pos, size, m_country); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + + +SegmentInfo::SegmentInfo( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_pMuxingAppAsUTF8(NULL), + m_pWritingAppAsUTF8(NULL), + m_pTitleAsUTF8(NULL) +{ +} + +SegmentInfo::~SegmentInfo() +{ + delete[] m_pMuxingAppAsUTF8; + m_pMuxingAppAsUTF8 = NULL; + + delete[] m_pWritingAppAsUTF8; + m_pWritingAppAsUTF8 = NULL; + + delete[] m_pTitleAsUTF8; + m_pTitleAsUTF8 = NULL; +} + + +long SegmentInfo::Parse() +{ + assert(m_pMuxingAppAsUTF8 == NULL); + assert(m_pWritingAppAsUTF8 == NULL); + assert(m_pTitleAsUTF8 == NULL); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + m_timecodeScale = 1000000; + m_duration = -1; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x0AD7B1) //Timecode Scale + { + m_timecodeScale = UnserializeUInt(pReader, pos, size); + + if (m_timecodeScale <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0489) //Segment duration + { + const long status = UnserializeFloat( + pReader, + pos, + size, + m_duration); + + if (status < 0) + return status; + + if (m_duration < 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0D80) //MuxingApp + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pMuxingAppAsUTF8); + + if (status) + return status; + } + else if (id == 0x1741) //WritingApp + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pWritingAppAsUTF8); + + if (status) + return status; + } + else if (id == 0x3BA9) //Title + { + const long status = UnserializeString( + pReader, + pos, + size, + m_pTitleAsUTF8); + + if (status) + return status; + } + + pos += size; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + + +long long SegmentInfo::GetTimeCodeScale() const +{ + return m_timecodeScale; +} + + +long long SegmentInfo::GetDuration() const +{ + if (m_duration < 0) + return -1; + + assert(m_timecodeScale >= 1); + + const double dd = double(m_duration) * double(m_timecodeScale); + const long long d = static_cast(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(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(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(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(count); +} + +long ContentEncoding::ParseContentEncAESSettingsEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes) { + assert(pReader); + assert(aes); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x7E8) { + // AESSettingsCipherMode + aes->cipher_mode = UnserializeUInt(pReader, pos, size); + if (aes->cipher_mode != 1) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + return 0; +} + +long ContentEncoding::ParseContentEncodingEntry(long long start, + long long size, + IMkvReader* pReader) { + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentCompression and ContentEncryption elements. + int compression_count = 0; + int encryption_count = 0; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x1034) // ContentCompression ID + ++compression_count; + + if (id == 0x1035) // ContentEncryption ID + ++encryption_count; + + pos += size; //consume payload + assert(pos <= stop); + } + + if (compression_count <= 0 && encryption_count <= 0) + return -1; + + if (compression_count > 0) { + compression_entries_ = + new (std::nothrow) ContentCompression*[compression_count]; + if (!compression_entries_) + return -1; + compression_entries_end_ = compression_entries_; + } + + if (encryption_count > 0) { + encryption_entries_ = + new (std::nothrow) ContentEncryption*[encryption_count]; + if (!encryption_entries_) { + delete [] compression_entries_; + return -1; + } + encryption_entries_end_ = encryption_entries_; + } + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x1031) { + // ContentEncodingOrder + encoding_order_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1032) { + // ContentEncodingScope + encoding_scope_ = UnserializeUInt(pReader, pos, size); + if (encoding_scope_ < 1) + return -1; + } else if (id == 0x1033) { + // ContentEncodingType + encoding_type_ = UnserializeUInt(pReader, pos, size); + } else if (id == 0x1034) { + // ContentCompression ID + ContentCompression* const compression = + new (std::nothrow) ContentCompression(); + if (!compression) + return -1; + + status = ParseCompressionEntry(pos, size, pReader, compression); + if (status) { + delete compression; + return status; + } + *compression_entries_end_++ = compression; + } else if (id == 0x1035) { + // ContentEncryption ID + ContentEncryption* const encryption = + new (std::nothrow) ContentEncryption(); + if (!encryption) + return -1; + + status = ParseEncryptionEntry(pos, size, pReader, encryption); + if (status) { + delete encryption; + return status; + } + *encryption_entries_end_++ = encryption; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + return 0; +} + +long ContentEncoding::ParseCompressionEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentCompression* compression) { + assert(pReader); + assert(compression); + + long long pos = start; + const long long stop = start + size; + + bool valid = false; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x254) { + // ContentCompAlgo + long long algo = UnserializeUInt(pReader, pos, size); + if (algo < 0) + return E_FILE_FORMAT_INVALID; + compression->algo = algo; + valid = true; + } else if (id == 0x255) { + // ContentCompSettings + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + compression->settings = buf; + compression->settings_len = buflen; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + // ContentCompAlgo is mandatory + if (!valid) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long ContentEncoding::ParseEncryptionEntry( + long long start, + long long size, + IMkvReader* pReader, + ContentEncryption* encryption) { + assert(pReader); + assert(encryption); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + if (id == 0x7E1) { + // ContentEncAlgo + encryption->algo = UnserializeUInt(pReader, pos, size); + if (encryption->algo != 5) + return E_FILE_FORMAT_INVALID; + } else if (id == 0x7E2) { + // ContentEncKeyID + delete[] encryption->key_id; + encryption->key_id = NULL; + encryption->key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->key_id = buf; + encryption->key_id_len = buflen; + } else if (id == 0x7E3) { + // ContentSignature + delete[] encryption->signature; + encryption->signature = NULL; + encryption->signature_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->signature = buf; + encryption->signature_len = buflen; + } else if (id == 0x7E4) { + // ContentSigKeyID + delete[] encryption->sig_key_id; + encryption->sig_key_id = NULL; + encryption->sig_key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast(size); + typedef unsigned char* buf_t; + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + if (buf == NULL) + return -1; + + const int read_status = pReader->Read(pos, buflen, buf); + if (read_status) { + delete [] buf; + return status; + } + + encryption->sig_key_id = buf; + encryption->sig_key_id_len = buflen; + } else if (id == 0x7E5) { + // ContentSigAlgo + encryption->sig_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E6) { + // ContentSigHashAlgo + encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size); + } else if (id == 0x7E7) { + // ContentEncAESSettings + const long status = ParseContentEncAESSettingsEntry( + pos, + size, + pReader, + &encryption->aes_settings); + if (status) + return status; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + return 0; +} + +Track::Track( + Segment* pSegment, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_element_start(element_start), + m_element_size(element_size), + content_encoding_entries_(NULL), + content_encoding_entries_end_(NULL) +{ +} + +Track::~Track() +{ + Info& info = const_cast(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(): + nameAsUTF8(NULL), + language(NULL), + codecId(NULL), + codecNameAsUTF8(NULL), + codecPrivate(NULL), + codecPrivateSize(0), + codecDelay(0), + seekPreRoll(0) +{ +} + +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(NULL)) + return -1; + + char*& dst = dst_.*str; + + if (dst) //should be NULL already + return -1; + + const char* const src = this->*str; + + if (src == NULL) + return 0; + + const size_t len = strlen(src); + + dst = new (std::nothrow) char[len+1]; + + if (dst == NULL) + return -1; + + strcpy(dst, src); + + return 0; +} + + +int Track::Info::Copy(Info& dst) const +{ + if (&dst == this) + return 0; + + dst.type = type; + dst.number = number; + dst.defaultDuration = defaultDuration; + dst.codecDelay = codecDelay; + dst.seekPreRoll = seekPreRoll; + dst.uid = uid; + dst.lacing = lacing; + dst.settings = settings; + + //We now copy the string member variables from src to dst. + //This involves memory allocation so in principle the operation + //can fail (indeed, that's why we have Info::Copy), so we must + //report this to the caller. An error return from this function + //therefore implies that the copy was only partially successful. + + if (int status = CopyStr(&Info::nameAsUTF8, dst)) + return status; + + if (int status = CopyStr(&Info::language, dst)) + return status; + + if (int status = CopyStr(&Info::codecId, dst)) + return status; + + if (int status = CopyStr(&Info::codecNameAsUTF8, dst)) + return status; + + if (codecPrivateSize > 0) + { + if (codecPrivate == NULL) + return -1; + + if (dst.codecPrivate) + return -1; + + if (dst.codecPrivateSize != 0) + return -1; + + dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize]; + + if (dst.codecPrivate == NULL) + return -1; + + memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize); + dst.codecPrivateSize = codecPrivateSize; + } + + return 0; +} + +const BlockEntry* Track::GetEOS() const +{ + return &m_eos; +} + +long Track::GetType() const +{ + return m_info.type; +} + +long Track::GetNumber() const +{ + return m_info.number; +} + +unsigned long long Track::GetUid() const +{ + return m_info.uid; +} + +const char* Track::GetNameAsUTF8() const +{ + return m_info.nameAsUTF8; +} + +const char* Track::GetLanguage() const +{ + return m_info.language; +} + +const char* Track::GetCodecNameAsUTF8() const +{ + return m_info.codecNameAsUTF8; +} + + +const char* Track::GetCodecId() const +{ + return m_info.codecId; +} + +const unsigned char* Track::GetCodecPrivate(size_t& size) const +{ + size = m_info.codecPrivateSize; + return m_info.codecPrivate; +} + + +bool Track::GetLacing() const +{ + return m_info.lacing; +} + +unsigned long long Track::GetDefaultDuration() const +{ + return m_info.defaultDuration; +} + +unsigned long long Track::GetCodecDelay() const +{ + return m_info.codecDelay; +} + +unsigned long long Track::GetSeekPreRoll() const +{ + return m_info.seekPreRoll; +} + +long Track::GetFirst(const BlockEntry*& pBlockEntry) const +{ + const Cluster* pCluster = m_pSegment->GetFirst(); + + for (int i = 0; ; ) + { + if (pCluster == NULL) + { + pBlockEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) + { +#if 0 + if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded + { + pBlockEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) + { + pBlockEntry = GetEOS(); + return 1; + } +#endif + + pBlockEntry = 0; + return E_BUFFER_NOT_FULL; + } + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) //error + return status; + + if (pBlockEntry == 0) //empty cluster + { + pCluster = m_pSegment->GetNext(pCluster); + continue; + } + + for (;;) + { + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + + const long long tn = pBlock->GetTrackNumber(); + + if ((tn == m_info.number) && VetEntry(pBlockEntry)) + return 0; + + const BlockEntry* pNextEntry; + + status = pCluster->GetNext(pBlockEntry, pNextEntry); + + if (status < 0) //error + return status; + + if (pNextEntry == 0) + break; + + pBlockEntry = pNextEntry; + } + + ++i; + + if (i >= 100) + break; + + pCluster = m_pSegment->GetNext(pCluster); + } + + //NOTE: if we get here, it means that we didn't find a block with + //a matching track number. We interpret that as an error (which + //might be too conservative). + + pBlockEntry = GetEOS(); //so we can return a non-NULL value + return 1; +} + + +long Track::GetNext( + const BlockEntry* pCurrEntry, + const BlockEntry*& pNextEntry) const +{ + assert(pCurrEntry); + assert(!pCurrEntry->EOS()); //? + + const Block* const pCurrBlock = pCurrEntry->GetBlock(); + assert(pCurrBlock->GetTrackNumber() == m_info.number); + + const Cluster* pCluster = pCurrEntry->GetCluster(); + assert(pCluster); + assert(!pCluster->EOS()); + + long status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) //error + return status; + + for (int i = 0; ; ) + { + while (pNextEntry) + { + const Block* const pNextBlock = pNextEntry->GetBlock(); + assert(pNextBlock); + + if (pNextBlock->GetTrackNumber() == m_info.number) + return 0; + + pCurrEntry = pNextEntry; + + status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) //error + return status; + } + + pCluster = m_pSegment->GetNext(pCluster); + + if (pCluster == NULL) + { + pNextEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) + { +#if 0 + if (m_pSegment->Unparsed() <= 0) //all clusters have been loaded + { + pNextEntry = GetEOS(); + return 1; + } +#else + if (m_pSegment->DoneParsing()) + { + pNextEntry = GetEOS(); + return 1; + } +#endif + + //TODO: there is a potential O(n^2) problem here: we tell the + //caller to (pre)load another cluster, which he does, but then he + //calls GetNext again, which repeats the same search. This is + //a pathological case, since the only way it can happen is if + //there exists a long sequence of clusters none of which contain a + // block from this track. One way around this problem is for the + //caller to be smarter when he loads another cluster: don't call + //us back until you have a cluster that contains a block from this + //track. (Of course, that's not cheap either, since our caller + //would have to scan the each cluster as it's loaded, so that + //would just push back the problem.) + + pNextEntry = NULL; + return E_BUFFER_NOT_FULL; + } + + status = pCluster->GetFirst(pNextEntry); + + if (status < 0) //error + return status; + + if (pNextEntry == NULL) //empty cluster + continue; + + ++i; + + if (i >= 100) + break; + } + + //NOTE: if we get here, it means that we didn't find a block with + //a matching track number after lots of searching, so we give + //up trying. + + pNextEntry = GetEOS(); //so we can return a non-NULL value + return 1; +} + +bool Track::VetEntry(const BlockEntry* pBlockEntry) const +{ + assert(pBlockEntry); + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + assert(pBlock->GetTrackNumber() == m_info.number); + + // 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(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(count); +} + +long Track::ParseContentEncodingsEntry(long long start, long long size) { + IMkvReader* const pReader = m_pSegment->m_pReader; + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentEncoding elements. + int count = 0; + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + + //pos now designates start of element + if (id == 0x2240) // ContentEncoding ID + ++count; + + pos += size; //consume payload + assert(pos <= stop); + } + + if (count <= 0) + return -1; + + content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count]; + if (!content_encoding_entries_) + return -1; + + content_encoding_entries_end_ = content_encoding_entries_; + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, + pos, + stop, + id, + size); + if (status < 0) //error + return status; + + //pos now designates start of element + if (id == 0x2240) { // ContentEncoding ID + ContentEncoding* const content_encoding = + new (std::nothrow) ContentEncoding(); + if (!content_encoding) + return -1; + + status = content_encoding->ParseContentEncodingEntry(pos, + size, + pReader); + if (status) { + delete content_encoding; + return status; + } + + *content_encoding_entries_end_++ = content_encoding; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; +} + +Track::EOSBlock::EOSBlock() : + BlockEntry(NULL, LONG_MIN) +{ +} + +BlockEntry::Kind Track::EOSBlock::GetKind() const +{ + return kBlockEOS; +} + + +const Block* Track::EOSBlock::GetBlock() const +{ + return NULL; +} + + +VideoTrack::VideoTrack( + Segment* pSegment, + long long element_start, + long long element_size) : + Track(pSegment, element_start, element_size) +{ +} + + +long VideoTrack::Parse( + Segment* pSegment, + const Info& info, + long long element_start, + long long element_size, + VideoTrack*& pResult) +{ + if (pResult) + return -1; + + if (info.type != Track::kVideo) + return -1; + + long long width = 0; + long long height = 0; + double rate = 0.0; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x30) //pixel width + { + width = UnserializeUInt(pReader, pos, size); + + if (width <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x3A) //pixel height + { + height = UnserializeUInt(pReader, pos, size); + + if (height <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x0383E3) //frame rate + { + const long status = UnserializeFloat( + pReader, + pos, + size, + rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + VideoTrack* const pTrack = new (std::nothrow) VideoTrack(pSegment, + element_start, + element_size); + + if (pTrack == NULL) + return -1; //generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) // error + { + delete pTrack; + return status; + } + + pTrack->m_width = width; + pTrack->m_height = height; + pTrack->m_rate = rate; + + pResult = pTrack; + return 0; //success +} + + +bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const +{ + return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey(); +} + +long VideoTrack::Seek( + long long time_ns, + const BlockEntry*& pResult) const +{ + const long status = GetFirst(pResult); + + if (status < 0) //buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); //loaded only, not pre-loaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) + { + //INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this, time_ns); + + if ((pResult != 0) && !pResult->EOS()) //found a keyframe + return 0; + + while (lo != i) + { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + +#if 0 + //TODO: + //We need to handle the case when a cluster + //contains multiple keyframes. Simply returning + //the largest keyframe on the cluster isn't + //good enough. + pResult = pCluster->GetMaxKey(this); +#else + pResult = pCluster->GetEntry(this, time_ns); +#endif + + if ((pResult != 0) && !pResult->EOS()) + return 0; + } + + //weird: we're on the first cluster, but no keyframe found + //should never happen but we must return something anyway + + pResult = GetEOS(); + return 0; +} + + +long long VideoTrack::GetWidth() const +{ + return m_width; +} + + +long long VideoTrack::GetHeight() const +{ + return m_height; +} + + +double VideoTrack::GetFrameRate() const +{ + return m_rate; +} + + +AudioTrack::AudioTrack( + Segment* pSegment, + long long element_start, + long long element_size) : + Track(pSegment, element_start, element_size) +{ +} + + +long AudioTrack::Parse( + Segment* pSegment, + const Info& info, + long long element_start, + long long element_size, + AudioTrack*& pResult) +{ + if (pResult) + return -1; + + if (info.type != Track::kAudio) + return -1; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + double rate = 8000.0; // MKV default + long long channels = 1; + long long bit_depth = 0; + + while (pos < stop) + { + long long id, size; + + long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (id == 0x35) //Sample Rate + { + status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x1F) //Channel Count + { + channels = UnserializeUInt(pReader, pos, size); + + if (channels <= 0) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x2264) //Bit Depth + { + bit_depth = UnserializeUInt(pReader, pos, size); + + if (bit_depth <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + AudioTrack* const pTrack = new (std::nothrow) AudioTrack(pSegment, + element_start, + element_size); + + if (pTrack == NULL) + return -1; //generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) + { + delete pTrack; + return status; + } + + pTrack->m_rate = rate; + pTrack->m_channels = channels; + pTrack->m_bitDepth = bit_depth; + + pResult = pTrack; + return 0; //success +} + + +double AudioTrack::GetSamplingRate() const +{ + return m_rate; +} + + +long long AudioTrack::GetChannels() const +{ + return m_channels; +} + +long long AudioTrack::GetBitDepth() const +{ + return m_bitDepth; +} + +Tracks::Tracks( + Segment* pSegment, + long long start, + long long size_, + long long element_start, + long long element_size) : + m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_trackEntries(NULL), + m_trackEntriesEnd(NULL) +{ +} + + +long Tracks::Parse() +{ + assert(m_trackEntries == NULL); + assert(m_trackEntriesEnd == NULL); + + const long long stop = m_start + m_size; + IMkvReader* const pReader = m_pSegment->m_pReader; + + int count = 0; + long long pos = m_start; + + while (pos < stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + size); + + if (status < 0) //error + return status; + + if (size == 0) //weird + continue; + + if (id == 0x2E) //TrackEntry ID + ++count; + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + + if (count <= 0) + return 0; //success + + m_trackEntries = new (std::nothrow) Track*[count]; + + if (m_trackEntries == NULL) + return -1; + + m_trackEntriesEnd = m_trackEntries; + + pos = m_start; + + while (pos < stop) + { + const long long element_start = pos; + + long long id, payload_size; + + const long status = ParseElementHeader( + pReader, + pos, + stop, + id, + payload_size); + + if (status < 0) //error + return status; + + if (payload_size == 0) //weird + continue; + + const long long payload_stop = pos + payload_size; + assert(payload_stop <= stop); //checked in ParseElement + + const long long element_size = payload_stop - element_start; + + if (id == 0x2E) //TrackEntry ID + { + Track*& pTrack = *m_trackEntriesEnd; + pTrack = NULL; + + const long status = ParseTrackEntry( + pos, + payload_size, + element_start, + element_size, + pTrack); + + if (status) + return status; + + if (pTrack) + ++m_trackEntriesEnd; + } + + pos = payload_stop; + assert(pos <= stop); + } + + assert(pos == stop); + + return 0; //success +} + + +unsigned long Tracks::GetTracksCount() const +{ + const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; + assert(result >= 0); + + return static_cast(result); +} + +long Tracks::ParseTrackEntry( + long long track_start, + long long track_size, + long long element_start, + long long element_size, + Track*& pResult) const +{ + if (pResult) + return -1; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = track_start; + const long long track_stop = track_start + track_size; + + Track::Info info; + + info.type = 0; + info.number = 0; + info.uid = 0; + info.defaultDuration = 0; + + Track::Settings v; + v.start = -1; + v.size = -1; + + Track::Settings a; + a.start = -1; + a.size = -1; + + Track::Settings e; //content_encodings_settings; + e.start = -1; + e.size = -1; + + long long lacing = 1; //default is true + + while (pos < track_stop) + { + long long id, size; + + const long status = ParseElementHeader( + pReader, + pos, + track_stop, + id, + size); + + if (status < 0) //error + return status; + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + const long long start = pos; + + if (id == 0x60) // VideoSettings ID + { + v.start = start; + v.size = size; + } + else if (id == 0x61) // AudioSettings ID + { + a.start = start; + a.size = size; + } + else if (id == 0x2D80) // ContentEncodings ID + { + e.start = start; + e.size = size; + } + else if (id == 0x33C5) //Track UID + { + if (size > 8) + return E_FILE_FORMAT_INVALID; + + info.uid = 0; + + long long pos_ = start; + const long long pos_end = start + size; + + while (pos_ != pos_end) + { + unsigned char b; + + const int status = pReader->Read(pos_, 1, &b); + + if (status) + return status; + + info.uid <<= 8; + info.uid |= b; + + ++pos_; + } + } + else if (id == 0x57) //Track Number + { + const long long num = UnserializeUInt(pReader, pos, size); + + if ((num <= 0) || (num > 127)) + return E_FILE_FORMAT_INVALID; + + info.number = static_cast(num); + } + else if (id == 0x03) //Track Type + { + const long long type = UnserializeUInt(pReader, pos, size); + + if ((type <= 0) || (type > 254)) + return E_FILE_FORMAT_INVALID; + + info.type = static_cast(type); + } + else if (id == 0x136E) //Track Name + { + const long status = UnserializeString( + pReader, + pos, + size, + info.nameAsUTF8); + + if (status) + return status; + } + else if (id == 0x02B59C) //Track Language + { + const long status = UnserializeString( + pReader, + pos, + size, + info.language); + + if (status) + return status; + } + else if (id == 0x03E383) //Default Duration + { + const long long duration = UnserializeUInt(pReader, pos, size); + + if (duration < 0) + return E_FILE_FORMAT_INVALID; + + info.defaultDuration = static_cast(duration); + } + else if (id == 0x06) //CodecID + { + const long status = UnserializeString( + pReader, + pos, + size, + info.codecId); + + if (status) + return status; + } + else if (id == 0x1C) //lacing + { + lacing = UnserializeUInt(pReader, pos, size); + + if ((lacing < 0) || (lacing > 1)) + return E_FILE_FORMAT_INVALID; + } + else if (id == 0x23A2) //Codec Private + { + delete[] info.codecPrivate; + info.codecPrivate = NULL; + info.codecPrivateSize = 0; + + const size_t buflen = static_cast(size); + + if (buflen) + { + typedef unsigned char* buf_t; + + const buf_t buf = new (std::nothrow) unsigned char[buflen]; + + if (buf == NULL) + return -1; + + const int status = pReader->Read(pos, buflen, buf); + + if (status) + { + delete[] buf; + return status; + } + + info.codecPrivate = buf; + info.codecPrivateSize = buflen; + } + } + else if (id == 0x058688) //Codec Name + { + const long status = UnserializeString( + pReader, + pos, + size, + info.codecNameAsUTF8); + + if (status) + return status; + } + else if (id == 0x16AA) //Codec Delay + { + info.codecDelay = UnserializeUInt(pReader, pos, size); + + } + else if (id == 0x16BB) //Seek Pre Roll + { + info.seekPreRoll = UnserializeUInt(pReader, pos, size); + } + + pos += size; //consume payload + assert(pos <= track_stop); + } + + assert(pos == track_stop); + + if (info.number <= 0) //not specified + return E_FILE_FORMAT_INVALID; + + if (GetTrackByNumber(info.number)) + return E_FILE_FORMAT_INVALID; + + if (info.type <= 0) //not specified + return E_FILE_FORMAT_INVALID; + + info.lacing = (lacing > 0) ? true : false; + + if (info.type == Track::kVideo) + { + if (v.start < 0) + return E_FILE_FORMAT_INVALID; + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = v; + + VideoTrack* pTrack = NULL; + + const long status = VideoTrack::Parse(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } + else if (info.type == Track::kAudio) + { + if (a.start < 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = a; + + AudioTrack* pTrack = NULL; + + const long status = AudioTrack::Parse(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } + else + { + // neither video nor audio - probably metadata or subtitles + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (e.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings.start = -1; + info.settings.size = 0; + + Track* pTrack = NULL; + + const long status = Track::Create(m_pSegment, + info, + element_start, + element_size, + pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + } + + return 0; //success +} + + +Tracks::~Tracks() +{ + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) + { + Track* const pTrack = *i++; + delete pTrack; + } + + delete[] m_trackEntries; +} + +const Track* Tracks::GetTrackByNumber(long tn) const +{ + if (tn < 0) + return NULL; + + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) + { + Track* const pTrack = *i++; + + if (pTrack == NULL) + continue; + + if (tn == pTrack->GetNumber()) + return pTrack; + } + + return NULL; //not found +} + + +const Track* Tracks::GetTrackByIndex(unsigned long idx) const +{ + const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries; + + if (idx >= static_cast(count)) + return NULL; + + return m_trackEntries[idx]; +} + +#if 0 +long long Cluster::Unparsed() const +{ + if (m_timecode < 0) //not even partially loaded + return LLONG_MAX; + + assert(m_pos >= m_element_start); + //assert(m_element_size > m_size); + + const long long element_stop = m_element_start + m_element_size; + assert(m_pos <= element_stop); + + const long long result = element_stop - m_pos; + assert(result >= 0); + + return result; +} +#endif + + +long Cluster::Load(long long& pos, long& len) const +{ + assert(m_pSegment); + assert(m_pos >= m_element_start); + + if (m_timecode >= 0) //at least partially loaded + return 0; + + assert(m_pos == m_element_start); + assert(m_element_size < 0); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + const int status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + assert((total < 0) || (m_pos <= total)); //TODO: verify this + + pos = m_pos; + + long long cluster_size = -1; + + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error or underflow + return static_cast(result); + + if (result > 0) //underflow (weird) + return E_BUFFER_NOT_FULL; + + //if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id_ = ReadUInt(pReader, pos, len); + + if (id_ < 0) //error + return static_cast(id_); + + if (id_ != 0x0F43B675) //Cluster ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume id + + //read cluster size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + //if ((pos + len) > segment_stop) + // return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(cluster_size); + + if (size == 0) + return E_FILE_FORMAT_INVALID; //TODO: verify this + + pos += len; //consume length of size of element + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) + cluster_size = size; + } + + //pos points to start of payload + +#if 0 + len = static_cast(size_); + + if (cluster_stop > avail) + return E_BUFFER_NOT_FULL; +#endif + + long long timecode = -1; + long long new_pos = -1; + bool bBlock = false; + + long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size; + + for (;;) + { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + //Parse ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) + return E_FILE_FORMAT_INVALID; + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + break; + + if (id == 0x0C53BB6B) //Cues ID + break; + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //pos now points to start of payload + + if (size == 0) //weird + continue; + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x67) //TimeCode ID + { + len = static_cast(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; + + timecode = UnserializeUInt(pReader, pos, size); + + if (timecode < 0) //error (or underflow) + return static_cast(timecode); + + new_pos = pos + size; + + if (bBlock) + break; + } + else if (id == 0x20) //BlockGroup ID + { + bBlock = true; + break; + } + else if (id == 0x23) //SimpleBlock ID + { + bBlock = true; + break; + } + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert((cluster_stop < 0) || (pos <= cluster_stop)); + + if (timecode < 0) //no timecode found + return E_FILE_FORMAT_INVALID; + + if (!bBlock) + return E_FILE_FORMAT_INVALID; + + m_pos = new_pos; //designates position just beyond timecode payload + m_timecode = timecode; // m_timecode >= 0 means we're partially loaded + + if (cluster_size >= 0) + m_element_size = cluster_stop - m_element_start; + + return 0; +} + + +long Cluster::Parse(long long& pos, long& len) const +{ + long status = Load(pos, len); + + if (status < 0) + return status; + + assert(m_pos >= m_element_start); + assert(m_timecode >= 0); + //assert(m_size > 0); + //assert(m_element_size > m_size); + + const long long cluster_stop = + (m_element_size < 0) ? -1 : m_element_start + m_element_size; + + if ((cluster_stop >= 0) && (m_pos >= cluster_stop)) + return 1; //nothing else to do + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_pos; + + for (;;) + { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + if ((total >= 0) && (pos >= total)) + { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + //Parse ID + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) //weird + return E_FILE_FORMAT_INVALID; + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) //Cluster or Cues ID + { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) //error + return static_cast(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //pos now points to start of payload + + if (size == 0) //weird + continue; + + //const long long block_start = pos; + const long long block_stop = pos + size; + + if (cluster_stop >= 0) + { + if (block_stop > cluster_stop) + { + if ((id == 0x20) || (id == 0x23)) + return E_FILE_FORMAT_INVALID; + + pos = cluster_stop; + break; + } + } + else if ((total >= 0) && (block_stop > total)) + { + m_element_size = total - m_element_start; + pos = total; + break; + } + else if (block_stop > avail) + { + len = static_cast(size); + return E_BUFFER_NOT_FULL; + } + + Cluster* const this_ = const_cast(this); + + if (id == 0x20) //BlockGroup + return this_->ParseBlockGroup(size, pos, len); + + if (id == 0x23) //SimpleBlock + return this_->ParseSimpleBlock(size, pos, len); + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } + + assert(m_element_size > 0); + + m_pos = pos; + assert((cluster_stop < 0) || (m_pos <= cluster_stop)); + + if (m_entries_count > 0) + { + const long idx = m_entries_count - 1; + + const BlockEntry* const pLast = m_entries[idx]; + assert(pLast); + + const Block* const pBlock = pLast->GetBlock(); + assert(pBlock); + + const long long start = pBlock->m_start; + + if ((total >= 0) && (start > total)) + return -1; //defend against trucated stream + + const long long size = pBlock->m_size; + + const long long stop = start + size; + assert((cluster_stop < 0) || (stop <= cluster_stop)); + + if ((total >= 0) && (stop > total)) + return -1; //defend against trucated stream + } + + return 1; //no more entries +} + + +long Cluster::ParseSimpleBlock( + long long block_size, + long long& pos, + long& len) +{ + const long long block_start = pos; + const long long block_stop = pos + block_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + //parse track number + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(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(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; //consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) + { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; //consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) //error or underflow + { + len = 1; + return status; + } + + ++pos; //consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) + { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + status = CreateBlock(0x23, //simple block id + block_start, block_size, + 0); //DiscardPadding + + if (status != 0) + return status; + + m_pos = block_stop; + + return 0; //success +} + + +long Cluster::ParseBlockGroup( + long long payload_size, + long long& pos, + long& len) +{ + const long long payload_start = pos; + const long long payload_stop = pos + payload_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + if ((total >= 0) && (payload_stop > total)) + return E_FILE_FORMAT_INVALID; + + if (payload_stop > avail) + { + len = static_cast(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(result); + + if (result > 0) //weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id == 0) //not a value ID + return E_FILE_FORMAT_INVALID; + + pos += len; //consume ID field + + //Parse Size + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field + + //pos now points to start of sub-block group payload + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if (id == 0x35A2) //DiscardPadding + { + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + status = UnserializeInt(pReader, pos, len, discard_padding); + + if (status < 0) //error + return status; + } + + if (id != 0x21) //sub-part of BlockGroup is not a Block + { + pos += size; //consume sub-part of block group + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + const long long block_stop = pos + size; + + if (block_stop > payload_stop) + return E_FILE_FORMAT_INVALID; + + //parse track number + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(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(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + +#if 0 + //TODO(matthewjheaney) + //This turned out to be too conservative. The problem is that + //if we see a track header in the tracks element with an unsupported + //track type, we throw that track header away, so it is not present + //in the track map. But even though we don't understand the track + //header, there are still blocks in the cluster with that track + //number. It was our decision to ignore that track header, so it's + //up to us to deal with blocks associated with that track -- we + //cannot simply report an error since technically there's nothing + //wrong with the file. + // + //For now we go ahead and finish the parse, creating a block entry + //for this block. This is somewhat wasteful, because without a + //track header there's nothing you can do with the block. What + //we really need here is a special return value that indicates to + //the caller that he should ignore this particular block, and + //continue parsing. + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(track); + + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return E_FILE_FORMAT_INVALID; +#endif + + pos += len; //consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) + { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; //consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) //error or underflow + { + len = 1; + return status; + } + + ++pos; //consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) + { + len = static_cast(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + pos = block_stop; //consume block-part of block group + assert(pos <= payload_stop); + } + + assert(pos == payload_stop); + + status = CreateBlock(0x20, //BlockGroup ID + payload_start, payload_size, + discard_padding); + if (status != 0) + return status; + + m_pos = payload_stop; + + return 0; //success +} + + +long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const +{ + assert(m_pos >= m_element_start); + + pEntry = NULL; + + if (index < 0) + return -1; //generic error + + if (m_entries_count < 0) + return E_BUFFER_NOT_FULL; + + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (index < m_entries_count) + { + pEntry = m_entries[index]; + assert(pEntry); + + return 1; //found entry + } + + if (m_element_size < 0) //we don't know cluster end yet + return E_BUFFER_NOT_FULL; //underflow + + const long long element_stop = m_element_start + m_element_size; + + if (m_pos >= element_stop) + return 0; //nothing left to parse + + return E_BUFFER_NOT_FULL; //underflow, since more remains to be parsed +} + + +Cluster* Cluster::Create( + Segment* pSegment, + long idx, + long long off) + //long long element_size) +{ + assert(pSegment); + assert(off >= 0); + + const long long element_start = pSegment->m_start + off; + + Cluster* const pCluster = new Cluster(pSegment, + idx, + element_start); + //element_size); + assert(pCluster); + + return pCluster; +} + + +Cluster::Cluster() : + m_pSegment(NULL), + m_element_start(0), + m_index(0), + m_pos(0), + m_element_size(0), + m_timecode(0), + m_entries(NULL), + m_entries_size(0), + m_entries_count(0) //means "no entries" +{ +} + + +Cluster::Cluster( + Segment* pSegment, + long idx, + long long element_start + /* long long element_size */ ) : + m_pSegment(pSegment), + m_element_start(element_start), + m_index(idx), + m_pos(element_start), + m_element_size(-1 /* element_size */ ), + m_timecode(-1), + m_entries(NULL), + m_entries_size(0), + m_entries_count(-1) //means "has not been parsed yet" +{ +} + + +Cluster::~Cluster() +{ + if (m_entries_count <= 0) + return; + + BlockEntry** i = m_entries; + BlockEntry** const j = m_entries + m_entries_count; + + while (i != j) + { + BlockEntry* p = *i++; + assert(p); + + delete p; + } + + delete[] m_entries; +} + + +bool Cluster::EOS() const +{ + return (m_pSegment == NULL); +} + + +long Cluster::GetIndex() const +{ + return m_index; +} + + +long long Cluster::GetPosition() const +{ + const long long pos = m_element_start - m_pSegment->m_start; + assert(pos >= 0); + + return pos; +} + + +long long Cluster::GetElementSize() const +{ + return m_element_size; +} + + +#if 0 +bool Cluster::HasBlockEntries( + const Segment* pSegment, + long long off) //relative to start of segment payload +{ + assert(pSegment); + assert(off >= 0); //relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long pos = pSegment->m_start + off; //absolute + long long size; + + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + (void)id; + assert(id >= 0); + assert(id == 0x0F43B675); //Cluster ID + + pos += len; //consume id + + size = ReadUInt(pReader, pos, len); + assert(size > 0); + + pos += len; //consume size + + //pos now points to start of payload + } + + const long long stop = pos + size; + + while (pos < stop) + { + long len; + + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume id + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume size + + if (id == 0x20) //BlockGroup ID + return true; + + if (id == 0x23) //SimpleBlock ID + return true; + + pos += size; //consume payload + assert(pos <= stop); + } + + return false; +} +#endif + + +long Cluster::HasBlockEntries( + const Segment* pSegment, + long long off, //relative to start of segment payload + long long& pos, + long& len) +{ + assert(pSegment); + assert(off >= 0); //relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) //error + return status; + + assert((total < 0) || (avail <= total)); + + pos = pSegment->m_start + off; //absolute + + if ((total >= 0) && (pos >= total)) + return 0; //we don't even have a complete cluster + + const long long segment_stop = + (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size; + + long long cluster_stop = -1; //interpreted later to mean "unknown size" + + { + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(result); + + if (result > 0) //need more data + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + if (id != 0x0F43B675) //weird: not cluster ID + return -1; //generic error + + pos += len; //consume Cluster ID field + + //read size field + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(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(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(result); + + if (result > 0) //need more data + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(pReader, pos, len); + + if (id < 0) //error + return static_cast(id); + + //This is the distinguished set of ID's we use to determine + //that we have exhausted the sub-element's inside the cluster + //whose ID we parsed earlier. + + if (id == 0x0F43B675) //Cluster ID + return 0; //no entries found + + if (id == 0x0C53BB6B) //Cues ID + return 0; //no entries found + + pos += len; //consume id field + + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return E_FILE_FORMAT_INVALID; + + //read size field + + if ((pos + 1) > avail) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) //error + return static_cast(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(size); + + pos += len; //consume size field + + //pos now points to start of payload + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (size == 0) //weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; //not supported inside cluster + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == 0x20) //BlockGroup ID + return 1; //have at least one entry + + if (id == 0x23) //SimpleBlock ID + return 1; //have at least one entry + + pos += size; //consume payload + assert((cluster_stop < 0) || (pos <= cluster_stop)); + } +} + + +long long Cluster::GetTimeCode() const +{ + long long pos; + long len; + + const long status = Load(pos, len); + + if (status < 0) //error + return status; + + return m_timecode; +} + + +long long Cluster::GetTime() const +{ + const long long tc = GetTimeCode(); + + if (tc < 0) + return tc; + + const SegmentInfo* const pInfo = m_pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long t = m_timecode * scale; + + return t; +} + + +long long Cluster::GetFirstTime() const +{ + const BlockEntry* pEntry; + + const long status = GetFirst(pEntry); + + if (status < 0) //error + return status; + + if (pEntry == NULL) //empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + + +long long Cluster::GetLastTime() const +{ + const BlockEntry* pEntry; + + const long status = GetLast(pEntry); + + if (status < 0) //error + return status; + + if (pEntry == NULL) //empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + + +long Cluster::CreateBlock( + long long id, + long long pos, //absolute pos of payload + long long size, + long long discard_padding) +{ + assert((id == 0x20) || (id == 0x23)); //BlockGroup or SimpleBlock + + if (m_entries_count < 0) //haven't parsed anything yet + { + assert(m_entries == NULL); + assert(m_entries_size == 0); + + m_entries_size = 1024; + m_entries = new BlockEntry*[m_entries_size]; + + m_entries_count = 0; + } + else + { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (m_entries_count >= m_entries_size) + { + const long entries_size = 2 * m_entries_size; + + BlockEntry** const entries = new BlockEntry*[entries_size]; + assert(entries); + + BlockEntry** src = m_entries; + BlockEntry** const src_end = src + m_entries_count; + + BlockEntry** dst = entries; + + while (src != src_end) + *dst++ = *src++; + + delete[] m_entries; + + m_entries = entries; + m_entries_size = entries_size; + } + } + + if (id == 0x20) //BlockGroup ID + return CreateBlockGroup(pos, size, discard_padding); + else //SimpleBlock ID + return CreateSimpleBlock(pos, size); +} + + +long Cluster::CreateBlockGroup( + long long start_offset, + long long size, + long long discard_padding) +{ + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = start_offset; + const long long stop = start_offset + size; + + //For WebM files, there is a bias towards previous reference times + //(in order to support alt-ref frames, which refer back to the previous + //keyframe). Normally a 0 value is not possible, but here we tenatively + //allow 0 as the value of a reference frame, with the interpretation + //that this is a "previous" reference time. + + long long prev = 1; //nonce + long long next = 0; //nonce + long long duration = -1; //really, this is unsigned + + long long bpos = -1; + long long bsize = -1; + + while (pos < stop) + { + long len; + const long long id = ReadUInt(pReader, pos, len); + assert(id >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); //TODO + assert((pos + len) <= stop); + + pos += len; //consume size + + if (id == 0x21) //Block ID + { + if (bpos < 0) //Block ID + { + bpos = pos; + bsize = size; + } + } + else if (id == 0x1B) //Duration ID + { + assert(size <= 8); + + duration = UnserializeUInt(pReader, pos, size); + assert(duration >= 0); //TODO + } + else if (id == 0x7B) //ReferenceBlock + { + assert(size <= 8); + const long size_ = static_cast(size); + + long long time; + + long status = UnserializeInt(pReader, pos, size_, time); + assert(status == 0); //TODO + + if (time <= 0) //see note above + prev = time; + else //weird + next = time; + } + + pos += size; //consume payload + assert(pos <= stop); + } + + assert(pos == stop); + assert(bpos >= 0); + assert(bsize >= 0); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) BlockGroup( + this, + idx, + bpos, + bsize, + prev, + next, + duration, + discard_padding); + + if (pEntry == NULL) + return -1; //generic error + + BlockGroup* const p = static_cast(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(pEntry); + + const long status = p->Parse(); + + if (status == 0) + { + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + + +long Cluster::GetFirst(const BlockEntry*& pFirst) const +{ + if (m_entries_count <= 0) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pFirst = NULL; + return status; + } + + if (m_entries_count <= 0) //empty cluster + { + pFirst = NULL; + return 0; + } + } + + assert(m_entries); + + pFirst = m_entries[0]; + assert(pFirst); + + return 0; //success +} + +long Cluster::GetLast(const BlockEntry*& pLast) const +{ + for (;;) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pLast = NULL; + return status; + } + + if (status > 0) //no new block + break; + } + + if (m_entries_count <= 0) + { + pLast = NULL; + return 0; + } + + assert(m_entries); + + const long idx = m_entries_count - 1; + + pLast = m_entries[idx]; + assert(pLast); + + return 0; +} + + +long Cluster::GetNext( + const BlockEntry* pCurr, + const BlockEntry*& pNext) const +{ + assert(pCurr); + assert(m_entries); + assert(m_entries_count > 0); + + size_t idx = pCurr->GetIndex(); + assert(idx < size_t(m_entries_count)); + assert(m_entries[idx] == pCurr); + + ++idx; + + if (idx >= size_t(m_entries_count)) + { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) //error + { + pNext = NULL; + return status; + } + + if (status > 0) + { + pNext = NULL; + return 0; + } + + assert(m_entries); + assert(m_entries_count > 0); + assert(idx < size_t(m_entries_count)); + } + + pNext = m_entries[idx]; + assert(pNext); + + return 0; +} + + +long Cluster::GetEntryCount() const +{ + return m_entries_count; +} + + +const BlockEntry* Cluster::GetEntry( + const Track* pTrack, + long long time_ns) const +{ + assert(pTrack); + + if (m_pSegment == NULL) //this is the special EOS cluster + return pTrack->GetEOS(); + +#if 0 + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return NULL; //return EOS here? + + const BlockEntry* pResult = pTrack->GetEOS(); + + BlockEntry** i = m_entries; + assert(i); + + BlockEntry** const j = i + m_entries_count; + + while (i != j) + { + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pTrack->VetEntry(pEntry)) + { + if (time_ns < 0) //just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + + pResult = pEntry; + } + else if (time_ns >= 0) + { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + break; + } + } + + return pResult; + +#else + + const BlockEntry* pResult = pTrack->GetEOS(); + + long index = 0; + + for (;;) + { + if (index >= m_entries_count) + { + long long pos; + long len; + + const long status = Parse(pos, len); + assert(status >= 0); + + if (status > 0) //completely parsed, and no more entries + return pResult; + + if (status < 0) //should never happen + return 0; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + { + ++index; + continue; + } + + if (pTrack->VetEntry(pEntry)) + { + if (time_ns < 0) //just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + + pResult = pEntry; //have a candidate + } + else if (time_ns >= 0) + { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + } + + ++index; + } + +#endif +} + + +const BlockEntry* +Cluster::GetEntry( + const CuePoint& cp, + const CuePoint::TrackPosition& tp) const +{ + assert(m_pSegment); + +#if 0 + + LoadBlockEntries(); + + if (m_entries == NULL) + return NULL; + + const long long count = m_entries_count; + + if (count <= 0) + return NULL; + + const long long tc = cp.GetTimeCode(); + + if ((tp.m_block > 0) && (tp.m_block <= count)) + { + const size_t block = static_cast(tp.m_block); + const size_t index = block - 1; + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) + { + return pEntry; + } + } + + const BlockEntry* const* i = m_entries; + const BlockEntry* const* const j = i + count; + + while (i != j) + { +#ifdef _DEBUG + const ptrdiff_t idx = i - m_entries; + idx; +#endif + + const BlockEntry* const pEntry = *i++; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) + continue; + + const long long tc_ = pBlock->GetTimeCode(this); + assert(tc_ >= 0); + + if (tc_ < tc) + continue; + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) //audio + return pEntry; + + if (type != 1) //not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + + return NULL; + +#else + + const long long tc = cp.GetTimeCode(); + + if (tp.m_block > 0) + { + const long block = static_cast(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(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) //audio + return pEntry; + + if (type != 1) //not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } + +#endif + +} + + +#if 0 +const BlockEntry* Cluster::GetMaxKey(const VideoTrack* pTrack) const +{ + assert(pTrack); + + if (m_pSegment == NULL) //EOS + return pTrack->GetEOS(); + + LoadBlockEntries(); + + if ((m_entries == NULL) || (m_entries_count <= 0)) + return pTrack->GetEOS(); + + BlockEntry** i = m_entries + m_entries_count; + BlockEntry** const j = m_entries; + + while (i != j) + { + const BlockEntry* const pEntry = *--i; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) + continue; + + if (pBlock->IsKey()) + return pEntry; + } + + return pTrack->GetEOS(); //no satisfactory block found +} +#endif + + +BlockEntry::BlockEntry(Cluster* p, long idx) : + m_pCluster(p), + m_index(idx) +{ +} + + +BlockEntry::~BlockEntry() +{ +} + + +bool BlockEntry::EOS() const +{ + return (GetKind() == kBlockEOS); +} + + +const Cluster* BlockEntry::GetCluster() const +{ + return m_pCluster; +} + + +long BlockEntry::GetIndex() const +{ + return m_index; +} + + +SimpleBlock::SimpleBlock( + Cluster* pCluster, + long idx, + long long start, + long long size) : + BlockEntry(pCluster, idx), + m_block(start, size, 0) +{ +} + + +long SimpleBlock::Parse() +{ + return m_block.Parse(m_pCluster); +} + + +BlockEntry::Kind SimpleBlock::GetKind() const +{ + return kBlockSimple; +} + + +const Block* SimpleBlock::GetBlock() const +{ + return &m_block; +} + + +BlockGroup::BlockGroup( + Cluster* pCluster, + long idx, + long long block_start, + long long block_size, + long long prev, + long long next, + long long duration, + long long discard_padding) : + BlockEntry(pCluster, idx), + m_block(block_start, block_size, discard_padding), + m_prev(prev), + m_next(next), + m_duration(duration) +{ +} + + +long BlockGroup::Parse() +{ + const long status = m_block.Parse(m_pCluster); + + if (status) + return status; + + m_block.SetKey((m_prev > 0) && (m_next <= 0)); + + return 0; +} + + +#if 0 +void BlockGroup::ParseBlock(long long start, long long size) +{ + IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; + + Block* const pBlock = new Block(start, size, pReader); + assert(pBlock); //TODO + + //TODO: the Matroska spec says you have multiple blocks within the + //same block group, with blocks ranked by priority (the flag bits). + + assert(m_pBlock == NULL); + m_pBlock = pBlock; +} +#endif + + +BlockEntry::Kind BlockGroup::GetKind() const +{ + return kBlockGroup; +} + + +const Block* BlockGroup::GetBlock() const +{ + return &m_block; +} + + +long long BlockGroup::GetPrevTimeCode() const +{ + return m_prev; +} + + +long long BlockGroup::GetNextTimeCode() const +{ + return m_next; +} + +long long BlockGroup::GetDurationTimeCode() const +{ + return m_duration; +} + +Block::Block(long long start, long long size_, long long discard_padding) : + m_start(start), + m_size(size_), + m_track(0), + m_timecode(-1), + m_flags(0), + m_frames(NULL), + m_frame_count(-1), + m_discard_padding(discard_padding) +{ +} + + +Block::~Block() +{ + delete[] m_frames; +} + + +long Block::Parse(const Cluster* pCluster) +{ + if (pCluster == NULL) + return -1; + + if (pCluster->m_pSegment == NULL) + return -1; + + assert(m_start >= 0); + assert(m_size >= 0); + assert(m_track <= 0); + assert(m_frames == NULL); + assert(m_frame_count <= 0); + + long long pos = m_start; + const long long stop = m_start + m_size; + + long len; + + IMkvReader* const pReader = pCluster->m_pSegment->m_pReader; + + m_track = ReadUInt(pReader, pos, len); + + if (m_track <= 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume track number + + if ((stop - pos) < 2) + return E_FILE_FORMAT_INVALID; + + long status; + long long value; + + status = UnserializeInt(pReader, pos, 2, value); + + if (status) + return E_FILE_FORMAT_INVALID; + + if (value < SHRT_MIN) + return E_FILE_FORMAT_INVALID; + + if (value > SHRT_MAX) + return E_FILE_FORMAT_INVALID; + + m_timecode = static_cast(value); + + pos += 2; + + if ((stop - pos) <= 0) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &m_flags); + + if (status) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(m_flags & 0x06) >> 1; + + ++pos; //consume flags byte + + if (lacing == 0) //no lacing + { + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + m_frame_count = 1; + m_frames = new Frame[m_frame_count]; + + Frame& f = m_frames[0]; + f.pos = pos; + + const long long frame_size = stop - pos; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + + return 0; //success + } + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + unsigned char biased_count; + + status = pReader->Read(pos, 1, &biased_count); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; //consume frame count + assert(pos <= stop); + + m_frame_count = int(biased_count) + 1; + + m_frames = new Frame[m_frame_count]; + assert(m_frames); + + if (lacing == 1) //Xiph + { + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + long size = 0; + int frame_count = m_frame_count; + + while (frame_count > 1) + { + long frame_size = 0; + + for (;;) + { + unsigned char val; + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &val); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; //consume xiph size byte + + frame_size += val; + + if (val < 255) + break; + } + + Frame& f = *pf++; + assert(pf < pf_end); + + f.pos = 0; //patch later + + f.len = frame_size; + size += frame_size; //contribution of this frame + + --frame_count; + } + + assert(pf < pf_end); + assert(pos <= stop); + + { + Frame& f = *pf++; + + if (pf != pf_end) + return E_FILE_FORMAT_INVALID; + + f.pos = 0; //patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) + { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } + else if (lacing == 2) //fixed-size lacing + { + const long long total_size = stop - pos; + + if ((total_size % m_frame_count) != 0) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size / m_frame_count; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + while (pf != pf_end) + { + assert((pos + frame_size) <= stop); + + Frame& f = *pf++; + + f.pos = pos; + f.len = static_cast(frame_size); + + pos += frame_size; + } + + assert(pos == stop); + } + else + { + assert(lacing == 3); //EBML lacing + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + long size = 0; + int frame_count = m_frame_count; + + long long frame_size = ReadUInt(pReader, pos, len); + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of size of first frame + + if ((pos + frame_size) > stop) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + { + Frame& curr = *pf; + + curr.pos = 0; //patch later + + curr.len = static_cast(frame_size); + size += curr.len; //contribution of this frame + } + + --frame_count; + + while (frame_count > 1) + { + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + + assert(pf < pf_end); + + Frame& curr = *pf; + + curr.pos = 0; //patch later + + const long long delta_size_ = ReadUInt(pReader, pos, len); + + if (delta_size_ < 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; //consume length of (delta) size + assert(pos <= stop); + + const int exp = 7*len - 1; + const long long bias = (1LL << exp) - 1LL; + const long long delta_size = delta_size_ - bias; + + frame_size += delta_size; + + if (frame_size < 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + size += curr.len; //contribution of this frame + + --frame_count; + } + + { + assert(pos <= stop); + assert(pf < pf_end); + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + + assert(pf < pf_end); + + Frame& curr = *pf++; + assert(pf == pf_end); + + curr.pos = 0; //patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + frame_size = total_size - size; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast(frame_size); + } + + pf = m_frames; + while (pf != pf_end) + { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + } + + return 0; //success +} + + +long long Block::GetTimeCode(const Cluster* pCluster) const +{ + if (pCluster == 0) + return m_timecode; + + const long long tc0 = pCluster->GetTimeCode(); + assert(tc0 >= 0); + + const long long tc = tc0 + m_timecode; + + return tc; //unscaled timecode units +} + + +long long Block::GetTime(const Cluster* pCluster) const +{ + assert(pCluster); + + const long long tc = GetTimeCode(pCluster); + + const Segment* const pSegment = pCluster->m_pSegment; + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long ns = tc * scale; + + return ns; +} + + +long long Block::GetTrackNumber() const +{ + return m_track; +} + + +bool Block::IsKey() const +{ + return ((m_flags & static_cast(1 << 7)) != 0); +} + + +void Block::SetKey(bool bKey) +{ + if (bKey) + m_flags |= static_cast(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(value); +} + + +int Block::GetFrameCount() const +{ + return m_frame_count; +} + + +const Block::Frame& Block::GetFrame(int idx) const +{ + assert(idx >= 0); + assert(idx < m_frame_count); + + const Frame& f = m_frames[idx]; + assert(f.pos > 0); + assert(f.len > 0); + + return f; +} + + +long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const +{ + assert(pReader); + assert(buf); + + const long status = pReader->Read(pos, len, buf); + return status; +} + +long long Block::GetDiscardPadding() const +{ + return m_discard_padding; +} + +} //end namespace mkvparser diff --git a/mkvparser.hpp b/mkvparser.hpp index d9c02b8..7184d26 100644 --- a/mkvparser.hpp +++ b/mkvparser.hpp @@ -1,1079 +1,1079 @@ -// Copyright (c) 2012 The WebM project authors. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. - -#ifndef MKVPARSER_HPP -#define MKVPARSER_HPP - -#include -#include -#include - -namespace mkvparser -{ - -const int E_FILE_FORMAT_INVALID = -2; -const int E_BUFFER_NOT_FULL = -3; - -class IMkvReader -{ -public: - virtual int Read(long long pos, long len, unsigned char* buf) = 0; - virtual int Length(long long* total, long long* available) = 0; -protected: - virtual ~IMkvReader(); -}; - -long long GetUIntLength(IMkvReader*, long long, long&); -long long ReadUInt(IMkvReader*, long long, long&); -long long UnserializeUInt(IMkvReader*, long long pos, long long size); - -long UnserializeFloat(IMkvReader*, long long pos, long long size, double&); -long UnserializeInt(IMkvReader*, long long pos, long len, long long& result); - -long UnserializeString( - IMkvReader*, - long long pos, - long long size, - char*& str); - -long ParseElementHeader( - IMkvReader* pReader, - long long& pos, //consume id and size fields - long long stop, //if you know size of element's parent - long long& id, - long long& size); - -bool Match(IMkvReader*, long long&, unsigned long, long long&); -bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&); - -void GetVersion(int& major, int& minor, int& build, int& revision); - -struct EBMLHeader -{ - EBMLHeader(); - ~EBMLHeader(); - long long m_version; - long long m_readVersion; - long long m_maxIdLength; - long long m_maxSizeLength; - char* m_docType; - long long m_docTypeVersion; - long long m_docTypeReadVersion; - - long long Parse(IMkvReader*, long long&); - void Init(); -}; - - -class Segment; -class Track; -class Cluster; - -class Block -{ - Block(const Block&); - Block& operator=(const Block&); - -public: - const long long m_start; - const long long m_size; - - Block(long long start, long long size, long long discard_padding); - ~Block(); - - long Parse(const Cluster*); - - long long GetTrackNumber() const; - long long GetTimeCode(const Cluster*) const; //absolute, but not scaled - long long GetTime(const Cluster*) const; //absolute, and scaled (ns) - bool IsKey() const; - void SetKey(bool); - bool IsInvisible() const; - - enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml }; - Lacing GetLacing() const; - - int GetFrameCount() const; //to index frames: [0, count) - - struct Frame - { - long long pos; //absolute offset - long len; - - long Read(IMkvReader*, unsigned char*) const; - }; - - const Frame& GetFrame(int frame_index) const; - - long long GetDiscardPadding() const; - -private: - long long m_track; //Track::Number() - short m_timecode; //relative to cluster - unsigned char m_flags; - - Frame* m_frames; - int m_frame_count; - -protected: - const long long m_discard_padding; -}; - - -class BlockEntry -{ - BlockEntry(const BlockEntry&); - BlockEntry& operator=(const BlockEntry&); - -protected: - BlockEntry(Cluster*, long index); - -public: - virtual ~BlockEntry(); - - bool EOS() const; - const Cluster* GetCluster() const; - long GetIndex() const; - virtual const Block* GetBlock() const = 0; - - enum Kind { kBlockEOS, kBlockSimple, kBlockGroup }; - virtual Kind GetKind() const = 0; - -protected: - Cluster* const m_pCluster; - const long m_index; - -}; - - -class SimpleBlock : public BlockEntry -{ - SimpleBlock(const SimpleBlock&); - SimpleBlock& operator=(const SimpleBlock&); - -public: - SimpleBlock(Cluster*, long index, long long start, long long size); - long Parse(); - - Kind GetKind() const; - const Block* GetBlock() const; - -protected: - Block m_block; - -}; - - -class BlockGroup : public BlockEntry -{ - BlockGroup(const BlockGroup&); - BlockGroup& operator=(const BlockGroup&); - -public: - BlockGroup( - Cluster*, - long index, - long long block_start, //absolute pos of block's payload - long long block_size, //size of block's payload - long long prev, - long long next, - long long duration, - long long discard_padding); - - long Parse(); - - Kind GetKind() const; - const Block* GetBlock() const; - - long long GetPrevTimeCode() const; //relative to block's time - long long GetNextTimeCode() const; //as above - long long GetDurationTimeCode() const; - -private: - Block m_block; - const long long m_prev; - const long long m_next; - const long long m_duration; -}; - -/////////////////////////////////////////////////////////////// -// ContentEncoding element -// Elements used to describe if the track data has been encrypted or -// compressed with zlib or header stripping. -class ContentEncoding { -public: - enum { - kCTR = 1 - }; - - ContentEncoding(); - ~ContentEncoding(); - - // ContentCompression element names - struct ContentCompression { - ContentCompression(); - ~ContentCompression(); - - unsigned long long algo; - unsigned char* settings; - long long settings_len; - }; - - // ContentEncAESSettings element names - struct ContentEncAESSettings { - ContentEncAESSettings() : cipher_mode(kCTR) {} - ~ContentEncAESSettings() {} - - unsigned long long cipher_mode; - }; - - // ContentEncryption element names - struct ContentEncryption { - ContentEncryption(); - ~ContentEncryption(); - - unsigned long long algo; - unsigned char* key_id; - long long key_id_len; - unsigned char* signature; - long long signature_len; - unsigned char* sig_key_id; - long long sig_key_id_len; - unsigned long long sig_algo; - unsigned long long sig_hash_algo; - - ContentEncAESSettings aes_settings; - }; - - // Returns ContentCompression represented by |idx|. Returns NULL if |idx| - // is out of bounds. - const ContentCompression* GetCompressionByIndex(unsigned long idx) const; - - // Returns number of ContentCompression elements in this ContentEncoding - // element. - unsigned long GetCompressionCount() const; - - // Parses the ContentCompression element from |pReader|. |start| is the - // starting offset of the ContentCompression payload. |size| is the size in - // bytes of the ContentCompression payload. |compression| is where the parsed - // values will be stored. - long ParseCompressionEntry(long long start, - long long size, - IMkvReader* pReader, - ContentCompression* compression); - - // Returns ContentEncryption represented by |idx|. Returns NULL if |idx| - // is out of bounds. - const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const; - - // Returns number of ContentEncryption elements in this ContentEncoding - // element. - unsigned long GetEncryptionCount() const; - - // Parses the ContentEncAESSettings element from |pReader|. |start| is the - // starting offset of the ContentEncAESSettings payload. |size| is the - // size in bytes of the ContentEncAESSettings payload. |encryption| is - // where the parsed values will be stored. - long ParseContentEncAESSettingsEntry(long long start, - long long size, - IMkvReader* pReader, - ContentEncAESSettings* aes); - - // Parses the ContentEncoding element from |pReader|. |start| is the - // starting offset of the ContentEncoding payload. |size| is the size in - // bytes of the ContentEncoding payload. Returns true on success. - long ParseContentEncodingEntry(long long start, - long long size, - IMkvReader* pReader); - - // Parses the ContentEncryption element from |pReader|. |start| is the - // starting offset of the ContentEncryption payload. |size| is the size in - // bytes of the ContentEncryption payload. |encryption| is where the parsed - // values will be stored. - long ParseEncryptionEntry(long long start, - long long size, - IMkvReader* pReader, - ContentEncryption* encryption); - - unsigned long long encoding_order() const { return encoding_order_; } - unsigned long long encoding_scope() const { return encoding_scope_; } - unsigned long long encoding_type() const { return encoding_type_; } - -private: - // Member variables for list of ContentCompression elements. - ContentCompression** compression_entries_; - ContentCompression** compression_entries_end_; - - // Member variables for list of ContentEncryption elements. - ContentEncryption** encryption_entries_; - ContentEncryption** encryption_entries_end_; - - // ContentEncoding element names - unsigned long long encoding_order_; - unsigned long long encoding_scope_; - unsigned long long encoding_type_; - - // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); - ContentEncoding(const ContentEncoding&); - ContentEncoding& operator=(const ContentEncoding&); -}; - -class Track -{ - Track(const Track&); - Track& operator=(const Track&); - -public: - class Info; - static long Create( - Segment*, - const Info&, - long long element_start, - long long element_size, - Track*&); - - enum Type { - kVideo = 1, - kAudio = 2, - kSubtitle = 0x11, - kMetadata = 0x21 - }; - - Segment* const m_pSegment; - const long long m_element_start; - const long long m_element_size; - virtual ~Track(); - - long GetType() const; - long GetNumber() const; - unsigned long long GetUid() const; - const char* GetNameAsUTF8() const; - const char* GetLanguage() const; - const char* GetCodecNameAsUTF8() const; - const char* GetCodecId() const; - const unsigned char* GetCodecPrivate(size_t&) const; - bool GetLacing() const; - unsigned long long GetDefaultDuration() const; - unsigned long long GetCodecDelay() const; - unsigned long long GetSeekPreRoll() const; - - const BlockEntry* GetEOS() const; - - struct Settings - { - long long start; - long long size; - }; - - class Info - { - public: - Info(); - ~Info(); - int Copy(Info&) const; - void Clear(); - long type; - long number; - unsigned long long uid; - unsigned long long defaultDuration; - unsigned long long codecDelay; - unsigned long long seekPreRoll; - char* nameAsUTF8; - char* language; - char* codecId; - char* codecNameAsUTF8; - unsigned char* codecPrivate; - size_t codecPrivateSize; - bool lacing; - Settings settings; - - private: - Info(const Info&); - Info& operator=(const Info&); - int CopyStr(char* Info::*str, Info&) const; - }; - - long GetFirst(const BlockEntry*&) const; - long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const; - virtual bool VetEntry(const BlockEntry*) const; - virtual long Seek(long long time_ns, const BlockEntry*&) const; - - const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const; - unsigned long GetContentEncodingCount() const; - - long ParseContentEncodingsEntry(long long start, long long size); - -protected: - Track( - Segment*, - long long element_start, - long long element_size); - - Info m_info; - - class EOSBlock : public BlockEntry - { - public: - EOSBlock(); - - Kind GetKind() const; - const Block* GetBlock() const; - }; - - EOSBlock m_eos; - -private: - ContentEncoding** content_encoding_entries_; - ContentEncoding** content_encoding_entries_end_; -}; - - -class VideoTrack : public Track -{ - VideoTrack(const VideoTrack&); - VideoTrack& operator=(const VideoTrack&); - - VideoTrack( - Segment*, - long long element_start, - long long element_size); - -public: - static long Parse( - Segment*, - const Info&, - long long element_start, - long long element_size, - VideoTrack*&); - - long long GetWidth() const; - long long GetHeight() const; - double GetFrameRate() const; - - bool VetEntry(const BlockEntry*) const; - long Seek(long long time_ns, const BlockEntry*&) const; - -private: - long long m_width; - long long m_height; - double m_rate; - -}; - - -class AudioTrack : public Track -{ - AudioTrack(const AudioTrack&); - AudioTrack& operator=(const AudioTrack&); - - AudioTrack( - Segment*, - long long element_start, - long long element_size); -public: - static long Parse( - Segment*, - const Info&, - long long element_start, - long long element_size, - AudioTrack*&); - - double GetSamplingRate() const; - long long GetChannels() const; - long long GetBitDepth() const; - -private: - double m_rate; - long long m_channels; - long long m_bitDepth; -}; - - -class Tracks -{ - Tracks(const Tracks&); - Tracks& operator=(const Tracks&); - -public: - Segment* const m_pSegment; - const long long m_start; - const long long m_size; - const long long m_element_start; - const long long m_element_size; - - Tracks( - Segment*, - long long start, - long long size, - long long element_start, - long long element_size); - - ~Tracks(); - - long Parse(); - - unsigned long GetTracksCount() const; - - const Track* GetTrackByNumber(long tn) const; - const Track* GetTrackByIndex(unsigned long idx) const; - -private: - Track** m_trackEntries; - Track** m_trackEntriesEnd; - - long ParseTrackEntry( - long long payload_start, - long long payload_size, - long long element_start, - long long element_size, - Track*&) const; - -}; - - -class Chapters -{ - Chapters(const Chapters&); - Chapters& operator=(const Chapters&); - -public: - Segment* const m_pSegment; - const long long m_start; - const long long m_size; - const long long m_element_start; - const long long m_element_size; - - Chapters( - Segment*, - long long payload_start, - long long payload_size, - long long element_start, - long long element_size); - - ~Chapters(); - - long Parse(); - - class Atom; - class Edition; - - class Display - { - friend class Atom; - Display(); - Display(const Display&); - ~Display(); - Display& operator=(const Display&); - public: - const char* GetString() const; - const char* GetLanguage() const; - const char* GetCountry() const; - private: - void Init(); - void ShallowCopy(Display&) const; - void Clear(); - long Parse(IMkvReader*, long long pos, long long size); - - char* m_string; - char* m_language; - char* m_country; - }; - - class Atom - { - friend class Edition; - Atom(); - Atom(const Atom&); - ~Atom(); - Atom& operator=(const Atom&); - public: - unsigned long long GetUID() const; - const char* GetStringUID() const; - - long long GetStartTimecode() const; - long long GetStopTimecode() const; - - long long GetStartTime(const Chapters*) const; - long long GetStopTime(const Chapters*) const; - - int GetDisplayCount() const; - const Display* GetDisplay(int index) const; - private: - void Init(); - void ShallowCopy(Atom&) const; - void Clear(); - long Parse(IMkvReader*, long long pos, long long size); - static long long GetTime(const Chapters*, long long timecode); - - long ParseDisplay(IMkvReader*, long long pos, long long size); - bool ExpandDisplaysArray(); - - char* m_string_uid; - unsigned long long m_uid; - long long m_start_timecode; - long long m_stop_timecode; - - Display* m_displays; - int m_displays_size; - int m_displays_count; - }; - - class Edition - { - friend class Chapters; - Edition(); - Edition(const Edition&); - ~Edition(); - Edition& operator=(const Edition&); - public: - int GetAtomCount() const; - const Atom* GetAtom(int index) const; - private: - void Init(); - void ShallowCopy(Edition&) const; - void Clear(); - long Parse(IMkvReader*, long long pos, long long size); - - long ParseAtom(IMkvReader*, long long pos, long long size); - bool ExpandAtomsArray(); - - Atom* m_atoms; - int m_atoms_size; - int m_atoms_count; - }; - - int GetEditionCount() const; - const Edition* GetEdition(int index) const; - -private: - long ParseEdition(long long pos, long long size); - bool ExpandEditionsArray(); - - Edition* m_editions; - int m_editions_size; - int m_editions_count; - -}; - - -class SegmentInfo -{ - SegmentInfo(const SegmentInfo&); - SegmentInfo& operator=(const SegmentInfo&); - -public: - Segment* const m_pSegment; - const long long m_start; - const long long m_size; - const long long m_element_start; - const long long m_element_size; - - SegmentInfo( - Segment*, - long long start, - long long size, - long long element_start, - long long element_size); - - ~SegmentInfo(); - - long Parse(); - - long long GetTimeCodeScale() const; - long long GetDuration() const; //scaled - const char* GetMuxingAppAsUTF8() const; - const char* GetWritingAppAsUTF8() const; - const char* GetTitleAsUTF8() const; - -private: - long long m_timecodeScale; - double m_duration; - char* m_pMuxingAppAsUTF8; - char* m_pWritingAppAsUTF8; - char* m_pTitleAsUTF8; -}; - - -class SeekHead -{ - SeekHead(const SeekHead&); - SeekHead& operator=(const SeekHead&); - -public: - Segment* const m_pSegment; - const long long m_start; - const long long m_size; - const long long m_element_start; - const long long m_element_size; - - SeekHead( - Segment*, - long long start, - long long size, - long long element_start, - long long element_size); - - ~SeekHead(); - - long Parse(); - - struct Entry - { - //the SeekHead entry payload - long long id; - long long pos; - - //absolute pos of SeekEntry ID - long long element_start; - - //SeekEntry ID size + size size + payload - long long element_size; - }; - - int GetCount() const; - const Entry* GetEntry(int idx) const; - - struct VoidElement - { - //absolute pos of Void ID - long long element_start; - - //ID size + size size + payload size - long long element_size; - }; - - int GetVoidElementCount() const; - const VoidElement* GetVoidElement(int idx) const; - -private: - Entry* m_entries; - int m_entry_count; - - VoidElement* m_void_elements; - int m_void_element_count; - - static bool ParseEntry( - IMkvReader*, - long long pos, //payload - long long size, - Entry*); - -}; - -class Cues; -class CuePoint -{ - friend class Cues; - - CuePoint(long, long long); - ~CuePoint(); - - CuePoint(const CuePoint&); - CuePoint& operator=(const CuePoint&); - -public: - long long m_element_start; - long long m_element_size; - - void Load(IMkvReader*); - - long long GetTimeCode() const; //absolute but unscaled - long long GetTime(const Segment*) const; //absolute and scaled (ns units) - - struct TrackPosition - { - long long m_track; - long long m_pos; //of cluster - long long m_block; - //codec_state //defaults to 0 - //reference = clusters containing req'd referenced blocks - // reftime = timecode of the referenced block - - void Parse(IMkvReader*, long long, long long); - }; - - const TrackPosition* Find(const Track*) const; - -private: - const long m_index; - long long m_timecode; - TrackPosition* m_track_positions; - size_t m_track_positions_count; - -}; - - -class Cues -{ - friend class Segment; - - Cues( - Segment*, - long long start, - long long size, - long long element_start, - long long element_size); - ~Cues(); - - Cues(const Cues&); - Cues& operator=(const Cues&); - -public: - Segment* const m_pSegment; - const long long m_start; - const long long m_size; - const long long m_element_start; - const long long m_element_size; - - bool Find( //lower bound of time_ns - long long time_ns, - const Track*, - const CuePoint*&, - const CuePoint::TrackPosition*&) const; - -#if 0 - bool FindNext( //upper_bound of time_ns - long long time_ns, - const Track*, - const CuePoint*&, - const CuePoint::TrackPosition*&) const; -#endif - - const CuePoint* GetFirst() const; - const CuePoint* GetLast() const; - const CuePoint* GetNext(const CuePoint*) const; - - const BlockEntry* GetBlock( - const CuePoint*, - const CuePoint::TrackPosition*) const; - - bool LoadCuePoint() const; - long GetCount() const; //loaded only - //long GetTotal() const; //loaded + preloaded - bool DoneParsing() const; - -private: - void Init() const; - void PreloadCuePoint(long&, long long) const; - - mutable CuePoint** m_cue_points; - mutable long m_count; - mutable long m_preload_count; - mutable long long m_pos; - -}; - - -class Cluster -{ - friend class Segment; - - Cluster(const Cluster&); - Cluster& operator=(const Cluster&); - -public: - Segment* const m_pSegment; - -public: - static Cluster* Create( - Segment*, - long index, //index in segment - long long off); //offset relative to segment - //long long element_size); - - Cluster(); //EndOfStream - ~Cluster(); - - bool EOS() const; - - long long GetTimeCode() const; //absolute, but not scaled - long long GetTime() const; //absolute, and scaled (nanosecond units) - long long GetFirstTime() const; //time (ns) of first (earliest) block - long long GetLastTime() const; //time (ns) of last (latest) block - - long GetFirst(const BlockEntry*&) const; - long GetLast(const BlockEntry*&) const; - long GetNext(const BlockEntry* curr, const BlockEntry*& next) const; - - const BlockEntry* GetEntry(const Track*, long long ns = -1) const; - const BlockEntry* GetEntry( - const CuePoint&, - const CuePoint::TrackPosition&) const; - //const BlockEntry* GetMaxKey(const VideoTrack*) const; - -// static bool HasBlockEntries(const Segment*, long long); - - static long HasBlockEntries( - const Segment*, - long long idoff, - long long& pos, - long& size); - - long GetEntryCount() const; - - long Load(long long& pos, long& size) const; - - long Parse(long long& pos, long& size) const; - long GetEntry(long index, const mkvparser::BlockEntry*&) const; - -protected: - Cluster( - Segment*, - long index, - long long element_start); - //long long element_size); - -public: - const long long m_element_start; - long long GetPosition() const; //offset relative to segment - - long GetIndex() const; - long long GetElementSize() const; - //long long GetPayloadSize() const; - - //long long Unparsed() const; - -private: - long m_index; - mutable long long m_pos; - //mutable long long m_size; - mutable long long m_element_size; - mutable long long m_timecode; - mutable BlockEntry** m_entries; - mutable long m_entries_size; - mutable long m_entries_count; - - long ParseSimpleBlock(long long, long long&, long&); - long ParseBlockGroup(long long, long long&, long&); - - long CreateBlock(long long id, long long pos, long long size, - long long discard_padding); - long CreateBlockGroup(long long start_offset, long long size, - long long discard_padding); - long CreateSimpleBlock(long long, long long); - -}; - - -class Segment -{ - friend class Cues; - friend class Track; - friend class VideoTrack; - - Segment(const Segment&); - Segment& operator=(const Segment&); - -private: - Segment( - IMkvReader*, - long long elem_start, - //long long elem_size, - long long pos, - long long size); - -public: - IMkvReader* const m_pReader; - const long long m_element_start; - //const long long m_element_size; - const long long m_start; //posn of segment payload - const long long m_size; //size of segment payload - Cluster m_eos; //TODO: make private? - - static long long CreateInstance(IMkvReader*, long long, Segment*&); - ~Segment(); - - long Load(); //loads headers and all clusters - - //for incremental loading - //long long Unparsed() const; - bool DoneParsing() const; - long long ParseHeaders(); //stops when first cluster is found - //long FindNextCluster(long long& pos, long& size) const; - long LoadCluster(long long& pos, long& size); //load one cluster - long LoadCluster(); - - long ParseNext( - const Cluster* pCurr, - const Cluster*& pNext, - long long& pos, - long& size); - -#if 0 - //This pair parses one cluster, but only changes the state of the - //segment object when the cluster is actually added to the index. - long ParseCluster(long long& cluster_pos, long long& new_pos) const; - bool AddCluster(long long cluster_pos, long long new_pos); -#endif - - const SeekHead* GetSeekHead() const; - const Tracks* GetTracks() const; - const SegmentInfo* GetInfo() const; - const Cues* GetCues() const; - const Chapters* GetChapters() const; - - long long GetDuration() const; - - unsigned long GetCount() const; - const Cluster* GetFirst() const; - const Cluster* GetLast() const; - const Cluster* GetNext(const Cluster*); - - const Cluster* FindCluster(long long time_nanoseconds) const; - //const BlockEntry* Seek(long long time_nanoseconds, const Track*) const; - - const Cluster* FindOrPreloadCluster(long long pos); - - long ParseCues( - long long cues_off, //offset relative to start of segment - long long& parse_pos, - long& parse_len); - -private: - - long long m_pos; //absolute file posn; what has been consumed so far - Cluster* m_pUnknownSize; - - SeekHead* m_pSeekHead; - SegmentInfo* m_pInfo; - Tracks* m_pTracks; - Cues* m_pCues; - Chapters* m_pChapters; - Cluster** m_clusters; - long m_clusterCount; //number of entries for which m_index >= 0 - long m_clusterPreloadCount; //number of entries for which m_index < 0 - long m_clusterSize; //array size - - long DoLoadCluster(long long&, long&); - long DoLoadClusterUnknownSize(long long&, long&); - long DoParseNext(const Cluster*&, long long&, long&); - - void AppendCluster(Cluster*); - void PreloadCluster(Cluster*, ptrdiff_t); - - //void ParseSeekHead(long long pos, long long size); - //void ParseSeekEntry(long long pos, long long size); - //void ParseCues(long long); - - const BlockEntry* GetBlock( - const CuePoint&, - const CuePoint::TrackPosition&); - -}; - -} //end namespace mkvparser - -inline long mkvparser::Segment::LoadCluster() -{ - long long pos; - long size; - - return LoadCluster(pos, size); -} - -#endif //MKVPARSER_HPP +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVPARSER_HPP +#define MKVPARSER_HPP + +#include +#include +#include + +namespace mkvparser +{ + +const int E_FILE_FORMAT_INVALID = -2; +const int E_BUFFER_NOT_FULL = -3; + +class IMkvReader +{ +public: + virtual int Read(long long pos, long len, unsigned char* buf) = 0; + virtual int Length(long long* total, long long* available) = 0; +protected: + virtual ~IMkvReader(); +}; + +long long GetUIntLength(IMkvReader*, long long, long&); +long long ReadUInt(IMkvReader*, long long, long&); +long long UnserializeUInt(IMkvReader*, long long pos, long long size); + +long UnserializeFloat(IMkvReader*, long long pos, long long size, double&); +long UnserializeInt(IMkvReader*, long long pos, long len, long long& result); + +long UnserializeString( + IMkvReader*, + long long pos, + long long size, + char*& str); + +long ParseElementHeader( + IMkvReader* pReader, + long long& pos, //consume id and size fields + long long stop, //if you know size of element's parent + long long& id, + long long& size); + +bool Match(IMkvReader*, long long&, unsigned long, long long&); +bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&); + +void GetVersion(int& major, int& minor, int& build, int& revision); + +struct EBMLHeader +{ + EBMLHeader(); + ~EBMLHeader(); + long long m_version; + long long m_readVersion; + long long m_maxIdLength; + long long m_maxSizeLength; + char* m_docType; + long long m_docTypeVersion; + long long m_docTypeReadVersion; + + long long Parse(IMkvReader*, long long&); + void Init(); +}; + + +class Segment; +class Track; +class Cluster; + +class Block +{ + Block(const Block&); + Block& operator=(const Block&); + +public: + const long long m_start; + const long long m_size; + + Block(long long start, long long size, long long discard_padding); + ~Block(); + + long Parse(const Cluster*); + + long long GetTrackNumber() const; + long long GetTimeCode(const Cluster*) const; //absolute, but not scaled + long long GetTime(const Cluster*) const; //absolute, and scaled (ns) + bool IsKey() const; + void SetKey(bool); + bool IsInvisible() const; + + enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml }; + Lacing GetLacing() const; + + int GetFrameCount() const; //to index frames: [0, count) + + struct Frame + { + long long pos; //absolute offset + long len; + + long Read(IMkvReader*, unsigned char*) const; + }; + + const Frame& GetFrame(int frame_index) const; + + long long GetDiscardPadding() const; + +private: + long long m_track; //Track::Number() + short m_timecode; //relative to cluster + unsigned char m_flags; + + Frame* m_frames; + int m_frame_count; + +protected: + const long long m_discard_padding; +}; + + +class BlockEntry +{ + BlockEntry(const BlockEntry&); + BlockEntry& operator=(const BlockEntry&); + +protected: + BlockEntry(Cluster*, long index); + +public: + virtual ~BlockEntry(); + + bool EOS() const; + const Cluster* GetCluster() const; + long GetIndex() const; + virtual const Block* GetBlock() const = 0; + + enum Kind { kBlockEOS, kBlockSimple, kBlockGroup }; + virtual Kind GetKind() const = 0; + +protected: + Cluster* const m_pCluster; + const long m_index; + +}; + + +class SimpleBlock : public BlockEntry +{ + SimpleBlock(const SimpleBlock&); + SimpleBlock& operator=(const SimpleBlock&); + +public: + SimpleBlock(Cluster*, long index, long long start, long long size); + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + +protected: + Block m_block; + +}; + + +class BlockGroup : public BlockEntry +{ + BlockGroup(const BlockGroup&); + BlockGroup& operator=(const BlockGroup&); + +public: + BlockGroup( + Cluster*, + long index, + long long block_start, //absolute pos of block's payload + long long block_size, //size of block's payload + long long prev, + long long next, + long long duration, + long long discard_padding); + + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + long long GetPrevTimeCode() const; //relative to block's time + long long GetNextTimeCode() const; //as above + long long GetDurationTimeCode() const; + +private: + Block m_block; + const long long m_prev; + const long long m_next; + const long long m_duration; +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +class ContentEncoding { +public: + enum { + kCTR = 1 + }; + + ContentEncoding(); + ~ContentEncoding(); + + // ContentCompression element names + struct ContentCompression { + ContentCompression(); + ~ContentCompression(); + + unsigned long long algo; + unsigned char* settings; + long long settings_len; + }; + + // ContentEncAESSettings element names + struct ContentEncAESSettings { + ContentEncAESSettings() : cipher_mode(kCTR) {} + ~ContentEncAESSettings() {} + + unsigned long long cipher_mode; + }; + + // ContentEncryption element names + struct ContentEncryption { + ContentEncryption(); + ~ContentEncryption(); + + unsigned long long algo; + unsigned char* key_id; + long long key_id_len; + unsigned char* signature; + long long signature_len; + unsigned char* sig_key_id; + long long sig_key_id_len; + unsigned long long sig_algo; + unsigned long long sig_hash_algo; + + ContentEncAESSettings aes_settings; + }; + + // Returns ContentCompression represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentCompression* GetCompressionByIndex(unsigned long idx) const; + + // Returns number of ContentCompression elements in this ContentEncoding + // element. + unsigned long GetCompressionCount() const; + + // Parses the ContentCompression element from |pReader|. |start| is the + // starting offset of the ContentCompression payload. |size| is the size in + // bytes of the ContentCompression payload. |compression| is where the parsed + // values will be stored. + long ParseCompressionEntry(long long start, + long long size, + IMkvReader* pReader, + ContentCompression* compression); + + // Returns ContentEncryption represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const; + + // Returns number of ContentEncryption elements in this ContentEncoding + // element. + unsigned long GetEncryptionCount() const; + + // Parses the ContentEncAESSettings element from |pReader|. |start| is the + // starting offset of the ContentEncAESSettings payload. |size| is the + // size in bytes of the ContentEncAESSettings payload. |encryption| is + // where the parsed values will be stored. + long ParseContentEncAESSettingsEntry(long long start, + long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes); + + // Parses the ContentEncoding element from |pReader|. |start| is the + // starting offset of the ContentEncoding payload. |size| is the size in + // bytes of the ContentEncoding payload. Returns true on success. + long ParseContentEncodingEntry(long long start, + long long size, + IMkvReader* pReader); + + // Parses the ContentEncryption element from |pReader|. |start| is the + // starting offset of the ContentEncryption payload. |size| is the size in + // bytes of the ContentEncryption payload. |encryption| is where the parsed + // values will be stored. + long ParseEncryptionEntry(long long start, + long long size, + IMkvReader* pReader, + ContentEncryption* encryption); + + unsigned long long encoding_order() const { return encoding_order_; } + unsigned long long encoding_scope() const { return encoding_scope_; } + unsigned long long encoding_type() const { return encoding_type_; } + +private: + // Member variables for list of ContentCompression elements. + ContentCompression** compression_entries_; + ContentCompression** compression_entries_end_; + + // Member variables for list of ContentEncryption elements. + ContentEncryption** encryption_entries_; + ContentEncryption** encryption_entries_end_; + + // ContentEncoding element names + unsigned long long encoding_order_; + unsigned long long encoding_scope_; + unsigned long long encoding_type_; + + // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); + ContentEncoding(const ContentEncoding&); + ContentEncoding& operator=(const ContentEncoding&); +}; + +class Track +{ + Track(const Track&); + Track& operator=(const Track&); + +public: + class Info; + static long Create( + Segment*, + const Info&, + long long element_start, + long long element_size, + Track*&); + + enum Type { + kVideo = 1, + kAudio = 2, + kSubtitle = 0x11, + kMetadata = 0x21 + }; + + Segment* const m_pSegment; + const long long m_element_start; + const long long m_element_size; + virtual ~Track(); + + long GetType() const; + long GetNumber() const; + unsigned long long GetUid() const; + const char* GetNameAsUTF8() const; + const char* GetLanguage() const; + const char* GetCodecNameAsUTF8() const; + const char* GetCodecId() const; + const unsigned char* GetCodecPrivate(size_t&) const; + bool GetLacing() const; + unsigned long long GetDefaultDuration() const; + unsigned long long GetCodecDelay() const; + unsigned long long GetSeekPreRoll() const; + + const BlockEntry* GetEOS() const; + + struct Settings + { + long long start; + long long size; + }; + + class Info + { + public: + Info(); + ~Info(); + int Copy(Info&) const; + void Clear(); + long type; + long number; + unsigned long long uid; + unsigned long long defaultDuration; + unsigned long long codecDelay; + unsigned long long seekPreRoll; + char* nameAsUTF8; + char* language; + char* codecId; + char* codecNameAsUTF8; + unsigned char* codecPrivate; + size_t codecPrivateSize; + bool lacing; + Settings settings; + + private: + Info(const Info&); + Info& operator=(const Info&); + int CopyStr(char* Info::*str, Info&) const; + }; + + long GetFirst(const BlockEntry*&) const; + long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const; + virtual bool VetEntry(const BlockEntry*) const; + virtual long Seek(long long time_ns, const BlockEntry*&) const; + + const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const; + unsigned long GetContentEncodingCount() const; + + long ParseContentEncodingsEntry(long long start, long long size); + +protected: + Track( + Segment*, + long long element_start, + long long element_size); + + Info m_info; + + class EOSBlock : public BlockEntry + { + public: + EOSBlock(); + + Kind GetKind() const; + const Block* GetBlock() const; + }; + + EOSBlock m_eos; + +private: + ContentEncoding** content_encoding_entries_; + ContentEncoding** content_encoding_entries_end_; +}; + + +class VideoTrack : public Track +{ + VideoTrack(const VideoTrack&); + VideoTrack& operator=(const VideoTrack&); + + VideoTrack( + Segment*, + long long element_start, + long long element_size); + +public: + static long Parse( + Segment*, + const Info&, + long long element_start, + long long element_size, + VideoTrack*&); + + long long GetWidth() const; + long long GetHeight() const; + double GetFrameRate() const; + + bool VetEntry(const BlockEntry*) const; + long Seek(long long time_ns, const BlockEntry*&) const; + +private: + long long m_width; + long long m_height; + double m_rate; + +}; + + +class AudioTrack : public Track +{ + AudioTrack(const AudioTrack&); + AudioTrack& operator=(const AudioTrack&); + + AudioTrack( + Segment*, + long long element_start, + long long element_size); +public: + static long Parse( + Segment*, + const Info&, + long long element_start, + long long element_size, + AudioTrack*&); + + double GetSamplingRate() const; + long long GetChannels() const; + long long GetBitDepth() const; + +private: + double m_rate; + long long m_channels; + long long m_bitDepth; +}; + + +class Tracks +{ + Tracks(const Tracks&); + Tracks& operator=(const Tracks&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Tracks( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~Tracks(); + + long Parse(); + + unsigned long GetTracksCount() const; + + const Track* GetTrackByNumber(long tn) const; + const Track* GetTrackByIndex(unsigned long idx) const; + +private: + Track** m_trackEntries; + Track** m_trackEntriesEnd; + + long ParseTrackEntry( + long long payload_start, + long long payload_size, + long long element_start, + long long element_size, + Track*&) const; + +}; + + +class Chapters +{ + Chapters(const Chapters&); + Chapters& operator=(const Chapters&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Chapters( + Segment*, + long long payload_start, + long long payload_size, + long long element_start, + long long element_size); + + ~Chapters(); + + long Parse(); + + class Atom; + class Edition; + + class Display + { + friend class Atom; + Display(); + Display(const Display&); + ~Display(); + Display& operator=(const Display&); + public: + const char* GetString() const; + const char* GetLanguage() const; + const char* GetCountry() const; + private: + void Init(); + void ShallowCopy(Display&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + char* m_string; + char* m_language; + char* m_country; + }; + + class Atom + { + friend class Edition; + Atom(); + Atom(const Atom&); + ~Atom(); + Atom& operator=(const Atom&); + public: + unsigned long long GetUID() const; + const char* GetStringUID() const; + + long long GetStartTimecode() const; + long long GetStopTimecode() const; + + long long GetStartTime(const Chapters*) const; + long long GetStopTime(const Chapters*) const; + + int GetDisplayCount() const; + const Display* GetDisplay(int index) const; + private: + void Init(); + void ShallowCopy(Atom&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + static long long GetTime(const Chapters*, long long timecode); + + long ParseDisplay(IMkvReader*, long long pos, long long size); + bool ExpandDisplaysArray(); + + char* m_string_uid; + unsigned long long m_uid; + long long m_start_timecode; + long long m_stop_timecode; + + Display* m_displays; + int m_displays_size; + int m_displays_count; + }; + + class Edition + { + friend class Chapters; + Edition(); + Edition(const Edition&); + ~Edition(); + Edition& operator=(const Edition&); + public: + int GetAtomCount() const; + const Atom* GetAtom(int index) const; + private: + void Init(); + void ShallowCopy(Edition&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + long ParseAtom(IMkvReader*, long long pos, long long size); + bool ExpandAtomsArray(); + + Atom* m_atoms; + int m_atoms_size; + int m_atoms_count; + }; + + int GetEditionCount() const; + const Edition* GetEdition(int index) const; + +private: + long ParseEdition(long long pos, long long size); + bool ExpandEditionsArray(); + + Edition* m_editions; + int m_editions_size; + int m_editions_count; + +}; + + +class SegmentInfo +{ + SegmentInfo(const SegmentInfo&); + SegmentInfo& operator=(const SegmentInfo&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SegmentInfo( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~SegmentInfo(); + + long Parse(); + + long long GetTimeCodeScale() const; + long long GetDuration() const; //scaled + const char* GetMuxingAppAsUTF8() const; + const char* GetWritingAppAsUTF8() const; + const char* GetTitleAsUTF8() const; + +private: + long long m_timecodeScale; + double m_duration; + char* m_pMuxingAppAsUTF8; + char* m_pWritingAppAsUTF8; + char* m_pTitleAsUTF8; +}; + + +class SeekHead +{ + SeekHead(const SeekHead&); + SeekHead& operator=(const SeekHead&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SeekHead( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + + ~SeekHead(); + + long Parse(); + + struct Entry + { + //the SeekHead entry payload + long long id; + long long pos; + + //absolute pos of SeekEntry ID + long long element_start; + + //SeekEntry ID size + size size + payload + long long element_size; + }; + + int GetCount() const; + const Entry* GetEntry(int idx) const; + + struct VoidElement + { + //absolute pos of Void ID + long long element_start; + + //ID size + size size + payload size + long long element_size; + }; + + int GetVoidElementCount() const; + const VoidElement* GetVoidElement(int idx) const; + +private: + Entry* m_entries; + int m_entry_count; + + VoidElement* m_void_elements; + int m_void_element_count; + + static bool ParseEntry( + IMkvReader*, + long long pos, //payload + long long size, + Entry*); + +}; + +class Cues; +class CuePoint +{ + friend class Cues; + + CuePoint(long, long long); + ~CuePoint(); + + CuePoint(const CuePoint&); + CuePoint& operator=(const CuePoint&); + +public: + long long m_element_start; + long long m_element_size; + + void Load(IMkvReader*); + + long long GetTimeCode() const; //absolute but unscaled + long long GetTime(const Segment*) const; //absolute and scaled (ns units) + + struct TrackPosition + { + long long m_track; + long long m_pos; //of cluster + long long m_block; + //codec_state //defaults to 0 + //reference = clusters containing req'd referenced blocks + // reftime = timecode of the referenced block + + void Parse(IMkvReader*, long long, long long); + }; + + const TrackPosition* Find(const Track*) const; + +private: + const long m_index; + long long m_timecode; + TrackPosition* m_track_positions; + size_t m_track_positions_count; + +}; + + +class Cues +{ + friend class Segment; + + Cues( + Segment*, + long long start, + long long size, + long long element_start, + long long element_size); + ~Cues(); + + Cues(const Cues&); + Cues& operator=(const Cues&); + +public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + bool Find( //lower bound of time_ns + long long time_ns, + const Track*, + const CuePoint*&, + const CuePoint::TrackPosition*&) const; + +#if 0 + bool FindNext( //upper_bound of time_ns + long long time_ns, + const Track*, + const CuePoint*&, + const CuePoint::TrackPosition*&) const; +#endif + + const CuePoint* GetFirst() const; + const CuePoint* GetLast() const; + const CuePoint* GetNext(const CuePoint*) const; + + const BlockEntry* GetBlock( + const CuePoint*, + const CuePoint::TrackPosition*) const; + + bool LoadCuePoint() const; + long GetCount() const; //loaded only + //long GetTotal() const; //loaded + preloaded + bool DoneParsing() const; + +private: + void Init() const; + void PreloadCuePoint(long&, long long) const; + + mutable CuePoint** m_cue_points; + mutable long m_count; + mutable long m_preload_count; + mutable long long m_pos; + +}; + + +class Cluster +{ + friend class Segment; + + Cluster(const Cluster&); + Cluster& operator=(const Cluster&); + +public: + Segment* const m_pSegment; + +public: + static Cluster* Create( + Segment*, + long index, //index in segment + long long off); //offset relative to segment + //long long element_size); + + Cluster(); //EndOfStream + ~Cluster(); + + bool EOS() const; + + long long GetTimeCode() const; //absolute, but not scaled + long long GetTime() const; //absolute, and scaled (nanosecond units) + long long GetFirstTime() const; //time (ns) of first (earliest) block + long long GetLastTime() const; //time (ns) of last (latest) block + + long GetFirst(const BlockEntry*&) const; + long GetLast(const BlockEntry*&) const; + long GetNext(const BlockEntry* curr, const BlockEntry*& next) const; + + const BlockEntry* GetEntry(const Track*, long long ns = -1) const; + const BlockEntry* GetEntry( + const CuePoint&, + const CuePoint::TrackPosition&) const; + //const BlockEntry* GetMaxKey(const VideoTrack*) const; + +// static bool HasBlockEntries(const Segment*, long long); + + static long HasBlockEntries( + const Segment*, + long long idoff, + long long& pos, + long& size); + + long GetEntryCount() const; + + long Load(long long& pos, long& size) const; + + long Parse(long long& pos, long& size) const; + long GetEntry(long index, const mkvparser::BlockEntry*&) const; + +protected: + Cluster( + Segment*, + long index, + long long element_start); + //long long element_size); + +public: + const long long m_element_start; + long long GetPosition() const; //offset relative to segment + + long GetIndex() const; + long long GetElementSize() const; + //long long GetPayloadSize() const; + + //long long Unparsed() const; + +private: + long m_index; + mutable long long m_pos; + //mutable long long m_size; + mutable long long m_element_size; + mutable long long m_timecode; + mutable BlockEntry** m_entries; + mutable long m_entries_size; + mutable long m_entries_count; + + long ParseSimpleBlock(long long, long long&, long&); + long ParseBlockGroup(long long, long long&, long&); + + long CreateBlock(long long id, long long pos, long long size, + long long discard_padding); + long CreateBlockGroup(long long start_offset, long long size, + long long discard_padding); + long CreateSimpleBlock(long long, long long); + +}; + + +class Segment +{ + friend class Cues; + friend class Track; + friend class VideoTrack; + + Segment(const Segment&); + Segment& operator=(const Segment&); + +private: + Segment( + IMkvReader*, + long long elem_start, + //long long elem_size, + long long pos, + long long size); + +public: + IMkvReader* const m_pReader; + const long long m_element_start; + //const long long m_element_size; + const long long m_start; //posn of segment payload + const long long m_size; //size of segment payload + Cluster m_eos; //TODO: make private? + + static long long CreateInstance(IMkvReader*, long long, Segment*&); + ~Segment(); + + long Load(); //loads headers and all clusters + + //for incremental loading + //long long Unparsed() const; + bool DoneParsing() const; + long long ParseHeaders(); //stops when first cluster is found + //long FindNextCluster(long long& pos, long& size) const; + long LoadCluster(long long& pos, long& size); //load one cluster + long LoadCluster(); + + long ParseNext( + const Cluster* pCurr, + const Cluster*& pNext, + long long& pos, + long& size); + +#if 0 + //This pair parses one cluster, but only changes the state of the + //segment object when the cluster is actually added to the index. + long ParseCluster(long long& cluster_pos, long long& new_pos) const; + bool AddCluster(long long cluster_pos, long long new_pos); +#endif + + const SeekHead* GetSeekHead() const; + const Tracks* GetTracks() const; + const SegmentInfo* GetInfo() const; + const Cues* GetCues() const; + const Chapters* GetChapters() const; + + long long GetDuration() const; + + unsigned long GetCount() const; + const Cluster* GetFirst() const; + const Cluster* GetLast() const; + const Cluster* GetNext(const Cluster*); + + const Cluster* FindCluster(long long time_nanoseconds) const; + //const BlockEntry* Seek(long long time_nanoseconds, const Track*) const; + + const Cluster* FindOrPreloadCluster(long long pos); + + long ParseCues( + long long cues_off, //offset relative to start of segment + long long& parse_pos, + long& parse_len); + +private: + + long long m_pos; //absolute file posn; what has been consumed so far + Cluster* m_pUnknownSize; + + SeekHead* m_pSeekHead; + SegmentInfo* m_pInfo; + Tracks* m_pTracks; + Cues* m_pCues; + Chapters* m_pChapters; + Cluster** m_clusters; + long m_clusterCount; //number of entries for which m_index >= 0 + long m_clusterPreloadCount; //number of entries for which m_index < 0 + long m_clusterSize; //array size + + long DoLoadCluster(long long&, long&); + long DoLoadClusterUnknownSize(long long&, long&); + long DoParseNext(const Cluster*&, long long&, long&); + + void AppendCluster(Cluster*); + void PreloadCluster(Cluster*, ptrdiff_t); + + //void ParseSeekHead(long long pos, long long size); + //void ParseSeekEntry(long long pos, long long size); + //void ParseCues(long long); + + const BlockEntry* GetBlock( + const CuePoint&, + const CuePoint::TrackPosition&); + +}; + +} //end namespace mkvparser + +inline long mkvparser::Segment::LoadCluster() +{ + long long pos; + long size; + + return LoadCluster(pos, size); +} + +#endif //MKVPARSER_HPP diff --git a/mkvreader.cpp b/mkvreader.cpp index 7935e9d..12c5c20 100644 --- a/mkvreader.cpp +++ b/mkvreader.cpp @@ -1,128 +1,128 @@ -// Copyright (c) 2010 The WebM project authors. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. - -#include "mkvreader.hpp" - -#include - -namespace mkvparser -{ - -MkvReader::MkvReader() : - m_file(NULL) -{ -} - -MkvReader::~MkvReader() -{ - Close(); -} - -int MkvReader::Open(const char* fileName) -{ - if (fileName == NULL) - return -1; - - if (m_file) - return -1; - -#ifdef WIN32 - const errno_t e = fopen_s(&m_file, fileName, "rb"); - - if (e) - return -1; //error -#else - m_file = fopen(fileName, "rb"); - - if (m_file == NULL) - return -1; -#endif - -#ifdef WIN32 - int status = _fseeki64(m_file, 0L, SEEK_END); - - if (status) - return -1; //error - - m_length = _ftelli64(m_file); -#else - fseek(m_file, 0L, SEEK_END); - m_length = ftell(m_file); -#endif - assert(m_length >= 0); - -#ifdef WIN32 - status = _fseeki64(m_file, 0L, SEEK_SET); - - if (status) - return -1; //error -#else - fseek(m_file, 0L, SEEK_SET); -#endif - - return 0; -} - -void MkvReader::Close() -{ - if (m_file != NULL) - { - fclose(m_file); - m_file = NULL; - } -} - -int MkvReader::Length(long long* total, long long* available) -{ - if (m_file == NULL) - return -1; - - if (total) - *total = m_length; - - if (available) - *available = m_length; - - return 0; -} - -int MkvReader::Read(long long offset, long len, unsigned char* buffer) -{ - if (m_file == NULL) - return -1; - - if (offset < 0) - return -1; - - if (len < 0) - return -1; - - if (len == 0) - return 0; - - if (offset >= m_length) - return -1; - -#ifdef WIN32 - const int status = _fseeki64(m_file, offset, SEEK_SET); - - if (status) - return -1; //error -#else - fseek(m_file, offset, SEEK_SET); -#endif - - const size_t size = fread(buffer, 1, len, m_file); - - if (size < size_t(len)) - return -1; //error - - return 0; //success -} - -} //end namespace mkvparser +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvreader.hpp" + +#include + +namespace mkvparser +{ + +MkvReader::MkvReader() : + m_file(NULL) +{ +} + +MkvReader::~MkvReader() +{ + Close(); +} + +int MkvReader::Open(const char* fileName) +{ + if (fileName == NULL) + return -1; + + if (m_file) + return -1; + +#ifdef WIN32 + const errno_t e = fopen_s(&m_file, fileName, "rb"); + + if (e) + return -1; //error +#else + m_file = fopen(fileName, "rb"); + + if (m_file == NULL) + return -1; +#endif + +#ifdef WIN32 + int status = _fseeki64(m_file, 0L, SEEK_END); + + if (status) + return -1; //error + + m_length = _ftelli64(m_file); +#else + fseek(m_file, 0L, SEEK_END); + m_length = ftell(m_file); +#endif + assert(m_length >= 0); + +#ifdef WIN32 + status = _fseeki64(m_file, 0L, SEEK_SET); + + if (status) + return -1; //error +#else + fseek(m_file, 0L, SEEK_SET); +#endif + + return 0; +} + +void MkvReader::Close() +{ + if (m_file != NULL) + { + fclose(m_file); + m_file = NULL; + } +} + +int MkvReader::Length(long long* total, long long* available) +{ + if (m_file == NULL) + return -1; + + if (total) + *total = m_length; + + if (available) + *available = m_length; + + return 0; +} + +int MkvReader::Read(long long offset, long len, unsigned char* buffer) +{ + if (m_file == NULL) + return -1; + + if (offset < 0) + return -1; + + if (len < 0) + return -1; + + if (len == 0) + return 0; + + if (offset >= m_length) + return -1; + +#ifdef WIN32 + const int status = _fseeki64(m_file, offset, SEEK_SET); + + if (status) + return -1; //error +#else + fseek(m_file, offset, SEEK_SET); +#endif + + const size_t size = fread(buffer, 1, len, m_file); + + if (size < size_t(len)) + return -1; //error + + return 0; //success +} + +} //end namespace mkvparser diff --git a/mkvreader.hpp b/mkvreader.hpp index 32adf6e..adcc29f 100644 --- a/mkvreader.hpp +++ b/mkvreader.hpp @@ -1,38 +1,38 @@ -// Copyright (c) 2010 The WebM project authors. All Rights Reserved. -// -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file in the root of the source -// tree. An additional intellectual property rights grant can be found -// in the file PATENTS. All contributing project authors may -// be found in the AUTHORS file in the root of the source tree. - -#ifndef MKVREADER_HPP -#define MKVREADER_HPP - -#include "mkvparser.hpp" -#include - -namespace mkvparser -{ - -class MkvReader : public IMkvReader -{ - MkvReader(const MkvReader&); - MkvReader& operator=(const MkvReader&); -public: - MkvReader(); - virtual ~MkvReader(); - - int Open(const char*); - void Close(); - - virtual int Read(long long position, long length, unsigned char* buffer); - virtual int Length(long long* total, long long* available); -private: - long long m_length; - FILE* m_file; -}; - -} //end namespace mkvparser - -#endif //MKVREADER_HPP +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVREADER_HPP +#define MKVREADER_HPP + +#include "mkvparser.hpp" +#include + +namespace mkvparser +{ + +class MkvReader : public IMkvReader +{ + MkvReader(const MkvReader&); + MkvReader& operator=(const MkvReader&); +public: + MkvReader(); + virtual ~MkvReader(); + + int Open(const char*); + void Close(); + + virtual int Read(long long position, long length, unsigned char* buffer); + virtual int Length(long long* total, long long* available); +private: + long long m_length; + FILE* m_file; +}; + +} //end namespace mkvparser + +#endif //MKVREADER_HPP diff --git a/sample.cpp b/sample.cpp index 821089a..d798a45 100644 --- a/sample.cpp +++ b/sample.cpp @@ -1,293 +1,293 @@ -// 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. -// -// This sample application demonstrates how to use the Matroska parser -// library, which allows clients to handle a Matroska format file. - -#include - -#include "mkvreader.hpp" -#include "mkvparser.hpp" - -#ifdef _MSC_VER -// Disable MSVC warnings that suggest making code non-portable. -#pragma warning(disable:4996) -#endif - -static const wchar_t* utf8towcs(const char* str) { - if (str == NULL) return NULL; - - // TODO: this probably requires that the locale be - // configured somehow: - - const size_t size = mbstowcs(NULL, str, 0); - - if (size == 0) - return NULL; - - wchar_t* const val = new wchar_t[size + 1]; - - mbstowcs(val, str, size); - val[size] = L'\0'; - - return val; -} - -int main(int argc, char* argv[]) { - if (argc == 1) { - printf("\t\t\tMkv Parser Sample Application\n"); - printf("\t\t\tUsage: \n"); - printf("\t\t\t ./sample [input file] \n"); - printf("\t\t\t ./sample sample.mkv \n"); - return -1; - } - - using namespace mkvparser; - - MkvReader reader; - - if (reader.Open(argv[1])) { - printf("\n Filename is invalid or error while opening.\n"); - return -1; - } - - int maj, min, build, rev; - - GetVersion(maj, min, build, rev); - printf("\t\t libmkv verison: %d.%d.%d.%d\n", maj, min, build, rev); - - long long pos = 0; - - EBMLHeader ebmlHeader; - - ebmlHeader.Parse(&reader, pos); - - printf("\t\t\t EBML Header\n"); - printf("\t\tEBML Version\t\t: %lld\n", ebmlHeader.m_version); - printf("\t\tEBML MaxIDLength\t: %lld\n", ebmlHeader.m_maxIdLength); - printf("\t\tEBML MaxSizeLength\t: %lld\n", ebmlHeader.m_maxSizeLength); - printf("\t\tDoc Type\t\t: %s\n", ebmlHeader.m_docType); - printf("\t\tPos\t\t\t: %lld\n", pos); - - typedef mkvparser::Segment seg_t; - seg_t* pSegment_; - - long long ret = seg_t::CreateInstance(&reader, pos, pSegment_); - if (ret) { - printf("\n Segment::CreateInstance() failed."); - return -1; - } - - const std::auto_ptr pSegment(pSegment_); - - ret = pSegment->Load(); - if (ret < 0) { - printf("\n Segment::Load() failed."); - return -1; - } - - const SegmentInfo* const pSegmentInfo = pSegment->GetInfo(); - - const long long timeCodeScale = pSegmentInfo->GetTimeCodeScale(); - const long long duration_ns = pSegmentInfo->GetDuration(); - - const char* const pTitle_ = pSegmentInfo->GetTitleAsUTF8(); - const wchar_t* const pTitle = utf8towcs(pTitle_); - - const char* const pMuxingApp_ = pSegmentInfo->GetMuxingAppAsUTF8(); - const wchar_t* const pMuxingApp = utf8towcs(pMuxingApp_); - - const char* const pWritingApp_ = pSegmentInfo->GetWritingAppAsUTF8(); - const wchar_t* const pWritingApp = utf8towcs(pWritingApp_); - - printf("\n"); - printf("\t\t\t Segment Info\n"); - printf("\t\tTimeCodeScale\t\t: %lld \n", timeCodeScale); - printf("\t\tDuration\t\t: %lld\n", duration_ns); - - const double duration_sec = double(duration_ns) / 1000000000; - printf("\t\tDuration(secs)\t\t: %7.3lf\n", duration_sec); - - if (pTitle == NULL) - printf("\t\tTrack Name\t\t: NULL\n"); - else { - printf("\t\tTrack Name\t\t: %ls\n", pTitle); - delete[] pTitle; - } - - if (pMuxingApp == NULL) - printf("\t\tMuxing App\t\t: NULL\n"); - else { - printf("\t\tMuxing App\t\t: %ls\n", pMuxingApp); - delete[] pMuxingApp; - } - - if (pWritingApp == NULL) - printf("\t\tWriting App\t\t: NULL\n"); - else { - printf("\t\tWriting App\t\t: %ls\n", pWritingApp); - delete[] pWritingApp; - } - - // pos of segment payload - printf("\t\tPosition(Segment)\t: %lld\n", pSegment->m_start); - - // size of segment payload - printf("\t\tSize(Segment)\t\t: %lld\n", pSegment->m_size); - - const mkvparser::Tracks* pTracks = pSegment->GetTracks(); - - unsigned long i = 0; - const unsigned long j = pTracks->GetTracksCount(); - - printf("\n\t\t\t Track Info\n"); - - while (i != j) { - const Track* const pTrack = pTracks->GetTrackByIndex(i++); - - if (pTrack == NULL) continue; - - const long trackType = pTrack->GetType(); - const long trackNumber = pTrack->GetNumber(); - const unsigned long long trackUid = pTrack->GetUid(); - const wchar_t* const pTrackName = utf8towcs(pTrack->GetNameAsUTF8()); - - printf("\t\tTrack Type\t\t: %ld\n", trackType); - printf("\t\tTrack Number\t\t: %ld\n", trackNumber); - printf("\t\tTrack Uid\t\t: %lld\n", trackUid); - - if (pTrackName == NULL) - printf("\t\tTrack Name\t\t: NULL\n"); - else { - printf("\t\tTrack Name\t\t: %ls \n", pTrackName); - delete[] pTrackName; - } - - const char* const pCodecId = pTrack->GetCodecId(); - - if (pCodecId == NULL) - printf("\t\tCodec Id\t\t: NULL\n"); - else - printf("\t\tCodec Id\t\t: %s\n", pCodecId); - - const char* const pCodecName_ = pTrack->GetCodecNameAsUTF8(); - const wchar_t* const pCodecName = utf8towcs(pCodecName_); - - if (pCodecName == NULL) - printf("\t\tCodec Name\t\t: NULL\n"); - else { - printf("\t\tCodec Name\t\t: %ls\n", pCodecName); - delete[] pCodecName; - } - - if (trackType == mkvparser::Track::kVideo) { - const VideoTrack* const pVideoTrack = - static_cast(pTrack); - - const long long width = pVideoTrack->GetWidth(); - printf("\t\tVideo Width\t\t: %lld\n", width); - - const long long height = pVideoTrack->GetHeight(); - printf("\t\tVideo Height\t\t: %lld\n", height); - - const double rate = pVideoTrack->GetFrameRate(); - printf("\t\tVideo Rate\t\t: %f\n", rate); - } - - if (trackType == mkvparser::Track::kAudio) { - const AudioTrack* const pAudioTrack = - static_cast(pTrack); - - const long long channels = pAudioTrack->GetChannels(); - printf("\t\tAudio Channels\t\t: %lld\n", channels); - - const long long bitDepth = pAudioTrack->GetBitDepth(); - printf("\t\tAudio BitDepth\t\t: %lld\n", bitDepth); - - const double sampleRate = pAudioTrack->GetSamplingRate(); - printf("\t\tAddio Sample Rate\t: %.3f\n", sampleRate); - - const long long codecDelay = pAudioTrack->GetCodecDelay(); - printf("\t\tAudio Codec Delay\t\t: %lld\n", codecDelay); - - const long long seekPreRoll = pAudioTrack->GetSeekPreRoll(); - printf("\t\tAudio Seek Pre Roll\t\t: %lld\n", seekPreRoll); - } - } - - printf("\n\n\t\t\t Cluster Info\n"); - const unsigned long clusterCount = pSegment->GetCount(); - - printf("\t\tCluster Count\t: %ld\n\n", clusterCount); - - if (clusterCount == 0) { - printf("\t\tSegment has no clusters.\n"); - return -1; - } - - const mkvparser::Cluster* pCluster = pSegment->GetFirst(); - - while ((pCluster != NULL) && !pCluster->EOS()) { - const long long timeCode = pCluster->GetTimeCode(); - printf("\t\tCluster Time Code\t: %lld\n", timeCode); - - const long long time_ns = pCluster->GetTime(); - printf("\t\tCluster Time (ns)\t: %lld\n", time_ns); - - const BlockEntry* pBlockEntry; - - long status = pCluster->GetFirst(pBlockEntry); - - if (status < 0) // error - { - printf("\t\tError parsing first block of cluster\n"); - fflush(stdout); - return -1; - } - - while ((pBlockEntry != NULL) && !pBlockEntry->EOS()) { - const Block* const pBlock = pBlockEntry->GetBlock(); - const long long trackNum = pBlock->GetTrackNumber(); - const unsigned long tn = static_cast(trackNum); - const Track* const pTrack = pTracks->GetTrackByNumber(tn); - - if (pTrack == NULL) - printf("\t\t\tBlock\t\t:UNKNOWN TRACK TYPE\n"); - else { - const long long trackType = pTrack->GetType(); - const int frameCount = pBlock->GetFrameCount(); - const long long time_ns = pBlock->GetTime(pCluster); - const long long discard_padding = pBlock->GetDiscardPadding(); - - printf("\t\t\tBlock\t\t:%s,%s,%15lld,%lld\n", - (trackType == mkvparser::Track::kVideo) ? "V" : "A", - pBlock->IsKey() ? "I" : "P", time_ns, discard_padding); - - for (int i = 0; i < frameCount; ++i) { - const Block::Frame &theFrame = pBlock->GetFrame(i); - const long size = theFrame.len; - const long long offset = theFrame.pos; - printf("\t\t\t %15ld,%15llx\n", size, offset); - } - } - - status = pCluster->GetNext(pBlockEntry, pBlockEntry); - - if (status < 0) { - printf("\t\t\tError parsing next block of cluster\n"); - fflush(stdout); - return -1; - } - } - - pCluster = pSegment->GetNext(pCluster); - } - - fflush(stdout); - return 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. +// +// This sample application demonstrates how to use the Matroska parser +// library, which allows clients to handle a Matroska format file. + +#include + +#include "mkvreader.hpp" +#include "mkvparser.hpp" + +#ifdef _MSC_VER +// Disable MSVC warnings that suggest making code non-portable. +#pragma warning(disable:4996) +#endif + +static const wchar_t* utf8towcs(const char* str) { + if (str == NULL) return NULL; + + // TODO: this probably requires that the locale be + // configured somehow: + + const size_t size = mbstowcs(NULL, str, 0); + + if (size == 0) + return NULL; + + wchar_t* const val = new wchar_t[size + 1]; + + mbstowcs(val, str, size); + val[size] = L'\0'; + + return val; +} + +int main(int argc, char* argv[]) { + if (argc == 1) { + printf("\t\t\tMkv Parser Sample Application\n"); + printf("\t\t\tUsage: \n"); + printf("\t\t\t ./sample [input file] \n"); + printf("\t\t\t ./sample sample.mkv \n"); + return -1; + } + + using namespace mkvparser; + + MkvReader reader; + + if (reader.Open(argv[1])) { + printf("\n Filename is invalid or error while opening.\n"); + return -1; + } + + int maj, min, build, rev; + + GetVersion(maj, min, build, rev); + printf("\t\t libmkv verison: %d.%d.%d.%d\n", maj, min, build, rev); + + long long pos = 0; + + EBMLHeader ebmlHeader; + + ebmlHeader.Parse(&reader, pos); + + printf("\t\t\t EBML Header\n"); + printf("\t\tEBML Version\t\t: %lld\n", ebmlHeader.m_version); + printf("\t\tEBML MaxIDLength\t: %lld\n", ebmlHeader.m_maxIdLength); + printf("\t\tEBML MaxSizeLength\t: %lld\n", ebmlHeader.m_maxSizeLength); + printf("\t\tDoc Type\t\t: %s\n", ebmlHeader.m_docType); + printf("\t\tPos\t\t\t: %lld\n", pos); + + typedef mkvparser::Segment seg_t; + seg_t* pSegment_; + + long long ret = seg_t::CreateInstance(&reader, pos, pSegment_); + if (ret) { + printf("\n Segment::CreateInstance() failed."); + return -1; + } + + const std::auto_ptr pSegment(pSegment_); + + ret = pSegment->Load(); + if (ret < 0) { + printf("\n Segment::Load() failed."); + return -1; + } + + const SegmentInfo* const pSegmentInfo = pSegment->GetInfo(); + + const long long timeCodeScale = pSegmentInfo->GetTimeCodeScale(); + const long long duration_ns = pSegmentInfo->GetDuration(); + + const char* const pTitle_ = pSegmentInfo->GetTitleAsUTF8(); + const wchar_t* const pTitle = utf8towcs(pTitle_); + + const char* const pMuxingApp_ = pSegmentInfo->GetMuxingAppAsUTF8(); + const wchar_t* const pMuxingApp = utf8towcs(pMuxingApp_); + + const char* const pWritingApp_ = pSegmentInfo->GetWritingAppAsUTF8(); + const wchar_t* const pWritingApp = utf8towcs(pWritingApp_); + + printf("\n"); + printf("\t\t\t Segment Info\n"); + printf("\t\tTimeCodeScale\t\t: %lld \n", timeCodeScale); + printf("\t\tDuration\t\t: %lld\n", duration_ns); + + const double duration_sec = double(duration_ns) / 1000000000; + printf("\t\tDuration(secs)\t\t: %7.3lf\n", duration_sec); + + if (pTitle == NULL) + printf("\t\tTrack Name\t\t: NULL\n"); + else { + printf("\t\tTrack Name\t\t: %ls\n", pTitle); + delete[] pTitle; + } + + if (pMuxingApp == NULL) + printf("\t\tMuxing App\t\t: NULL\n"); + else { + printf("\t\tMuxing App\t\t: %ls\n", pMuxingApp); + delete[] pMuxingApp; + } + + if (pWritingApp == NULL) + printf("\t\tWriting App\t\t: NULL\n"); + else { + printf("\t\tWriting App\t\t: %ls\n", pWritingApp); + delete[] pWritingApp; + } + + // pos of segment payload + printf("\t\tPosition(Segment)\t: %lld\n", pSegment->m_start); + + // size of segment payload + printf("\t\tSize(Segment)\t\t: %lld\n", pSegment->m_size); + + const mkvparser::Tracks* pTracks = pSegment->GetTracks(); + + unsigned long i = 0; + const unsigned long j = pTracks->GetTracksCount(); + + printf("\n\t\t\t Track Info\n"); + + while (i != j) { + const Track* const pTrack = pTracks->GetTrackByIndex(i++); + + if (pTrack == NULL) continue; + + const long trackType = pTrack->GetType(); + const long trackNumber = pTrack->GetNumber(); + const unsigned long long trackUid = pTrack->GetUid(); + const wchar_t* const pTrackName = utf8towcs(pTrack->GetNameAsUTF8()); + + printf("\t\tTrack Type\t\t: %ld\n", trackType); + printf("\t\tTrack Number\t\t: %ld\n", trackNumber); + printf("\t\tTrack Uid\t\t: %lld\n", trackUid); + + if (pTrackName == NULL) + printf("\t\tTrack Name\t\t: NULL\n"); + else { + printf("\t\tTrack Name\t\t: %ls \n", pTrackName); + delete[] pTrackName; + } + + const char* const pCodecId = pTrack->GetCodecId(); + + if (pCodecId == NULL) + printf("\t\tCodec Id\t\t: NULL\n"); + else + printf("\t\tCodec Id\t\t: %s\n", pCodecId); + + const char* const pCodecName_ = pTrack->GetCodecNameAsUTF8(); + const wchar_t* const pCodecName = utf8towcs(pCodecName_); + + if (pCodecName == NULL) + printf("\t\tCodec Name\t\t: NULL\n"); + else { + printf("\t\tCodec Name\t\t: %ls\n", pCodecName); + delete[] pCodecName; + } + + if (trackType == mkvparser::Track::kVideo) { + const VideoTrack* const pVideoTrack = + static_cast(pTrack); + + const long long width = pVideoTrack->GetWidth(); + printf("\t\tVideo Width\t\t: %lld\n", width); + + const long long height = pVideoTrack->GetHeight(); + printf("\t\tVideo Height\t\t: %lld\n", height); + + const double rate = pVideoTrack->GetFrameRate(); + printf("\t\tVideo Rate\t\t: %f\n", rate); + } + + if (trackType == mkvparser::Track::kAudio) { + const AudioTrack* const pAudioTrack = + static_cast(pTrack); + + const long long channels = pAudioTrack->GetChannels(); + printf("\t\tAudio Channels\t\t: %lld\n", channels); + + const long long bitDepth = pAudioTrack->GetBitDepth(); + printf("\t\tAudio BitDepth\t\t: %lld\n", bitDepth); + + const double sampleRate = pAudioTrack->GetSamplingRate(); + printf("\t\tAddio Sample Rate\t: %.3f\n", sampleRate); + + const long long codecDelay = pAudioTrack->GetCodecDelay(); + printf("\t\tAudio Codec Delay\t\t: %lld\n", codecDelay); + + const long long seekPreRoll = pAudioTrack->GetSeekPreRoll(); + printf("\t\tAudio Seek Pre Roll\t\t: %lld\n", seekPreRoll); + } + } + + printf("\n\n\t\t\t Cluster Info\n"); + const unsigned long clusterCount = pSegment->GetCount(); + + printf("\t\tCluster Count\t: %ld\n\n", clusterCount); + + if (clusterCount == 0) { + printf("\t\tSegment has no clusters.\n"); + return -1; + } + + const mkvparser::Cluster* pCluster = pSegment->GetFirst(); + + while ((pCluster != NULL) && !pCluster->EOS()) { + const long long timeCode = pCluster->GetTimeCode(); + printf("\t\tCluster Time Code\t: %lld\n", timeCode); + + const long long time_ns = pCluster->GetTime(); + printf("\t\tCluster Time (ns)\t: %lld\n", time_ns); + + const BlockEntry* pBlockEntry; + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) // error + { + printf("\t\tError parsing first block of cluster\n"); + fflush(stdout); + return -1; + } + + while ((pBlockEntry != NULL) && !pBlockEntry->EOS()) { + const Block* const pBlock = pBlockEntry->GetBlock(); + const long long trackNum = pBlock->GetTrackNumber(); + const unsigned long tn = static_cast(trackNum); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + printf("\t\t\tBlock\t\t:UNKNOWN TRACK TYPE\n"); + else { + const long long trackType = pTrack->GetType(); + const int frameCount = pBlock->GetFrameCount(); + const long long time_ns = pBlock->GetTime(pCluster); + const long long discard_padding = pBlock->GetDiscardPadding(); + + printf("\t\t\tBlock\t\t:%s,%s,%15lld,%lld\n", + (trackType == mkvparser::Track::kVideo) ? "V" : "A", + pBlock->IsKey() ? "I" : "P", time_ns, discard_padding); + + for (int i = 0; i < frameCount; ++i) { + const Block::Frame &theFrame = pBlock->GetFrame(i); + const long size = theFrame.len; + const long long offset = theFrame.pos; + printf("\t\t\t %15ld,%15llx\n", size, offset); + } + } + + status = pCluster->GetNext(pBlockEntry, pBlockEntry); + + if (status < 0) { + printf("\t\t\tError parsing next block of cluster\n"); + fflush(stdout); + return -1; + } + } + + pCluster = pSegment->GetNext(pCluster); + } + + fflush(stdout); + return 0; } \ No newline at end of file