| // Copyright (c) 2010 The WebM project authors. All Rights Reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style license | |
| // that can be found in the LICENSE file in the root of the source | |
| // tree. An additional intellectual property rights grant can be found | |
| // in the file PATENTS. All contributing project authors may | |
| // be found in the AUTHORS file in the root of the source tree. | |
| #include "mkvparser.hpp" | |
| #include <cassert> | |
| #include <cstring> | |
| #include <new> | |
| mkvparser::IMkvReader::~IMkvReader() | |
| { | |
| } | |
| void mkvparser::GetVersion(int& major, int& minor, int& build, int& revision) | |
| { | |
| major = 1; | |
| minor = 0; | |
| build = 0; | |
| revision = 1; | |
| return; | |
| } | |
| long long mkvparser::ReadUInt(IMkvReader* pReader, long long pos, long& len) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(pos < available); | |
| assert((available - pos) >= 1); //assume here max u-int len is 8 | |
| unsigned char b; | |
| hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| assert(hr == 0L); | |
| if (b & 0x80) //1000 0000 | |
| { | |
| len = 1; | |
| b &= 0x7F; //0111 1111 | |
| } | |
| else if (b & 0x40) //0100 0000 | |
| { | |
| len = 2; | |
| b &= 0x3F; //0011 1111 | |
| } | |
| else if (b & 0x20) //0010 0000 | |
| { | |
| len = 3; | |
| b &= 0x1F; //0001 1111 | |
| } | |
| else if (b & 0x10) //0001 0000 | |
| { | |
| len = 4; | |
| b &= 0x0F; //0000 1111 | |
| } | |
| else if (b & 0x08) //0000 1000 | |
| { | |
| len = 5; | |
| b &= 0x07; //0000 0111 | |
| } | |
| else if (b & 0x04) //0000 0100 | |
| { | |
| len = 6; | |
| b &= 0x03; //0000 0011 | |
| } | |
| else if (b & 0x02) //0000 0010 | |
| { | |
| len = 7; | |
| b &= 0x01; //0000 0001 | |
| } | |
| else | |
| { | |
| assert(b & 0x01); //0000 0001 | |
| len = 8; | |
| b = 0; //0000 0000 | |
| } | |
| assert((available - pos) >= len); | |
| long long result = b; | |
| ++pos; | |
| for (long i = 1; i < len; ++i) | |
| { | |
| hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| assert(hr == 0L); | |
| 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; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| if (pos >= available) | |
| return pos; //too few bytes available | |
| unsigned char b; | |
| hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| assert(hr == 0L); | |
| if (b == 0) //we can't handle u-int values larger than 8 bytes | |
| return E_FILE_FORMAT_INVALID; | |
| unsigned char m = 0x80; | |
| len = 1; | |
| while (!(b & m)) | |
| { | |
| m >>= 1; | |
| ++len; | |
| } | |
| return 0; //success | |
| } | |
| long long mkvparser::SyncReadUInt( | |
| IMkvReader* pReader, | |
| long long pos, | |
| long long stop, | |
| long& len) | |
| { | |
| assert(pReader); | |
| if (pos >= stop) | |
| return E_FILE_FORMAT_INVALID; | |
| unsigned char b; | |
| long hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| if (hr != 0L) | |
| 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; | |
| len = 1; | |
| while (!(b & m)) | |
| { | |
| m >>= 1; | |
| ++len; | |
| } | |
| if ((pos + len) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| long long result = b & (~m); | |
| ++pos; | |
| for (int i = 1; i < len; ++i) | |
| { | |
| hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| if (hr != 0L) | |
| return E_BUFFER_NOT_FULL; | |
| result <<= 8; | |
| result |= b; | |
| ++pos; | |
| } | |
| return result; | |
| } | |
| long long mkvparser::UnserializeUInt( | |
| IMkvReader* pReader, | |
| long long pos, | |
| long long size) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| assert(size > 0); | |
| assert(size <= 8); | |
| long long result = 0; | |
| for (long long i = 0; i < size; ++i) | |
| { | |
| unsigned char b; | |
| const long hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| result <<= 8; | |
| result |= b; | |
| ++pos; | |
| } | |
| return result; | |
| } | |
| float mkvparser::Unserialize4Float( | |
| IMkvReader* pReader, | |
| long long pos) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| assert((pos + 4) <= available); | |
| float result; | |
| unsigned char* const p = (unsigned char*)&result; | |
| unsigned char* q = p + 4; | |
| for (;;) | |
| { | |
| hr = pReader->Read(pos, 1, --q); | |
| assert(hr == 0L); | |
| if (q == p) | |
| break; | |
| ++pos; | |
| } | |
| return result; | |
| } | |
| double mkvparser::Unserialize8Double( | |
| IMkvReader* pReader, | |
| long long pos) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| double result; | |
| unsigned char* const p = (unsigned char*)&result; | |
| unsigned char* q = p + 8; | |
| for (;;) | |
| { | |
| const long hr = pReader->Read(pos, 1, --q); | |
| assert(hr == 0L); | |
| if (q == p) | |
| break; | |
| ++pos; | |
| } | |
| return result; | |
| } | |
| signed char mkvparser::Unserialize1SInt( | |
| IMkvReader* pReader, | |
| long long pos) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr == 0); | |
| assert(available <= total); | |
| assert(pos < available); | |
| signed char result; | |
| hr = pReader->Read(pos, 1, (unsigned char*)&result); | |
| assert(hr == 0); | |
| return result; | |
| } | |
| short mkvparser::Unserialize2SInt( | |
| IMkvReader* pReader, | |
| long long pos) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| assert((pos + 2) <= available); | |
| short result; | |
| unsigned char* const p = (unsigned char*)&result; | |
| unsigned char* q = p + 2; | |
| for (;;) | |
| { | |
| hr = pReader->Read(pos, 1, --q); | |
| assert(hr == 0L); | |
| if (q == p) | |
| break; | |
| ++pos; | |
| } | |
| return result; | |
| } | |
| bool mkvparser::Match( | |
| IMkvReader* pReader, | |
| long long& pos, | |
| unsigned long id_, | |
| long long& val) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| if ((unsigned long)id != id_) | |
| return false; | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); | |
| assert(size <= 8); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| pos += len; //consume length of size of payload | |
| val = UnserializeUInt(pReader, pos, size); | |
| assert(val >= 0); | |
| pos += size; //consume size of payload | |
| return true; | |
| } | |
| bool mkvparser::Match( | |
| IMkvReader* pReader, | |
| long long& pos, | |
| unsigned long id_, | |
| char*& val) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| if ((unsigned long)id != id_) | |
| return false; | |
| pos += len; //consume id | |
| const long long size_ = ReadUInt(pReader, pos, len); | |
| assert(size_ >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| pos += len; //consume length of size of payload | |
| assert((pos + size_) <= available); | |
| const size_t size = static_cast<size_t>(size_); | |
| val = new char[size+1]; | |
| for (size_t i = 0; i < size; ++i) | |
| { | |
| char c; | |
| hr = pReader->Read(pos + i, 1, (unsigned char*)&c); | |
| assert(hr == 0L); | |
| val[i] = c; | |
| if (c == '\0') | |
| break; | |
| } | |
| val[size] = '\0'; | |
| pos += size_; //consume size of payload | |
| return true; | |
| } | |
| #if 0 | |
| bool mkvparser::Match( | |
| IMkvReader* pReader, | |
| long long& pos, | |
| unsigned long id, | |
| wchar_t*& val) | |
| { | |
| char* str; | |
| if (!Match(pReader, pos, id, str)) | |
| return false; | |
| const size_t size = mbstowcs(NULL, str, 0); | |
| if (size == 0) | |
| val = NULL; | |
| else | |
| { | |
| val = new wchar_t[size+1]; | |
| mbstowcs(val, str, size); | |
| val[size] = L'\0'; | |
| } | |
| delete[] str; | |
| return true; | |
| } | |
| #endif | |
| 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 hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| if ((unsigned long)id != id_) | |
| return false; | |
| pos += len; //consume id | |
| const long long size_ = ReadUInt(pReader, pos, len); | |
| assert(size_ >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| assert((pos + len) <= available); | |
| pos += len; //consume length of size of payload | |
| assert((pos + size_) <= available); | |
| const long buflen_ = static_cast<long>(size_); | |
| buf = new (std::nothrow) unsigned char[buflen_]; | |
| assert(buf); //TODO | |
| hr = pReader->Read(pos, buflen_, buf); | |
| assert(hr == 0L); | |
| buflen = buflen_; | |
| pos += size_; //consume size of payload | |
| return true; | |
| } | |
| bool mkvparser::Match( | |
| IMkvReader* pReader, | |
| long long& pos, | |
| unsigned long id_, | |
| double& val) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| long idlen; | |
| const long long id = ReadUInt(pReader, pos, idlen); | |
| assert(id >= 0); //TODO | |
| if ((unsigned long)id != id_) | |
| return false; | |
| long sizelen; | |
| const long long size = ReadUInt(pReader, pos + idlen, sizelen); | |
| switch (size) | |
| { | |
| case 4: | |
| case 8: | |
| break; | |
| default: | |
| return false; | |
| } | |
| pos += idlen + sizelen; //consume id and size fields | |
| assert((pos + size) <= available); | |
| if (size == 4) | |
| val = Unserialize4Float(pReader, pos); | |
| else | |
| { | |
| assert(size == 8); | |
| val = Unserialize8Double(pReader, pos); | |
| } | |
| pos += size; //consume size of payload | |
| return true; | |
| } | |
| bool mkvparser::Match( | |
| IMkvReader* pReader, | |
| long long& pos, | |
| unsigned long id_, | |
| short& val) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); | |
| 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 <= 2); | |
| assert((pos + len) <= available); | |
| pos += len; //consume length of size of payload | |
| assert((pos + size) <= available); | |
| //TODO: | |
| // Generalize this to work for any size signed int | |
| if (size == 1) | |
| val = Unserialize1SInt(pReader, pos); | |
| else | |
| val = Unserialize2SInt(pReader, pos); | |
| pos += size; //consume size of payload | |
| return true; | |
| } | |
| namespace mkvparser | |
| { | |
| EBMLHeader::EBMLHeader(): | |
| m_docType(NULL) | |
| { | |
| } | |
| EBMLHeader::~EBMLHeader() | |
| { | |
| delete[] m_docType; | |
| } | |
| long long EBMLHeader::Parse( | |
| IMkvReader* pReader, | |
| long long& pos) | |
| { | |
| assert(pReader); | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| if (hr < 0) | |
| return hr; | |
| pos = 0; | |
| long long end = (1024 < available)? 1024: available; | |
| for (;;) | |
| { | |
| unsigned char b = 0; | |
| while (pos < end) | |
| { | |
| hr = pReader->Read(pos, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| if (b == 0x1A) | |
| break; | |
| ++pos; | |
| } | |
| if (b != 0x1A) | |
| { | |
| if ((pos >= 1024) || | |
| (available >= total) || | |
| ((total - available) < 5)) | |
| return -1; | |
| return available + 5; //5 = 4-byte ID + 1st byte of size | |
| } | |
| if ((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) //ReadId masks-off length indicator bits | |
| { | |
| assert(len == 4); | |
| pos += len; | |
| break; | |
| } | |
| ++pos; //throw away just the 0x1A byte, and try again | |
| } | |
| 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 - pos) < len) | |
| return E_FILE_FORMAT_INVALID; | |
| if ((available - pos) < len) | |
| return pos + len; //try again later | |
| result = ReadUInt(pReader, pos, len); | |
| if (result < 0) //error | |
| return result; | |
| pos += len; //consume u-int | |
| if ((total - pos) < result) | |
| return E_FILE_FORMAT_INVALID; | |
| if ((available - pos) < result) | |
| return pos + result; | |
| end = pos + result; | |
| m_version = 1; | |
| m_readVersion = 1; | |
| m_maxIdLength = 4; | |
| m_maxSizeLength = 8; | |
| m_docTypeVersion = 1; | |
| m_docTypeReadVersion = 1; | |
| while (pos < end) | |
| { | |
| if (Match(pReader, pos, 0x0286, m_version)) | |
| ; | |
| else if (Match(pReader, pos, 0x02F7, m_readVersion)) | |
| ; | |
| else if (Match(pReader, pos, 0x02F2, m_maxIdLength)) | |
| ; | |
| else if (Match(pReader, pos, 0x02F3, m_maxSizeLength)) | |
| ; | |
| else if (Match(pReader, pos, 0x0282, m_docType)) | |
| ; | |
| else if (Match(pReader, pos, 0x0287, m_docTypeVersion)) | |
| ; | |
| else if (Match(pReader, pos, 0x0285, m_docTypeReadVersion)) | |
| ; | |
| else | |
| { | |
| result = ReadUInt(pReader, pos, len); | |
| assert(result > 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| pos += len; | |
| assert(pos < end); | |
| result = ReadUInt(pReader, pos, len); | |
| assert(result >= 0); | |
| assert(len > 0); | |
| assert(len <= 8); | |
| pos += len + result; | |
| assert(pos <= end); | |
| } | |
| } | |
| assert(pos == end); | |
| return 0; | |
| } | |
| Segment::Segment( | |
| IMkvReader* pReader, | |
| long long start, | |
| long long size) : | |
| m_pReader(pReader), | |
| m_start(start), | |
| m_size(size), | |
| m_pos(start), | |
| m_pInfo(NULL), | |
| m_pTracks(NULL), | |
| m_clusterCount(0) | |
| { | |
| } | |
| Segment::~Segment() | |
| { | |
| Cluster** i = m_clusters; | |
| Cluster** j = m_clusters + m_clusterCount; | |
| while (i != j) | |
| { | |
| Cluster* p = *i++; | |
| assert(p); | |
| delete p; | |
| } | |
| delete[] m_clusters; | |
| delete m_pTracks; | |
| delete m_pInfo; | |
| } | |
| long long Segment::CreateInstance( | |
| IMkvReader* pReader, | |
| long long pos, | |
| Segment*& pSegment) | |
| { | |
| assert(pReader); | |
| assert(pos >= 0); | |
| pSegment = NULL; | |
| long long total, available; | |
| long hr = pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| //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. | |
| while (pos < total) | |
| { | |
| //Read ID | |
| long len; | |
| long long result = GetUIntLength(pReader, pos, len); | |
| if (result) //error, or too few available bytes | |
| return result; | |
| if ((pos + len) > total) | |
| return E_FILE_FORMAT_INVALID; | |
| if ((pos + len) > available) | |
| return pos + len; | |
| //TODO: if we liberalize the behavior of ReadUInt, we can | |
| //probably eliminate having to use GetUIntLength here. | |
| 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 ((pos + len) > total) | |
| return E_FILE_FORMAT_INVALID; | |
| if ((pos + len) > available) | |
| return pos + len; | |
| //TODO: if we liberalize the behavior of ReadUInt, we can | |
| //probably eliminate having to use GetUIntLength here. | |
| const long long size = ReadUInt(pReader, pos, len); | |
| if (size < 0) | |
| return size; | |
| pos += len; //consume length of size of element | |
| //Pos now points to start of payload | |
| if ((pos + size) > total) | |
| return E_FILE_FORMAT_INVALID; | |
| if (id == 0x08538067) //Segment ID | |
| { | |
| pSegment = new Segment(pReader, pos, size); | |
| assert(pSegment); //TODO | |
| return 0; //success | |
| } | |
| pos += size; //consume payload | |
| } | |
| assert(pos == total); | |
| pSegment = new Segment(pReader, pos, 0); | |
| assert(pSegment); //TODO | |
| return 0; //success (sort of) | |
| } | |
| 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; | |
| long hr = m_pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available <= total); | |
| const long long stop = m_start + m_size; | |
| assert(stop <= total); | |
| assert(m_pos <= stop); | |
| bool bQuit = false; | |
| while ((m_pos < stop) && !bQuit) | |
| { | |
| long long pos = m_pos; | |
| long len; | |
| long long result = GetUIntLength(m_pReader, pos, len); | |
| if (result) //error, or too few available bytes | |
| return result; | |
| if ((pos + len) > 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; | |
| pos += len; //consume ID | |
| //Read Size | |
| result = GetUIntLength(m_pReader, pos, len); | |
| if (result) //error, or too few available bytes | |
| return result; | |
| if ((pos + len) > 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) | |
| return size; | |
| pos += len; //consume length of size of element | |
| //Pos now points to start of payload | |
| if ((pos + size) > 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 | |
| { | |
| assert(m_pInfo == NULL); | |
| m_pInfo = new SegmentInfo(this, pos, size); | |
| assert(m_pInfo); //TODO | |
| if (m_pTracks) | |
| bQuit = true; | |
| } | |
| else if (id == 0x0654AE6B) //Tracks ID | |
| { | |
| assert(m_pTracks == NULL); | |
| m_pTracks = new Tracks(this, pos, size); | |
| assert(m_pTracks); //TODO | |
| if (m_pInfo) | |
| bQuit = true; | |
| } | |
| else if (id == 0x0F43B675) //Cluster ID | |
| { | |
| #if 0 | |
| if (m_pInfo == NULL) //TODO: liberalize | |
| ; | |
| else if (m_pTracks == NULL) | |
| ; | |
| else | |
| //ParseCluster(idpos, pos, size); | |
| Cluster::Parse(this, m_clusters, pos, size); | |
| #endif | |
| bQuit = true; | |
| } | |
| m_pos = pos + size; //consume payload | |
| } | |
| assert(m_pos <= stop); | |
| return 0; //success | |
| } | |
| long Segment::ParseCluster(Cluster*& pCluster, long long& pos_) const | |
| { | |
| pCluster = NULL; | |
| pos_ = -1; | |
| const long long stop = m_start + m_size; | |
| assert(m_pos <= stop); | |
| long long pos = m_pos; | |
| long long off = -1; | |
| while (pos < stop) | |
| { | |
| long len; | |
| const long long idpos = pos; | |
| const long long id = SyncReadUInt(m_pReader, pos, stop, len); | |
| if (id < 0) //error | |
| return static_cast<long>(id); | |
| if (id == 0) | |
| return E_FILE_FORMAT_INVALID; | |
| pos += len; //consume id | |
| assert(pos < stop); | |
| const long long size = SyncReadUInt(m_pReader, pos, stop, len); | |
| if (size < 0) //error | |
| return static_cast<long>(size); | |
| pos += len; //consume size | |
| assert(pos <= stop); | |
| if (size == 0) //weird | |
| continue; | |
| //pos now points to start of payload | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| if (off >= 0) | |
| { | |
| pos_ = idpos; | |
| break; | |
| } | |
| if (id == 0x0F43B675) //Cluster ID | |
| off = idpos - m_start; | |
| } | |
| Segment* const this_ = const_cast<Segment*>(this); | |
| const size_t idx = m_clusterCount; | |
| if (pos >= stop) | |
| { | |
| pos_ = stop; | |
| #if 0 | |
| if (off < 0) | |
| { | |
| pCluster = Cluster::CreateEndOfStream(this_, idx); | |
| return 1L; | |
| } | |
| #else | |
| if (off < 0) | |
| return 1L; | |
| #endif | |
| //Reading 0 bytes at pos might work too -- it would depend | |
| //on how the reader is implemented. | |
| unsigned char b; | |
| const long hr = m_pReader->Read(pos - 1, 1, &b); | |
| if (hr < 0) | |
| return hr; | |
| if (hr != 0L) | |
| return E_BUFFER_NOT_FULL; | |
| } | |
| assert(off >= 0); | |
| assert(pos_ >= m_start); | |
| assert(pos_ <= stop); | |
| pCluster = Cluster::Parse(this_, idx, off); | |
| return 0L; | |
| } | |
| bool Segment::AddCluster(Cluster* pCluster, long long pos) | |
| { | |
| assert(pos >= m_start); | |
| const long long stop = m_start + m_size; | |
| assert(pos <= stop); | |
| if (pCluster) | |
| m_clusters[pos] = pCluster; | |
| m_pos = pos; //m_pos >= stop is now we know we have all clusters | |
| return (pos >= stop); | |
| } | |
| long Segment::Load() | |
| { | |
| //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 stop = m_start + m_size; | |
| #ifdef _DEBUG | |
| { | |
| long long total, available; | |
| long hr = m_pReader->Length(&total, &available); | |
| assert(hr >= 0); | |
| assert(available >= total); | |
| assert(stop <= total); | |
| } | |
| #endif | |
| long long index = m_pos; | |
| m_clusterCount = 0; | |
| long long* fileposition_of_clusters = NULL; | |
| long long size_of_cluster_pos = 0; | |
| while (index < stop) | |
| { | |
| long len = 0; | |
| long long result = GetUIntLength(m_pReader, index, len); | |
| if (result < 0) //error | |
| return static_cast<long>(result); | |
| if ((index + len) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| const long long idpos = index; | |
| const long long id = ReadUInt(m_pReader, idpos, len); | |
| if (id < 0) //error | |
| return static_cast<long>(id); | |
| index += len; //consume ID | |
| //Read Size | |
| result = GetUIntLength(m_pReader, index, len); | |
| if (result < 0) //error | |
| return static_cast<long>(result); | |
| if ((index + len) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| const long long size = ReadUInt(m_pReader, index, len); | |
| if (size < 0) //error | |
| return static_cast<long>(size); | |
| index += len; //consume length of size of element | |
| if (id == 0x0F43B675) // Cluster ID | |
| { | |
| assert(fileposition_of_clusters); | |
| if (m_clusterCount >= size_of_cluster_pos) | |
| { | |
| size_of_cluster_pos *= 2; | |
| long long* const temp = new long long[size_of_cluster_pos]; | |
| memset(temp, 0, sizeof(long long) * size_of_cluster_pos); | |
| memcpy(temp, fileposition_of_clusters, sizeof(long long) \ | |
| * m_clusterCount); | |
| delete [] fileposition_of_clusters; | |
| fileposition_of_clusters = temp; | |
| } | |
| fileposition_of_clusters[m_clusterCount] = idpos; | |
| ++m_clusterCount; | |
| } | |
| else if (id == 0x0549A966) //Segment Info ID | |
| { | |
| assert(m_pInfo == NULL); | |
| m_pInfo = new SegmentInfo(this, index, size); | |
| assert(m_pInfo); | |
| assert(m_clusterCount == 0); | |
| const long long duration = m_pInfo->GetDuration(); | |
| size_of_cluster_pos = duration / 1000000000 + 1; | |
| fileposition_of_clusters = new long long[size_of_cluster_pos]; | |
| memset(fileposition_of_clusters, 0, size_of_cluster_pos); | |
| } | |
| index += size; | |
| } | |
| if (m_clusterCount == 0) | |
| return -1L; | |
| m_clusters = new Cluster*[m_clusterCount]; | |
| for (int i = 0; i < m_clusterCount; ++i) | |
| m_clusters[i] = Cluster::Parse(this, i, fileposition_of_clusters[i]); | |
| delete [] fileposition_of_clusters; | |
| while (m_pos < stop) | |
| { | |
| long long pos = m_pos; | |
| long len; | |
| long long result = GetUIntLength(m_pReader, pos, len); | |
| if (result < 0) //error | |
| return static_cast<long>(result); | |
| if ((pos + len) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| const long long idpos = pos; | |
| const long long id = ReadUInt(m_pReader, idpos, len); | |
| if (id < 0) //error | |
| return static_cast<long>(id); | |
| pos += len; //consume ID | |
| //Read Size | |
| result = GetUIntLength(m_pReader, pos, len); | |
| if (result < 0) //error | |
| return static_cast<long>(result); | |
| if ((pos + len) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| 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 ((pos + size) > stop) | |
| return E_FILE_FORMAT_INVALID; | |
| if (id == 0x0F43B675) //Cluster ID | |
| break; | |
| if (id == 0x0654AE6B) //Tracks ID | |
| { | |
| assert(m_pTracks == NULL); | |
| m_pTracks = new Tracks(this, pos, size); | |
| assert(m_pTracks); //TODO | |
| } | |
| m_pos = pos + size; //consume payload | |
| } | |
| assert(m_clusters); | |
| //TODO: see notes above. This check is here (temporarily) to ensure | |
| //that the first seekhead has entries for the clusters (because that's | |
| //when they're loaded). In case we are given a file that lists the | |
| //clusters in a second seekhead, the worst thing that happens is that | |
| //we treat this as an invalid file (which is better then simply | |
| //asserting somewhere). But that's only a work-around. What we need | |
| //to do is be able to handle having multiple seekheads, and having | |
| //clusters listed somewhere besides the first seekhead. | |
| // | |
| //if (m_clusters == NULL) | |
| // return E_FILE_FORMAT_INVALID; | |
| //NOTE: we stop parsing when we reach the first cluster, under the | |
| //assumption all clusters are named in some SeekHead. Clusters | |
| //will have been (pre)loaded, so we indicate that we have all clusters | |
| //by adjusting the parse position: | |
| m_pos = stop; //means "we have all clusters" | |
| return 0L; | |
| } | |
| void Segment::ParseSeekHead(long long start, long long size_, size_t* pIndex) | |
| { | |
| long long pos = start; | |
| const long long stop = start + size_; | |
| while (pos < stop) | |
| { | |
| long len; | |
| const long long id = ReadUInt(m_pReader, pos, len); | |
| assert(id >= 0); //TODO | |
| assert((pos + len) <= stop); | |
| pos += len; //consume ID | |
| const long long size = ReadUInt(m_pReader, pos, len); | |
| assert(size >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume Size field | |
| assert((pos + size) <= stop); | |
| if (id == 0x0DBB) //SeekEntry ID | |
| ParseSeekEntry(pos, size, pIndex); | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| assert(pos == stop); | |
| } | |
| void Segment::ParseSecondarySeekHead(long long off, size_t* pIndex) | |
| { | |
| assert(off >= 0); | |
| assert(off < m_size); | |
| long long pos = m_start + off; | |
| const long long stop = m_start + m_size; | |
| long len; | |
| long long result = GetUIntLength(m_pReader, pos, len); | |
| assert(result == 0); | |
| assert((pos + len) <= stop); | |
| const long long idpos = pos; | |
| const long long id = ReadUInt(m_pReader, idpos, len); | |
| assert(id == 0x014D9B74); //SeekHead ID | |
| pos += len; //consume ID | |
| assert(pos < stop); | |
| //Read Size | |
| result = GetUIntLength(m_pReader, pos, len); | |
| assert(result == 0); | |
| assert((pos + len) <= stop); | |
| const long long size = ReadUInt(m_pReader, pos, len); | |
| assert(size >= 0); | |
| pos += len; //consume length of size of element | |
| assert((pos + size) <= stop); | |
| //Pos now points to start of payload | |
| ParseSeekHead(pos, size, pIndex); | |
| } | |
| void Segment::ParseSeekEntry(long long start, long long size_, size_t* pIndex) | |
| { | |
| long long pos = start; | |
| const long long stop = start + size_; | |
| long len; | |
| const long long seekIdId = ReadUInt(m_pReader, pos, len); | |
| //seekIdId; | |
| assert(seekIdId == 0x13AB); //SeekID ID | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long seekIdSize = ReadUInt(m_pReader, pos, len); | |
| assert(seekIdSize >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume size | |
| const long long seekId = ReadUInt(m_pReader, pos, len); //payload | |
| assert(seekId >= 0); | |
| assert(len == seekIdSize); | |
| assert((pos + len) <= stop); | |
| pos += seekIdSize; //consume payload | |
| const long long seekPosId = ReadUInt(m_pReader, pos, len); | |
| //seekPosId; | |
| assert(seekPosId == 0x13AC); //SeekPos ID | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long seekPosSize = ReadUInt(m_pReader, pos, len); | |
| assert(seekPosSize >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume size | |
| assert((pos + seekPosSize) <= stop); | |
| const long long seekOff = UnserializeUInt(m_pReader, pos, seekPosSize); | |
| assert(seekOff >= 0); | |
| assert(seekOff < m_size); | |
| pos += seekPosSize; //consume payload | |
| assert(pos == stop); | |
| const long long seekPos = m_start + seekOff; | |
| assert(seekPos < (m_start + m_size)); | |
| if (seekId == 0x0F43B675) //cluster id | |
| { | |
| if (pIndex == NULL) | |
| ++m_clusterCount; | |
| else | |
| { | |
| assert(m_clusters); | |
| assert(m_clusterCount > 0); | |
| size_t& index = *pIndex; | |
| assert(index < m_clusterCount); | |
| Cluster*& pCluster = m_clusters[index]; | |
| pCluster = Cluster::Parse(this, index, seekOff); | |
| assert(pCluster); //TODO | |
| ++index; | |
| } | |
| } | |
| else if (seekId == 0x014D9B74) //SeekHead ID | |
| { | |
| ParseSecondarySeekHead(seekOff, pIndex); | |
| } | |
| } | |
| long long Segment::Unparsed() const | |
| { | |
| const long long stop = m_start + m_size; | |
| const long long result = stop - m_pos; | |
| assert(result >= 0); | |
| return result; | |
| } | |
| #if 0 //NOTE: too inefficient | |
| long long Segment::Load(long long time_ns) | |
| { | |
| if (Unparsed() <= 0) | |
| return 0; | |
| while (m_clusters.empty()) | |
| { | |
| const long long result = Parse(); | |
| if (result) //error, or not enough bytes available | |
| return result; | |
| if (Unparsed() <= 0) | |
| return 0; | |
| } | |
| while (m_clusters.back()->GetTime() < time_ns) | |
| { | |
| const long long result = Parse(); | |
| if (result) //error, or not enough bytes available | |
| return result; | |
| if (Unparsed() <= 0) | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| #endif | |
| Cluster* Segment::GetFirst() | |
| { | |
| if ((m_clusters == NULL) || (m_clusterCount <= 0)) | |
| return &m_eos; | |
| Cluster* const pCluster = m_clusters[0]; | |
| assert(pCluster); | |
| return pCluster; | |
| } | |
| Cluster* Segment::GetLast() | |
| { | |
| if ((m_clusters == NULL) || (m_clusterCount <= 0)) | |
| return &m_eos; | |
| const size_t idx = m_clusterCount - 1; | |
| Cluster* const pCluster = m_clusters[idx]; | |
| assert(pCluster); | |
| return pCluster; | |
| } | |
| unsigned long Segment::GetCount() const | |
| { | |
| //TODO: m_clusterCount should not be long long. | |
| return static_cast<unsigned long>(m_clusterCount); | |
| } | |
| Cluster* Segment::GetNext(const Cluster* pCurr) | |
| { | |
| assert(pCurr); | |
| assert(pCurr != &m_eos); | |
| assert(m_clusters); | |
| assert(m_clusterCount > 0); | |
| size_t idx = pCurr->m_index; | |
| assert(idx < m_clusterCount); | |
| assert(pCurr == m_clusters[idx]); | |
| idx++; | |
| if (idx >= m_clusterCount) | |
| return &m_eos; | |
| Cluster* const pNext = m_clusters[idx]; | |
| assert(pNext); | |
| return pNext; | |
| } | |
| Cluster* Segment::GetCluster(long long time_ns) | |
| { | |
| 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 | |
| size_t i = 0; | |
| size_t j = m_clusterCount; | |
| while (i < j) | |
| { | |
| //INVARIANT: | |
| //[0, i) <= time_ns | |
| //[i, j) ? | |
| //[j, m_clusterCount) > time_ns | |
| const size_t 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 size_t k = i - 1; | |
| Cluster* const pCluster = m_clusters[k]; | |
| assert(pCluster); | |
| assert(pCluster->m_index == k); | |
| assert(pCluster->GetTime() <= time_ns); | |
| return pCluster; | |
| } | |
| Tracks* Segment::GetTracks() const | |
| { | |
| return m_pTracks; | |
| } | |
| const SegmentInfo* const Segment::GetInfo() const | |
| { | |
| return m_pInfo; | |
| } | |
| long long Segment::GetDuration() const | |
| { | |
| assert(m_pInfo); | |
| return m_pInfo->GetDuration(); | |
| } | |
| SegmentInfo::SegmentInfo(Segment* pSegment, long long start, long long size_) : | |
| m_pSegment(pSegment), | |
| m_start(start), | |
| m_size(size_), | |
| m_pMuxingAppAsUTF8(NULL), | |
| m_pWritingAppAsUTF8(NULL), | |
| m_pTitleAsUTF8(NULL) | |
| { | |
| IMkvReader* const pReader = m_pSegment->m_pReader; | |
| long long pos = start; | |
| const long long stop = start + size_; | |
| m_timecodeScale = 1000000; | |
| m_duration = 0; | |
| while (pos < stop) | |
| { | |
| if (Match(pReader, pos, 0x0AD7B1, m_timecodeScale)) | |
| assert(m_timecodeScale > 0); | |
| else if (Match(pReader, pos, 0x0489, m_duration)) | |
| assert(m_duration >= 0); | |
| else if (Match(pReader, pos, 0x0D80, m_pMuxingAppAsUTF8)) //[4D][80] | |
| assert(m_pMuxingAppAsUTF8); | |
| else if (Match(pReader, pos, 0x1741, m_pWritingAppAsUTF8)) //[57][41] | |
| assert(m_pWritingAppAsUTF8); | |
| else if (Match(pReader, pos, 0x3BA9, m_pTitleAsUTF8)) //[7B][A9] | |
| assert(m_pTitleAsUTF8); | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| //id; | |
| assert(id >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| assert((stop - pos) > 0); | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len + size; //consume size and payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| assert(pos == stop); | |
| } | |
| SegmentInfo::~SegmentInfo() | |
| { | |
| if (m_pMuxingAppAsUTF8) | |
| { | |
| delete[] m_pMuxingAppAsUTF8; | |
| m_pMuxingAppAsUTF8 = NULL; | |
| } | |
| if (m_pWritingAppAsUTF8) | |
| { | |
| delete[] m_pWritingAppAsUTF8; | |
| m_pWritingAppAsUTF8 = NULL; | |
| } | |
| if (m_pTitleAsUTF8) | |
| { | |
| delete[] m_pTitleAsUTF8; | |
| m_pTitleAsUTF8 = NULL; | |
| } | |
| } | |
| long long SegmentInfo::GetTimeCodeScale() const | |
| { | |
| return m_timecodeScale; | |
| } | |
| long long SegmentInfo::GetDuration() const | |
| { | |
| assert(m_duration >= 0); | |
| 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; | |
| } | |
| Track::Track(Segment* pSegment, const Info& i) : | |
| m_pSegment(pSegment), | |
| m_info(i) | |
| { | |
| } | |
| Track::~Track() | |
| { | |
| Info& info = const_cast<Info&>(m_info); | |
| info.Clear(); | |
| } | |
| Track::Info::Info(): | |
| type(-1), | |
| number(-1), | |
| uid(-1), | |
| nameAsUTF8(NULL), | |
| codecId(NULL), | |
| codecPrivate(NULL), | |
| codecPrivateSize(0), | |
| codecNameAsUTF8(NULL) | |
| { | |
| } | |
| void Track::Info::Clear() | |
| { | |
| delete[] nameAsUTF8; | |
| nameAsUTF8 = NULL; | |
| delete[] codecId; | |
| codecId = NULL; | |
| delete[] codecPrivate; | |
| codecPrivate = NULL; | |
| codecPrivateSize = 0; | |
| delete[] codecNameAsUTF8; | |
| codecNameAsUTF8 = NULL; | |
| } | |
| const BlockEntry* Track::GetEOS() const | |
| { | |
| return &m_eos; | |
| } | |
| long long Track::GetType() const | |
| { | |
| const unsigned long result = static_cast<unsigned long>(m_info.type); | |
| return result; | |
| } | |
| unsigned long Track::GetNumber() const | |
| { | |
| assert(m_info.number >= 0); | |
| const unsigned long result = static_cast<unsigned long>(m_info.number); | |
| return result; | |
| } | |
| const char* Track::GetNameAsUTF8() const | |
| { | |
| return m_info.nameAsUTF8; | |
| } | |
| 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; | |
| } | |
| long Track::GetFirst(const BlockEntry*& pBlockEntry) const | |
| { | |
| Cluster* const pCluster = m_pSegment->GetFirst(); | |
| //If Segment::GetFirst returns NULL, then this must be a network | |
| //download, and we haven't loaded any clusters yet. In this case, | |
| //returning NULL from Track::GetFirst means the same thing. | |
| if ((pCluster == NULL) || pCluster->EOS()) | |
| { | |
| pBlockEntry = NULL; | |
| return E_BUFFER_NOT_FULL; //return 1L instead? | |
| } | |
| pBlockEntry = pCluster->GetFirst(); | |
| while (pBlockEntry) | |
| { | |
| const Block* const pBlock = pBlockEntry->GetBlock(); | |
| assert(pBlock); | |
| if (pBlock->GetTrackNumber() == (unsigned long)m_info.number) | |
| return 0L; | |
| pBlockEntry = pCluster->GetNext(pBlockEntry); | |
| } | |
| //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 1L; | |
| } | |
| long Track::GetNext(const BlockEntry* pCurrEntry, const BlockEntry*& pNextEntry) const | |
| { | |
| assert(pCurrEntry); | |
| assert(!pCurrEntry->EOS()); //? | |
| assert(pCurrEntry->GetBlock()->GetTrackNumber() == (unsigned long)m_info.number); | |
| const Cluster* const pCurrCluster = pCurrEntry->GetCluster(); | |
| assert(pCurrCluster); | |
| assert(!pCurrCluster->EOS()); | |
| pNextEntry = pCurrCluster->GetNext(pCurrEntry); | |
| while (pNextEntry) | |
| { | |
| const Block* const pNextBlock = pNextEntry->GetBlock(); | |
| assert(pNextBlock); | |
| if (pNextBlock->GetTrackNumber() == (unsigned long)m_info.number) | |
| return 0L; | |
| pNextEntry = pCurrCluster->GetNext(pNextEntry); | |
| } | |
| Segment* pSegment = pCurrCluster->m_pSegment; | |
| Cluster* const pNextCluster = pSegment->GetNext(pCurrCluster); | |
| if ((pNextCluster == NULL) || pNextCluster->EOS()) | |
| { | |
| if (pSegment->Unparsed() <= 0) //all clusters have been loaded | |
| { | |
| pNextEntry = GetEOS(); | |
| return 1L; | |
| } | |
| pNextEntry = NULL; | |
| return E_BUFFER_NOT_FULL; | |
| } | |
| pNextEntry = pNextCluster->GetFirst(); | |
| while (pNextEntry) | |
| { | |
| const Block* const pNextBlock = pNextEntry->GetBlock(); | |
| assert(pNextBlock); | |
| if (pNextBlock->GetTrackNumber() == (unsigned long)m_info.number) | |
| return 0L; | |
| pNextEntry = pNextCluster->GetNext(pNextEntry); | |
| } | |
| //TODO: what has happened here is that we did not find a block | |
| //with a matching track number on the next cluster. It might | |
| //be the case that some cluster beyond the next cluster | |
| //contains a block having a matching track number, but for | |
| //now we terminate the search immediately. We do this so that | |
| //we don't end up searching the entire file looking for the | |
| //next block. Another possibility is to try searching for the next | |
| //block in a small, fixed number of clusters (intead searching | |
| //just the next one), or to terminate the search when when the | |
| //there is a large gap in time, or large gap in file position. It | |
| //might very well be the case that the approach we use here is | |
| //unnecessarily conservative. | |
| //TODO: again, here's a case where we need to return the special | |
| //EOS block. Or something. It's OK if pNext is NULL, because | |
| //we only need it to set the stop time of the media sample. | |
| //(The start time is determined from pCurr, which is non-NULL | |
| //and non-EOS.) The problem is when we set pCurr=pNext; when | |
| //pCurr has the value NULL we interpret that to mean that we | |
| //haven't fully initialized pCurr and we attempt to set it to | |
| //point to the first block for this track. But that's not what | |
| //we want at all; we want the next call to PopulateSample to | |
| //return end-of-stream, not (re)start from the beginning. | |
| // | |
| //One work-around is to send EOS immediately. We would send | |
| //the EOS the next pass anyway, so maybe it's no great loss. The | |
| //only problem is that if this the stream really does end one | |
| //cluster early (relative to other tracks), or the last frame | |
| //happens to be a keyframe ("CanSeekToEnd"). | |
| // | |
| //The problem is that we need a way to mark as stream as | |
| //"at end of stream" without actually being at end of stream. | |
| //We need to give pCurr some value that means "you've reached EOS". | |
| //We can't synthesize the special EOS Cluster immediately | |
| //(when we first open the file, say), because we use the existance | |
| //of that special cluster value to mean that we've read all of | |
| //the clusters (this is a network download, so we can't know apriori | |
| //how many we have). | |
| // | |
| //Or, we could return E_FAIL, and set another bit in the stream | |
| //object itself, to indicate that it should send EOS earlier | |
| //than when (pCurr=pStop). | |
| // | |
| //Or, probably the best solution, when we actually load the | |
| //blocks into a cluster: if we notice that there's no block | |
| //for a track, we synthesize a nonce EOS block for that track. | |
| //That way we always have something to return. But that will | |
| //only work for sequential scan??? | |
| //pNext = NULL; | |
| //return E_FAIL; | |
| pNextEntry = GetEOS(); | |
| return 1L; | |
| } | |
| Track::EOSBlock::EOSBlock() | |
| { | |
| } | |
| bool Track::EOSBlock::EOS() const | |
| { | |
| return true; | |
| } | |
| Cluster* Track::EOSBlock::GetCluster() const | |
| { | |
| return NULL; | |
| } | |
| size_t Track::EOSBlock::GetIndex() const | |
| { | |
| return 0; | |
| } | |
| const Block* Track::EOSBlock::GetBlock() const | |
| { | |
| return NULL; | |
| } | |
| bool Track::EOSBlock::IsBFrame() const | |
| { | |
| return false; | |
| } | |
| VideoTrack::VideoTrack(Segment* pSegment, const Info& i) : | |
| Track(pSegment, i), | |
| m_width(-1), | |
| m_height(-1), | |
| m_rate(-1) | |
| { | |
| assert(i.type == 1); | |
| assert(i.number > 0); | |
| IMkvReader* const pReader = pSegment->m_pReader; | |
| const Settings& s = i.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) | |
| { | |
| #ifdef _DEBUG | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| #endif | |
| if (Match(pReader, pos, 0x30, m_width)) | |
| ; | |
| else if (Match(pReader, pos, 0x3A, m_height)) | |
| ; | |
| else if (Match(pReader, pos, 0x0383E3, m_rate)) | |
| ; | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume length of size | |
| assert((pos + size) <= stop); | |
| //pos now designates start of payload | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| return; | |
| } | |
| bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const | |
| { | |
| assert(pBlockEntry); | |
| const Block* const pBlock = pBlockEntry->GetBlock(); | |
| assert(pBlock); | |
| assert(pBlock->GetTrackNumber() == (unsigned long)m_info.number); | |
| return pBlock->IsKey(); | |
| } | |
| long long VideoTrack::GetWidth() const | |
| { | |
| return m_width; | |
| } | |
| long long VideoTrack::GetHeight() const | |
| { | |
| return m_height; | |
| } | |
| double VideoTrack::GetFrameRate() const | |
| { | |
| return m_rate; | |
| } | |
| AudioTrack::AudioTrack(Segment* pSegment, const Info& i) : | |
| Track(pSegment, i), | |
| m_rate(0.0), | |
| m_channels(0), | |
| m_bitDepth(-1) | |
| { | |
| assert(i.type == 2); | |
| assert(i.number > 0); | |
| IMkvReader* const pReader = pSegment->m_pReader; | |
| const Settings& s = i.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) | |
| { | |
| #ifdef _DEBUG | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| #endif | |
| if (Match(pReader, pos, 0x35, m_rate)) | |
| ; | |
| else if (Match(pReader, pos, 0x1F, m_channels)) | |
| ; | |
| else if (Match(pReader, pos, 0x2264, m_bitDepth)) | |
| ; | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume length of size | |
| assert((pos + size) <= stop); | |
| //pos now designates start of payload | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| return; | |
| } | |
| bool AudioTrack::VetEntry(const BlockEntry* pBlockEntry) const | |
| { | |
| assert(pBlockEntry); | |
| const Block* const pBlock = pBlockEntry->GetBlock(); | |
| assert(pBlock); | |
| assert(pBlock->GetTrackNumber() == (unsigned long)m_info.number); | |
| return true; | |
| } | |
| 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_) : | |
| m_pSegment(pSegment), | |
| m_start(start), | |
| m_size(size_), | |
| m_trackEntries(NULL), | |
| m_trackEntriesEnd(NULL) | |
| { | |
| long long stop = m_start + m_size; | |
| IMkvReader* const pReader = m_pSegment->m_pReader; | |
| long long pos1 = m_start; | |
| int count = 0; | |
| while (pos1 < stop) | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos1, len); | |
| assert(id >= 0); | |
| assert((pos1 + len) <= stop); | |
| pos1 += len; //consume id | |
| const long long size = ReadUInt(pReader, pos1, len); | |
| assert(size >= 0); | |
| assert((pos1 + len) <= stop); | |
| pos1 += len; //consume length of size | |
| //pos now desinates start of element | |
| if (id == 0x2E) //TrackEntry ID | |
| ++count; | |
| pos1 += size; //consume payload | |
| assert(pos1 <= stop); | |
| } | |
| if (count <= 0) | |
| return; | |
| m_trackEntries = new Track*[count]; | |
| m_trackEntriesEnd = m_trackEntries; | |
| long long pos = m_start; | |
| 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 size1 = ReadUInt(pReader, pos, len); | |
| assert(size1 >= 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume length of size | |
| //pos now desinates start of element | |
| if (id == 0x2E) //TrackEntry ID | |
| ParseTrackEntry(pos, size1, *m_trackEntriesEnd++); | |
| pos += size1; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| unsigned long Tracks::GetTracksCount() const | |
| { | |
| const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; | |
| assert(result >= 0); | |
| return static_cast<unsigned long>(result); | |
| } | |
| void Tracks::ParseTrackEntry( | |
| long long start, | |
| long long size, | |
| Track*& pTrack) | |
| { | |
| IMkvReader* const pReader = m_pSegment->m_pReader; | |
| long long pos = start; | |
| const long long stop = start + size; | |
| Track::Info i; | |
| Track::Settings videoSettings; | |
| videoSettings.start = -1; | |
| Track::Settings audioSettings; | |
| audioSettings.start = -1; | |
| while (pos < stop) | |
| { | |
| #ifdef _DEBUG | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| len; | |
| id; | |
| #endif | |
| if (Match(pReader, pos, 0x57, i.number)) | |
| assert(i.number > 0); | |
| else if (Match(pReader, pos, 0x33C5, i.uid)) | |
| ; | |
| else if (Match(pReader, pos, 0x03, i.type)) | |
| ; | |
| else if (Match(pReader, pos, 0x136E, i.nameAsUTF8)) | |
| assert(i.nameAsUTF8); | |
| else if (Match(pReader, pos, 0x06, i.codecId)) | |
| ; | |
| else if (Match(pReader, pos, 0x23A2, i.codecPrivate, i.codecPrivateSize)) | |
| ; | |
| else if (Match(pReader, pos, 0x058688, i.codecNameAsUTF8)) | |
| assert(i.codecNameAsUTF8); | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); //TODO: handle error case | |
| assert((pos + len) <= stop); | |
| pos += len; //consume length of size | |
| const long long start = pos; | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| if (id == 0x60) | |
| { | |
| videoSettings.start = start; | |
| videoSettings.size = size; | |
| } | |
| else if (id == 0x61) | |
| { | |
| audioSettings.start = start; | |
| audioSettings.size = size; | |
| } | |
| } | |
| } | |
| assert(pos == stop); | |
| //TODO: propertly vet info.number, to ensure both its existence, | |
| //and that it is unique among all tracks. | |
| assert(i.number > 0); | |
| //TODO: vet settings, to ensure that video settings (0x60) | |
| //were specified when type = 1, and that audio settings (0x61) | |
| //were specified when type = 2. | |
| if (i.type == 1) //video | |
| { | |
| assert(audioSettings.start < 0); | |
| assert(videoSettings.start >= 0); | |
| i.settings = videoSettings; | |
| VideoTrack* const t = new VideoTrack(m_pSegment, i); | |
| assert(t); //TODO | |
| pTrack = t; | |
| } | |
| else if (i.type == 2) //audio | |
| { | |
| assert(videoSettings.start < 0); | |
| assert(audioSettings.start >= 0); | |
| i.settings = audioSettings; | |
| AudioTrack* const t = new AudioTrack(m_pSegment, i); | |
| assert(t); //TODO | |
| pTrack = t; | |
| } | |
| else | |
| { | |
| // for now we do not support other track types yet. | |
| // TODO: support other track types | |
| i.Clear(); | |
| pTrack = NULL; | |
| } | |
| return; | |
| } | |
| Tracks::~Tracks() | |
| { | |
| Track** i = m_trackEntries; | |
| Track** const j = m_trackEntriesEnd; | |
| while (i != j) | |
| { | |
| Track* pTrack = *i++; | |
| delete pTrack; | |
| pTrack = NULL; | |
| } | |
| delete[] m_trackEntries; | |
| } | |
| Track* Tracks::GetTrackByNumber(unsigned long tn) const | |
| { | |
| 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 | |
| } | |
| 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]; | |
| } | |
| void Cluster::Load() | |
| { | |
| assert(m_pSegment); | |
| if (m_start > 0) | |
| { | |
| assert(m_size > 0); | |
| assert(m_timecode >= 0); | |
| return; | |
| } | |
| assert(m_size == 0); | |
| assert(m_timecode < 0); | |
| IMkvReader* const pReader = m_pSegment->m_pReader; | |
| long long pos = -m_start; | |
| long len; | |
| const long long id_ = ReadUInt(pReader, pos, len); | |
| assert(id_ >= 0); | |
| assert(id_ == 0x0F43B675); //Cluster ID | |
| pos += len; //consume id | |
| const long long size_ = ReadUInt(pReader, pos, len); | |
| assert(size_ >= 0); | |
| pos += len; //consume size | |
| m_start = pos; | |
| m_size = size_; | |
| const long long stop = m_start + size_; | |
| long long timecode = -1; | |
| while (pos < stop) | |
| { | |
| if (Match(pReader, pos, 0x67, timecode)) | |
| break; | |
| else | |
| { | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); //TODO | |
| assert((pos + len) <= stop); | |
| pos += len; //consume size | |
| if (id == 0x20) //BlockGroup ID | |
| break; | |
| if (id == 0x23) //SimpleBlock ID | |
| break; | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| assert(pos <= stop); | |
| assert(timecode >= 0); | |
| m_timecode = timecode; | |
| } | |
| Cluster* Cluster::Parse( | |
| Segment* pSegment, | |
| size_t idx, | |
| long long off) | |
| { | |
| assert(pSegment); | |
| assert(off >= 0); | |
| assert(off < pSegment->m_size); | |
| Cluster* const pCluster = new Cluster(pSegment, idx, -off); | |
| assert(pCluster); | |
| return pCluster; | |
| } | |
| Cluster::Cluster() : | |
| m_pSegment(NULL), | |
| m_index(0), | |
| m_start(0), | |
| m_size(0), | |
| m_timecode(0), | |
| m_pEntries(NULL), | |
| m_entriesCount(0) | |
| { | |
| } | |
| Cluster::Cluster( | |
| Segment* pSegment, | |
| size_t idx, | |
| long long off) : | |
| m_pSegment(pSegment), | |
| m_index(idx), | |
| m_start(off), | |
| m_size(0), | |
| m_timecode(-1), | |
| m_pEntries(NULL), | |
| m_entriesCount(0) | |
| { | |
| } | |
| Cluster::~Cluster() | |
| { | |
| #if 0 | |
| while (!m_pEntries.empty()) | |
| { | |
| BlockEntry* pBlockEntry = m_pEntries.front(); | |
| assert(pBlockEntry); | |
| m_pEntries.pop_front(); | |
| delete pBlockEntry; | |
| } | |
| #else | |
| BlockEntry** i = m_pEntries; | |
| BlockEntry** const j = m_pEntries + m_entriesCount; | |
| while (i != j) | |
| { | |
| BlockEntry* p = *i++; | |
| assert(p); | |
| delete p; | |
| } | |
| delete[] m_pEntries; | |
| #endif | |
| } | |
| bool Cluster::EOS() const | |
| { | |
| return (m_pSegment == 0); | |
| } | |
| void Cluster::LoadBlockEntries() | |
| { | |
| if (m_pEntries) | |
| return; | |
| Load(); | |
| assert(m_timecode >= 0); | |
| assert(m_start > 0); | |
| assert(m_size > 0); | |
| IMkvReader* const pReader = m_pSegment->m_pReader; | |
| long long pos = m_start; | |
| const long long stop = m_start + m_size; | |
| long long timecode = -1; | |
| long long idx = pos; | |
| m_entriesCount = 0; | |
| while (idx < stop) | |
| { | |
| if (Match(pReader, idx, 0x67, timecode)) | |
| assert(timecode == m_timecode); | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, idx, len); | |
| assert(id >= 0); //TODO | |
| assert((idx + len) <= stop); | |
| idx += len; //consume id | |
| const long long size = ReadUInt(pReader, idx, len); | |
| assert(size >= 0); //TODO | |
| assert((idx + len) <= stop); | |
| idx += len; //consume size | |
| if (id == 0x20) //BlockGroup ID | |
| ++m_entriesCount; | |
| else if (id == 0x23) //SimpleBlock ID | |
| ++m_entriesCount; | |
| idx += size; //consume payload | |
| assert(idx <= stop); | |
| } | |
| } | |
| if (m_entriesCount == 0) | |
| return; | |
| m_pEntries = new BlockEntry*[m_entriesCount]; | |
| size_t index = 0; | |
| while (pos < stop) | |
| { | |
| if (Match(pReader, pos, 0x67, timecode)) | |
| assert(timecode == m_timecode); | |
| else | |
| { | |
| long len; | |
| const long long id = ReadUInt(pReader, pos, len); | |
| assert(id >= 0); //TODO | |
| assert((pos + len) <= stop); | |
| pos += len; //consume id | |
| const long long size = ReadUInt(pReader, pos, len); | |
| assert(size >= 0); //TODO | |
| assert((pos + len) <= stop); | |
| pos += len; //consume size | |
| if (id == 0x20) //BlockGroup ID | |
| ParseBlockGroup(pos, size, index++); | |
| else if (id == 0x23) //SimpleBlock ID | |
| ParseSimpleBlock(pos, size, index++); | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| assert(pos == stop); | |
| assert(timecode >= 0); | |
| assert(index == m_entriesCount); | |
| } | |
| long long Cluster::GetTimeCode() | |
| { | |
| Load(); | |
| return m_timecode; | |
| } | |
| long long Cluster::GetTime() | |
| { | |
| const long long tc = GetTimeCode(); | |
| assert(tc >= 0); | |
| 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; | |
| } | |
| void Cluster::ParseBlockGroup(long long start, long long size, size_t index) | |
| { | |
| assert(m_pEntries); | |
| assert(m_entriesCount); | |
| assert(index < m_entriesCount); | |
| BlockGroup* const pGroup = new BlockGroup(this, index, start, size); | |
| assert(pGroup); //TODO | |
| m_pEntries[index] = pGroup; | |
| } | |
| void Cluster::ParseSimpleBlock(long long start, long long size, size_t index) | |
| { | |
| assert(m_pEntries); | |
| assert(m_entriesCount); | |
| assert(index < m_entriesCount); | |
| SimpleBlock* const pSimpleBlock = new SimpleBlock(this, index, start, size); | |
| assert(pSimpleBlock); //TODO | |
| m_pEntries[index] = pSimpleBlock; | |
| } | |
| const BlockEntry* Cluster::GetFirst() | |
| { | |
| LoadBlockEntries(); | |
| return m_pEntries[0]; | |
| } | |
| const BlockEntry* Cluster::GetLast() | |
| { | |
| if (m_entriesCount == 0) | |
| return m_pEntries[0]; | |
| return m_pEntries[m_entriesCount-1]; | |
| } | |
| const BlockEntry* Cluster::GetNext(const BlockEntry* pEntry) const | |
| { | |
| assert(pEntry); | |
| size_t idx = pEntry->GetIndex(); | |
| ++idx; | |
| if (idx == m_entriesCount) | |
| return NULL; | |
| return m_pEntries[idx]; | |
| } | |
| const BlockEntry* Cluster::GetEntry(const Track* pTrack) | |
| { | |
| assert(pTrack); | |
| if (m_pSegment == NULL) //EOS | |
| return pTrack->GetEOS(); | |
| LoadBlockEntries(); | |
| BlockEntry* i = *m_pEntries; | |
| BlockEntry* j = *m_pEntries + m_entriesCount; | |
| while (i != j) | |
| { | |
| BlockEntry* pEntry = i; | |
| i++; | |
| assert(pEntry); | |
| assert(!pEntry->EOS()); | |
| const Block* const pBlock = pEntry->GetBlock(); | |
| assert(pBlock); | |
| if (pBlock->GetTrackNumber() != pTrack->GetNumber()) | |
| continue; | |
| if (pTrack->VetEntry(pEntry)) | |
| return pEntry; | |
| } | |
| return pTrack->GetEOS(); //no satisfactory block found | |
| } | |
| BlockEntry::BlockEntry() | |
| { | |
| } | |
| BlockEntry::~BlockEntry() | |
| { | |
| } | |
| SimpleBlock::SimpleBlock( | |
| Cluster* pCluster, | |
| size_t idx, | |
| long long start, | |
| long long size) : | |
| m_pCluster(pCluster), | |
| m_index(idx), | |
| m_block(start, size, pCluster->m_pSegment->m_pReader) | |
| { | |
| } | |
| bool SimpleBlock::EOS() const | |
| { | |
| return false; | |
| } | |
| Cluster* SimpleBlock::GetCluster() const | |
| { | |
| return m_pCluster; | |
| } | |
| size_t SimpleBlock::GetIndex() const | |
| { | |
| return m_index; | |
| } | |
| const Block* SimpleBlock::GetBlock() const | |
| { | |
| return &m_block; | |
| } | |
| bool SimpleBlock::IsBFrame() const | |
| { | |
| return false; | |
| } | |
| BlockGroup::BlockGroup( | |
| Cluster* pCluster, | |
| size_t idx, | |
| long long start, | |
| long long size_) : | |
| m_pCluster(pCluster), | |
| m_index(idx), | |
| m_prevTimeCode(0), | |
| m_nextTimeCode(0), | |
| m_pBlock(NULL) //TODO: accept multiple blocks within a block group | |
| { | |
| IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; | |
| long long pos = start; | |
| const long long stop = start + size_; | |
| bool bSimpleBlock = false; | |
| bool bReferenceBlock = false; | |
| while (pos < stop) | |
| { | |
| short t; | |
| if (Match(pReader, pos, 0x7B, t)) | |
| { | |
| if (t < 0) | |
| m_prevTimeCode = t; | |
| else if (t > 0) | |
| m_nextTimeCode = t; | |
| else | |
| assert(false); | |
| bReferenceBlock = true; | |
| } | |
| else | |
| { | |
| 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 | |
| switch (id) | |
| { | |
| case 0x23: //SimpleBlock ID | |
| bSimpleBlock = true; | |
| //YES, FALL THROUGH TO NEXT CASE | |
| case 0x21: //Block ID | |
| ParseBlock(pos, size); | |
| break; | |
| default: | |
| break; | |
| } | |
| pos += size; //consume payload | |
| assert(pos <= stop); | |
| } | |
| } | |
| assert(pos == stop); | |
| assert(m_pBlock); | |
| if (!bSimpleBlock) | |
| m_pBlock->SetKey(!bReferenceBlock); | |
| } | |
| BlockGroup::~BlockGroup() | |
| { | |
| delete m_pBlock; | |
| } | |
| void BlockGroup::ParseBlock(long long start, long long size) | |
| { | |
| IMkvReader* const pReader = m_pCluster->m_pSegment->m_pReader; | |
| Block* const pBlock = new Block(start, size, pReader); | |
| assert(pBlock); //TODO | |
| //TODO: the Matroska spec says you have multiple blocks within the | |
| //same block group, with blocks ranked by priority (the flag bits). | |
| //I haven't ever seen such a file (mkvmux certainly doesn't make | |
| //one), so until then I'll just assume block groups contain a single | |
| //block. | |
| #if 0 | |
| m_blocks.push_back(pBlock); | |
| #else | |
| assert(m_pBlock == NULL); | |
| m_pBlock = pBlock; | |
| #endif | |
| #if 0 | |
| Track* const pTrack = pBlock->GetTrack(); | |
| assert(pTrack); | |
| pTrack->Insert(pBlock); | |
| #endif | |
| } | |
| bool BlockGroup::EOS() const | |
| { | |
| return false; | |
| } | |
| Cluster* BlockGroup::GetCluster() const | |
| { | |
| return m_pCluster; | |
| } | |
| size_t BlockGroup::GetIndex() const | |
| { | |
| return m_index; | |
| } | |
| const Block* BlockGroup::GetBlock() const | |
| { | |
| return m_pBlock; | |
| } | |
| short BlockGroup::GetPrevTimeCode() const | |
| { | |
| return m_prevTimeCode; | |
| } | |
| short BlockGroup::GetNextTimeCode() const | |
| { | |
| return m_nextTimeCode; | |
| } | |
| bool BlockGroup::IsBFrame() const | |
| { | |
| return (m_nextTimeCode > 0); | |
| } | |
| Block::Block(long long start, long long size_, IMkvReader* pReader) : | |
| m_start(start), | |
| m_size(size_) | |
| { | |
| long long pos = start; | |
| const long long stop = start + size_; | |
| long len; | |
| m_track = ReadUInt(pReader, pos, len); | |
| assert(m_track > 0); | |
| assert((pos + len) <= stop); | |
| pos += len; //consume track number | |
| assert((stop - pos) >= 2); | |
| m_timecode = Unserialize2SInt(pReader, pos); | |
| pos += 2; | |
| assert((stop - pos) >= 1); | |
| const long hr = pReader->Read(pos, 1, &m_flags); | |
| assert(hr == 0L); | |
| ++pos; | |
| assert(pos <= stop); | |
| m_frameOff = pos; | |
| const long long frame_size = stop - pos; | |
| assert(frame_size <= 2147483647L); | |
| m_frameSize = static_cast<long>(frame_size); | |
| } | |
| long long Block::GetTimeCode(Cluster* pCluster) const | |
| { | |
| assert(pCluster); | |
| const long long tc0 = pCluster->GetTimeCode(); | |
| assert(tc0 >= 0); | |
| const long long tc = tc0 + static_cast<long long>(m_timecode); | |
| assert(tc >= 0); | |
| return tc; //unscaled timecode units | |
| } | |
| long long Block::GetTime(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; | |
| } | |
| unsigned long Block::GetTrackNumber() const | |
| { | |
| assert(m_track > 0); | |
| return static_cast<unsigned long>(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; | |
| } | |
| long Block::GetSize() const | |
| { | |
| return m_frameSize; | |
| } | |
| long Block::Read(IMkvReader* pReader, unsigned char* buf) const | |
| { | |
| assert(pReader); | |
| assert(buf); | |
| const long hr = pReader->Read(m_frameOff, m_frameSize, buf); | |
| return hr; | |
| } | |
| } //end namespace mkvparser |