From a762932c4569ad20ad9cd0ab7341d0ab4a5218df Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 24 Jan 2018 20:04:50 +0100 Subject: [PATCH] EBML library, input and output, supports MKV and WebM. --- CMakeLists.txt | 7 + lib/ebml.cpp | 682 ++++++++++++++++++++++++++++++++ lib/ebml.h | 118 ++++++ lib/ebml_socketglue.cpp | 202 ++++++++++ lib/ebml_socketglue.h | 33 ++ src/analysers/analyser_ebml.cpp | 49 +++ src/analysers/analyser_ebml.h | 17 + src/input/input_ebml.cpp | 428 ++++++++++++++++++++ src/input/input_ebml.h | 108 +++++ src/output/output_ebml.cpp | 463 ++++++++++++++++++++++ src/output/output_ebml.h | 34 ++ 11 files changed, 2141 insertions(+) create mode 100644 lib/ebml.cpp create mode 100644 lib/ebml.h create mode 100644 lib/ebml_socketglue.cpp create mode 100644 lib/ebml_socketglue.h create mode 100644 src/analysers/analyser_ebml.cpp create mode 100644 src/analysers/analyser_ebml.h create mode 100644 src/input/input_ebml.cpp create mode 100644 src/input/input_ebml.h create mode 100644 src/output/output_ebml.cpp create mode 100644 src/output/output_ebml.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3db56499..4ae664f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,8 @@ set(libHeaders lib/util.h lib/vorbis.h lib/opus.h + lib/ebml.h + lib/ebml_socketglue.h ) ######################################## @@ -172,6 +174,8 @@ add_library (mist lib/util.cpp lib/vorbis.cpp lib/opus.cpp + lib/ebml.cpp + lib/ebml_socketglue.cpp ) if (NOT APPLE) set (LIBRT -lrt) @@ -246,6 +250,7 @@ makeAnalyser(FLV flv) makeAnalyser(DTSC dtsc) makeAnalyser(MP4 mp4) makeAnalyser(OGG ogg) +makeAnalyser(EBML ebml) ######################################## # MistServer - Utilities # @@ -302,6 +307,7 @@ makeInput(FLV flv) makeInput(OGG ogg) makeInput(Buffer buffer) makeInput(H264 h264) +makeInput(EBML ebml) ######################################## # MistServer - Outputs # @@ -351,6 +357,7 @@ makeOutput(JSON json http) makeOutput(TS ts ts) makeOutput(HTTPTS httpts http ts) makeOutput(HLS hls http ts) +makeOutput(EBML ebml) add_executable(MistOutHTTP src/output/mist_out.cpp diff --git a/lib/ebml.cpp b/lib/ebml.cpp new file mode 100644 index 00000000..6e5e1652 --- /dev/null +++ b/lib/ebml.cpp @@ -0,0 +1,682 @@ +#include "ebml.h" +#include "bitfields.h" +#include "defines.h" +#include +#include + +namespace EBML{ + + /// Reads the size of an EBML-encoded integer from a pointer + uint8_t UniInt::readSize(const char *p){ + if (p[0] & 0x80){return 1;} + if (p[0] & 0x40){return 2;} + if (p[0] & 0x20){return 3;} + if (p[0] & 0x10){return 4;} + if (p[0] & 0x08){return 5;} + if (p[0] & 0x04){return 6;} + if (p[0] & 0x02){return 7;} + if (p[0] & 0x01){return 8;} + return 0; + } + + /// Returns the size of an EBML-encoded integer for a given numerical value + uint8_t UniInt::writeSize(const uint64_t val){ + if (val <= 0x7Eull){return 1;} + if (val <= 0x3FFEull){return 2;} + if (val <= 0x1FFFFEull){return 3;} + if (val <= 0xFFFFFFEull){return 4;} + if (val <= 0x7FFFFFFFEull){return 5;} + if (val <= 0x3FFFFFFFFFEull){return 6;} + if (val <= 0x1FFFFFFFFFFFEull){return 7;} + if (val <= 0xFFFFFFFFFFFFFEull){return 8;} + return 0; + } + + /// Reads an EBML-encoded integer from a pointer. Expects the whole number to be readable without + /// bounds checking. + uint64_t UniInt::readInt(const char *p){ + switch (readSize(p)){ + case 1: + if (p[0] == 0xFF){ + return 0xFFFFFFFFFFFFFFFFull; + }else{ + return p[0] & 0x7F; + } + case 2: return Bit::btohs(p) & 0x3FFFull; + case 3: return Bit::btoh24(p) & 0x1FFFFFull; + case 4: return Bit::btohl(p) & 0xFFFFFFFull; + case 5: return Bit::btoh40(p) & 0x7FFFFFFFFull; + case 6: return Bit::btoh48(p) & 0x3FFFFFFFFFFull; + case 7: return Bit::btoh56(p) & 0x1FFFFFFFFFFFFull; + case 8: return Bit::btohll(p) & 0xFFFFFFFFFFFFFFull; + } + } + + void UniInt::writeInt(char *p, const uint64_t val){ + switch (writeSize(val)){ + case 1: p[0] = val | 0x80; break; + case 2: Bit::htobs(p, val | 0x4000); break; + case 3: Bit::htob24(p, val | 0x200000); break; + case 4: Bit::htobl(p, val | 0x10000000); break; + case 5: Bit::htob40(p, val | 0x800000000); break; + case 6: Bit::htob48(p, val | 0x40000000000); break; + case 7: Bit::htob56(p, val | 0x2000000000000); break; + case 8: Bit::htobll(p, val | 0x100000000000000); break; + } + } + + /// Reads an EBML-encoded singed integer from a pointer. Expects the whole number to be readable without + /// bounds checking. + int64_t UniInt::readSInt(const char *p){ + switch (readSize(p)){ + case 1: return ((int64_t)readInt(p)) - 0x3Fll; + case 2: return ((int64_t)readInt(p)) - 0x1FFFll; + case 3: return ((int64_t)readInt(p)) - 0xFFFFFll; + case 4: return ((int64_t)readInt(p)) - 0x7FFFFFFll; + case 5: return ((int64_t)readInt(p)) - 0x3FFFFFFFFll; + case 6: return ((int64_t)readInt(p)) - 0x1FFFFFFFFFFll; + case 7: return ((int64_t)readInt(p)) - 0xFFFFFFFFFFFFll; + case 8: return ((int64_t)readInt(p)) - 0x7FFFFFFFFFFFFFll; + } + } + + void UniInt::writeSInt(char *p, const int64_t sval){ + FAIL_MSG("Writing signed UniInt values not yet implemented!"); + } + + /// Given a pointer and available byte count, returns how many bytes must be available for more + /// data to be readable. + /// If minimal is true, returns only the header size if the element is an ELEM_MASTER type. + uint64_t Element::needBytes(const char *p, uint64_t availBytes, bool minimal){ + if (availBytes < 2){return 2;} + uint64_t needed = UniInt::readSize(p); + if (availBytes < needed + 1){return needed + 1;} + const char *sizeOffset = p + needed; + needed += UniInt::readSize(sizeOffset); + if (availBytes < needed){return needed;} + // ELEM_MASTER types do not contain payload if minimal is true + if (minimal && Element(p, true).getType() == ELEM_MASTER){return needed;} + uint64_t pSize = UniInt::readInt(sizeOffset); + if (pSize != 0xFFFFFFFFFFFFFFFFull){ + needed += pSize; + } + return needed; + } + + std::string Element::getIDString(uint32_t id){ + if (id > 0xFFFFFF){ + id &= 0xFFFFFFF; + }else{ + if (id > 0xFFFF){ + id &= 0x1FFFFF; + }else{ + if (id > 0xFF){ + id &= 0x3FFF; + }else{ + id &= 0x7F; + } + } + } + switch (id){ + case EID_EBML: return "EBML"; + case EID_SEGMENT: return "Segment"; + case EID_CLUSTER: return "Cluster"; + case EID_TIMECODE: return "Timecode"; + case 0x20: return "BlockGroup"; + case 0x21: return "Block"; + case EID_SIMPLEBLOCK: return "SimpleBlock"; + case 0x35A2: return "DiscardPadding"; + case EID_SEEKHEAD: return "SeekHead"; + case EID_SEEK: return "Seek"; + case EID_SEEKID: return "SeekID"; + case EID_SEEKPOSITION: return "SeekPosition"; + case EID_INFO: return "Info"; + case EID_TIMECODESCALE: return "TimecodeScale"; + case EID_MUXINGAPP: return "MuxingApp"; + case EID_WRITINGAPP: return "WritingApp"; + case EID_DURATION: return "Duration"; + case EID_TRACKS: return "Tracks"; + case EID_TRACKENTRY: return "TrackEntry"; + case EID_TRACKNUMBER: return "TrackNumber"; + case EID_TRACKUID: return "TrackUID"; + case EID_FLAGLACING: return "FlagLacing"; + case EID_LANGUAGE: return "Language"; + case EID_CODECID: return "CodecID"; + case EID_TRACKTYPE: return "TrackType"; + case EID_VIDEO: return "Video"; + case EID_PIXELWIDTH: return "PixelWidth"; + case EID_PIXELHEIGHT: return "PixelHeight"; + case 0x1A: return "FlagInterlaced"; + case 0x14B0: return "DisplayWidth"; + case 0x14BA: return "DisplayHeight"; + case 0x15B0: return "Colour"; + case 0x15B7: return "ChromaSitingHorz"; + case 0x15B8: return "ChromaSitingVert"; + case 0x15BA: return "TransferCharacteristics"; + case 0x15B1: return "MatrixCoefficients"; + case 0x15BB: return "Primaries"; + case 0x15B9: return "Range"; + case 0x136E: return "Name"; + case 0x2DE7: return "MinCache"; + case EID_AUDIO: return "Audio"; + 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_CODECPRIVATE: return "CodecPrivate"; + case EID_DEFAULTDURATION: return "DefaultDuration"; + case EID_EBMLVERSION: return "EBMLVersion"; + case EID_EBMLREADVERSION: return "EBMLReadVersion"; + case EID_EBMLMAXIDLENGTH: return "EBMLMaxIDLength"; + case EID_EBMLMAXSIZELENGTH: return "EBMLMaxSizeLength"; + case EID_DOCTYPE: return "DocType"; + case EID_DOCTYPEVERSION: return "DocTypeVersion"; + case EID_DOCTYPEREADVERSION: return "DocTypeReadVersion"; + case EID_CUES: return "Cues"; + case EID_CUEPOINT: return "CuePoint"; + case EID_CUETIME: return "CueTime"; + case EID_CUETRACKPOSITIONS: return "CueTrackPositions"; + case EID_CUETRACK: return "CueTrack"; + case EID_CUECLUSTERPOSITION: return "CueClusterPosition"; + case EID_CUERELATIVEPOSITION: return "CueRelativePosition"; + case 0x6C: return "Void"; + case 0x3F: return "CRC-32"; + case 0x33A4: return "SegmentUID"; + case 0x254c367: return "Tags"; + case 0x3373: return "Tag"; + case 0x23C0: return "Targets"; + case 0x27C8: return "SimpleTag"; + case 0x5A3: return "TagName"; + case 0x487: return "TagString"; + case 0x23C5: return "TagTrackUID"; + case 0x43a770: return "Chapters"; + case 0x3a770: return "Chapters"; + case 0x941a469: return "Attachments"; + case 0x8: return "FlagDefault"; + case 0x461: return "DateUTC"; + case 0x3BA9: return "Title"; + case 0x1B: return "BlockDuration"; + case 0x21A7: return "AttachedFile"; + case 0x66E: return "FileName"; + case 0x65C: return "FileData"; + case 0x6AE: return "FileUID"; + case 0x67E: return "FileDescription"; + case 0x660: return "FileMimeType"; + case 0x5B9: return "EditionEntry"; + case 0x5BD: return "EditionFlagHidden"; + case 0x5DB: return "EditionFlagDefault"; + case 0x5BC: return "EditionUID"; + case 0x36: return "ChapterAtom"; + case 0x33C4: return "ChapterUID"; + case 0x11: return "ChapterTimeStart"; + case 0x18: return "ChapterFlagHidden"; + case 0x598: return "ChapterFlagEnabled"; + case 0x0: return "ChapterDisplay"; + case 0x5: return "ChapString"; + case 0x37C: return "ChapLanguage"; + default: + std::stringstream ret; + ret << "UNKNOWN: 0x" << std::hex << std::setw(8) << std::setfill('0') << id; + return ret.str(); + } + } + + /// If minimal is set to true, ELEM_MASTER elements will never attempt to access their payload + /// data. + Element::Element(const char *p, bool minimal){ + data = p; + minimalMode = minimal; + } + + uint32_t Element::getID() const{return UniInt::readInt(data);} + + uint64_t Element::getPayloadLen() const{ + uint8_t sizeOffset = UniInt::readSize(data); + return UniInt::readInt(data + sizeOffset); + } + + uint8_t Element::getHeaderLen() const{ + uint8_t sizeOffset = UniInt::readSize(data); + return sizeOffset + UniInt::readSize(data + sizeOffset); + } + + const char *Element::getPayload() const{return data + getHeaderLen();} + + uint64_t Element::getOuterLen() const{ + uint8_t sizeOffset = UniInt::readSize(data); + if (minimalMode && UniInt::readInt(data + sizeOffset) == 0xFFFFFFFFFFFFFFFFull){ + return sizeOffset + UniInt::readSize(data + sizeOffset); + }else{ + return UniInt::readInt(data + sizeOffset) + sizeOffset + UniInt::readSize(data + sizeOffset); + } + } + + ElementType Element::getType() const{ + switch (getID()){ + case EID_EBML: + case EID_SEGMENT: + case EID_CLUSTER: + case EID_SEEKHEAD: + case EID_INFO: + case EID_TRACKS: + case EID_CUES: + case EID_SEEK: + case EID_TRACKENTRY: + case EID_VIDEO: + case EID_AUDIO: + case 0x20: + case EID_CUEPOINT: + case EID_CUETRACKPOSITIONS: + case 0x15B0: + case 0x254c367: + case 0x3373: + case 0x23C0: + case 0x43a770: + case 0x3a770: + case 0x941a469: + case 0x21A7: + case 0x5B9: + case 0x36: + case 0x0: + case 0x27C8: return ELEM_MASTER; + case EID_EBMLVERSION: + case EID_EBMLREADVERSION: + case EID_EBMLMAXIDLENGTH: + case EID_EBMLMAXSIZELENGTH: + case EID_DOCTYPEVERSION: + case EID_DOCTYPEREADVERSION: + case EID_SEEKPOSITION: + case EID_TIMECODESCALE: + case EID_TIMECODE: + case EID_TRACKNUMBER: + case EID_TRACKUID: + case EID_FLAGLACING: + case EID_TRACKTYPE: + case EID_DEFAULTDURATION: + case 0x16AA: + case 0x16BB: + case EID_CUETIME: + case EID_CUETRACK: + case EID_CUECLUSTERPOSITION: + case EID_CUERELATIVEPOSITION: + case EID_PIXELWIDTH: + case EID_PIXELHEIGHT: + case 0x1A: + case 0x14B0: + case 0x14BA: + case EID_CHANNELS: + case EID_BITDEPTH: + case 0x15B7: + case 0x15B8: + case 0x15BA: + case 0x15B9: + case 0x15B1: + case 0x15BB: + case 0x2DE7: + case 0x8: + case 0x1B: + case 0x6AE: + case 0x5BD: + case 0x5DB: + case 0x5BC: + case 0x33C4: + case 0x11: + case 0x18: + case 0x598: + case 0x23C5: return ELEM_UINT; + case 0x35A2: return ELEM_INT; + case EID_SAMPLINGFREQUENCY: + case EID_DURATION: return ELEM_FLOAT; + case EID_DOCTYPE: + case EID_LANGUAGE: + case 0x660: + case 0x37C: + case EID_CODECID: return ELEM_STRING; + case EID_MUXINGAPP: + case EID_WRITINGAPP: + case 0x5A3: + case 0x136E: + case 0x3BA9: + case 0x66E: + case 0x67E: + case 0x5: + case 0x487: return ELEM_UTF8; + case 0x6C: + case EID_SEEKID: + case EID_CODECPRIVATE: + case 0x3F: + case 0x65C: + case 0x33A4: return ELEM_BIN; + case EID_SIMPLEBLOCK: + case 0x21: return ELEM_BLOCK; + case 0x461: return ELEM_DATE; + default: return ELEM_UNKNOWN; + } + } + + const Element Element::findChild(uint32_t id) const{ + if (getID() == id){return *this;} + if (getType() != ELEM_MASTER){return Element();} + if (minimalMode){ + ERROR_MSG("Attempted to find child element in header-only EBML buffer!"); + return Element(); + } + const uint64_t payLen = getPayloadLen(); + const char *payDat = getPayload(); + uint64_t offset = 0; + while (offset < payLen){ + if (needBytes(payDat + offset, payLen - offset) > payLen - offset){ + WARN_MSG("Trying to read beyond boundaries of element! Aborted."); + break; + } + Element e(payDat + offset); + Element f = e.findChild(id); + if (f){return f;} + offset += e.getOuterLen(); + } + return Element(); + } + + std::string Element::toPrettyString(const uint8_t indent, const uint8_t detail) const{ + std::stringstream ret; + switch (getType()){ + case ELEM_MASTER:{ + const uint64_t payLen = getPayloadLen(); + ret << std::string(indent, ' ') << "Element [" << getIDString(getID()) << "] (" + << getOuterLen() << "b, "; + if (payLen == 0xFFFFFFFFFFFFFFFFull){ + ret << "infinite"; + }else{ + ret << payLen << "b"; + } + ret << " payload)" << std::endl; + const char *payDat = getPayload(); + uint64_t offset = 0; + while (!minimalMode && offset < payLen){ + if (needBytes(payDat + offset, payLen - offset) > payLen - offset){ + WARN_MSG("Trying to read beyond boundaries of element! Aborted."); + break; + } + Element e(payDat + offset); + ret << e.toPrettyString(indent + 2, detail); + offset += e.getOuterLen(); + } + }break; + case ELEM_UINT:{ + ret << std::string(indent, ' ') << "Element (" << getPayloadLen() << "/" << getOuterLen() + << ") [" << getIDString(getID()) << "] = " << getValUInt() << std::endl; + }break; + case ELEM_INT:{ + ret << std::string(indent, ' ') << "Element (" << getPayloadLen() << "/" << getOuterLen() + << ") [" << getIDString(getID()) << "] = " << getValInt() << std::endl; + }break; + case ELEM_FLOAT:{ + ret << std::string(indent, ' ') << "Element (" << getPayloadLen() << "/" << getOuterLen() + << ") [" << getIDString(getID()) << "] = " << getValFloat() << std::endl; + }break; + case ELEM_STRING: + case ELEM_UTF8:{ + ret << std::string(indent, ' ') << "Element (" << getPayloadLen() << "/" << getOuterLen() + << ") [" << getIDString(getID()) << "] = " << getValString() << std::endl; + }break; + case ELEM_BLOCK:{ + return Block(data).toPrettyString(indent, detail); + }break; + case ELEM_BIN:{ + const uint32_t EID = getID(); + const char *payDat = getPayload(); + const uint64_t payLen = getPayloadLen(); + if (EID == EID_SEEKID){ + ret << std::string(indent, ' ') << "Element (" << payLen << "/" << getOuterLen() << ") [" + << getIDString(getID()) << "] = " << getIDString(getValUInt()) << std::endl; + return ret.str(); + } + if (payLen > 256 || (detail < 4 && payLen > 32)){ + ret << std::string(indent, ' ') << "Element (" << getOuterLen() << ") [" + << getIDString(getID()) << "] = " << payLen << " bytes of binary data" << std::endl; + }else{ + if (getPayloadLen() <= 32){ + ret << std::string(indent, ' ') << "Element (" << payLen << "/" << getOuterLen() << ") [" + << getIDString(getID()) << "] = "; + for (uint64_t i = 0; i < payLen; ++i){ + ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i]; + } + + }else{ + ret << std::string(indent, ' ') << "Element (" << payLen << "/" << getOuterLen() << ") [" + << getIDString(getID()) << "] ="; + for (uint64_t i = 0; i < payLen; ++i){ + if ((i % 32) == 0){ret << std::endl << std::string(indent + 2, ' ');} + ret << std::hex << std::setw(2) << std::setfill('0') << (unsigned int)payDat[i]; + } + } + ret << std::endl; + } + }break; + default: + ret << std::string(indent, ' ') << "Element [" << getIDString(getID()) << "] (" + << getOuterLen() << "b, " << getPayloadLen() << "b payload)" << std::endl; + ret << std::string(indent + 2, ' ') << "{Payload type not implemented}" << std::endl; + } + return ret.str(); + } + + uint64_t Element::getValUInt() const{ + const char *payDat = getPayload(); + uint64_t val = 0; + switch (getPayloadLen()){ + case 1: val = payDat[0]; break; + case 2: val = Bit::btohs(payDat); break; + case 3: val = Bit::btoh24(payDat); break; + case 4: val = Bit::btohl(payDat); break; + case 5: val = Bit::btoh40(payDat); break; + case 6: val = Bit::btoh48(payDat); break; + case 7: val = Bit::btoh56(payDat); break; + case 8: val = Bit::btohll(payDat); break; + default: WARN_MSG("UInt payload size %llu not implemented", getPayloadLen()); + } + return val; + } + + int64_t Element::getValInt() const{ + const char *payDat = getPayload(); + int64_t val = 0; + switch (getPayloadLen()){ + case 1: val = (int8_t)payDat[0]; break; + case 2: val = (((int64_t)Bit::btohs(payDat)) << 48) >> 48; break; + case 3: val = (((int64_t)Bit::btoh24(payDat)) << 40) >> 40; break; + case 4: val = (int32_t)Bit::btohl(payDat); break; + case 5: val = (((int64_t)Bit::btoh40(payDat)) << 24) >> 24; break; + case 6: val = (((int64_t)Bit::btoh48(payDat)) << 16) >> 16; break; + case 7: val = (((int64_t)Bit::btoh56(payDat)) << 8) >> 8; break; + case 8: val = Bit::btohll(payDat); break; + default: WARN_MSG("Int payload size %llu not implemented", getPayloadLen()); + } + return val; + } + + double Element::getValFloat() const{ + const char *payDat = getPayload(); + double val = 0; + switch (getPayloadLen()){ + case 4: val = Bit::btohf(payDat); break; + case 8: val = Bit::btohd(payDat); break; + default: WARN_MSG("Float payload size %llu not implemented", getPayloadLen()); + } + return val; + } + + std::string Element::getValString() const{return std::string(getPayload(), getPayloadLen());} + + uint64_t Block::getTrackNum() const{return UniInt::readInt(getPayload());} + + int16_t Block::getTimecode() const{ + return Bit::btohs(getPayload() + UniInt::readSize(getPayload())); + } + + bool Block::isKeyframe() const{return getPayload()[UniInt::readSize(getPayload()) + 2] & 0x80;} + + bool Block::isInvisible() const{ + return getPayload()[UniInt::readSize(getPayload()) + 2] & 0x08; + } + + bool Block::isDiscardable() const{ + return getPayload()[UniInt::readSize(getPayload()) + 2] & 0x01; + } + + uint8_t Block::getLacing() const{ + return (getPayload()[UniInt::readSize(getPayload()) + 2] & 0x6) >> 1; + } + + uint8_t Block::getFrameCount() const{ + if (getLacing() == 0){return 1;} + return getPayload()[UniInt::readSize(getPayload()) + 3] + 1; + } + + uint32_t Block::getFrameSize(uint8_t no) const{ + switch (getLacing()){ + case 0://No lacing + return getPayloadLen() - (UniInt::readSize(getPayload()) + 3); + case 1:{//Xiph lacing + uint64_t offset = (UniInt::readSize(getPayload()) + 3) + 1; + uint8_t frames = getFrameCount(); + if (no > frames - 1){return 0;}//out of bounds + uint64_t laceNo = 0; + uint32_t currSize = 0; + uint32_t totSize = 0; + while (laceNo <= no && (laceNo < frames-1) && offset < getPayloadLen()){ + currSize += getPayload()[offset]; + if (getPayload()[offset] != 255){ + totSize += currSize; + if (laceNo == no){return currSize;} + currSize = 0; + ++laceNo; + } + ++offset; + } + return getPayloadLen() - offset - totSize;//last frame is rest of the data + } + case 3:{//EBML lacing + const char * pl = getPayload(); + uint64_t offset = (UniInt::readSize(pl) + 3) + 1; + uint8_t frames = getFrameCount(); + if (no > frames - 1){return 0;}//out of bounds + uint64_t laceNo = 0; + uint32_t currSize = 0; + uint32_t totSize = 0; + while (laceNo <= no && (laceNo < frames-1) && offset < getPayloadLen()){ + if (laceNo == 0){ + currSize = UniInt::readInt(pl + offset); + }else{ + currSize += UniInt::readSInt(pl + offset); + } + totSize += currSize; + if (laceNo == no){return currSize;} + ++laceNo; + offset += UniInt::readSize(pl + offset); + } + return getPayloadLen() - offset - totSize;//last frame is rest of the data + } + case 2://Fixed lacing + return (getPayloadLen() - (UniInt::readSize(getPayload()) + 3)) / getFrameCount(); + } + WARN_MSG("Lacing type not yet implemented!"); + return 0; + } + + const char *Block::getFrameData(uint8_t no) const{ + switch (getLacing()){ + case 0://No lacing + return getPayload() + (UniInt::readSize(getPayload()) + 3); + case 1:{//Xiph lacing + uint64_t offset = (UniInt::readSize(getPayload()) + 3) + 1; + uint8_t frames = getFrameCount(); + if (no > frames - 1){return 0;}//out of bounds + uint64_t laceNo = 0; + uint32_t currSize = 0; + while ((laceNo < frames-1) && offset < getPayloadLen()){ + if (laceNo < no){ + currSize += getPayload()[offset]; + } + if (getPayload()[offset] != 255){ + ++laceNo; + } + ++offset; + } + return getPayload() + offset + currSize; + } + case 3:{//EBML lacing + const char * pl = getPayload(); + uint64_t offset = (UniInt::readSize(pl) + 3) + 1; + uint8_t frames = getFrameCount(); + if (no > frames - 1){return 0;}//out of bounds + uint64_t laceNo = 0; + uint32_t currSize = 0; + uint32_t totSize = 0; + while ((laceNo < frames-1) && offset < getPayloadLen()){ + if (laceNo == 0){ + currSize = UniInt::readInt(pl + offset); + }else{ + currSize += UniInt::readSInt(pl + offset); + } + if (laceNo < no){ + totSize += currSize; + } + ++laceNo; + offset += UniInt::readSize(pl + offset); + } + return pl + offset + totSize; + } + case 2://Fixed lacing + return getPayload() + (UniInt::readSize(getPayload()) + 3) + 1 + no * getFrameSize(no); + } + WARN_MSG("Lacing type not yet implemented!"); + return 0; + } + + std::string Block::toPrettyString(const uint8_t indent, const uint8_t detail) const{ + std::stringstream ret; + ret << std::string(indent, ' ') << getIDString(getID()) << " with " + << (unsigned int)getFrameCount() << " frame(s) for track " << getTrackNum() << " @ " + << getTimecode(); + if (isKeyframe()){ret << " [KeyOnly]";} + if (isInvisible()){ret << " [Invisible]";} + if (isDiscardable()){ret << " [Discardable]";} + switch (getLacing()){ + case 0: + break; // No lacing + case 1: ret << " [Lacing: Xiph]"; break; + case 3: ret << " [Lacing: EMBL]"; break; + case 2: ret << " [Lacing: Fixed]"; break; + } + if (detail < 8){ + ret << std::endl; + return ret.str(); + } + 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;} + 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; + return ret.str(); + } +} + diff --git a/lib/ebml.h b/lib/ebml.h new file mode 100644 index 00000000..1597d8a6 --- /dev/null +++ b/lib/ebml.h @@ -0,0 +1,118 @@ +#include +#include + +namespace EBML{ + + class UniInt{ + public: + static uint8_t readSize(const char *p); + static uint8_t writeSize(const uint64_t val); + static uint64_t readInt(const char *p); + static void writeInt(char *p, const uint64_t val); + static int64_t readSInt(const char *p); + static void writeSInt(char *p, const int64_t val); + }; + + enum ElementType{ + ELEM_UNKNOWN, + ELEM_MASTER, + ELEM_UINT, + ELEM_INT, + ELEM_STRING, + ELEM_UTF8, + ELEM_BIN, + ELEM_FLOAT, + ELEM_DATE, + ELEM_BLOCK + }; + + enum ElementID{ + EID_EBML = 0x0A45DFA3, + EID_EBMLVERSION = 0x286, + EID_EBMLREADVERSION = 0x2F7, + EID_EBMLMAXIDLENGTH = 0x2F2, + EID_EBMLMAXSIZELENGTH = 0x2F3, + EID_DOCTYPE = 0x282, + EID_DOCTYPEVERSION = 0x287, + EID_DOCTYPEREADVERSION = 0x285, + EID_CODECID = 0x6, + EID_TRACKTYPE = 0x3, + EID_DEFAULTDURATION = 0x3E383, + EID_DURATION = 0x489, + EID_CHANNELS = 0x1F, + EID_SAMPLINGFREQUENCY = 0x35, + EID_TIMECODE = 0x67, + EID_BITDEPTH = 0x2264, + EID_TRACKENTRY = 0x2E, + EID_TRACKUID = 0x33C5, + EID_PIXELWIDTH = 0x30, + EID_FLAGLACING = 0x1C, + EID_PIXELHEIGHT = 0x3A, + EID_TRACKNUMBER = 0x57, + EID_CODECPRIVATE = 0x23A2, + EID_LANGUAGE = 0x2B59C, + EID_VIDEO = 0x60, + EID_AUDIO = 0x61, + EID_TIMECODESCALE = 0xAD7B1, + EID_MUXINGAPP = 0xD80, + EID_WRITINGAPP = 0x1741, + EID_CLUSTER = 0x0F43B675, + EID_SEGMENT = 0x08538067, + EID_INFO = 0x0549A966, + EID_TRACKS = 0x0654AE6B, + EID_SIMPLEBLOCK = 0x23, + EID_SEEKHEAD = 0x014D9B74, + EID_SEEK = 0xDBB, + EID_SEEKID = 0x13AB, + EID_SEEKPOSITION = 0x13AC, + EID_CUES = 0xC53BB6B, + EID_CUETRACK = 0x77, + EID_CUECLUSTERPOSITION = 0x71, + EID_CUERELATIVEPOSITION = 0x70, + EID_CUETRACKPOSITIONS = 0x37, + EID_CUETIME = 0x33, + EID_CUEPOINT = 0x3B, + EID_UNKNOWN = 0 + }; + + class Element{ + public: + static uint64_t needBytes(const char *p, uint64_t availBytes, bool minimal = false); + static std::string getIDString(uint32_t id); + Element(const char *p = 0, bool minimal = false); + inline operator bool() const{return data;} + uint32_t getID() const; + uint64_t getPayloadLen() const; + uint8_t getHeaderLen() const; + const char *getPayload() const; + uint64_t getOuterLen() const; + ElementType getType() const; + virtual std::string toPrettyString(const uint8_t indent = 0, const uint8_t detail = 3) const; + uint64_t getValUInt() const; + int64_t getValInt() const; + double getValFloat() const; + std::string getValString() const; + const Element findChild(uint32_t id) const; + + private: + const char *data; + bool minimalMode; ///= 0x100000000000000){ + return 8; + }else if (val >= 0x1000000000000){ + return 7; + }else if (val >= 0x10000000000){ + return 6; + }else if (val >= 0x100000000){ + return 5; + }else if (val >= 0x1000000){ + return 4; + }else if (val >= 0x10000){ + return 3; + }else if (val >= 0x100){ + return 2; + }else{ + return 1; + } + } + + uint32_t sizeElemUInt(uint32_t ID, const uint64_t val){ + uint8_t iSize = sizeUInt(val); + return sizeElemHead(ID, iSize) + iSize; + } + + uint32_t sizeElemID(uint32_t ID, const uint64_t val){ + uint8_t iSize = UniInt::writeSize(val); + return sizeElemHead(ID, iSize) + iSize; + } + + uint32_t sizeElemDbl(uint32_t ID, const double val){ + uint8_t iSize = (val == (float)val) ? 4 : 8; + return sizeElemHead(ID, iSize) + iSize; + } + + uint32_t sizeElemStr(uint32_t ID, const std::string &val){ + return sizeElemHead(ID, val.size()) + val.size(); + } + + void sendElemHead(Socket::Connection &C, uint32_t ID, const uint64_t size){ + sendUniInt(C, ID); + sendUniInt(C, size); + } + + void sendElemUInt(Socket::Connection &C, uint32_t ID, const uint64_t val){ + char tmp[8]; + uint8_t wSize = sizeUInt(val); + switch (wSize){ + case 8: Bit::htobll(tmp, val); break; + case 7: Bit::htob56(tmp, val); break; + case 6: Bit::htob48(tmp, val); break; + case 5: Bit::htob40(tmp, val); break; + case 4: Bit::htobl(tmp, val); break; + case 3: Bit::htob24(tmp, val); break; + case 2: Bit::htobs(tmp, val); break; + case 1: tmp[0] = val; break; + } + sendElemHead(C, ID, wSize); + C.SendNow(tmp, wSize); + } + + void sendElemID(Socket::Connection &C, uint32_t ID, const uint64_t val){ + char tmp[8]; + uint8_t wSize = UniInt::writeSize(val); + sendElemHead(C, ID, wSize); + sendUniInt(C, val); + } + + void sendElemDbl(Socket::Connection &C, uint32_t ID, const double val){ + char tmp[8]; + uint8_t wSize = (val == (float)val) ? 4 : 8; + switch (wSize){ + case 4: Bit::htobf(tmp, val); break; + case 8: Bit::htobd(tmp, val); break; + } + sendElemHead(C, ID, wSize); + C.SendNow(tmp, wSize); + } + + void sendElemStr(Socket::Connection &C, uint32_t ID, const std::string &val){ + sendElemHead(C, ID, val.size()); + C.SendNow(val); + } + + void sendElemEBML(Socket::Connection &C, const std::string &doctype){ + sendElemHead(C, EID_EBML, 27 + doctype.size()); + sendElemUInt(C, EID_EBMLVERSION, 1); + sendElemUInt(C, EID_EBMLREADVERSION, 1); + sendElemUInt(C, EID_EBMLMAXIDLENGTH, 4); + sendElemUInt(C, EID_EBMLMAXSIZELENGTH, 8); + sendElemStr(C, EID_DOCTYPE, doctype); + if (doctype == "matroska"){ + sendElemUInt(C, EID_DOCTYPEVERSION, 4); + sendElemUInt(C, EID_DOCTYPEREADVERSION, 1); + }else{ + sendElemUInt(C, EID_DOCTYPEVERSION, 1); + sendElemUInt(C, EID_DOCTYPEREADVERSION, 1); + } + } + + void sendElemInfo(Socket::Connection &C, const std::string &appName, double duration){ + sendElemHead(C, EID_INFO, 13 + 2 * appName.size() + (duration>0?sizeElemDbl(EID_DURATION, duration):0)); + sendElemUInt(C, EID_TIMECODESCALE, 1000000); + if (duration > 0){ + sendElemDbl(C, EID_DURATION, duration); + } + sendElemStr(C, EID_MUXINGAPP, appName); + sendElemStr(C, EID_WRITINGAPP, appName); + } + + uint32_t sizeElemEBML(const std::string &doctype){ + return 27 + doctype.size() + sizeElemHead(EID_EBML, 27 + doctype.size()); + } + + uint32_t sizeElemInfo(const std::string &appName, double duration){ + return 13 + 2 * appName.size() + (duration>0?sizeElemDbl(EID_DURATION, duration):0) + sizeElemHead(EID_INFO, 13 + 2 * appName.size() + (duration>0?sizeElemDbl(EID_DURATION, duration):0)); + } + + void sendSimpleBlock(Socket::Connection &C, DTSC::Packet & pkt, uint64_t clusterTime, bool forceKeyframe){ + unsigned int dataLen = 0; + char * dataPointer = 0; + pkt.getString("data", dataPointer, dataLen); + uint32_t blockSize = UniInt::writeSize(pkt.getTrackId()) + 3 + dataLen; + sendElemHead(C, EID_SIMPLEBLOCK, blockSize); + sendUniInt(C, pkt.getTrackId()); + char blockHead[3] = {0, 0, 0}; + if (pkt.hasMember("keyframe") || forceKeyframe){ + blockHead[2] = 0x80; + } + int offset = 0; + if (pkt.hasMember("offset")){ + offset = pkt.getInt("offset"); + } + Bit::htobs(blockHead, (int16_t)(pkt.getTime() + offset - clusterTime)); + C.SendNow(blockHead, 3); + C.SendNow(dataPointer, dataLen); + } + + uint32_t sizeSimpleBlock(uint64_t trackId, uint32_t dataSize){ + uint32_t ret = UniInt::writeSize(trackId) + 3 + dataSize; + return ret + sizeElemHead(EID_SIMPLEBLOCK, ret); + } + + void sendElemSeek(Socket::Connection &C, uint32_t ID, uint64_t bytePos){ + uint32_t elems = sizeElemUInt(EID_SEEKID, ID) + sizeElemUInt(EID_SEEKPOSITION, bytePos); + sendElemHead(C, EID_SEEK, elems); + sendElemID(C, EID_SEEKID, ID); + sendElemUInt(C, EID_SEEKPOSITION, bytePos); + } + + uint32_t sizeElemSeek(uint32_t ID, uint64_t bytePos){ + uint32_t elems = sizeElemID(EID_SEEKID, ID) + sizeElemUInt(EID_SEEKPOSITION, bytePos); + return sizeElemHead(EID_SEEK, elems) + elems; + } + + void sendElemCuePoint(Socket::Connection &C, uint64_t time, uint64_t track, uint64_t clusterPos, uint64_t relaPos){ + uint32_t elemsA = 0, elemsB = 0; + elemsA += sizeElemUInt(EID_CUETRACK, track); + elemsA += sizeElemUInt(EID_CUECLUSTERPOSITION, clusterPos); + elemsA += sizeElemUInt(EID_CUERELATIVEPOSITION, relaPos); + elemsB = elemsA + sizeElemUInt(EID_CUETIME, time) + sizeElemHead(EID_CUETRACKPOSITIONS, elemsA); + sendElemHead(C, EID_CUEPOINT, elemsB); + sendElemUInt(C, EID_CUETIME, time); + sendElemHead(C, EID_CUETRACKPOSITIONS, elemsA); + sendElemUInt(C, EID_CUETRACK, track); + sendElemUInt(C, EID_CUECLUSTERPOSITION, clusterPos); + sendElemUInt(C, EID_CUERELATIVEPOSITION, relaPos); + } + + uint32_t sizeElemCuePoint(uint64_t time, uint64_t track, uint64_t clusterPos, uint64_t relaPos){ + uint32_t elems = 0; + elems += sizeElemUInt(EID_CUETRACK, track); + elems += sizeElemUInt(EID_CUECLUSTERPOSITION, clusterPos); + elems += sizeElemUInt(EID_CUERELATIVEPOSITION, relaPos); + elems += sizeElemHead(EID_CUETRACKPOSITIONS, elems); + elems += sizeElemUInt(EID_CUETIME, time); + return sizeElemHead(EID_CUEPOINT, elems) + elems; + } + + +} + diff --git a/lib/ebml_socketglue.h b/lib/ebml_socketglue.h new file mode 100644 index 00000000..a6bf284e --- /dev/null +++ b/lib/ebml_socketglue.h @@ -0,0 +1,33 @@ +#include "ebml.h" +#include "socket.h" +#include "bitfields.h" +#include "dtsc.h" + +namespace EBML{ + static void sendUniInt(Socket::Connection &C, const uint64_t val); + void sendElemHead(Socket::Connection &C, uint32_t ID, const uint64_t size); + void sendElemUInt(Socket::Connection &C, uint32_t ID, const uint64_t val); + void sendElemID(Socket::Connection &C, uint32_t ID, const uint64_t val); + void sendElemDbl(Socket::Connection &C, uint32_t ID, const double val); + void sendElemStr(Socket::Connection &C, uint32_t ID, const std::string &val); + void sendElemEBML(Socket::Connection &C, const std::string &doctype); + void sendElemInfo(Socket::Connection &C, const std::string &appName, double duration); + uint32_t sizeElemEBML(const std::string &doctype); + uint32_t sizeElemInfo(const std::string &appName, double duration); + + void sendElemSeek(Socket::Connection &C, uint32_t ID, uint64_t bytePos); + uint32_t sizeElemSeek(uint32_t ID, uint64_t bytePos); + void sendElemCuePoint(Socket::Connection &C, uint64_t time, uint64_t track, uint64_t clusterPos, uint64_t relaPos); + uint32_t sizeElemCuePoint(uint64_t time, uint64_t track, uint64_t clusterPos, uint64_t relaPos); + + uint8_t sizeUInt(const uint64_t val); + uint32_t sizeElemHead(uint32_t ID, const uint64_t size); + uint32_t sizeElemUInt(uint32_t ID, const uint64_t val); + uint32_t sizeElemID(uint32_t ID, const uint64_t val); + uint32_t sizeElemDbl(uint32_t ID, const double val); + uint32_t sizeElemStr(uint32_t ID, const std::string &val); + + void sendSimpleBlock(Socket::Connection &C, DTSC::Packet & pkt, uint64_t clusterTime, bool forceKeyframe = false); + uint32_t sizeSimpleBlock(uint64_t trackId, uint32_t dataSize); +} + diff --git a/src/analysers/analyser_ebml.cpp b/src/analysers/analyser_ebml.cpp new file mode 100644 index 00000000..22e183ee --- /dev/null +++ b/src/analysers/analyser_ebml.cpp @@ -0,0 +1,49 @@ +#include "analyser_ebml.h" +#include +#include + +void AnalyserEBML::init(Util::Config &conf){ + Analyser::init(conf); +} + +AnalyserEBML::AnalyserEBML(Util::Config &conf) : Analyser(conf){ + curPos = prePos = 0; +} + +bool AnalyserEBML::parsePacket(){ + prePos = curPos; + // Read in smart bursts until we have enough data + while (isOpen() && dataBuffer.size() < neededBytes()){ + uint64_t needed = neededBytes(); + 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 (dataBuffer.size() < neededBytes()){return false;} + + 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);} + if (depthStash.size()){ + depthStash.front() -= E.getOuterLen(); + } + if (E.getType() == EBML::ELEM_MASTER){ + depthStash.push_front(E.getPayloadLen()); + } + while (depthStash.size() && !depthStash.front()){ + depthStash.pop_front(); + } + ///\TODO update mediaTime with the current timestamp + dataBuffer.erase(0, E.getOuterLen()); + return true; +} + +/// Calculates how many bytes we need to read a whole box. +uint64_t AnalyserEBML::neededBytes(){ + return EBML::Element::needBytes(dataBuffer.data(), dataBuffer.size(), true); +} + diff --git a/src/analysers/analyser_ebml.h b/src/analysers/analyser_ebml.h new file mode 100644 index 00000000..ba5ac9c4 --- /dev/null +++ b/src/analysers/analyser_ebml.h @@ -0,0 +1,17 @@ +#include "analyser.h" +#include + +class AnalyserEBML : public Analyser{ +public: + AnalyserEBML(Util::Config &conf); + static void init(Util::Config &conf); + bool parsePacket(); + +private: + uint64_t neededBytes(); + std::string dataBuffer; + uint64_t curPos; + uint64_t prePos; + std::deque depthStash;/// +#include +#include + +namespace Mist{ + InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){ + capa["name"] = "EBML"; + capa["desc"] = "Enables MKV and WebM input"; + capa["source_match"].append("/*.mkv"); + capa["source_match"].append("/*.mka"); + capa["source_match"].append("/*.mk3d"); + capa["source_match"].append("/*.mks"); + capa["source_match"].append("/*.webm"); + capa["priority"] = 9ll; + capa["codecs"].append("H264"); + capa["codecs"].append("HEVC"); + capa["codecs"].append("VP8"); + capa["codecs"].append("VP9"); + capa["codecs"].append("opus"); + capa["codecs"].append("vorbis"); + capa["codecs"].append("theora"); + capa["codecs"].append("AAC"); + capa["codecs"].append("PCM"); + capa["codecs"].append("ALAW"); + capa["codecs"].append("ULAW"); + capa["codecs"].append("MP2"); + capa["codecs"].append("MPEG2"); + capa["codecs"].append("MP3"); + capa["codecs"].append("AC3"); + capa["codecs"].append("FLOAT"); + 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; + } + if (!config->getString("streamname").size()){ + if (config->getString("output") == "-"){ + std::cerr << "Output to stdout not yet supported" << std::endl; + return false; + } + }else{ + if (config->getString("output") != "-"){ + std::cerr << "File output in player mode not supported" << std::endl; + return false; + } + } + return true; + } + + bool InputEBML::preRun(){ + // open File + inFile = fopen(config->getString("input").c_str(), "r"); + if (!inFile){return false;} + return true; + } + + bool InputEBML::readElement(){ + ptr.size() = 0; + readingMinimal = true; + uint32_t needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); + 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!"); + return false; + } + ptr.size() = needed; + needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); + if (ptr.size() >= needed){ + // Make sure TrackEntry types are read whole + if (readingMinimal && EBML::Element(ptr).getID() == EBML::EID_TRACKENTRY){ + readingMinimal = false; + needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal); + } + } + } + EBML::Element E(ptr); + if (E.getID() == EBML::EID_CLUSTER){lastClusterBPos = Util::ftell(inFile);} + if (E.getID() == EBML::EID_TIMECODE){lastClusterTime = E.getValUInt();} + return true; + } + + bool InputEBML::readExistingHeader(){ + if (!Input::readExistingHeader()){return false;} + for (std::map::iterator it = myMeta.tracks.begin(); + it != myMeta.tracks.end(); ++it){ + if (it->second.codec == "PCMLE"){ + it->second.codec = "PCM"; + swapEndianness.insert(it->first); + } + } + return true; + } + + bool InputEBML::readHeader(){ + if (!inFile){return false;} + // Create header file from file + uint64_t bench = Util::getMicros(); + + while (readElement()){ + EBML::Element E(ptr, readingMinimal); + if (E.getID() == EBML::EID_TRACKENTRY){ + EBML::Element tmpElem = E.findChild(EBML::EID_TRACKNUMBER); + if (!tmpElem){ + ERROR_MSG("Track without track number encountered, ignoring"); + continue; + } + uint64_t trackNo = tmpElem.getValUInt(); + tmpElem = E.findChild(EBML::EID_CODECID); + if (!tmpElem){ + ERROR_MSG("Track without codec id encountered, ignoring"); + continue; + } + std::string codec = tmpElem.getValString(), trueCodec, trueType, lang, init; + if (codec == "V_MPEG4/ISO/AVC"){ + trueCodec = "H264"; + trueType = "video"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "V_MPEGH/ISO/HEVC"){ + trueCodec = "HEVC"; + trueType = "video"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "V_VP9"){ + trueCodec = "VP9"; + trueType = "video"; + } + if (codec == "V_VP8"){ + trueCodec = "VP8"; + trueType = "video"; + } + if (codec == "A_OPUS"){ + trueCodec = "opus"; + trueType = "audio"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "A_VORBIS"){ + trueCodec = "vorbis"; + trueType = "audio"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "V_THEORA"){ + trueCodec = "theora"; + trueType = "video"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "A_AAC"){ + trueCodec = "AAC"; + trueType = "audio"; + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){init = tmpElem.getValString();} + } + if (codec == "A_PCM/INT/BIG"){ + trueCodec = "PCM"; + trueType = "audio"; + } + if (codec == "A_PCM/INT/LIT"){ + trueCodec = "PCMLE"; + trueType = "audio"; + } + if (codec == "A_AC3"){ + trueCodec = "AC3"; + trueType = "audio"; + } + if (codec == "A_MPEG/L3"){ + trueCodec = "MP3"; + trueType = "audio"; + } + if (codec == "A_MPEG/L2"){ + trueCodec = "MP2"; + trueType = "audio"; + } + if (codec == "V_MPEG2"){ + trueCodec = "MPEG2"; + trueType = "video"; + } + if (codec == "A_PCM/FLOAT/IEEE"){ + trueCodec = "FLOAT"; + trueType = "audio"; + } + if (codec == "A_MS/ACM"){ + tmpElem = E.findChild(EBML::EID_CODECPRIVATE); + if (tmpElem){ + std::string WAVEFORMATEX = tmpElem.getValString(); + unsigned int formatTag = Bit::btohs_le(WAVEFORMATEX.data()); + switch (formatTag){ + case 3: + trueCodec = "FLOAT"; + trueType = "audio"; + break; + case 6: + trueCodec = "ALAW"; + trueType = "audio"; + break; + case 7: + trueCodec = "ULAW"; + trueType = "audio"; + break; + case 85: + trueCodec = "MP3"; + trueType = "audio"; + break; + default: + ERROR_MSG("Unimplemented A_MS/ACM formatTag: %u", formatTag); + break; + } + } + } + if (!trueCodec.size()){ + WARN_MSG("Unrecognised codec id %s ignoring", codec.c_str()); + continue; + } + tmpElem = E.findChild(EBML::EID_LANGUAGE); + if (tmpElem){lang = tmpElem.getValString();} + DTSC::Track &Trk = myMeta.tracks[trackNo]; + Trk.trackID = trackNo; + Trk.lang = lang; + Trk.codec = trueCodec; + Trk.type = trueType; + Trk.init = init; + if (Trk.type == "video"){ + tmpElem = E.findChild(EBML::EID_PIXELWIDTH); + Trk.width = tmpElem ? tmpElem.getValUInt() : 0; + tmpElem = E.findChild(EBML::EID_PIXELHEIGHT); + Trk.height = tmpElem ? tmpElem.getValUInt() : 0; + Trk.fpks = 0; + } + if (Trk.type == "audio"){ + tmpElem = E.findChild(EBML::EID_CHANNELS); + Trk.channels = tmpElem ? tmpElem.getValUInt() : 1; + tmpElem = E.findChild(EBML::EID_BITDEPTH); + Trk.size = tmpElem ? tmpElem.getValUInt() : 0; + tmpElem = E.findChild(EBML::EID_SAMPLINGFREQUENCY); + Trk.rate = tmpElem ? (int)tmpElem.getValFloat() : 8000; + } + INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str()); + } + if (E.getType() == EBML::ELEM_BLOCK){ + EBML::Block B(ptr); + uint64_t tNum = B.getTrackNum(); + uint64_t newTime = lastClusterTime + B.getTimecode(); + trackPredictor &TP = packBuf[tNum]; + DTSC::Track &Trk = myMeta.tracks[tNum]; + bool isVideo = (Trk.type == "video"); + for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ + if (frameNo){ + if (Trk.codec == "AAC"){ + newTime += 1000000 / Trk.rate;//assume ~1000 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); + } + } + while (TP.hasPackets()){ + packetData &C = TP.getPacketData(isVideo); + myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); + TP.remove(); + } + } + } + + if (packBuf.size()){ + for (std::map::iterator it = packBuf.begin(); it != packBuf.end(); + ++it){ + trackPredictor &TP = it->second; + while (TP.hasPackets(true)){ + packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); + myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key); + TP.remove(); + } + } + } + + bench = Util::getMicros(bench); + INFO_MSG("Header generated in %llu ms", bench / 1000); + packBuf.clear(); + bufferedPacks = 0; + myMeta.toFile(config->getString("input") + ".dtsh"); + for (std::map::iterator it = myMeta.tracks.begin(); + it != myMeta.tracks.end(); ++it){ + if (it->second.codec == "PCMLE"){ + it->second.codec = "PCM"; + swapEndianness.insert(it->first); + } + } + return true; + } + + void InputEBML::fillPacket(packetData &C){ + if (swapEndianness.count(C.track)){ + switch (myMeta.tracks[C.track].size){ + case 16:{ + char *ptr = C.ptr; + uint32_t ptrSize = C.dsize; + for (uint32_t i = 0; i < ptrSize; i += 2){ + char tmpchar = ptr[i]; + ptr[i] = ptr[i + 1]; + ptr[i + 1] = tmpchar; + } + }break; + case 24:{ + char *ptr = C.ptr; + uint32_t ptrSize = C.dsize; + for (uint32_t i = 0; i < ptrSize; i += 3){ + char tmpchar = ptr[i]; + ptr[i] = ptr[i + 2]; + ptr[i + 2] = tmpchar; + } + }break; + case 32:{ + char *ptr = C.ptr; + uint32_t ptrSize = C.dsize; + for (uint32_t i = 0; i < ptrSize; i += 4){ + char tmpchar = ptr[i]; + ptr[i] = ptr[i + 3]; + ptr[i + 3] = tmpchar; + tmpchar = ptr[i + 1]; + ptr[i + 1] = ptr[i + 2]; + ptr[i + 2] = tmpchar; + } + }break; + } + } + thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize, C.bpos, C.key); + } + + void InputEBML::getNext(bool smart){ + // Make sure we empty our buffer first + if (bufferedPacks && packBuf.size()){ + for (std::map::iterator it = packBuf.begin(); + it != packBuf.end(); ++it){ + trackPredictor &TP = it->second; + if (TP.hasPackets()){ + packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); + fillPacket(C); + TP.remove(); + --bufferedPacks; + return; + } + } + } + EBML::Block B; + do{ + if (!readElement()){ + // Make sure we empty our buffer first + if (bufferedPacks && packBuf.size()){ + for (std::map::iterator it = packBuf.begin(); + it != packBuf.end(); ++it){ + trackPredictor &TP = it->second; + if (TP.hasPackets(true)){ + packetData &C = TP.getPacketData(myMeta.tracks[it->first].type == "video"); + fillPacket(C); + TP.remove(); + --bufferedPacks; + return; + } + } + } + // No more buffer? Set to empty + thisPacket.null(); + return; + } + B = EBML::Block(ptr); + }while (!B || B.getType() != EBML::ELEM_BLOCK || !selectedTracks.count(B.getTrackNum())); + + uint64_t tNum = B.getTrackNum(); + uint64_t newTime = lastClusterTime + B.getTimecode(); + trackPredictor &TP = packBuf[tNum]; + DTSC::Track & Trk = myMeta.tracks[tNum]; + bool isVideo = (Trk.type == "video"); + for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){ + if (frameNo){ + if (Trk.codec == "AAC"){ + newTime += 1000000 / Trk.rate;//assume ~1000 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; + } + } + if (TP.hasPackets()){ + packetData &C = TP.getPacketData(isVideo); + fillPacket(C); + TP.remove(); + --bufferedPacks; + }else{ + // We didn't set thisPacket yet. Read another. + // Recursing is fine, this can only happen a few times in a row. + getNext(smart); + } + } + + void InputEBML::seek(int seekTime){ + packBuf.clear(); + bufferedPacks = 0; + DTSC::Track Trk = myMeta.tracks[getMainSelectedTrack()]; + uint64_t seekPos = Trk.keys[0].getBpos(); + for (unsigned int i = 0; i < Trk.keys.size(); i++){ + if (Trk.keys[i].getTime() > seekTime){break;} + seekPos = Trk.keys[i].getBpos(); + } + Util::fseek(inFile, seekPos, SEEK_SET); + } + +}// namespace Mist + diff --git a/src/input/input_ebml.h b/src/input/input_ebml.h new file mode 100644 index 00000000..7b5d725a --- /dev/null +++ b/src/input/input_ebml.h @@ -0,0 +1,108 @@ +#include "input.h" +#include + +namespace Mist{ + + class packetData{ + public: + uint64_t time, offset, track, dsize, bpos; + bool key; + Util::ResizeablePointer ptr; + packetData(){ + time = 0; + offset = 0; + track = 0; + dsize = 0; + bpos = 0; + key = false; + } + void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){ + time = packTime; + offset = packOffset; + track = packTrack; + dsize = packDataSize; + bpos = packBytePos; + key = isKeyframe; + if (dataPtr){ + ptr.assign(dataPtr, packDataSize); + } + } + packetData(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){ + set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr); + } + }; + class trackPredictor{ + public: + packetData pkts[16]; + uint16_t smallestFrame; + uint64_t lastTime; + uint64_t ctr; + uint64_t rem; + trackPredictor(){ + smallestFrame = 0; + lastTime = 0; + ctr = 0; + rem = 0; + } + bool hasPackets(bool finished = false){ + if (finished){ + return (ctr - rem > 0); + }else{ + return (ctr - rem > 8); + } + } + 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; + } + } + 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; + } + } + pkts[ctr % 16].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr); + ++ctr; + } + void remove(){ + ++rem; + } + + }; + + class InputEBML : public Input{ + public: + InputEBML(Util::Config *cfg); + + protected: + void fillPacket(packetData & C); + bool checkArguments(); + bool preRun(); + bool readHeader(); + bool readElement(); + void getNext(bool smart = true); + void seek(int seekTime); + FILE *inFile; + Util::ResizeablePointer ptr; + bool readingMinimal; + uint64_t lastClusterBPos; + uint64_t lastClusterTime; + uint64_t bufferedPacks; + std::map packBuf; + std::set swapEndianness; + bool readExistingHeader(); + }; +} + +typedef Mist::InputEBML mistIn; + diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp new file mode 100644 index 00000000..36b62c8c --- /dev/null +++ b/src/output/output_ebml.cpp @@ -0,0 +1,463 @@ +#include "output_ebml.h" +#include +#include + +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::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::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::iterator it = selectedTracks.begin(); + it != selectedTracks.end(); it++){ + trackSizes += sizeElemTrackEntry(myMeta.tracks[*it]); + } + EBML::sendElemHead(myConn, EBML::EID_TRACKS, trackSizes); + for (std::set::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::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::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::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::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::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 + diff --git a/src/output/output_ebml.h b/src/output/output_ebml.h new file mode 100644 index 00000000..5e977be2 --- /dev/null +++ b/src/output/output_ebml.h @@ -0,0 +1,34 @@ +#include "output_http.h" + +namespace Mist{ + class OutEBML : public HTTPOutput{ + public: + OutEBML(Socket::Connection &conn); + static void init(Util::Config *cfg); + void onHTTP(); + void sendNext(); + void sendHeader(); + uint32_t clusterSize(uint64_t start, uint64_t end); + + private: + std::string doctype; + void sendElemTrackEntry(const DTSC::Track & Trk); + uint32_t sizeElemTrackEntry(const DTSC::Track & Trk); + std::string trackCodecID(const DTSC::Track & Trk); + uint64_t currentClusterTime; + uint64_t newClusterTime; + //VoD-only + void calcVodSizes(); + uint64_t segmentSize;//size of complete segment contents (excl. header) + uint32_t tracksSize;//size of Tracks (incl. header) + uint32_t infoSize;//size of Info (incl. header) + 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) + void byteSeek(uint64_t startPos); + }; +} + +typedef Mist::OutEBML mistOut; +