diff --git a/lib/ebml.cpp b/lib/ebml.cpp index b3cc9332..c865f841 100644 --- a/lib/ebml.cpp +++ b/lib/ebml.cpp @@ -16,7 +16,7 @@ namespace EBML{ if (p[0] & 0x04){return 6;} if (p[0] & 0x02){return 7;} if (p[0] & 0x01){return 8;} - return 0; + return 1; } /// 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_PIXELHEIGHT: return "PixelHeight"; case 0x1A: return "FlagInterlaced"; - case 0x14B0: return "DisplayWidth"; - case 0x14BA: return "DisplayHeight"; + case EID_DISPLAYWIDTH: return "DisplayWidth"; + case EID_DISPLAYHEIGHT: return "DisplayHeight"; case 0x15B0: return "Colour"; case 0x15B7: return "ChromaSitingHorz"; case 0x15B8: return "ChromaSitingVert"; @@ -164,8 +164,8 @@ namespace EBML{ case EID_CHANNELS: return "Channels"; case EID_SAMPLINGFREQUENCY: return "SamplingFrequency"; case EID_BITDEPTH: return "BitDepth"; - case 0x16AA: return "CodecDelay"; - case 0x16BB: return "SeekPreRoll"; + case EID_CODECDELAY: return "CodecDelay"; + case EID_SEEKPREROLL: return "SeekPreRoll"; case EID_CODECPRIVATE: return "CodecPrivate"; case EID_DEFAULTDURATION: return "DefaultDuration"; case EID_EBMLVERSION: return "EBMLVersion"; @@ -185,7 +185,7 @@ namespace EBML{ case 0x6C: return "Void"; case 0x3F: return "CRC-32"; case 0x33A4: return "SegmentUID"; - case 0x254c367: return "Tags"; + case EID_TAGS: return "Tags"; case 0x3373: return "Tag"; case 0x23C0: return "Targets"; case 0x27C8: return "SimpleTag"; @@ -271,7 +271,7 @@ namespace EBML{ case EID_CUEPOINT: case EID_CUETRACKPOSITIONS: case 0x15B0: - case 0x254c367: + case EID_TAGS: case 0x3373: case 0x23C0: case 0x43a770: @@ -296,8 +296,8 @@ namespace EBML{ case EID_FLAGLACING: case EID_TRACKTYPE: case EID_DEFAULTDURATION: - case 0x16AA: - case 0x16BB: + case EID_CODECDELAY: + case EID_SEEKPREROLL: case EID_CUETIME: case EID_CUETRACK: case EID_CUECLUSTERPOSITION: @@ -305,8 +305,8 @@ namespace EBML{ case EID_PIXELWIDTH: case EID_PIXELHEIGHT: case 0x1A: - case 0x14B0: - case 0x14BA: + case EID_DISPLAYWIDTH: + case EID_DISPLAYHEIGHT: case EID_CHANNELS: case EID_BITDEPTH: case 0x15B7: @@ -652,26 +652,28 @@ namespace EBML{ case 3: ret << " [Lacing: EMBL]"; break; case 2: ret << " [Lacing: Fixed]"; break; } - if (detail < 8){ - ret << std::endl; - return ret.str(); + ret << std::endl; + if (detail >= 4){ + 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){ uint32_t extraStuff = (UniInt::readSize(getPayload()) + 3); const char *payDat = getPayload() + extraStuff; const uint64_t payLen = getPayloadLen() - extraStuff; - ret << std::endl << 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;} + 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]; diff --git a/lib/ebml.h b/lib/ebml.h index 1597d8a6..972fe071 100644 --- a/lib/ebml.h +++ b/lib/ebml.h @@ -48,6 +48,8 @@ namespace EBML{ EID_PIXELWIDTH = 0x30, EID_FLAGLACING = 0x1C, EID_PIXELHEIGHT = 0x3A, + EID_DISPLAYWIDTH = 0x14B0, + EID_DISPLAYHEIGHT = 0x14BA, EID_TRACKNUMBER = 0x57, EID_CODECPRIVATE = 0x23A2, EID_LANGUAGE = 0x2B59C, @@ -72,6 +74,9 @@ namespace EBML{ EID_CUETRACKPOSITIONS = 0x37, EID_CUETIME = 0x33, EID_CUEPOINT = 0x3B, + EID_TAGS = 0x254c367, + EID_CODECDELAY = 0x16AA, + EID_SEEKPREROLL = 0x16BB, EID_UNKNOWN = 0 }; diff --git a/src/analysers/analyser_ebml.cpp b/src/analysers/analyser_ebml.cpp index 22e183ee..e643caba 100644 --- a/src/analysers/analyser_ebml.cpp +++ b/src/analysers/analyser_ebml.cpp @@ -8,6 +8,8 @@ void AnalyserEBML::init(Util::Config &conf){ AnalyserEBML::AnalyserEBML(Util::Config &conf) : Analyser(conf){ curPos = prePos = 0; + lastSeekId = 0; + lastSeekPos = 0; } bool AnalyserEBML::parsePacket(){ @@ -15,11 +17,18 @@ bool AnalyserEBML::parsePacket(){ // Read in smart bursts until we have enough data while (isOpen() && dataBuffer.size() < neededBytes()){ uint64_t needed = neededBytes(); + if (needed > 1024*1024){ + dataBuffer.erase(0, 1); + continue; + } dataBuffer.reserve(needed); for (uint64_t i = dataBuffer.size(); i < needed; ++i){ dataBuffer += std::cin.get(); ++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); HIGH_MSG("Read an element at position %d", prePos); 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()){ depthStash.front() -= E.getOuterLen(); } @@ -36,6 +72,25 @@ bool AnalyserEBML::parsePacket(){ } while (depthStash.size() && !depthStash.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 dataBuffer.erase(0, E.getOuterLen()); diff --git a/src/analysers/analyser_ebml.h b/src/analysers/analyser_ebml.h index ba5ac9c4..4934204e 100644 --- a/src/analysers/analyser_ebml.h +++ b/src/analysers/analyser_ebml.h @@ -12,6 +12,10 @@ private: std::string dataBuffer; uint64_t curPos; uint64_t prePos; + uint64_t segmentOffset; + uint32_t lastSeekId; + uint64_t lastSeekPos; + std::map seekChecks; std::deque depthStash;/// namespace Mist{ + + uint16_t maxEBMLFrameOffset = 0; + bool frameOffsetKnown = false; + InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){ + timeScale = 1.0; 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("/*.mka"); capa["source_match"].append("/*.mk3d"); @@ -17,6 +22,7 @@ namespace Mist{ capa["codecs"].append("HEVC"); capa["codecs"].append("VP8"); capa["codecs"].append("VP9"); + capa["codecs"].append("AV1"); capa["codecs"].append("opus"); capa["codecs"].append("vorbis"); capa["codecs"].append("theora"); @@ -29,16 +35,40 @@ namespace Mist{ capa["codecs"].append("MP3"); capa["codecs"].append("AC3"); capa["codecs"].append("FLOAT"); + capa["codecs"].append("JSON"); + capa["codecs"].append("subtitle"); lastClusterBPos = 0; lastClusterTime = 0; bufferedPacks = 0; } - bool InputEBML::checkArguments(){ - if (config->getString("input") == "-"){ - std::cerr << "Input from stdin not yet supported" << std::endl; - return false; + std::string ASStoSRT(const char * ptr, uint32_t len){ + uint16_t commas = 0; + uint16_t brackets = 0; + 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("output") == "-"){ std::cerr << "Output to stdout not yet supported" << std::endl; @@ -53,10 +83,23 @@ namespace Mist{ 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(){ - // open File - inFile = fopen(config->getString("input").c_str(), "r"); - if (!inFile){return false;} + if (config->getString("input") == "-"){ + inFile = stdin; + }else{ + // open File + inFile = fopen(config->getString("input").c_str(), "r"); + if (!inFile){return false;} + } return true; } @@ -67,7 +110,10 @@ namespace Mist{ while (ptr.size() < needed){ if (!ptr.allocate(needed)){return false;} 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; } ptr.size() = needed; @@ -81,8 +127,18 @@ namespace Mist{ } } EBML::Element E(ptr); - if (E.getID() == EBML::EID_CLUSTER){lastClusterBPos = Util::ftell(inFile);} - if (E.getID() == EBML::EID_TIMECODE){lastClusterTime = E.getValUInt();} + if (E.getID() == EBML::EID_CLUSTER){ + 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; } @@ -95,6 +151,13 @@ namespace Mist{ 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; } @@ -130,6 +193,10 @@ namespace Mist{ tmpElem = E.findChild(EBML::EID_CODECPRIVATE); if (tmpElem){init = tmpElem.getValString();} } + if (codec == "V_AV1"){ + trueCodec = "AV1"; + trueType = "video"; + } if (codec == "V_VP9"){ trueCodec = "VP9"; trueType = "video"; @@ -190,6 +257,20 @@ namespace Mist{ trueCodec = "FLOAT"; 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"){ tmpElem = E.findChild(EBML::EID_CODECPRIVATE); if (tmpElem){ @@ -247,6 +328,13 @@ namespace Mist{ } 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){ EBML::Block B(ptr); uint64_t tNum = B.getTrackNum(); @@ -254,21 +342,32 @@ namespace Mist{ trackPredictor &TP = packBuf[tNum]; DTSC::Track &Trk = myMeta.tracks[tNum]; 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){ if (frameNo){ 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{ + newTime += 1/timeScale; ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str()); } } 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){ - TP.add(newTime, 0, tNum, frameSize, lastClusterBPos, - B.isKeyframe() && isVideo); + TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos, + B.isKeyframe() && !isAudio, isVideo); } } - while (TP.hasPackets()){ + while (TP.hasPackets() && (isVideo || frameOffsetKnown)){ + frameOffsetKnown = true; packetData &C = TP.getPacketData(isVideo); myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); TP.remove(); @@ -288,6 +387,8 @@ namespace Mist{ } } + myMeta.inputLocalVars["maxframeoffset"] = (long long)maxEBMLFrameOffset; + bench = Util::getMicros(bench); INFO_MSG("Header generated in %llu ms", bench / 1000); packBuf.clear(); @@ -385,19 +486,31 @@ namespace Mist{ trackPredictor &TP = packBuf[tNum]; DTSC::Track & Trk = myMeta.tracks[tNum]; 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){ if (frameNo){ 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{ ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str()); } } uint32_t frameSize = B.getFrameSize(frameNo); if (frameSize){ - TP.add(newTime, 0, tNum, frameSize, lastClusterBPos, - B.isKeyframe() && isVideo, (void *)B.getFrameData(frameNo)); - ++bufferedPacks; + char * ptr = (char *)B.getFrameData(frameNo); + if (isASS){ + 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()){ @@ -415,10 +528,29 @@ namespace Mist{ void InputEBML::seek(int seekTime){ packBuf.clear(); 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(); + // 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++){ - 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(); } Util::fseek(inFile, seekPos, SEEK_SET); diff --git a/src/input/input_ebml.h b/src/input/input_ebml.h index 7b5d725a..10f3a700 100644 --- a/src/input/input_ebml.h +++ b/src/input/input_ebml.h @@ -3,6 +3,11 @@ namespace Mist{ + + extern uint16_t maxEBMLFrameOffset; + extern bool frameOffsetKnown; +#define PKT_COUNT 64 + class packetData{ public: uint64_t time, offset, track, dsize, bpos; @@ -33,7 +38,7 @@ namespace Mist{ }; class trackPredictor{ public: - packetData pkts[16]; + packetData pkts[PKT_COUNT]; uint16_t smallestFrame; uint64_t lastTime; uint64_t ctr; @@ -48,31 +53,49 @@ namespace Mist{ if (finished){ return (ctr - rem > 0); }else{ - return (ctr - rem > 8); + return (ctr - rem > 12); } } packetData & getPacketData(bool mustCalcOffsets){ - packetData & p = pkts[rem % 16]; - if (rem && mustCalcOffsets){ - if (p.time > lastTime + smallestFrame){ - while (p.time - (lastTime + smallestFrame) > smallestFrame * 8){ - lastTime += smallestFrame; - } - p.offset = p.time - (lastTime + smallestFrame); - p.time = lastTime + smallestFrame; + frameOffsetKnown = true; + //grab the next packet to output + packetData & p = pkts[rem % PKT_COUNT]; + //Substract the max frame offset, so we know all offsets are positive, no matter what. + //if it's not the first and we're calculating offsets, see if we need an offset + if (!mustCalcOffsets){ + p.time += maxEBMLFrameOffset; + 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; return p; } - void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){ - if (ctr && ctr > rem){ - if ((pkts[(ctr-1)%16].time < packTime - 2) && (!smallestFrame || packTime - pkts[(ctr-1)%16].time < smallestFrame)){ - smallestFrame = packTime - pkts[(ctr-1)%16].time; + 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 (isVideo && ctr && ctr >= rem){ + int32_t currOffset = packTime - pkts[(ctr-1)%PKT_COUNT].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; + if (ctr == PKT_COUNT-1){frameOffsetKnown = true;} } void remove(){ ++rem; @@ -83,7 +106,7 @@ namespace Mist{ class InputEBML : public Input{ public: InputEBML(Util::Config *cfg); - + bool needsLock(); protected: void fillPacket(packetData & C); bool checkArguments(); @@ -101,6 +124,12 @@ namespace Mist{ std::map packBuf; std::set swapEndianness; bool readExistingHeader(); + void parseStreamHeader(){ + readHeader(); + } + bool openStreamSource(){return true;} + bool needHeader(){return needsLock();} + double timeScale; }; } diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index 3f3aad96..e353202a 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -1,6 +1,7 @@ #include "output_ebml.h" #include #include +#include namespace Mist{ OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){ @@ -27,6 +28,7 @@ namespace Mist{ capa["codecs"][0u][0u].append("VP9"); capa["codecs"][0u][0u].append("theora"); capa["codecs"][0u][0u].append("MPEG2"); + capa["codecs"][0u][0u].append("AV1"); capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("vorbis"); capa["codecs"][0u][1u].append("opus"); @@ -37,6 +39,7 @@ namespace Mist{ capa["codecs"][0u][1u].append("MP3"); capa["codecs"][0u][1u].append("FLOAT"); capa["codecs"][0u][1u].append("AC3"); + capa["codecs"][0u][2u].append("+JSON"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/video/webm"; capa["methods"][0u]["priority"] = 11ll; @@ -92,12 +95,13 @@ namespace Mist{ currentClusterTime = thisPacket.getTime(); if (myMeta.vod){ //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()]; 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; + //Limit clusters to 30s, and the last fragment should always be 30s, just in case. + if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){ + newClusterTime = currentClusterTime + 30000; } EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime)); }else{ @@ -118,6 +122,7 @@ namespace Mist{ 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 == "AV1"){return "V_AV1";} if (Trk.codec == "AAC"){return "A_AAC";} if (Trk.codec == "vorbis"){return "A_VORBIS";} if (Trk.codec == "theora"){return "V_THEORA";} @@ -129,6 +134,7 @@ namespace Mist{ 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";} + if (Trk.codec == "JSON"){return "M_JSON";} return "E_UNKNOWN"; } @@ -146,10 +152,16 @@ namespace Mist{ }else{ 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"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); 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); } if (Trk.type == "audio"){ @@ -159,6 +171,9 @@ namespace Mist{ subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); } + if (Trk.type == "meta"){ + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); + } sendLen += subLen; // Now actually send. @@ -176,11 +191,17 @@ namespace Mist{ }else{ 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"){ 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); + EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width); + EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height); } if (Trk.type == "audio"){ EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2); @@ -189,6 +210,9 @@ namespace Mist{ EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate); 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){ @@ -205,10 +229,16 @@ namespace Mist{ }else{ 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"){ sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); 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); } if (Trk.type == "audio"){ @@ -218,6 +248,9 @@ namespace Mist{ subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); } + if (Trk.type == "meta"){ + sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); + } sendLen += subLen; return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen; } @@ -257,14 +290,9 @@ namespace Mist{ 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::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; + for (std::map::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ + EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0); + tmpsegSize += it->second; } } sentHeader = true; @@ -292,10 +320,10 @@ namespace Mist{ for (std::map::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()); + HIGH_MSG("Seek to fragment at %llu ms", it->first); myConn.skipBytes(startPos); - seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime()); - newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime(); + seek(it->first); + newClusterTime = it->first; return; } startPos -= it->second; @@ -445,10 +473,17 @@ namespace Mist{ 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); + uint64_t clusterTmpEnd = clusterEnd; + do { + clusterTmpEnd = clusterEnd; + //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; } //Calculating Cues size @@ -461,14 +496,9 @@ namespace Mist{ oldcuesSize = cuesSize; segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); uint32_t cuesInside = 0; - fragNo = 0; - for (std::deque::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; + for (std::map::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ + cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0); + segmentSize += it->second; } cuesSize = cuesInside; }while(cuesSize != oldcuesSize); diff --git a/src/output/output_ebml.h b/src/output/output_ebml.h index 5e977be2..37242bdf 100644 --- a/src/output/output_ebml.h +++ b/src/output/output_ebml.h @@ -25,7 +25,7 @@ namespace Mist{ uint32_t cuesSize;//size of Cues (excl. header) uint32_t seekheadSize;//size of SeekHead (incl. header) uint32_t seekSize;//size of contents of SeekHead (excl. header) - std::map clusterSizes;//sizes of Clusters (incl. header) + std::map clusterSizes;//sizes of Clusters by start time (incl. header) void byteSeek(uint64_t startPos); }; }