463 lines
20 KiB
C++
463 lines
20 KiB
C++
#include "output_ebml.h"
|
|
#include <mist/ebml_socketglue.h>
|
|
#include <mist/riff.h>
|
|
|
|
namespace Mist{
|
|
OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){
|
|
currentClusterTime = 0;
|
|
newClusterTime = 0;
|
|
segmentSize = 0xFFFFFFFFFFFFFFFFull;
|
|
tracksSize = 0;
|
|
infoSize = 0;
|
|
cuesSize = 0;
|
|
seekheadSize = 0;
|
|
doctype = "matroska";
|
|
}
|
|
|
|
void OutEBML::init(Util::Config *cfg){
|
|
HTTPOutput::init(cfg);
|
|
capa["name"] = "EBML";
|
|
capa["desc"] = "Enables MKV and WebM streaming over HTTP.";
|
|
capa["url_rel"] = "/$.webm";
|
|
capa["url_match"].append("/$.mkv");
|
|
capa["url_match"].append("/$.webm");
|
|
capa["codecs"][0u][0u].append("H264");
|
|
capa["codecs"][0u][0u].append("HEVC");
|
|
capa["codecs"][0u][0u].append("VP8");
|
|
capa["codecs"][0u][0u].append("VP9");
|
|
capa["codecs"][0u][0u].append("theora");
|
|
capa["codecs"][0u][0u].append("MPEG2");
|
|
capa["codecs"][0u][1u].append("AAC");
|
|
capa["codecs"][0u][1u].append("vorbis");
|
|
capa["codecs"][0u][1u].append("opus");
|
|
capa["codecs"][0u][1u].append("PCM");
|
|
capa["codecs"][0u][1u].append("ALAW");
|
|
capa["codecs"][0u][1u].append("ULAW");
|
|
capa["codecs"][0u][1u].append("MP2");
|
|
capa["codecs"][0u][1u].append("MP3");
|
|
capa["codecs"][0u][1u].append("FLOAT");
|
|
capa["codecs"][0u][1u].append("AC3");
|
|
capa["methods"][0u]["handler"] = "http";
|
|
capa["methods"][0u]["type"] = "html5/video/webm";
|
|
capa["methods"][0u]["priority"] = 8ll;
|
|
}
|
|
|
|
/// Calculates the size of a Cluster (contents only) and returns it.
|
|
/// Bases the calculation on the currently selected tracks and the given start/end time for the cluster.
|
|
uint32_t OutEBML::clusterSize(uint64_t start, uint64_t end){
|
|
uint32_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start);
|
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
|
it != selectedTracks.end(); it++){
|
|
DTSC::Track &thisTrack = myMeta.tracks[*it];
|
|
uint32_t firstPart = 0;
|
|
unsigned long long int prevParts = 0;
|
|
uint64_t curMS = 0;
|
|
for (std::deque<DTSC::Key>::iterator it2 = thisTrack.keys.begin();
|
|
it2 != thisTrack.keys.end(); it2++){
|
|
if (it2->getTime() > start && it2 != thisTrack.keys.begin()){break;}
|
|
firstPart += prevParts;
|
|
prevParts = it2->getParts();
|
|
curMS = it2->getTime();
|
|
}
|
|
size_t maxParts = thisTrack.parts.size();
|
|
for (size_t i = firstPart; i < maxParts; i++){
|
|
if (curMS >= end){break;}
|
|
if (curMS >= start){
|
|
uint32_t blkLen = EBML::sizeSimpleBlock(thisTrack.trackID, thisTrack.parts[i].getSize());
|
|
sendLen += blkLen;
|
|
}
|
|
curMS += thisTrack.parts[i].getDuration();
|
|
}
|
|
}
|
|
return sendLen;
|
|
}
|
|
|
|
void OutEBML::sendNext(){
|
|
if (thisPacket.getTime() >= newClusterTime){
|
|
currentClusterTime = thisPacket.getTime();
|
|
if (myMeta.vod){
|
|
//In case of VoD, clusters are aligned with the main track fragments
|
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
|
uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
|
|
newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration();
|
|
//The last fragment should run until the end of time
|
|
if (fragIndice == Trk.fragments.size() - 1){
|
|
newClusterTime = 0xFFFFFFFFFFFFFFFFull;
|
|
}
|
|
EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
|
|
}else{
|
|
//In live, clusters are aligned with the lookAhead time
|
|
newClusterTime += needsLookAhead;
|
|
}
|
|
EBML::sendElemHead(myConn, EBML::EID_CLUSTER, clusterSize(currentClusterTime, newClusterTime));
|
|
EBML::sendElemUInt(myConn, EBML::EID_TIMECODE, currentClusterTime);
|
|
}
|
|
|
|
EBML::sendSimpleBlock(myConn, thisPacket, currentClusterTime,
|
|
myMeta.tracks[thisPacket.getTrackId()].type != "video");
|
|
}
|
|
|
|
std::string OutEBML::trackCodecID(const DTSC::Track &Trk){
|
|
if (Trk.codec == "opus"){return "A_OPUS";}
|
|
if (Trk.codec == "H264"){return "V_MPEG4/ISO/AVC";}
|
|
if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
|
|
if (Trk.codec == "VP8"){return "V_VP8";}
|
|
if (Trk.codec == "VP9"){return "V_VP9";}
|
|
if (Trk.codec == "AAC"){return "A_AAC";}
|
|
if (Trk.codec == "vorbis"){return "A_VORBIS";}
|
|
if (Trk.codec == "theora"){return "V_THEORA";}
|
|
if (Trk.codec == "MPEG2"){return "V_MPEG2";}
|
|
if (Trk.codec == "PCM"){return "A_PCM/INT/BIG";}
|
|
if (Trk.codec == "MP2"){return "A_MPEG/L2";}
|
|
if (Trk.codec == "MP3"){return "A_MPEG/L3";}
|
|
if (Trk.codec == "AC3"){return "A_AC3";}
|
|
if (Trk.codec == "ALAW"){return "A_MS/ACM";}
|
|
if (Trk.codec == "ULAW"){return "A_MS/ACM";}
|
|
if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
|
|
return "E_UNKNOWN";
|
|
}
|
|
|
|
void OutEBML::sendElemTrackEntry(const DTSC::Track &Trk){
|
|
// First calculate the sizes of the TrackEntry and Audio/Video elements.
|
|
uint32_t sendLen = 0;
|
|
uint32_t subLen = 0;
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID);
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID);
|
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk));
|
|
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
|
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000'));
|
|
}else{
|
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
|
}
|
|
if (Trk.type == "video"){
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
|
}
|
|
if (Trk.type == "audio"){
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels);
|
|
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
|
}
|
|
sendLen += subLen;
|
|
|
|
// Now actually send.
|
|
EBML::sendElemHead(myConn, EBML::EID_TRACKENTRY, sendLen);
|
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKNUMBER, Trk.trackID);
|
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKUID, Trk.trackID);
|
|
EBML::sendElemStr(myConn, EBML::EID_CODECID, trackCodecID(Trk));
|
|
EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
|
EBML::sendElemUInt(myConn, EBML::EID_FLAGLACING, 0);
|
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
|
std::string init =
|
|
RIFF::fmt::generate(((Trk.codec == "ALAW") ? 6 : 7), Trk.channels, Trk.rate, Trk.bps,
|
|
Trk.channels * (Trk.size << 3), Trk.size);
|
|
EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, init.substr(8));
|
|
}else{
|
|
if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
|
|
}
|
|
if (Trk.type == "video"){
|
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
|
|
EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
|
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
|
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
|
|
}
|
|
if (Trk.type == "audio"){
|
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2);
|
|
EBML::sendElemHead(myConn, EBML::EID_AUDIO, subLen);
|
|
EBML::sendElemUInt(myConn, EBML::EID_CHANNELS, Trk.channels);
|
|
EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
|
EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size);
|
|
}
|
|
}
|
|
|
|
uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
|
|
// Calculate the sizes of the TrackEntry and Audio/Video elements.
|
|
uint32_t sendLen = 0;
|
|
uint32_t subLen = 0;
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID);
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID);
|
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk));
|
|
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
|
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000'));
|
|
}else{
|
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
|
}
|
|
if (Trk.type == "video"){
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
|
}
|
|
if (Trk.type == "audio"){
|
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels);
|
|
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
|
}
|
|
sendLen += subLen;
|
|
return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen;
|
|
}
|
|
|
|
void OutEBML::sendHeader(){
|
|
double duration = 0;
|
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
|
if (myMeta.vod){
|
|
duration = Trk.lastms - Trk.firstms;
|
|
}
|
|
if (myMeta.live){
|
|
needsLookAhead = 420;
|
|
}
|
|
//EBML header and Segment
|
|
EBML::sendElemEBML(myConn, doctype);
|
|
EBML::sendElemHead(myConn, EBML::EID_SEGMENT, segmentSize); // Default = Unknown size
|
|
if (myMeta.vod){
|
|
//SeekHead
|
|
EBML::sendElemHead(myConn, EBML::EID_SEEKHEAD, seekSize);
|
|
EBML::sendElemSeek(myConn, EBML::EID_INFO, seekheadSize);
|
|
EBML::sendElemSeek(myConn, EBML::EID_TRACKS, seekheadSize + infoSize);
|
|
EBML::sendElemSeek(myConn, EBML::EID_CUES, seekheadSize + infoSize + tracksSize);
|
|
}
|
|
//Info
|
|
EBML::sendElemInfo(myConn, "MistServer " PACKAGE_VERSION, duration);
|
|
//Tracks
|
|
uint32_t trackSizes = 0;
|
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
|
it != selectedTracks.end(); it++){
|
|
trackSizes += sizeElemTrackEntry(myMeta.tracks[*it]);
|
|
}
|
|
EBML::sendElemHead(myConn, EBML::EID_TRACKS, trackSizes);
|
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
|
it != selectedTracks.end(); it++){
|
|
sendElemTrackEntry(myMeta.tracks[*it]);
|
|
}
|
|
if (myMeta.vod){
|
|
EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
|
|
uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
|
uint32_t fragNo = 0;
|
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
|
//The first fragment always starts at time 0, even if the main track does not.
|
|
if (!fragNo){clusterStart = 0;}
|
|
EBML::sendElemCuePoint(myConn, clusterStart, Trk.trackID, tmpsegSize, 0);
|
|
tmpsegSize += clusterSizes[fragNo];
|
|
++fragNo;
|
|
}
|
|
}
|
|
sentHeader = true;
|
|
}
|
|
|
|
/// Seeks to the given byte position by doing a regular seek and remembering the byte offset from that point
|
|
void OutEBML::byteSeek(uint64_t startPos){
|
|
INFO_MSG("Seeking to %llu bytes", startPos);
|
|
sentHeader = false;
|
|
newClusterTime = 0;
|
|
if (startPos == 0){
|
|
seek(0);
|
|
return;
|
|
}
|
|
uint64_t headerSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize;
|
|
if (startPos < headerSize){
|
|
HIGH_MSG("Seek went into or before header");
|
|
seek(0);
|
|
myConn.skipBytes(startPos);
|
|
return;
|
|
}
|
|
startPos -= headerSize;
|
|
sentHeader = true;//skip the header
|
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
|
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
|
|
VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos);
|
|
if (startPos < it->second){
|
|
HIGH_MSG("Seek to fragment %llu (%llu ms)", it->first, Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
|
myConn.skipBytes(startPos);
|
|
seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
|
newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime();
|
|
return;
|
|
}
|
|
startPos -= it->second;
|
|
}
|
|
//End of file. This probably won't work right, but who cares, it's the end of the file.
|
|
}
|
|
|
|
void OutEBML::onHTTP(){
|
|
std::string method = H.method;
|
|
if(method == "OPTIONS" || method == "HEAD"){
|
|
H.Clean();
|
|
H.setCORSHeaders();
|
|
H.SetHeader("Content-Type", "video/MP4");
|
|
H.SetHeader("Accept-Ranges", "bytes, parsec");
|
|
H.SendResponse("200", "OK", myConn);
|
|
return;
|
|
}
|
|
if (H.url.find(".webm") != std::string::npos){
|
|
doctype = "webm";
|
|
}else{
|
|
doctype = "matroska";
|
|
}
|
|
|
|
//Calculate the sizes of various parts, if we're VoD.
|
|
uint64_t totalSize = 0;
|
|
if (myMeta.vod){
|
|
calcVodSizes();
|
|
//We now know the full size of the segment, thus can calculate the total size
|
|
totalSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + segmentSize;
|
|
}
|
|
|
|
uint64_t byteEnd = totalSize-1;
|
|
uint64_t byteStart = 0;
|
|
|
|
/*LTS-START*/
|
|
// allow setting of max lead time through buffer variable.
|
|
// max lead time is set in MS, but the variable is in integer seconds for simplicity.
|
|
if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;}
|
|
//allow setting of play back rate through buffer variable.
|
|
//play back rate is set in MS per second, but the variable is a simple multiplier.
|
|
if (H.GetVar("rate") != ""){
|
|
long long int multiplier = JSON::Value(H.GetVar("rate")).asInt();
|
|
if (multiplier){
|
|
realTime = 1000 / multiplier;
|
|
}else{
|
|
realTime = 0;
|
|
}
|
|
}
|
|
if (H.GetHeader("X-Mist-Rate") != ""){
|
|
long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt();
|
|
if (multiplier){
|
|
realTime = 1000 / multiplier;
|
|
}else{
|
|
realTime = 0;
|
|
}
|
|
}
|
|
/*LTS-END*/
|
|
|
|
char rangeType = ' ';
|
|
if (!myMeta.live){
|
|
if (H.GetHeader("Range") != ""){
|
|
if (parseRange(byteStart, byteEnd)){
|
|
if (H.GetVar("buffer") == ""){
|
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
|
maxSkipAhead = (Trk.lastms - Trk.firstms) / 20 + 7500;
|
|
}
|
|
}
|
|
rangeType = H.GetHeader("Range")[0];
|
|
}
|
|
}
|
|
H.Clean(); //make sure no parts of old requests are left in any buffers
|
|
H.setCORSHeaders();
|
|
H.SetHeader("Content-Type", "video/webm");
|
|
if (myMeta.vod){
|
|
H.SetHeader("Accept-Ranges", "bytes, parsec");
|
|
}
|
|
if (rangeType != ' '){
|
|
if (!byteEnd){
|
|
if (rangeType == 'p'){
|
|
H.SetBody("Starsystem not in communications range");
|
|
H.SendResponse("416", "Starsystem not in communications range", myConn);
|
|
return;
|
|
}else{
|
|
H.SetBody("Requested Range Not Satisfiable");
|
|
H.SendResponse("416", "Requested Range Not Satisfiable", myConn);
|
|
return;
|
|
}
|
|
}else{
|
|
std::stringstream rangeReply;
|
|
rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize;
|
|
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
|
H.SetHeader("Content-Range", rangeReply.str());
|
|
/// \todo Switch to chunked?
|
|
H.SendResponse("206", "Partial content", myConn);
|
|
//H.StartResponse("206", "Partial content", HTTP_R, conn);
|
|
byteSeek(byteStart);
|
|
}
|
|
}else{
|
|
if (myMeta.vod){
|
|
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
|
}
|
|
/// \todo Switch to chunked?
|
|
H.SendResponse("200", "OK", myConn);
|
|
//HTTP_S.StartResponse(HTTP_R, conn);
|
|
}
|
|
parseData = true;
|
|
wantRequest = false;
|
|
}
|
|
|
|
void OutEBML::calcVodSizes(){
|
|
if (segmentSize != 0xFFFFFFFFFFFFFFFFull){
|
|
//Already calculated
|
|
return;
|
|
}
|
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
|
double duration = Trk.lastms - Trk.firstms;
|
|
//Calculate the segment size
|
|
//Segment contains SeekHead, Info, Tracks, Cues (in that order)
|
|
//Howeveer, SeekHead is dependent on Info/Tracks sizes, so we calculate those first.
|
|
//Calculating Info size
|
|
infoSize = EBML::sizeElemInfo("MistServer " PACKAGE_VERSION, duration);
|
|
//Calculating Tracks size
|
|
tracksSize = 0;
|
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
|
it != selectedTracks.end(); it++){
|
|
tracksSize += sizeElemTrackEntry(myMeta.tracks[*it]);
|
|
}
|
|
tracksSize += EBML::sizeElemHead(EBML::EID_TRACKS, tracksSize);
|
|
//Calculating SeekHead size
|
|
//Positions are relative to the first Segment, byte 0 = first byte of contents of Segment.
|
|
//Tricky starts here: the size of the SeekHead element is dependent on the seek offsets contained inside,
|
|
//which are in turn dependent on the size of the SeekHead element. Fun times! We loop until it stabilizes.
|
|
uint32_t oldseekSize = 0;
|
|
do {
|
|
oldseekSize = seekSize;
|
|
seekSize = EBML::sizeElemSeek(EBML::EID_INFO, seekheadSize) +
|
|
EBML::sizeElemSeek(EBML::EID_TRACKS, seekheadSize + infoSize) +
|
|
EBML::sizeElemSeek(EBML::EID_CUES, seekheadSize + infoSize + tracksSize);
|
|
seekheadSize = EBML::sizeElemHead(EBML::EID_SEEKHEAD, seekSize) + seekSize;
|
|
}while(seekSize != oldseekSize);
|
|
//The Cues are tricky: the Cluster offsets are dependent on the size of Cues itself.
|
|
//Which, in turn, is dependent on the Cluster offsets.
|
|
//We make this a bit easier by pre-calculating the sizes of all clusters first
|
|
uint64_t fragNo = 0;
|
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
|
uint64_t clusterEnd = clusterStart + it->getDuration();
|
|
//The first fragment always starts at time 0, even if the main track does not.
|
|
if (!fragNo){clusterStart = 0;}
|
|
//The last fragment always ends at the end, even if the main track does not.
|
|
if (fragNo == Trk.fragments.size() - 1){clusterEnd = 0xFFFFFFFFFFFFFFFFull;}
|
|
uint64_t cSize = clusterSize(clusterStart, clusterEnd);
|
|
clusterSizes[fragNo] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
|
|
++fragNo;
|
|
}
|
|
//Calculating Cues size
|
|
//We also calculate Clusters here: Clusters are grouped by fragments of the main track.
|
|
//CueClusterPosition uses the same offsets as SeekPosition.
|
|
//CueRelativePosition is the offset from that Cluster's first content byte.
|
|
//All this uses the same technique as above. More fun times!
|
|
uint32_t oldcuesSize = 0;
|
|
do {
|
|
oldcuesSize = cuesSize;
|
|
segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
|
uint32_t cuesInside = 0;
|
|
fragNo = 0;
|
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
|
//The first fragment always starts at time 0, even if the main track does not.
|
|
if (!fragNo){clusterStart = 0;}
|
|
cuesInside += EBML::sizeElemCuePoint(clusterStart, Trk.trackID, segmentSize, 0);
|
|
segmentSize += clusterSizes[fragNo];
|
|
++fragNo;
|
|
}
|
|
cuesSize = cuesInside;
|
|
}while(cuesSize != oldcuesSize);
|
|
}
|
|
|
|
}// namespace Mist
|
|
|