EBML library, input and output, supports MKV and WebM.
This commit is contained in:
parent
105b1677d1
commit
a762932c45
11 changed files with 2141 additions and 0 deletions
|
@ -133,6 +133,8 @@ set(libHeaders
|
||||||
lib/util.h
|
lib/util.h
|
||||||
lib/vorbis.h
|
lib/vorbis.h
|
||||||
lib/opus.h
|
lib/opus.h
|
||||||
|
lib/ebml.h
|
||||||
|
lib/ebml_socketglue.h
|
||||||
)
|
)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -172,6 +174,8 @@ add_library (mist
|
||||||
lib/util.cpp
|
lib/util.cpp
|
||||||
lib/vorbis.cpp
|
lib/vorbis.cpp
|
||||||
lib/opus.cpp
|
lib/opus.cpp
|
||||||
|
lib/ebml.cpp
|
||||||
|
lib/ebml_socketglue.cpp
|
||||||
)
|
)
|
||||||
if (NOT APPLE)
|
if (NOT APPLE)
|
||||||
set (LIBRT -lrt)
|
set (LIBRT -lrt)
|
||||||
|
@ -246,6 +250,7 @@ makeAnalyser(FLV flv)
|
||||||
makeAnalyser(DTSC dtsc)
|
makeAnalyser(DTSC dtsc)
|
||||||
makeAnalyser(MP4 mp4)
|
makeAnalyser(MP4 mp4)
|
||||||
makeAnalyser(OGG ogg)
|
makeAnalyser(OGG ogg)
|
||||||
|
makeAnalyser(EBML ebml)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# MistServer - Utilities #
|
# MistServer - Utilities #
|
||||||
|
@ -302,6 +307,7 @@ makeInput(FLV flv)
|
||||||
makeInput(OGG ogg)
|
makeInput(OGG ogg)
|
||||||
makeInput(Buffer buffer)
|
makeInput(Buffer buffer)
|
||||||
makeInput(H264 h264)
|
makeInput(H264 h264)
|
||||||
|
makeInput(EBML ebml)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# MistServer - Outputs #
|
# MistServer - Outputs #
|
||||||
|
@ -351,6 +357,7 @@ makeOutput(JSON json http)
|
||||||
makeOutput(TS ts ts)
|
makeOutput(TS ts ts)
|
||||||
makeOutput(HTTPTS httpts http ts)
|
makeOutput(HTTPTS httpts http ts)
|
||||||
makeOutput(HLS hls http ts)
|
makeOutput(HLS hls http ts)
|
||||||
|
makeOutput(EBML ebml)
|
||||||
|
|
||||||
add_executable(MistOutHTTP
|
add_executable(MistOutHTTP
|
||||||
src/output/mist_out.cpp
|
src/output/mist_out.cpp
|
||||||
|
|
682
lib/ebml.cpp
Normal file
682
lib/ebml.cpp
Normal file
|
@ -0,0 +1,682 @@
|
||||||
|
#include "ebml.h"
|
||||||
|
#include "bitfields.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
118
lib/ebml.h
Normal file
118
lib/ebml.h
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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; ///<If set, ELEM_MASTER elements will not access payload data when
|
||||||
|
/// pretty-printing.
|
||||||
|
};
|
||||||
|
|
||||||
|
class Block : public Element{
|
||||||
|
public:
|
||||||
|
Block(const char *p = 0) : Element(p){}
|
||||||
|
uint64_t getTrackNum() const;
|
||||||
|
int16_t getTimecode() const;
|
||||||
|
bool isKeyframe() const;
|
||||||
|
bool isInvisible() const;
|
||||||
|
bool isDiscardable() const;
|
||||||
|
uint8_t getLacing() const;
|
||||||
|
uint8_t getFrameCount() const;
|
||||||
|
uint32_t getFrameSize(uint8_t no) const;
|
||||||
|
const char *getFrameData(uint8_t no) const;
|
||||||
|
virtual std::string toPrettyString(const uint8_t indent = 0, const uint8_t detail = 3) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
202
lib/ebml_socketglue.cpp
Normal file
202
lib/ebml_socketglue.cpp
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
#include "ebml_socketglue.h"
|
||||||
|
|
||||||
|
namespace EBML{
|
||||||
|
|
||||||
|
void sendUniInt(Socket::Connection &C, const uint64_t val){
|
||||||
|
uint8_t wSize = UniInt::writeSize(val);
|
||||||
|
if (!wSize){
|
||||||
|
C.SendNow("\377"); // Unknown size, all ones.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char tmp[8];
|
||||||
|
UniInt::writeInt(tmp, val);
|
||||||
|
C.SendNow(tmp, wSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t sizeElemHead(uint32_t ID, const uint64_t size){
|
||||||
|
uint8_t sLen = UniInt::writeSize(size);
|
||||||
|
return UniInt::writeSize(ID) + (sLen ? sLen : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sizeUInt(const uint64_t val){
|
||||||
|
if (val >= 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
33
lib/ebml_socketglue.h
Normal file
33
lib/ebml_socketglue.h
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
|
49
src/analysers/analyser_ebml.cpp
Normal file
49
src/analysers/analyser_ebml.cpp
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#include "analyser_ebml.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <mist/ebml.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
17
src/analysers/analyser_ebml.h
Normal file
17
src/analysers/analyser_ebml.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
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<uint64_t> depthStash;///<Contains bytes to read to go up a level in the element depth.
|
||||||
|
};
|
||||||
|
|
428
src/input/input_ebml.cpp
Normal file
428
src/input/input_ebml.cpp
Normal file
|
@ -0,0 +1,428 @@
|
||||||
|
#include "input_ebml.h"
|
||||||
|
#include <mist/defines.h>
|
||||||
|
#include <mist/ebml.h>
|
||||||
|
#include <mist/bitfields.h>
|
||||||
|
|
||||||
|
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<unsigned int, DTSC::Track>::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<uint64_t, trackPredictor>::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<unsigned int, DTSC::Track>::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<uint64_t, trackPredictor>::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<uint64_t, trackPredictor>::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
|
||||||
|
|
108
src/input/input_ebml.h
Normal file
108
src/input/input_ebml.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
#include "input.h"
|
||||||
|
#include <mist/util.h>
|
||||||
|
|
||||||
|
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<uint64_t, trackPredictor> packBuf;
|
||||||
|
std::set<uint64_t> swapEndianness;
|
||||||
|
bool readExistingHeader();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Mist::InputEBML mistIn;
|
||||||
|
|
463
src/output/output_ebml.cpp
Normal file
463
src/output/output_ebml.cpp
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
#include "output_ebml.h"
|
||||||
|
#include <mist/ebml_socketglue.h>
|
||||||
|
#include <mist/riff.h>
|
||||||
|
|
||||||
|
namespace Mist{
|
||||||
|
OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){
|
||||||
|
currentClusterTime = 0;
|
||||||
|
newClusterTime = 0;
|
||||||
|
segmentSize = 0xFFFFFFFFFFFFFFFFull;
|
||||||
|
tracksSize = 0;
|
||||||
|
infoSize = 0;
|
||||||
|
cuesSize = 0;
|
||||||
|
seekheadSize = 0;
|
||||||
|
doctype = "matroska";
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::init(Util::Config *cfg){
|
||||||
|
HTTPOutput::init(cfg);
|
||||||
|
capa["name"] = "EBML";
|
||||||
|
capa["desc"] = "Enables MKV and WebM streaming over HTTP.";
|
||||||
|
capa["url_rel"] = "/$.webm";
|
||||||
|
capa["url_match"].append("/$.mkv");
|
||||||
|
capa["url_match"].append("/$.webm");
|
||||||
|
capa["codecs"][0u][0u].append("H264");
|
||||||
|
capa["codecs"][0u][0u].append("HEVC");
|
||||||
|
capa["codecs"][0u][0u].append("VP8");
|
||||||
|
capa["codecs"][0u][0u].append("VP9");
|
||||||
|
capa["codecs"][0u][0u].append("theora");
|
||||||
|
capa["codecs"][0u][0u].append("MPEG2");
|
||||||
|
capa["codecs"][0u][1u].append("AAC");
|
||||||
|
capa["codecs"][0u][1u].append("vorbis");
|
||||||
|
capa["codecs"][0u][1u].append("opus");
|
||||||
|
capa["codecs"][0u][1u].append("PCM");
|
||||||
|
capa["codecs"][0u][1u].append("ALAW");
|
||||||
|
capa["codecs"][0u][1u].append("ULAW");
|
||||||
|
capa["codecs"][0u][1u].append("MP2");
|
||||||
|
capa["codecs"][0u][1u].append("MP3");
|
||||||
|
capa["codecs"][0u][1u].append("FLOAT");
|
||||||
|
capa["codecs"][0u][1u].append("AC3");
|
||||||
|
capa["methods"][0u]["handler"] = "http";
|
||||||
|
capa["methods"][0u]["type"] = "html5/video/webm";
|
||||||
|
capa["methods"][0u]["priority"] = 8ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the size of a Cluster (contents only) and returns it.
|
||||||
|
/// Bases the calculation on the currently selected tracks and the given start/end time for the cluster.
|
||||||
|
uint32_t OutEBML::clusterSize(uint64_t start, uint64_t end){
|
||||||
|
uint32_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start);
|
||||||
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
||||||
|
it != selectedTracks.end(); it++){
|
||||||
|
DTSC::Track &thisTrack = myMeta.tracks[*it];
|
||||||
|
uint32_t firstPart = 0;
|
||||||
|
unsigned long long int prevParts = 0;
|
||||||
|
uint64_t curMS = 0;
|
||||||
|
for (std::deque<DTSC::Key>::iterator it2 = thisTrack.keys.begin();
|
||||||
|
it2 != thisTrack.keys.end(); it2++){
|
||||||
|
if (it2->getTime() > start && it2 != thisTrack.keys.begin()){break;}
|
||||||
|
firstPart += prevParts;
|
||||||
|
prevParts = it2->getParts();
|
||||||
|
curMS = it2->getTime();
|
||||||
|
}
|
||||||
|
size_t maxParts = thisTrack.parts.size();
|
||||||
|
for (size_t i = firstPart; i < maxParts; i++){
|
||||||
|
if (curMS >= end){break;}
|
||||||
|
if (curMS >= start){
|
||||||
|
uint32_t blkLen = EBML::sizeSimpleBlock(thisTrack.trackID, thisTrack.parts[i].getSize());
|
||||||
|
sendLen += blkLen;
|
||||||
|
}
|
||||||
|
curMS += thisTrack.parts[i].getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sendLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::sendNext(){
|
||||||
|
if (thisPacket.getTime() >= newClusterTime){
|
||||||
|
currentClusterTime = thisPacket.getTime();
|
||||||
|
if (myMeta.vod){
|
||||||
|
//In case of VoD, clusters are aligned with the main track fragments
|
||||||
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
|
uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
|
||||||
|
newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration();
|
||||||
|
//The last fragment should run until the end of time
|
||||||
|
if (fragIndice == Trk.fragments.size() - 1){
|
||||||
|
newClusterTime = 0xFFFFFFFFFFFFFFFFull;
|
||||||
|
}
|
||||||
|
EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
|
||||||
|
}else{
|
||||||
|
//In live, clusters are aligned with the lookAhead time
|
||||||
|
newClusterTime += needsLookAhead;
|
||||||
|
}
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_CLUSTER, clusterSize(currentClusterTime, newClusterTime));
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TIMECODE, currentClusterTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
EBML::sendSimpleBlock(myConn, thisPacket, currentClusterTime,
|
||||||
|
myMeta.tracks[thisPacket.getTrackId()].type != "video");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OutEBML::trackCodecID(const DTSC::Track &Trk){
|
||||||
|
if (Trk.codec == "opus"){return "A_OPUS";}
|
||||||
|
if (Trk.codec == "H264"){return "V_MPEG4/ISO/AVC";}
|
||||||
|
if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
|
||||||
|
if (Trk.codec == "VP8"){return "V_VP8";}
|
||||||
|
if (Trk.codec == "VP9"){return "V_VP9";}
|
||||||
|
if (Trk.codec == "AAC"){return "A_AAC";}
|
||||||
|
if (Trk.codec == "vorbis"){return "A_VORBIS";}
|
||||||
|
if (Trk.codec == "theora"){return "V_THEORA";}
|
||||||
|
if (Trk.codec == "MPEG2"){return "V_MPEG2";}
|
||||||
|
if (Trk.codec == "PCM"){return "A_PCM/INT/BIG";}
|
||||||
|
if (Trk.codec == "MP2"){return "A_MPEG/L2";}
|
||||||
|
if (Trk.codec == "MP3"){return "A_MPEG/L3";}
|
||||||
|
if (Trk.codec == "AC3"){return "A_AC3";}
|
||||||
|
if (Trk.codec == "ALAW"){return "A_MS/ACM";}
|
||||||
|
if (Trk.codec == "ULAW"){return "A_MS/ACM";}
|
||||||
|
if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
|
||||||
|
return "E_UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::sendElemTrackEntry(const DTSC::Track &Trk){
|
||||||
|
// First calculate the sizes of the TrackEntry and Audio/Video elements.
|
||||||
|
uint32_t sendLen = 0;
|
||||||
|
uint32_t subLen = 0;
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID);
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID);
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk));
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
|
||||||
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000'));
|
||||||
|
}else{
|
||||||
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
|
}
|
||||||
|
if (Trk.type == "video"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
||||||
|
}
|
||||||
|
if (Trk.type == "audio"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels);
|
||||||
|
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
||||||
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
||||||
|
}
|
||||||
|
sendLen += subLen;
|
||||||
|
|
||||||
|
// Now actually send.
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_TRACKENTRY, sendLen);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKNUMBER, Trk.trackID);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKUID, Trk.trackID);
|
||||||
|
EBML::sendElemStr(myConn, EBML::EID_CODECID, trackCodecID(Trk));
|
||||||
|
EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_FLAGLACING, 0);
|
||||||
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
||||||
|
std::string init =
|
||||||
|
RIFF::fmt::generate(((Trk.codec == "ALAW") ? 6 : 7), Trk.channels, Trk.rate, Trk.bps,
|
||||||
|
Trk.channels * (Trk.size << 3), Trk.size);
|
||||||
|
EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, init.substr(8));
|
||||||
|
}else{
|
||||||
|
if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
|
}
|
||||||
|
if (Trk.type == "video"){
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
}
|
||||||
|
if (Trk.type == "audio"){
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2);
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_AUDIO, subLen);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_CHANNELS, Trk.channels);
|
||||||
|
EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
||||||
|
EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
|
||||||
|
// Calculate the sizes of the TrackEntry and Audio/Video elements.
|
||||||
|
uint32_t sendLen = 0;
|
||||||
|
uint32_t subLen = 0;
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, Trk.trackID);
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, Trk.trackID);
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(Trk));
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, Trk.lang.size() ? Trk.lang : "und");
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
|
||||||
|
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
|
||||||
|
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, std::string((size_t)18, '\000'));
|
||||||
|
}else{
|
||||||
|
if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);}
|
||||||
|
}
|
||||||
|
if (Trk.type == "video"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
|
||||||
|
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
|
||||||
|
}
|
||||||
|
if (Trk.type == "audio"){
|
||||||
|
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 2);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, Trk.channels);
|
||||||
|
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, Trk.rate);
|
||||||
|
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size);
|
||||||
|
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
|
||||||
|
}
|
||||||
|
sendLen += subLen;
|
||||||
|
return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::sendHeader(){
|
||||||
|
double duration = 0;
|
||||||
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
|
if (myMeta.vod){
|
||||||
|
duration = Trk.lastms - Trk.firstms;
|
||||||
|
}
|
||||||
|
if (myMeta.live){
|
||||||
|
needsLookAhead = 420;
|
||||||
|
}
|
||||||
|
//EBML header and Segment
|
||||||
|
EBML::sendElemEBML(myConn, doctype);
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_SEGMENT, segmentSize); // Default = Unknown size
|
||||||
|
if (myMeta.vod){
|
||||||
|
//SeekHead
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_SEEKHEAD, seekSize);
|
||||||
|
EBML::sendElemSeek(myConn, EBML::EID_INFO, seekheadSize);
|
||||||
|
EBML::sendElemSeek(myConn, EBML::EID_TRACKS, seekheadSize + infoSize);
|
||||||
|
EBML::sendElemSeek(myConn, EBML::EID_CUES, seekheadSize + infoSize + tracksSize);
|
||||||
|
}
|
||||||
|
//Info
|
||||||
|
EBML::sendElemInfo(myConn, "MistServer " PACKAGE_VERSION, duration);
|
||||||
|
//Tracks
|
||||||
|
uint32_t trackSizes = 0;
|
||||||
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
||||||
|
it != selectedTracks.end(); it++){
|
||||||
|
trackSizes += sizeElemTrackEntry(myMeta.tracks[*it]);
|
||||||
|
}
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_TRACKS, trackSizes);
|
||||||
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
||||||
|
it != selectedTracks.end(); it++){
|
||||||
|
sendElemTrackEntry(myMeta.tracks[*it]);
|
||||||
|
}
|
||||||
|
if (myMeta.vod){
|
||||||
|
EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
|
||||||
|
uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
||||||
|
uint32_t fragNo = 0;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
||||||
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
||||||
|
//The first fragment always starts at time 0, even if the main track does not.
|
||||||
|
if (!fragNo){clusterStart = 0;}
|
||||||
|
EBML::sendElemCuePoint(myConn, clusterStart, Trk.trackID, tmpsegSize, 0);
|
||||||
|
tmpsegSize += clusterSizes[fragNo];
|
||||||
|
++fragNo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sentHeader = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seeks to the given byte position by doing a regular seek and remembering the byte offset from that point
|
||||||
|
void OutEBML::byteSeek(uint64_t startPos){
|
||||||
|
INFO_MSG("Seeking to %llu bytes", startPos);
|
||||||
|
sentHeader = false;
|
||||||
|
newClusterTime = 0;
|
||||||
|
if (startPos == 0){
|
||||||
|
seek(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t headerSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + seekheadSize + infoSize + tracksSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize) + cuesSize;
|
||||||
|
if (startPos < headerSize){
|
||||||
|
HIGH_MSG("Seek went into or before header");
|
||||||
|
seek(0);
|
||||||
|
myConn.skipBytes(startPos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startPos -= headerSize;
|
||||||
|
sentHeader = true;//skip the header
|
||||||
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
|
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
|
||||||
|
VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos);
|
||||||
|
if (startPos < it->second){
|
||||||
|
HIGH_MSG("Seek to fragment %llu (%llu ms)", it->first, Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
||||||
|
myConn.skipBytes(startPos);
|
||||||
|
seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime());
|
||||||
|
newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startPos -= it->second;
|
||||||
|
}
|
||||||
|
//End of file. This probably won't work right, but who cares, it's the end of the file.
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::onHTTP(){
|
||||||
|
std::string method = H.method;
|
||||||
|
if(method == "OPTIONS" || method == "HEAD"){
|
||||||
|
H.Clean();
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetHeader("Content-Type", "video/MP4");
|
||||||
|
H.SetHeader("Accept-Ranges", "bytes, parsec");
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (H.url.find(".webm") != std::string::npos){
|
||||||
|
doctype = "webm";
|
||||||
|
}else{
|
||||||
|
doctype = "matroska";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Calculate the sizes of various parts, if we're VoD.
|
||||||
|
uint64_t totalSize = 0;
|
||||||
|
if (myMeta.vod){
|
||||||
|
calcVodSizes();
|
||||||
|
//We now know the full size of the segment, thus can calculate the total size
|
||||||
|
totalSize = EBML::sizeElemEBML(doctype) + EBML::sizeElemHead(EBML::EID_SEGMENT, segmentSize) + segmentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t byteEnd = totalSize-1;
|
||||||
|
uint64_t byteStart = 0;
|
||||||
|
|
||||||
|
/*LTS-START*/
|
||||||
|
// allow setting of max lead time through buffer variable.
|
||||||
|
// max lead time is set in MS, but the variable is in integer seconds for simplicity.
|
||||||
|
if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;}
|
||||||
|
//allow setting of play back rate through buffer variable.
|
||||||
|
//play back rate is set in MS per second, but the variable is a simple multiplier.
|
||||||
|
if (H.GetVar("rate") != ""){
|
||||||
|
long long int multiplier = JSON::Value(H.GetVar("rate")).asInt();
|
||||||
|
if (multiplier){
|
||||||
|
realTime = 1000 / multiplier;
|
||||||
|
}else{
|
||||||
|
realTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (H.GetHeader("X-Mist-Rate") != ""){
|
||||||
|
long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt();
|
||||||
|
if (multiplier){
|
||||||
|
realTime = 1000 / multiplier;
|
||||||
|
}else{
|
||||||
|
realTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*LTS-END*/
|
||||||
|
|
||||||
|
char rangeType = ' ';
|
||||||
|
if (!myMeta.live){
|
||||||
|
if (H.GetHeader("Range") != ""){
|
||||||
|
if (parseRange(byteStart, byteEnd)){
|
||||||
|
if (H.GetVar("buffer") == ""){
|
||||||
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
|
maxSkipAhead = (Trk.lastms - Trk.firstms) / 20 + 7500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rangeType = H.GetHeader("Range")[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H.Clean(); //make sure no parts of old requests are left in any buffers
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetHeader("Content-Type", "video/webm");
|
||||||
|
if (myMeta.vod){
|
||||||
|
H.SetHeader("Accept-Ranges", "bytes, parsec");
|
||||||
|
}
|
||||||
|
if (rangeType != ' '){
|
||||||
|
if (!byteEnd){
|
||||||
|
if (rangeType == 'p'){
|
||||||
|
H.SetBody("Starsystem not in communications range");
|
||||||
|
H.SendResponse("416", "Starsystem not in communications range", myConn);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
H.SetBody("Requested Range Not Satisfiable");
|
||||||
|
H.SendResponse("416", "Requested Range Not Satisfiable", myConn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
std::stringstream rangeReply;
|
||||||
|
rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize;
|
||||||
|
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
||||||
|
H.SetHeader("Content-Range", rangeReply.str());
|
||||||
|
/// \todo Switch to chunked?
|
||||||
|
H.SendResponse("206", "Partial content", myConn);
|
||||||
|
//H.StartResponse("206", "Partial content", HTTP_R, conn);
|
||||||
|
byteSeek(byteStart);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (myMeta.vod){
|
||||||
|
H.SetHeader("Content-Length", byteEnd - byteStart + 1);
|
||||||
|
}
|
||||||
|
/// \todo Switch to chunked?
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
//HTTP_S.StartResponse(HTTP_R, conn);
|
||||||
|
}
|
||||||
|
parseData = true;
|
||||||
|
wantRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutEBML::calcVodSizes(){
|
||||||
|
if (segmentSize != 0xFFFFFFFFFFFFFFFFull){
|
||||||
|
//Already calculated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
|
||||||
|
double duration = Trk.lastms - Trk.firstms;
|
||||||
|
//Calculate the segment size
|
||||||
|
//Segment contains SeekHead, Info, Tracks, Cues (in that order)
|
||||||
|
//Howeveer, SeekHead is dependent on Info/Tracks sizes, so we calculate those first.
|
||||||
|
//Calculating Info size
|
||||||
|
infoSize = EBML::sizeElemInfo("MistServer " PACKAGE_VERSION, duration);
|
||||||
|
//Calculating Tracks size
|
||||||
|
tracksSize = 0;
|
||||||
|
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
|
||||||
|
it != selectedTracks.end(); it++){
|
||||||
|
tracksSize += sizeElemTrackEntry(myMeta.tracks[*it]);
|
||||||
|
}
|
||||||
|
tracksSize += EBML::sizeElemHead(EBML::EID_TRACKS, tracksSize);
|
||||||
|
//Calculating SeekHead size
|
||||||
|
//Positions are relative to the first Segment, byte 0 = first byte of contents of Segment.
|
||||||
|
//Tricky starts here: the size of the SeekHead element is dependent on the seek offsets contained inside,
|
||||||
|
//which are in turn dependent on the size of the SeekHead element. Fun times! We loop until it stabilizes.
|
||||||
|
uint32_t oldseekSize = 0;
|
||||||
|
do {
|
||||||
|
oldseekSize = seekSize;
|
||||||
|
seekSize = EBML::sizeElemSeek(EBML::EID_INFO, seekheadSize) +
|
||||||
|
EBML::sizeElemSeek(EBML::EID_TRACKS, seekheadSize + infoSize) +
|
||||||
|
EBML::sizeElemSeek(EBML::EID_CUES, seekheadSize + infoSize + tracksSize);
|
||||||
|
seekheadSize = EBML::sizeElemHead(EBML::EID_SEEKHEAD, seekSize) + seekSize;
|
||||||
|
}while(seekSize != oldseekSize);
|
||||||
|
//The Cues are tricky: the Cluster offsets are dependent on the size of Cues itself.
|
||||||
|
//Which, in turn, is dependent on the Cluster offsets.
|
||||||
|
//We make this a bit easier by pre-calculating the sizes of all clusters first
|
||||||
|
uint64_t fragNo = 0;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
||||||
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
||||||
|
uint64_t clusterEnd = clusterStart + it->getDuration();
|
||||||
|
//The first fragment always starts at time 0, even if the main track does not.
|
||||||
|
if (!fragNo){clusterStart = 0;}
|
||||||
|
//The last fragment always ends at the end, even if the main track does not.
|
||||||
|
if (fragNo == Trk.fragments.size() - 1){clusterEnd = 0xFFFFFFFFFFFFFFFFull;}
|
||||||
|
uint64_t cSize = clusterSize(clusterStart, clusterEnd);
|
||||||
|
clusterSizes[fragNo] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize);
|
||||||
|
++fragNo;
|
||||||
|
}
|
||||||
|
//Calculating Cues size
|
||||||
|
//We also calculate Clusters here: Clusters are grouped by fragments of the main track.
|
||||||
|
//CueClusterPosition uses the same offsets as SeekPosition.
|
||||||
|
//CueRelativePosition is the offset from that Cluster's first content byte.
|
||||||
|
//All this uses the same technique as above. More fun times!
|
||||||
|
uint32_t oldcuesSize = 0;
|
||||||
|
do {
|
||||||
|
oldcuesSize = cuesSize;
|
||||||
|
segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
|
||||||
|
uint32_t cuesInside = 0;
|
||||||
|
fragNo = 0;
|
||||||
|
for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){
|
||||||
|
uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime();
|
||||||
|
//The first fragment always starts at time 0, even if the main track does not.
|
||||||
|
if (!fragNo){clusterStart = 0;}
|
||||||
|
cuesInside += EBML::sizeElemCuePoint(clusterStart, Trk.trackID, segmentSize, 0);
|
||||||
|
segmentSize += clusterSizes[fragNo];
|
||||||
|
++fragNo;
|
||||||
|
}
|
||||||
|
cuesSize = cuesInside;
|
||||||
|
}while(cuesSize != oldcuesSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
}// namespace Mist
|
||||||
|
|
34
src/output/output_ebml.h
Normal file
34
src/output/output_ebml.h
Normal file
|
@ -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<uint64_t, uint64_t> clusterSizes;//sizes of Clusters (incl. header)
|
||||||
|
void byteSeek(uint64_t startPos);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Mist::OutEBML mistOut;
|
||||||
|
|
Loading…
Add table
Reference in a new issue