webm/mkvparser.cpp
Tom Finegan 986b64b8c0 mkvparser: Add error checking in Block::Parse.
Instead of relying solely on asserts, which compile away to
nothing in downstream projects using libwebm for parsing webm
input streams, actually check for the conditions being asserted,
and return errors when appropriate.

Change-Id: Id8b6352e8dda69782129dcea8f67203fd9c4f572
2015-08-14 10:45:46 -07:00

7592 lines
172 KiB
C++

// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include "mkvparser.hpp"
#include <cassert>
#include <cstring>
#include <new>
#include <climits>
#ifdef _MSC_VER
// Disable MSVC warnings that suggest making code non-portable.
#pragma warning(disable : 4996)
#endif
mkvparser::IMkvReader::~IMkvReader() {}
void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) {
major = 1;
minor = 0;
build = 0;
revision = 30;
}
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
}
// TODO(vigneshv): This function assumes that unsigned values never have their
// high bit set.
long long mkvparser::UnserializeUInt(IMkvReader* pReader, long long pos,
long long size) {
assert(pReader);
assert(pos >= 0);
if ((size <= 0) || (size > 8))
return E_FILE_FORMAT_INVALID;
long long result = 0;
for (long long i = 0; i < size; ++i) {
unsigned char b;
const long status = pReader->Read(pos, 1, &b);
if (status < 0)
return status;
result <<= 8;
result |= b;
++pos;
}
return result;
}
long mkvparser::UnserializeFloat(IMkvReader* pReader, long long pos,
long long size_, double& result) {
assert(pReader);
assert(pos >= 0);
if ((size_ != 4) && (size_ != 8))
return E_FILE_FORMAT_INVALID;
const long size = static_cast<long>(size_);
unsigned char buf[8];
const int status = pReader->Read(pos, size, buf);
if (status < 0) // error
return status;
if (size == 4) {
union {
float f;
unsigned long ff;
};
ff = 0;
for (int i = 0;;) {
ff |= buf[i];
if (++i >= 4)
break;
ff <<= 8;
}
result = f;
} else {
assert(size == 8);
union {
double d;
unsigned long long dd;
};
dd = 0;
for (int i = 0;;) {
dd |= buf[i];
if (++i >= 8)
break;
dd <<= 8;
}
result = d;
}
return 0;
}
long mkvparser::UnserializeInt(IMkvReader* pReader, long long pos,
long long size, long long& result) {
assert(pReader);
assert(pos >= 0);
assert(size > 0);
assert(size <= 8);
{
signed char b;
const long status = pReader->Read(pos, 1, (unsigned char*)&b);
if (status < 0)
return status;
result = b;
++pos;
}
for (long i = 1; i < size; ++i) {
unsigned char b;
const long status = pReader->Read(pos, 1, &b);
if (status < 0)
return status;
result <<= 8;
result |= b;
++pos;
}
return 0; // success
}
long mkvparser::UnserializeString(IMkvReader* pReader, long long pos,
long long size_, char*& str) {
delete[] str;
str = NULL;
if (size_ >= LONG_MAX) // we need (size+1) chars
return E_FILE_FORMAT_INVALID;
const long size = static_cast<long>(size_);
str = new (std::nothrow) char[size + 1];
if (str == NULL)
return -1;
unsigned char* const buf = reinterpret_cast<unsigned char*>(str);
const long status = pReader->Read(pos, size, buf);
if (status) {
delete[] str;
str = NULL;
return status;
}
str[size] = '\0';
return 0; // success
}
long mkvparser::ParseElementHeader(IMkvReader* pReader, long long& pos,
long long stop, long long& id,
long long& size) {
if ((stop >= 0) && (pos >= stop))
return E_FILE_FORMAT_INVALID;
long len;
id = ReadUInt(pReader, pos, len);
if (id < 0)
return E_FILE_FORMAT_INVALID;
pos += len; // consume id
if ((stop >= 0) && (pos >= stop))
return E_FILE_FORMAT_INVALID;
size = ReadUInt(pReader, pos, len);
if (size < 0)
return E_FILE_FORMAT_INVALID;
pos += len; // consume length of size
// pos now designates payload
if ((stop >= 0) && ((pos + size) > stop))
return E_FILE_FORMAT_INVALID;
return 0; // success
}
bool mkvparser::Match(IMkvReader* pReader, long long& pos, unsigned long id_,
long long& val) {
assert(pReader);
assert(pos >= 0);
long long total, available;
const long status = pReader->Length(&total, &available);
assert(status >= 0);
assert((total < 0) || (available <= total));
if (status < 0)
return false;
long len;
const long long id = ReadUInt(pReader, pos, len);
assert(id >= 0);
assert(len > 0);
assert(len <= 8);
assert((pos + len) <= available);
if ((unsigned long)id != id_)
return false;
pos += len; // consume id
const long long size = ReadUInt(pReader, pos, len);
assert(size >= 0);
assert(size <= 8);
assert(len > 0);
assert(len <= 8);
assert((pos + len) <= available);
pos += len; // consume length of size of payload
val = UnserializeUInt(pReader, pos, size);
assert(val >= 0);
pos += size; // consume size of payload
return true;
}
bool mkvparser::Match(IMkvReader* pReader, long long& pos, unsigned long id_,
unsigned char*& buf, size_t& buflen) {
assert(pReader);
assert(pos >= 0);
long long total, available;
long status = pReader->Length(&total, &available);
assert(status >= 0);
assert((total < 0) || (available <= total));
if (status < 0)
return false;
long len;
const long long id = ReadUInt(pReader, pos, len);
assert(id >= 0);
assert(len > 0);
assert(len <= 8);
assert((pos + len) <= available);
if ((unsigned long)id != id_)
return false;
pos += len; // consume id
const long long size_ = ReadUInt(pReader, pos, len);
assert(size_ >= 0);
assert(len > 0);
assert(len <= 8);
assert((pos + len) <= available);
pos += len; // consume length of size of payload
assert((pos + size_) <= available);
const long buflen_ = static_cast<long>(size_);
buf = new (std::nothrow) unsigned char[buflen_];
assert(buf); // TODO
status = pReader->Read(pos, buflen_, buf);
assert(status == 0); // TODO
buflen = buflen_;
pos += size_; // consume size of payload
return true;
}
namespace mkvparser {
EBMLHeader::EBMLHeader() : m_docType(NULL) { Init(); }
EBMLHeader::~EBMLHeader() { delete[] m_docType; }
void EBMLHeader::Init() {
m_version = 1;
m_readVersion = 1;
m_maxIdLength = 4;
m_maxSizeLength = 8;
if (m_docType) {
delete[] m_docType;
m_docType = NULL;
}
m_docTypeVersion = 1;
m_docTypeReadVersion = 1;
}
long long EBMLHeader::Parse(IMkvReader* pReader, long long& pos) {
assert(pReader);
long long total, available;
long status = pReader->Length(&total, &available);
if (status < 0) // error
return status;
pos = 0;
long long end = (available >= 1024) ? 1024 : available;
for (;;) {
unsigned char b = 0;
while (pos < end) {
status = pReader->Read(pos, 1, &b);
if (status < 0) // error
return status;
if (b == 0x1A)
break;
++pos;
}
if (b != 0x1A) {
if (pos >= 1024)
return E_FILE_FORMAT_INVALID; // don't bother looking anymore
if ((total >= 0) && ((total - available) < 5))
return E_FILE_FORMAT_INVALID;
return available + 5; // 5 = 4-byte ID + 1st byte of size
}
if ((total >= 0) && ((total - pos) < 5))
return E_FILE_FORMAT_INVALID;
if ((available - pos) < 5)
return pos + 5; // try again later
long len;
const long long result = ReadUInt(pReader, pos, len);
if (result < 0) // error
return result;
if (result == 0x0A45DFA3) { // EBML Header ID
pos += len; // consume ID
break;
}
++pos; // throw away just the 0x1A byte, and try again
}
// pos designates start of size field
// get length of size field
long len;
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return result;
if (result > 0) // need more data
return result;
assert(len > 0);
assert(len <= 8);
if ((total >= 0) && ((total - pos) < len))
return E_FILE_FORMAT_INVALID;
if ((available - pos) < len)
return pos + len; // try again later
// get the EBML header size
result = ReadUInt(pReader, pos, len);
if (result < 0) // error
return result;
pos += len; // consume size field
// pos now designates start of payload
if ((total >= 0) && ((total - pos) < result))
return E_FILE_FORMAT_INVALID;
if ((available - pos) < result)
return pos + result;
end = pos + result;
Init();
while (pos < end) {
long long id, size;
status = ParseElementHeader(pReader, pos, end, id, size);
if (status < 0) // error
return status;
if (size == 0) // weird
return E_FILE_FORMAT_INVALID;
if (id == 0x0286) { // version
m_version = UnserializeUInt(pReader, pos, size);
if (m_version <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x02F7) { // read version
m_readVersion = UnserializeUInt(pReader, pos, size);
if (m_readVersion <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x02F2) { // max id length
m_maxIdLength = UnserializeUInt(pReader, pos, size);
if (m_maxIdLength <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x02F3) { // max size length
m_maxSizeLength = UnserializeUInt(pReader, pos, size);
if (m_maxSizeLength <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x0282) { // doctype
if (m_docType)
return E_FILE_FORMAT_INVALID;
status = UnserializeString(pReader, pos, size, m_docType);
if (status) // error
return status;
} else if (id == 0x0287) { // doctype version
m_docTypeVersion = UnserializeUInt(pReader, pos, size);
if (m_docTypeVersion <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x0285) { // doctype read version
m_docTypeReadVersion = UnserializeUInt(pReader, pos, size);
if (m_docTypeReadVersion <= 0)
return E_FILE_FORMAT_INVALID;
}
pos += size;
}
assert(pos == end);
return 0;
}
Segment::Segment(IMkvReader* pReader, long long elem_start,
// long long elem_size,
long long start, long long size)
: m_pReader(pReader),
m_element_start(elem_start),
// m_element_size(elem_size),
m_start(start),
m_size(size),
m_pos(start),
m_pUnknownSize(0),
m_pSeekHead(NULL),
m_pInfo(NULL),
m_pTracks(NULL),
m_pCues(NULL),
m_pChapters(NULL),
m_pTags(NULL),
m_clusters(NULL),
m_clusterCount(0),
m_clusterPreloadCount(0),
m_clusterSize(0) {}
Segment::~Segment() {
const long count = m_clusterCount + m_clusterPreloadCount;
Cluster** i = m_clusters;
Cluster** j = m_clusters + count;
while (i != j) {
Cluster* const p = *i++;
assert(p);
delete p;
}
delete[] m_clusters;
delete m_pTracks;
delete m_pInfo;
delete m_pCues;
delete m_pChapters;
delete m_pTags;
delete m_pSeekHead;
}
long long Segment::CreateInstance(IMkvReader* pReader, long long pos,
Segment*& pSegment) {
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;
}
} else if (id == 0x0254C367) { // Tags ID
if (m_pTags == NULL) {
m_pTags = new (std::nothrow)
Tags(this, pos, size, element_start, element_size);
if (m_pTags == NULL)
return -1;
const long status = m_pTags->Parse();
if (status)
return status;
}
}
m_pos = pos + size; // consume payload
}
assert((segment_stop < 0) || (m_pos <= segment_stop));
if (m_pInfo == NULL) // TODO: liberalize this behavior
return E_FILE_FORMAT_INVALID;
if (m_pTracks == NULL)
return E_FILE_FORMAT_INVALID;
return 0; // success
}
long Segment::LoadCluster(long long& pos, long& len) {
for (;;) {
const long result = DoLoadCluster(pos, len);
if (result <= 1)
return result;
}
}
long Segment::DoLoadCluster(long long& pos, long& len) {
if (m_pos < 0)
return DoLoadClusterUnknownSize(pos, len);
long long total, avail;
long status = m_pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
long long cluster_off = -1; // offset relative to start of segment
long long cluster_size = -1; // size of cluster payload
for (;;) {
if ((total >= 0) && (m_pos >= total))
return 1; // no more clusters
if ((segment_stop >= 0) && (m_pos >= segment_stop))
return 1; // no more clusters
pos = m_pos;
// Read ID
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long idpos = pos;
const long long id = ReadUInt(m_pReader, idpos, len);
if (id < 0) // error (or underflow)
return static_cast<long>(id);
pos += len; // consume ID
// Read Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(m_pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume length of size of element
// pos now points to start of payload
if (size == 0) { // weird
m_pos = pos;
continue;
}
const long long unknown_size = (1LL << (7 * len)) - 1;
if ((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; // 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)
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)
}
long Segment::DoLoadClusterUnknownSize(long long& pos, long& len) {
assert(m_pos < 0);
assert(m_pUnknownSize);
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
}
void Segment::AppendCluster(Cluster* pCluster) {
assert(pCluster);
assert(pCluster->m_index >= 0);
const long count = m_clusterCount + m_clusterPreloadCount;
long& size = m_clusterSize;
assert(size >= count);
const long idx = pCluster->m_index;
assert(idx == m_clusterCount);
if (count >= size) {
const long n = (size <= 0) ? 2048 : 2 * size;
Cluster** const qq = new Cluster*[n];
Cluster** q = qq;
Cluster** p = m_clusters;
Cluster** const pp = p + count;
while (p != pp)
*q++ = *p++;
delete[] m_clusters;
m_clusters = qq;
size = n;
}
if (m_clusterPreloadCount > 0) {
assert(m_clusters);
Cluster** const p = m_clusters + m_clusterCount;
assert(*p);
assert((*p)->m_index < 0);
Cluster** q = p + m_clusterPreloadCount;
assert(q < (m_clusters + size));
for (;;) {
Cluster** const qq = q - 1;
assert((*qq)->m_index < 0);
*q = *qq;
q = qq;
if (q == p)
break;
}
}
m_clusters[idx] = pCluster;
++m_clusterCount;
}
void Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) {
assert(pCluster);
assert(pCluster->m_index < 0);
assert(idx >= m_clusterCount);
const long count = m_clusterCount + m_clusterPreloadCount;
long& size = m_clusterSize;
assert(size >= count);
if (count >= size) {
const long n = (size <= 0) ? 2048 : 2 * size;
Cluster** const qq = new Cluster*[n];
Cluster** q = qq;
Cluster** p = m_clusters;
Cluster** const pp = p + count;
while (p != pp)
*q++ = *p++;
delete[] m_clusters;
m_clusters = qq;
size = n;
}
assert(m_clusters);
Cluster** const p = m_clusters + idx;
Cluster** q = m_clusters + count;
assert(q >= p);
assert(q < (m_clusters + size));
while (q > p) {
Cluster** const qq = q - 1;
assert((*qq)->m_index < 0);
*q = *qq;
q = qq;
}
m_clusters[idx] = pCluster;
++m_clusterPreloadCount;
}
long Segment::Load() {
assert(m_clusters == NULL);
assert(m_clusterSize == 0);
assert(m_clusterCount == 0);
// assert(m_size >= 0);
// Outermost (level 0) segment object has been constructed,
// and pos designates start of payload. We need to find the
// inner (level 1) elements.
const long long header_status = ParseHeaders();
if (header_status < 0) // error
return static_cast<long>(header_status);
if (header_status > 0) // underflow
return E_BUFFER_NOT_FULL;
assert(m_pInfo);
assert(m_pTracks);
for (;;) {
const int status = LoadCluster();
if (status < 0) // error
return status;
if (status >= 1) // no more clusters
return 0;
}
}
SeekHead::SeekHead(Segment* pSegment, long long start, long long size_,
long long element_start, long long element_size)
: m_pSegment(pSegment),
m_start(start),
m_size(size_),
m_element_start(element_start),
m_element_size(element_size),
m_entries(0),
m_entry_count(0),
m_void_elements(0),
m_void_element_count(0) {}
SeekHead::~SeekHead() {
delete[] m_entries;
delete[] m_void_elements;
}
long SeekHead::Parse() {
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = m_start;
const long long stop = m_start + m_size;
// first count the seek head entries
int entry_count = 0;
int void_element_count = 0;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x0DBB) // SeekEntry ID
++entry_count;
else if (id == 0x6C) // Void ID
++void_element_count;
pos += size; // consume payload
assert(pos <= stop);
}
assert(pos == stop);
m_entries = new (std::nothrow) Entry[entry_count];
if (m_entries == NULL)
return -1;
m_void_elements = new (std::nothrow) VoidElement[void_element_count];
if (m_void_elements == NULL)
return -1;
// now parse the entries and void elements
Entry* pEntry = m_entries;
VoidElement* pVoidElement = m_void_elements;
pos = m_start;
while (pos < stop) {
const long long idpos = pos;
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x0DBB) { // SeekEntry ID
if (ParseEntry(pReader, pos, size, pEntry)) {
Entry& e = *pEntry++;
e.element_start = idpos;
e.element_size = (pos + size) - idpos;
}
} else if (id == 0x6C) { // Void ID
VoidElement& e = *pVoidElement++;
e.element_start = idpos;
e.element_size = (pos + size) - idpos;
}
pos += size; // consume payload
assert(pos <= stop);
}
assert(pos == stop);
ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries);
assert(count_ >= 0);
assert(count_ <= entry_count);
m_entry_count = static_cast<int>(count_);
count_ = ptrdiff_t(pVoidElement - m_void_elements);
assert(count_ >= 0);
assert(count_ <= void_element_count);
m_void_element_count = static_cast<int>(count_);
return 0;
}
int SeekHead::GetCount() const { return m_entry_count; }
const SeekHead::Entry* SeekHead::GetEntry(int idx) const {
if (idx < 0)
return 0;
if (idx >= m_entry_count)
return 0;
return m_entries + idx;
}
int SeekHead::GetVoidElementCount() const { return m_void_element_count; }
const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const {
if (idx < 0)
return 0;
if (idx >= m_void_element_count)
return 0;
return m_void_elements + idx;
}
long Segment::ParseCues(long long off, long long& pos, long& len) {
if (m_pCues)
return 0; // success
if (off < 0)
return -1;
long long total, avail;
const int status = m_pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
pos = m_start + off;
if ((total < 0) || (pos >= total))
return 1; // don't bother parsing cues
const long long element_start = pos;
const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // underflow (weird)
{
len = 1;
return E_BUFFER_NOT_FULL;
}
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long idpos = pos;
const long long id = ReadUInt(m_pReader, idpos, len);
if (id != 0x0C53BB6B) // Cues ID
return E_FILE_FORMAT_INVALID;
pos += len; // consume ID
assert((segment_stop < 0) || (pos <= segment_stop));
// Read Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // underflow (weird)
{
len = 1;
return E_BUFFER_NOT_FULL;
}
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(m_pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
if (size == 0) // weird, although technically not illegal
return 1; // done
pos += len; // consume length of size of element
assert((segment_stop < 0) || (pos <= segment_stop));
// Pos now points to start of payload
const long long element_stop = pos + size;
if ((segment_stop >= 0) && (element_stop > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((total >= 0) && (element_stop > total))
return 1; // don't bother parsing anymore
len = static_cast<long>(size);
if (element_stop > avail)
return E_BUFFER_NOT_FULL;
const long long element_size = element_stop - element_start;
m_pCues =
new (std::nothrow) Cues(this, pos, size, element_start, element_size);
assert(m_pCues); // TODO
return 0; // success
}
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;
}
Cues::Cues(Segment* pSegment, long long start_, long long size_,
long long element_start, long long element_size)
: m_pSegment(pSegment),
m_start(start_),
m_size(size_),
m_element_start(element_start),
m_element_size(element_size),
m_cue_points(NULL),
m_count(0),
m_preload_count(0),
m_pos(start_) {}
Cues::~Cues() {
const long n = m_count + m_preload_count;
CuePoint** p = m_cue_points;
CuePoint** const q = p + n;
while (p != q) {
CuePoint* const pCP = *p++;
assert(pCP);
delete pCP;
}
delete[] m_cue_points;
}
long Cues::GetCount() const {
if (m_cue_points == NULL)
return -1;
return m_count; // TODO: really ignore preload count?
}
bool Cues::DoneParsing() const {
const long long stop = m_start + m_size;
return (m_pos >= stop);
}
bool Cues::Init() const {
if (m_cue_points)
return true;
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);
if (id < 0 || (pos + len) > stop) {
return false;
}
pos += len; // consume ID
const long long size = ReadUInt(pReader, pos, len);
if (size < 0 || (pos + len > stop)) {
return false;
}
pos += len; // consume Size field
if (pos + size > stop) {
return false;
}
if (id == 0x3B) // CuePoint ID
PreloadCuePoint(cue_points_size, idpos);
pos += size; // skip payload
}
return true;
}
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
if (!Init()) {
m_pos = stop;
return false;
}
IMkvReader* const pReader = m_pSegment->m_pReader;
while (m_pos < stop) {
const long long idpos = m_pos;
long len;
const long long id = ReadUInt(pReader, m_pos, len);
assert(id >= 0); // TODO
assert((m_pos + len) <= stop);
m_pos += len; // consume ID
const long long size = ReadUInt(pReader, m_pos, len);
assert(size >= 0);
assert((m_pos + len) <= stop);
m_pos += len; // consume Size field
assert((m_pos + size) <= stop);
if (id != 0x3B) { // CuePoint ID
m_pos += size; // consume payload
assert(m_pos <= stop);
continue;
}
assert(m_preload_count > 0);
CuePoint* const pCP = m_cue_points[m_count];
assert(pCP);
assert((pCP->GetTimeCode() >= 0) || (-pCP->GetTimeCode() == idpos));
if (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos))
return false;
if (!pCP->Load(pReader)) {
m_pos = stop;
return false;
}
++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 (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);
// TODO: here and elsewhere, it's probably not correct to search
// for the cue point with this time, and then search for a matching
// track. In principle, the matching track could be on some earlier
// cue point, and with our current algorithm, we'd miss it. To make
// this bullet-proof, we'd need to create a secondary structure,
// with a list of cue points that apply to a track, and then search
// that track-based structure for a matching cue point.
pTP = pCP->Find(pTrack);
return (pTP != NULL);
}
const CuePoint* Cues::GetFirst() const {
if (m_cue_points == NULL)
return NULL;
if (m_count == 0)
return NULL;
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;
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);
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);
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);
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; }
bool CuePoint::Load(IMkvReader* pReader) {
// odbgstream os;
// os << "CuePoint::Load(begin): timecode=" << m_timecode << endl;
if (m_timecode >= 0) // already loaded
return true;
assert(m_track_positions == NULL);
assert(m_track_positions_count == 0);
long long pos_ = -m_timecode;
const long long element_start = pos_;
long long stop;
{
long len;
const long long id = ReadUInt(pReader, pos_, len);
assert(id == 0x3B); // CuePoint ID
if (id != 0x3B)
return false;
pos_ += len; // consume ID
const long long size = ReadUInt(pReader, pos_, len);
assert(size >= 0);
pos_ += len; // consume Size field
// pos_ now points to start of payload
stop = pos_ + size;
}
const long long element_size = stop - element_start;
long long pos = pos_;
// First count number of track positions
while (pos < stop) {
long len;
const long long id = ReadUInt(pReader, pos, len);
if ((id < 0) || (pos + len > stop)) {
return false;
}
pos += len; // consume ID
const long long size = ReadUInt(pReader, pos, len);
if ((size < 0) || (pos + len > stop)) {
return false;
}
pos += len; // consume Size field
if ((pos + size) > stop) {
return false;
}
if (id == 0x33) // CueTime ID
m_timecode = UnserializeUInt(pReader, pos, size);
else if (id == 0x37) // CueTrackPosition(s) ID
++m_track_positions_count;
pos += size; // consume payload
}
if (m_timecode < 0 || m_track_positions_count <= 0) {
return false;
}
// os << "CuePoint::Load(cont'd): idpos=" << idpos
// << " timecode=" << m_timecode
// << endl;
m_track_positions = new 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);
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++;
if (!tp.Parse(pReader, pos, size)) {
return false;
}
}
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;
return true;
}
bool CuePoint::TrackPosition::Parse(IMkvReader* pReader, long long start_,
long long size_) {
const long long stop = start_ + size_;
long long pos = start_;
m_track = -1;
m_pos = -1;
m_block = 1; // default
while (pos < stop) {
long len;
const long long id = ReadUInt(pReader, pos, len);
if ((id < 0) || ((pos + len) > stop)) {
return false;
}
pos += len; // consume ID
const long long size = ReadUInt(pReader, pos, len);
if ((size < 0) || ((pos + len) > stop)) {
return false;
}
pos += len; // consume Size field
if ((pos + size) > stop) {
return false;
}
if (id == 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
}
if ((m_pos < 0) || (m_track <= 0)) {
return false;
}
return true;
}
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;
}
bool Segment::DoneParsing() const {
if (m_size < 0) {
long long total, avail;
const int status = m_pReader->Length(&total, &avail);
if (status < 0) // error
return true; // must assume done
if (total < 0)
return false; // assume live stream
return (m_pos >= total);
}
const long long stop = m_start + m_size;
return (m_pos >= stop);
}
const Cluster* Segment::GetFirst() const {
if ((m_clusters == NULL) || (m_clusterCount <= 0))
return &m_eos;
Cluster* const pCluster = m_clusters[0];
assert(pCluster);
return pCluster;
}
const Cluster* Segment::GetLast() const {
if ((m_clusters == NULL) || (m_clusterCount <= 0))
return &m_eos;
const long idx = m_clusterCount - 1;
Cluster* const pCluster = m_clusters[idx];
assert(pCluster);
return pCluster;
}
unsigned long Segment::GetCount() const { return m_clusterCount; }
const Cluster* Segment::GetNext(const Cluster* pCurr) {
assert(pCurr);
assert(pCurr != &m_eos);
assert(m_clusters);
long idx = pCurr->m_index;
if (idx >= 0) {
assert(m_clusterCount > 0);
assert(idx < m_clusterCount);
assert(pCurr == m_clusters[idx]);
++idx;
if (idx >= m_clusterCount)
return &m_eos; // caller will LoadCluster as desired
Cluster* const pNext = m_clusters[idx];
assert(pNext);
assert(pNext->m_index >= 0);
assert(pNext->m_index == idx);
return pNext;
}
assert(m_clusterPreloadCount > 0);
long long pos = pCurr->m_element_start;
assert(m_size >= 0); // TODO
const long long stop = m_start + m_size; // end of segment
{
long len;
long long result = GetUIntLength(m_pReader, pos, len);
assert(result == 0);
assert((pos + len) <= stop); // TODO
if (result != 0)
return NULL;
const long long id = ReadUInt(m_pReader, pos, len);
assert(id == 0x0F43B675); // Cluster ID
if (id != 0x0F43B675)
return NULL;
pos += len; // consume ID
// Read Size
result = GetUIntLength(m_pReader, pos, len);
assert(result == 0); // TODO
assert((pos + len) <= stop); // TODO
const long long size = ReadUInt(m_pReader, pos, len);
assert(size > 0); // TODO
// assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
pos += len; // consume length of size of element
assert((pos + size) <= stop); // TODO
// Pos now points to start of payload
pos += size; // consume payload
}
long long off_next = 0;
while (pos < stop) {
long len;
long long result = GetUIntLength(m_pReader, pos, len);
assert(result == 0);
assert((pos + len) <= stop); // TODO
if (result != 0)
return NULL;
const long long idpos = pos; // pos of next (potential) cluster
const long long id = ReadUInt(m_pReader, idpos, len);
assert(id > 0); // TODO
pos += len; // consume ID
// Read Size
result = GetUIntLength(m_pReader, pos, len);
assert(result == 0); // TODO
assert((pos + len) <= stop); // TODO
const long long size = ReadUInt(m_pReader, pos, len);
assert(size >= 0); // TODO
pos += len; // consume length of size of element
assert((pos + size) <= stop); // TODO
// Pos now points to start of payload
if (size == 0) // weird
continue;
if (id == 0x0F43B675) { // Cluster ID
const long long off_next_ = idpos - m_start;
long long pos_;
long len_;
const long status = Cluster::HasBlockEntries(this, off_next_, pos_, len_);
assert(status >= 0);
if (status > 0) {
off_next = off_next_;
break;
}
}
pos += size; // consume payload
}
if (off_next <= 0)
return 0;
Cluster** const ii = m_clusters + m_clusterCount;
Cluster** i = ii;
Cluster** const jj = ii + m_clusterPreloadCount;
Cluster** j = jj;
while (i < j) {
// INVARIANT:
//[0, i) < pos_next
//[i, j) ?
//[j, jj) > pos_next
Cluster** const k = i + (j - i) / 2;
assert(k < jj);
Cluster* const pNext = *k;
assert(pNext);
assert(pNext->m_index < 0);
// const long long pos_ = pNext->m_pos;
// assert(pos_);
// pos = pos_ * ((pos_ < 0) ? -1 : 1);
pos = pNext->GetPosition();
if (pos < off_next)
i = k + 1;
else if (pos > off_next)
j = k;
else
return pNext;
}
assert(i == j);
Cluster* const pNext = Cluster::Create(this, -1, off_next);
assert(pNext);
const ptrdiff_t idx_next = i - m_clusters; // insertion position
PreloadCluster(pNext, idx_next);
assert(m_clusters);
assert(idx_next < m_clusterSize);
assert(m_clusters[idx_next] == pNext);
return pNext;
}
long Segment::ParseNext(const Cluster* pCurr, const Cluster*& pResult,
long long& pos, long& len) {
assert(pCurr);
assert(!pCurr->EOS());
assert(m_clusters);
pResult = 0;
if (pCurr->m_index >= 0) { // loaded (not merely preloaded)
assert(m_clusters[pCurr->m_index] == pCurr);
const long next_idx = pCurr->m_index + 1;
if (next_idx < m_clusterCount) {
pResult = m_clusters[next_idx];
return 0; // success
}
// curr cluster is last among loaded
const long result = LoadCluster(pos, len);
if (result < 0) // error or underflow
return result;
if (result > 0) // no more clusters
{
// pResult = &m_eos;
return 1;
}
pResult = GetLast();
return 0; // success
}
assert(m_pos > 0);
long long total, avail;
long status = m_pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
// interrogate curr cluster
pos = pCurr->m_element_start;
if (pCurr->m_element_size >= 0)
pos += pCurr->m_element_size;
else {
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(m_pReader, pos, len);
if (id != 0x0F43B675) // weird: not Cluster ID
return -1;
pos += len; // consume ID
// Read Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(m_pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume size field
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size) // TODO: should never happen
return E_FILE_FORMAT_INVALID; // TODO: resolve this
// assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
if ((segment_stop >= 0) && ((pos + size) > segment_stop))
return E_FILE_FORMAT_INVALID;
// Pos now points to start of payload
pos += size; // consume payload (that is, the current cluster)
assert((segment_stop < 0) || (pos <= segment_stop));
// By consuming the payload, we are assuming that the curr
// cluster isn't interesting. That is, we don't bother checking
// whether the payload of the curr cluster is less than what
// happens to be available (obtained via IMkvReader::Length).
// Presumably the caller has already dispensed with the current
// cluster, and really does want the next cluster.
}
// pos now points to just beyond the last fully-loaded cluster
for (;;) {
const long status = DoParseNext(pResult, pos, len);
if (status <= 1)
return status;
}
}
long Segment::DoParseNext(const Cluster*& pResult, long long& pos, long& len) {
long long total, avail;
long status = m_pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
// Parse next cluster. This is strictly a parsing activity.
// Creation of a new cluster object happens later, after the
// parsing is done.
long long off_next = 0;
long long cluster_size = -1;
for (;;) {
if ((total >= 0) && (pos >= total))
return 1; // EOF
if ((segment_stop >= 0) && (pos >= segment_stop))
return 1; // EOF
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long idpos = pos; // absolute
const long long idoff = pos - m_start; // relative
const long long id = ReadUInt(m_pReader, idpos, len); // absolute
if (id < 0) // error
return static_cast<long>(id);
if (id == 0) // weird
return -1; // generic error
pos += len; // consume ID
// Read Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(m_pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume length of size of element
// Pos now points to start of payload
if (size == 0) // weird
continue;
const long long unknown_size = (1LL << (7 * len)) - 1;
if ((segment_stop >= 0) && (size != unknown_size) &&
((pos + size) > segment_stop)) {
return E_FILE_FORMAT_INVALID;
}
if (id == 0x0C53BB6B) { // Cues ID
if (size == unknown_size)
return E_FILE_FORMAT_INVALID;
const long long element_stop = pos + size;
if ((segment_stop >= 0) && (element_stop > segment_stop))
return E_FILE_FORMAT_INVALID;
const long long element_start = idpos;
const long long element_size = element_stop - element_start;
if (m_pCues == NULL) {
m_pCues = new Cues(this, pos, size, element_start, element_size);
assert(m_pCues); // TODO
}
pos += size; // consume payload
assert((segment_stop < 0) || (pos <= segment_stop));
continue;
}
if (id != 0x0F43B675) { // not a Cluster ID
if (size == unknown_size)
return E_FILE_FORMAT_INVALID;
pos += size; // consume payload
assert((segment_stop < 0) || (pos <= segment_stop));
continue;
}
// We have a cluster.
off_next = idoff;
if (size != unknown_size)
cluster_size = size;
break;
}
assert(off_next > 0); // have cluster
// We have parsed the next cluster.
// We have not created a cluster object yet. What we need
// to do now is determine whether it has already be preloaded
//(in which case, an object for this cluster has already been
// created), and if not, create a new cluster object.
Cluster** const ii = m_clusters + m_clusterCount;
Cluster** i = ii;
Cluster** const jj = ii + m_clusterPreloadCount;
Cluster** j = jj;
while (i < j) {
// INVARIANT:
//[0, i) < pos_next
//[i, j) ?
//[j, jj) > pos_next
Cluster** const k = i + (j - i) / 2;
assert(k < jj);
const Cluster* const pNext = *k;
assert(pNext);
assert(pNext->m_index < 0);
pos = pNext->GetPosition();
assert(pos >= 0);
if (pos < off_next)
i = k + 1;
else if (pos > off_next)
j = k;
else {
pResult = pNext;
return 0; // success
}
}
assert(i == j);
long long pos_;
long len_;
status = Cluster::HasBlockEntries(this, off_next, pos_, len_);
if (status < 0) { // error or underflow
pos = pos_;
len = len_;
return status;
}
if (status > 0) { // means "found at least one block entry"
Cluster* const pNext = Cluster::Create(this,
-1, // preloaded
off_next);
// element_size);
assert(pNext);
const ptrdiff_t idx_next = i - m_clusters; // insertion position
PreloadCluster(pNext, idx_next);
assert(m_clusters);
assert(idx_next < m_clusterSize);
assert(m_clusters[idx_next] == pNext);
pResult = pNext;
return 0; // success
}
// status == 0 means "no block entries found"
if (cluster_size < 0) { // unknown size
const long long payload_pos = pos; // absolute pos of cluster payload
for (;;) { // determine cluster size
if ((total >= 0) && (pos >= total))
break;
if ((segment_stop >= 0) && (pos >= segment_stop))
break; // no more clusters
// Read ID
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long idpos = pos;
const long long id = ReadUInt(m_pReader, idpos, len);
if (id < 0) // error (or underflow)
return static_cast<long>(id);
// This is the distinguished set of ID's we use to determine
// that we have exhausted the sub-element's inside the cluster
// whose ID we parsed earlier.
if (id == 0x0F43B675) // Cluster ID
break;
if (id == 0x0C53BB6B) // Cues ID
break;
pos += len; // consume ID (of sub-element)
// Read Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(m_pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(m_pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume size field of element
// pos now points to start of sub-element's payload
if (size == 0) // weird
continue;
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size)
return E_FILE_FORMAT_INVALID; // not allowed for sub-elements
if ((segment_stop >= 0) && ((pos + size) > segment_stop)) // weird
return E_FILE_FORMAT_INVALID;
pos += size; // consume payload of sub-element
assert((segment_stop < 0) || (pos <= segment_stop));
} // determine cluster size
cluster_size = pos - payload_pos;
assert(cluster_size >= 0); // TODO: handle cluster_size = 0
pos = payload_pos; // reset and re-parse original cluster
}
pos += cluster_size; // consume payload
assert((segment_stop < 0) || (pos <= segment_stop));
return 2; // try to find a cluster that follows next
}
const Cluster* Segment::FindCluster(long long time_ns) const {
if ((m_clusters == NULL) || (m_clusterCount <= 0))
return &m_eos;
{
Cluster* const pCluster = m_clusters[0];
assert(pCluster);
assert(pCluster->m_index == 0);
if (time_ns <= pCluster->GetTime())
return pCluster;
}
// Binary search of cluster array
long i = 0;
long j = m_clusterCount;
while (i < j) {
// INVARIANT:
//[0, i) <= time_ns
//[i, j) ?
//[j, m_clusterCount) > time_ns
const long k = i + (j - i) / 2;
assert(k < m_clusterCount);
Cluster* const pCluster = m_clusters[k];
assert(pCluster);
assert(pCluster->m_index == k);
const long long t = pCluster->GetTime();
if (t <= time_ns)
i = k + 1;
else
j = k;
assert(i <= j);
}
assert(i == j);
assert(i > 0);
assert(i <= m_clusterCount);
const long k = i - 1;
Cluster* const pCluster = m_clusters[k];
assert(pCluster);
assert(pCluster->m_index == k);
assert(pCluster->GetTime() <= time_ns);
return pCluster;
}
const Tracks* Segment::GetTracks() const { return m_pTracks; }
const SegmentInfo* Segment::GetInfo() const { return m_pInfo; }
const Cues* Segment::GetCues() const { return m_pCues; }
const Chapters* Segment::GetChapters() const { return m_pChapters; }
const Tags* Segment::GetTags() const { return m_pTags; }
const SeekHead* Segment::GetSeekHead() const { return m_pSeekHead; }
long long Segment::GetDuration() const {
assert(m_pInfo);
return m_pInfo->GetDuration();
}
Chapters::Chapters(Segment* pSegment, long long payload_start,
long long payload_size, long long element_start,
long long element_size)
: m_pSegment(pSegment),
m_start(payload_start),
m_size(payload_size),
m_element_start(element_start),
m_element_size(element_size),
m_editions(NULL),
m_editions_size(0),
m_editions_count(0) {}
Chapters::~Chapters() {
while (m_editions_count > 0) {
Edition& e = m_editions[--m_editions_count];
e.Clear();
}
delete[] m_editions;
}
long Chapters::Parse() {
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = m_start; // payload start
const long long stop = pos + m_size; // payload stop
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (size == 0) // weird
continue;
if (id == 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
long long val;
status = UnserializeInt(pReader, pos, size, val);
if (status < 0) // error
return status;
m_uid = static_cast<unsigned long long>(val);
} else if (id == 0x11) { // TimeStart ID
const long long val = UnserializeUInt(pReader, pos, size);
if (val < 0) // error
return static_cast<long>(val);
m_start_timecode = val;
} else if (id == 0x12) { // TimeEnd ID
const long long val = UnserializeUInt(pReader, pos, size);
if (val < 0) // error
return static_cast<long>(val);
m_stop_timecode = val;
}
pos += size;
assert(pos <= stop);
}
assert(pos == stop);
return 0;
}
long long Chapters::Atom::GetTime(const Chapters* pChapters,
long long timecode) {
if (pChapters == NULL)
return -1;
Segment* const pSegment = pChapters->m_pSegment;
if (pSegment == NULL) // weird
return -1;
const SegmentInfo* const pInfo = pSegment->GetInfo();
if (pInfo == NULL)
return -1;
const long long timecode_scale = pInfo->GetTimeCodeScale();
if (timecode_scale < 1) // weird
return -1;
if (timecode < 0)
return -1;
const long long result = timecode_scale * timecode;
return result;
}
long Chapters::Atom::ParseDisplay(IMkvReader* pReader, long long pos,
long long size) {
if (!ExpandDisplaysArray())
return -1;
Display& d = m_displays[m_displays_count++];
d.Init();
return d.Parse(pReader, pos, size);
}
bool Chapters::Atom::ExpandDisplaysArray() {
if (m_displays_size > m_displays_count)
return true; // nothing else to do
const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size;
Display* const displays = new (std::nothrow) Display[size];
if (displays == NULL)
return false;
for (int idx = 0; idx < m_displays_count; ++idx) {
m_displays[idx].ShallowCopy(displays[idx]);
}
delete[] m_displays;
m_displays = displays;
m_displays_size = size;
return true;
}
Chapters::Display::Display() {}
Chapters::Display::~Display() {}
const char* Chapters::Display::GetString() const { return m_string; }
const char* Chapters::Display::GetLanguage() const { return m_language; }
const char* Chapters::Display::GetCountry() const { return m_country; }
void Chapters::Display::Init() {
m_string = NULL;
m_language = NULL;
m_country = NULL;
}
void Chapters::Display::ShallowCopy(Display& rhs) const {
rhs.m_string = m_string;
rhs.m_language = m_language;
rhs.m_country = m_country;
}
void Chapters::Display::Clear() {
delete[] m_string;
m_string = NULL;
delete[] m_language;
m_language = NULL;
delete[] m_country;
m_country = NULL;
}
long Chapters::Display::Parse(IMkvReader* pReader, long long pos,
long long size) {
const long long stop = pos + size;
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (size == 0) // weird
continue;
if (id == 0x05) { // ChapterString ID
status = UnserializeString(pReader, pos, size, m_string);
if (status)
return status;
} else if (id == 0x037C) { // ChapterLanguage ID
status = UnserializeString(pReader, pos, size, m_language);
if (status)
return status;
} else if (id == 0x037E) { // ChapterCountry ID
status = UnserializeString(pReader, pos, size, m_country);
if (status)
return status;
}
pos += size;
assert(pos <= stop);
}
assert(pos == stop);
return 0;
}
Tags::Tags(Segment* pSegment, long long payload_start, long long payload_size,
long long element_start, long long element_size)
: m_pSegment(pSegment),
m_start(payload_start),
m_size(payload_size),
m_element_start(element_start),
m_element_size(element_size),
m_tags(NULL),
m_tags_size(0),
m_tags_count(0) {}
Tags::~Tags() {
while (m_tags_count > 0) {
Tag& t = m_tags[--m_tags_count];
t.Clear();
}
delete[] m_tags;
}
long Tags::Parse() {
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = m_start; // payload start
const long long stop = pos + m_size; // payload stop
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0)
return status;
if (size == 0) // 0 length tag, read another
continue;
if (id == 0x3373) { // Tag ID
status = ParseTag(pos, size);
if (status < 0)
return status;
}
pos += size;
assert(pos <= stop);
if (pos > stop)
return -1;
}
assert(pos == stop);
if (pos != stop)
return -1;
return 0;
}
int Tags::GetTagCount() const { return m_tags_count; }
const Tags::Tag* Tags::GetTag(int idx) const {
if (idx < 0)
return NULL;
if (idx >= m_tags_count)
return NULL;
return m_tags + idx;
}
bool Tags::ExpandTagsArray() {
if (m_tags_size > m_tags_count)
return true; // nothing else to do
const int size = (m_tags_size == 0) ? 1 : 2 * m_tags_size;
Tag* const tags = new (std::nothrow) Tag[size];
if (tags == NULL)
return false;
for (int idx = 0; idx < m_tags_count; ++idx) {
m_tags[idx].ShallowCopy(tags[idx]);
}
delete[] m_tags;
m_tags = tags;
m_tags_size = size;
return true;
}
long Tags::ParseTag(long long pos, long long size) {
if (!ExpandTagsArray())
return -1;
Tag& t = m_tags[m_tags_count++];
t.Init();
return t.Parse(m_pSegment->m_pReader, pos, size);
}
Tags::Tag::Tag() {}
Tags::Tag::~Tag() {}
int Tags::Tag::GetSimpleTagCount() const { return m_simple_tags_count; }
const Tags::SimpleTag* Tags::Tag::GetSimpleTag(int index) const {
if (index < 0)
return NULL;
if (index >= m_simple_tags_count)
return NULL;
return m_simple_tags + index;
}
void Tags::Tag::Init() {
m_simple_tags = NULL;
m_simple_tags_size = 0;
m_simple_tags_count = 0;
}
void Tags::Tag::ShallowCopy(Tag& rhs) const {
rhs.m_simple_tags = m_simple_tags;
rhs.m_simple_tags_size = m_simple_tags_size;
rhs.m_simple_tags_count = m_simple_tags_count;
}
void Tags::Tag::Clear() {
while (m_simple_tags_count > 0) {
SimpleTag& d = m_simple_tags[--m_simple_tags_count];
d.Clear();
}
delete[] m_simple_tags;
m_simple_tags = NULL;
m_simple_tags_size = 0;
}
long Tags::Tag::Parse(IMkvReader* pReader, long long pos, long long size) {
const long long stop = pos + size;
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0)
return status;
if (size == 0) // 0 length tag, read another
continue;
if (id == 0x27C8) { // SimpleTag ID
status = ParseSimpleTag(pReader, pos, size);
if (status < 0)
return status;
}
pos += size;
assert(pos <= stop);
if (pos > stop)
return -1;
}
assert(pos == stop);
if (pos != stop)
return -1;
return 0;
}
long Tags::Tag::ParseSimpleTag(IMkvReader* pReader, long long pos,
long long size) {
if (!ExpandSimpleTagsArray())
return -1;
SimpleTag& st = m_simple_tags[m_simple_tags_count++];
st.Init();
return st.Parse(pReader, pos, size);
}
bool Tags::Tag::ExpandSimpleTagsArray() {
if (m_simple_tags_size > m_simple_tags_count)
return true; // nothing else to do
const int size = (m_simple_tags_size == 0) ? 1 : 2 * m_simple_tags_size;
SimpleTag* const displays = new (std::nothrow) SimpleTag[size];
if (displays == NULL)
return false;
for (int idx = 0; idx < m_simple_tags_count; ++idx) {
m_simple_tags[idx].ShallowCopy(displays[idx]);
}
delete[] m_simple_tags;
m_simple_tags = displays;
m_simple_tags_size = size;
return true;
}
Tags::SimpleTag::SimpleTag() {}
Tags::SimpleTag::~SimpleTag() {}
const char* Tags::SimpleTag::GetTagName() const { return m_tag_name; }
const char* Tags::SimpleTag::GetTagString() const { return m_tag_string; }
void Tags::SimpleTag::Init() {
m_tag_name = NULL;
m_tag_string = NULL;
}
void Tags::SimpleTag::ShallowCopy(SimpleTag& rhs) const {
rhs.m_tag_name = m_tag_name;
rhs.m_tag_string = m_tag_string;
}
void Tags::SimpleTag::Clear() {
delete[] m_tag_name;
m_tag_name = NULL;
delete[] m_tag_string;
m_tag_string = NULL;
}
long Tags::SimpleTag::Parse(IMkvReader* pReader, long long pos,
long long size) {
const long long stop = pos + size;
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (size == 0) // weird
continue;
if (id == 0x5A3) { // TagName ID
status = UnserializeString(pReader, pos, size, m_tag_name);
if (status)
return status;
} else if (id == 0x487) { // TagString ID
status = UnserializeString(pReader, pos, size, m_tag_string);
if (status)
return status;
}
pos += size;
assert(pos <= stop);
if (pos > stop)
return -1;
}
assert(pos == stop);
if (pos != stop)
return -1;
return 0;
}
SegmentInfo::SegmentInfo(Segment* pSegment, long long start, long long size_,
long long element_start, long long element_size)
: m_pSegment(pSegment),
m_start(start),
m_size(size_),
m_element_start(element_start),
m_element_size(element_size),
m_pMuxingAppAsUTF8(NULL),
m_pWritingAppAsUTF8(NULL),
m_pTitleAsUTF8(NULL) {}
SegmentInfo::~SegmentInfo() {
delete[] m_pMuxingAppAsUTF8;
m_pMuxingAppAsUTF8 = NULL;
delete[] m_pWritingAppAsUTF8;
m_pWritingAppAsUTF8 = NULL;
delete[] m_pTitleAsUTF8;
m_pTitleAsUTF8 = NULL;
}
long SegmentInfo::Parse() {
assert(m_pMuxingAppAsUTF8 == NULL);
assert(m_pWritingAppAsUTF8 == NULL);
assert(m_pTitleAsUTF8 == NULL);
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = m_start;
const long long stop = m_start + m_size;
m_timecodeScale = 1000000;
m_duration = -1;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x0AD7B1) { // Timecode Scale
m_timecodeScale = UnserializeUInt(pReader, pos, size);
if (m_timecodeScale <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x0489) { // Segment duration
const long status = UnserializeFloat(pReader, pos, size, m_duration);
if (status < 0)
return status;
if (m_duration < 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x0D80) { // MuxingApp
const long status =
UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8);
if (status)
return status;
} else if (id == 0x1741) { // WritingApp
const long status =
UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8);
if (status)
return status;
} else if (id == 0x3BA9) { // Title
const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8);
if (status)
return status;
}
pos += size;
assert(pos <= stop);
}
assert(pos == stop);
return 0;
}
long long SegmentInfo::GetTimeCodeScale() const { return m_timecodeScale; }
long long SegmentInfo::GetDuration() const {
if (m_duration < 0)
return -1;
assert(m_timecodeScale >= 1);
const double dd = double(m_duration) * double(m_timecodeScale);
const long long d = static_cast<long long>(dd);
return d;
}
const char* SegmentInfo::GetMuxingAppAsUTF8() const {
return m_pMuxingAppAsUTF8;
}
const char* SegmentInfo::GetWritingAppAsUTF8() const {
return m_pWritingAppAsUTF8;
}
const char* SegmentInfo::GetTitleAsUTF8() const { return m_pTitleAsUTF8; }
///////////////////////////////////////////////////////////////
// ContentEncoding element
ContentEncoding::ContentCompression::ContentCompression()
: algo(0), settings(NULL), settings_len(0) {}
ContentEncoding::ContentCompression::~ContentCompression() {
delete[] settings;
}
ContentEncoding::ContentEncryption::ContentEncryption()
: algo(0),
key_id(NULL),
key_id_len(0),
signature(NULL),
signature_len(0),
sig_key_id(NULL),
sig_key_id_len(0),
sig_algo(0),
sig_hash_algo(0) {}
ContentEncoding::ContentEncryption::~ContentEncryption() {
delete[] key_id;
delete[] signature;
delete[] sig_key_id;
}
ContentEncoding::ContentEncoding()
: compression_entries_(NULL),
compression_entries_end_(NULL),
encryption_entries_(NULL),
encryption_entries_end_(NULL),
encoding_order_(0),
encoding_scope_(1),
encoding_type_(0) {}
ContentEncoding::~ContentEncoding() {
ContentCompression** comp_i = compression_entries_;
ContentCompression** const comp_j = compression_entries_end_;
while (comp_i != comp_j) {
ContentCompression* const comp = *comp_i++;
delete comp;
}
delete[] compression_entries_;
ContentEncryption** enc_i = encryption_entries_;
ContentEncryption** const enc_j = encryption_entries_end_;
while (enc_i != enc_j) {
ContentEncryption* const enc = *enc_i++;
delete enc;
}
delete[] encryption_entries_;
}
const ContentEncoding::ContentCompression*
ContentEncoding::GetCompressionByIndex(unsigned long idx) const {
const ptrdiff_t count = compression_entries_end_ - compression_entries_;
assert(count >= 0);
if (idx >= static_cast<unsigned long>(count))
return NULL;
return compression_entries_[idx];
}
unsigned long ContentEncoding::GetCompressionCount() const {
const ptrdiff_t count = compression_entries_end_ - compression_entries_;
assert(count >= 0);
return static_cast<unsigned long>(count);
}
const ContentEncoding::ContentEncryption* ContentEncoding::GetEncryptionByIndex(
unsigned long idx) const {
const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
assert(count >= 0);
if (idx >= static_cast<unsigned long>(count))
return NULL;
return encryption_entries_[idx];
}
unsigned long ContentEncoding::GetEncryptionCount() const {
const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
assert(count >= 0);
return static_cast<unsigned long>(count);
}
long ContentEncoding::ParseContentEncAESSettingsEntry(
long long start, long long size, IMkvReader* pReader,
ContentEncAESSettings* aes) {
assert(pReader);
assert(aes);
long long pos = start;
const long long stop = start + size;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x7E8) {
// AESSettingsCipherMode
aes->cipher_mode = UnserializeUInt(pReader, pos, size);
if (aes->cipher_mode != 1)
return E_FILE_FORMAT_INVALID;
}
pos += size; // consume payload
assert(pos <= stop);
}
return 0;
}
long ContentEncoding::ParseContentEncodingEntry(long long start, long long size,
IMkvReader* pReader) {
assert(pReader);
long long pos = start;
const long long stop = start + size;
// Count ContentCompression and ContentEncryption elements.
int compression_count = 0;
int encryption_count = 0;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x1034) // ContentCompression ID
++compression_count;
if (id == 0x1035) // ContentEncryption ID
++encryption_count;
pos += size; // consume payload
assert(pos <= stop);
}
if (compression_count <= 0 && encryption_count <= 0)
return -1;
if (compression_count > 0) {
compression_entries_ =
new (std::nothrow) ContentCompression*[compression_count];
if (!compression_entries_)
return -1;
compression_entries_end_ = compression_entries_;
}
if (encryption_count > 0) {
encryption_entries_ =
new (std::nothrow) ContentEncryption*[encryption_count];
if (!encryption_entries_) {
delete[] compression_entries_;
return -1;
}
encryption_entries_end_ = encryption_entries_;
}
pos = start;
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x1031) {
// ContentEncodingOrder
encoding_order_ = UnserializeUInt(pReader, pos, size);
} else if (id == 0x1032) {
// ContentEncodingScope
encoding_scope_ = UnserializeUInt(pReader, pos, size);
if (encoding_scope_ < 1)
return -1;
} else if (id == 0x1033) {
// ContentEncodingType
encoding_type_ = UnserializeUInt(pReader, pos, size);
} else if (id == 0x1034) {
// ContentCompression ID
ContentCompression* const compression =
new (std::nothrow) ContentCompression();
if (!compression)
return -1;
status = ParseCompressionEntry(pos, size, pReader, compression);
if (status) {
delete compression;
return status;
}
*compression_entries_end_++ = compression;
} else if (id == 0x1035) {
// ContentEncryption ID
ContentEncryption* const encryption =
new (std::nothrow) ContentEncryption();
if (!encryption)
return -1;
status = ParseEncryptionEntry(pos, size, pReader, encryption);
if (status) {
delete encryption;
return status;
}
*encryption_entries_end_++ = encryption;
}
pos += size; // consume payload
assert(pos <= stop);
}
assert(pos == stop);
return 0;
}
long ContentEncoding::ParseCompressionEntry(long long start, long long size,
IMkvReader* pReader,
ContentCompression* compression) {
assert(pReader);
assert(compression);
long long pos = start;
const long long stop = start + size;
bool valid = false;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x254) {
// ContentCompAlgo
long long algo = UnserializeUInt(pReader, pos, size);
if (algo < 0)
return E_FILE_FORMAT_INVALID;
compression->algo = algo;
valid = true;
} else if (id == 0x255) {
// ContentCompSettings
if (size <= 0)
return E_FILE_FORMAT_INVALID;
const size_t buflen = static_cast<size_t>(size);
typedef unsigned char* buf_t;
const buf_t buf = new (std::nothrow) unsigned char[buflen];
if (buf == NULL)
return -1;
const int read_status =
pReader->Read(pos, static_cast<long>(buflen), buf);
if (read_status) {
delete[] buf;
return status;
}
compression->settings = buf;
compression->settings_len = buflen;
}
pos += size; // consume payload
assert(pos <= stop);
}
// ContentCompAlgo is mandatory
if (!valid)
return E_FILE_FORMAT_INVALID;
return 0;
}
long ContentEncoding::ParseEncryptionEntry(long long start, long long size,
IMkvReader* pReader,
ContentEncryption* encryption) {
assert(pReader);
assert(encryption);
long long pos = start;
const long long stop = start + size;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x7E1) {
// ContentEncAlgo
encryption->algo = UnserializeUInt(pReader, pos, size);
if (encryption->algo != 5)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x7E2) {
// ContentEncKeyID
delete[] encryption->key_id;
encryption->key_id = NULL;
encryption->key_id_len = 0;
if (size <= 0)
return E_FILE_FORMAT_INVALID;
const size_t buflen = static_cast<size_t>(size);
typedef unsigned char* buf_t;
const buf_t buf = new (std::nothrow) unsigned char[buflen];
if (buf == NULL)
return -1;
const int read_status =
pReader->Read(pos, static_cast<long>(buflen), buf);
if (read_status) {
delete[] buf;
return status;
}
encryption->key_id = buf;
encryption->key_id_len = buflen;
} else if (id == 0x7E3) {
// ContentSignature
delete[] encryption->signature;
encryption->signature = NULL;
encryption->signature_len = 0;
if (size <= 0)
return E_FILE_FORMAT_INVALID;
const size_t buflen = static_cast<size_t>(size);
typedef unsigned char* buf_t;
const buf_t buf = new (std::nothrow) unsigned char[buflen];
if (buf == NULL)
return -1;
const int read_status =
pReader->Read(pos, static_cast<long>(buflen), buf);
if (read_status) {
delete[] buf;
return status;
}
encryption->signature = buf;
encryption->signature_len = buflen;
} else if (id == 0x7E4) {
// ContentSigKeyID
delete[] encryption->sig_key_id;
encryption->sig_key_id = NULL;
encryption->sig_key_id_len = 0;
if (size <= 0)
return E_FILE_FORMAT_INVALID;
const size_t buflen = static_cast<size_t>(size);
typedef unsigned char* buf_t;
const buf_t buf = new (std::nothrow) unsigned char[buflen];
if (buf == NULL)
return -1;
const int read_status =
pReader->Read(pos, static_cast<long>(buflen), buf);
if (read_status) {
delete[] buf;
return status;
}
encryption->sig_key_id = buf;
encryption->sig_key_id_len = buflen;
} else if (id == 0x7E5) {
// ContentSigAlgo
encryption->sig_algo = UnserializeUInt(pReader, pos, size);
} else if (id == 0x7E6) {
// ContentSigHashAlgo
encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size);
} else if (id == 0x7E7) {
// ContentEncAESSettings
const long status = ParseContentEncAESSettingsEntry(
pos, size, pReader, &encryption->aes_settings);
if (status)
return status;
}
pos += size; // consume payload
assert(pos <= stop);
}
return 0;
}
Track::Track(Segment* pSegment, long long element_start, long long element_size)
: m_pSegment(pSegment),
m_element_start(element_start),
m_element_size(element_size),
content_encoding_entries_(NULL),
content_encoding_entries_end_(NULL) {}
Track::~Track() {
Info& info = const_cast<Info&>(m_info);
info.Clear();
ContentEncoding** i = content_encoding_entries_;
ContentEncoding** const j = content_encoding_entries_end_;
while (i != j) {
ContentEncoding* const encoding = *i++;
delete encoding;
}
delete[] content_encoding_entries_;
}
long Track::Create(Segment* pSegment, const Info& info, long long element_start,
long long element_size, Track*& pResult) {
if (pResult)
return -1;
Track* const pTrack =
new (std::nothrow) Track(pSegment, element_start, element_size);
if (pTrack == NULL)
return -1; // generic error
const int status = info.Copy(pTrack->m_info);
if (status) { // error
delete pTrack;
return status;
}
pResult = pTrack;
return 0; // success
}
Track::Info::Info()
: uid(0),
defaultDuration(0),
codecDelay(0),
seekPreRoll(0),
nameAsUTF8(NULL),
language(NULL),
codecId(NULL),
codecNameAsUTF8(NULL),
codecPrivate(NULL),
codecPrivateSize(0),
lacing(false) {}
Track::Info::~Info() { Clear(); }
void Track::Info::Clear() {
delete[] nameAsUTF8;
nameAsUTF8 = NULL;
delete[] language;
language = NULL;
delete[] codecId;
codecId = NULL;
delete[] codecPrivate;
codecPrivate = NULL;
codecPrivateSize = 0;
delete[] codecNameAsUTF8;
codecNameAsUTF8 = NULL;
}
int Track::Info::CopyStr(char* Info::*str, Info& dst_) const {
if (str == static_cast<char * Info::*>(NULL))
return -1;
char*& dst = dst_.*str;
if (dst) // should be NULL already
return -1;
const char* const src = this->*str;
if (src == NULL)
return 0;
const size_t len = strlen(src);
dst = new (std::nothrow) char[len + 1];
if (dst == NULL)
return -1;
strcpy(dst, src);
return 0;
}
int Track::Info::Copy(Info& dst) const {
if (&dst == this)
return 0;
dst.type = type;
dst.number = number;
dst.defaultDuration = defaultDuration;
dst.codecDelay = codecDelay;
dst.seekPreRoll = seekPreRoll;
dst.uid = uid;
dst.lacing = lacing;
dst.settings = settings;
// We now copy the string member variables from src to dst.
// This involves memory allocation so in principle the operation
// can fail (indeed, that's why we have Info::Copy), so we must
// report this to the caller. An error return from this function
// therefore implies that the copy was only partially successful.
if (int status = CopyStr(&Info::nameAsUTF8, dst))
return status;
if (int status = CopyStr(&Info::language, dst))
return status;
if (int status = CopyStr(&Info::codecId, dst))
return status;
if (int status = CopyStr(&Info::codecNameAsUTF8, dst))
return status;
if (codecPrivateSize > 0) {
if (codecPrivate == NULL)
return -1;
if (dst.codecPrivate)
return -1;
if (dst.codecPrivateSize != 0)
return -1;
dst.codecPrivate = new (std::nothrow) unsigned char[codecPrivateSize];
if (dst.codecPrivate == NULL)
return -1;
memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize);
dst.codecPrivateSize = codecPrivateSize;
}
return 0;
}
const BlockEntry* Track::GetEOS() const { return &m_eos; }
long Track::GetType() const { return m_info.type; }
long Track::GetNumber() const { return m_info.number; }
unsigned long long Track::GetUid() const { return m_info.uid; }
const char* Track::GetNameAsUTF8() const { return m_info.nameAsUTF8; }
const char* Track::GetLanguage() const { return m_info.language; }
const char* Track::GetCodecNameAsUTF8() const { return m_info.codecNameAsUTF8; }
const char* Track::GetCodecId() const { return m_info.codecId; }
const unsigned char* Track::GetCodecPrivate(size_t& size) const {
size = m_info.codecPrivateSize;
return m_info.codecPrivate;
}
bool Track::GetLacing() const { return m_info.lacing; }
unsigned long long Track::GetDefaultDuration() const {
return m_info.defaultDuration;
}
unsigned long long Track::GetCodecDelay() const { return m_info.codecDelay; }
unsigned long long Track::GetSeekPreRoll() const { return m_info.seekPreRoll; }
long Track::GetFirst(const BlockEntry*& pBlockEntry) const {
const Cluster* pCluster = m_pSegment->GetFirst();
for (int i = 0;;) {
if (pCluster == NULL) {
pBlockEntry = GetEOS();
return 1;
}
if (pCluster->EOS()) {
if (m_pSegment->DoneParsing()) {
pBlockEntry = GetEOS();
return 1;
}
pBlockEntry = 0;
return E_BUFFER_NOT_FULL;
}
long status = pCluster->GetFirst(pBlockEntry);
if (status < 0) // error
return status;
if (pBlockEntry == 0) { // empty cluster
pCluster = m_pSegment->GetNext(pCluster);
continue;
}
for (;;) {
const Block* const pBlock = pBlockEntry->GetBlock();
assert(pBlock);
const long long tn = pBlock->GetTrackNumber();
if ((tn == m_info.number) && VetEntry(pBlockEntry))
return 0;
const BlockEntry* pNextEntry;
status = pCluster->GetNext(pBlockEntry, pNextEntry);
if (status < 0) // error
return status;
if (pNextEntry == 0)
break;
pBlockEntry = pNextEntry;
}
++i;
if (i >= 100)
break;
pCluster = m_pSegment->GetNext(pCluster);
}
// NOTE: if we get here, it means that we didn't find a block with
// a matching track number. We interpret that as an error (which
// might be too conservative).
pBlockEntry = GetEOS(); // so we can return a non-NULL value
return 1;
}
long Track::GetNext(const BlockEntry* pCurrEntry,
const BlockEntry*& pNextEntry) const {
assert(pCurrEntry);
assert(!pCurrEntry->EOS()); //?
const Block* const pCurrBlock = pCurrEntry->GetBlock();
assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number);
if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number)
return -1;
const Cluster* pCluster = pCurrEntry->GetCluster();
assert(pCluster);
assert(!pCluster->EOS());
long status = pCluster->GetNext(pCurrEntry, pNextEntry);
if (status < 0) // error
return status;
for (int i = 0;;) {
while (pNextEntry) {
const Block* const pNextBlock = pNextEntry->GetBlock();
assert(pNextBlock);
if (pNextBlock->GetTrackNumber() == m_info.number)
return 0;
pCurrEntry = pNextEntry;
status = pCluster->GetNext(pCurrEntry, pNextEntry);
if (status < 0) // error
return status;
}
pCluster = m_pSegment->GetNext(pCluster);
if (pCluster == NULL) {
pNextEntry = GetEOS();
return 1;
}
if (pCluster->EOS()) {
if (m_pSegment->DoneParsing()) {
pNextEntry = GetEOS();
return 1;
}
// TODO: there is a potential O(n^2) problem here: we tell the
// caller to (pre)load another cluster, which he does, but then he
// calls GetNext again, which repeats the same search. This is
// a pathological case, since the only way it can happen is if
// there exists a long sequence of clusters none of which contain a
// block from this track. One way around this problem is for the
// caller to be smarter when he loads another cluster: don't call
// us back until you have a cluster that contains a block from this
// track. (Of course, that's not cheap either, since our caller
// would have to scan the each cluster as it's loaded, so that
// would just push back the problem.)
pNextEntry = NULL;
return E_BUFFER_NOT_FULL;
}
status = pCluster->GetFirst(pNextEntry);
if (status < 0) // error
return status;
if (pNextEntry == NULL) // empty cluster
continue;
++i;
if (i >= 100)
break;
}
// NOTE: if we get here, it means that we didn't find a block with
// a matching track number after lots of searching, so we give
// up trying.
pNextEntry = GetEOS(); // so we can return a non-NULL value
return 1;
}
bool Track::VetEntry(const BlockEntry* pBlockEntry) const {
assert(pBlockEntry);
const Block* const pBlock = pBlockEntry->GetBlock();
assert(pBlock);
assert(pBlock->GetTrackNumber() == m_info.number);
if (!pBlock || pBlock->GetTrackNumber() != m_info.number)
return false;
// This function is used during a seek to determine whether the
// frame is a valid seek target. This default function simply
// returns true, which means all frames are valid seek targets.
// It gets overridden by the VideoTrack class, because only video
// keyframes can be used as seek target.
return true;
}
long Track::Seek(long long time_ns, const BlockEntry*& pResult) const {
const long status = GetFirst(pResult);
if (status < 0) // buffer underflow, etc
return status;
assert(pResult);
if (pResult->EOS())
return 0;
const Cluster* pCluster = pResult->GetCluster();
assert(pCluster);
assert(pCluster->GetIndex() >= 0);
if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
return 0;
Cluster** const clusters = m_pSegment->m_clusters;
assert(clusters);
const long count = m_pSegment->GetCount(); // loaded only, not preloaded
assert(count > 0);
Cluster** const i = clusters + pCluster->GetIndex();
assert(i);
assert(*i == pCluster);
assert(pCluster->GetTime() <= time_ns);
Cluster** const j = clusters + count;
Cluster** lo = i;
Cluster** hi = j;
while (lo < hi) {
// INVARIANT:
//[i, lo) <= time_ns
//[lo, hi) ?
//[hi, j) > time_ns
Cluster** const mid = lo + (hi - lo) / 2;
assert(mid < hi);
pCluster = *mid;
assert(pCluster);
assert(pCluster->GetIndex() >= 0);
assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
const long long t = pCluster->GetTime();
if (t <= time_ns)
lo = mid + 1;
else
hi = mid;
assert(lo <= hi);
}
assert(lo == hi);
assert(lo > i);
assert(lo <= j);
while (lo > i) {
pCluster = *--lo;
assert(pCluster);
assert(pCluster->GetTime() <= time_ns);
pResult = pCluster->GetEntry(this);
if ((pResult != 0) && !pResult->EOS())
return 0;
// landed on empty cluster (no entries)
}
pResult = GetEOS(); // weird
return 0;
}
const ContentEncoding* Track::GetContentEncodingByIndex(
unsigned long idx) const {
const ptrdiff_t count =
content_encoding_entries_end_ - content_encoding_entries_;
assert(count >= 0);
if (idx >= static_cast<unsigned long>(count))
return NULL;
return content_encoding_entries_[idx];
}
unsigned long Track::GetContentEncodingCount() const {
const ptrdiff_t count =
content_encoding_entries_end_ - content_encoding_entries_;
assert(count >= 0);
return static_cast<unsigned long>(count);
}
long Track::ParseContentEncodingsEntry(long long start, long long size) {
IMkvReader* const pReader = m_pSegment->m_pReader;
assert(pReader);
long long pos = start;
const long long stop = start + size;
// Count ContentEncoding elements.
int count = 0;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
// pos now designates start of element
if (id == 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;
long long display_width = 0;
long long display_height = 0;
long long display_unit = 0;
long long stereo_mode = 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 == 0x14B0) { // display width
display_width = UnserializeUInt(pReader, pos, size);
if (display_width <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x14BA) { // display height
display_height = UnserializeUInt(pReader, pos, size);
if (display_height <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x14B2) { // display unit
display_unit = UnserializeUInt(pReader, pos, size);
if (display_unit < 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x13B8) { // stereo mode
stereo_mode = UnserializeUInt(pReader, pos, size);
if (stereo_mode < 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_display_width = display_width;
pTrack->m_display_height = display_height;
pTrack->m_display_unit = display_unit;
pTrack->m_stereo_mode = stereo_mode;
pTrack->m_rate = rate;
pResult = pTrack;
return 0; // success
}
bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const {
return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey();
}
long VideoTrack::Seek(long long time_ns, const BlockEntry*& pResult) const {
const long status = GetFirst(pResult);
if (status < 0) // buffer underflow, etc
return status;
assert(pResult);
if (pResult->EOS())
return 0;
const Cluster* pCluster = pResult->GetCluster();
assert(pCluster);
assert(pCluster->GetIndex() >= 0);
if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
return 0;
Cluster** const clusters = m_pSegment->m_clusters;
assert(clusters);
const long count = m_pSegment->GetCount(); // loaded only, not pre-loaded
assert(count > 0);
Cluster** const i = clusters + pCluster->GetIndex();
assert(i);
assert(*i == pCluster);
assert(pCluster->GetTime() <= time_ns);
Cluster** const j = clusters + count;
Cluster** lo = i;
Cluster** hi = j;
while (lo < hi) {
// INVARIANT:
//[i, lo) <= time_ns
//[lo, hi) ?
//[hi, j) > time_ns
Cluster** const mid = lo + (hi - lo) / 2;
assert(mid < hi);
pCluster = *mid;
assert(pCluster);
assert(pCluster->GetIndex() >= 0);
assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
const long long t = pCluster->GetTime();
if (t <= time_ns)
lo = mid + 1;
else
hi = mid;
assert(lo <= hi);
}
assert(lo == hi);
assert(lo > i);
assert(lo <= j);
pCluster = *--lo;
assert(pCluster);
assert(pCluster->GetTime() <= time_ns);
pResult = pCluster->GetEntry(this, time_ns);
if ((pResult != 0) && !pResult->EOS()) // found a keyframe
return 0;
while (lo != i) {
pCluster = *--lo;
assert(pCluster);
assert(pCluster->GetTime() <= time_ns);
pResult = pCluster->GetEntry(this, time_ns);
if ((pResult != 0) && !pResult->EOS())
return 0;
}
// weird: we're on the first cluster, but no keyframe found
// should never happen but we must return something anyway
pResult = GetEOS();
return 0;
}
long long VideoTrack::GetWidth() const { return m_width; }
long long VideoTrack::GetHeight() const { return m_height; }
long long VideoTrack::GetDisplayWidth() const {
return m_display_width > 0 ? m_display_width : GetWidth();
}
long long VideoTrack::GetDisplayHeight() const {
return m_display_height > 0 ? m_display_height : GetHeight();
}
long long VideoTrack::GetDisplayUnit() const { return m_display_unit; }
long long VideoTrack::GetStereoMode() const { return m_stereo_mode; }
double VideoTrack::GetFrameRate() const { return m_rate; }
AudioTrack::AudioTrack(Segment* pSegment, long long element_start,
long long element_size)
: Track(pSegment, element_start, element_size) {}
long AudioTrack::Parse(Segment* pSegment, const Info& info,
long long element_start, long long element_size,
AudioTrack*& pResult) {
if (pResult)
return -1;
if (info.type != Track::kAudio)
return -1;
IMkvReader* const pReader = pSegment->m_pReader;
const Settings& s = info.settings;
assert(s.start >= 0);
assert(s.size >= 0);
long long pos = s.start;
assert(pos >= 0);
const long long stop = pos + s.size;
double rate = 8000.0; // MKV default
long long channels = 1;
long long bit_depth = 0;
while (pos < stop) {
long long id, size;
long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (id == 0x35) { // Sample Rate
status = UnserializeFloat(pReader, pos, size, rate);
if (status < 0)
return status;
if (rate <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x1F) { // Channel Count
channels = UnserializeUInt(pReader, pos, size);
if (channels <= 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x2264) { // Bit Depth
bit_depth = UnserializeUInt(pReader, pos, size);
if (bit_depth <= 0)
return E_FILE_FORMAT_INVALID;
}
pos += size; // consume payload
assert(pos <= stop);
}
assert(pos == stop);
AudioTrack* const pTrack =
new (std::nothrow) AudioTrack(pSegment, element_start, element_size);
if (pTrack == NULL)
return -1; // generic error
const int status = info.Copy(pTrack->m_info);
if (status) {
delete pTrack;
return status;
}
pTrack->m_rate = rate;
pTrack->m_channels = channels;
pTrack->m_bitDepth = bit_depth;
pResult = pTrack;
return 0; // success
}
double AudioTrack::GetSamplingRate() const { return m_rate; }
long long AudioTrack::GetChannels() const { return m_channels; }
long long AudioTrack::GetBitDepth() const { return m_bitDepth; }
Tracks::Tracks(Segment* pSegment, long long start, long long size_,
long long element_start, long long element_size)
: m_pSegment(pSegment),
m_start(start),
m_size(size_),
m_element_start(element_start),
m_element_size(element_size),
m_trackEntries(NULL),
m_trackEntriesEnd(NULL) {}
long Tracks::Parse() {
assert(m_trackEntries == NULL);
assert(m_trackEntriesEnd == NULL);
const long long stop = m_start + m_size;
IMkvReader* const pReader = m_pSegment->m_pReader;
int count = 0;
long long pos = m_start;
while (pos < stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, stop, id, size);
if (status < 0) // error
return status;
if (size == 0) // weird
continue;
if (id == 0x2E) // TrackEntry ID
++count;
pos += size; // consume payload
assert(pos <= stop);
}
assert(pos == stop);
if (count <= 0)
return 0; // success
m_trackEntries = new (std::nothrow) Track*[count];
if (m_trackEntries == NULL)
return -1;
m_trackEntriesEnd = m_trackEntries;
pos = m_start;
while (pos < stop) {
const long long element_start = pos;
long long id, payload_size;
const long status =
ParseElementHeader(pReader, pos, stop, id, payload_size);
if (status < 0) // error
return status;
if (payload_size == 0) // weird
continue;
const long long payload_stop = pos + payload_size;
assert(payload_stop <= stop); // checked in ParseElement
const long long element_size = payload_stop - element_start;
if (id == 0x2E) { // TrackEntry ID
Track*& pTrack = *m_trackEntriesEnd;
pTrack = NULL;
const long status = ParseTrackEntry(pos, payload_size, element_start,
element_size, pTrack);
if (status)
return status;
if (pTrack)
++m_trackEntriesEnd;
}
pos = payload_stop;
assert(pos <= stop);
}
assert(pos == stop);
return 0; // success
}
unsigned long Tracks::GetTracksCount() const {
const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries;
assert(result >= 0);
return static_cast<unsigned long>(result);
}
long Tracks::ParseTrackEntry(long long track_start, long long track_size,
long long element_start, long long element_size,
Track*& pResult) const {
if (pResult)
return -1;
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = track_start;
const long long track_stop = track_start + track_size;
Track::Info info;
info.type = 0;
info.number = 0;
info.uid = 0;
info.defaultDuration = 0;
Track::Settings v;
v.start = -1;
v.size = -1;
Track::Settings a;
a.start = -1;
a.size = -1;
Track::Settings e; // content_encodings_settings;
e.start = -1;
e.size = -1;
long long lacing = 1; // default is true
while (pos < track_stop) {
long long id, size;
const long status = ParseElementHeader(pReader, pos, track_stop, id, size);
if (status < 0) // error
return status;
if (size < 0)
return E_FILE_FORMAT_INVALID;
const long long start = pos;
if (id == 0x60) { // VideoSettings ID
v.start = start;
v.size = size;
} else if (id == 0x61) { // AudioSettings ID
a.start = start;
a.size = size;
} else if (id == 0x2D80) { // ContentEncodings ID
e.start = start;
e.size = size;
} else if (id == 0x33C5) { // Track UID
if (size > 8)
return E_FILE_FORMAT_INVALID;
info.uid = 0;
long long pos_ = start;
const long long pos_end = start + size;
while (pos_ != pos_end) {
unsigned char b;
const int status = pReader->Read(pos_, 1, &b);
if (status)
return status;
info.uid <<= 8;
info.uid |= b;
++pos_;
}
} else if (id == 0x57) { // Track Number
const long long num = UnserializeUInt(pReader, pos, size);
if ((num <= 0) || (num > 127))
return E_FILE_FORMAT_INVALID;
info.number = static_cast<long>(num);
} else if (id == 0x03) { // Track Type
const long long type = UnserializeUInt(pReader, pos, size);
if ((type <= 0) || (type > 254))
return E_FILE_FORMAT_INVALID;
info.type = static_cast<long>(type);
} else if (id == 0x136E) { // Track Name
const long status =
UnserializeString(pReader, pos, size, info.nameAsUTF8);
if (status)
return status;
} else if (id == 0x02B59C) { // Track Language
const long status = UnserializeString(pReader, pos, size, info.language);
if (status)
return status;
} else if (id == 0x03E383) { // Default Duration
const long long duration = UnserializeUInt(pReader, pos, size);
if (duration < 0)
return E_FILE_FORMAT_INVALID;
info.defaultDuration = static_cast<unsigned long long>(duration);
} else if (id == 0x06) { // CodecID
const long status = UnserializeString(pReader, pos, size, info.codecId);
if (status)
return status;
} else if (id == 0x1C) { // lacing
lacing = UnserializeUInt(pReader, pos, size);
if ((lacing < 0) || (lacing > 1))
return E_FILE_FORMAT_INVALID;
} else if (id == 0x23A2) { // Codec Private
delete[] info.codecPrivate;
info.codecPrivate = NULL;
info.codecPrivateSize = 0;
const size_t buflen = static_cast<size_t>(size);
if (buflen) {
typedef unsigned char* buf_t;
const buf_t buf = new (std::nothrow) unsigned char[buflen];
if (buf == NULL)
return -1;
const int status = pReader->Read(pos, static_cast<long>(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 (info.type == Track::kMetadata && e.start >= 0)
return E_FILE_FORMAT_INVALID;
info.settings.start = -1;
info.settings.size = 0;
Track* pTrack = NULL;
const long status =
Track::Create(m_pSegment, info, element_start, element_size, pTrack);
if (status)
return status;
pResult = pTrack;
assert(pResult);
}
return 0; // success
}
Tracks::~Tracks() {
Track** i = m_trackEntries;
Track** const j = m_trackEntriesEnd;
while (i != j) {
Track* const pTrack = *i++;
delete pTrack;
}
delete[] m_trackEntries;
}
const Track* Tracks::GetTrackByNumber(long tn) const {
if (tn < 0)
return NULL;
Track** i = m_trackEntries;
Track** const j = m_trackEntriesEnd;
while (i != j) {
Track* const pTrack = *i++;
if (pTrack == NULL)
continue;
if (tn == pTrack->GetNumber())
return pTrack;
}
return NULL; // not found
}
const Track* Tracks::GetTrackByIndex(unsigned long idx) const {
const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries;
if (idx >= static_cast<unsigned long>(count))
return NULL;
return m_trackEntries[idx];
}
long Cluster::Load(long long& pos, long& len) const {
assert(m_pSegment);
assert(m_pos >= m_element_start);
if (m_timecode >= 0) // at least partially loaded
return 0;
assert(m_pos == m_element_start);
assert(m_element_size < 0);
IMkvReader* const pReader = m_pSegment->m_pReader;
long long total, avail;
const int status = pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
assert((total < 0) || (m_pos <= total)); // TODO: verify this
pos = m_pos;
long long cluster_size = -1;
{
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error or underflow
return static_cast<long>(result);
if (result > 0) // underflow (weird)
return E_BUFFER_NOT_FULL;
// if ((pos + len) > segment_stop)
// return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id_ = ReadUInt(pReader, pos, len);
if (id_ < 0) // error
return static_cast<long>(id_);
if (id_ != 0x0F43B675) // Cluster ID
return E_FILE_FORMAT_INVALID;
pos += len; // consume id
// read cluster size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
// if ((pos + len) > segment_stop)
// return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(cluster_size);
if (size == 0)
return E_FILE_FORMAT_INVALID; // TODO: verify this
pos += len; // consume length of size of element
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size != unknown_size)
cluster_size = size;
}
// pos points to start of payload
long long timecode = -1;
long long new_pos = -1;
bool bBlock = false;
long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size;
for (;;) {
if ((cluster_stop >= 0) && (pos >= cluster_stop))
break;
// Parse ID
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(pReader, pos, len);
if (id < 0) // error
return static_cast<long>(id);
if (id == 0)
return E_FILE_FORMAT_INVALID;
// This is the distinguished set of ID's we use to determine
// that we have exhausted the sub-element's inside the cluster
// whose ID we parsed earlier.
if (id == 0x0F43B675) // Cluster ID
break;
if (id == 0x0C53BB6B) // Cues ID
break;
pos += len; // consume ID field
// Parse Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size)
return E_FILE_FORMAT_INVALID;
pos += len; // consume size field
if ((cluster_stop >= 0) && (pos > cluster_stop))
return E_FILE_FORMAT_INVALID;
// pos now points to start of payload
if (size == 0) // weird
continue;
if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if (id == 0x67) { // TimeCode ID
len = static_cast<long>(size);
if ((pos + size) > avail)
return E_BUFFER_NOT_FULL;
timecode = UnserializeUInt(pReader, pos, size);
if (timecode < 0) // error (or underflow)
return static_cast<long>(timecode);
new_pos = pos + size;
if (bBlock)
break;
} else if (id == 0x20) { // BlockGroup ID
bBlock = true;
break;
} else if (id == 0x23) { // SimpleBlock ID
bBlock = true;
break;
}
pos += size; // consume payload
assert((cluster_stop < 0) || (pos <= cluster_stop));
}
assert((cluster_stop < 0) || (pos <= cluster_stop));
if (timecode < 0) // no timecode found
return E_FILE_FORMAT_INVALID;
if (!bBlock)
return E_FILE_FORMAT_INVALID;
m_pos = new_pos; // designates position just beyond timecode payload
m_timecode = timecode; // m_timecode >= 0 means we're partially loaded
if (cluster_size >= 0)
m_element_size = cluster_stop - m_element_start;
return 0;
}
long Cluster::Parse(long long& pos, long& len) const {
long status = Load(pos, len);
if (status < 0)
return status;
assert(m_pos >= m_element_start);
assert(m_timecode >= 0);
// assert(m_size > 0);
// assert(m_element_size > m_size);
const long long cluster_stop =
(m_element_size < 0) ? -1 : m_element_start + m_element_size;
if ((cluster_stop >= 0) && (m_pos >= cluster_stop))
return 1; // nothing else to do
IMkvReader* const pReader = m_pSegment->m_pReader;
long long total, avail;
status = pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
pos = m_pos;
for (;;) {
if ((cluster_stop >= 0) && (pos >= cluster_stop))
break;
if ((total >= 0) && (pos >= total)) {
if (m_element_size < 0)
m_element_size = pos - m_element_start;
break;
}
// Parse ID
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(pReader, pos, len);
if (id < 0) // error
return static_cast<long>(id);
if (id == 0) // weird
return E_FILE_FORMAT_INVALID;
// This is the distinguished set of ID's we use to determine
// that we have exhausted the sub-element's inside the cluster
// whose ID we parsed earlier.
if ((id == 0x0F43B675) || (id == 0x0C53BB6B)) { // Cluster or Cues ID
if (m_element_size < 0)
m_element_size = pos - m_element_start;
break;
}
pos += len; // consume ID field
// Parse Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size)
return E_FILE_FORMAT_INVALID;
pos += len; // consume size field
if ((cluster_stop >= 0) && (pos > cluster_stop))
return E_FILE_FORMAT_INVALID;
// pos now points to start of payload
if (size == 0) // weird
continue;
// const long long block_start = pos;
const long long block_stop = pos + size;
if (cluster_stop >= 0) {
if (block_stop > cluster_stop) {
if ((id == 0x20) || (id == 0x23))
return E_FILE_FORMAT_INVALID;
pos = cluster_stop;
break;
}
} else if ((total >= 0) && (block_stop > total)) {
m_element_size = total - m_element_start;
pos = total;
break;
} else if (block_stop > avail) {
len = static_cast<long>(size);
return E_BUFFER_NOT_FULL;
}
Cluster* const this_ = const_cast<Cluster*>(this);
if (id == 0x20) // BlockGroup
return this_->ParseBlockGroup(size, pos, len);
if (id == 0x23) // SimpleBlock
return this_->ParseSimpleBlock(size, pos, len);
pos += size; // consume payload
assert((cluster_stop < 0) || (pos <= cluster_stop));
}
assert(m_element_size > 0);
m_pos = pos;
assert((cluster_stop < 0) || (m_pos <= cluster_stop));
if (m_entries_count > 0) {
const long idx = m_entries_count - 1;
const BlockEntry* const pLast = m_entries[idx];
assert(pLast);
const Block* const pBlock = pLast->GetBlock();
assert(pBlock);
const long long start = pBlock->m_start;
if ((total >= 0) && (start > total))
return -1; // defend against trucated stream
const long long size = pBlock->m_size;
const long long stop = start + size;
assert((cluster_stop < 0) || (stop <= cluster_stop));
if ((total >= 0) && (stop > total))
return -1; // defend against trucated stream
}
return 1; // no more entries
}
long Cluster::ParseSimpleBlock(long long block_size, long long& pos,
long& len) {
const long long block_start = pos;
const long long block_stop = pos + block_size;
IMkvReader* const pReader = m_pSegment->m_pReader;
long long total, avail;
long status = pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
// parse track number
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((pos + len) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long track = ReadUInt(pReader, pos, len);
if (track < 0) // error
return static_cast<long>(track);
if (track == 0)
return E_FILE_FORMAT_INVALID;
pos += len; // consume track number
if ((pos + 2) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + 2) > avail) {
len = 2;
return E_BUFFER_NOT_FULL;
}
pos += 2; // consume timecode
if ((pos + 1) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
unsigned char flags;
status = pReader->Read(pos, 1, &flags);
if (status < 0) { // error or underflow
len = 1;
return status;
}
++pos; // consume flags byte
assert(pos <= avail);
if (pos >= block_stop)
return E_FILE_FORMAT_INVALID;
const int lacing = int(flags & 0x06) >> 1;
if ((lacing != 0) && (block_stop > avail)) {
len = static_cast<long>(block_stop - pos);
return E_BUFFER_NOT_FULL;
}
status = CreateBlock(0x23, // simple block id
block_start, block_size,
0); // DiscardPadding
if (status != 0)
return status;
m_pos = block_stop;
return 0; // success
}
long Cluster::ParseBlockGroup(long long payload_size, long long& pos,
long& len) {
const long long payload_start = pos;
const long long payload_stop = pos + payload_size;
IMkvReader* const pReader = m_pSegment->m_pReader;
long long total, avail;
long status = pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
if ((total >= 0) && (payload_stop > total))
return E_FILE_FORMAT_INVALID;
if (payload_stop > avail) {
len = static_cast<long>(payload_size);
return E_BUFFER_NOT_FULL;
}
long long discard_padding = 0;
while (pos < payload_stop) {
// parse sub-block element ID
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((pos + len) > payload_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(pReader, pos, len);
if (id < 0) // error
return static_cast<long>(id);
if (id == 0) // not a value ID
return E_FILE_FORMAT_INVALID;
pos += len; // consume ID field
// Parse Size
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((pos + len) > payload_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume size field
// pos now points to start of sub-block group payload
if (pos > payload_stop)
return E_FILE_FORMAT_INVALID;
if (size == 0) // weird
continue;
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size)
return E_FILE_FORMAT_INVALID;
if (id == 0x35A2) { // DiscardPadding
status = UnserializeInt(pReader, pos, size, discard_padding);
if (status < 0) // error
return status;
}
if (id != 0x21) { // sub-part of BlockGroup is not a Block
pos += size; // consume sub-part of block group
if (pos > payload_stop)
return E_FILE_FORMAT_INVALID;
continue;
}
const long long block_stop = pos + size;
if (block_stop > payload_stop)
return E_FILE_FORMAT_INVALID;
// parse track number
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((pos + len) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long track = ReadUInt(pReader, pos, len);
if (track < 0) // error
return static_cast<long>(track);
if (track == 0)
return E_FILE_FORMAT_INVALID;
pos += len; // consume track number
if ((pos + 2) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + 2) > avail) {
len = 2;
return E_BUFFER_NOT_FULL;
}
pos += 2; // consume timecode
if ((pos + 1) > block_stop)
return E_FILE_FORMAT_INVALID;
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
unsigned char flags;
status = pReader->Read(pos, 1, &flags);
if (status < 0) { // error or underflow
len = 1;
return status;
}
++pos; // consume flags byte
assert(pos <= avail);
if (pos >= block_stop)
return E_FILE_FORMAT_INVALID;
const int lacing = int(flags & 0x06) >> 1;
if ((lacing != 0) && (block_stop > avail)) {
len = static_cast<long>(block_stop - pos);
return E_BUFFER_NOT_FULL;
}
pos = block_stop; // consume block-part of block group
assert(pos <= payload_stop);
}
assert(pos == payload_stop);
status = CreateBlock(0x20, // BlockGroup ID
payload_start, payload_size, discard_padding);
if (status != 0)
return status;
m_pos = payload_stop;
return 0; // success
}
long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const {
assert(m_pos >= m_element_start);
pEntry = NULL;
if (index < 0)
return -1; // generic error
if (m_entries_count < 0)
return E_BUFFER_NOT_FULL;
assert(m_entries);
assert(m_entries_size > 0);
assert(m_entries_count <= m_entries_size);
if (index < m_entries_count) {
pEntry = m_entries[index];
assert(pEntry);
return 1; // found entry
}
if (m_element_size < 0) // we don't know cluster end yet
return E_BUFFER_NOT_FULL; // underflow
const long long element_stop = m_element_start + m_element_size;
if (m_pos >= element_stop)
return 0; // nothing left to parse
return E_BUFFER_NOT_FULL; // underflow, since more remains to be parsed
}
Cluster* Cluster::Create(Segment* pSegment, long idx, long long off)
// long long element_size)
{
assert(pSegment);
assert(off >= 0);
const long long element_start = pSegment->m_start + off;
Cluster* const pCluster = new Cluster(pSegment, idx, element_start);
// element_size);
assert(pCluster);
return pCluster;
}
Cluster::Cluster()
: m_pSegment(NULL),
m_element_start(0),
m_index(0),
m_pos(0),
m_element_size(0),
m_timecode(0),
m_entries(NULL),
m_entries_size(0),
m_entries_count(0) // means "no entries"
{}
Cluster::Cluster(Segment* pSegment, long idx, long long element_start
/* long long element_size */)
: m_pSegment(pSegment),
m_element_start(element_start),
m_index(idx),
m_pos(element_start),
m_element_size(-1 /* element_size */),
m_timecode(-1),
m_entries(NULL),
m_entries_size(0),
m_entries_count(-1) // means "has not been parsed yet"
{}
Cluster::~Cluster() {
if (m_entries_count <= 0)
return;
BlockEntry** i = m_entries;
BlockEntry** const j = m_entries + m_entries_count;
while (i != j) {
BlockEntry* p = *i++;
assert(p);
delete p;
}
delete[] m_entries;
}
bool Cluster::EOS() const { return (m_pSegment == NULL); }
long Cluster::GetIndex() const { return m_index; }
long long Cluster::GetPosition() const {
const long long pos = m_element_start - m_pSegment->m_start;
assert(pos >= 0);
return pos;
}
long long Cluster::GetElementSize() const { return m_element_size; }
long Cluster::HasBlockEntries(
const Segment* pSegment,
long long off, // relative to start of segment payload
long long& pos, long& len) {
assert(pSegment);
assert(off >= 0); // relative to segment
IMkvReader* const pReader = pSegment->m_pReader;
long long total, avail;
long status = pReader->Length(&total, &avail);
if (status < 0) // error
return status;
assert((total < 0) || (avail <= total));
pos = pSegment->m_start + off; // absolute
if ((total >= 0) && (pos >= total))
return 0; // we don't even have a complete cluster
const long long segment_stop =
(pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size;
long long cluster_stop = -1; // interpreted later to mean "unknown size"
{
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // need more data
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((total >= 0) && ((pos + len) > total))
return 0;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(pReader, pos, len);
if (id < 0) // error
return static_cast<long>(id);
if (id != 0x0F43B675) // weird: not cluster ID
return -1; // generic error
pos += len; // consume Cluster ID field
// read size field
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // weird
return E_BUFFER_NOT_FULL;
if ((segment_stop >= 0) && ((pos + len) > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((total >= 0) && ((pos + len) > total))
return 0;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
if (size == 0)
return 0; // cluster does not have entries
pos += len; // consume size field
// pos now points to start of payload
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size != unknown_size) {
cluster_stop = pos + size;
assert(cluster_stop >= 0);
if ((segment_stop >= 0) && (cluster_stop > segment_stop))
return E_FILE_FORMAT_INVALID;
if ((total >= 0) && (cluster_stop > total))
// return E_FILE_FORMAT_INVALID; //too conservative
return 0; // cluster does not have any entries
}
}
for (;;) {
if ((cluster_stop >= 0) && (pos >= cluster_stop))
return 0; // no entries detected
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
long long result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // need more data
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long id = ReadUInt(pReader, pos, len);
if (id < 0) // error
return static_cast<long>(id);
// This is the distinguished set of ID's we use to determine
// that we have exhausted the sub-element's inside the cluster
// whose ID we parsed earlier.
if (id == 0x0F43B675) // Cluster ID
return 0; // no entries found
if (id == 0x0C53BB6B) // Cues ID
return 0; // no entries found
pos += len; // consume id field
if ((cluster_stop >= 0) && (pos >= cluster_stop))
return E_FILE_FORMAT_INVALID;
// read size field
if ((pos + 1) > avail) {
len = 1;
return E_BUFFER_NOT_FULL;
}
result = GetUIntLength(pReader, pos, len);
if (result < 0) // error
return static_cast<long>(result);
if (result > 0) // underflow
return E_BUFFER_NOT_FULL;
if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if ((pos + len) > avail)
return E_BUFFER_NOT_FULL;
const long long size = ReadUInt(pReader, pos, len);
if (size < 0) // error
return static_cast<long>(size);
pos += len; // consume size field
// pos now points to start of payload
if ((cluster_stop >= 0) && (pos > cluster_stop))
return E_FILE_FORMAT_INVALID;
if (size == 0) // weird
continue;
const long long unknown_size = (1LL << (7 * len)) - 1;
if (size == unknown_size)
return E_FILE_FORMAT_INVALID; // not supported inside cluster
if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
return E_FILE_FORMAT_INVALID;
if (id == 0x20) // BlockGroup ID
return 1; // have at least one entry
if (id == 0x23) // SimpleBlock ID
return 1; // have at least one entry
pos += size; // consume payload
assert((cluster_stop < 0) || (pos <= cluster_stop));
}
}
long long Cluster::GetTimeCode() const {
long long pos;
long len;
const long status = Load(pos, len);
if (status < 0) // error
return status;
return m_timecode;
}
long long Cluster::GetTime() const {
const long long tc = GetTimeCode();
if (tc < 0)
return tc;
const SegmentInfo* const pInfo = m_pSegment->GetInfo();
assert(pInfo);
const long long scale = pInfo->GetTimeCodeScale();
assert(scale >= 1);
const long long t = m_timecode * scale;
return t;
}
long long Cluster::GetFirstTime() const {
const BlockEntry* pEntry;
const long status = GetFirst(pEntry);
if (status < 0) // error
return status;
if (pEntry == NULL) // empty cluster
return GetTime();
const Block* const pBlock = pEntry->GetBlock();
assert(pBlock);
return pBlock->GetTime(this);
}
long long Cluster::GetLastTime() const {
const BlockEntry* pEntry;
const long status = GetLast(pEntry);
if (status < 0) // error
return status;
if (pEntry == NULL) // empty cluster
return GetTime();
const Block* const pBlock = pEntry->GetBlock();
assert(pBlock);
return pBlock->GetTime(this);
}
long Cluster::CreateBlock(long long id,
long long pos, // absolute pos of payload
long long size, long long discard_padding) {
assert((id == 0x20) || (id == 0x23)); // BlockGroup or SimpleBlock
if (m_entries_count < 0) { // haven't parsed anything yet
assert(m_entries == NULL);
assert(m_entries_size == 0);
m_entries_size = 1024;
m_entries = new BlockEntry*[m_entries_size];
m_entries_count = 0;
} else {
assert(m_entries);
assert(m_entries_size > 0);
assert(m_entries_count <= m_entries_size);
if (m_entries_count >= m_entries_size) {
const long entries_size = 2 * m_entries_size;
BlockEntry** const entries = new BlockEntry*[entries_size];
assert(entries);
BlockEntry** src = m_entries;
BlockEntry** const src_end = src + m_entries_count;
BlockEntry** dst = entries;
while (src != src_end)
*dst++ = *src++;
delete[] m_entries;
m_entries = entries;
m_entries_size = entries_size;
}
}
if (id == 0x20) // BlockGroup ID
return CreateBlockGroup(pos, size, discard_padding);
else // SimpleBlock ID
return CreateSimpleBlock(pos, size);
}
long Cluster::CreateBlockGroup(long long start_offset, long long size,
long long discard_padding) {
assert(m_entries);
assert(m_entries_size > 0);
assert(m_entries_count >= 0);
assert(m_entries_count < m_entries_size);
IMkvReader* const pReader = m_pSegment->m_pReader;
long long pos = start_offset;
const long long stop = start_offset + size;
// For WebM files, there is a bias towards previous reference times
//(in order to support alt-ref frames, which refer back to the previous
// keyframe). Normally a 0 value is not possible, but here we tenatively
// allow 0 as the value of a reference frame, with the interpretation
// that this is a "previous" reference time.
long long prev = 1; // nonce
long long next = 0; // nonce
long long duration = -1; // really, this is unsigned
long long bpos = -1;
long long bsize = -1;
while (pos < stop) {
long len;
const long long id = ReadUInt(pReader, pos, len);
assert(id >= 0); // TODO
assert((pos + len) <= stop);
pos += len; // consume ID
const long long size = ReadUInt(pReader, pos, len);
assert(size >= 0); // TODO
assert((pos + len) <= stop);
pos += len; // consume size
if (id == 0x21) { // Block ID
if (bpos < 0) { // Block ID
bpos = pos;
bsize = size;
}
} else if (id == 0x1B) { // Duration ID
if (size > 8)
return E_FILE_FORMAT_INVALID;
duration = UnserializeUInt(pReader, pos, size);
if (duration < 0)
return E_FILE_FORMAT_INVALID;
} else if (id == 0x7B) { // ReferenceBlock
if (size > 8 || size <= 0)
return E_FILE_FORMAT_INVALID;
const long size_ = static_cast<long>(size);
long long time;
long status = UnserializeInt(pReader, pos, size_, time);
assert(status == 0);
if (status != 0)
return -1;
if (time <= 0) // see note above
prev = time;
else // weird
next = time;
}
pos += size; // consume payload
assert(pos <= stop);
}
if (bpos < 0)
return E_FILE_FORMAT_INVALID;
assert(pos == stop);
assert(bsize >= 0);
const long idx = m_entries_count;
BlockEntry** const ppEntry = m_entries + idx;
BlockEntry*& pEntry = *ppEntry;
pEntry = new (std::nothrow)
BlockGroup(this, idx, bpos, bsize, prev, next, duration, discard_padding);
if (pEntry == NULL)
return -1; // generic error
BlockGroup* const p = static_cast<BlockGroup*>(pEntry);
const long status = p->Parse();
if (status == 0) { // success
++m_entries_count;
return 0;
}
delete pEntry;
pEntry = 0;
return status;
}
long Cluster::CreateSimpleBlock(long long st, long long sz) {
assert(m_entries);
assert(m_entries_size > 0);
assert(m_entries_count >= 0);
assert(m_entries_count < m_entries_size);
const long idx = m_entries_count;
BlockEntry** const ppEntry = m_entries + idx;
BlockEntry*& pEntry = *ppEntry;
pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz);
if (pEntry == NULL)
return -1; // generic error
SimpleBlock* const p = static_cast<SimpleBlock*>(pEntry);
const long status = p->Parse();
if (status == 0) {
++m_entries_count;
return 0;
}
delete pEntry;
pEntry = 0;
return status;
}
long Cluster::GetFirst(const BlockEntry*& pFirst) const {
if (m_entries_count <= 0) {
long long pos;
long len;
const long status = Parse(pos, len);
if (status < 0) { // error
pFirst = NULL;
return status;
}
if (m_entries_count <= 0) { // empty cluster
pFirst = NULL;
return 0;
}
}
assert(m_entries);
pFirst = m_entries[0];
assert(pFirst);
return 0; // success
}
long Cluster::GetLast(const BlockEntry*& pLast) const {
for (;;) {
long long pos;
long len;
const long status = Parse(pos, len);
if (status < 0) { // error
pLast = NULL;
return status;
}
if (status > 0) // no new block
break;
}
if (m_entries_count <= 0) {
pLast = NULL;
return 0;
}
assert(m_entries);
const long idx = m_entries_count - 1;
pLast = m_entries[idx];
assert(pLast);
return 0;
}
long Cluster::GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const {
assert(pCurr);
assert(m_entries);
assert(m_entries_count > 0);
size_t idx = pCurr->GetIndex();
assert(idx < size_t(m_entries_count));
assert(m_entries[idx] == pCurr);
++idx;
if (idx >= size_t(m_entries_count)) {
long long pos;
long len;
const long status = Parse(pos, len);
if (status < 0) { // error
pNext = NULL;
return status;
}
if (status > 0) {
pNext = NULL;
return 0;
}
assert(m_entries);
assert(m_entries_count > 0);
assert(idx < size_t(m_entries_count));
}
pNext = m_entries[idx];
assert(pNext);
return 0;
}
long Cluster::GetEntryCount() const { return m_entries_count; }
const BlockEntry* Cluster::GetEntry(const Track* pTrack,
long long time_ns) const {
assert(pTrack);
if (m_pSegment == NULL) // this is the special EOS cluster
return pTrack->GetEOS();
const BlockEntry* pResult = pTrack->GetEOS();
long index = 0;
for (;;) {
if (index >= m_entries_count) {
long long pos;
long len;
const long status = Parse(pos, len);
assert(status >= 0);
if (status > 0) // completely parsed, and no more entries
return pResult;
if (status < 0) // should never happen
return 0;
assert(m_entries);
assert(index < m_entries_count);
}
const BlockEntry* const pEntry = m_entries[index];
assert(pEntry);
assert(!pEntry->EOS());
const Block* const pBlock = pEntry->GetBlock();
assert(pBlock);
if (pBlock->GetTrackNumber() != pTrack->GetNumber()) {
++index;
continue;
}
if (pTrack->VetEntry(pEntry)) {
if (time_ns < 0) // just want first candidate block
return pEntry;
const long long ns = pBlock->GetTime(this);
if (ns > time_ns)
return pResult;
pResult = pEntry; // have a candidate
} else if (time_ns >= 0) {
const long long ns = pBlock->GetTime(this);
if (ns > time_ns)
return pResult;
}
++index;
}
}
const BlockEntry* Cluster::GetEntry(const CuePoint& cp,
const CuePoint::TrackPosition& tp) const {
assert(m_pSegment);
const long long tc = cp.GetTimeCode();
if (tp.m_block > 0) {
const long block = static_cast<long>(tp.m_block);
const long index = block - 1;
while (index >= m_entries_count) {
long long pos;
long len;
const long status = Parse(pos, len);
if (status < 0) // TODO: can this happen?
return NULL;
if (status > 0) // nothing remains to be parsed
return NULL;
}
const BlockEntry* const pEntry = m_entries[index];
assert(pEntry);
assert(!pEntry->EOS());
const Block* const pBlock = pEntry->GetBlock();
assert(pBlock);
if ((pBlock->GetTrackNumber() == tp.m_track) &&
(pBlock->GetTimeCode(this) == tc)) {
return pEntry;
}
}
long index = 0;
for (;;) {
if (index >= m_entries_count) {
long long pos;
long len;
const long status = Parse(pos, len);
if (status < 0) // TODO: can this happen?
return NULL;
if (status > 0) // nothing remains to be parsed
return NULL;
assert(m_entries);
assert(index < m_entries_count);
}
const BlockEntry* const pEntry = m_entries[index];
assert(pEntry);
assert(!pEntry->EOS());
const Block* const pBlock = pEntry->GetBlock();
assert(pBlock);
if (pBlock->GetTrackNumber() != tp.m_track) {
++index;
continue;
}
const long long tc_ = pBlock->GetTimeCode(this);
if (tc_ < tc) {
++index;
continue;
}
if (tc_ > tc)
return NULL;
const Tracks* const pTracks = m_pSegment->GetTracks();
assert(pTracks);
const long tn = static_cast<long>(tp.m_track);
const Track* const pTrack = pTracks->GetTrackByNumber(tn);
if (pTrack == NULL)
return NULL;
const long long type = pTrack->GetType();
if (type == 2) // audio
return pEntry;
if (type != 1) // not video
return NULL;
if (!pBlock->IsKey())
return NULL;
return pEntry;
}
}
BlockEntry::BlockEntry(Cluster* p, long idx) : m_pCluster(p), m_index(idx) {}
BlockEntry::~BlockEntry() {}
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;
}
BlockEntry::Kind BlockGroup::GetKind() const { return kBlockGroup; }
const Block* BlockGroup::GetBlock() const { return &m_block; }
long long BlockGroup::GetPrevTimeCode() const { return m_prev; }
long long BlockGroup::GetNextTimeCode() const { return m_next; }
long long BlockGroup::GetDurationTimeCode() const { return m_duration; }
Block::Block(long long start, long long size_, long long discard_padding)
: m_start(start),
m_size(size_),
m_track(0),
m_timecode(-1),
m_flags(0),
m_frames(NULL),
m_frame_count(-1),
m_discard_padding(discard_padding) {}
Block::~Block() { delete[] m_frames; }
long Block::Parse(const Cluster* pCluster) {
if (pCluster == NULL)
return -1;
if (pCluster->m_pSegment == NULL)
return -1;
assert(m_start >= 0);
assert(m_size >= 0);
assert(m_track <= 0);
assert(m_frames == NULL);
assert(m_frame_count <= 0);
long long pos = m_start;
const long long stop = m_start + m_size;
long len;
IMkvReader* const pReader = pCluster->m_pSegment->m_pReader;
m_track = ReadUInt(pReader, pos, len);
if (m_track <= 0)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > stop)
return E_FILE_FORMAT_INVALID;
pos += len; // consume track number
if ((stop - pos) < 2)
return E_FILE_FORMAT_INVALID;
long status;
long long value;
status = UnserializeInt(pReader, pos, 2, value);
if (status)
return E_FILE_FORMAT_INVALID;
if (value < SHRT_MIN)
return E_FILE_FORMAT_INVALID;
if (value > SHRT_MAX)
return E_FILE_FORMAT_INVALID;
m_timecode = static_cast<short>(value);
pos += 2;
if ((stop - pos) <= 0)
return E_FILE_FORMAT_INVALID;
status = pReader->Read(pos, 1, &m_flags);
if (status)
return E_FILE_FORMAT_INVALID;
const int lacing = int(m_flags & 0x06) >> 1;
++pos; // consume flags byte
if (lacing == 0) { // no lacing
if (pos > stop)
return E_FILE_FORMAT_INVALID;
m_frame_count = 1;
m_frames = new Frame[m_frame_count];
Frame& f = m_frames[0];
f.pos = pos;
const long long frame_size = stop - pos;
if (frame_size > LONG_MAX || frame_size <= 0)
return E_FILE_FORMAT_INVALID;
f.len = static_cast<long>(frame_size);
return 0; // success
}
if (pos >= stop)
return E_FILE_FORMAT_INVALID;
unsigned char biased_count;
status = pReader->Read(pos, 1, &biased_count);
if (status)
return E_FILE_FORMAT_INVALID;
++pos; // consume frame count
assert(pos <= stop);
if (pos > stop)
return E_FILE_FORMAT_INVALID;
m_frame_count = int(biased_count) + 1;
m_frames = new Frame[m_frame_count];
assert(m_frames);
if (!m_frames)
return E_FILE_FORMAT_INVALID;
if (lacing == 1) { // Xiph
Frame* pf = m_frames;
Frame* const pf_end = pf + m_frame_count;
long size = 0;
int frame_count = m_frame_count;
while (frame_count > 1) {
long frame_size = 0;
for (;;) {
unsigned char val;
if (pos >= stop)
return E_FILE_FORMAT_INVALID;
status = pReader->Read(pos, 1, &val);
if (status)
return E_FILE_FORMAT_INVALID;
++pos; // consume xiph size byte
frame_size += val;
if (val < 255)
break;
}
Frame& f = *pf++;
assert(pf < pf_end);
if (pf >= pf_end)
return E_FILE_FORMAT_INVALID;
f.pos = 0; // patch later
if (frame_size <= 0)
return E_FILE_FORMAT_INVALID;
f.len = frame_size;
size += frame_size; // contribution of this frame
--frame_count;
}
assert(pf < pf_end);
assert(pos <= stop);
if (pf >= pf_end || pos > stop)
return E_FILE_FORMAT_INVALID;
{
Frame& f = *pf++;
if (pf != pf_end)
return E_FILE_FORMAT_INVALID;
f.pos = 0; // patch later
const long long total_size = stop - pos;
if (total_size < size)
return E_FILE_FORMAT_INVALID;
const long long frame_size = total_size - size;
if (frame_size > LONG_MAX || frame_size <= 0)
return E_FILE_FORMAT_INVALID;
f.len = static_cast<long>(frame_size);
}
pf = m_frames;
while (pf != pf_end) {
Frame& f = *pf++;
assert((pos + f.len) <= stop);
if ((pos + f.len) > stop)
return E_FILE_FORMAT_INVALID;
f.pos = pos;
pos += f.len;
}
assert(pos == stop);
if (pos != stop)
return E_FILE_FORMAT_INVALID;
} else if (lacing == 2) { // fixed-size lacing
if (pos >= stop)
return E_FILE_FORMAT_INVALID;
const long long total_size = stop - pos;
if ((total_size % m_frame_count) != 0)
return E_FILE_FORMAT_INVALID;
const long long frame_size = total_size / m_frame_count;
if (frame_size > LONG_MAX || frame_size <= 0)
return E_FILE_FORMAT_INVALID;
Frame* pf = m_frames;
Frame* const pf_end = pf + m_frame_count;
while (pf != pf_end) {
assert((pos + frame_size) <= stop);
if ((pos + frame_size) > stop)
return E_FILE_FORMAT_INVALID;
Frame& f = *pf++;
f.pos = pos;
f.len = static_cast<long>(frame_size);
pos += frame_size;
}
assert(pos == stop);
if (pos != stop)
return E_FILE_FORMAT_INVALID;
} else {
assert(lacing == 3); // EBML lacing
if (pos >= stop)
return E_FILE_FORMAT_INVALID;
long size = 0;
int frame_count = m_frame_count;
long long frame_size = ReadUInt(pReader, pos, len);
if (frame_size <= 0)
return E_FILE_FORMAT_INVALID;
if (frame_size > LONG_MAX)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > stop)
return E_FILE_FORMAT_INVALID;
pos += len; // consume length of size of first frame
if ((pos + frame_size) > stop)
return E_FILE_FORMAT_INVALID;
Frame* pf = m_frames;
Frame* const pf_end = pf + m_frame_count;
{
Frame& curr = *pf;
curr.pos = 0; // patch later
curr.len = static_cast<long>(frame_size);
size += curr.len; // contribution of this frame
}
--frame_count;
while (frame_count > 1) {
if (pos >= stop)
return E_FILE_FORMAT_INVALID;
assert(pf < pf_end);
if (pf >= pf_end)
return E_FILE_FORMAT_INVALID;
const Frame& prev = *pf++;
assert(prev.len == frame_size);
if (prev.len != frame_size)
return E_FILE_FORMAT_INVALID;
assert(pf < pf_end);
if (pf >= pf_end)
return E_FILE_FORMAT_INVALID;
Frame& curr = *pf;
curr.pos = 0; // patch later
const long long delta_size_ = ReadUInt(pReader, pos, len);
if (delta_size_ < 0)
return E_FILE_FORMAT_INVALID;
if ((pos + len) > stop)
return E_FILE_FORMAT_INVALID;
pos += len; // consume length of (delta) size
assert(pos <= stop);
if (pos > stop)
return E_FILE_FORMAT_INVALID;
const int exp = 7 * len - 1;
const long long bias = (1LL << exp) - 1LL;
const long long delta_size = delta_size_ - bias;
frame_size += delta_size;
if (frame_size <= 0)
return E_FILE_FORMAT_INVALID;
if (frame_size > LONG_MAX)
return E_FILE_FORMAT_INVALID;
curr.len = static_cast<long>(frame_size);
size += curr.len; // contribution of this frame
--frame_count;
}
// parse last frame
if (frame_count > 0) {
assert(pos <= stop);
assert(pf < pf_end);
const Frame& prev = *pf++;
assert(prev.len == frame_size);
if (prev.len != frame_size)
return E_FILE_FORMAT_INVALID;
assert(pf < pf_end);
if (pf >= pf_end)
return E_FILE_FORMAT_INVALID;
Frame& curr = *pf++;
assert(pf == pf_end);
if (pf != pf_end)
return E_FILE_FORMAT_INVALID;
curr.pos = 0; // patch later
const long long total_size = stop - pos;
if (total_size < size)
return E_FILE_FORMAT_INVALID;
frame_size = total_size - size;
if (frame_size > LONG_MAX || frame_size <= 0)
return E_FILE_FORMAT_INVALID;
curr.len = static_cast<long>(frame_size);
}
pf = m_frames;
while (pf != pf_end) {
Frame& f = *pf++;
assert((pos + f.len) <= stop);
if ((pos + f.len) > stop)
return E_FILE_FORMAT_INVALID;
f.pos = pos;
pos += f.len;
}
if (pos != stop)
return E_FILE_FORMAT_INVALID;
}
return 0; // success
}
long long Block::GetTimeCode(const Cluster* pCluster) const {
if (pCluster == 0)
return m_timecode;
const long long tc0 = pCluster->GetTimeCode();
assert(tc0 >= 0);
const long long tc = tc0 + m_timecode;
return tc; // unscaled timecode units
}
long long Block::GetTime(const Cluster* pCluster) const {
assert(pCluster);
const long long tc = GetTimeCode(pCluster);
const Segment* const pSegment = pCluster->m_pSegment;
const SegmentInfo* const pInfo = pSegment->GetInfo();
assert(pInfo);
const long long scale = pInfo->GetTimeCodeScale();
assert(scale >= 1);
const long long ns = tc * scale;
return ns;
}
long long Block::GetTrackNumber() const { return m_track; }
bool Block::IsKey() const {
return ((m_flags & static_cast<unsigned char>(1 << 7)) != 0);
}
void Block::SetKey(bool bKey) {
if (bKey)
m_flags |= static_cast<unsigned char>(1 << 7);
else
m_flags &= 0x7F;
}
bool Block::IsInvisible() const { return bool(int(m_flags & 0x08) != 0); }
Block::Lacing Block::GetLacing() const {
const int value = int(m_flags & 0x06) >> 1;
return static_cast<Lacing>(value);
}
int Block::GetFrameCount() const { return m_frame_count; }
const Block::Frame& Block::GetFrame(int idx) const {
assert(idx >= 0);
assert(idx < m_frame_count);
const Frame& f = m_frames[idx];
assert(f.pos > 0);
assert(f.len > 0);
return f;
}
long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const {
assert(pReader);
assert(buf);
const long status = pReader->Read(pos, len, buf);
return status;
}
long long Block::GetDiscardPadding() const { return m_discard_padding; }
} // end namespace mkvparser