JD2022-TU1/main/extern/libwebm/custom/mkvparser/cluster.cpp

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