540 lines
14 KiB
C++
540 lines
14 KiB
C++
#include "segment.hpp"
|
|
#include "tracks.hpp"
|
|
#include "segmentinfo.hpp"
|
|
#include "cues.hpp"
|
|
#include "seekhead.hpp"
|
|
#include "imkvreader.hpp"
|
|
#include "ebmlreadhelpers.hpp"
|
|
#include "cluster.hpp"
|
|
|
|
#include <stddef.h>
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
|
|
namespace mkvparser
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
// ClusterVect_t comparison functors
|
|
struct ClusterPosLT
|
|
{
|
|
ClusterPosLT(long long cluster_off)
|
|
: m_cluster_off(cluster_off) {}
|
|
|
|
bool operator()(const Cluster* lhs, const Cluster* rhs) const
|
|
{
|
|
if (lhs)
|
|
return lhs->GetPosition() < m_cluster_off;
|
|
if (rhs)
|
|
return rhs->GetPosition() < m_cluster_off;
|
|
return false;
|
|
}
|
|
|
|
long long m_cluster_off;
|
|
};
|
|
|
|
struct ClusterLT
|
|
{
|
|
bool operator()(const Cluster* lhs, const Cluster* rhs) const
|
|
{
|
|
assert(lhs);
|
|
assert(rhs);
|
|
return lhs->GetPosition() < rhs->GetPosition();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
Segment::Segment(IMkvReader* pReader, const EBMLElement& _ebmlInfo)
|
|
: m_pReader(pReader)
|
|
, m_segInfo(_ebmlInfo)
|
|
, m_firstClusterPos(-1)
|
|
, m_pSeekHead(NULL)
|
|
, m_pInfo(NULL)
|
|
, m_pTracks(NULL)
|
|
, m_pCues(NULL)
|
|
{
|
|
const size_t clusterVectReserveSize = 2048;
|
|
m_clusters.reserve(clusterVectReserveSize);
|
|
}
|
|
|
|
|
|
Segment::~Segment()
|
|
{
|
|
m_clusters.clear();
|
|
|
|
delete m_pTracks;
|
|
delete m_pInfo;
|
|
delete m_pCues;
|
|
delete m_pSeekHead;
|
|
}
|
|
|
|
|
|
long long Segment::CreateInstance(
|
|
IMkvReader* pReader,
|
|
long long pos,
|
|
Segment*& pSegment)
|
|
{
|
|
assert(pReader);
|
|
assert(pos >= 0);
|
|
|
|
pSegment = NULL;
|
|
|
|
long long total, available;
|
|
|
|
const long status = pReader->Length(&total, &available);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
if (available < 0)
|
|
return -1;
|
|
|
|
if ((total >= 0) && (available > total))
|
|
return -1;
|
|
|
|
//I would assume that in practice this loop would execute
|
|
//exactly once, but we allow for other elements (e.g. Void)
|
|
//to immediately follow the EBML header.
|
|
//There is an implied "give up early" by only parsing up
|
|
//to the available limit.
|
|
|
|
for (;;)
|
|
{
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(pReader, pos);
|
|
if (result != 0)
|
|
return result;
|
|
|
|
if (elemInfo.GetID() == 0x08538067) //Segment ID
|
|
{
|
|
pSegment = new Segment(pReader, elemInfo);
|
|
|
|
if (pSegment == 0)
|
|
return -1; //generic error
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
if ((total >= 0) && ((elemInfo.GetPayloadPos() + elemInfo.GetPayloadSize()) > total))
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
if ((elemInfo.GetPayloadPos() + elemInfo.GetPayloadSize()) > available)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
pos += elemInfo.GetSize(); // consume element
|
|
}
|
|
}
|
|
|
|
|
|
long Segment::Load()
|
|
{
|
|
assert(m_clusters.empty());
|
|
assert(!m_clusters.IsClusterLimitOn());
|
|
|
|
//Outermost (level 0) segment object has been constructed,
|
|
//and pos designates start of payload. We need to find the
|
|
//inner (level 1) elements.
|
|
|
|
const long long header_status = ParseHeaders();
|
|
|
|
if (header_status < 0) //error
|
|
return static_cast<long>(header_status);
|
|
|
|
if (header_status > 0) //underflow
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
assert(m_pInfo);
|
|
assert(m_pTracks);
|
|
|
|
Cluster* pCurr = NULL;
|
|
long status = ParseFirstCluster(pCurr);
|
|
while(status == 0 && pCurr)
|
|
{
|
|
status = ParseNextCluster(pCurr, pCurr);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
long long Segment::ParseHeaders()
|
|
{
|
|
// the function must be called only once
|
|
assert(m_pSeekHead == NULL);
|
|
assert(m_pInfo == NULL);
|
|
assert(m_pTracks == NULL);
|
|
assert(m_pCues == NULL);
|
|
|
|
//Outermost (level 0) segment object has been constructed,
|
|
//and pos designates start of payload. We need to find the
|
|
//inner (level 1) elements.
|
|
long long total, available;
|
|
|
|
const int status = m_pReader->Length(&total, &available);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
assert((total < 0) || (available <= total));
|
|
|
|
long long pos = m_segInfo.GetPayloadPos();
|
|
const long long segment_stop = m_segInfo.GetEndPos();
|
|
assert((segment_stop < 0) || (total < 0) || (segment_stop <= total));
|
|
assert((segment_stop < 0) || (pos <= segment_stop));
|
|
|
|
for (;;)
|
|
{
|
|
if ((total >= 0) && (pos >= total))
|
|
break;
|
|
|
|
if ((segment_stop >= 0) && (pos >= segment_stop))
|
|
break;
|
|
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(m_pReader, pos);
|
|
if (result != 0)
|
|
return result;
|
|
|
|
if (elemInfo.GetID() == 0x0F43B675) //Cluster ID, stop parsing
|
|
{
|
|
m_firstClusterPos = pos;
|
|
break;
|
|
}
|
|
|
|
if ((segment_stop >= 0) && (elemInfo.GetEndPos() > segment_stop))
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
//We read EBML elements either in total or nothing at all.
|
|
if (elemInfo.GetEndPos() > available)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
if (elemInfo.GetID() == 0x0549A966) //Segment Info ID
|
|
{
|
|
if (m_pInfo)
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
m_pInfo = new SegmentInfo(this, elemInfo);
|
|
|
|
if (m_pInfo == NULL)
|
|
return -1;
|
|
|
|
const long status = m_pInfo->Parse();
|
|
|
|
if (status)
|
|
return status;
|
|
}
|
|
else if (elemInfo.GetID() == 0x0654AE6B) //Tracks ID
|
|
{
|
|
if (m_pTracks)
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
m_pTracks = new Tracks(this, elemInfo);
|
|
|
|
if (m_pTracks == NULL)
|
|
return -1;
|
|
|
|
const long status = m_pTracks->Parse();
|
|
|
|
if (status)
|
|
return status;
|
|
}
|
|
else if (elemInfo.GetID() == 0x0C53BB6B) //Cues ID
|
|
{
|
|
if (m_pCues == NULL)
|
|
{
|
|
m_pCues = new Cues(this, elemInfo);
|
|
|
|
if (m_pCues == NULL)
|
|
return -1;
|
|
}
|
|
}
|
|
else if (elemInfo.GetID() == 0x014D9B74) //SeekHead ID
|
|
{
|
|
if (m_pSeekHead == NULL)
|
|
{
|
|
m_pSeekHead = new SeekHead(this, elemInfo);
|
|
|
|
if (m_pSeekHead == NULL)
|
|
return -1;
|
|
|
|
const long status = m_pSeekHead->Parse();
|
|
|
|
if (status)
|
|
return status;
|
|
}
|
|
}
|
|
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
}
|
|
|
|
assert((segment_stop < 0) || (pos <= segment_stop));
|
|
|
|
if (m_pInfo == NULL) //TODO: liberalize this behavior
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
if (m_pTracks == NULL)
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
|
|
long Segment::ParseCues(long long off)
|
|
{
|
|
if (m_pCues)
|
|
return 0; //success
|
|
|
|
if (off < 0)
|
|
return -1;
|
|
|
|
long long total, avail;
|
|
|
|
const int status = m_pReader->Length(&total, &avail);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
assert((total < 0) || (avail <= total));
|
|
|
|
long long pos = m_segInfo.GetPayloadPos() + off;
|
|
|
|
if ((total < 0) || (pos >= total))
|
|
return 1; //don't bother parsing cues
|
|
|
|
const long long segment_stop = m_segInfo.GetEndPos();
|
|
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(m_pReader, pos);
|
|
if (result != 0)
|
|
return static_cast<long>(result);
|
|
|
|
if (elemInfo.GetID() != 0x0C53BB6B) //Cues ID
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
const long long element_stop = elemInfo.GetEndPos();
|
|
if ((segment_stop >= 0) && (element_stop > segment_stop))
|
|
return E_FILE_FORMAT_INVALID;
|
|
|
|
if ((total >= 0) && (element_stop > total))
|
|
return 1; //don't bother parsing anymore
|
|
|
|
if (element_stop > avail)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
m_pCues = new Cues(this, elemInfo);
|
|
assert(m_pCues); //TODO
|
|
|
|
return 0; //success
|
|
}
|
|
|
|
|
|
long Segment::ParseFirstCluster(ClusterPtr_t& pFirst)
|
|
{
|
|
//check if it is already there
|
|
const Cluster* first = GetFirstParsedCluster();
|
|
if (first && first->GetElementStart() == m_firstClusterPos)
|
|
{
|
|
pFirst = const_cast<Cluster*>(first);
|
|
return 0; // success
|
|
}
|
|
return DoLoadClusterOrCues(m_firstClusterPos, pFirst);
|
|
}
|
|
|
|
|
|
long Segment::ParseNextCluster(const ClusterPtrConst_t& pCurr, ClusterPtr_t& pNext)
|
|
{
|
|
assert(pCurr);
|
|
assert(!m_clusters.empty());
|
|
assert(pCurr->GetElementSize() > 0);
|
|
|
|
long long next_start_pos = pCurr->GetElementStart() + pCurr->GetElementSize();
|
|
|
|
//is the next already there?
|
|
const Cluster* next = GetNextParsedCluster(pCurr);
|
|
if (next && next->GetElementStart() == next_start_pos)
|
|
{
|
|
pNext = const_cast<Cluster*>(next);
|
|
return 0; // success
|
|
}
|
|
return DoLoadClusterOrCues(next_start_pos, pNext);
|
|
}
|
|
|
|
|
|
long long Segment::GetDuration() const
|
|
{
|
|
assert(m_pInfo);
|
|
return m_pInfo->GetDuration();
|
|
}
|
|
|
|
|
|
unsigned long Segment::GetParsedClustersCount() const
|
|
{
|
|
return static_cast<unsigned long>(m_clusters.size());
|
|
}
|
|
|
|
|
|
const Cluster* Segment::GetFirstParsedCluster() const
|
|
{
|
|
if (m_clusters.empty())
|
|
return NULL;
|
|
|
|
Cluster* const pCluster = m_clusters[0];
|
|
assert(pCluster);
|
|
|
|
return pCluster;
|
|
}
|
|
|
|
|
|
const Cluster* Segment::GetLastParsedCluster() const
|
|
{
|
|
if (m_clusters.empty())
|
|
return NULL;
|
|
|
|
Cluster* const pCluster = m_clusters.back();
|
|
assert(pCluster);
|
|
|
|
return pCluster;
|
|
}
|
|
|
|
|
|
const Cluster* Segment::GetNextParsedCluster(const Cluster* pCurr) const
|
|
{
|
|
assert(!m_clusters.empty());
|
|
assert(pCurr);
|
|
|
|
Clusters::const_iterator it = std::lower_bound(m_clusters.begin(), m_clusters.end(), pCurr, ClusterLT());
|
|
assert(it != m_clusters.end());
|
|
assert((*it));
|
|
assert((*it) == pCurr);
|
|
|
|
++it;
|
|
if(it != m_clusters.end())
|
|
return (*it);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const BlockEntry* Segment::GetBlock(const CuePoint& cp, const CuePoint::TrackPosition& tp)
|
|
{
|
|
Cluster* pCluster;
|
|
const long long requested_pos = tp.m_pos;
|
|
|
|
// find the right cluster
|
|
Clusters::iterator it = std::lower_bound(m_clusters.begin(), m_clusters.end(), ClusterPtr_t(0), ClusterPosLT(requested_pos));
|
|
if (it != m_clusters.end() && (*it) && (*it)->GetPosition() == requested_pos)
|
|
{
|
|
// found a match
|
|
pCluster = *it;
|
|
}
|
|
else
|
|
{
|
|
// load the cluster
|
|
const long long abs_requested_pos = requested_pos + m_segInfo.GetPayloadPos();
|
|
long status = DoLoadClusterOrCues(abs_requested_pos, pCluster);
|
|
if(status != 0)
|
|
pCluster = 0;
|
|
}
|
|
|
|
if (pCluster)
|
|
return pCluster->GetEntry(cp, tp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
// private functions
|
|
long Segment::DoLoadClusterOrCues(long long pos, ClusterPtr_t& pLoadedCluster)
|
|
{
|
|
long long total, avail;
|
|
|
|
long status = m_pReader->Length(&total, &avail);
|
|
|
|
if (status < 0) //error
|
|
return status;
|
|
|
|
assert((total < 0) || (avail <= total));
|
|
|
|
const long long segment_stop = m_segInfo.GetEndPos();
|
|
|
|
for (;;)
|
|
{
|
|
if ((total >= 0) && (pos >= total))
|
|
return 1; //no more clusters
|
|
|
|
if ((segment_stop >= 0) && (pos >= segment_stop))
|
|
return 1; //no more clusters
|
|
|
|
EBMLElement elemInfo;
|
|
long long result = elemInfo.Parse(m_pReader, pos);
|
|
if (result != 0)
|
|
return static_cast<long>(result);
|
|
|
|
if (elemInfo.GetPayloadSize() == 0) //weird
|
|
{
|
|
pos += elemInfo.GetSize();
|
|
continue;
|
|
}
|
|
|
|
if ((segment_stop >= 0) && (elemInfo.GetEndPos() > segment_stop))
|
|
{
|
|
return E_FILE_FORMAT_INVALID;
|
|
}
|
|
|
|
if (elemInfo.GetEndPos() > avail)
|
|
return E_BUFFER_NOT_FULL;
|
|
|
|
if (elemInfo.GetID() == 0x0C53BB6B) //Cues ID
|
|
{
|
|
if (m_pCues == NULL)
|
|
{
|
|
m_pCues = new Cues(this, elemInfo);
|
|
assert(m_pCues); //TODO
|
|
}
|
|
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
continue;
|
|
}
|
|
// not a cluster element
|
|
else if (elemInfo.GetID() != 0x0F43B675) //Cluster ID
|
|
{
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//We have a cluster.
|
|
long long cluster_off = elemInfo.GetPos() - m_segInfo.GetPayloadPos(); //relative pos
|
|
|
|
// is the cluster already in the list?
|
|
Clusters::iterator it = std::lower_bound(m_clusters.begin(), m_clusters.end(), ClusterPtr_t(0), ClusterPosLT(cluster_off));
|
|
if(it != m_clusters.end() && (*it) && (*it)->GetPosition() == cluster_off)
|
|
{
|
|
// already exists
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
assert((segment_stop < 0) || (pos <= segment_stop));
|
|
|
|
pLoadedCluster = *it;
|
|
return 0; //success
|
|
}
|
|
else
|
|
{
|
|
// new cluster needed
|
|
Cluster* const pCluster = Cluster::Create(this, elemInfo);
|
|
assert(pCluster);
|
|
|
|
// iterator points where the cluster should be inserted
|
|
m_clusters.insert(it, pCluster);
|
|
|
|
pos += elemInfo.GetSize(); //consume payload
|
|
assert((segment_stop < 0) || (pos <= segment_stop));
|
|
|
|
pLoadedCluster = pCluster;
|
|
return 0; //success
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace mkvparser
|