795 lines
17 KiB
C++
795 lines
17 KiB
C++
#include "cluster.hpp"
|
|
#include "segment.hpp"
|
|
#include "imkvreader.hpp"
|
|
#include "ebmlreadhelpers.hpp"
|
|
#include "block.hpp"
|
|
#include "blockentry.hpp"
|
|
#include "track.hpp"
|
|
#include "tracks.hpp"
|
|
#include "simpleblock.hpp"
|
|
#include "segmentinfo.hpp"
|
|
#include "blockgroup.hpp"
|
|
#include "mkvprofiler.hpp"
|
|
|
|
#include <assert.h>
|
|
|
|
namespace mkvparser
|
|
{
|
|
|
|
Cluster* Cluster::Create(Segment* pSegment, const EBMLElement& elemInfo)
|
|
{
|
|
assert(pSegment);
|
|
assert(elemInfo.GetPos() >= 0);
|
|
|
|
Cluster* const pCluster = new Cluster(pSegment, elemInfo);
|
|
assert(pCluster);
|
|
|
|
return pCluster;
|
|
}
|
|
|
|
|
|
Cluster::Cluster() :
|
|
m_pSegment(NULL),
|
|
m_pos(0),
|
|
m_timecode(0)
|
|
{
|
|
}
|
|
|
|
|
|
Cluster::Cluster(Segment* pSegment, const EBMLElement& elemInfo)
|
|
: m_pSegment(pSegment)
|
|
, m_elemInfo(elemInfo)
|
|
, m_pos(m_elemInfo.GetPayloadPos())
|
|
, m_timecode(-1)
|
|
{
|
|
}
|
|
|
|
|
|
Cluster::~Cluster()
|
|
{
|
|
BlockEntryVect_t::iterator first = m_entries.begin();
|
|
BlockEntryVect_t::iterator last = m_entries.end();
|
|
|
|
for( ; first != last; ++first)
|
|
{
|
|
BlockEntry* p = *first;
|
|
assert(p);
|
|
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
|
|
long Cluster::Load() const
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::Load");
|
|
|
|
assert(m_pSegment);
|
|
assert(m_pos >= m_elemInfo.GetPayloadPos());
|
|
|
|
if (m_timecode >= 0) //at least partially loaded
|
|
return 0;
|
|
|
|
assert(m_pos == m_elemInfo.GetPayloadPos());
|
|
|
|
IMkvReader* const pReader = m_pSegment->GetReader();
|
|
|
|
long long total, avail;
|
|
|
|
long status = pReader->Length(&total, &avail);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
assert((total < 0) || (avail <= total));
|
|
|
|
//pos points to start of payload
|
|
long long pos = m_pos;
|
|
|
|
long long timecode = -1;
|
|
long long new_pos = -1;
|
|
bool bBlock = false;
|
|
|
|
for (;;)
|
|
{
|
|
if (pos >= m_elemInfo.GetEndPos())
|
|
break;
|
|
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(pReader, pos);
|
|
if (result != 0)
|
|
return static_cast<long>(result);
|
|
|
|
if (elemInfo.GetID() == 0x67) //TimeCode ID
|
|
{
|
|
if (elemInfo.GetEndPos() > avail)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
timecode = UnserializeUInt(pReader, elemInfo.GetPayloadPos(), elemInfo.GetPayloadSize());
|
|
|
|
if (timecode < 0) //error (or underflow)
|
|
return static_cast<long>(timecode);
|
|
|
|
new_pos = elemInfo.GetEndPos();
|
|
}
|
|
else if (elemInfo.GetID() == 0x20) //BlockGroup ID
|
|
{
|
|
bBlock = true;
|
|
break;
|
|
}
|
|
else if (elemInfo.GetID() == 0x23) //SimpleBlock ID
|
|
{
|
|
bBlock = true;
|
|
break;
|
|
}
|
|
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
assert(pos <= m_elemInfo.GetEndPos());
|
|
}
|
|
|
|
assert(pos <= m_elemInfo.GetEndPos());
|
|
|
|
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
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
long Cluster::Parse() const
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::Parse");
|
|
|
|
long status = Load();
|
|
|
|
if (status < 0)
|
|
return status;
|
|
|
|
assert(m_pos >= m_elemInfo.GetPayloadPos());
|
|
assert(m_timecode >= 0);
|
|
|
|
const long long cluster_stop = m_elemInfo.GetEndPos();
|
|
|
|
if ((cluster_stop >= 0) && (m_pos >= cluster_stop))
|
|
return 1; //nothing else to do
|
|
|
|
IMkvReader* const pReader = m_pSegment->GetReader();
|
|
|
|
long long total, avail;
|
|
|
|
status = pReader->Length(&total, &avail);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
assert((total < 0) || (avail <= total));
|
|
|
|
long long pos = m_pos;
|
|
|
|
for (;;)
|
|
{
|
|
if ((cluster_stop >= 0) && (pos >= cluster_stop))
|
|
break;
|
|
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(pReader, pos);
|
|
if (result != 0)
|
|
return static_cast<long>(result);
|
|
|
|
const long long block_stop = elemInfo.GetEndPos();
|
|
if (block_stop > avail)
|
|
{
|
|
return E_BUFFER_NOT_FULL;
|
|
}
|
|
|
|
Cluster* const this_ = const_cast<Cluster*>(this);
|
|
|
|
if (elemInfo.GetID() == 0x20) //BlockGroup
|
|
return this_->ParseBlockGroup(elemInfo);
|
|
|
|
if (elemInfo.GetID() == 0x23) //SimpleBlock
|
|
return this_->ParseSimpleBlock(elemInfo);
|
|
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
assert((cluster_stop < 0) || (pos <= cluster_stop));
|
|
}
|
|
|
|
m_pos = pos;
|
|
assert((cluster_stop < 0) || (m_pos <= cluster_stop));
|
|
|
|
if (!m_entries.empty())
|
|
{
|
|
const BlockEntry* const pLast = m_entries.back();
|
|
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(const EBMLElement& elemInfo)
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::ParseSimpleBlock");
|
|
|
|
long status = CreateBlock(0x23, elemInfo.GetPayloadPos(), elemInfo.GetPayloadSize()); //simple block id
|
|
|
|
if (status != 0)
|
|
return status;
|
|
|
|
m_pos = elemInfo.GetEndPos();
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
|
|
long Cluster::ParseBlockGroup(const EBMLElement& elemInfo)
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::ParseBlockGroup");
|
|
|
|
long status = CreateBlock(0x20, elemInfo.GetPayloadPos(), elemInfo.GetPayloadSize()); //BlockGroup ID
|
|
|
|
if (status != 0)
|
|
return status;
|
|
|
|
m_pos = elemInfo.GetEndPos();
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
|
|
long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const
|
|
{
|
|
assert(m_pos >= m_elemInfo.GetPayloadPos());
|
|
|
|
pEntry = NULL;
|
|
|
|
if (index < 0)
|
|
return -1; //generic error
|
|
|
|
if (m_entries.capacity() == 0)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
if (index < static_cast<long>(m_entries.size()))
|
|
{
|
|
pEntry = m_entries[index];
|
|
assert(pEntry);
|
|
|
|
return 1; //found entry
|
|
}
|
|
|
|
if (m_pos >= m_elemInfo.GetEndPos())
|
|
return 0; //nothing left to parse
|
|
|
|
return E_BUFFER_NOT_FULL; //underflow, since more remains to be parsed
|
|
}
|
|
|
|
|
|
long long Cluster::GetPosition() const
|
|
{
|
|
const long long pos = m_elemInfo.GetPos() - m_pSegment->GetStartPos();
|
|
assert(pos >= 0);
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
long long Cluster::GetTimeCode() const
|
|
{
|
|
const long status = Load();
|
|
|
|
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)
|
|
{
|
|
assert((id == 0x20) || (id == 0x23)); //BlockGroup or SimpleBlock
|
|
|
|
if (m_entries.capacity() == 0) //haven't parsed anything yet
|
|
{
|
|
m_entries.reserve(1024);
|
|
}
|
|
|
|
if (id == 0x20) //BlockGroup ID
|
|
return CreateBlockGroup(pos, size);
|
|
else //SimpleBlock ID
|
|
return CreateSimpleBlock(pos, size);
|
|
}
|
|
|
|
|
|
long Cluster::CreateBlockGroup(
|
|
long long st,
|
|
long long sz)
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::CreateBlockGroup");
|
|
|
|
assert(m_entries.capacity() > 0);
|
|
|
|
IMkvReader* const pReader = m_pSegment->GetReader();
|
|
|
|
long long pos = st;
|
|
const long long stop = st + sz;
|
|
|
|
//For WebM files, there is a bias towards previous reference times
|
|
//(in order to support alt-ref frames, which refer back to the previous
|
|
//keyframe). Normally a 0 value is not possible, but here we tenatively
|
|
//allow 0 as the value of a reference frame, with the interpretation
|
|
//that this is a "previous" reference time.
|
|
|
|
long long prev = 1; //nonce
|
|
long long next = 0; //nonce
|
|
long long duration = -1; //really, this is unsigned
|
|
|
|
long long bpos = -1;
|
|
long long bsize = -1;
|
|
|
|
while (pos < stop)
|
|
{
|
|
long len;
|
|
const long long id = ReadUInt(pReader, pos, len);
|
|
assert(id >= 0); //TODO
|
|
assert((pos + len) <= stop);
|
|
|
|
pos += len; //consume ID
|
|
|
|
const long long size = ReadUInt(pReader, pos, len);
|
|
assert(size >= 0); //TODO
|
|
assert((pos + len) <= stop);
|
|
|
|
pos += len; //consume size
|
|
|
|
if (id == 0x21) //Block ID
|
|
{
|
|
if (bpos < 0) //Block ID
|
|
{
|
|
bpos = pos;
|
|
bsize = size;
|
|
}
|
|
}
|
|
else if (id == 0x1B) //Duration ID
|
|
{
|
|
assert(size <= 8);
|
|
|
|
duration = UnserializeUInt(pReader, pos, size);
|
|
assert(duration >= 0); //TODO
|
|
}
|
|
else if (id == 0x7B) //ReferenceBlock
|
|
{
|
|
assert(size <= 8);
|
|
const long size_ = static_cast<long>(size);
|
|
|
|
long long time;
|
|
|
|
long status = UnserializeInt(pReader, pos, size_, time);
|
|
assert(status == 0); //TODO
|
|
|
|
if (time <= 0) //see note above
|
|
prev = time;
|
|
else //weird
|
|
next = time;
|
|
}
|
|
|
|
pos += size; //consume payload
|
|
assert(pos <= stop);
|
|
}
|
|
|
|
assert(pos == stop);
|
|
assert(bpos >= 0);
|
|
assert(bsize >= 0);
|
|
|
|
const long idx = static_cast<long>(m_entries.size());
|
|
|
|
BlockEntry* pEntry = new BlockGroup(
|
|
this,
|
|
idx,
|
|
bpos,
|
|
bsize,
|
|
prev,
|
|
next,
|
|
duration);
|
|
|
|
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.push_back(pEntry);
|
|
return 0;
|
|
}
|
|
|
|
delete pEntry;
|
|
pEntry = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
long Cluster::CreateSimpleBlock(
|
|
long long st,
|
|
long long sz)
|
|
{
|
|
assert(m_entries.capacity() > 0);
|
|
|
|
const long idx = static_cast<long>(m_entries.size());
|
|
|
|
BlockEntry* pEntry = new 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.push_back(pEntry);
|
|
return 0;
|
|
}
|
|
|
|
delete pEntry;
|
|
pEntry = 0;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
long Cluster::GetFirst(const BlockEntry*& pFirst) const
|
|
{
|
|
if (m_entries.empty())
|
|
{
|
|
const long status = Parse();
|
|
|
|
if (status < 0) //error
|
|
{
|
|
pFirst = NULL;
|
|
return status;
|
|
}
|
|
|
|
if (m_entries.empty()) //empty cluster
|
|
{
|
|
pFirst = NULL;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pFirst = m_entries[0];
|
|
assert(pFirst);
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
|
|
long Cluster::GetLast(const BlockEntry*& pLast) const
|
|
{
|
|
for (;;)
|
|
{
|
|
const long status = Parse();
|
|
|
|
if (status < 0) //error
|
|
{
|
|
pLast = NULL;
|
|
return status;
|
|
}
|
|
|
|
if (status > 0) //no new block
|
|
break;
|
|
}
|
|
|
|
if (m_entries.empty())
|
|
{
|
|
pLast = NULL;
|
|
return 0;
|
|
}
|
|
|
|
pLast = m_entries.back();
|
|
assert(pLast);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
long Cluster::GetNext(
|
|
const BlockEntry* pCurr,
|
|
const BlockEntry*& pNext) const
|
|
{
|
|
assert(pCurr);
|
|
assert(!m_entries.empty());
|
|
|
|
size_t idx = pCurr->GetIndex();
|
|
assert(idx < m_entries.size());
|
|
assert(m_entries[idx] == pCurr);
|
|
|
|
++idx;
|
|
|
|
if (idx >= m_entries.size())
|
|
{
|
|
const long status = Parse();
|
|
|
|
if (status < 0) //error
|
|
{
|
|
pNext = NULL;
|
|
return status;
|
|
}
|
|
|
|
if (status > 0)
|
|
{
|
|
pNext = NULL;
|
|
return 0;
|
|
}
|
|
|
|
assert(!m_entries.empty());
|
|
assert(idx < m_entries.size());
|
|
}
|
|
|
|
pNext = m_entries[idx];
|
|
assert(pNext);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
const BlockEntry* Cluster::GetEntry(
|
|
const Track* pTrack,
|
|
long long time_ns) const
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::GetEntry(pTrack)");
|
|
|
|
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 >= static_cast<long>(m_entries.size()))
|
|
{
|
|
const long status = Parse();
|
|
assert(status >= 0);
|
|
|
|
if (status > 0) //completely parsed, and no more entries
|
|
return pResult;
|
|
|
|
if (status < 0) //should never happen
|
|
return 0;
|
|
|
|
assert(index < static_cast<long>(m_entries.size()));
|
|
}
|
|
|
|
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
|
|
{
|
|
PROFILER_SCOPED_EVENT("webm::Cluster::GetEntry(cp)");
|
|
|
|
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 >= static_cast<long>(m_entries.size()))
|
|
{
|
|
const long status = Parse();
|
|
|
|
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 >= static_cast<long>(m_entries.size()))
|
|
{
|
|
const long status = Parse();
|
|
|
|
if (status < 0) //TODO: can this happen?
|
|
return NULL;
|
|
|
|
if (status > 0) //nothing remains to be parsed
|
|
return NULL;
|
|
|
|
assert(index < static_cast<long>(m_entries.size()));
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
} // namespace mkvparser
|