EBML updates:
- AV1 support - Support for outputting fragments longer than 30 seconds in duration - Fixed FireFox support for Opus audio tracks - Added support for stdin live input of EBML - Fixed broken timestamps when seeking in VoD EBML files - Analyser now calculates offsets for (manual) double-checking - Added JSON track support to EBML input and output - Added basic input support for SRT/ASS/SSA subtitles - Opus CODECDELAY now actually calculated. - Fixed Opus in Firefox - Improved MP3 support, more robust handling of corruption, support for non-standard timescale sources
This commit is contained in:
parent
7f770b27b7
commit
68a1bff34f
8 changed files with 351 additions and 91 deletions
54
lib/ebml.cpp
54
lib/ebml.cpp
|
@ -16,7 +16,7 @@ namespace EBML{
|
||||||
if (p[0] & 0x04){return 6;}
|
if (p[0] & 0x04){return 6;}
|
||||||
if (p[0] & 0x02){return 7;}
|
if (p[0] & 0x02){return 7;}
|
||||||
if (p[0] & 0x01){return 8;}
|
if (p[0] & 0x01){return 8;}
|
||||||
return 0;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the size of an EBML-encoded integer for a given numerical value
|
/// Returns the size of an EBML-encoded integer for a given numerical value
|
||||||
|
@ -149,8 +149,8 @@ namespace EBML{
|
||||||
case EID_PIXELWIDTH: return "PixelWidth";
|
case EID_PIXELWIDTH: return "PixelWidth";
|
||||||
case EID_PIXELHEIGHT: return "PixelHeight";
|
case EID_PIXELHEIGHT: return "PixelHeight";
|
||||||
case 0x1A: return "FlagInterlaced";
|
case 0x1A: return "FlagInterlaced";
|
||||||
case 0x14B0: return "DisplayWidth";
|
case EID_DISPLAYWIDTH: return "DisplayWidth";
|
||||||
case 0x14BA: return "DisplayHeight";
|
case EID_DISPLAYHEIGHT: return "DisplayHeight";
|
||||||
case 0x15B0: return "Colour";
|
case 0x15B0: return "Colour";
|
||||||
case 0x15B7: return "ChromaSitingHorz";
|
case 0x15B7: return "ChromaSitingHorz";
|
||||||
case 0x15B8: return "ChromaSitingVert";
|
case 0x15B8: return "ChromaSitingVert";
|
||||||
|
@ -164,8 +164,8 @@ namespace EBML{
|
||||||
case EID_CHANNELS: return "Channels";
|
case EID_CHANNELS: return "Channels";
|
||||||
case EID_SAMPLINGFREQUENCY: return "SamplingFrequency";
|
case EID_SAMPLINGFREQUENCY: return "SamplingFrequency";
|
||||||
case EID_BITDEPTH: return "BitDepth";
|
case EID_BITDEPTH: return "BitDepth";
|
||||||
case 0x16AA: return "CodecDelay";
|
case EID_CODECDELAY: return "CodecDelay";
|
||||||
case 0x16BB: return "SeekPreRoll";
|
case EID_SEEKPREROLL: return "SeekPreRoll";
|
||||||
case EID_CODECPRIVATE: return "CodecPrivate";
|
case EID_CODECPRIVATE: return "CodecPrivate";
|
||||||
case EID_DEFAULTDURATION: return "DefaultDuration";
|
case EID_DEFAULTDURATION: return "DefaultDuration";
|
||||||
case EID_EBMLVERSION: return "EBMLVersion";
|
case EID_EBMLVERSION: return "EBMLVersion";
|
||||||
|
@ -185,7 +185,7 @@ namespace EBML{
|
||||||
case 0x6C: return "Void";
|
case 0x6C: return "Void";
|
||||||
case 0x3F: return "CRC-32";
|
case 0x3F: return "CRC-32";
|
||||||
case 0x33A4: return "SegmentUID";
|
case 0x33A4: return "SegmentUID";
|
||||||
case 0x254c367: return "Tags";
|
case EID_TAGS: return "Tags";
|
||||||
case 0x3373: return "Tag";
|
case 0x3373: return "Tag";
|
||||||
case 0x23C0: return "Targets";
|
case 0x23C0: return "Targets";
|
||||||
case 0x27C8: return "SimpleTag";
|
case 0x27C8: return "SimpleTag";
|
||||||
|
@ -271,7 +271,7 @@ namespace EBML{
|
||||||
case EID_CUEPOINT:
|
case EID_CUEPOINT:
|
||||||
case EID_CUETRACKPOSITIONS:
|
case EID_CUETRACKPOSITIONS:
|
||||||
case 0x15B0:
|
case 0x15B0:
|
||||||
case 0x254c367:
|
case EID_TAGS:
|
||||||
case 0x3373:
|
case 0x3373:
|
||||||
case 0x23C0:
|
case 0x23C0:
|
||||||
case 0x43a770:
|
case 0x43a770:
|
||||||
|
@ -296,8 +296,8 @@ namespace EBML{
|
||||||
case EID_FLAGLACING:
|
case EID_FLAGLACING:
|
||||||
case EID_TRACKTYPE:
|
case EID_TRACKTYPE:
|
||||||
case EID_DEFAULTDURATION:
|
case EID_DEFAULTDURATION:
|
||||||
case 0x16AA:
|
case EID_CODECDELAY:
|
||||||
case 0x16BB:
|
case EID_SEEKPREROLL:
|
||||||
case EID_CUETIME:
|
case EID_CUETIME:
|
||||||
case EID_CUETRACK:
|
case EID_CUETRACK:
|
||||||
case EID_CUECLUSTERPOSITION:
|
case EID_CUECLUSTERPOSITION:
|
||||||
|
@ -305,8 +305,8 @@ namespace EBML{
|
||||||
case EID_PIXELWIDTH:
|
case EID_PIXELWIDTH:
|
||||||
case EID_PIXELHEIGHT:
|
case EID_PIXELHEIGHT:
|
||||||
case 0x1A:
|
case 0x1A:
|
||||||
case 0x14B0:
|
case EID_DISPLAYWIDTH:
|
||||||
case 0x14BA:
|
case EID_DISPLAYHEIGHT:
|
||||||
case EID_CHANNELS:
|
case EID_CHANNELS:
|
||||||
case EID_BITDEPTH:
|
case EID_BITDEPTH:
|
||||||
case 0x15B7:
|
case 0x15B7:
|
||||||
|
@ -652,26 +652,28 @@ namespace EBML{
|
||||||
case 3: ret << " [Lacing: EMBL]"; break;
|
case 3: ret << " [Lacing: EMBL]"; break;
|
||||||
case 2: ret << " [Lacing: Fixed]"; break;
|
case 2: ret << " [Lacing: Fixed]"; break;
|
||||||
}
|
}
|
||||||
if (detail < 8){
|
ret << std::endl;
|
||||||
ret << std::endl;
|
if (detail >= 4){
|
||||||
return ret.str();
|
for (uint32_t frameNo = 0; frameNo < getFrameCount(); ++frameNo){
|
||||||
|
const char *payDat = getFrameData(frameNo);
|
||||||
|
const uint64_t payLen = getFrameSize(frameNo);
|
||||||
|
ret << std::dec << std::string(indent + 4, ' ') << "Frame " << (frameNo+1) << " (" << payLen << "b):";
|
||||||
|
if (!payDat || !payLen || detail < 6){
|
||||||
|
ret << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (uint64_t i = 0; i < payLen; ++i){
|
||||||
|
if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
|
||||||
|
ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
|
||||||
|
}
|
||||||
|
ret << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret << ":";
|
|
||||||
if (detail >= 10){
|
if (detail >= 10){
|
||||||
uint32_t extraStuff = (UniInt::readSize(getPayload()) + 3);
|
uint32_t extraStuff = (UniInt::readSize(getPayload()) + 3);
|
||||||
const char *payDat = getPayload() + extraStuff;
|
const char *payDat = getPayload() + extraStuff;
|
||||||
const uint64_t payLen = getPayloadLen() - extraStuff;
|
const uint64_t payLen = getPayloadLen() - extraStuff;
|
||||||
ret << std::endl << std::dec << std::string(indent + 4, ' ') << "Raw data:";
|
ret << std::dec << std::string(indent + 4, ' ') << "Raw data:";
|
||||||
for (uint64_t i = 0; i < payLen; ++i){
|
|
||||||
if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
|
|
||||||
ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (uint32_t frameNo = 0; frameNo < getFrameCount(); ++frameNo){
|
|
||||||
const char *payDat = getFrameData(frameNo);
|
|
||||||
const uint64_t payLen = getFrameSize(frameNo);
|
|
||||||
ret << std::endl << std::dec << std::string(indent + 4, ' ') << "Frame " << (frameNo+1) << " (" << payLen << "b):";
|
|
||||||
if (!payDat || !payLen){continue;}
|
|
||||||
for (uint64_t i = 0; i < payLen; ++i){
|
for (uint64_t i = 0; i < payLen; ++i){
|
||||||
if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
|
if ((i % 32) == 0){ret << std::endl << std::string(indent + 6, ' ');}
|
||||||
ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
|
ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i];
|
||||||
|
|
|
@ -48,6 +48,8 @@ namespace EBML{
|
||||||
EID_PIXELWIDTH = 0x30,
|
EID_PIXELWIDTH = 0x30,
|
||||||
EID_FLAGLACING = 0x1C,
|
EID_FLAGLACING = 0x1C,
|
||||||
EID_PIXELHEIGHT = 0x3A,
|
EID_PIXELHEIGHT = 0x3A,
|
||||||
|
EID_DISPLAYWIDTH = 0x14B0,
|
||||||
|
EID_DISPLAYHEIGHT = 0x14BA,
|
||||||
EID_TRACKNUMBER = 0x57,
|
EID_TRACKNUMBER = 0x57,
|
||||||
EID_CODECPRIVATE = 0x23A2,
|
EID_CODECPRIVATE = 0x23A2,
|
||||||
EID_LANGUAGE = 0x2B59C,
|
EID_LANGUAGE = 0x2B59C,
|
||||||
|
@ -72,6 +74,9 @@ namespace EBML{
|
||||||
EID_CUETRACKPOSITIONS = 0x37,
|
EID_CUETRACKPOSITIONS = 0x37,
|
||||||
EID_CUETIME = 0x33,
|
EID_CUETIME = 0x33,
|
||||||
EID_CUEPOINT = 0x3B,
|
EID_CUEPOINT = 0x3B,
|
||||||
|
EID_TAGS = 0x254c367,
|
||||||
|
EID_CODECDELAY = 0x16AA,
|
||||||
|
EID_SEEKPREROLL = 0x16BB,
|
||||||
EID_UNKNOWN = 0
|
EID_UNKNOWN = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ void AnalyserEBML::init(Util::Config &conf){
|
||||||
|
|
||||||
AnalyserEBML::AnalyserEBML(Util::Config &conf) : Analyser(conf){
|
AnalyserEBML::AnalyserEBML(Util::Config &conf) : Analyser(conf){
|
||||||
curPos = prePos = 0;
|
curPos = prePos = 0;
|
||||||
|
lastSeekId = 0;
|
||||||
|
lastSeekPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnalyserEBML::parsePacket(){
|
bool AnalyserEBML::parsePacket(){
|
||||||
|
@ -15,11 +17,18 @@ bool AnalyserEBML::parsePacket(){
|
||||||
// Read in smart bursts until we have enough data
|
// Read in smart bursts until we have enough data
|
||||||
while (isOpen() && dataBuffer.size() < neededBytes()){
|
while (isOpen() && dataBuffer.size() < neededBytes()){
|
||||||
uint64_t needed = neededBytes();
|
uint64_t needed = neededBytes();
|
||||||
|
if (needed > 1024*1024){
|
||||||
|
dataBuffer.erase(0, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
dataBuffer.reserve(needed);
|
dataBuffer.reserve(needed);
|
||||||
for (uint64_t i = dataBuffer.size(); i < needed; ++i){
|
for (uint64_t i = dataBuffer.size(); i < needed; ++i){
|
||||||
dataBuffer += std::cin.get();
|
dataBuffer += std::cin.get();
|
||||||
++curPos;
|
++curPos;
|
||||||
if (!std::cin.good()){dataBuffer.erase(dataBuffer.size() - 1, 1);}
|
if (!std::cin.good()){
|
||||||
|
dataBuffer.erase(dataBuffer.size() - 1, 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +37,33 @@ bool AnalyserEBML::parsePacket(){
|
||||||
EBML::Element E(dataBuffer.data(), true);
|
EBML::Element E(dataBuffer.data(), true);
|
||||||
HIGH_MSG("Read an element at position %d", prePos);
|
HIGH_MSG("Read an element at position %d", prePos);
|
||||||
if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
|
if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
|
||||||
|
switch (E.getID()){
|
||||||
|
case EBML::EID_SEGMENT:
|
||||||
|
segmentOffset = prePos + E.getHeaderLen();
|
||||||
|
std::cout << "[OFFSET INFORMATION] Segment offset is " << segmentOffset << std::endl;
|
||||||
|
break;
|
||||||
|
case EBML::EID_CLUSTER:
|
||||||
|
std::cout << "[OFFSET INFORMATION] Cluster at " << (prePos-segmentOffset) << std::endl;
|
||||||
|
break;
|
||||||
|
case EBML::EID_SEEKID:
|
||||||
|
lastSeekId = E.getValUInt();
|
||||||
|
break;
|
||||||
|
case EBML::EID_SEEKPOSITION:
|
||||||
|
lastSeekPos = E.getValUInt();
|
||||||
|
break;
|
||||||
|
case EBML::EID_INFO:
|
||||||
|
case EBML::EID_TRACKS:
|
||||||
|
case EBML::EID_TAGS:
|
||||||
|
case EBML::EID_CUES:
|
||||||
|
{
|
||||||
|
uint32_t sID = E.getID();
|
||||||
|
std::cout << "Encountered " << sID << std::endl;
|
||||||
|
if (seekChecks.count(sID)){
|
||||||
|
std::cout << "[OFFSET INFORMATION] Segment " << EBML::Element::getIDString(sID) << " is at " << prePos << ", expected was " << seekChecks[sID] << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (depthStash.size()){
|
if (depthStash.size()){
|
||||||
depthStash.front() -= E.getOuterLen();
|
depthStash.front() -= E.getOuterLen();
|
||||||
}
|
}
|
||||||
|
@ -36,6 +72,25 @@ bool AnalyserEBML::parsePacket(){
|
||||||
}
|
}
|
||||||
while (depthStash.size() && !depthStash.front()){
|
while (depthStash.size() && !depthStash.front()){
|
||||||
depthStash.pop_front();
|
depthStash.pop_front();
|
||||||
|
if (lastSeekId){
|
||||||
|
if (lastSeekId > 0xFFFFFF){
|
||||||
|
lastSeekId &= 0xFFFFFFF;
|
||||||
|
}else{
|
||||||
|
if (lastSeekId > 0xFFFF){
|
||||||
|
lastSeekId &= 0x1FFFFF;
|
||||||
|
}else{
|
||||||
|
if (lastSeekId > 0xFF){
|
||||||
|
lastSeekId &= 0x3FFF;
|
||||||
|
}else{
|
||||||
|
lastSeekId &= 0x7F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seekChecks[lastSeekId] = segmentOffset+lastSeekPos;
|
||||||
|
std::cout << "[OFFSET INFORMATION] Segment offset for " << EBML::Element::getIDString(lastSeekId) << " (" << lastSeekId << ") is " << (segmentOffset+lastSeekPos) << std::endl;
|
||||||
|
lastSeekId = 0;
|
||||||
|
lastSeekPos = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
///\TODO update mediaTime with the current timestamp
|
///\TODO update mediaTime with the current timestamp
|
||||||
dataBuffer.erase(0, E.getOuterLen());
|
dataBuffer.erase(0, E.getOuterLen());
|
||||||
|
|
|
@ -12,6 +12,10 @@ private:
|
||||||
std::string dataBuffer;
|
std::string dataBuffer;
|
||||||
uint64_t curPos;
|
uint64_t curPos;
|
||||||
uint64_t prePos;
|
uint64_t prePos;
|
||||||
|
uint64_t segmentOffset;
|
||||||
|
uint32_t lastSeekId;
|
||||||
|
uint64_t lastSeekPos;
|
||||||
|
std::map<uint32_t, uint64_t> seekChecks;
|
||||||
std::deque<uint64_t> depthStash;///<Contains bytes to read to go up a level in the element depth.
|
std::deque<uint64_t> depthStash;///<Contains bytes to read to go up a level in the element depth.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
#include <mist/bitfields.h>
|
#include <mist/bitfields.h>
|
||||||
|
|
||||||
namespace Mist{
|
namespace Mist{
|
||||||
|
|
||||||
|
uint16_t maxEBMLFrameOffset = 0;
|
||||||
|
bool frameOffsetKnown = false;
|
||||||
|
|
||||||
InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){
|
InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){
|
||||||
|
timeScale = 1.0;
|
||||||
capa["name"] = "EBML";
|
capa["name"] = "EBML";
|
||||||
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand.";
|
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or accepts live streams in those formats over standard input.";
|
||||||
capa["source_match"].append("/*.mkv");
|
capa["source_match"].append("/*.mkv");
|
||||||
capa["source_match"].append("/*.mka");
|
capa["source_match"].append("/*.mka");
|
||||||
capa["source_match"].append("/*.mk3d");
|
capa["source_match"].append("/*.mk3d");
|
||||||
|
@ -18,6 +23,7 @@ namespace Mist{
|
||||||
capa["codecs"].append("HEVC");
|
capa["codecs"].append("HEVC");
|
||||||
capa["codecs"].append("VP8");
|
capa["codecs"].append("VP8");
|
||||||
capa["codecs"].append("VP9");
|
capa["codecs"].append("VP9");
|
||||||
|
capa["codecs"].append("AV1");
|
||||||
capa["codecs"].append("opus");
|
capa["codecs"].append("opus");
|
||||||
capa["codecs"].append("vorbis");
|
capa["codecs"].append("vorbis");
|
||||||
capa["codecs"].append("theora");
|
capa["codecs"].append("theora");
|
||||||
|
@ -30,16 +36,40 @@ namespace Mist{
|
||||||
capa["codecs"].append("MP3");
|
capa["codecs"].append("MP3");
|
||||||
capa["codecs"].append("AC3");
|
capa["codecs"].append("AC3");
|
||||||
capa["codecs"].append("FLOAT");
|
capa["codecs"].append("FLOAT");
|
||||||
|
capa["codecs"].append("JSON");
|
||||||
|
capa["codecs"].append("subtitle");
|
||||||
lastClusterBPos = 0;
|
lastClusterBPos = 0;
|
||||||
lastClusterTime = 0;
|
lastClusterTime = 0;
|
||||||
bufferedPacks = 0;
|
bufferedPacks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InputEBML::checkArguments(){
|
std::string ASStoSRT(const char * ptr, uint32_t len){
|
||||||
if (config->getString("input") == "-"){
|
uint16_t commas = 0;
|
||||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
uint16_t brackets = 0;
|
||||||
return false;
|
std::string tmpStr;
|
||||||
|
tmpStr.reserve(len);
|
||||||
|
for (uint32_t i = 0; i < len; ++i){
|
||||||
|
//Skip everything until the 8th comma
|
||||||
|
if (commas < 8){
|
||||||
|
if (ptr[i] == ','){commas++;}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ptr[i] == '{'){brackets++; continue;}
|
||||||
|
if (ptr[i] == '}'){brackets--; continue;}
|
||||||
|
if (!brackets){
|
||||||
|
if (ptr[i] == '\\' && i < len-1 && (ptr[i+1] == 'N' || ptr[i+1] == 'n')){
|
||||||
|
tmpStr += '\n';
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tmpStr += ptr[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return tmpStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool InputEBML::checkArguments(){
|
||||||
if (!config->getString("streamname").size()){
|
if (!config->getString("streamname").size()){
|
||||||
if (config->getString("output") == "-"){
|
if (config->getString("output") == "-"){
|
||||||
std::cerr << "Output to stdout not yet supported" << std::endl;
|
std::cerr << "Output to stdout not yet supported" << std::endl;
|
||||||
|
@ -54,10 +84,23 @@ namespace Mist{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InputEBML::needsLock() {
|
||||||
|
//Standard input requires no lock, everything else does.
|
||||||
|
if (config->getString("input") != "-"){
|
||||||
|
return true;
|
||||||
|
}else{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool InputEBML::preRun(){
|
bool InputEBML::preRun(){
|
||||||
// open File
|
if (config->getString("input") == "-"){
|
||||||
inFile = fopen(config->getString("input").c_str(), "r");
|
inFile = stdin;
|
||||||
if (!inFile){return false;}
|
}else{
|
||||||
|
// open File
|
||||||
|
inFile = fopen(config->getString("input").c_str(), "r");
|
||||||
|
if (!inFile){return false;}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +111,10 @@ namespace Mist{
|
||||||
while (ptr.size() < needed){
|
while (ptr.size() < needed){
|
||||||
if (!ptr.allocate(needed)){return false;}
|
if (!ptr.allocate(needed)){return false;}
|
||||||
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
|
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
|
||||||
FAIL_MSG("Could not read more data!");
|
//We assume if there is no current data buffered, that we are at EOF and don't print a warning
|
||||||
|
if (ptr.size()){
|
||||||
|
FAIL_MSG("Could not read more data! (have %lu, need %lu)", ptr.size(), needed);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ptr.size() = needed;
|
ptr.size() = needed;
|
||||||
|
@ -82,8 +128,18 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EBML::Element E(ptr);
|
EBML::Element E(ptr);
|
||||||
if (E.getID() == EBML::EID_CLUSTER){lastClusterBPos = Util::ftell(inFile);}
|
if (E.getID() == EBML::EID_CLUSTER){
|
||||||
if (E.getID() == EBML::EID_TIMECODE){lastClusterTime = E.getValUInt();}
|
if (inFile == stdin){
|
||||||
|
lastClusterBPos = 0;
|
||||||
|
}else{
|
||||||
|
lastClusterBPos = Util::ftell(inFile);
|
||||||
|
}
|
||||||
|
DONTEVEN_MSG("Found a cluster at position %llu", lastClusterBPos);
|
||||||
|
}
|
||||||
|
if (E.getID() == EBML::EID_TIMECODE){
|
||||||
|
lastClusterTime = E.getValUInt();
|
||||||
|
DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +152,13 @@ namespace Mist{
|
||||||
swapEndianness.insert(it->first);
|
swapEndianness.insert(it->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (myMeta.inputLocalVars.isMember("timescale")){
|
||||||
|
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
|
||||||
|
}
|
||||||
|
if (myMeta.inputLocalVars.isMember("maxframeoffset")){
|
||||||
|
maxEBMLFrameOffset = myMeta.inputLocalVars["maxframeoffset"].asInt();
|
||||||
|
frameOffsetKnown = true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +194,10 @@ namespace Mist{
|
||||||
tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
|
tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
|
||||||
if (tmpElem){init = tmpElem.getValString();}
|
if (tmpElem){init = tmpElem.getValString();}
|
||||||
}
|
}
|
||||||
|
if (codec == "V_AV1"){
|
||||||
|
trueCodec = "AV1";
|
||||||
|
trueType = "video";
|
||||||
|
}
|
||||||
if (codec == "V_VP9"){
|
if (codec == "V_VP9"){
|
||||||
trueCodec = "VP9";
|
trueCodec = "VP9";
|
||||||
trueType = "video";
|
trueType = "video";
|
||||||
|
@ -191,6 +258,20 @@ namespace Mist{
|
||||||
trueCodec = "FLOAT";
|
trueCodec = "FLOAT";
|
||||||
trueType = "audio";
|
trueType = "audio";
|
||||||
}
|
}
|
||||||
|
if (codec == "M_JSON"){
|
||||||
|
trueCodec = "JSON";
|
||||||
|
trueType = "meta";
|
||||||
|
}
|
||||||
|
if (codec == "S_TEXT/UTF8"){
|
||||||
|
trueCodec = "subtitle";
|
||||||
|
trueType = "meta";
|
||||||
|
}
|
||||||
|
if (codec == "S_TEXT/ASS" || codec == "S_TEXT/SSA"){
|
||||||
|
trueCodec = "subtitle";
|
||||||
|
trueType = "meta";
|
||||||
|
tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
|
||||||
|
if (tmpElem){init = tmpElem.getValString();}
|
||||||
|
}
|
||||||
if (codec == "A_MS/ACM"){
|
if (codec == "A_MS/ACM"){
|
||||||
tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
|
tmpElem = E.findChild(EBML::EID_CODECPRIVATE);
|
||||||
if (tmpElem){
|
if (tmpElem){
|
||||||
|
@ -248,6 +329,13 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str());
|
INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str());
|
||||||
}
|
}
|
||||||
|
if (E.getID() == EBML::EID_TIMECODESCALE){
|
||||||
|
uint64_t timeScaleVal = E.getValUInt();
|
||||||
|
myMeta.inputLocalVars["timescale"] = (long long)timeScaleVal;
|
||||||
|
timeScale = ((double)timeScaleVal) / 1000000.0;
|
||||||
|
}
|
||||||
|
//Live streams stop parsing the header as soon as the first Cluster is encountered
|
||||||
|
if (E.getID() == EBML::EID_CLUSTER && !needsLock()){return true;}
|
||||||
if (E.getType() == EBML::ELEM_BLOCK){
|
if (E.getType() == EBML::ELEM_BLOCK){
|
||||||
EBML::Block B(ptr);
|
EBML::Block B(ptr);
|
||||||
uint64_t tNum = B.getTrackNum();
|
uint64_t tNum = B.getTrackNum();
|
||||||
|
@ -255,21 +343,32 @@ namespace Mist{
|
||||||
trackPredictor &TP = packBuf[tNum];
|
trackPredictor &TP = packBuf[tNum];
|
||||||
DTSC::Track &Trk = myMeta.tracks[tNum];
|
DTSC::Track &Trk = myMeta.tracks[tNum];
|
||||||
bool isVideo = (Trk.type == "video");
|
bool isVideo = (Trk.type == "video");
|
||||||
|
bool isAudio = (Trk.type == "audio");
|
||||||
|
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
|
||||||
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
|
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
|
||||||
if (frameNo){
|
if (frameNo){
|
||||||
if (Trk.codec == "AAC"){
|
if (Trk.codec == "AAC"){
|
||||||
newTime += 1000000 / Trk.rate;//assume ~1000 samples per frame
|
newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
|
||||||
|
} else if (Trk.codec == "MP3"){
|
||||||
|
newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
|
||||||
}else{
|
}else{
|
||||||
|
newTime += 1/timeScale;
|
||||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint32_t frameSize = B.getFrameSize(frameNo);
|
uint32_t frameSize = B.getFrameSize(frameNo);
|
||||||
|
if (isASS){
|
||||||
|
char * ptr = (char *)B.getFrameData(frameNo);
|
||||||
|
std::string assStr = ASStoSRT(ptr, frameSize);
|
||||||
|
frameSize = assStr.size();
|
||||||
|
}
|
||||||
if (frameSize){
|
if (frameSize){
|
||||||
TP.add(newTime, 0, tNum, frameSize, lastClusterBPos,
|
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
|
||||||
B.isKeyframe() && isVideo);
|
B.isKeyframe() && !isAudio, isVideo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (TP.hasPackets()){
|
while (TP.hasPackets() && (isVideo || frameOffsetKnown)){
|
||||||
|
frameOffsetKnown = true;
|
||||||
packetData &C = TP.getPacketData(isVideo);
|
packetData &C = TP.getPacketData(isVideo);
|
||||||
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
|
||||||
TP.remove();
|
TP.remove();
|
||||||
|
@ -289,6 +388,8 @@ namespace Mist{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
myMeta.inputLocalVars["maxframeoffset"] = (long long)maxEBMLFrameOffset;
|
||||||
|
|
||||||
bench = Util::getMicros(bench);
|
bench = Util::getMicros(bench);
|
||||||
INFO_MSG("Header generated in %llu ms", bench / 1000);
|
INFO_MSG("Header generated in %llu ms", bench / 1000);
|
||||||
packBuf.clear();
|
packBuf.clear();
|
||||||
|
@ -386,19 +487,31 @@ namespace Mist{
|
||||||
trackPredictor &TP = packBuf[tNum];
|
trackPredictor &TP = packBuf[tNum];
|
||||||
DTSC::Track & Trk = myMeta.tracks[tNum];
|
DTSC::Track & Trk = myMeta.tracks[tNum];
|
||||||
bool isVideo = (Trk.type == "video");
|
bool isVideo = (Trk.type == "video");
|
||||||
|
bool isAudio = (Trk.type == "audio");
|
||||||
|
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
|
||||||
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
|
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
|
||||||
if (frameNo){
|
if (frameNo){
|
||||||
if (Trk.codec == "AAC"){
|
if (Trk.codec == "AAC"){
|
||||||
newTime += 1000000 / Trk.rate;//assume ~1000 samples per frame
|
newTime += (1000000 / Trk.rate)/timeScale;//assume ~1000 samples per frame
|
||||||
|
} else if (Trk.codec == "MP3"){
|
||||||
|
newTime += (1152000 / Trk.rate)/timeScale;//1152 samples per frame
|
||||||
}else{
|
}else{
|
||||||
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uint32_t frameSize = B.getFrameSize(frameNo);
|
uint32_t frameSize = B.getFrameSize(frameNo);
|
||||||
if (frameSize){
|
if (frameSize){
|
||||||
TP.add(newTime, 0, tNum, frameSize, lastClusterBPos,
|
char * ptr = (char *)B.getFrameData(frameNo);
|
||||||
B.isKeyframe() && isVideo, (void *)B.getFrameData(frameNo));
|
if (isASS){
|
||||||
++bufferedPacks;
|
std::string assStr = ASStoSRT(ptr, frameSize);
|
||||||
|
frameSize = assStr.size();
|
||||||
|
memcpy(ptr, assStr.data(), frameSize);
|
||||||
|
}
|
||||||
|
if (frameSize){
|
||||||
|
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
|
||||||
|
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
|
||||||
|
++bufferedPacks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (TP.hasPackets()){
|
if (TP.hasPackets()){
|
||||||
|
@ -416,10 +529,29 @@ namespace Mist{
|
||||||
void InputEBML::seek(int seekTime){
|
void InputEBML::seek(int seekTime){
|
||||||
packBuf.clear();
|
packBuf.clear();
|
||||||
bufferedPacks = 0;
|
bufferedPacks = 0;
|
||||||
DTSC::Track Trk = myMeta.tracks[getMainSelectedTrack()];
|
uint64_t mainTrack = getMainSelectedTrack();
|
||||||
|
DTSC::Track Trk = myMeta.tracks[mainTrack];
|
||||||
|
bool isVideo = (Trk.type == "video");
|
||||||
uint64_t seekPos = Trk.keys[0].getBpos();
|
uint64_t seekPos = Trk.keys[0].getBpos();
|
||||||
|
// Replay the parts of the previous keyframe, so the timestaps match up
|
||||||
|
uint64_t partCount = 0;
|
||||||
for (unsigned int i = 0; i < Trk.keys.size(); i++){
|
for (unsigned int i = 0; i < Trk.keys.size(); i++){
|
||||||
if (Trk.keys[i].getTime() > seekTime){break;}
|
if (Trk.keys[i].getTime() > seekTime){
|
||||||
|
if (i > 1){
|
||||||
|
partCount -= Trk.keys[i-1].getParts() + Trk.keys[i-2].getParts();
|
||||||
|
uint64_t partEnd = partCount + Trk.keys[i-2].getParts();
|
||||||
|
uint64_t partTime = Trk.keys[i-2].getTime();
|
||||||
|
for (uint64_t prt = partCount; prt < partEnd; ++prt){
|
||||||
|
INSANE_MSG("Replay part %llu, timestamp: %llu+%llu", prt, partTime, Trk.parts[prt].getOffset());
|
||||||
|
packBuf[mainTrack].add(partTime, Trk.parts[prt].getOffset(), mainTrack, 0, 0, false, isVideo, (void *)0);
|
||||||
|
packBuf[mainTrack].remove();
|
||||||
|
partTime += Trk.parts[prt].getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
partCount += Trk.keys[i].getParts();
|
||||||
|
DONTEVEN_MSG("Seeking to %lu, found %llu...", seekTime, Trk.keys[i].getTime());
|
||||||
seekPos = Trk.keys[i].getBpos();
|
seekPos = Trk.keys[i].getBpos();
|
||||||
}
|
}
|
||||||
Util::fseek(inFile, seekPos, SEEK_SET);
|
Util::fseek(inFile, seekPos, SEEK_SET);
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
|
|
||||||
namespace Mist{
|
namespace Mist{
|
||||||
|
|
||||||
|
|
||||||
|
extern uint16_t maxEBMLFrameOffset;
|
||||||
|
extern bool frameOffsetKnown;
|
||||||
|
#define PKT_COUNT 64
|
||||||
|
|
||||||
class packetData{
|
class packetData{
|
||||||
public:
|
public:
|
||||||
uint64_t time, offset, track, dsize, bpos;
|
uint64_t time, offset, track, dsize, bpos;
|
||||||
|
@ -33,7 +38,7 @@ namespace Mist{
|
||||||
};
|
};
|
||||||
class trackPredictor{
|
class trackPredictor{
|
||||||
public:
|
public:
|
||||||
packetData pkts[16];
|
packetData pkts[PKT_COUNT];
|
||||||
uint16_t smallestFrame;
|
uint16_t smallestFrame;
|
||||||
uint64_t lastTime;
|
uint64_t lastTime;
|
||||||
uint64_t ctr;
|
uint64_t ctr;
|
||||||
|
@ -48,31 +53,49 @@ namespace Mist{
|
||||||
if (finished){
|
if (finished){
|
||||||
return (ctr - rem > 0);
|
return (ctr - rem > 0);
|
||||||
}else{
|
}else{
|
||||||
return (ctr - rem > 8);
|
return (ctr - rem > 12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packetData & getPacketData(bool mustCalcOffsets){
|
packetData & getPacketData(bool mustCalcOffsets){
|
||||||
packetData & p = pkts[rem % 16];
|
frameOffsetKnown = true;
|
||||||
if (rem && mustCalcOffsets){
|
//grab the next packet to output
|
||||||
if (p.time > lastTime + smallestFrame){
|
packetData & p = pkts[rem % PKT_COUNT];
|
||||||
while (p.time - (lastTime + smallestFrame) > smallestFrame * 8){
|
//Substract the max frame offset, so we know all offsets are positive, no matter what.
|
||||||
lastTime += smallestFrame;
|
//if it's not the first and we're calculating offsets, see if we need an offset
|
||||||
}
|
if (!mustCalcOffsets){
|
||||||
p.offset = p.time - (lastTime + smallestFrame);
|
p.time += maxEBMLFrameOffset;
|
||||||
p.time = lastTime + smallestFrame;
|
DONTEVEN_MSG("Outputting %llu + %llu (%llu -> %llu)", p.time, maxEBMLFrameOffset, rem, rem % PKT_COUNT);
|
||||||
|
return p;
|
||||||
|
}else{
|
||||||
|
if (rem && !p.key){
|
||||||
|
p.offset = p.time + maxEBMLFrameOffset - (lastTime + smallestFrame);
|
||||||
|
//If we calculate an offset less than a frame away,
|
||||||
|
//we assume it's just time stamp drift due to lack of precision.
|
||||||
|
p.time = (lastTime + smallestFrame);
|
||||||
|
}else{
|
||||||
|
p.offset = maxEBMLFrameOffset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastTime = p.time;
|
lastTime = p.time;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){
|
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, bool isVideo, void * dataPtr = 0){
|
||||||
if (ctr && ctr > rem){
|
if (isVideo && ctr && ctr >= rem){
|
||||||
if ((pkts[(ctr-1)%16].time < packTime - 2) && (!smallestFrame || packTime - pkts[(ctr-1)%16].time < smallestFrame)){
|
int32_t currOffset = packTime - pkts[(ctr-1)%PKT_COUNT].time;
|
||||||
smallestFrame = packTime - pkts[(ctr-1)%16].time;
|
if (currOffset < 0){currOffset *= -1;}
|
||||||
|
if (!smallestFrame || currOffset < smallestFrame){
|
||||||
|
smallestFrame = currOffset;
|
||||||
|
HIGH_MSG("Smallest frame is now %u", smallestFrame);
|
||||||
|
}
|
||||||
|
if (!frameOffsetKnown && currOffset < 8*smallestFrame && currOffset*2 > maxEBMLFrameOffset && ctr < PKT_COUNT/2){
|
||||||
|
maxEBMLFrameOffset = currOffset*2;
|
||||||
|
INFO_MSG("Max frame offset is now %u", maxEBMLFrameOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pkts[ctr % 16].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
|
DONTEVEN_MSG("Ingesting %llu (%llu -> %llu)", packTime, ctr, ctr % PKT_COUNT);
|
||||||
|
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
|
||||||
++ctr;
|
++ctr;
|
||||||
|
if (ctr == PKT_COUNT-1){frameOffsetKnown = true;}
|
||||||
}
|
}
|
||||||
void remove(){
|
void remove(){
|
||||||
++rem;
|
++rem;
|
||||||
|
@ -83,7 +106,7 @@ namespace Mist{
|
||||||
class InputEBML : public Input{
|
class InputEBML : public Input{
|
||||||
public:
|
public:
|
||||||
InputEBML(Util::Config *cfg);
|
InputEBML(Util::Config *cfg);
|
||||||
|
bool needsLock();
|
||||||
protected:
|
protected:
|
||||||
void fillPacket(packetData & C);
|
void fillPacket(packetData & C);
|
||||||
bool checkArguments();
|
bool checkArguments();
|
||||||
|
@ -101,6 +124,12 @@ namespace Mist{
|
||||||
std::map<uint64_t, trackPredictor> packBuf;
|
std::map<uint64_t, trackPredictor> packBuf;
|
||||||
std::set<uint64_t> swapEndianness;
|
std::set<uint64_t> swapEndianness;
|
||||||
bool readExistingHeader();
|
bool readExistingHeader();
|
||||||
|
void parseStreamHeader(){
|
||||||
|
readHeader();
|
||||||
|
}
|
||||||
|
bool openStreamSource(){return true;}
|
||||||
|
bool needHeader(){return needsLock();}
|
||||||
|
double timeScale;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "output_ebml.h"
|
#include "output_ebml.h"
|
||||||
#include <mist/ebml_socketglue.h>
|
#include <mist/ebml_socketglue.h>
|
||||||
#include <mist/riff.h>
|
#include <mist/riff.h>
|
||||||
|
#include <mist/opus.h>
|
||||||
|
|
||||||
namespace Mist{
|
namespace Mist{
|
||||||
OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){
|
OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){
|
||||||
|
@ -15,6 +16,9 @@ namespace Mist{
|
||||||
if (config->getString("target").size()){
|
if (config->getString("target").size()){
|
||||||
if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";}
|
if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";}
|
||||||
initialize();
|
initialize();
|
||||||
|
if (myMeta.vod){
|
||||||
|
calcVodSizes();
|
||||||
|
}
|
||||||
if (!streamName.size()){
|
if (!streamName.size()){
|
||||||
WARN_MSG("Recording unconnected EBML output to file! Cancelled.");
|
WARN_MSG("Recording unconnected EBML output to file! Cancelled.");
|
||||||
conn.close();
|
conn.close();
|
||||||
|
@ -55,6 +59,7 @@ namespace Mist{
|
||||||
capa["codecs"][0u][0u].append("VP9");
|
capa["codecs"][0u][0u].append("VP9");
|
||||||
capa["codecs"][0u][0u].append("theora");
|
capa["codecs"][0u][0u].append("theora");
|
||||||
capa["codecs"][0u][0u].append("MPEG2");
|
capa["codecs"][0u][0u].append("MPEG2");
|
||||||
|
capa["codecs"][0u][0u].append("AV1");
|
||||||
capa["codecs"][0u][1u].append("AAC");
|
capa["codecs"][0u][1u].append("AAC");
|
||||||
capa["codecs"][0u][1u].append("vorbis");
|
capa["codecs"][0u][1u].append("vorbis");
|
||||||
capa["codecs"][0u][1u].append("opus");
|
capa["codecs"][0u][1u].append("opus");
|
||||||
|
@ -65,6 +70,7 @@ namespace Mist{
|
||||||
capa["codecs"][0u][1u].append("MP3");
|
capa["codecs"][0u][1u].append("MP3");
|
||||||
capa["codecs"][0u][1u].append("FLOAT");
|
capa["codecs"][0u][1u].append("FLOAT");
|
||||||
capa["codecs"][0u][1u].append("AC3");
|
capa["codecs"][0u][1u].append("AC3");
|
||||||
|
capa["codecs"][0u][2u].append("+JSON");
|
||||||
capa["methods"][0u]["handler"] = "http";
|
capa["methods"][0u]["handler"] = "http";
|
||||||
capa["methods"][0u]["type"] = "html5/video/webm";
|
capa["methods"][0u]["type"] = "html5/video/webm";
|
||||||
capa["methods"][0u]["priority"] = 11ll;
|
capa["methods"][0u]["priority"] = 11ll;
|
||||||
|
@ -131,12 +137,13 @@ namespace Mist{
|
||||||
currentClusterTime = thisPacket.getTime();
|
currentClusterTime = thisPacket.getTime();
|
||||||
if (myMeta.vod){
|
if (myMeta.vod){
|
||||||
//In case of VoD, clusters are aligned with the main track fragments
|
//In case of VoD, clusters are aligned with the main track fragments
|
||||||
|
//EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
|
||||||
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
|
uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
|
||||||
newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration();
|
newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration();
|
||||||
//The last fragment should run until the end of time
|
//Limit clusters to 30s, and the last fragment should always be 30s, just in case.
|
||||||
if (fragIndice == Trk.fragments.size() - 1){
|
if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){
|
||||||
newClusterTime = 0xFFFFFFFFFFFFFFFFull;
|
newClusterTime = currentClusterTime + 30000;
|
||||||
}
|
}
|
||||||
EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
|
EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
|
||||||
}else{
|
}else{
|
||||||
|
@ -157,6 +164,7 @@ namespace Mist{
|
||||||
if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
|
if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
|
||||||
if (Trk.codec == "VP8"){return "V_VP8";}
|
if (Trk.codec == "VP8"){return "V_VP8";}
|
||||||
if (Trk.codec == "VP9"){return "V_VP9";}
|
if (Trk.codec == "VP9"){return "V_VP9";}
|
||||||
|
if (Trk.codec == "AV1"){return "V_AV1";}
|
||||||
if (Trk.codec == "AAC"){return "A_AAC";}
|
if (Trk.codec == "AAC"){return "A_AAC";}
|
||||||
if (Trk.codec == "vorbis"){return "A_VORBIS";}
|
if (Trk.codec == "vorbis"){return "A_VORBIS";}
|
||||||
if (Trk.codec == "theora"){return "V_THEORA";}
|
if (Trk.codec == "theora"){return "V_THEORA";}
|
||||||
|
@ -168,6 +176,7 @@ namespace Mist{
|
||||||
if (Trk.codec == "ALAW"){return "A_MS/ACM";}
|
if (Trk.codec == "ALAW"){return "A_MS/ACM";}
|
||||||
if (Trk.codec == "ULAW"){return "A_MS/ACM";}
|
if (Trk.codec == "ULAW"){return "A_MS/ACM";}
|
||||||
if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
|
if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
|
||||||
|
if (Trk.codec == "JSON"){return "M_JSON";}
|
||||||
return "E_UNKNOWN";
|
return "E_UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,10 +194,16 @@ namespace Mist{
|
||||||
}else{
|
}else{
|
||||||
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
}
|
}
|
||||||
|
if (Trk.codec == "opus" && Trk.init.size() > 11){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
|
||||||
|
}
|
||||||
if (Trk.type == "video"){
|
if (Trk.type == "video"){
|
||||||
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
|
||||||
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
||||||
}
|
}
|
||||||
if (Trk.type == "audio"){
|
if (Trk.type == "audio"){
|
||||||
|
@ -198,6 +213,9 @@ namespace Mist{
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
||||||
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
||||||
}
|
}
|
||||||
|
if (Trk.type == "meta"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);
|
||||||
|
}
|
||||||
sendLen += subLen;
|
sendLen += subLen;
|
||||||
|
|
||||||
// Now actually send.
|
// Now actually send.
|
||||||
|
@ -215,11 +233,17 @@ namespace Mist{
|
||||||
}else{
|
}else{
|
||||||
if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
|
if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
}
|
}
|
||||||
|
if (Trk.codec == "opus"){
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000);
|
||||||
|
}
|
||||||
if (Trk.type == "video"){
|
if (Trk.type == "video"){
|
||||||
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
|
||||||
EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
|
EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
|
||||||
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height);
|
||||||
}
|
}
|
||||||
if (Trk.type == "audio"){
|
if (Trk.type == "audio"){
|
||||||
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2);
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2);
|
||||||
|
@ -228,6 +252,9 @@ namespace Mist{
|
||||||
EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
||||||
EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size);
|
EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size);
|
||||||
}
|
}
|
||||||
|
if (Trk.type == "meta"){
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
|
uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
|
||||||
|
@ -244,10 +271,16 @@ namespace Mist{
|
||||||
}else{
|
}else{
|
||||||
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
}
|
}
|
||||||
|
if (Trk.codec == "opus"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48);
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
|
||||||
|
}
|
||||||
if (Trk.type == "video"){
|
if (Trk.type == "video"){
|
||||||
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
|
||||||
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
||||||
}
|
}
|
||||||
if (Trk.type == "audio"){
|
if (Trk.type == "audio"){
|
||||||
|
@ -257,6 +290,9 @@ namespace Mist{
|
||||||
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
||||||
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
||||||
}
|
}
|
||||||
|
if (Trk.type == "meta"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);
|
||||||
|
}
|
||||||
sendLen += subLen;
|
sendLen += subLen;
|
||||||
return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen;
|
return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen;
|
||||||
}
|
}
|
||||||
|
@ -296,14 +332,9 @@ namespace Mist{
|
||||||
if (myMeta.vod){
|
if (myMeta.vod){
|
||||||
EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
|
EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
|
||||||
uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
||||||
uint32_t fragNo = 0;
|
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
|
||||||
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0);
|
||||||
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
tmpsegSize += it->second;
|
||||||
//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;
|
sentHeader = true;
|
||||||
|
@ -331,10 +362,10 @@ namespace Mist{
|
||||||
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
|
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);
|
VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos);
|
||||||
if (startPos < it->second){
|
if (startPos < it->second){
|
||||||
HIGH_MSG("Seek to fragment %llu (%llu ms)", it->first, Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
HIGH_MSG("Seek to fragment at %llu ms", it->first);
|
||||||
myConn.skipBytes(startPos);
|
myConn.skipBytes(startPos);
|
||||||
seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
seek(it->first);
|
||||||
newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime();
|
newClusterTime = it->first;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startPos -= it->second;
|
startPos -= it->second;
|
||||||
|
@ -484,10 +515,17 @@ namespace Mist{
|
||||||
uint64_t clusterEnd = clusterStart + it->getDuration();
|
uint64_t clusterEnd = clusterStart + it->getDuration();
|
||||||
//The first fragment always starts at time 0, even if the main track does not.
|
//The first fragment always starts at time 0, even if the main track does not.
|
||||||
if (!fragNo){clusterStart = 0;}
|
if (!fragNo){clusterStart = 0;}
|
||||||
//The last fragment always ends at the end, even if the main track does not.
|
uint64_t clusterTmpEnd = clusterEnd;
|
||||||
if (fragNo == Trk.fragments.size() - 1){clusterEnd = 0xFFFFFFFFFFFFFFFFull;}
|
do {
|
||||||
uint64_t cSize = clusterSize(clusterStart, clusterEnd);
|
clusterTmpEnd = clusterEnd;
|
||||||
clusterSizes[fragNo] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
|
//The last fragment always ends at the end, even if the main track does not.
|
||||||
|
if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;}
|
||||||
|
//Limit clusters to 30 seconds.
|
||||||
|
if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;}
|
||||||
|
uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd);
|
||||||
|
clusterSizes[clusterStart] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
|
||||||
|
clusterStart = clusterTmpEnd;//Continue at the end of this cluster, if continuing.
|
||||||
|
}while(clusterTmpEnd < clusterEnd);
|
||||||
++fragNo;
|
++fragNo;
|
||||||
}
|
}
|
||||||
//Calculating Cues size
|
//Calculating Cues size
|
||||||
|
@ -500,14 +538,9 @@ namespace Mist{
|
||||||
oldcuesSize = cuesSize;
|
oldcuesSize = cuesSize;
|
||||||
segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
||||||
uint32_t cuesInside = 0;
|
uint32_t cuesInside = 0;
|
||||||
fragNo = 0;
|
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
|
||||||
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0);
|
||||||
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
segmentSize += it->second;
|
||||||
//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;
|
cuesSize = cuesInside;
|
||||||
}while(cuesSize != oldcuesSize);
|
}while(cuesSize != oldcuesSize);
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace Mist{
|
||||||
uint32_t cuesSize;//size of Cues (excl. header)
|
uint32_t cuesSize;//size of Cues (excl. header)
|
||||||
uint32_t seekheadSize;//size of SeekHead (incl. header)
|
uint32_t seekheadSize;//size of SeekHead (incl. header)
|
||||||
uint32_t seekSize;//size of contents of SeekHead (excl. header)
|
uint32_t seekSize;//size of contents of SeekHead (excl. header)
|
||||||
std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters (incl. header)
|
std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters by start time (incl. header)
|
||||||
void byteSeek(uint64_t startPos);
|
void byteSeek(uint64_t startPos);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue