Merge branch 'development' into LTS_development

# Conflicts:
#	CMakeLists.txt
This commit is contained in:
Thulinma 2018-01-24 20:16:35 +01:00
commit 26cd8251e2
11 changed files with 2141 additions and 0 deletions

View 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);
}

View 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
View 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
View 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
View 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
View 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;