New Meta commit

This commit is contained in:
Phencys 2021-04-21 18:10:03 +02:00 committed by Thulinma
parent fccf66fba2
commit 2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions

View file

@ -67,10 +67,10 @@ int Analyser::run(Util::Config &conf){
if (validate && ((finTime - upTime + 10) * 1000 < mediaTime)){
uint32_t sleepMs = mediaTime - (Util::bootSecs() - upTime + 10) * 1000;
if ((finTime - upTime + sleepMs / 1000) >= timeOut){
WARN_MSG("Reached timeout of %llu seconds, stopping", timeOut);
WARN_MSG("Reached timeout of %" PRIu64 " seconds, stopping", timeOut);
return 3;
}
INFO_MSG("Sleeping for %lums", sleepMs);
INFO_MSG("Sleeping for %" PRIu32 "ms", sleepMs);
Util::sleep(sleepMs);
finTime = Util::bootSecs();
}
@ -80,7 +80,7 @@ int Analyser::run(Util::Config &conf){
return 4;
}
if ((finTime - upTime) >= timeOut){
WARN_MSG("Reached timeout of %llu seconds, stopping", timeOut);
WARN_MSG("Reached timeout of %" PRIu64 " seconds, stopping", timeOut);
return 3;
}
}

View file

@ -30,17 +30,14 @@ bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size
offset--;
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
offset = blockStart + 1; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
blockEnd++; // include delim
// DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
@ -50,15 +47,12 @@ bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size
return false; // name string not found.
}
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
blockStart++; // clip off quote characters
offset = blockStart; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
// DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
@ -67,23 +61,17 @@ bool getString(std::string &data, std::string name, std::string &output){
size_t blockEnd = 0;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) )
output = data.substr(blockStart, (blockEnd - blockStart));
// looks like this function is working as expected
// DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str());
return true;
}
bool getLong(std::string &data, std::string name, long &output){
size_t blockStart, blockEnd;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str());
output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str());
return true;
}
@ -96,7 +84,7 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar
}
if (blockStart == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block start found for name: %s at offset: %i", name.c_str(), offset);
INFO_MSG("no block start found for name: %s at offset: %i", name.c_str(), offset);
return false;
}
@ -104,75 +92,71 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar
if (blockEnd == std::string::npos){
blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block end found.");
INFO_MSG("no block end found.");
return false;
}
size_t temp = data.find("<", blockStart + 1, (blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
size_t temp = data.find("<", blockStart + 1,
(blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
if (temp != std::string::npos){// all info is epxected between <name ... />
DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str());
FAIL_MSG("block start found before block end. offset: %lu block: %s", temp, data.c_str());
return false;
}
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after />
}else{
blockEnd += name.size() + 2; // position after /name>
}
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true;
}
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if (!getString(
data, "mimeType",
mimeType)){// get first occurence of mimeType. --> this will break if multiple mimetypes
// should be read from this block because no offset is provided. solution:
// use this on a substring containing the desired information.
DEBUG_MSG(DLVL_FAIL, "mimeType not found");
if (!getString(data, "mimeType",
mimeType)){// get first occurence of mimeType. --> this will break if multiple mimetypes
// should be read from this block because no offset is provided. solution:
// use this on a substring containing the desired information.
FAIL_MSG("mimeType not found");
return false;
}
DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK
INFO_MSG("mimeType: %s", mimeType.c_str()); // checked, OK
if (mimeType.find("video") != std::string::npos){tempSD.trackType = VIDEO;}
if (mimeType.find("audio") != std::string::npos){tempSD.trackType = AUDIO;}
if (tempSD.trackType == OTHER){
DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up.");
FAIL_MSG("no audio or video type found. giving up.");
return false;
}
// find an ID within this adaptationSet block.
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "Representation not found");
FAIL_MSG("Representation not found");
return false;
}
// representation string
std::string block = data.substr(blockStart, (blockEnd - blockStart));
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str());
// check if block is not junk?
INFO_MSG("Representation block: %s", block.c_str());
///\todo check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)){
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str());
FAIL_MSG("Representation id not found in block %s", block.c_str());
return false;
}
DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK
INFO_MSG("Representation/id: %li", tempSD.trackID); // checked, OK
offset = 0;
// get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found");
FAIL_MSG("SegmentTemplate not found");
return false;
}
block = data.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK
getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media);
@ -181,20 +165,19 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
FAIL_MSG("Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str());
FAIL_MSG("Failed to find and replace $Time$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s",
tempSD.initialization.c_str());
FAIL_MSG("Failed to find and replace $RepresentationID$ in %s", tempSD.initialization.c_str());
return false;
}
tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
@ -202,12 +185,12 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// get segment timeline block from within segment template:
size_t blockOffset = 0; // offset should be 0 because this is a new block
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found");
FAIL_MSG("SegmentTimeline block not found");
return false;
}
std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
// DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK
std::string block2 = block.substr(blockStart,
(blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
int numS = 0;
offset = 0;
@ -216,10 +199,10 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
FAIL_MSG("no S found within SegmentTimeline");
return false;
}else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
INFO_MSG("all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true)
}
}
@ -227,16 +210,14 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// stuff S data into: currentPos
// searching for t(start position)
std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK!
if (getLong(sBlock, "t", timeValue)){
totalDuration = timeValue; // reset totalDuration to value of t
}
if (!getLong(sBlock, "d", timeValue)){// expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S");
FAIL_MSG("no d found within S");
return false;
}
// stuff data with old value (start of block)
// DEBUG_MSG(DLVL_INFO, "stuffing info from S into set");
seekPos thisPos;
thisPos.trackType = tempSD.trackType;
thisPos.trackID = tempSD.trackID;
@ -249,7 +230,6 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
static char charBuf[512];
snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration);
thisPos.url.assign(charBuf);
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration
@ -265,30 +245,30 @@ bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<Stre
size_t currentOffset = 0;
size_t adaptationSetStart;
size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)){
tempSD.adaptationSet = numAdaptationSet;
numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart,
adaptationSetEnd, (adaptationSetEnd - adaptationSetStart));
INFO_MSG("adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart,
adaptationSetEnd, (adaptationSetEnd - adaptationSetStart));
// get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart));
// function was verified: output as expected.
if (!parseAdaptationSet(adaptationSet, currentPos)){
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype. in that case it might be
// desirable to continue searching for valid data instead of quitting.
FAIL_MSG("parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype.
// in that case it might be desirable to continue
// searching for valid data instead of quitting.
return false;
}
streamData.push_back(tempSD); // put temp values into adaptation set vector
currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset.
}
if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
FAIL_MSG("no adaptationSet found.");
return false;
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
INFO_MSG("all adaptation sets found. total: %i", numAdaptationSet);
return true;
}
@ -297,7 +277,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
url = conf.getString("url");
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
FAIL_MSG("The URL must start with http://");
// return -1;
exit(1);
}
@ -329,9 +309,9 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
}
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
INFO_MSG("url %s server: %s port: %d", url.c_str(), server.c_str(), port);
urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
INFO_MSG("prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);}
pos = 0;
@ -346,13 +326,8 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
currentPos;
streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
FAIL_MSG("Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
@ -362,30 +337,30 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
INFO_MSG("*********");
INFO_MSG("*SUMMARY*");
INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
INFO_MSG("num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
INFO_MSG("");
INFO_MSG("ID in vector %d", i);
INFO_MSG("trackID %ld", streamData[i].trackID);
INFO_MSG("adaptationSet %d", streamData[i].adaptationSet);
INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
INFO_MSG("TimeScale %ld", streamData[i].timeScale);
INFO_MSG("Media string %s", streamData[i].media.c_str());
INFO_MSG("Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
INFO_MSG("");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ",
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str());
INFO_MSG("init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet,
streamData[i].trackID, streamData[i].initURL.c_str());
}
}
@ -403,8 +378,7 @@ dashAnalyser::~dashAnalyser(){
int dashAnalyser::doAnalyse(){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
// match adaptation set and track id?
///\todo match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID &&
@ -415,20 +389,16 @@ int dashAnalyser::doAnalyse(){
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(),
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration);
INFO_MSG("Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
// break;
FAIL_MSG("No data downloaded from %s", H.url.c_str());
return 0;
}
@ -440,17 +410,14 @@ int dashAnalyser::doAnalyse(){
}
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
// break;
FAIL_MSG("No mdat present. Sadface. :-(");
return 0;
}
if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
FAIL_MSG("%lu bytes left in body. Assuming horrible things...",
H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){
// break;
return 0;
}
if (beforeParse == H.body.size()){return 0;}
}
H.Clean();
@ -516,7 +483,7 @@ int main2(int argc, char **argv){
std::string url = conf.getString("url");
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
FAIL_MSG("The URL must start with http://");
return -1;
}
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
@ -535,9 +502,9 @@ int main2(int argc, char **argv){
Socket::Connection conn(server, port, false);
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
INFO_MSG("url %s server: %s port: %d", url.c_str(), server.c_str(), port);
std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
INFO_MSG("prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);}
unsigned int pos = 0;
HTTP::Parser H;
@ -551,13 +518,8 @@ int main2(int argc, char **argv){
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
FAIL_MSG("Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
@ -566,37 +528,36 @@ int main2(int argc, char **argv){
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
INFO_MSG("*********");
INFO_MSG("*SUMMARY*");
INFO_MSG("*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
INFO_MSG("num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
INFO_MSG("");
INFO_MSG("ID in vector %d", i);
INFO_MSG("trackID %ld", streamData[i].trackID);
INFO_MSG("adaptationSet %d", streamData[i].adaptationSet);
INFO_MSG("trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
INFO_MSG("TimeScale %ld", streamData[i].timeScale);
INFO_MSG("Media string %s", streamData[i].media.c_str());
INFO_MSG("Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
INFO_MSG("");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ",
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str());
INFO_MSG("init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet,
streamData[i].trackID, streamData[i].initURL.c_str());
}
while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
std::cout << "blaa" << std::endl;
// match adaptation set and track id?
///\todo match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID &&
@ -607,18 +568,15 @@ int main2(int argc, char **argv){
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(),
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration);
INFO_MSG("Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
FAIL_MSG("No data downloaded from %s", H.url.c_str());
break;
}
size_t beforeParse = H.body.size();
@ -628,11 +586,12 @@ int main2(int argc, char **argv){
if (mp4Data.isType("mdat")){mdatSeen = true;}
}
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
FAIL_MSG("No mdat present. Sadface. :-(");
break;
}
if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
FAIL_MSG("%lu bytes left in body. Assuming horrible things...",
H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){break;}
}

View file

@ -35,7 +35,7 @@ bool AnalyserDTSC::parsePacket(){
}
P.reInit(conn);
if (conn && !P){
FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes)
FAIL_MSG("Invalid DTSC packet @ byte %" PRIu64, totalBytes)
return false;
}
if (!conn && !P){
@ -70,8 +70,7 @@ bool AnalyserDTSC::parsePacket(){
case DTSC::DTSC_HEAD:{
if (detail >= 3){
std::cout << "DTSC header: ";
DTSC::Meta(P).toPrettyString(
std::cout, 0, (detail == 3 ? 0 : (detail == 4 ? 0x01 : (detail == 5 ? 0x03 : 0x07))));
std::cout << DTSC::Meta("", P.getScan()).toPrettyString();
}
if (detail == 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;}
if (detail == 1){
@ -79,30 +78,34 @@ bool AnalyserDTSC::parsePacket(){
bool hasAAC = false;
JSON::Value result;
std::stringstream issues;
DTSC::Meta M(P);
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin(); it != M.tracks.end(); it++){
DTSC::Meta M("", P.getScan());
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
std::string codec = M.getCodec(*it);
JSON::Value track;
track["kbits"] = (uint64_t)((double)it->second.bps * 8 / 1024);
track["codec"] = it->second.codec;
track["kbits"] = M.getBps(*it) * 8 / 1024;
track["codec"] = codec;
uint32_t shrtest_key = 0xFFFFFFFFul;
uint32_t longest_key = 0;
uint32_t shrtest_prt = 0xFFFFFFFFul;
uint32_t longest_prt = 0;
uint32_t shrtest_cnt = 0xFFFFFFFFul;
uint32_t longest_cnt = 0;
for (std::deque<DTSC::Key>::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){
if (!k->getLength()){continue;}
if (k->getLength() > longest_key){longest_key = k->getLength();}
if (k->getLength() < shrtest_key){shrtest_key = k->getLength();}
if (k->getParts() > longest_cnt){longest_cnt = k->getParts();}
if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();}
if (k->getParts()){
if ((k->getLength() / k->getParts()) > longest_prt){
longest_prt = (k->getLength() / k->getParts());
}
if ((k->getLength() / k->getParts()) < shrtest_prt){
shrtest_prt = (k->getLength() / k->getParts());
}
DTSC::Keys keys(M.keys(*it));
uint32_t firstKey = keys.getFirstValid();
uint32_t endKey = keys.getEndValid();
for (int i = firstKey; i < endKey; i++){
uint64_t keyDuration = keys.getDuration(i);
uint64_t keyParts = keys.getParts(i);
if (!keyDuration){continue;}
if (keyDuration > longest_key){longest_key = keyDuration;}
if (keyDuration < shrtest_key){shrtest_key = keyDuration;}
if (keyParts > longest_cnt){longest_cnt = keyParts;}
if (keyParts < shrtest_cnt){shrtest_cnt = keyParts;}
if (keyParts){
if ((keyDuration / keyParts) > longest_prt){longest_prt = (keyDuration / keyParts);}
if ((keyDuration / keyParts) < shrtest_prt){shrtest_prt = (keyDuration / keyParts);}
}
}
track["keys"]["ms_min"] = shrtest_key;
@ -112,28 +115,28 @@ bool AnalyserDTSC::parsePacket(){
track["keys"]["frames_min"] = shrtest_cnt;
track["keys"]["frames_max"] = longest_cnt;
if (longest_prt > 500){
issues << "unstable connection (" << longest_prt << "ms " << it->second.codec << " frame)! ";
issues << "unstable connection (" << longest_prt << "ms " << codec << " frame)! ";
}
if (shrtest_cnt < 6){
issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec << " frames in key)! ";
issues << "unstable connection (" << shrtest_cnt << " " << codec << " frames in key)! ";
}
if (it->second.codec == "AAC"){hasAAC = true;}
if (it->second.codec == "H264"){hasH264 = true;}
if (it->second.type == "video"){
track["width"] = it->second.width;
track["height"] = it->second.height;
track["fpks"] = it->second.fpks;
if (it->second.codec == "H264"){
if (codec == "AAC"){hasAAC = true;}
if (codec == "H264"){hasH264 = true;}
if (M.getType(*it) == "video"){
track["width"] = M.getWidth(*it);
track["height"] = M.getHeight(*it);
track["fpks"] = M.getFpks(*it);
if (codec == "H264"){
h264::sequenceParameterSet sps;
sps.fromDTSCInit(it->second.init);
sps.fromDTSCInit(M.getInit(*it));
h264::SPSMeta spsData = sps.getCharacteristics();
track["h264"]["profile"] = spsData.profile;
track["h264"]["level"] = spsData.level;
}
}
result[it->second.getWritableIdentifier()] = track;
result[M.getTrackIdentifier(*it)] = track;
}
if ((hasAAC || hasH264) && M.tracks.size() > 1){
if (hasAAC || hasH264){
if (!hasAAC){issues << "HLS no audio!";}
if (!hasH264){issues << "HLS no video!";}
}
@ -147,7 +150,7 @@ bool AnalyserDTSC::parsePacket(){
if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;}
break;
}
default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break;
default: FAIL_MSG("Invalid DTSC packet @ byte %" PRIu64, totalBytes); break;
}
totalBytes += P.getDataLen();

View file

@ -35,7 +35,7 @@ bool AnalyserEBML::parsePacket(){
if (dataBuffer.size() < neededBytes()){return false;}
EBML::Element E(dataBuffer.data(), true);
HIGH_MSG("Read an element at position %d", prePos);
HIGH_MSG("Read an element at position %zu", prePos);
if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
switch (E.getID()){
case EBML::EID_SEGMENT:

View file

@ -11,7 +11,7 @@ private:
uint64_t neededBytes();
std::string dataBuffer;
uint64_t curPos;
uint64_t prePos;
size_t prePos;
uint64_t segmentOffset;
uint32_t lastSeekId;
uint64_t lastSeekPos;

View file

@ -32,7 +32,8 @@ bool AnalyserFLV::parsePacket(){
// If we arrive here, we've loaded a FLV packet
if (!filter || filter == flvData.data[0]){
DETAIL_MED("[%llu+%llu] %s", flvData.tagTime(), flvData.offset(), flvData.tagType().c_str());
DETAIL_MED("[%" PRIu64 "+%" PRId64 "] %s", flvData.tagTime(), flvData.offset(),
flvData.tagType().c_str());
}
mediaTime = flvData.tagTime();
return true;

View file

@ -9,5 +9,5 @@ public:
private:
FLV::Tag flvData;
long long filter;
int64_t filter;
};

View file

@ -39,7 +39,7 @@ bool AnalyserH264::parsePacket(){
size = 0;
nalPtr = h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended);
if (nalPtr){
HIGH_MSG("Read a %lu-byte NAL unit at position %llu", size, prePos);
HIGH_MSG("Read a %lu-byte NAL unit at position %" PRIu64, size, prePos);
if (detail >= 2){nalPtr->toPrettyString(std::cout);}
dataBuffer.erase(0, size); // erase the NAL unit we just read
prePos += size;
@ -47,7 +47,7 @@ bool AnalyserH264::parsePacket(){
///\TODO update mediaTime with current timestamp
}while (nalPtr);
if (!nalPtr){
FAIL_MSG("Could not read a NAL unit at position %llu", prePos);
FAIL_MSG("Could not read a NAL unit at position %" PRIu64, prePos);
return false;
}
return true;

View file

@ -40,7 +40,7 @@ void AnalyserHLS::getParts(const std::string &body){
}
if (!parsedPart || no > parsedPart){
HTTP::URL newURL = root.link(line);
INFO_MSG("Discovered #%llu: %s", no, newURL.getUrl().c_str());
INFO_MSG("Discovered #%" PRIu64 ": %s", no, newURL.getUrl().c_str());
parts.push_back(HLSPart(newURL, no, durat));
}
++no;
@ -111,7 +111,7 @@ bool AnalyserHLS::parsePacket(){
}
}
if (DL.data().size() % 188){
FAIL_MSG("Expected a multiple of 188 bytes, received %d bytes", DL.data().size());
FAIL_MSG("Expected a multiple of 188 bytes, received %zu bytes", DL.data().size());
return false;
}
parsedPart = part.no;
@ -124,8 +124,8 @@ bool AnalyserHLS::parsePacket(){
// Hm. I guess we had no parts to get.
if (refreshAt && refreshAt > Util::bootSecs()){
// We're getting a live stream. Let's wait and check again.
uint32_t sleepSecs = (refreshAt - Util::bootSecs());
INFO_MSG("Sleeping for %lu seconds", sleepSecs);
uint64_t sleepSecs = (refreshAt - Util::bootSecs());
INFO_MSG("Sleeping for %" PRIu64 " seconds", sleepSecs);
Util::sleep(sleepSecs * 1000);
}
// The non-live case is already handled in isOpen()

View file

@ -1,4 +1,5 @@
#include "analyser_mp4.h"
#include <mist/bitfields.h>
void AnalyserMP4::init(Util::Config &conf){
Analyser::init(conf);
@ -22,23 +23,21 @@ bool AnalyserMP4::parsePacket(){
}
if (mp4Data.read(mp4Buffer)){
INFO_MSG("Read a box at position %d", prePos);
INFO_MSG("Read a box at position %" PRIu64, prePos);
if (detail >= 2){std::cout << mp4Data.toPrettyString(0) << std::endl;}
///\TODO update mediaTime with the current timestamp
return true;
}
FAIL_MSG("Could not read box at position %llu", prePos);
FAIL_MSG("Could not read box at position %" PRIu64, prePos);
return false;
}
/// Calculates how many bytes we need to read a whole box.
uint64_t AnalyserMP4::neededBytes(){
if (mp4Buffer.size() < 4){return 4;}
uint64_t size = ntohl(((int *)mp4Buffer.data())[0]);
uint64_t size = Bit::btohl(mp4Buffer.data());
if (size != 1){return size;}
if (mp4Buffer.size() < 16){return 16;}
size = 0 + ntohl(((int *)mp4Buffer.data())[2]);
size <<= 32;
size += ntohl(((int *)mp4Buffer.data())[3]);
size = Bit::btohll(mp4Buffer.data() + 8);
return size;
}

View file

@ -28,18 +28,18 @@ bool AnalyserOGG::parsePacket(){
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus";
}
if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){
INFO_MSG("Bitstream %llu recognized as %s", oggPage.getBitstreamSerialNumber(),
INFO_MSG("Bitstream %" PRIu64 " recognized as %s", oggPage.getBitstreamSerialNumber(),
sn2Codec[oggPage.getBitstreamSerialNumber()].c_str());
}else{
WARN_MSG("Bitstream %llu not recognized!", oggPage.getBitstreamSerialNumber());
WARN_MSG("Bitstream %" PRIu64 " not recognized!", oggPage.getBitstreamSerialNumber());
}
}
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
if (detail >= 2){std::cout << " Theora data" << std::endl;}
static unsigned int numParts = 0;
static unsigned int keyCount = 0;
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
static size_t numParts = 0;
static size_t keyCount = 0;
for (size_t i = 0; i < oggPage.getAllSegments().size(); i++){
theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
if (tmpHeader.isHeader()){
if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();}

View file

@ -8,8 +8,8 @@ public:
static void init(Util::Config &conf);
private:
std::map<int, std::string> sn2Codec;
std::map<uint64_t, std::string> sn2Codec;
std::string oggBuffer;
OGG::Page oggPage;
int kfgshift;
uint16_t kfgshift;
};

View file

@ -26,7 +26,7 @@ bool AnalyserRIFF::parsePacket(){
if (dataBuffer.size() < 8){return false;}
RIFF::Chunk C(dataBuffer.data(), dataBuffer.size());
INFO_MSG("Read a chunk at position %d", prePos);
INFO_MSG("Read a chunk at position %" PRIu64, prePos);
if (detail >= 2){C.toPrettyString(std::cout);}
///\TODO update mediaTime with the current timestamp
if (C){

View file

@ -2,6 +2,7 @@
/// Debugging tool for RTMP data.
#include "analyser_rtmp.h"
#include <mist/bitfields.h>
void AnalyserRTMP::init(Util::Config &conf){
Analyser::init(conf);
@ -42,23 +43,19 @@ bool AnalyserRTMP::parsePacket(){
// While we can't parse a packet,
while (!next.Parse(strbuf)){
// fill our internal buffer "strbuf" in (up to) 1024 byte chunks
if (std::cin.good()){
unsigned int charCount = 0;
std::string tmpbuffer;
tmpbuffer.reserve(1024);
while (std::cin.good() && charCount < 1024){
char newchar = std::cin.get();
if (std::cin.good()){
tmpbuffer += newchar;
++read_in;
++charCount;
}
if (!std::cin.good()){return false;}
size_t charCount = 0;
std::string tmpbuffer;
tmpbuffer.reserve(1024);
while (std::cin.good() && charCount < 1024){
char newchar = std::cin.get();
if (std::cin.good()){
tmpbuffer += newchar;
++read_in;
++charCount;
}
strbuf.append(tmpbuffer);
}else{
// if we can't fill the buffer, and have no parsable packet(s), return false
return false;
}
strbuf.append(tmpbuffer);
}
// We now know for sure that we've parsed a packet
@ -72,71 +69,66 @@ bool AnalyserRTMP::parsePacket(){
break; // happens when connection breaks unexpectedly
case 1: // set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
DETAIL_MED("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
DETAIL_MED("CTRL: Set chunk size: %" PRIu64, RTMPStream::chunk_rec_max);
break;
case 2: // abort message - we ignore this one
DETAIL_MED("CTRL: Abort message: %i", ntohl(*(int *)next.data.c_str()));
DETAIL_MED("CTRL: Abort message: %" PRIu32, Bit::btohl(next.data.data()));
// 4 bytes of stream id to drop
break;
case 3: // ack
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at);
RTMPStream::snd_window_at = Bit::btohl(next.data.data());
DETAIL_MED("CTRL: Acknowledgement: %" PRIu64, RTMPStream::snd_window_at);
break;
case 4:{
short int ucmtype = ntohs(*(short int *)next.data.c_str());
int16_t ucmtype = Bit::btohs(next.data.data());
switch (ucmtype){
case 0:
DETAIL_MED("CTRL: User control message: stream begin %u",
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: stream begin %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 1:
DETAIL_MED("CTRL: User control message: stream EOF %u", ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: stream EOF %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 2:
DETAIL_MED("CTRL: User control message: stream dry %u", ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: stream dry %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 3:
DETAIL_MED("CTRL: User control message: setbufferlen %u",
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: setbufferlen %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 4:
DETAIL_MED("CTRL: User control message: streamisrecorded %u",
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: streamisrecorded %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 6:
DETAIL_MED("CTRL: User control message: pingrequest %u",
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: pingrequest %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 7:
DETAIL_MED("CTRL: User control message: pingresponse %u",
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_MED("CTRL: User control message: pingresponse %" PRIu32, Bit::btohl(next.data.data() + 2));
break;
case 31:
case 32:
// don't know, but not interesting anyway
// don't know, but not interes ting anyway
break;
default:
DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", ucmtype,
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
DETAIL_LOW("CTRL: User control message: UNKNOWN %" PRId16 " - %" PRIu32, ucmtype,
Bit::btohl(next.data.data() + 2));
break;
}
}break;
case 5: // window size of other end
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
RTMPStream::rec_window_size = Bit::btohl(next.data.data());
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
DETAIL_MED("CTRL: Window size: %i", RTMPStream::rec_window_size);
DETAIL_MED("CTRL: Window size: %" PRIu64, RTMPStream::rec_window_size);
break;
case 6:
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str());
RTMPStream::snd_window_size = Bit::btohl(next.data.data());
// 4 bytes window size, 1 byte limit type (ignored)
DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size);
DETAIL_MED("CTRL: Set peer bandwidth: %" PRIu64, RTMPStream::snd_window_size);
break;
case 8:
case 9:
if (detail >= 4 || reconstruct.good() || validate){
F.ChunkLoader(next);
mediaTime = F.tagTime();
DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str());
DETAIL_VHI("[%" PRIu64 "+%" PRId64 "] %s", F.tagTime(), F.offset(), F.tagType().c_str());
if (reconstruct.good()){reconstruct.write(F.data, F.len);}
}
break;

View file

@ -7,7 +7,7 @@ class AnalyserRTMP : public Analyser{
private:
RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk
FLV::Tag F; ///< Holds the most recently created FLV packet
unsigned int read_in; ///< Amounts of bytes read to fill 'strbuf' so far
size_t read_in; ///< Amounts of bytes read to fill 'strbuf' so far
Socket::Buffer strbuf; ///< Internal buffer from where 'next' is filled
AMF::Object amfdata; ///< Last read AMF object
AMF::Object3 amf3data; ///< Last read AMF3 object

View file

@ -14,9 +14,9 @@ void AnalyserRTSP::incoming(const DTSC::Packet &pkt){
char *dataPtr;
size_t dataSize;
pkt.getString("data", dataPtr, dataSize);
DETAIL_MED("Received %ub %sfor track %lu (%s) @ %llums", dataSize,
DETAIL_MED("Received %zub %sfor track %zu (%s) @ %" PRIu64 "ms", dataSize,
pkt.getFlag("keyframe") ? "keyframe " : "", pkt.getTrackId(),
myMeta.tracks[pkt.getTrackId()].getIdentifier().c_str(), pkt.getTime());
myMeta.getTrackIdentifier(pkt.getTrackId()).c_str(), pkt.getTime());
if (detail >= 8){
for (uint32_t i = 0; i < dataSize; ++i){
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)dataPtr[i] << " ";
@ -58,9 +58,9 @@ bool AnalyserRTSP::parsePacket(){
return true;
}
if (HTTP.hasHeader("Transport")){
uint32_t trackNo = sdpState.parseSetup(HTTP, "", "");
size_t trackNo = sdpState.parseSetup(HTTP, "", "");
if (trackNo){
DETAIL_MED("Parsed transport for track: %lu", trackNo);
DETAIL_MED("Parsed transport for track: %zu", trackNo);
}else{
DETAIL_MED("Could not parse transport string!");
}
@ -95,15 +95,15 @@ bool AnalyserRTSP::parsePacket(){
RTP::Packet pkt(tcpPacket.data() + 4, len);
uint8_t chan = tcpHead.data()[1];
uint32_t trackNo = sdpState.getTrackNoForChannel(chan);
DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %llu", len,
(unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp());
DETAIL_HI("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len, pkt.getSequence(),
chan, pkt.getTimeStamp());
if (!trackNo && (chan % 2) != 1){
DETAIL_MED("Received packet for unknown track number on channel %u", chan);
}
if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();}
if (detail >= 10){
char *pl = pkt.getPayload();
const char *pl = pkt.getPayload();
uint32_t payLen = pkt.getPayloadSize();
for (uint32_t i = 0; i < payLen; ++i){
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)pl[i] << " ";

View file

@ -46,7 +46,7 @@ bool AnalyserTS::parsePacket(){
static char packetPtr[188];
std::cin.read(packetPtr, 188);
if (std::cin.gcount() != 188){return false;}
DONTEVEN_MSG("Reading from position %llu", bytes);
DONTEVEN_MSG("Reading from position %" PRIu64, bytes);
bytes += 188;
if (!packet.FromPointer(packetPtr)){return false;}
if (detail){
@ -74,15 +74,15 @@ bool AnalyserTS::parsePacket(){
}
AnalyserTS::~AnalyserTS(){
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
for (std::map<size_t, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
if ((detail & 1) && (!pidOnly || it->first == pidOnly)){
std::cout << printPES(it->second, it->first);
}
}
}
std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
unsigned int headSize = 0;
std::string AnalyserTS::printPES(const std::string &d, size_t PID){
size_t headSize = 0;
std::stringstream res;
bool known = false;
res << "[PES " << PID << "]";
@ -98,7 +98,7 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
if (d[0] != 0 || d[1] != 0 || d[2] != 1){
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]";
}
unsigned int padding = 0;
size_t padding = 0;
if (known){
if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";}
if (d[6] & 0x30){res << " [SCRAMBLED]";}
@ -144,19 +144,19 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
res << " [Padding: " << padding << "b]";
}
if (timeFlags & 0x02){
long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1);
uint64_t time = ((d[9] & 0xE) >> 1);
time <<= 15;
time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F);
time |= ((uint32_t)d[10] << 7) | ((d[11] >> 1) & 0x7F);
time <<= 15;
time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F);
time |= ((uint32_t)d[12] << 7) | ((d[13] >> 1) & 0x7F);
res << " [PTS " << ((double)time / 90000) << "s]";
}
if (timeFlags & 0x01){
long long unsigned int time = ((d[14] >> 1) & 0x07);
uint64_t time = ((d[14] >> 1) & 0x07);
time <<= 15;
time |= ((int)d[15] << 7) | (d[16] >> 1);
time |= ((uint32_t)d[15] << 7) | (d[16] >> 1);
time <<= 15;
time |= ((int)d[17] << 7) | (d[18] >> 1);
time |= ((uint32_t)d[17] << 7) | (d[18] >> 1);
res << " [DTS " << ((double)time / 90000) << "s]";
}
}
@ -169,12 +169,18 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
res << std::endl;
if (detail & 32){
unsigned int counter = 0;
for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i){
size_t counter = 0;
for (size_t i = 9 + headSize + padding; i < d.size(); ++i){
if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1){
res << std::endl;
counter = 0;
}
if ((i < d.size() - 3) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 1){
if (counter > 1){
res << std::endl << " ";
counter = 0;
}
}
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i] & 0xff) << " ";
if ((counter) % 32 == 31){res << std::endl;}
counter++;

View file

@ -8,11 +8,11 @@ public:
~AnalyserTS();
bool parsePacket();
static void init(Util::Config &conf);
std::string printPES(const std::string &d, unsigned long PID);
std::string printPES(const std::string &d, size_t PID);
private:
std::map<unsigned long long, std::string> payloads;
uint32_t pidOnly;
std::map<size_t, std::string> payloads;
size_t pidOnly;
TS::Packet packet;
uint64_t bytes;
};

View file

@ -1,485 +0,0 @@
/// \file dash_analyzer.cpp
/// Contains the code for the DASH Analysing tool.
/// Currently, only mp4 is supported, and the xml parser assumes a representation id tag exists
#include <fstream>
#include <iostream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/timing.h>
#include <set>
#define OTHER 0x00
#define VIDEO 0x01
#define AUDIO 0x02
///\brief simple struct for storage of stream-specific data
struct StreamData{
long timeScale;
std::string media;
std::string initialization;
std::string initURL;
long trackID;
unsigned int adaptationSet;
unsigned char trackType;
};
StreamData tempSD; // temp global
///\brief another simple structure used for ordering byte seek positions.
struct seekPos{
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator<(const seekPos &rhs) const{
if ((seekTime * rhs.timeScale) < (rhs.seekTime * timeScale)){
return true;
}else{
if ((seekTime * rhs.timeScale) == (rhs.seekTime * timeScale)){
if (adaptationSet < rhs.adaptationSet){
return true;
}else if (adaptationSet == rhs.adaptationSet){
if (trackID < rhs.trackID){return true;}
}
}
}
return false;
}
long timeScale;
long long unsigned int bytePos; /// ?
long long unsigned int seekTime; /// start
long long unsigned int duration; /// duration
unsigned int trackID; /// stores representation ID
unsigned int adaptationSet; /// stores type
unsigned char trackType; /// stores type
std::string url;
};
bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim){
size_t offset = data.find(name);
if (offset == std::string::npos){
return false; // name string not found.
}
// expected: delim character BEFORE blockstart.
offset--;
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
offset = blockStart + 1; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
blockEnd++; // include delim
// DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim){
size_t offset = data.find(name);
if (offset == std::string::npos){
return false; // name string not found.
}
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
blockStart++; // clip off quote characters
offset = blockStart; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos){
return false; // no start/end quotes found
}
// DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getString(std::string &data, std::string name, std::string &output){
size_t blockStart = 0;
size_t blockEnd = 0;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) )
output = data.substr(blockStart, (blockEnd - blockStart));
// looks like this function is working as expected
// DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str());
return true;
}
bool getLong(std::string &data, std::string name, long &output){
size_t blockStart, blockEnd;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")){
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str());
output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str());
return true;
}
// block expecting separate name and /name occurence, or name and /> before another occurence of <.
bool getBlock(std::string &data, std::string name, int offset, size_t &blockStart, size_t &blockEnd){
blockStart = data.find("<" + name + ">", offset);
if (blockStart == std::string::npos){
blockStart = data.find("<" + name + " ", offset); // this considers both valid situations <name> and <name bla="bla"/>
}
if (blockStart == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block start found for name: %s at offset: %i", name.c_str(), offset);
return false;
}
blockEnd = data.find("/" + name + ">", blockStart);
if (blockEnd == std::string::npos){
blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos){
DEBUG_MSG(DLVL_INFO, "no block end found.");
return false;
}
size_t temp = data.find("<", blockStart + 1, (blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
if (temp != std::string::npos){// all info is epxected between <name ... />
DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str());
return false;
}
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after />
}else{
blockEnd += name.size() + 2; // position after /name>
}
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true;
}
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if (!getString(data, "mimeType", mimeType)){// get first occurence of mimeType. --> this will break
// if multiple mimetypes should be read from this block
// because no offset is provided. solution: use this on a substring containing the desired information.
DEBUG_MSG(DLVL_FAIL, "mimeType not found");
return false;
}
DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK
if (mimeType.find("video") != std::string::npos){tempSD.trackType = VIDEO;}
if (mimeType.find("audio") != std::string::npos){tempSD.trackType = AUDIO;}
if (tempSD.trackType == OTHER){
DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up.");
return false;
}
// find an ID within this adaptationSet block.
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "Representation not found");
return false;
}
// representation string
std::string block = data.substr(blockStart, (blockEnd - blockStart));
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str());
// check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)){
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str());
return false;
}
DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK
offset = 0;
// get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found");
return false;
}
block = data.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK
getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media);
getString(block, "initialization", tempSD.initialization);
size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s",
tempSD.initialization.c_str());
return false;
}
tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
// get segment timeline block from within segment template:
size_t blockOffset = 0; // offset should be 0 because this is a new block
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found");
return false;
}
std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
// DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK
int numS = 0;
offset = 0;
long long unsigned int totalDuration = 0;
long timeValue;
while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
return false;
}else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true)
}
}
numS++;
// stuff S data into: currentPos
// searching for t(start position)
std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK!
if (getLong(sBlock, "t", timeValue)){
totalDuration = timeValue; // reset totalDuration to value of t
}
if (!getLong(sBlock, "d", timeValue)){// expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S");
return false;
}
// stuff data with old value (start of block)
// DEBUG_MSG(DLVL_INFO, "stuffing info from S into set");
seekPos thisPos;
thisPos.trackType = tempSD.trackType;
thisPos.trackID = tempSD.trackID;
thisPos.adaptationSet = tempSD.adaptationSet;
// thisPos.trackID=id;
thisPos.seekTime = totalDuration; // previous total duration is start time of this S.
thisPos.duration = timeValue;
thisPos.timeScale = tempSD.timeScale;
static char charBuf[512];
snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration);
thisPos.url.assign(charBuf);
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration
offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant.
}
return true;
}
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData){
// for all adaptation sets
// representation ID
int numAdaptationSet = 0;
size_t currentOffset = 0;
size_t adaptationSetStart;
size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)){
tempSD.adaptationSet = numAdaptationSet;
numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart,
adaptationSetEnd, (adaptationSetEnd - adaptationSetStart));
// get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart));
// function was verified: output as expected.
if (!parseAdaptationSet(adaptationSet, currentPos)){
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case
// of OTHER mimetype. in that case it might be desirable to continue searching for valid data instead of quitting.
return false;
}
streamData.push_back(tempSD); // put temp values into adaptation set vector
currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset.
}
if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
return false;
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
return true;
}
int main(int argc, char **argv){
Util::Config conf = Util::Config(argv[0]);
conf.addOption("mode",
JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", "
"\"default\":\"analyse\", \"help\":\"What to do with the stream. "
"Valid modes are 'analyse', 'validate', 'output'.\"}"));
conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to "
"HLS stream index file to retrieve.\"}"));
conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", "
"\"arg\":\"integer\", \"default\":-1, \"help\":\"Abort "
"after this many seconds of downloading. Negative "
"values mean unlimited, which is the default.\"}"));
conf.parseArgs(argc, argv);
conf.activate();
unsigned int port = 80;
std::string url = conf.getString("url");
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
return -1;
}
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
std::string server = url.substr(0, url.find('/'));
url = url.substr(url.find('/'));
if (server.find(':') != std::string::npos){
port = atoi(server.substr(server.find(':') + 1).c_str());
server = server.substr(0, server.find(':'));
}
long long int startTime = Util::bootSecs();
long long int abortTime = conf.getInteger("abort");
Socket::Connection conn(server, port, false);
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
if (!conn){conn.open(server, port, false);}
unsigned int pos = 0;
HTTP::Parser H;
H.url = url;
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SendRequest(conn);
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}
H.BuildResponse();
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)){
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return -1;
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++){
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++){// get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ",
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str());
}
while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)){
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
// match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++){
if (streamData[i].trackID == currentPos.begin()->trackID &&
streamData[i].adaptationSet == currentPos.begin()->adaptationSet)
tempID = i;
}
if (!conn){conn.open(server, port, false);}
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(),
currentPos.begin()->seekTime, currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))){}// ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
break;
}
size_t beforeParse = H.body.size();
MP4::Box mp4Data;
bool mdatSeen = false;
while (mp4Data.read(H.body)){
if (mp4Data.isType("mdat")){mdatSeen = true;}
}
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
break;
}
if (H.body.size()){
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()){break;}
}
H.Clean();
pos = 1000 * (currentPos.begin()->seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale;
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos){
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
}
currentPos.erase(currentPos.begin());
}
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return 0;
}

View file

@ -0,0 +1,78 @@
#include <cstdio>
#include <iostream>
#include <string>
#include <mist/bitfields.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/h264.h>
///\brief Holds everything unique to the analysers.
namespace Analysers{
int analyseH264(Util::Config conf){
FILE *F = fopen(conf.getString("filename").c_str(), "r+b");
if (!F){FAIL_MSG("No such file");}
h264::nalUnit *nalPtr = h264::nalFactory(F);
while (nalPtr){
if (nalPtr->getType() == 0x07){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
bw.append(br.get(8), 8); // profile_idc
bw.append(br.get(8), 8); // constraint flags
bw.append(br.get(8), 8); // level_idc
br.getUExpGolomb();
bw.appendUExpGolomb(0); // seq_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}else if (nalPtr->getType() == 0x08){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
br.getUExpGolomb();
bw.appendUExpGolomb(0); // pic_parameter_set_id
br.getUExpGolomb();
bw.appendUExpGolomb(0); // seq_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}else if (nalPtr->getType() == 0x01 || nalPtr->getType() == 0x05 || nalPtr->getType() == 0x19){
Utils::bitstream br;
br << nalPtr->payload;
Utils::bitWriter bw;
bw.append(br.get(8), 8); // nalType
bw.appendUExpGolomb(br.getUExpGolomb()); // first_mb_in_slice
bw.appendUExpGolomb(br.getUExpGolomb()); // slice_type
br.getUExpGolomb();
bw.appendUExpGolomb(0); // pic_parameter_set_id
while (br.size() >= 64){bw.append(br.get(64), 64);}
size_t remainder = br.size();
if (remainder){bw.append(br.get(remainder), remainder);}
nalPtr->payload = bw.str();
}
nalPtr->write(std::cout);
delete nalPtr;
nalPtr = h264::nalFactory(F);
}
return 0;
}
}// namespace Analysers
int main(int argc, char **argv){
Util::Config conf = Util::Config(argv[0]);
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Full "
"path of the file to analyse.\"}"));
conf.parseArgs(argc, argv);
return Analysers::analyseH264(conf);
}

View file

@ -476,6 +476,31 @@ int main_loop(int argc, char **argv){
}
}
// Upgrade old configurations
{
bool foundCMAF = false;
bool edit = false;
JSON::Value newVal;
jsonForEach(Controller::Storage["config"]["protocols"], it){
if ((*it)["connector"].asStringRef() == "HSS"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "DASH"){
edit = true;
continue;
}
if ((*it)["connector"].asStringRef() == "CMAF"){foundCMAF = true;}
newVal.append(*it);
}
if (edit && !foundCMAF){newVal.append(JSON::fromString("{\"connector\":\"CMAF\"}"));}
if (edit){
Controller::Storage["config"]["protocols"] = newVal;
Controller::Log("CONF", "Translated protocols to new versions");
}
}
Controller::Log("CONF", "Controller started");
// Generate instanceId once per boot.
if (Controller::instanceId == ""){

View file

@ -95,17 +95,11 @@ namespace Controller{
trgs["STREAM_PUSH"]["response"] = "always";
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
trgs["STREAM_TRACK_ADD"]["when"] = "Before a new track is accepted by a live stream buffer";
trgs["STREAM_TRACK_ADD"]["stream_specific"] = true;
trgs["STREAM_TRACK_ADD"]["payload"] = "stream name (string)\ntrack ID (integer)\n";
trgs["STREAM_TRACK_ADD"]["response"] = "ignored";
trgs["STREAM_TRACK_ADD"]["response_action"] = "None.";
trgs["STREAM_TRACK_REMOVE"]["when"] = "Before a track is removed by a live stream buffer";
trgs["STREAM_TRACK_REMOVE"]["stream_specific"] = true;
trgs["STREAM_TRACK_REMOVE"]["payload"] = "stream name (string)\ntrack ID (integer)\n";
trgs["STREAM_TRACK_REMOVE"]["response"] = "ignored";
trgs["STREAM_TRACK_REMOVE"]["response_action"] = "None.";
trgs["LIVE_TRACK_LIST"]["when"] = "After the list of valid tracks has been updated";
trgs["LIVE_TRACK_LIST"]["stream_specific"] = true;
trgs["LIVE_TRACK_LIST"]["payload"] = "stream name (string)\ntrack list (JSON)\n";
trgs["LIVE_TRACK_LIST"]["response"] = "ignored";
trgs["LIVE_TRACK_LIST"]["response_action"] = "None.";
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
trgs["STREAM_BUFFER"]["stream_specific"] = true;

View file

@ -145,17 +145,18 @@ namespace Controller{
if (pipedCapa.isMember("optional")){builPipedPart(p, argarr, argnum, pipedCapa["optional"]);}
}
///\brief Checks current protocol configuration, updates state of enabled connectors if neccessary.
///\param p An object containing all protocols.
///\param capabilities An object containing the detected capabilities.
///\returns True if any action was taken
///\brief Checks current protocol configuration, updates state of enabled connectors if
/// neccessary. \param p An object containing all protocols. \param capabilities An object
/// containing the detected capabilities. \returns True if any action was taken
///
/// \triggers
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is started. It cannot be cancelled. Its payload is:
/// The `"OUTPUT_START"` trigger is global, and is ran whenever a new protocol listener is
/// started. It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~
/// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated. It cannot be cancelled. Its payload is:
/// The `"OUTPUT_STOP"` trigger is global, and is ran whenever a protocol listener is terminated.
/// It cannot be cancelled. Its payload is:
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~

View file

@ -118,11 +118,12 @@ namespace Controller{
for (unsigned int i = 0; i < 8; ++i){
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
char ivec[16];
memset(ivec, 0, 16);
Encryption::AES crypter;
crypter.setEncryptKey(aesKey);
// 0 here for 0-filled ivec.
dl.setHeader("X-IRDGAF",
Encodings::Base64::encode(Encryption::AES_Crypt(
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
Encodings::Base64::encode(crypter.encryptBlockCTR(0, RELEASE "|" PACKAGE_VERSION)));
}
if (!dl.get(url) || !dl.isOk()){return;}
response = JSON::fromString(dl.data());
@ -143,11 +144,12 @@ namespace Controller{
aesKey[15 - i] = ((licID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
std::string cipher = Encodings::Base64::decode(input);
std::string deCrypted;
// magic ivecs, they are empty. It's secretly 16 times \0.
char ivec[16];
memset(ivec, 0, 16);
deCrypted = Encryption::AES_Crypt(cipher.c_str(), cipher.size(), aesKey, ivec);
Encryption::AES crypter;
crypter.setEncryptKey(aesKey);
// 0 here for 0-filled ivec.
std::string deCrypted = crypter.encryptBlockCTR(0, cipher);
// get time stamps and license.
// verify checksum

View file

@ -135,7 +135,10 @@ namespace Controller{
void pushCheckLoop(void *np){
{
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);}
if (pushReadPage.mapped){
readPushList(pushReadPage.mapped);
pushReadPage.master = true;
}
}
pushListRead = true;
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);

View file

@ -30,6 +30,7 @@
#define STAT_CLI_BPS_DOWN 128
#define STAT_CLI_BPS_UP 256
#define STAT_CLI_CRC 512
#define STAT_CLI_SESSID 1024
#define STAT_CLI_ALL 0xFFFF
// These are used to store "totals" field requests in a bitfield for speedup.
#define STAT_TOT_CLIENTS 1
@ -48,6 +49,8 @@ std::map<std::string, Controller::triggerLog> Controller::triggerStats; ///< Hol
bool Controller::killOnExit = KILL_ON_EXIT;
tthread::mutex Controller::statsMutex;
unsigned int Controller::maxConnsPerIP = 0;
uint64_t Controller::statDropoff = 0;
char noBWCountMatches[1717];
uint64_t bwLimit = 128 * 1024 * 1024; // gigabit default limit
@ -96,35 +99,27 @@ static uint64_t servInputs = 0;
static uint64_t servOutputs = 0;
static uint64_t servViewers = 0;
Controller::sessIndex::sessIndex(std::string dhost, unsigned int dcrc, std::string dstreamName,
std::string dconnector){
ID = "UNSET";
host = dhost;
crc = dcrc;
streamName = dstreamName;
connector = dconnector;
}
Controller::sessIndex::sessIndex(){
crc = 0;
}
/// Initializes a sessIndex from a statistics object + index, converting binary format IP addresses
/// into strings. This extracts the host, stream name, connector and crc field, ignoring everything
/// else.
Controller::sessIndex::sessIndex(const Comms::Statistics &statComm, size_t id){
host = statComm.getHost(id);
streamName = statComm.getStream(id);
connector = statComm.getConnector(id);
crc = statComm.getCRC(id);
ID = statComm.getSessId(id);
}
std::string Controller::sessIndex::toStr(){
std::stringstream s;
s << ID << "(" << host << " " << crc << " " << streamName << " " << connector << ")";
return s.str();
}
/// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into
/// strings. This extracts the host, stream name, connector and crc field, ignoring everything else.
Controller::sessIndex::sessIndex(IPC::statExchange &data){
Socket::hostBytesToStr(data.host().c_str(), 16, host);
streamName = data.streamName();
connector = data.connector();
crc = data.crc();
ID = data.getSessId();
}
bool Controller::sessIndex::operator==(const Controller::sessIndex &b) const{
return (host == b.host && crc == b.crc && streamName == b.streamName && connector == b.connector);
}
@ -166,13 +161,13 @@ void Controller::streamStopped(std::string stream){
INFO_MSG("Stream %s became inactive", stream.c_str());
}
/// \todo Make this prettier.
IPC::sharedServer *statPointer = 0;
Comms::Statistics statComm;
bool statCommActive = false;
/// Invalidates all current sessions for the given streamname
/// Updates the session cache, afterwards.
void Controller::sessions_invalidate(const std::string &streamname){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In shutdown procedure - cannot invalidate sessions.");
return;
}
@ -209,7 +204,7 @@ void Controller::sessions_shutdown(JSON::Iter &i){
/// Shuts down the given session
/// Updates the session cache, afterwards.
void Controller::sessId_shutdown(const std::string &sessId){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -231,7 +226,7 @@ void Controller::sessId_shutdown(const std::string &sessId){
/// Tags the given session
void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot tag sessions.");
return;
}
@ -250,7 +245,7 @@ void Controller::sessId_tag(const std::string &sessId, const std::string &tag){
/// Shuts down sessions with the given tag set
/// Updates the session cache, afterwards.
void Controller::tag_shutdown(const std::string &tag){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -272,7 +267,7 @@ void Controller::tag_shutdown(const std::string &tag){
/// Shuts down all current sessions for the given streamname
/// Updates the session cache, afterwards.
void Controller::sessions_shutdown(const std::string &streamname, const std::string &protocol){
if (!statPointer){
if (!statCommActive){
FAIL_MSG("In controller shutdown procedure - cannot shutdown sessions.");
return;
}
@ -325,9 +320,13 @@ void Controller::writeSessionCache(){
/// old statistics that have disconnected over 10 minutes ago.
void Controller::SharedMemStats(void *config){
HIGH_MSG("Starting stats thread");
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
statPointer = &statServer;
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true);
statComm.reload(true);
statCommActive = true;
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, false, false);
if (!shmSessions || !shmSessions->mapped){
if (shmSessions){delete shmSessions;}
shmSessions = new IPC::sharedPage(SHM_SESSIONS, SHM_SESSIONS_SIZE, true);
}
cacheLock = new IPC::semaphore(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
cacheLock->unlink();
cacheLock->open(SEM_SESSCACHE, O_CREAT | O_RDWR, ACCESSPERMS, 1);
@ -341,7 +340,10 @@ void Controller::SharedMemStats(void *config){
tthread::lock_guard<tthread::mutex> guard2(statsMutex);
cacheLock->wait(); /*LTS*/
// parse current users
statServer.parseEach(parseStatistics);
statLeadIn();
COMM_LOOP(statComm, statOnActive(id), statOnDisconnect(id));
statLeadOut();
if (firstRun){
firstRun = false;
servUpOtherBytes = 0;
@ -357,18 +359,27 @@ void Controller::SharedMemStats(void *config){
// wipe old statistics
if (sessions.size()){
std::list<sessIndex> mustWipe;
unsigned long long cutOffPoint = Util::epoch() - STAT_CUTOFF;
unsigned long long disconnectPointIn = Util::epoch() - STATS_INPUT_DELAY;
unsigned long long disconnectPointOut = Util::epoch() - STATS_DELAY;
uint64_t cutOffPoint = Util::bootSecs() - STAT_CUTOFF;
uint64_t disconnectPointIn = Util::bootSecs() - STATS_INPUT_DELAY;
uint64_t disconnectPointOut = Util::bootSecs() - STATS_DELAY;
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
unsigned long long dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut;
it->second.ping(it->first, dPoint);
uint64_t dPoint = it->second.getSessType() == SESS_INPUT ? disconnectPointIn : disconnectPointOut;
if (it->second.sync == 100){
// Denied entries are connection-entry-wiped as soon as they become boring
it->second.wipeOld(dPoint);
}else{
// Normal entries are summarized after STAT_CUTOFF seconds
it->second.wipeOld(cutOffPoint);
}
if (!it->second.hasData()){mustWipe.push_back(it->first);}
// This part handles ending sessions, keeping them in cache for now
if (it->second.isTracked() && !it->second.isConnected() && it->second.getEnd() < dPoint){
it->second.dropSession(it->first);
}
// This part handles wiping from the session cache
if (!it->second.hasData()){
it->second.dropSession(it->first); // End the session, just in case it wasn't yet
mustWipe.push_back(it->first);
}
}
while (mustWipe.size()){
sessions.erase(mustWipe.front());
@ -429,15 +440,18 @@ void Controller::SharedMemStats(void *config){
}
Util::wait(1000);
}
statPointer = 0;
statCommActive = false;
HIGH_MSG("Stopping stats thread");
if (Util::Config::is_restarting){
statServer.abandon();
statComm.setMaster(false);
shmSessions->master = false;
}else{/*LTS-START*/
if (Controller::killOnExit){
WARN_MSG("Killing all connected clients to force full shutdown");
statServer.finishEach();
for (uint32_t id = statComm.firstValid(); id != statComm.endValid(); id++){
if (statComm.getStatus(id) == COMM_STATUS_INVALID){continue;}
statComm.kill(id, true);
}
}
/*LTS-END*/
}
@ -474,12 +488,10 @@ std::set<std::string> Controller::getActiveStreams(const std::string &prefix){
uint32_t Controller::statSession::invalidate(){
uint32_t ret = 0;
sync = 1;
if (curConns.size() && statPointer){
if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first);
if (data){
IPC::statExchange tmpEx(data);
tmpEx.setSync(2);
if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
statComm.setSync(2, jt->first);
ret++;
}
}
@ -492,16 +504,14 @@ uint32_t Controller::statSession::invalidate(){
uint32_t Controller::statSession::kill(){
uint32_t ret = 0;
sync = 100;
if (curConns.size() && statPointer){
if (curConns.size() && statCommActive){
for (std::map<uint64_t, statStorage>::iterator jt = curConns.begin(); jt != curConns.end(); ++jt){
char *data = statPointer->getIndex(jt->first);
if (data){
IPC::statExchange tmpEx(data);
tmpEx.setSync(100);
uint32_t pid = tmpEx.getPID();
if (statComm.getStatus(jt->first) != COMM_STATUS_INVALID){
statComm.setSync(100, jt->first);
uint32_t pid = statComm.getPid(jt->first);
if (pid > 1){
Util::Procs::Stop(pid);
INFO_MSG("Killing PID %lu", pid);
INFO_MSG("Killing PID %" PRIu32, pid);
}
ret++;
}
@ -511,15 +521,18 @@ uint32_t Controller::statSession::kill(){
}
/// Updates the given active connection with new stats data.
void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 = state known (100=denied, 10=accepted)
if (!data.getSync()){
sessIndex tmpidx(data);
std::string myHost = tmpidx.host;
void Controller::statSession::update(uint64_t index, Comms::Statistics &statComm){
std::string myHost = statComm.getHost(index);
std::string myStream = statComm.getStream(index);
std::string myConnector = statComm.getConnector(index);
// update the sync byte: 0 = requesting fill, 2 = requesting refill, 1 = needs checking, > 2 =
// state known (100=denied, 10=accepted)
if (!statComm.getSync(index)){
sessIndex tmpidx(statComm, index);
// if we have a maximum connection count per IP, enforce it
if (maxConnsPerIP && !data.getSync()){
if (maxConnsPerIP && !statComm.getSync(index)){
unsigned int currConns = 1;
long long shortly = Util::epoch();
long long shortly = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (&it->second != this && it->first.host == myHost &&
@ -533,35 +546,35 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
}
if (currConns > maxConnsPerIP){
WARN_MSG("Disconnecting session from %s: exceeds max connection count of %u", myHost.c_str(), maxConnsPerIP);
data.setSync(100);
statComm.setSync(100, index);
}
}
if (data.getSync() != 100){
if (statComm.getSync(index) != 100){
// only set the sync if this is the first connection in the list
// we also catch the case that there are no connections, which is an error-state
if (!sessions[tmpidx].curConns.size() || sessions[tmpidx].curConns.begin()->first == index){
MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu);
data.setSync(sync);
MEDIUM_MSG("Requesting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
statComm.setSync(sync, index);
}
// and, always set the sync if it is > 2
if (sync > 2){
MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %lu", sync, data.streamName().c_str(),
data.connector().c_str(), myHost.c_str(), data.crc() & 0xFFFFFFFFu);
data.setSync(sync);
MEDIUM_MSG("Setting sync to %u for %s, %s, %s, %" PRIu32, sync, myStream.c_str(),
myConnector.c_str(), myHost.c_str(), statComm.getCRC(index) & 0xFFFFFFFFu);
statComm.setSync(sync, index);
}
}
}else{
if (sync < 2 && data.getSync() > 2){sync = data.getSync();}
if (sync < 2 && statComm.getSync(index) > 2){sync = statComm.getSync(index);}
}
long long prevDown = getDown();
long long prevUp = getUp();
curConns[index].update(data);
curConns[index].update(statComm, index);
// store timestamp of first received data, if older
if (firstSec > data.now()){firstSec = data.now();}
if (firstSec > statComm.getNow(index)){firstSec = statComm.getNow(index);}
// store timestamp of last received data, if newer
if (data.now() > lastSec){
lastSec = data.now();
if (statComm.getNow(index) > lastSec){
lastSec = statComm.getNow(index);
if (!tracked){
tracked = true;
firstActive = firstSec;
@ -571,13 +584,13 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
long long currUp = getUp();
if (currUp - prevUp < 0 || currDown - prevDown < 0){
INFO_MSG("Negative data usage! %lldu/%lldd (u%lld->%lld) in %s over %s, #%lu", currUp - prevUp,
currDown - prevDown, prevUp, currUp, data.streamName().c_str(), data.connector().c_str(), index);
currDown - prevDown, prevUp, currUp, myStream.c_str(), myConnector.c_str(), index);
}else{
if (!noBWCount){
size_t bwMatchOffset = 0;
noBWCount = 1;
while (noBWCountMatches[bwMatchOffset + 16] != 0 && bwMatchOffset < 1700){
if (Socket::matchIPv6Addr(data.host(), std::string(noBWCountMatches + bwMatchOffset, 16),
if (Socket::matchIPv6Addr(statComm.getHost(index), std::string(noBWCountMatches + bwMatchOffset, 16),
noBWCountMatches[bwMatchOffset + 16])){
noBWCount = 2;
break;
@ -599,40 +612,39 @@ void Controller::statSession::update(uint64_t index, IPC::statExchange &data){
}
}
if (currDown + currUp >= COUNTABLE_BYTES){
std::string streamName = data.streamName();
if (sessionType == SESS_UNSET){
if (data.connector() == "INPUT"){
if (myConnector == "INPUT"){
++servInputs;
streamStats[streamName].inputs++;
streamStats[streamName].currIns++;
streamStats[myStream].inputs++;
streamStats[myStream].currIns++;
sessionType = SESS_INPUT;
}else if (data.connector() == "OUTPUT"){
}else if (myConnector == "OUTPUT"){
++servOutputs;
streamStats[streamName].outputs++;
streamStats[streamName].currOuts++;
streamStats[myStream].outputs++;
streamStats[myStream].currOuts++;
sessionType = SESS_OUTPUT;
}else{
++servViewers;
streamStats[streamName].viewers++;
streamStats[streamName].currViews++;
streamStats[myStream].viewers++;
streamStats[myStream].currViews++;
sessionType = SESS_VIEWER;
}
}
// If previous < COUNTABLE_BYTES, we haven't counted any data so far.
// We need to count all the data in that case, otherwise we only count the difference.
if (prevUp + prevDown < COUNTABLE_BYTES){
if (!streamName.size() || streamName[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);}
if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{
streamStats[streamName].upBytes += currUp;
streamStats[streamName].downBytes += currDown;
streamStats[myStream].upBytes += currUp;
streamStats[myStream].downBytes += currDown;
}
}else{
if (!streamName.size() || streamName[0] == 0){
if (streamStats.count(streamName)){streamStats.erase(streamName);}
if (!myStream.size() || myStream[0] == 0){
if (streamStats.count(myStream)){streamStats.erase(myStream);}
}else{
streamStats[streamName].upBytes += currUp - prevUp;
streamStats[streamName].downBytes += currDown - prevDown;
streamStats[myStream].upBytes += currUp - prevUp;
streamStats[myStream].downBytes += currDown - prevDown;
}
}
}
@ -642,7 +654,7 @@ Controller::sessType Controller::statSession::getSessType(){
return sessionType;
}
/// Archives the given connection.
/// Archives connection log entries older than the given cutOff point.
void Controller::statSession::wipeOld(uint64_t cutOff){
if (firstSec > cutOff){return;}
firstSec = 0xFFFFFFFFFFFFFFFFull;
@ -673,76 +685,74 @@ void Controller::statSession::wipeOld(uint64_t cutOff){
}
}
void Controller::statSession::ping(const Controller::sessIndex &index, uint64_t disconnectPoint){
void Controller::statSession::dropSession(const Controller::sessIndex &index){
if (!tracked || curConns.size()){return;}
if (lastSec < disconnectPoint){
switch (sessionType){
case SESS_INPUT:
if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;}
break;
case SESS_OUTPUT:
if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;}
break;
case SESS_VIEWER:
if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;}
break;
default: break;
}
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration,
getUp(), getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
switch (sessionType){
case SESS_INPUT:
if (streamStats[index.streamName].currIns){streamStats[index.streamName].currIns--;}
break;
case SESS_OUTPUT:
if (streamStats[index.streamName].currOuts){streamStats[index.streamName].currOuts--;}
break;
case SESS_VIEWER:
if (streamStats[index.streamName].currViews){streamStats[index.streamName].currViews--;}
break;
default: break;
}
uint64_t duration = lastSec - firstActive;
if (duration < 1){duration = 1;}
std::stringstream tagStream;
if (tags.size()){
for (std::set<std::string>::iterator it = tags.begin(); it != tags.end(); ++it){
tagStream << "[" << *it << "]";
}
}
Controller::logAccess(index.ID, index.streamName, index.connector, index.host, duration, getUp(),
getDown(), tagStream.str());
if (Controller::accesslog.size()){
if (Controller::accesslog == "LOG"){
std::stringstream accessStr;
accessStr << "Session <" << index.ID << "> " << index.streamName << " (" << index.connector
<< ") from " << index.host << " ended after " << duration << "s, avg "
<< getUp() / duration / 1024 << "KB/s up " << getDown() / duration / 1024 << "KB/s down.";
if (tags.size()){accessStr << " Tags: " << tagStream.str();}
Controller::Log("ACCS", accessStr.str());
}else{
static std::ofstream accLogFile;
static std::string accLogFileName;
if (accLogFileName != Controller::accesslog || !accLogFile.good()){
accLogFile.close();
accLogFile.open(Controller::accesslog.c_str(), std::ios_base::app);
if (!accLogFile.good()){
FAIL_MSG("Could not open access log file '%s': %s", Controller::accesslog.c_str(), strerror(errno));
}else{
accLogFileName = Controller::accesslog;
}
}
if (accLogFile.good()){
time_t rawtime;
struct tm *timeinfo;
struct tm tmptime;
char buffer[100];
time(&rawtime);
timeinfo = localtime_r(&rawtime, &tmptime);
strftime(buffer, 100, "%F %H:%M:%S", timeinfo);
accLogFile << buffer << ", " << index.ID << ", " << index.streamName << ", "
<< index.connector << ", " << index.host << ", " << duration << ", "
<< getUp() / duration / 1024 << ", " << getDown() / duration / 1024 << ", ";
if (tags.size()){accLogFile << tagStream.str();}
accLogFile << std::endl;
}
}
}
tracked = false;
firstActive = 0;
firstSec = 0xFFFFFFFFFFFFFFFFull;
lastSec = 0;
wipedUp = 0;
wipedDown = 0;
oldConns.clear();
sessionType = SESS_UNSET;
}
/// Archives the given connection.
@ -836,16 +846,12 @@ bool Controller::statSession::hasDataFor(uint64_t t){
/// Returns true if there is any data for this session.
bool Controller::statSession::hasData(){
if (!firstSec && !lastSec){return false;}
if (curConns.size()){return true;}
if (oldConns.size()){
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){return true;}
}
}
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){return true;}
}
}
return false;
}
@ -854,26 +860,14 @@ bool Controller::statSession::isViewerOn(uint64_t t){
return getUp(t) + getDown(t) > COUNTABLE_BYTES;
}
/// Returns true if this session should count as a viewer
bool Controller::statSession::isViewer(){
long long upTotal = wipedUp + wipedDown;
if (oldConns.size()){
for (std::deque<statStorage>::iterator it = oldConns.begin(); it != oldConns.end(); ++it){
if (it->log.size()){
upTotal += it->log.rbegin()->second.up + it->log.rbegin()->second.down;
if (upTotal > COUNTABLE_BYTES){return true;}
}
}
}
if (curConns.size()){
for (std::map<uint64_t, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
if (it->second.log.size()){
upTotal += it->second.log.rbegin()->second.up + it->second.log.rbegin()->second.down;
if (upTotal > COUNTABLE_BYTES){return true;}
}
}
}
return false;
/// Returns true if this session should be considered connected
bool Controller::statSession::isConnected(){
return curConns.size();
}
/// Returns true if this session has started (tracked == true) but not yet ended (log entry written)
bool Controller::statSession::isTracked(){
return tracked;
}
/// Returns the cumulative connected time for this session at timestamp t.
@ -1014,58 +1008,60 @@ Controller::statLog &Controller::statStorage::getDataFor(unsigned long long t){
/// This function is called by parseStatistics.
/// It updates the internally saved statistics data.
void Controller::statStorage::update(IPC::statExchange &data){
void Controller::statStorage::update(Comms::Statistics &statComm, size_t index){
statLog tmp;
tmp.time = data.time();
tmp.lastSecond = data.lastSecond();
tmp.down = data.down();
tmp.up = data.up();
log[data.now()] = tmp;
tmp.time = statComm.getTime(index);
tmp.lastSecond = statComm.getLastSecond(index);
tmp.down = statComm.getDown(index);
tmp.up = statComm.getUp(index);
log[statComm.getNow(index)] = tmp;
// wipe data older than approx. STAT_CUTOFF seconds
/// \todo Remove least interesting data first.
if (log.size() > STAT_CUTOFF){log.erase(log.begin());}
}
/// This function is called by the shared memory page that holds statistics.
/// It updates the internally saved statistics data, moving across sessions or archiving when necessary.
void Controller::parseStatistics(char *data, size_t len, uint32_t id){
// retrieve stats data
IPC::statExchange tmpEx(data);
void Controller::statLeadIn(){
statDropoff = Util::bootSecs() - 3;
}
void Controller::statOnActive(size_t id){
// calculate the current session index, store as idx.
sessIndex idx(tmpEx);
// if the connection was already indexed and it has changed, move it
if (connToSession.count(id) && connToSession[id] != idx){
if (sessions[connToSession[id]].getSessType() != SESS_UNSET){
INFO_MSG("Switching connection %" PRIu32 " from active session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}else{
INFO_MSG("Switching connection %" PRIu32 " from inactive session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
sessIndex idx(statComm, id);
if (statComm.getNow(id) >= statDropoff){
// if the connection was already indexed and it has changed, move it
if (connToSession.count(id) && connToSession[id] != idx){
if (sessions[connToSession[id]].getSessType() != SESS_UNSET){
INFO_MSG("Switching connection %zu from active session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}else{
INFO_MSG("Switching connection %zu from inactive session %s over to %s", id,
connToSession[id].toStr().c_str(), idx.toStr().c_str());
}
sessions[connToSession[id]].switchOverTo(sessions[idx], id);
// Destroy this session without calling dropSession, because it was merged into another. What session? We never made it. Stop asking hard questions. Go, shoo. *sprays water*
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);}
}
sessions[connToSession[id]].switchOverTo(sessions[idx], id);
if (!sessions[connToSession[id]].hasData()){sessions.erase(connToSession[id]);}
}
if (!connToSession.count(id)){
INSANE_MSG("New connection: %" PRIu32 " as %s", id, idx.toStr().c_str());
}
// store the index for later comparison
connToSession[id] = idx;
// update the session with the latest data
sessions[idx].update(id, tmpEx);
// check validity of stats data
char counter = (*(data - 1)) & 0x7F;
if (counter == 126 || counter == 127){
// the data is no longer valid - connection has gone away, store for later
INSANE_MSG("Ended connection: %" PRIu32 " as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
if (!connToSession.count(id)){
INSANE_MSG("New connection: %zu as %s", id, idx.toStr().c_str());
}
// store the index for later comparison
connToSession[id] = idx;
// update the session with the latest data
sessions[idx].update(id, statComm);
}
}
void Controller::statOnDisconnect(size_t id){
sessIndex idx(statComm, id);
INSANE_MSG("Ended connection: %zu as %s", id, idx.toStr().c_str());
sessions[idx].finish(id);
connToSession.erase(id);
}
void Controller::statLeadOut(){}
/// Returns true if this stream has at least one connected client.
bool Controller::hasViewers(std::string streamName){
if (sessions.size()){
long long currTime = Util::epoch();
long long currTime = Util::bootSecs();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
if (it->first.streamName == streamName &&
(it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime - 1))){
@ -1119,7 +1115,11 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
// to make sure no nasty timing business takes place, we store the case "now" as a bool.
bool now = (reqTime == 0);
// add the current time, if negative or zero.
if (reqTime <= 0){reqTime += Util::epoch();}
if (reqTime <= 0){
reqTime += Util::bootSecs();
}else{
reqTime -= (Util::epoch() - Util::bootSecs());
}
// at this point, reqTime is the absolute timestamp.
rep["time"] = reqTime; // fill the absolute timestamp
@ -1136,6 +1136,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if ((*it).asStringRef() == "up"){fields |= STAT_CLI_UP;}
if ((*it).asStringRef() == "downbps"){fields |= STAT_CLI_BPS_DOWN;}
if ((*it).asStringRef() == "upbps"){fields |= STAT_CLI_BPS_UP;}
if ((*it).asStringRef() == "sessid"){fields |= STAT_CLI_SESSID;}
}
}
// select all, if none selected
@ -1162,6 +1163,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){rep["fields"].append("downbps");}
if (fields & STAT_CLI_BPS_UP){rep["fields"].append("upbps");}
if (fields & STAT_CLI_CRC){rep["fields"].append("crc");}
if (fields & STAT_CLI_SESSID){rep["fields"].append("sessid");}
// output the data itself
rep["data"].null();
// loop over all sessions
@ -1185,6 +1187,7 @@ void Controller::fillClients(JSON::Value &req, JSON::Value &rep){
if (fields & STAT_CLI_BPS_DOWN){d.append(it->second.getBpsDown(time));}
if (fields & STAT_CLI_BPS_UP){d.append(it->second.getBpsUp(time));}
if (fields & STAT_CLI_CRC){d.append(it->first.crc);}
if (fields & STAT_CLI_SESSID){d.append(it->first.ID);}
rep["data"].append(d);
}
}
@ -1235,8 +1238,8 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
// collect the data first
std::set<std::string> streams;
std::map<std::string, uint64_t> clients;
unsigned int tOut = Util::epoch() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY;
uint64_t tOut = Util::bootSecs() - STATS_DELAY;
uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
{
tthread::lock_guard<tthread::mutex> guard(statsMutex);
@ -1263,26 +1266,14 @@ void Controller::fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow){
jsonForEach(req, j){
if (j->asStringRef() == "clients"){rep[*it].append(clients[*it]);}
if (j->asStringRef() == "lastms"){
char pageId[NAME_BUFFER_SIZE];
IPC::sharedPage streamIndex;
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, it->c_str());
streamIndex.init(pageId, DEFAULT_STRM_PAGE_SIZE, false, false);
if (streamIndex.mapped){
static char liveSemName[NAME_BUFFER_SIZE];
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, it->c_str());
IPC::semaphore metaLocker(liveSemName, O_CREAT | O_RDWR, (S_IRWXU | S_IRWXG | S_IRWXO), 8);
metaLocker.wait();
DTSC::Scan strm = DTSC::Packet(streamIndex.mapped, streamIndex.len, true).getScan();
DTSC::Meta M(*it, false);
if (M){
uint64_t lms = 0;
DTSC::Scan trcks = strm.getMember("tracks");
unsigned int trcks_ctr = trcks.getSize();
for (unsigned int i = 0; i < trcks_ctr; ++i){
if (trcks.getIndice(i).getMember("lastms").asInt() > lms){
lms = trcks.getIndice(i).getMember("lastms").asInt();
}
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator jt = validTracks.begin(); jt != validTracks.end(); jt++){
if (M.getLastms(*jt) > lms){lms = M.getLastms(*jt);}
}
rep[*it].append(lms);
metaLocker.post();
}else{
rep[*it].append(-1);
}
@ -1330,9 +1321,9 @@ void Controller::fillTotals(JSON::Value &req, JSON::Value &rep){
if (req.isMember("start")){reqStart = req["start"].asInt();}
if (req.isMember("end")){reqEnd = req["end"].asInt();}
// add the current time, if negative or zero.
if (reqStart < 0){reqStart += Util::epoch();}
if (reqStart == 0){reqStart = Util::epoch() - STAT_CUTOFF;}
if (reqEnd <= 0){reqEnd += Util::epoch();}
if (reqStart < 0){reqStart += Util::bootSecs();}
if (reqStart == 0){reqStart = Util::bootSecs() - STAT_CUTOFF;}
if (reqEnd <= 0){reqEnd += Util::bootSecs();}
// at this point, reqStart and reqEnd are the absolute timestamp.
unsigned int fields = 0;
@ -1458,7 +1449,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
c_total = c_user + c_nice + c_syst + c_idle;
if (c_total - cl_total > 0){
if (c_total > cl_total){
cpu_use = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
}else{
cpu_use = 0;
@ -1556,8 +1547,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first
std::map<std::string, uint32_t> outputs;
unsigned long totViewers = 0, totInputs = 0, totOutputs = 0;
unsigned int tOut = Util::epoch() - STATS_DELAY;
unsigned int tIn = Util::epoch() - STATS_INPUT_DELAY;
unsigned int tOut = Util::bootSecs() - STATS_DELAY;
unsigned int tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
@ -1629,8 +1620,7 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
<< it->second.currOuts << "\n";
response << "mist_viewcount{stream=\"" << it->first << "\"}" << it->second.viewers << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"up\"}" << it->second.upBytes << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}"
<< it->second.downBytes << "\n";
response << "mist_bw{stream=\"" << it->first << "\",direction=\"down\"}" << it->second.downBytes << "\n";
}
}
H.Chunkify(response.str(), conn);
@ -1657,8 +1647,8 @@ void Controller::handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int
// collect the data first
std::map<std::string, uint32_t> outputs;
uint64_t totViewers = 0, totInputs = 0, totOutputs = 0;
uint64_t tOut = Util::epoch() - STATS_DELAY;
uint64_t tIn = Util::epoch() - STATS_INPUT_DELAY;
uint64_t tOut = Util::bootSecs() - STATS_DELAY;
uint64_t tIn = Util::bootSecs() - STATS_INPUT_DELAY;
// check all sessions
if (sessions.size()){
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){

View file

@ -1,5 +1,6 @@
#pragma once
#include <map>
#include <mist/comms.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/json.h>
@ -37,9 +38,8 @@ namespace Controller{
/// Whenever two of these objects are not equal, it will create a new session.
class sessIndex{
public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange &data);
sessIndex();
sessIndex(const Comms::Statistics &statComm, size_t id);
std::string ID;
std::string host;
unsigned int crc;
@ -57,7 +57,7 @@ namespace Controller{
class statStorage{
public:
void update(IPC::statExchange &data);
void update(Comms::Statistics &statComm, size_t index);
bool hasDataFor(unsigned long long);
statLog &getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
@ -87,12 +87,13 @@ namespace Controller{
void wipeOld(uint64_t);
void finish(uint64_t index);
void switchOverTo(statSession &newSess, uint64_t index);
void update(uint64_t index, IPC::statExchange &data);
void ping(const sessIndex &index, uint64_t disconnectPoint);
void update(uint64_t index, Comms::Statistics &data);
void dropSession(const sessIndex &index);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool isConnected();
bool isTracked();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
@ -110,6 +111,7 @@ namespace Controller{
extern std::map<sessIndex, statSession> sessions;
extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex;
extern uint64_t statDropoff;
struct triggerLog{
uint64_t totalCount;
@ -119,8 +121,12 @@ namespace Controller{
extern std::map<std::string, triggerLog> triggerStats;
void statLeadIn();
void statOnActive(size_t id);
void statOnDisconnect(size_t id);
void statLeadOut();
std::set<std::string> getActiveStreams(const std::string &prefix = "");
void parseStatistics(char *data, size_t len, unsigned int id);
void killStatistics(char *data, size_t len, unsigned int id);
void fillClients(JSON::Value &req, JSON::Value &rep);
void fillActive(JSON::Value &req, JSON::Value &rep, bool onlyNow = false);

View file

@ -139,7 +139,11 @@ namespace Controller{
void initState(){
tthread::lock_guard<tthread::mutex> guard(logMutex);
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, false, false); // max 1M of logs cached
if (!shmLogs || !shmLogs->mapped){
if (shmLogs){delete shmLogs;}
shmLogs = new IPC::sharedPage(SHM_STATE_LOGS, 1024 * 1024, true); // max 1M of logs cached
}
if (!shmLogs->mapped){
FAIL_MSG("Could not open memory page for logs buffer");
return;
@ -156,7 +160,11 @@ namespace Controller{
}
maxLogsRecs = (1024 * 1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, false, false); // max 1M of accesslogs cached
if (!shmAccs || !shmAccs->mapped){
if (shmAccs){delete shmAccs;}
shmAccs = new IPC::sharedPage(SHM_STATE_ACCS, 1024 * 1024, true); // max 1M of accesslogs cached
}
if (!shmAccs->mapped){
FAIL_MSG("Could not open memory page for access logs buffer");
return;
@ -176,7 +184,11 @@ namespace Controller{
}
maxAccsRecs = (1024 * 1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, false, false); // max 1M of stream data
if (!shmStrm || !shmStrm->mapped){
if (shmStrm){delete shmStrm;}
shmStrm = new IPC::sharedPage(SHM_STATE_STREAMS, 1024 * 1024, true); // max 1M of stream data
}
if (!shmStrm->mapped){
FAIL_MSG("Could not open memory page for stream data");
return;

View file

@ -295,8 +295,8 @@ namespace Controller{
Util::sanitizeName(cleaned);
std::string strmSource;
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
DTSC::Meta mData = Util::getStreamMeta(cleaned);
if (mData.sourceURI.size()){strmSource = mData.sourceURI;}
DTSC::Meta M(cleaned, false);
if (M && M.getSource().size()){strmSource = M.getSource();}
}
if (!strmSource.size()){
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));

View file

@ -24,30 +24,6 @@
#define SHARED_SECRET "empty"
#endif
static std::string readFile(std::string filename){
std::ifstream file(filename.c_str());
if (!file.good()){return "";}
file.seekg(0, std::ios::end);
unsigned int len = file.tellg();
file.seekg(0, std::ios::beg);
std::string out;
out.reserve(len);
unsigned int i = 0;
while (file.good() && i++ < len){out += file.get();}
file.close();
return out;
}
static bool writeFile(std::string filename, std::string &contents){
unlink(filename.c_str());
std::ofstream file(filename.c_str(), std::ios_base::trunc | std::ios_base::out);
if (!file.is_open()){return false;}
file << contents;
file.close();
chmod(filename.c_str(), S_IRWXU | S_IRWXG);
return true;
}
tthread::mutex updaterMutex;
uint8_t updatePerc = 0;
JSON::Value updates;

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,11 @@
#include <cstdlib>
#include <fstream>
#include <map>
#include <mist/bitfields.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/encryption.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
@ -13,9 +15,9 @@
namespace Mist{
struct booking{
int first;
int curKey;
int curPart;
uint32_t first;
uint32_t curKey;
uint32_t curPart;
};
class Input : public InOutBase{
@ -26,61 +28,63 @@ namespace Mist{
virtual int boot(int argc, char *argv[]);
virtual ~Input(){};
bool keepAlive();
void reloadClientMeta();
bool hasMeta() const;
static Util::Config *config;
virtual bool needsLock(){return !config->getBool("realtime");}
protected:
static void callbackWrapper(char *data, size_t len, unsigned int id);
virtual bool checkArguments() = 0;
virtual bool readHeader() = 0;
virtual bool readHeader();
virtual bool needHeader(){return !readExistingHeader();}
virtual bool preRun(){return true;}
virtual bool isSingular(){return !config->getBool("realtime");}
virtual bool readExistingHeader();
virtual bool atKeyFrame();
virtual void getNext(bool smart = true){}
virtual void seek(int seekTime){};
virtual void getNext(size_t idx = INVALID_TRACK_ID){}
virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
virtual void finish();
virtual bool keepRunning();
virtual bool openStreamSource(){return readHeader();}
virtual void closeStreamSource(){}
virtual void parseStreamHeader(){}
void play(int until = 0);
void playOnce();
void quitPlay();
void checkHeaderTimes(std::string streamFile);
virtual void removeUnused();
virtual void trackSelect(std::string trackSpec);
virtual void userCallback(char *data, size_t len, unsigned int id);
virtual void convert();
virtual void serve();
virtual void stream();
virtual size_t streamByteCount(){
return 0;
}; // For live streams: to update the stats with correct values.
virtual std::string streamMainLoop();
virtual std::string realtimeMainLoop();
bool isAlwaysOn();
virtual void userLeadIn();
virtual void userOnActive(size_t id);
virtual void userOnDisconnect(size_t id);
virtual void userLeadOut();
virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum);
bool bufferFrame(size_t track, uint32_t keyNum);
unsigned int packTime; /// Media-timestamp of the last packet.
int lastActive; /// Timestamp of the last time we received or sent something.
int initialTime;
int playing;
unsigned int playUntil;
bool isBuffer;
uint64_t activityCounter;
JSON::Value capa;
std::map<int, std::set<int> > keyTimes;
int64_t timeOffset;
std::map<size_t, std::set<uint64_t> > keyTimes;
// Create server for user pages
IPC::sharedServer userPage;
Comms::Users users;
size_t connectedUsers;
Encryption::AES aesCipher;
IPC::sharedPage streamStatus;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
std::map<size_t, std::map<uint32_t, size_t> > pageCounter;
static Input *singleton;
@ -93,6 +97,7 @@ namespace Mist{
DTSC::Packet srtPack;
uint64_t simStartTime;
};
void handleBuyDRM();
};
}// namespace Mist

View file

@ -72,7 +72,7 @@ namespace Mist{
if (ret != 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
FAIL_MSG("Could not open file: %s", errstr);
return false; // Couldn't open file
}
@ -81,7 +81,7 @@ namespace Mist{
if (ret < 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
FAIL_MSG("Could not find stream info: %s", errstr);
return false;
}
return true;
@ -160,12 +160,12 @@ namespace Mist{
return true;
}
void inputAV::getNext(bool smart){
void inputAV::getNext(){
AVPacket packet;
while (av_read_frame(pFormatCtx, &packet) >= 0){
// filter tracks we don't care about
if (!selectedTracks.count(packet.stream_index + 1)){
DEBUG_MSG(DLVL_HIGH, "Track %u not selected", packet.stream_index + 1);
HIGH_MSG("Track %u not selected", packet.stream_index + 1);
continue;
}
AVStream *strm = pFormatCtx->streams[packet.stream_index];
@ -187,7 +187,7 @@ namespace Mist{
thisPacket.null();
preRun();
// failure :-(
DEBUG_MSG(DLVL_FAIL, "getNext failed");
FAIL_MSG("getNext failed");
}
void inputAV::seek(int seekTime){

View file

@ -23,7 +23,7 @@ namespace Mist{
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void getNext();
void seek(int seekTime);
void trackSelect(std::string trackSpec);

View file

@ -47,7 +47,7 @@ namespace Mist{
Socket::Connection balConn(url.host, url.getPort(), true);
if (!balConn){
WARN_MSG("Failed to reach %s on port %lu", url.host.c_str(), url.getPort());
WARN_MSG("Failed to reach %s on port %" PRIu16, url.host.c_str(), url.getPort());
}else{
HTTP::Parser http;
http.url = "/" + url.path;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
#include <fstream>
#include "input.h"
#include <fstream>
#include <mist/dtsc.h>
#include <mist/shared_memory.h>
@ -12,11 +11,12 @@ namespace Mist{
void onCrash();
private:
void fillBufferDetails(JSON::Value &details);
unsigned int bufferTime;
unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
void fillBufferDetails(JSON::Value &details) const;
uint64_t bufferTime;
uint64_t cutTime;
size_t segmentSize; /*LTS*/
uint64_t lastReTime; /*LTS*/
uint64_t finalMillis;
bool hasPush;
bool resumeMode;
IPC::semaphore *liveMeta;
@ -28,29 +28,30 @@ namespace Mist{
void updateMeta();
bool readHeader(){return false;}
bool needHeader(){return false;}
void getNext(bool smart = true){}
void updateTrackMeta(unsigned long tNum);
void updateMetaFromPage(unsigned long tNum, unsigned long pageNum);
void seek(int seekTime){}
void trackSelect(std::string trackSpec){}
bool removeKey(unsigned int tid);
void getNext(size_t idx = INVALID_TRACK_ID){};
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){};
void removeTrack(size_t tid);
bool removeKey(size_t tid);
void removeUnused();
void eraseTrackDataPages(unsigned long tid);
void finish();
void userCallback(char *data, size_t len, unsigned int id);
std::set<unsigned long> negotiatingTracks;
std::set<unsigned long> activeTracks;
std::map<unsigned long, unsigned long long> lastUpdated;
std::map<unsigned long, unsigned long long> negotiationTimeout;
/// Maps trackid to a pagenum->pageData map
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
std::map<unsigned long, char *> pushLocation;
inputBuffer *singleton;
uint64_t retrieveSetting(DTSC::Scan &streamCfg, const std::string &setting, const std::string &option = "");
void userLeadIn();
void userOnActive(size_t id);
void userOnDisconnect(size_t id);
void userLeadOut();
// This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, std::string> initData;
std::map<size_t, std::string> initData;
uint64_t findTrack(const std::string &trackVal);
void checkProcesses(const JSON::Value &procs); // LTS
std::map<std::string, pid_t> runningProcs; // LTS
std::set<size_t> generatePids;
std::map<size_t, std::set<size_t> > sourcePids;
};
}// namespace Mist

View file

@ -60,10 +60,19 @@ namespace Mist{
capa["optional"]["segmentsize"]["type"] = "uint";
capa["optional"]["segmentsize"]["default"] = 1900;
/*LTS-END*/
F = NULL;
lockCache = false;
lockNeeded = false;
}
bool inputDTSC::needsLock(){
return config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
if (!lockCache){
lockNeeded =
config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
lockCache = true;
}
return lockNeeded;
}
void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port,
@ -129,37 +138,40 @@ namespace Mist{
void inputDTSC::parseStreamHeader(){
while (srcConn.connected() && config->is_active){
srcConn.spool();
if (srcConn.Received().available(8)){
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC"){
// Command message
std::string toRec = srcConn.Received().copy(8);
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
if (!srcConn.Received().available(8 + rSize)){
nProxy.userClient.keepAlive();
Util::sleep(100);
continue; // abort - not enough data yet
}
// Ignore initial DTCM message, as this is a "hi" message from the server
if (srcConn.Received().copy(4) == "DTCM"){
srcConn.Received().remove(8 + rSize);
}else{
std::string dataPacket = srcConn.Received().remove(8 + rSize);
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
myMeta.reinit(metaPack);
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
continueNegotiate(it->first, true);
}
break;
}
}else{
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
break;
}
}else{
if (!srcConn.Received().available(8)){
Util::sleep(100);
nProxy.userClient.keepAlive();
keepAlive();
continue;
}
if (srcConn.Received().copy(4) != "DTCM" && srcConn.Received().copy(4) != "DTSC"){
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
break;
}
// Command message
std::string toRec = srcConn.Received().copy(8);
uint32_t rSize = Bit::btohl(toRec.c_str() + 4);
if (!srcConn.Received().available(8 + rSize)){
keepAlive();
Util::sleep(100);
continue; // abort - not enough data yet
}
// Ignore initial DTCM message, as this is a "hi" message from the server
if (srcConn.Received().copy(4) == "DTCM"){
srcConn.Received().remove(8 + rSize);
continue;
}
std::string dataPacket = srcConn.Received().remove(8 + rSize);
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
DTSC::Meta nM("", metaPack.getScan());
meta.reInit(streamName, false);
meta.merge(nM);
std::set<size_t> validTracks = M.getMySourceTracks(getpid());
userSelect.clear();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
userSelect[*it].reload(streamName, *it, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
}
break;
}
}
@ -194,25 +206,26 @@ namespace Mist{
void inputDTSC::closeStreamSource(){srcConn.close();}
bool inputDTSC::checkArguments(){
if (!needsLock()){
return true;
}else{
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;
}
if (!needsLock()){return true;}
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;
}
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile){return false;}
}
// open File
F = fopen(config->getString("input").c_str(), "r+b");
if (!F){
HIGH_MSG("Could not open file %s", config->getString("input").c_str());
return false;
}
fseek(F, 0, SEEK_SET);
return true;
}
@ -222,120 +235,215 @@ namespace Mist{
}
bool inputDTSC::readHeader(){
if (!inFile){return false;}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0){
DEBUG_MSG(DLVL_FAIL, "Missing external header file");
return false;
if (!F){return false;}
if (!readExistingHeader()){
size_t moreHeader = 0;
do{
// read existing header from file here?
char hdr[8];
fseek(F, moreHeader, SEEK_SET);
if (fread(hdr, 8, 1, F) != 1){
FAIL_MSG("Could not read header @ bpos %zu", moreHeader);
return false;
}
if (memcmp(hdr, DTSC::Magic_Header, 4)){
FAIL_MSG("File does not have a DTSC header @ bpos %zu", moreHeader);
return false;
}
size_t pktLen = Bit::btohl(hdr + 4);
char *pkt = (char *)malloc(8 + pktLen * sizeof(char));
fseek(F, moreHeader, SEEK_SET);
if (fread(pkt, 8 + pktLen, 1, F) != 1){
free(pkt);
FAIL_MSG("Could not read packet @ bpos %zu", moreHeader);
}
DTSC::Scan S(pkt + 8, pktLen);
if (S.hasMember("moreheader") && S.getMember("moreheader").asInt()){
moreHeader = S.getMember("moreheader").asInt();
}else{
moreHeader = 0;
meta.reInit(streamName, moreHeader);
}
free(pkt);
}while (moreHeader);
}
myMeta = DTSC::Meta(inFile.getMeta());
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size());
return true;
return meta;
}
void inputDTSC::getNext(bool smart){
void inputDTSC::getNext(size_t idx){
if (!needsLock()){
thisPacket.reInit(srcConn);
while (config->is_active){
if (thisPacket.getVersion() == DTSC::DTCM){
nProxy.userClient.keepAlive();
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd == "reset"){
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta newMeta;
newMeta.reinit(thisPacket);
// Detect new tracks
std::set<unsigned int> newTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
it != newMeta.tracks.end(); it++){
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
}
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
INFO_MSG("Reset: adding track %d", *it);
myMeta.tracks[*it] = newMeta.tracks[*it];
continueNegotiate(*it, true);
}
// Detect removed tracks
std::set<unsigned int> deletedTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (!newMeta.tracks.count(it->first)){deletedTracks.insert(it->first);}
}
for (std::set<unsigned int>::iterator it = deletedTracks.begin();
it != deletedTracks.end(); it++){
INFO_MSG("Reset: deleting track %d", *it);
myMeta.tracks.erase(*it);
}
thisPacket.reInit(srcConn); // read the next packet before continuing
}else{
myMeta = DTSC::Meta();
}
}else{
thisPacket.reInit(srcConn); // read the next packet before continuing
}
continue; // parse the next packet before returning
}else if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta newMeta;
newMeta.reinit(thisPacket);
std::set<unsigned int> newTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin();
it != newMeta.tracks.end(); it++){
if (!myMeta.tracks.count(it->first)){newTracks.insert(it->first);}
}
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
INFO_MSG("New header: adding track %d (%s)", *it, newMeta.tracks[*it].type.c_str());
myMeta.tracks[*it] = newMeta.tracks[*it];
continueNegotiate(*it, true);
}
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
// We now know we have either a data packet, or an error.
if (!thisPacket.getTrackId()){
if (thisPacket.getVersion() == DTSC::DTSC_V2){
WARN_MSG("Received bad packet for stream %s: %llu@%llu", streamName.c_str(),
thisPacket.getTrackId(), thisPacket.getTime());
}else{
// All types except data packets are handled above, so if it's not a V2 data packet, we assume corruption
WARN_MSG("Invalid packet header for stream %s", streamName.c_str());
}
}
return; // we have a packet
getNextFromStream(idx);
return;
}
if (!currentPositions.size()){
WARN_MSG("No seek positions set - returning empty packet.");
thisPacket.null();
return;
}
seekPos thisPos = *currentPositions.begin();
fseek(F, thisPos.bytePos, SEEK_SET);
if (feof(F)){
thisPacket.null();
return;
}
clearerr(F);
currentPositions.erase(currentPositions.begin());
lastreadpos = ftell(F);
if (fread(buffer, 4, 1, F) != 1){
if (feof(F)){
INFO_MSG("End of file reached while seeking @ %" PRIu64, lastreadpos);
}else{
ERROR_MSG("Could not seek to next @ %" PRIu64, lastreadpos);
}
thisPacket.null();
return;
}
if (memcmp(buffer, DTSC::Magic_Header, 4) == 0){
seekNext(thisPacket.getTime(), thisPacket.getTrackId(), true);
getNext(idx);
return;
}
uint8_t version = 0;
if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0){version = 1;}
if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0){version = 2;}
if (version == 0){
ERROR_MSG("Invalid packet header @ %#" PRIx64 " - %.4s != %.4s @ %" PRIu64, lastreadpos,
buffer, DTSC::Magic_Packet2, lastreadpos);
thisPacket.null();
return;
}
if (fread(buffer + 4, 4, 1, F) != 1){
ERROR_MSG("Could not read packet size @ %" PRIu64, lastreadpos);
thisPacket.null();
return;
}
std::string pBuf;
uint32_t packSize = Bit::btohl(buffer + 4);
pBuf.resize(8 + packSize);
memcpy((char *)pBuf.data(), buffer, 8);
if (fread((void *)(pBuf.data() + 8), packSize, 1, F) != 1){
ERROR_MSG("Could not read packet @ %" PRIu64, lastreadpos);
thisPacket.null();
return;
}
thisPacket.reInit(pBuf.data(), pBuf.size());
seekNext(thisPos.seekTime, thisPos.trackID);
fseek(F, thisPos.bytePos, SEEK_SET);
}
void inputDTSC::getNextFromStream(size_t idx){
thisPacket.reInit(srcConn);
while (config->is_active){
if (thisPacket.getVersion() == DTSC::DTCM){
// userClient.keepAlive();
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd != "reset"){
thisPacket.reInit(srcConn);
continue;
}
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() != DTSC::DTSC_HEAD){
meta.clear();
continue;
}
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, true, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta nM("", thisPacket.getScan());
meta.merge(nM, false, false);
thisPacket.reInit(srcConn); // read the next packet before continuing
continue; // parse the next packet before returning
}
thisPacket = DTSC::Packet(thisPacket, M.trackIDToIndex(thisPacket.getTrackId(), getpid()));
return; // we have a packet
}
}
void inputDTSC::seek(uint64_t seekTime, size_t idx){
currentPositions.clear();
if (idx != INVALID_TRACK_ID){
seekNext(seekTime, idx, true);
}else{
if (smart){
inFile.seekNext();
}else{
inFile.parseNext();
std::set<size_t> tracks = M.getValidTracks();
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
seekNext(seekTime, *it, true);
}
thisPacket = inFile.getPacket();
}
}
void inputDTSC::seek(int seekTime){
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
void inputDTSC::seekNext(uint64_t ms, size_t trackIdx, bool forceSeek){
seekPos tmpPos;
tmpPos.trackID = trackIdx;
if (!forceSeek && thisPacket && ms >= thisPacket.getTime() && trackIdx >= thisPacket.getTrackId()){
tmpPos.seekTime = thisPacket.getTime();
tmpPos.bytePos = ftell(F);
}else{
tmpPos.seekTime = 0;
tmpPos.bytePos = 0;
}
if (feof(F)){
clearerr(F);
fseek(F, 0, SEEK_SET);
tmpPos.bytePos = 0;
tmpPos.seekTime = 0;
}
DTSC::Keys keys(M.keys(trackIdx));
uint32_t keyNum = keys.getNumForTime(ms);
if (keys.getTime(keyNum) > tmpPos.seekTime){
tmpPos.seekTime = keys.getTime(keyNum);
tmpPos.bytePos = keys.getBpos(keyNum);
}
bool foundPacket = false;
while (!foundPacket){
lastreadpos = ftell(F);
if (feof(F)){
WARN_MSG("Reached EOF during seek to %" PRIu64 " in track %zu - aborting @ %" PRIu64, ms,
trackIdx, lastreadpos);
return;
}
// Seek to first packet after ms.
fseek(F, tmpPos.bytePos, SEEK_SET);
lastreadpos = ftell(F);
// read the header
char header[20];
if (fread((void *)header, 20, 1, F) != 1){
WARN_MSG("Could not read header from file. Much sadface.");
return;
}
// check if packetID matches, if not, skip size + 8 bytes.
uint32_t packSize = Bit::btohl(header + 4);
uint32_t packID = Bit::btohl(header + 8);
if (memcmp(header, DTSC::Magic_Packet2, 4) != 0 || packID != trackIdx){
if (memcmp(header, "DT", 2) != 0){
WARN_MSG("Invalid header during seek to %" PRIu64 " in track %zu @ %" PRIu64
" - resetting bytePos from %" PRIu64 " to zero",
ms, trackIdx, lastreadpos, tmpPos.bytePos);
tmpPos.bytePos = 0;
continue;
}
tmpPos.bytePos += 8 + packSize;
continue;
}
// get timestamp of packet, if too large, break, if not, skip size bytes.
uint64_t myTime = Bit::btohll(header + 12);
tmpPos.seekTime = myTime;
if (myTime >= ms){
foundPacket = true;
}else{
trackSpec = "";
tmpPos.bytePos += 8 + packSize;
continue;
}
}
inFile.selectTracks(selectedTracks);
// HIGH_MSG("Seek to %u:%d resulted in %lli", trackIdx, ms, tmpPos.seekTime);
if (tmpPos.seekTime > 0xffffffffffffff00ll){tmpPos.seekTime = 0;}
currentPositions.insert(tmpPos);
return;
}
}// namespace Mist

View file

@ -1,7 +1,27 @@
#include "input.h"
#include <set>
#include <stdio.h> //for FILE
#include <mist/dtsc.h>
namespace Mist{
///\brief A simple structure used for ordering byte seek positions.
struct seekPos{
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator<(const seekPos &rhs) const{
if (seekTime < rhs.seekTime){return true;}
if (seekTime == rhs.seekTime){return trackID < rhs.trackID;}
return false;
}
uint64_t seekTime; ///< Stores the timestamp of the DTSC packet referenced by this structure.
uint64_t bytePos; ///< Stores the byteposition of the DTSC packet referenced by this structure.
uint32_t trackID; ///< Stores the track the DTSC packet referenced by this structure is
///< associated with.
};
class inputDTSC : public Input{
public:
inputDTSC(Util::Config *cfg);
@ -15,13 +35,24 @@ namespace Mist{
bool checkArguments();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void getNextFromStream(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
DTSC::File inFile;
FILE *F;
Socket::Connection srcConn;
bool lockCache;
bool lockNeeded;
std::set<seekPos> currentPositions;
uint64_t lastreadpos;
char buffer[8];
void seekNext(uint64_t ms, size_t trackIdx, bool forceSeek = false);
};
}// namespace Mist

View file

@ -41,6 +41,7 @@ namespace Mist{
lastClusterTime = 0;
bufferedPacks = 0;
wantBlocks = true;
totalBytes = 0;
}
std::string ASStoSRT(const char *ptr, uint32_t len){
@ -112,13 +113,15 @@ namespace Mist{
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)){
int64_t toRead = needed - ptr.size();
if (!fread(ptr + ptr.size(), toRead, 1, inFile)){
// We assume if there is no current data buffered, that we are at EOF and don't print a warning
if (ptr.size()){
FAIL_MSG("Could not read more data! (have %lu, need %lu)", ptr.size(), needed);
FAIL_MSG("Could not read more data! (have %zu, need %" PRIu32 ")", ptr.size(), needed);
}
return false;
}
totalBytes += toRead;
ptr.size() = needed;
needed = EBML::Element::needBytes(ptr, ptr.size(), readingMinimal);
if (ptr.size() >= needed){
@ -141,26 +144,26 @@ namespace Mist{
lastClusterBPos = bp;
}
}
DONTEVEN_MSG("Found a cluster at position %llu", lastClusterBPos);
DONTEVEN_MSG("Found a cluster at position %" PRIu64, lastClusterBPos);
}
if (E.getID() == EBML::EID_TIMECODE){
lastClusterTime = E.getValUInt();
DONTEVEN_MSG("Cluster time %llu ms", lastClusterTime);
DONTEVEN_MSG("Cluster time %" PRIu64 " ms", lastClusterTime);
}
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);
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getCodec(*it) == "PCMLE"){
meta.setCodec(*it, "PCM");
swapEndianness.insert(*it);
}
}
if (myMeta.inputLocalVars.isMember("timescale")){
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
if (M.inputLocalVars.isMember("timescale")){
timeScale = ((double)M.inputLocalVars["timescale"].asInt()) / 1000000.0;
}
return true;
}
@ -169,6 +172,7 @@ namespace Mist{
if (!inFile){return false;}
// Create header file from file
uint64_t bench = Util::getMicros();
if (!meta){meta.reInit(streamName);}
while (readElement()){
EBML::Element E(ptr, readingMinimal);
@ -178,7 +182,7 @@ namespace Mist{
ERROR_MSG("Track without track number encountered, ignoring");
continue;
}
uint64_t trackNo = tmpElem.getValUInt();
uint64_t trackID = tmpElem.getValUInt();
tmpElem = E.findChild(EBML::EID_CODECID);
if (!tmpElem){
ERROR_MSG("Track without codec id encountered, ignoring");
@ -311,32 +315,33 @@ namespace Mist{
}
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"){
size_t idx = M.trackIDToIndex(trackID, getpid());
if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
meta.setID(idx, trackID);
meta.setLang(idx, lang);
meta.setCodec(idx, trueCodec);
meta.setType(idx, trueType);
meta.setInit(idx, init);
if (trueType == "video"){
tmpElem = E.findChild(EBML::EID_PIXELWIDTH);
Trk.width = tmpElem ? tmpElem.getValUInt() : 0;
meta.setWidth(idx, tmpElem ? tmpElem.getValUInt() : 0);
tmpElem = E.findChild(EBML::EID_PIXELHEIGHT);
Trk.height = tmpElem ? tmpElem.getValUInt() : 0;
Trk.fpks = 0;
meta.setHeight(idx, tmpElem ? tmpElem.getValUInt() : 0);
meta.setFpks(idx, 0);
}
if (Trk.type == "audio"){
if (trueType == "audio"){
tmpElem = E.findChild(EBML::EID_CHANNELS);
Trk.channels = tmpElem ? tmpElem.getValUInt() : 1;
meta.setChannels(idx, tmpElem ? tmpElem.getValUInt() : 1);
tmpElem = E.findChild(EBML::EID_BITDEPTH);
Trk.size = tmpElem ? tmpElem.getValUInt() : 0;
meta.setSize(idx, tmpElem ? tmpElem.getValUInt() : 0);
tmpElem = E.findChild(EBML::EID_SAMPLINGFREQUENCY);
Trk.rate = tmpElem ? (int)tmpElem.getValFloat() : 8000;
meta.setRate(idx, tmpElem ? (int)tmpElem.getValFloat() : 8000);
}
INFO_MSG("Detected track: %s", Trk.getIdentifier().c_str());
INFO_MSG("Detected track: %s", M.getTrackIdentifier(idx).c_str());
}
if (E.getID() == EBML::EID_TIMECODESCALE){
uint64_t timeScaleVal = E.getValUInt();
myMeta.inputLocalVars["timescale"] = timeScaleVal;
meta.inputLocalVars["timescale"] = timeScaleVal;
timeScale = ((double)timeScaleVal) / 1000000.0;
}
// Live streams stop parsing the header as soon as the first Cluster is encountered
@ -346,34 +351,35 @@ namespace Mist{
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");
bool isAudio = (Trk.type == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
size_t idx = meta.trackIDToIndex(tNum, getpid());
bool isVideo = (M.getType(idx) == "video");
bool isAudio = (M.getType(idx) == "audio");
bool isASS = (M.getCodec(idx) == "subtitle" && M.getInit(idx).size());
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe()){
while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(true);
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
meta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
TP.remove();
}
TP.flush();
}
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){
if (Trk.codec == "AAC"){
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){
if (M.getCodec(idx) == "AAC"){
newTime += (1000000 / M.getRate(idx)) / timeScale; // assume ~1000 samples per frame
}else if (M.getCodec(idx) == "MP3"){
newTime += (1152000 / M.getRate(idx)) / timeScale; // 1152 samples per frame
}else if (M.getCodec(idx) == "DTS"){
// Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data
// is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale;
newTime += (512000 / M.getRate(idx)) / timeScale;
}else{
newTime += 1 / timeScale;
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
M.getCodec(idx).c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
@ -388,7 +394,7 @@ namespace Mist{
}
while (TP.hasPackets()){
packetData &C = TP.getPacketData(isVideo);
myMeta.update(C.time, C.offset, C.track, C.dsize, C.bpos, C.key);
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
TP.remove();
}
}
@ -398,23 +404,25 @@ namespace Mist{
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);
packetData &C =
TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
meta.update(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.dsize, C.bpos, C.key);
TP.remove();
}
}
}
bench = Util::getMicros(bench);
INFO_MSG("Header generated in %llu ms", bench / 1000);
INFO_MSG("Header generated in %" PRIu64 " ms", bench / 1000);
clearPredictors();
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);
M.toFile(config->getString("input") + ".dtsh");
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getCodec(*it) == "PCMLE"){
meta.setCodec(*it, "PCM");
swapEndianness.insert(*it);
}
}
return true;
@ -422,7 +430,7 @@ namespace Mist{
void InputEBML::fillPacket(packetData &C){
if (swapEndianness.count(C.track)){
switch (myMeta.tracks[C.track].size){
switch (M.getSize(M.trackIDToIndex(C.track, getpid()))){
case 16:{
char *ptr = C.ptr;
uint32_t ptrSize = C.dsize;
@ -455,16 +463,18 @@ namespace Mist{
}break;
}
}
thisPacket.genericFill(C.time, C.offset, C.track, C.ptr, C.dsize, C.bpos, C.key);
thisPacket.genericFill(C.time, C.offset, M.trackIDToIndex(C.track, getpid()), C.ptr, C.dsize,
C.bpos, C.key);
}
void InputEBML::getNext(bool smart){
void InputEBML::getNext(size_t idx){
// 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");
packetData &C =
TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
fillPacket(C);
TP.remove();
--bufferedPacks;
@ -481,7 +491,7 @@ namespace Mist{
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");
packetData &C = TP.getPacketData(M.getType(M.trackIDToIndex(it->first, getpid())) == "video");
fillPacket(C);
TP.remove();
--bufferedPacks;
@ -494,7 +504,8 @@ namespace Mist{
return;
}
B = EBML::Block(ptr);
}while (!B || B.getType() != EBML::ELEM_BLOCK || !selectedTracks.count(B.getTrackNum()));
}while (!B || B.getType() != EBML::ELEM_BLOCK ||
(idx != INVALID_TRACK_ID && M.getID(idx) != B.getTrackNum()));
}else{
B = EBML::Block(ptr);
}
@ -502,10 +513,10 @@ namespace Mist{
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");
bool isAudio = (Trk.type == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
size_t trackIdx = M.trackIDToIndex(tNum, getpid());
bool isVideo = (M.getType(trackIdx) == "video");
bool isAudio = (M.getType(trackIdx) == "audio");
bool isASS = (M.getCodec(trackIdx) == "subtitle" && M.getInit(trackIdx).size());
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe() && bufferedPacks){
@ -523,18 +534,19 @@ namespace Mist{
for (uint64_t frameNo = 0; frameNo < B.getFrameCount(); ++frameNo){
if (frameNo){
if (Trk.codec == "AAC"){
newTime += (1000000 / Trk.rate) / timeScale; // assume ~1000 samples per frame
}else if (Trk.codec == "MP3"){
newTime += (1152000 / Trk.rate) / timeScale; // 1152 samples per frame
}else if (Trk.codec == "DTS"){
if (M.getCodec(trackIdx) == "AAC"){
newTime += (1000000 / M.getRate(trackIdx)) / timeScale; // assume ~1000 samples per frame
}else if (M.getCodec(trackIdx) == "MP3"){
newTime += (1152000 / M.getRate(trackIdx)) / timeScale; // 1152 samples per frame
}else if (M.getCodec(trackIdx) == "DTS"){
// Assume 512 samples per frame (DVD default)
// actual amount can be calculated from data, but data
// is not available during header generation...
// See: http://www.stnsoft.com/DVD/dtshdr.html
newTime += (512000 / Trk.rate) / timeScale;
newTime += (512000 / M.getRate(trackIdx)) / timeScale;
}else{
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!",
M.getCodec(trackIdx).c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
@ -560,22 +572,26 @@ namespace Mist{
}else{
// We didn't set thisPacket yet. Read another.
// Recursing is fine, this can only happen a few times in a row.
getNext(smart);
getNext(idx);
}
}
void InputEBML::seek(int seekTime){
void InputEBML::seek(uint64_t seekTime, size_t idx){
wantBlocks = true;
clearPredictors();
bufferedPacks = 0;
uint64_t mainTrack = getMainSelectedTrack();
DTSC::Track Trk = myMeta.tracks[mainTrack];
bool isVideo = (Trk.type == "video");
uint64_t seekPos = Trk.keys[0].getBpos();
DTSC::Keys keys(M.keys(mainTrack));
DTSC::Parts parts(M.parts(mainTrack));
uint64_t seekPos = keys.getBpos(0);
// Replay the parts of the previous keyframe, so the timestaps match up
for (unsigned int i = 1; i < Trk.keys.size(); i++){
if (Trk.keys[i].getTime() > seekTime){break;}
seekPos = Trk.keys[i].getBpos();
uint64_t partCount = 0;
for (size_t i = 0; i < keys.getEndValid(); i++){
if (keys.getTime(i) > seekTime){break;}
partCount += keys.getParts(i);
DONTEVEN_MSG("Seeking to %" PRIu64 ", found %" PRIu64 "...", seekTime, keys.getTime(i));
seekPos = keys.getBpos(i);
}
Util::fseek(inFile, seekPos, SEEK_SET);
}

View file

@ -11,14 +11,7 @@ namespace Mist{
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;
}
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;
@ -132,10 +125,12 @@ namespace Mist{
p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
}
lastTime = p.time;
INSANE_MSG("Outputting%s %llu+%llu (#%llu, Max=%llu), display at %llu", (p.key ? "KEY" : ""),
p.time, p.offset, rem, maxOffset, p.time + p.offset);
INSANE_MSG("Outputting%s %" PRIu64 "+%" PRIu64 " (#%" PRIu64 ", Max=%" PRIu64
"), display at %" PRIu64,
(p.key ? "KEY" : ""), p.time, p.offset, rem, maxOffset, p.time + p.offset);
return p;
}
void add(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, bool isVideo, void *dataPtr = 0){
if (!ctr){lowestTime = packTime;}
@ -155,13 +150,16 @@ namespace Mist{
bool needsLock();
protected:
virtual size_t streamByteCount(){
return totalBytes;
}; // For live streams: to update the stats with correct values.
void fillPacket(packetData &C);
bool checkArguments();
bool preRun();
bool readHeader();
bool readElement();
void getNext(bool smart = true);
void seek(int seekTime);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void clearPredictors();
FILE *inFile;
Util::ResizeablePointer ptr;
@ -177,6 +175,7 @@ namespace Mist{
bool needHeader(){return needsLock() && !readExistingHeader();}
double timeScale;
bool wantBlocks;
size_t totalBytes;
};
}// namespace Mist

View file

@ -28,6 +28,8 @@ namespace Mist{
capa["codecs"][0u][1u].append("MP3");
}
inputFLV::~inputFLV(){}
bool inputFLV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
@ -77,45 +79,49 @@ namespace Mist{
bool inputFLV::readHeader(){
if (!inFile){return false;}
meta.reInit(config->getString("streamname"));
// Create header file from FLV data
Util::fseek(inFile, 13, SEEK_SET);
AMF::Object amf_storage;
long long int lastBytePos = 13;
uint64_t lastBytePos = 13;
uint64_t bench = Util::getMicros();
while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){
tmpTag.toMeta(myMeta, amf_storage);
tmpTag.toMeta(meta, amf_storage);
if (!tmpTag.getDataLen()){continue;}
if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;}
myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getDataLen(),
lastBytePos, tmpTag.isKeyframe);
size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
if (tNumber != INVALID_TRACK_ID){
meta.update(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getDataLen(), lastBytePos,
tmpTag.isKeyframe);
}
lastBytePos = Util::ftell(inFile);
}
}
bench = Util::getMicros(bench);
INFO_MSG("Header generated in %llu ms: @%lld, %s, %s", bench / 1000, lastBytePos,
myMeta.vod ? "VoD" : "NOVoD", myMeta.live ? "Live" : "NOLive");
INFO_MSG("Header generated in %" PRIu64 " ms: @%" PRIu64 ", %s, %s", bench / 1000, lastBytePos,
M.getVod() ? "VoD" : "NOVoD", M.getLive() ? "Live" : "NOLive");
if (FLV::Parse_Error){
tmpTag = FLV::Tag();
FLV::Parse_Error = false;
ERROR_MSG("Stopping at FLV parse error @%lld: %s", lastBytePos, FLV::Error_Str.c_str());
ERROR_MSG("Stopping at FLV parse error @%" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
}
myMeta.toFile(config->getString("input") + ".dtsh");
M.toFile(config->getString("input") + ".dtsh");
Util::fseek(inFile, 13, SEEK_SET);
return true;
}
void inputFLV::getNext(bool smart){
long long int lastBytePos = Util::ftell(inFile);
if (selectedTracks.size() == 1){
void inputFLV::getNext(size_t idx){
uint64_t lastBytePos = Util::ftell(inFile);
if (idx != INVALID_TRACK_ID){
uint8_t targetTag = 0x08;
if (selectedTracks.count(1)){targetTag = 0x09;}
if (selectedTracks.count(3)){targetTag = 0x12;}
if (M.getType(idx) == "video"){targetTag = 0x09;}
if (M.getType(idx) == "meta"){targetTag = 0x12;}
FLV::seekToTagType(inFile, targetTag);
}
while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){
if (!selectedTracks.count(tmpTag.getTrackID())){
if (idx != INVALID_TRACK_ID && M.getID(idx) != tmpTag.getTrackID()){
lastBytePos = Util::ftell(inFile);
continue;
}
@ -129,22 +135,22 @@ namespace Mist{
if (FLV::Parse_Error){
FLV::Parse_Error = false;
tmpTag = FLV::Tag();
FAIL_MSG("FLV error @ %lld: %s", lastBytePos, FLV::Error_Str.c_str());
FAIL_MSG("FLV error @ %" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str());
thisPacket.null();
return;
}
if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){
return getNext();
return getNext(idx);
}
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(),
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data
size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid());
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getData(),
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe);
DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()];
if (trk.codec == "PCM" && trk.size == 16){
if (M.getCodec(idx) == "PCM" && M.getSize(idx) == 16){
char *ptr = 0;
size_t ptrSize = 0;
thisPacket.getString("data", ptr, ptrSize);
for (uint32_t i = 0; i < ptrSize; i += 2){
for (size_t i = 0; i < ptrSize; i += 2){
char tmpchar = ptr[i];
ptr[i] = ptr[i + 1];
ptr[i + 1] = tmpchar;
@ -152,29 +158,12 @@ namespace Mist{
}
}
void inputFLV::seek(int seekTime){
void inputFLV::seek(uint64_t seekTime, size_t idx){
// We will seek to the corresponding keyframe of the video track if selected, otherwise audio
// keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio.
int trackSeek = (selectedTracks.count(1) ? 1 : 2);
uint64_t seekPos = myMeta.tracks[trackSeek].keys[0].getBpos();
for (unsigned int i = 0; i < myMeta.tracks[trackSeek].keys.size(); i++){
if (myMeta.tracks[trackSeek].keys[i].getTime() > seekTime){break;}
seekPos = myMeta.tracks[trackSeek].keys[i].getBpos();
}
Util::fseek(inFile, seekPos, SEEK_SET);
}
void inputFLV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
size_t seekTrack = (idx == INVALID_TRACK_ID ? M.mainTrack() : idx);
DTSC::Keys keys(M.keys(seekTrack));
uint32_t keyNum = keys.getNumForTime(seekTime);
Util::fseek(inFile, keys.getBpos(keyNum), SEEK_SET);
}
}// namespace Mist

View file

@ -6,15 +6,15 @@ namespace Mist{
class inputFLV : public Input{
public:
inputFLV(Util::Config *cfg);
~inputFLV();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
bool keepRunning();
FLV::Tag tmpTag;
uint64_t lastModTime;

View file

@ -11,6 +11,8 @@ namespace Mist{
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
void getNext(size_t idx = INVALID_TRACK_ID){}
void seek(uint64_t time, size_t idx = INVALID_TRACK_ID){}
};
}// namespace Mist

View file

@ -17,10 +17,9 @@ namespace Mist{
inputProcess = 0;
}
bool InputH264::preRun(){
bool InputH264::openStreamSource(){
if (config->getString("input") != "-"){
std::string input = config->getString("input");
const char *argv[2];
input = input.substr(10);
char *args[128];
@ -50,15 +49,20 @@ namespace Mist{
myConn.open(fileno(stdout), fileno(stdin));
}
myConn.Received().splitter.assign("\000\000\001", 3);
myMeta.vod = false;
myMeta.live = true;
myMeta.tracks[1].type = "video";
myMeta.tracks[1].codec = "H264";
myMeta.tracks[1].trackID = 1;
waitsSinceData = 0;
return true;
}
void InputH264::parseStreamHeader(){
tNumber = meta.addTrack();
meta.setType(tNumber, "video");
meta.setCodec(tNumber, "H264");
meta.setID(tNumber, tNumber);
waitsSinceData = 0;
INFO_MSG("Waiting for init data...");
while (myConn && !M.getInit(tNumber).size()){getNext();}
INFO_MSG("Init data received!");
}
bool InputH264::checkArguments(){
std::string input = config->getString("input");
if (input != "-" && input.substr(0, 10) != "h264-exec:"){
@ -68,7 +72,7 @@ namespace Mist{
return true;
}
void InputH264::getNext(bool smart){
void InputH264::getNext(size_t idx){
do{
if (!myConn.spool()){
Util::sleep(25);
@ -87,18 +91,18 @@ namespace Mist{
while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;}
if (!nalSize){continue;}
uint8_t nalType = NAL.data()[0] & 0x1F;
INSANE_MSG("NAL unit, type %u, size %lu", nalType, nalSize);
INSANE_MSG("NAL unit, type %u, size %" PRIu32, nalType, nalSize);
if (nalType == 7 || nalType == 8){
if (nalType == 7){spsInfo = NAL.substr(0, nalSize);}
if (nalType == 8){ppsInfo = NAL.substr(0, nalSize);}
if (!myMeta.tracks[1].init.size() && spsInfo.size() && ppsInfo.size()){
if (!meta.getInit(tNumber).size() && spsInfo.size() && ppsInfo.size()){
h264::sequenceParameterSet sps(spsInfo.data(), spsInfo.size());
h264::SPSMeta spsChar = sps.getCharacteristics();
myMeta.tracks[1].width = spsChar.width;
myMeta.tracks[1].height = spsChar.height;
myMeta.tracks[1].fpks = spsChar.fps * 1000;
if (myMeta.tracks[1].fpks < 100 || myMeta.tracks[1].fpks > 1000000){
myMeta.tracks[1].fpks = 0;
meta.setWidth(tNumber, spsChar.width);
meta.setHeight(tNumber, spsChar.height);
meta.setFpks(tNumber, spsChar.fps * 1000);
if (M.getFpks(tNumber) < 100 || M.getFpks(tNumber) > 1000000){
meta.setFpks(tNumber, 0);
}
MP4::AVCC avccBox;
avccBox.setVersion(1);
@ -109,14 +113,14 @@ namespace Mist{
avccBox.setSPS(spsInfo);
avccBox.setPPSCount(1);
avccBox.setPPS(ppsInfo);
myMeta.tracks[1].init = std::string(avccBox.payload(), avccBox.payloadSize());
meta.setInit(tNumber, avccBox.payload(), avccBox.payloadSize());
}
continue;
}
if (myMeta.tracks[1].init.size()){
if (M.getInit(tNumber).size()){
uint64_t ts = Util::bootMS() - startTime;
if (myMeta.tracks[1].fpks){ts = frameCount * (1000000 / myMeta.tracks[1].fpks);}
thisPacket.genericFill(ts, 0, 1, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize));
if (M.getFpks(tNumber)){ts = frameCount * (1000000 / M.getFpks(tNumber));}
thisPacket.genericFill(ts, 0, tNumber, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize));
thisPacket.appendNal(NAL.data(), nalSize);
++frameCount;
return;

View file

@ -8,24 +8,24 @@ namespace Mist{
InputH264(Util::Config *cfg);
protected:
virtual bool needHeader(){return false;}
bool checkArguments();
bool preRun();
void getNext(bool smart = true);
void getNext(size_t idx = INVALID_TRACK_ID);
Socket::Connection myConn;
std::string ppsInfo;
std::string spsInfo;
uint64_t frameCount;
// Empty defaults
bool readHeader(){return true;}
bool openStreamSource(){return true;}
bool openStreamSource();
void closeStreamSource(){}
void parseStreamHeader(){}
void seek(int seekTime){}
void trackSelect(std::string trackSpec){}
void parseStreamHeader();
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}
bool needsLock(){return false;}
uint64_t startTime;
pid_t inputProcess;
uint32_t waitsSinceData;
size_t tNumber;
};
}// namespace Mist

View file

@ -118,7 +118,7 @@ namespace Mist{
/// Called by the global callbackFunc, to prevent timeouts
bool inputHLS::callback(){
if (nProxy.userClient.isAlive()){nProxy.userClient.keepAlive();}
keepAlive();
return config->is_active;
}
@ -415,7 +415,7 @@ namespace Mist{
if (key == "TARGETDURATION"){
waitTime = atoi(val.c_str()) / 2;
if (waitTime < 5){waitTime = 5;}
if (waitTime < 2){waitTime = 2;}
}
if (key == "MEDIA-SEQUENCE"){fileNo = atoll(val.c_str());}
@ -520,12 +520,13 @@ namespace Mist{
memset(entry.keyAES, 0, 16);
}
if (!isUrl()){
std::ifstream fileSource;
std::string test = root.link(entry.filename).getFilePath();
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
totalBytes += fileSource.tellg();
if (!isUrl()){
std::ifstream fileSource;
std::string test = root.link(entry.filename).getFilePath();
fileSource.open(test.c_str(), std::ios::ate | std::ios::binary);
if (!fileSource.good()){WARN_MSG("file: %s, error: %s", test.c_str(), strerror(errno));}
entry.byteEnd = fileSource.tellg();
totalBytes += entry.byteEnd;
}
entry.timestamp = lastTimestamp + startTime;
@ -588,20 +589,6 @@ namespace Mist{
return true;
}
void inputHLS::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
}
void inputHLS::parseStreamHeader(){
if (!initPlaylist(config->getString("input"))){
FAIL_MSG("Failed to load HLS playlist, aborting");
@ -668,6 +655,8 @@ namespace Mist{
}while (!segDowner.atEnd());
if (preCounter < counter){break;}// We're done reading this playlist!
}
in.close();
}
tsStream.clear();
currentPlaylist = 0;
@ -681,13 +670,12 @@ namespace Mist{
bool hasHeader = false;
// See whether a separate header file exists.
DTSC::File tmp(config->getString("input") + ".dtsh");
if (tmp){
myMeta = tmp.getMeta();
if (myMeta){hasHeader = true;}
}
meta.reInit(config->getString("streamname"), config->getString("input") + ".dtsh");
hasHeader = (bool)M;
if (!hasHeader){myMeta = DTSC::Meta();}
if (M){return true;}
if (!hasHeader){meta.reInit(config->getString("streamname"), true);}
TS::Packet packet; // to analyse and extract data
@ -728,19 +716,22 @@ namespace Mist{
counter++;
}
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId);
size_t idx = M.trackIDToIndex(packetId, getpid());
INFO_MSG("PacketID: %" PRIu64 ", pid: %d, mapped to %zu", packetId, getpid(), idx);
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
INFO_MSG("InitializingMeta for track %zu -> %zu", tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid());
}
if (!hasHeader){
headerPack.getString("data", data, dataLen);
uint64_t pBPos = headerPack.getInt("bpos");
// keyframe data exists, so always add 19 bytes keyframedata.
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize);
uint32_t packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
size_t packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize);
}
}
@ -766,19 +757,18 @@ namespace Mist{
counter++;
}
if (!hasHeader && (!myMeta.tracks.count(packetId) || !myMeta.tracks[packetId].codec.size())){
tsStream.initializeMetadata(myMeta, tmpTrackId, packetId);
if (!hasHeader && (idx == INVALID_TRACK_ID || !M.getCodec(idx).size())){
tsStream.initializeMetadata(meta, tmpTrackId, packetId);
idx = M.trackIDToIndex(packetId, getpid());
}
if (!hasHeader){
headerPack.getString("data", data, dataLen);
uint64_t pBPos = headerPack.getInt("bpos");
// keyframe data exists, so always add 19 bytes keyframedata.
long long packOffset = headerPack.hasMember("offset") ? headerPack.getInt("offset") : 0;
long long packSendSize = 24 + (packOffset ? 17 : 0) + (entId >= 0 ? 15 : 0) + 19 + dataLen + 11;
myMeta.update(headerPack.getTime(), packOffset, packetId, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize);
meta.update(headerPack.getTime(), packOffset, idx, dataLen, entId,
headerPack.hasMember("keyframe"), packSendSize);
}
tsStream.getEarliestPacket(headerPack);
}
@ -790,10 +780,8 @@ namespace Mist{
if (streamIsLive){return true;}
INFO_MSG("write header file...");
std::ofstream oFile((config->getString("input") + ".dtsh").c_str());
oFile << myMeta.toJSON().toNetPacked();
oFile.close();
M.toFile((config->getString("input") + ".dtsh").c_str());
in.close();
return true;
}
@ -802,34 +790,30 @@ namespace Mist{
bool inputHLS::openStreamSource(){return true;}
void inputHLS::getNext(bool smart){
void inputHLS::getNext(size_t idx){
INSANE_MSG("Getting next");
uint32_t tid = 0;
bool finished = false;
if (selectedTracks.size()){tid = *selectedTracks.begin();}
if (userSelect.size()){tid = userSelect.begin()->first;}
thisPacket.null();
while (config->is_active && (needsLock() || nProxy.userClient.isAlive())){
while (config->is_active && (needsLock() || keepAlive())){
// Check if we have a packet
bool hasPacket = false;
if (streamIsLive){
hasPacket = tsStream.hasPacketOnEachTrack() || (segDowner.atEnd() && tsStream.hasPacket());
}else{
hasPacket = tsStream.hasPacket(getMappedTrackId(tid));
hasPacket = tsStream.hasPacket(M.getID(idx) & 0xFFFF);
}
// Yes? Excellent! Read and return it.
if (hasPacket){
// Read
if (myMeta.live){
if (M.getLive()){
tsStream.getEarliestPacket(thisPacket);
tid = getOriginalTrackId(currentPlaylist, thisPacket.getTrackId());
if (!tid){
INFO_MSG("Track %" PRIu64 " on PLS %u -> %" PRIu32, thisPacket.getTrackId(), currentPlaylist, tid);
continue;
}
tid = M.trackIDToIndex((((uint64_t)currentPlaylist) << 16) + thisPacket.getTrackId(), getpid());
}else{
tsStream.getPacket(getMappedTrackId(tid), thisPacket);
tsStream.getPacket(M.getID(idx) & 0xFFFF, thisPacket);
}
if (!thisPacket){
FAIL_MSG("Could not getNext TS packet!");
@ -940,25 +924,19 @@ namespace Mist{
}
// Note: bpos is overloaded here for playlist entry!
void inputHLS::seek(int seekTime){
void inputHLS::seek(uint64_t seekTime, size_t idx){
plsTimeOffset.clear();
plsLastTime.clear();
plsInterval.clear();
tsStream.clear();
int trackId = 0;
uint64_t trackId = M.getID(idx);
unsigned long plistEntry = 0xFFFFFFFFull;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned long thisBPos = 0;
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin();
keyIt != myMeta.tracks[*it].keys.end(); keyIt++){
if (keyIt->getTime() > seekTime){break;}
thisBPos = keyIt->getBpos();
}
if (thisBPos < plistEntry){
plistEntry = thisBPos;
trackId = *it;
}
unsigned long plistEntry = 0;
DTSC::Keys keys(M.keys(idx));
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); i++){
if (keys.getTime(i) > seekTime){break;}
plistEntry = keys.getBpos(i);
}
if (plistEntry < 1){
@ -995,7 +973,7 @@ namespace Mist{
}
}
int inputHLS::getEntryId(int playlistId, uint64_t bytePos){
size_t inputHLS::getEntryId(uint32_t playlistId, uint64_t bytePos){
if (bytePos == 0){return 0;}
tthread::lock_guard<tthread::mutex> guard(entryMutex);
for (int i = 0; i < listEntries[playlistId].size(); i++){
@ -1248,9 +1226,9 @@ namespace Mist{
/// return the playlist id from which we need to read the first upcoming segment
/// by timestamp.
/// this will keep the playlists in sync while reading segments.
int inputHLS::firstSegment(){
size_t inputHLS::firstSegment(){
// Only one selected? Immediately return the right playlist.
if (selectedTracks.size() == 1){return getMappedTrackPlaylist(*selectedTracks.begin());}
if (userSelect.size() == 1){return ((M.getID(userSelect.begin()->first) >> 16) & 0xFFFF);}
uint64_t firstTimeStamp = 0;
int tmpId = -1;
int segCount = 0;

View file

@ -27,8 +27,8 @@ namespace Mist{
uint64_t bytePos;
uint64_t mUTC; ///< UTC unix millis timestamp of first packet, if known
float duration;
unsigned int timestamp;
unsigned int wait;
uint64_t timestamp;
uint64_t wait;
char ivec[16];
char keyAES[16];
};
@ -75,10 +75,10 @@ namespace Mist{
int noChangeCount;
uint64_t lastFileIndex;
int waitTime;
uint64_t waitTime;
PlaylistType playlistType;
unsigned int lastTimestamp;
unsigned int startTime;
uint64_t lastTimestamp;
uint64_t startTime;
uint64_t nextUTC; ///< If non-zero, the UTC timestamp of the next segment on this playlist
char keyAES[16];
std::map<std::string, std::string> keys;
@ -103,7 +103,7 @@ namespace Mist{
int version;
int targetDuration;
bool endPlaylist;
int currentPlaylist;
uint64_t currentPlaylist;
bool allowRemap; ///< True if the next packet may remap the timestamps
bool allowSoftRemap; ///< True if the next packet may soft-remap the timestamps
@ -113,7 +113,7 @@ namespace Mist{
std::map<int, uint64_t> plsLastTime;
std::map<int, uint64_t> plsInterval;
int currentIndex;
size_t currentIndex;
std::string currentFile;
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
@ -128,9 +128,9 @@ namespace Mist{
bool preSetup();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
FILE *inFile;
FILE *tsFile;
@ -141,10 +141,7 @@ namespace Mist{
void parseStreamHeader();
uint32_t getMappedTrackId(uint64_t id);
uint32_t getMappedTrackPlaylist(uint64_t id);
uint64_t getOriginalTrackId(uint32_t playlistId, uint32_t id);
int getEntryId(int playlistId, uint64_t bytePos);
size_t getEntryId(uint32_t playlistId, uint64_t bytePos);
};
}// namespace Mist

View file

@ -40,297 +40,210 @@ namespace Mist{
}
return true;
}
bool inputISMV::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){return false;}
return true;
return inFile; // True if not null
}
bool inputISMV::readHeader(){
if (!inFile){return false;}
meta.reInit(streamName);
// parse ismv header
fseek(inFile, 0, SEEK_SET);
std::string ftyp;
readBox("ftyp", ftyp);
if (ftyp == ""){return false;}
std::string boxRes;
readBox("moov", boxRes);
if (boxRes == ""){return false;}
MP4::MOOV hdrBox;
hdrBox.read(boxRes);
parseMoov(hdrBox);
int tId;
std::vector<MP4::trunSampleInformation> trunSamples;
std::vector<std::string> initVecs;
std::string mdat;
unsigned int currOffset;
JSON::Value lastPack;
unsigned int lastBytePos = 0;
std::map<int, unsigned int> currentDuration;
unsigned int curBytePos = ftell(inFile);
// Skip mandatory ftyp box
MP4::skipBox(inFile);
MP4::MOOV moovBox;
moovBox.read(inFile);
parseMoov(moovBox);
std::map<size_t, uint64_t> duration;
uint64_t currOffset;
uint64_t lastBytePos = 0;
uint64_t curBytePos = ftell(inFile);
// parse fragments form here
while (parseFrag(tId, trunSamples, initVecs, mdat)){
if (!currentDuration.count(tId)){currentDuration[tId] = 0;}
size_t tId;
std::vector<MP4::trunSampleInformation> trunSamples;
while (readMoofSkipMdat(tId, trunSamples) && !feof(inFile)){
if (!duration.count(tId)){duration[tId] = 0;}
currOffset = 8;
int i = 0;
while (currOffset < mdat.size()){
lastPack.null();
lastPack["time"] = currentDuration[tId] / 10000;
lastPack["trackid"] = tId;
lastPack["data"] = mdat.substr(currOffset, trunSamples[i].sampleSize);
if (initVecs.size() == trunSamples.size()){lastPack["ivec"] = initVecs[i];}
lastPack["duration"] = trunSamples[i].sampleDuration;
if (myMeta.tracks[tId].type == "video"){
if (i){
lastBytePos++;
}else{
lastPack["keyframe"] = 1;
lastBytePos = curBytePos;
}
lastPack["bpos"] = lastBytePos;
unsigned int offsetConv = trunSamples[i].sampleOffset / 10000;
lastPack["offset"] = (int)offsetConv;
for (std::vector<MP4::trunSampleInformation>::iterator it = trunSamples.begin();
it != trunSamples.end(); it++){
bool first = (it == trunSamples.begin());
int64_t offsetConv = 0;
if (M.getType(tId) == "video"){offsetConv = it->sampleOffset / 10000;}
if (first){
lastBytePos = curBytePos;
}else{
if (i == 0){
lastPack["keyframe"] = 1;
lastPack["bpos"] = curBytePos;
}
++lastBytePos;
}
myMeta.update(lastPack);
currentDuration[tId] += trunSamples[i].sampleDuration;
currOffset += trunSamples[i].sampleSize;
i++;
meta.update(duration[tId] / 10000, offsetConv, tId, it->sampleSize, lastBytePos, first);
duration[tId] += it->sampleDuration;
currOffset += it->sampleSize;
}
curBytePos = ftell(inFile);
}
myMeta.toFile(config->getString("input") + ".dtsh");
M.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputISMV::getNext(bool smart){
static JSON::Value thisPack;
thisPack.null();
if (!buffered.size()){
thisPacket.null();
return;
}
int tId = buffered.begin()->trackId;
thisPack["time"] = (uint64_t)(buffered.begin()->time / 10000);
thisPack["trackid"] = tId;
fseek(inFile, buffered.begin()->position, SEEK_SET);
char *tmpData = (char *)malloc(buffered.begin()->size * sizeof(char));
fread(tmpData, buffered.begin()->size, 1, inFile);
thisPack["data"] = std::string(tmpData, buffered.begin()->size);
free(tmpData);
if (buffered.begin()->iVec != ""){thisPack["ivec"] = buffered.begin()->iVec;}
if (myMeta.tracks[tId].type == "video"){
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
thisPack["offset"] = (uint64_t)(buffered.begin()->offset / 10000);
}else{
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
}
thisPack["bpos"] = (uint64_t)buffered.begin()->position;
void inputISMV::getNext(size_t idx){
thisPacket.null();
if (!buffered.size()){return;}
seekPos thisPos = *buffered.begin();
buffered.erase(buffered.begin());
if (buffered.size() < 2 * selectedTracks.size()){
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
parseFragHeader(*it, lastKeyNum[*it]);
lastKeyNum[*it]++;
fseek(inFile, thisPos.position, SEEK_SET);
dataPointer.allocate(thisPos.size);
fread(dataPointer, thisPos.size, 1, inFile);
thisPacket.genericFill(thisPos.time / 10000, thisPos.offset / 10000, thisPos.trackId,
dataPointer, thisPos.size, 0, thisPos.isKeyFrame);
if (buffered.size() < 2 * (idx == INVALID_TRACK_ID ? M.getValidTracks().size() : 1)){
std::set<size_t> validTracks = M.getValidTracks();
if (idx != INVALID_TRACK_ID){
validTracks.clear();
validTracks.insert(idx);
}
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
bufferFragmentData(*it, ++lastKeyNum[*it]);
}
}
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
if (idx != INVALID_TRACK_ID && thisPacket.getTrackId() != M.getID(idx)){getNext(idx);}
}
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
bool inputISMV::atKeyFrame(){return thisPacket.getFlag("keyframe");}
void inputISMV::seek(int seekTime){
void inputISMV::seek(uint64_t seekTime, size_t idx){
buffered.clear();
// Seek to corresponding keyframes on EACH track
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned int i;
for (i = 0; i < myMeta.tracks[*it].keys.size(); i++){
if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){// Ehh, whut?
break;
}
}
i--;
DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i);
parseFragHeader(*it, i);
lastKeyNum[*it] = i + 1;
}
}
lastKeyNum.clear();
void inputISMV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
// Select tracks
std::set<size_t> validTracks = M.getValidTracks();
if (idx != INVALID_TRACK_ID){
validTracks.clear();
validTracks.insert(idx);
}
// For each selected track
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
DTSC::Keys keys(M.keys(*it));
uint32_t i;
for (i = keys.getFirstValid(); i < keys.getEndValid(); i++){
if (keys.getTime(i) >= seekTime){break;}
}
INFO_MSG("ISMV seek frag %zu:%" PRIu32, *it, i);
bufferFragmentData(*it, i);
lastKeyNum[*it] = i;
}
seek(0);
}
void inputISMV::parseMoov(MP4::MOOV &moovBox){
for (unsigned int i = 0; i < moovBox.getContentCount(); i++){
if (moovBox.getContent(i).isType("mvhd")){
MP4::MVHD content = (MP4::MVHD &)moovBox.getContent(i);
}
if (moovBox.getContent(i).isType("trak")){
MP4::TRAK content = (MP4::TRAK &)moovBox.getContent(i);
int trackId;
for (unsigned int j = 0; j < content.getContentCount(); j++){
if (content.getContent(j).isType("tkhd")){
MP4::TKHD subContent = (MP4::TKHD &)content.getContent(j);
trackId = subContent.getTrackID();
myMeta.tracks[trackId].trackID = trackId;
}
if (content.getContent(j).isType("mdia")){
MP4::MDIA subContent = (MP4::MDIA &)content.getContent(j);
for (unsigned int k = 0; k < subContent.getContentCount(); k++){
if (subContent.getContent(k).isType("hdlr")){
MP4::HDLR subsubContent = (MP4::HDLR &)subContent.getContent(k);
if (subsubContent.getHandlerType() == "soun"){
myMeta.tracks[trackId].type = "audio";
}
if (subsubContent.getHandlerType() == "vide"){
myMeta.tracks[trackId].type = "video";
}
}
if (subContent.getContent(k).isType("minf")){
MP4::MINF subsubContent = (MP4::MINF &)subContent.getContent(k);
for (unsigned int l = 0; l < subsubContent.getContentCount(); l++){
if (subsubContent.getContent(l).isType("stbl")){
MP4::STBL stblBox = (MP4::STBL &)subsubContent.getContent(l);
for (unsigned int m = 0; m < stblBox.getContentCount(); m++){
if (stblBox.getContent(m).isType("stsd")){
MP4::STSD stsdBox = (MP4::STSD &)stblBox.getContent(m);
for (unsigned int n = 0; n < stsdBox.getEntryCount(); n++){
if (stsdBox.getEntry(n).isType("mp4a") ||
stsdBox.getEntry(n).isType("enca")){
MP4::MP4A mp4aBox = (MP4::MP4A &)stsdBox.getEntry(n);
myMeta.tracks[trackId].codec = "AAC";
std::string tmpStr;
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
myMeta.tracks[trackId].init = tmpStr;
myMeta.tracks[trackId].channels = mp4aBox.getChannelCount();
myMeta.tracks[trackId].size = mp4aBox.getSampleSize();
myMeta.tracks[trackId].rate = mp4aBox.getSampleRate();
}
if (stsdBox.getEntry(n).isType("avc1") ||
stsdBox.getEntry(n).isType("encv")){
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsdBox.getEntry(n);
myMeta.tracks[trackId].height = avc1Box.getHeight();
myMeta.tracks[trackId].width = avc1Box.getWidth();
myMeta.tracks[trackId].init =
std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
myMeta.tracks[trackId].codec = "H264";
}
}
}
}
}
}
}
}
}
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
for (std::deque<MP4::TRAK>::iterator it = trak.begin(); it != trak.end(); it++){
size_t tNumber = meta.addTrack();
meta.setID(tNumber, it->getChild<MP4::TKHD>().getTrackID());
MP4::MDIA mdia = it->getChild<MP4::MDIA>();
MP4::HDLR hdlr = mdia.getChild<MP4::HDLR>();
if (hdlr.getHandlerType() == "soun"){meta.setType(tNumber, "audio");}
if (hdlr.getHandlerType() == "vide"){meta.setType(tNumber, "video");}
MP4::STSD stsd = mdia.getChild<MP4::MINF>().getChild<MP4::STBL>().getChild<MP4::STSD>();
for (size_t i = 0; i < stsd.getEntryCount(); ++i){
if (stsd.getEntry(i).isType("mp4a") || stsd.getEntry(i).isType("enca")){
MP4::MP4A mp4aBox = (MP4::MP4A &)stsd.getEntry(i);
std::string tmpStr;
tmpStr += (char)((mp4aBox.toAACInit() & 0xFF00) >> 8);
tmpStr += (char)(mp4aBox.toAACInit() & 0x00FF);
meta.setCodec(tNumber, "AAC");
meta.setInit(tNumber, tmpStr);
meta.setChannels(tNumber, mp4aBox.getChannelCount());
meta.setSize(tNumber, mp4aBox.getSampleSize());
meta.setRate(tNumber, mp4aBox.getSampleRate());
}
if (stsd.getEntry(i).isType("avc1") || stsd.getEntry(i).isType("encv")){
MP4::AVC1 avc1Box = (MP4::AVC1 &)stsd.getEntry(i);
meta.setCodec(tNumber, "H264");
meta.setInit(tNumber, avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
meta.setHeight(tNumber, avc1Box.getHeight());
meta.setWidth(tNumber, avc1Box.getWidth());
}
}
}
}
bool inputISMV::parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
std::vector<std::string> &initVecs, std::string &mdat){
tId = -1;
bool inputISMV::readMoofSkipMdat(size_t &tId, std::vector<MP4::trunSampleInformation> &trunSamples){
tId = INVALID_TRACK_ID;
trunSamples.clear();
initVecs.clear();
mdat.clear();
std::string boxRes;
readBox("moof", boxRes);
if (boxRes == ""){return false;}
MP4::MOOF moof;
moof.read(boxRes);
for (unsigned int i = 0; i < moof.getContentCount(); i++){
if (moof.getContent(i).isType("traf")){
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
trunSamples.push_back(trunBox.getSampleInformation(i));
}
}
if (trafBox.getContent(j).isType("tfhd")){
tId = ((MP4::TFHD &)trafBox.getContent(j)).getTrackID();
}
/*LTS-START*/
if (trafBox.getContent(j).isType("uuid")){
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() ==
"a2394f52-5a9b-4f14-a244-6c427c648df4"){
MP4::UUID_SampleEncryption uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++){
initVecs.push_back(uuidBox.getSample(i).InitializationVector);
}
}
}
/*LTS-END*/
moof.read(inFile);
if (feof(inFile)){return false;}
MP4::TRAF trafBox = moof.getChild<MP4::TRAF>();
for (size_t j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
MP4::TRUN trunBox = (MP4::TRUN &)trafBox.getContent(j);
for (size_t i = 0; i < trunBox.getSampleInformationCount(); i++){
trunSamples.push_back(trunBox.getSampleInformation(i));
}
}
if (trafBox.getContent(j).isType("tfhd")){
tId = M.trackIDToIndex(((MP4::TFHD &)trafBox.getContent(j)).getTrackID(), getpid());
}
}
readBox("mdat", mdat);
if (mdat == ""){return false;}
return true;
MP4::skipBox(inFile);
return !feof(inFile);
}
void inputISMV::parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum){
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)){return;}
long long int lastPos = myMeta.tracks[trackId].keys[keyNum].getBpos();
long long int lastTime = myMeta.tracks[trackId].keys[keyNum].getTime() * 10000;
fseek(inFile, lastPos, SEEK_SET);
std::string boxRes;
readBox("moof", boxRes);
if (boxRes == ""){return;}
MP4::MOOF moof;
moof.read(boxRes);
void inputISMV::bufferFragmentData(size_t trackId, uint32_t keyNum){
INFO_MSG("Bpos seek for %zu/%" PRIu32, trackId, keyNum);
if (trackId == INVALID_TRACK_ID){return;}
DTSC::Keys keys(M.keys(trackId));
INFO_MSG("Key %" PRIu32 " / %zu", keyNum, keys.getEndValid());
if (keyNum >= keys.getEndValid()){return;}
uint64_t currentPosition = keys.getBpos(keyNum);
uint64_t currentTime = keys.getTime(keyNum) * 10000;
INFO_MSG("Bpos seek to %" PRIu64, currentPosition);
fseek(inFile, currentPosition, SEEK_SET);
MP4::MOOF moofBox;
moofBox.read(inFile);
MP4::TRAF trafBox = moofBox.getChild<MP4::TRAF>();
MP4::TRUN trunBox;
MP4::UUID_SampleEncryption uuidBox; /*LTS*/
for (unsigned int i = 0; i < moof.getContentCount(); i++){
if (moof.getContent(i).isType("traf")){
MP4::TRAF trafBox = (MP4::TRAF &)moof.getContent(i);
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){
trunBox = (MP4::TRUN &)trafBox.getContent(j);
}
if (trafBox.getContent(j).isType("tfhd")){
if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
DEBUG_MSG(DLVL_FAIL, "Trackids do not match");
return;
}
}
/*LTS-START*/
if (trafBox.getContent(j).isType("uuid")){
if (((MP4::UUID &)trafBox.getContent(j)).getUUID() ==
"a2394f52-5a9b-4f14-a244-6c427c648df4"){
uuidBox = (MP4::UUID_SampleEncryption &)trafBox.getContent(j);
}
}
/*LTS-END*/
MP4::UUID_SampleEncryption uuidBox;
for (unsigned int j = 0; j < trafBox.getContentCount(); j++){
if (trafBox.getContent(j).isType("trun")){trunBox = (MP4::TRUN &)trafBox.getContent(j);}
if (trafBox.getContent(j).isType("tfhd")){
if (M.getID(trackId) != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
FAIL_MSG("Trackids do not match");
return;
}
}
}
lastPos = ftell(inFile) + 8;
currentPosition = ftell(inFile) + 8;
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
seekPos myPos;
myPos.position = lastPos;
myPos.position = currentPosition;
myPos.trackId = trackId;
myPos.time = lastTime;
myPos.time = currentTime;
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
myPos.size = trunBox.getSampleInformation(i).sampleSize;
if (trunBox.getFlags() & MP4::trunsampleOffsets){
@ -340,29 +253,9 @@ namespace Mist{
myPos.offset = 0;
}
myPos.isKeyFrame = (i == 0);
/*LTS-START*/
if (i <= uuidBox.getSampleCount()){myPos.iVec = uuidBox.getSample(i).InitializationVector;}
/*LTS-END*/
lastTime += trunBox.getSampleInformation(i).sampleDuration;
lastPos += trunBox.getSampleInformation(i).sampleSize;
currentTime += trunBox.getSampleInformation(i).sampleDuration;
currentPosition += trunBox.getSampleInformation(i).sampleSize;
buffered.insert(myPos);
}
}
void inputISMV::readBox(const char *type, std::string &result){
int pos = ftell(inFile);
char mp4Head[8];
fread(mp4Head, 8, 1, inFile);
fseek(inFile, pos, SEEK_SET);
if (memcmp(mp4Head + 4, type, 4)){
DEBUG_MSG(DLVL_FAIL, "No %.4s box found at position %d", type, pos);
result = "";
return;
}
unsigned int boxSize = (mp4Head[0] << 24) + (mp4Head[1] << 16) + (mp4Head[2] << 8) + mp4Head[3];
char *tmpBox = (char *)malloc(boxSize * sizeof(char));
fread(tmpBox, boxSize, 1, inFile);
result = std::string(tmpBox, boxSize);
free(tmpBox);
}
}// namespace Mist

View file

@ -3,6 +3,7 @@
#include <mist/mp4.h>
#include <mist/mp4_encryption.h>
#include <mist/mp4_generic.h>
#include <mist/util.h>
#include <set>
namespace Mist{
@ -11,12 +12,12 @@ namespace Mist{
if (time < rhs.time){return true;}
return (time == rhs.time && trackId < rhs.trackId);
}
long long int position;
int trackId;
long long int time;
long long int duration;
int size;
long long int offset;
uint64_t position;
size_t trackId;
uint64_t time;
uint64_t duration;
uint64_t size;
int64_t offset;
bool isKeyFrame;
std::string iVec;
};
@ -30,20 +31,19 @@ namespace Mist{
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool atKeyFrame();
virtual void getNext(size_t idx = INVALID_TRACK_ID);
virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
FILE *inFile;
void parseMoov(MP4::MOOV &moovBox);
bool parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
std::vector<std::string> &initVecs, std::string &mdat);
void parseFragHeader(const unsigned int &trackId, const unsigned int &keyNum);
void readBox(const char *type, std::string &result);
bool readMoofSkipMdat(size_t &tId, std::vector<MP4::trunSampleInformation> &trunSamples);
void bufferFragmentData(size_t trackId, uint32_t keyNum);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
std::map<size_t, uint32_t> lastKeyNum;
Util::ResizeablePointer dataPointer;
};
}// namespace Mist

View file

@ -51,44 +51,45 @@ namespace Mist{
bool inputMP3::readHeader(){
if (!inFile){return false;}
myMeta = DTSC::Meta();
myMeta.tracks[1].trackID = 1;
myMeta.tracks[1].type = "audio";
myMeta.tracks[1].codec = "MP3";
meta.reInit(config->getString("streamname"));
size_t tNum = meta.addTrack();
meta.setID(tNum, tNum);
meta.setType(tNum, "audio");
meta.setCodec(tNum, "MP3");
// Create header file from MP3 data
char header[10];
fread(header, 10, 1, inFile); // Read a 10 byte header
if (header[0] == 'I' || header[1] == 'D' || header[2] == '3'){
size_t id3size = (((int)header[6] & 0x7F) << 21) | (((int)header[7] & 0x7F) << 14) |
(((int)header[8] & 0x7F) << 7) |
(header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0);
((header[9] & 0x7F) + 10 + ((header[5] & 0x10) ? 10 : 0));
INFO_MSG("id3 size: %lu bytes", id3size);
fseek(inFile, id3size, SEEK_SET);
}else{
fseek(inFile, 0, SEEK_SET);
}
// Read the first mp3 header for bitrate and such
size_t filePos = ftell(inFile);
uint64_t filePos = ftell(inFile);
fread(header, 4, 1, inFile);
fseek(inFile, filePos, SEEK_SET);
Mpeg::MP2Info mp2Info = Mpeg::parseMP2Header(header);
myMeta.tracks[1].rate = mp2Info.sampleRate;
myMeta.tracks[1].channels = mp2Info.channels;
meta.setRate(tNum, mp2Info.sampleRate);
meta.setChannels(tNum, mp2Info.channels);
getNext();
while (thisPacket){
myMeta.update(thisPacket);
meta.update(thisPacket);
getNext();
}
fseek(inFile, 0, SEEK_SET);
timestamp = 0;
myMeta.toFile(config->getString("input") + ".dtsh");
M.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP3::getNext(bool smart){
void inputMP3::getNext(size_t idx){
thisPacket.null();
static char packHeader[3000];
size_t filePos = ftell(inFile);
@ -107,7 +108,7 @@ namespace Mist{
}
}
if (!offset){
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
FAIL_MSG("Sync byte not found from offset %zu", filePos);
return;
}
filePos += offset;
@ -141,34 +142,16 @@ namespace Mist{
fseek(inFile, filePos + dataSize, SEEK_SET);
// Create a json value with the right data
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = 1;
thisPack["bpos"] = (uint64_t)filePos;
thisPack["data"] = std::string(packHeader, dataSize);
thisPack["time"] = timestamp;
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
thisPacket.genericFill(timestamp, 0, idx, packHeader, dataSize, filePos, false);
// Update the internal timestamp
timestamp += (sampleCount / (sampleRate / 1000));
}
void inputMP3::seek(int seekTime){
std::deque<DTSC::Key> &keys = myMeta.tracks[1].keys;
size_t seekPos = keys[0].getBpos();
for (unsigned int i = 0; i < keys.size(); i++){
if (keys[i].getTime() > seekTime){break;}
seekPos = keys[i].getBpos();
timestamp = keys[i].getTime();
}
timestamp = seekTime;
fseek(inFile, seekPos, SEEK_SET);
}
void inputMP3::trackSelect(std::string trackSpec){
// Ignore, do nothing
// MP3 Always has only 1 track, so we can't select much else..
void inputMP3::seek(uint64_t seekTime, size_t idx){
DTSC::Keys keys(M.keys(idx));
uint32_t keyNum = keys.getNumForTime(seekTime);
fseek(inFile, keys.getBpos(keyNum), SEEK_SET);
timestamp = keys.getTime(keyNum);
}
}// namespace Mist

View file

@ -21,9 +21,9 @@ namespace Mist{
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
double timestamp;
FILE *inFile;

View file

@ -32,6 +32,7 @@ namespace Mist{
stcoBox.clear();
co64Box.clear();
stco64 = false;
trackId = 0;
}
uint64_t mp4TrackHeader::size(){return (stszBox.asBox() ? stszBox.getSampleCount() : 0);}
@ -45,6 +46,7 @@ namespace Mist{
MP4::MDIA mdiaBox = trakBox.getChild<MP4::MDIA>();
timeScale = mdiaBox.getChild<MP4::MDHD>().getTimeScale();
trackId = trakBox.getChild<MP4::TKHD>().getTrackID();
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
@ -148,6 +150,14 @@ namespace Mist{
size = stszBox.getEntrySize(index);
}
mp4TrackHeader &inputMP4::headerData(size_t trackID){
static mp4TrackHeader none;
for (std::deque<mp4TrackHeader>::iterator it = trackHeaders.begin(); it != trackHeaders.end(); it++){
if (it->trackId == trackID){return *it;}
}
return none;
}
inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){
malSize = 4; // initialise data read buffer to 0;
data = (char *)malloc(malSize);
@ -200,9 +210,8 @@ namespace Mist{
return false;
}
uint32_t trackNo = 0;
// first we get the necessary header parts
size_t tNumber = 0;
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
if (boxType == "erro"){break;}
@ -213,7 +222,8 @@ namespace Mist{
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
headerData[++trackNo].read(*trakIt);
trackHeaders.push_back(mp4TrackHeader());
trackHeaders.rbegin()->read(*trakIt);
}
continue;
}
@ -228,7 +238,9 @@ namespace Mist{
if (readExistingHeader()){return true;}
HIGH_MSG("Not read existing header");
trackNo = 0;
meta.reInit(streamName);
tNumber = 0;
// Create header file from MP4 data
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
@ -241,96 +253,95 @@ namespace Mist{
HIGH_MSG("Obtained %zu trak Boxes", trak.size());
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
uint64_t trackNo = myMeta.tracks.size() + 1;
myMeta.tracks[trackNo].trackID = trackNo;
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
if (tkhdBox.getWidth() > 0){
myMeta.tracks[trackNo].width = tkhdBox.getWidth();
myMeta.tracks[trackNo].height = tkhdBox.getHeight();
}
MP4::MDIA mdiaBox = trakIt->getChild<MP4::MDIA>();
MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
uint64_t timescale = mdhdBox.getTimeScale();
myMeta.tracks[trackNo].lang = mdhdBox.getLanguage();
std::string hdlrType = mdiaBox.getChild<MP4::HDLR>().getHandlerType();
if (hdlrType != "vide" && hdlrType != "soun" && hdlrType != "sbtl"){
headerData.erase(trackNo);
myMeta.tracks.erase(trackNo);
break;
INFO_MSG("Unsupported handler: %s", hdlrType.c_str());
continue;
}
tNumber = meta.addTrack();
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
if (tkhdBox.getWidth() > 0){
meta.setWidth(tNumber, tkhdBox.getWidth());
meta.setHeight(tNumber, tkhdBox.getHeight());
}
meta.setID(tNumber, tkhdBox.getTrackID());
MP4::MDHD mdhdBox = mdiaBox.getChild<MP4::MDHD>();
uint64_t timescale = mdhdBox.getTimeScale();
meta.setLang(tNumber, mdhdBox.getLanguage());
MP4::STBL stblBox = mdiaBox.getChild<MP4::MINF>().getChild<MP4::STBL>();
MP4::STSD stsdBox = stblBox.getChild<MP4::STSD>();
MP4::Box sEntryBox = stsdBox.getEntry(0);
std::string sType = sEntryBox.getType();
HIGH_MSG("Found track %zu of type %s", trackNo, sType.c_str());
HIGH_MSG("Found track %zu of type %s", tNumber, sType.c_str());
if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "H264";
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
meta.setType(tNumber, "video");
meta.setCodec(tNumber, "H264");
if (!meta.getWidth(tNumber)){
meta.setWidth(tNumber, vEntryBox.getWidth());
meta.setHeight(tNumber, vEntryBox.getHeight());
}
MP4::Box initBox = vEntryBox.getCLAP();
if (initBox.isType("avcC")){
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
}
initBox = vEntryBox.getPASP();
if (initBox.isType("avcC")){
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
}
/// this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data...
if (!myMeta.tracks[trackNo].width){
/// this is a hacky way around invalid FLV data (since it gets ignored nearly
/// everywhere, but we do need correct data...
if (!meta.getWidth(tNumber)){
h264::sequenceParameterSet sps;
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
sps.fromDTSCInit(meta.getInit(tNumber));
h264::SPSMeta spsChar = sps.getCharacteristics();
myMeta.tracks[trackNo].width = spsChar.width;
myMeta.tracks[trackNo].height = spsChar.height;
meta.setWidth(tNumber, spsChar.width);
meta.setHeight(tNumber, spsChar.height);
}
}
if (sType == "hev1" || sType == "hvc1"){
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "HEVC";
if (!myMeta.tracks[trackNo].width){
myMeta.tracks[trackNo].width = vEntryBox.getWidth();
myMeta.tracks[trackNo].height = vEntryBox.getHeight();
meta.setType(tNumber, "video");
meta.setCodec(tNumber, "HEVC");
if (!meta.getWidth(tNumber)){
meta.setWidth(tNumber, vEntryBox.getWidth());
meta.setHeight(tNumber, vEntryBox.getHeight());
}
MP4::Box initBox = vEntryBox.getCLAP();
if (initBox.isType("hvcC")){
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
}
initBox = vEntryBox.getPASP();
if (initBox.isType("hvcC")){
myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize());
meta.setInit(tNumber, initBox.payload(), initBox.payloadSize());
}
}
if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "audio";
myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount();
myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate();
meta.setType(tNumber, "audio");
meta.setChannels(tNumber, aEntryBox.getChannelCount());
meta.setRate(tNumber, aEntryBox.getSampleRate());
if (sType == "ac-3"){
myMeta.tracks[trackNo].codec = "AC3";
meta.setCodec(tNumber, "AC3");
}else{
MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
myMeta.tracks[trackNo].codec = esdsBox.getCodec();
myMeta.tracks[trackNo].init = esdsBox.getInitData();
meta.setCodec(tNumber, esdsBox.getCodec());
meta.setInit(tNumber, esdsBox.getInitData());
}
myMeta.tracks[trackNo].size = 16; ///\todo this might be nice to calculate from mp4 file;
meta.setSize(tNumber, 16); ///\todo this might be nice to calculate from mp4 file;
}
if (sType == "tx3g"){// plain text subtitles
myMeta.tracks[trackNo].type = "meta";
myMeta.tracks[trackNo].codec = "subtitle";
meta.setType(tNumber, "meta");
meta.setCodec(tNumber, "subtitle");
}
MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
@ -374,7 +385,7 @@ namespace Mist{
nextFirstChunk =
(stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
}
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount &&
BsetPart.keyframe = (meta.getType(tNumber) == "video" && stssIndex < stssCount &&
stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
if (BsetPart.keyframe){++stssIndex;}
// in bpos set
@ -417,12 +428,12 @@ namespace Mist{
long long packSendSize = 0;
packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
}
}else{
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
meta.update(BsetPart.time, BsetPart.timeOffset, tNumber,
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
}
}
}
@ -436,11 +447,11 @@ namespace Mist{
clearerr(inFile);
// outputting dtsh file
myMeta.toFile(config->getString("input") + ".dtsh");
M.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP4::getNext(bool smart){// get next part from track in stream
void inputMP4::getNext(size_t idx){// get next part from track in stream
if (curPositions.empty()){
thisPacket.null();
return;
@ -450,17 +461,17 @@ namespace Mist{
curPositions.erase(curPositions.begin());
bool isKeyframe = false;
if (nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
DTSC::Keys keys(M.keys(curPart.trackID));
uint32_t nextKeyNum = nextKeyframe[curPart.trackID];
if (nextKeyNum < keys.getEndValid()){
// checking if this is a keyframe
if (myMeta.tracks[curPart.trackID].type == "video" &&
(long long int)curPart.time ==
myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime()){
if (meta.getType(curPart.trackID) == "video" && curPart.time == keys.getTime(nextKeyNum)){
isKeyframe = true;
}
// if a keyframe has passed, we find the next keyframe
if (myMeta.tracks[curPart.trackID].keys[(nextKeyframe[curPart.trackID])].getTime() <=
(long long int)curPart.time){
nextKeyframe[curPart.trackID]++;
if (keys.getTime(nextKeyNum) <= curPart.time){
++nextKeyframe[curPart.trackID];
++nextKeyNum;
}
}
if (fseeko(inFile, curPart.bpos, SEEK_SET)){
@ -478,85 +489,63 @@ namespace Mist{
return;
}
if (myMeta.tracks[curPart.trackID].codec == "subtitle"){
if (M.getCodec(curPart.trackID) == "subtitle"){
unsigned int txtLen = Bit::btohs(data);
if (!txtLen && false){
curPart.index++;
return getNext(smart);
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe);
}else{
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = (uint64_t)curPart.trackID;
thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg();
thisPack["data"] = std::string(data + 2, txtLen);
thisPack["time"] = curPart.time;
if (curPart.duration){thisPack["duration"] = curPart.duration;}
thisPack["keyframe"] = true;
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
// return;
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
return getNext(idx);
}
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = curPart.trackID;
thisPack["bpos"] = curPart.bpos; //(long long)fileSource.tellg();
thisPack["data"] = std::string(data + 2, txtLen);
thisPack["time"] = curPart.time;
if (curPart.duration){thisPack["duration"] = curPart.duration;}
thisPack["keyframe"] = true;
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
}else{
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size,
0 /*Note: no bpos*/, isKeyframe);
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0, isKeyframe);
}
// get the next part for this track
curPart.index++;
if (curPart.index < headerData[curPart.trackID].size()){
headerData[curPart.trackID].getPart(curPart.index, curPart.bpos, curPart.size, curPart.time,
curPart.offset, curPart.duration);
if (curPart.index < headerData(M.getID(curPart.trackID)).size()){
headerData(M.getID(curPart.trackID))
.getPart(curPart.index, curPart.bpos, curPart.size, curPart.time, curPart.offset, curPart.duration);
curPositions.insert(curPart);
}
}
void inputMP4::seek(int seekTime){// seek to a point
void inputMP4::seek(uint64_t seekTime, size_t idx){// seek to a point
nextKeyframe.clear();
// for all tracks
curPositions.clear();
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
nextKeyframe[*it] = 0;
mp4PartTime addPart;
addPart.bpos = 0;
addPart.size = 0;
addPart.time = 0;
addPart.trackID = *it;
// for all indexes in those tracks
for (unsigned int i = 0; i < headerData[*it].size(); i++){
// if time > seekTime
headerData[*it].getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration);
// check for keyframe time in myMeta and update nextKeyframe
//
if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){
nextKeyframe[*it]++;
}
if (addPart.time >= seekTime){
addPart.index = i;
// use addPart thingy in time set and break
curPositions.insert(addPart);
break;
}// end if time > seektime
}// end for all indexes
}// rof all tracks
if (idx != INVALID_TRACK_ID){
handleSeek(seekTime, idx);
}else{
std::set<size_t> tracks = M.getValidTracks();
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
handleSeek(seekTime, *it);
}
}
}
void inputMP4::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long int index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d",
atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos);
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
void inputMP4::handleSeek(uint64_t seekTime, size_t idx){
nextKeyframe[idx] = 0;
mp4PartTime addPart;
addPart.trackID = idx;
// for all stsz samples in those tracks
mp4TrackHeader &thisHeader = headerData(M.getID(idx));
size_t headerDataSize = thisHeader.size();
DTSC::Keys keys(M.keys(idx));
for (size_t i = 0; i < headerDataSize; i++){
thisHeader.getPart(i, addPart.bpos, addPart.size, addPart.time, addPart.offset, addPart.duration);
if (keys.getTime(nextKeyframe[idx]) < addPart.time){nextKeyframe[idx]++;}
if (addPart.time >= seekTime){
addPart.index = i;
curPositions.insert(addPart);
break;
}
}
}

View file

@ -5,7 +5,7 @@
namespace Mist{
class mp4PartTime{
public:
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0){}
mp4PartTime() : time(0), duration(0), offset(0), trackID(0), bpos(0), size(0), index(0){}
bool operator<(const mp4PartTime &rhs) const{
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
@ -40,6 +40,7 @@ namespace Mist{
class mp4TrackHeader{
public:
mp4TrackHeader();
size_t trackId;
void read(MP4::TRAK &trakBox);
MP4::STCO stcoBox;
MP4::CO64 co64Box;
@ -80,21 +81,24 @@ namespace Mist{
bool preRun();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void handleSeek(uint64_t seekTime, size_t idx);
FILE *inFile;
std::map<unsigned int, mp4TrackHeader> headerData;
mp4TrackHeader &headerData(size_t trackID);
std::deque<mp4TrackHeader> trackHeaders;
std::set<mp4PartTime> curPositions;
// remember last seeked keyframe;
std::map<unsigned int, unsigned int> nextKeyframe;
std::map<size_t, uint32_t> nextKeyframe;
// these next two variables keep a buffer for reading from filepointer inFile;
uint64_t malSize;
char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of memory to read from files
char *data; ///\todo rename this variable to a more sensible name, it is a temporary piece of
/// memory to read from files
};
}// namespace Mist

View file

@ -21,9 +21,8 @@ namespace Mist{
retval["time"] = time;
retval["trackid"] = tid;
std::string tmpString = "";
for (unsigned int i = 0; i < parts.size(); i++){tmpString += parts[i];}
for (size_t i = 0; i < parts.size(); i++){tmpString += parts[i];}
retval["data"] = tmpString;
// INFO_MSG("Setting bpos for packet on track %llu, time %llu, to %llu", tid, time, bytepos);
retval["bpos"] = bytepos;
if (myCodec == OGG::THEORA){
if (!theora::isHeader(tmpString.data(), tmpString.size())){
@ -34,12 +33,6 @@ namespace Mist{
return retval;
}
/*
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){//WTF!!?
return blockSize[vModes[vModeIndex].blockFlag];
}
*/
inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){
capa["name"] = "OGG";
capa["desc"] = "This input allows streaming of OGG files as Video on Demand.";
@ -68,70 +61,74 @@ namespace Mist{
///\todo check if all trackID (tid) instances are replaced with bitstream serial numbers
void inputOGG::parseBeginOfStream(OGG::Page &bosPage){
// long long int tid = snum2tid.size() + 1;
unsigned int tid = bosPage.getBitstreamSerialNumber();
size_t tid = bosPage.getBitstreamSerialNumber();
size_t idx = M.trackIDToIndex(tid, getpid());
if (idx == INVALID_TRACK_ID){idx = meta.addTrack();}
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){
theora::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[tid].codec = OGG::THEORA;
oggTracks[tid].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
oggTracks[tid].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations
myMeta.tracks[tid].type = "video";
myMeta.tracks[tid].codec = "theora";
myMeta.tracks[tid].trackID = tid;
myMeta.tracks[tid].fpks = (tmpHead.getFRN() * 1000) / tmpHead.getFRD();
myMeta.tracks[tid].height = tmpHead.getPICH();
myMeta.tracks[tid].width = tmpHead.getPICW();
if (!myMeta.tracks[tid].init.size()){
myMeta.tracks[tid].init = (char)((bosPage.getPayloadSize() >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(bosPage.getPayloadSize() & 0xFF);
myMeta.tracks[tid].init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[idx].codec = OGG::THEORA;
oggTracks[idx].msPerFrame = (double)(tmpHead.getFRD() * 1000) / (double)tmpHead.getFRN(); // this should be: 1000/( tmpHead.getFRN()/ tmpHead.getFRD() )
oggTracks[idx].KFGShift = tmpHead.getKFGShift(); // store KFGShift for granule calculations
meta.setType(idx, "video");
meta.setCodec(idx, "theora");
meta.setID(idx, tid);
meta.setFpks(idx, (double)(tmpHead.getFRN() * 1000) / tmpHead.getFRD());
meta.setHeight(idx, tmpHead.getPICH());
meta.setWidth(idx, tmpHead.getPICW());
if (!M.getInit(idx).size()){
std::string init = " ";
Bit::htobs((char *)init.data(), bosPage.getPayloadSize());
init.append(bosPage.getSegment(0), bosPage.getSegmentLen(0));
meta.setInit(idx, init);
}
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(tid).c_str());
}
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){
vorbis::header tmpHead((char *)bosPage.getSegment(0), bosPage.getSegmentLen(0));
oggTracks[tid].codec = OGG::VORBIS;
oggTracks[tid].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
DEBUG_MSG(DLVL_DEVEL, "vorbis trackID: %d msperFrame %f ", tid, oggTracks[tid].msPerFrame);
oggTracks[tid].channels = tmpHead.getAudioChannels();
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
oggTracks[idx].codec = OGG::VORBIS;
oggTracks[idx].msPerFrame = (double)1000.0f / tmpHead.getAudioSampleRate();
oggTracks[idx].channels = tmpHead.getAudioChannels();
oggTracks[idx].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[idx].blockSize[1] = 1 << tmpHead.getBlockSize1();
DEVEL_MSG("vorbis trackID: %zu msperFrame %f ", tid, oggTracks[idx].msPerFrame);
// Abusing .contBuffer for temporarily storing the idHeader
bosPage.getSegment(0, oggTracks[tid].contBuffer);
bosPage.getSegment(0, oggTracks[idx].contBuffer);
myMeta.tracks[tid].type = "audio";
myMeta.tracks[tid].codec = "vorbis";
myMeta.tracks[tid].rate = tmpHead.getAudioSampleRate();
myMeta.tracks[tid].trackID = tid;
myMeta.tracks[tid].channels = tmpHead.getAudioChannels();
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
meta.setType(idx, "audio");
meta.setCodec(idx, "vorbis");
meta.setRate(idx, tmpHead.getAudioSampleRate());
meta.setID(idx, tid);
meta.setChannels(idx, tmpHead.getAudioChannels());
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
}
if (memcmp(bosPage.getSegment(0), "OpusHead", 8) == 0){
oggTracks[tid].codec = OGG::OPUS;
myMeta.tracks[tid].type = "audio";
myMeta.tracks[tid].codec = "opus";
myMeta.tracks[tid].rate = 48000;
myMeta.tracks[tid].trackID = tid;
myMeta.tracks[tid].init.assign(bosPage.getSegment(0), bosPage.getSegmentLen(0));
myMeta.tracks[tid].channels = myMeta.tracks[tid].init[9];
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
oggTracks[idx].codec = OGG::OPUS;
meta.setType(idx, "audio");
meta.setCodec(idx, "opus");
meta.setRate(idx, 48000);
meta.setID(idx, tid);
meta.setInit(idx, bosPage.getSegment(0), bosPage.getSegmentLen(0));
meta.setChannels(idx, M.getInit(idx)[9]);
INFO_MSG("Track with id %zu is %s", tid, M.getCodec(idx).c_str());
}
}
bool inputOGG::readHeader(){
meta.reInit(config->getString("streamname"), true);
OGG::Page myPage;
fseek(inFile, 0, SEEK_SET);
while (myPage.read(inFile)){// assumes all headers are sent before any data
unsigned int tid = myPage.getBitstreamSerialNumber();
size_t tid = myPage.getBitstreamSerialNumber();
size_t idx = M.trackIDToIndex(tid, getpid());
if (myPage.getHeaderType() & OGG::BeginOfStream){
parseBeginOfStream(myPage);
INFO_MSG("Read BeginOfStream for track %d", tid);
INFO_MSG("Read BeginOfStream for track %zu", tid);
continue; // Continue reading next pages
}
bool readAllHeaders = true;
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
it != oggTracks.end(); it++){
for (std::map<size_t, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
if (!it->second.parsedHeaders){
readAllHeaders = false;
break;
@ -139,143 +136,142 @@ namespace Mist{
}
if (readAllHeaders){break;}
// INFO_MSG("tid: %d",tid);
// Parsing headers
if (myMeta.tracks[tid].codec == "theora"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
if (M.getCodec(idx) == "theora"){
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
size_t len = myPage.getSegmentLen(i);
theora::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){// not copying the header anymore, should this check isHeader?
DEBUG_MSG(DLVL_FAIL, "Theora Header read failed!");
FAIL_MSG("Theora Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
// Case 0 is being handled by parseBeginOfStream
case 1:{
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
std::string init = M.getInit(idx);
init += (char)((len >> 8) & 0xFF);
init += (char)(len & 0xFF);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
break;
}
case 2:{
myMeta.tracks[tid].init += (char)((len >> 8) & 0xFF);
myMeta.tracks[tid].init += (char)(len & 0xFF);
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].lastGran = 0;
oggTracks[tid].parsedHeaders = true;
std::string init = M.getInit(idx);
init += (char)((len >> 8) & 0xFF);
init += (char)(len & 0xFF);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
oggTracks[idx].lastGran = 0;
oggTracks[idx].parsedHeaders = true;
break;
}
}
}
}
if (myMeta.tracks[tid].codec == "vorbis"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
if (M.getCodec(idx) == "vorbis"){
for (size_t i = 0; i < myPage.getAllSegments().size(); i++){
size_t len = myPage.getSegmentLen(i);
vorbis::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){
DEBUG_MSG(DLVL_FAIL, "Header read failed!");
FAIL_MSG("Header read failed!");
return false;
}
switch (tmpHead.getHeaderType()){
// Case 1 is being handled by parseBeginOfStream
case 3:{
// we have the first header stored in contBuffer
myMeta.tracks[tid].init += (char)0x02;
std::string init = M.getInit(idx);
init += (char)0x02;
// ID header size
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
for (size_t j = 0; j < (oggTracks[idx].contBuffer.size() / 255); j++){
init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(oggTracks[tid].contBuffer.size() % 255);
init += (char)(oggTracks[idx].contBuffer.size() % 255);
// Comment header size
for (unsigned int j = 0; j < (len / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(len % 255);
myMeta.tracks[tid].init += oggTracks[tid].contBuffer;
oggTracks[tid].contBuffer.clear();
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
for (size_t j = 0; j < (len / 255); j++){init += (char)0xFF;}
init += (char)(len % 255);
init += oggTracks[idx].contBuffer;
oggTracks[idx].contBuffer.clear();
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
break;
}
case 5:{
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
oggTracks[tid].parsedHeaders = true;
std::string init = M.getInit(idx);
init.append(myPage.getSegment(i), len);
meta.setInit(idx, init);
oggTracks[idx].vModes = tmpHead.readModeDeque(oggTracks[idx].channels);
oggTracks[idx].parsedHeaders = true;
break;
}
}
}
}
if (myMeta.tracks[tid].codec == "opus"){oggTracks[tid].parsedHeaders = true;}
if (M.getCodec(idx) == "opus"){oggTracks[idx].parsedHeaders = true;}
}
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
it != oggTracks.end(); it++){
for (std::map<size_t, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
fseek(inFile, 0, SEEK_SET);
INFO_MSG("Finding first data for track %lu", it->first);
INFO_MSG("Finding first data for track %zu", it->first);
position tmp = seekFirstData(it->first);
if (tmp.trackID){
currentPositions.insert(tmp);
}else{
INFO_MSG("missing track: %lu", it->first);
}
currentPositions.insert(tmp);
}
getNext();
while (thisPacket){
myMeta.update(thisPacket);
meta.update(thisPacket);
getNext();
}
myMeta.toFile(config->getString("input") + ".dtsh");
meta.toFile(config->getString("input") + ".dtsh");
return true;
}
position inputOGG::seekFirstData(long long unsigned int tid){
position inputOGG::seekFirstData(size_t idx){
fseek(inFile, 0, SEEK_SET);
position res;
res.time = 0;
res.trackID = tid;
res.trackID = idx;
res.segmentNo = 0;
bool readSuccesfull = true;
bool quitloop = false;
while (!quitloop){
quitloop = true;
res.bytepos = ftell(inFile);
readSuccesfull = oggTracks[tid].myPage.read(inFile);
readSuccesfull = oggTracks[idx].myPage.read(inFile);
if (!readSuccesfull){
quitloop = true; // break :(
break;
}
if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){
if (oggTracks[idx].myPage.getBitstreamSerialNumber() != M.getID(idx)){
quitloop = false;
continue;
}
if (oggTracks[tid].myPage.getHeaderType() != OGG::Plain){
if (oggTracks[idx].myPage.getHeaderType() != OGG::Plain){
quitloop = false;
continue;
}
if (oggTracks[tid].codec == OGG::OPUS){
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){quitloop = false;}
if (oggTracks[idx].codec == OGG::OPUS){
if (std::string(oggTracks[idx].myPage.getSegment(0), 2) == "Op"){quitloop = false;}
}
if (oggTracks[tid].codec == OGG::VORBIS){
vorbis::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].myPage.getSegmentLen(0));
if (oggTracks[idx].codec == OGG::VORBIS){
vorbis::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
oggTracks[idx].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
if (oggTracks[tid].codec == OGG::THEORA){
theora::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].myPage.getSegmentLen(0));
if (oggTracks[idx].codec == OGG::THEORA){
theora::header tmpHead((char *)oggTracks[idx].myPage.getSegment(0),
oggTracks[idx].myPage.getSegmentLen(0));
if (tmpHead.isHeader()){quitloop = false;}
}
}// while ( oggTracks[tid].myPage.getHeaderType() != OGG::Plain && readSuccesfull && oggTracks[tid].myPage.getBitstreamSerialNumber() != tid);
INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ",
res.bytepos, tid, oggTracks[tid].myPage.getHeaderType());
}
INFO_MSG("seek first bytepos: %" PRIu64 " tid: %zu oggTracks[idx].myPage.getHeaderType(): %d ",
res.bytepos, idx, oggTracks[idx].myPage.getHeaderType());
if (!readSuccesfull){res.trackID = 0;}
return res;
}
void inputOGG::getNext(bool smart){
void inputOGG::getNext(size_t idx){
if (!currentPositions.size()){
thisPacket.null();
return;
@ -287,7 +283,7 @@ namespace Mist{
thisSegment.tid = curPos.trackID;
thisSegment.time = curPos.time;
thisSegment.bytepos = curPos.bytepos + curPos.segmentNo;
unsigned int oldSegNo = curPos.segmentNo;
size_t oldSegNo = curPos.segmentNo;
fseek(inFile, curPos.bytepos, SEEK_SET);
OGG::Page curPage;
curPage.read(inFile);
@ -296,7 +292,7 @@ namespace Mist{
bool readFullPacket = false;
if (curPos.segmentNo == curPage.getAllSegments().size() - 1){
OGG::Page tmpPage;
unsigned int bPos;
uint64_t bPos;
while (!readFullPacket){
bPos = ftell(inFile); //<-- :(
if (!tmpPage.read(inFile)){break;}
@ -315,11 +311,11 @@ namespace Mist{
}else{
curPos.segmentNo++;
// if (oggTracks[thisSegment.tid].codec == OGG::THEORA && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)
// && curPos.segmentNo == curPage.getAllSegments().size() - 1){//if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
if ((oggTracks[thisSegment.tid].codec == OGG::THEORA || oggTracks[thisSegment.tid].codec == OGG::VORBIS) &&
if ((oggTracks[curPos.trackID].codec == OGG::THEORA || oggTracks[curPos.trackID].codec == OGG::VORBIS) &&
curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull) &&
curPos.segmentNo == curPage.getAllSegments().size() - 1){// if the next segment is the last one on the page, the (theora) granule should be used to sync the time for the current segment
curPos.segmentNo == curPage.getAllSegments().size() -
1){// if the next segment is the last one on the page, the (theora) granule
// should be used to sync the time for the current segment
OGG::Page tmpPage;
while (tmpPage.read(inFile) && tmpPage.getBitstreamSerialNumber() != thisSegment.tid){}
if ((tmpPage.getBitstreamSerialNumber() == thisSegment.tid) && tmpPage.getHeaderType() == OGG::Continued){
@ -328,37 +324,36 @@ namespace Mist{
}
readFullPacket = true;
}
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){
unsigned long blockSize = 0;
if (oggTracks[curPos.trackID].codec == OGG::VORBIS){
size_t blockSize = 0;
Utils::bitstreamLSBF packet;
packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
if (!packet.get(1)){
// Read index first
unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1));
size_t vModeIndex = packet.get(vorbis::ilog(oggTracks[curPos.trackID].vModes.size() - 1));
blockSize =
oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; // almost readable.
oggTracks[curPos.trackID].blockSize[oggTracks[curPos.trackID].vModes[vModeIndex].blockFlag]; // almost
// readable.
}else{
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
WARN_MSG("Packet type != 0");
}
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
}else if (oggTracks[thisSegment.tid].codec == OGG::THEORA){
curPos.time += oggTracks[curPos.trackID].msPerFrame * (blockSize / oggTracks[curPos.trackID].channels);
}else if (oggTracks[curPos.trackID].codec == OGG::THEORA){
if (lastCompleteSegment == true && curPage.getGranulePosition() != (0xFFFFFFFFFFFFFFFFull)){// this segment should be used to sync time using granule
long long unsigned int parseGranuleUpper =
curPage.getGranulePosition() >> oggTracks[thisSegment.tid].KFGShift;
long long unsigned int parseGranuleLower(curPage.getGranulePosition() &
((1 << oggTracks[thisSegment.tid].KFGShift) - 1));
thisSegment.time =
oggTracks[thisSegment.tid].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
uint64_t parseGranuleUpper = curPage.getGranulePosition() >> oggTracks[curPos.trackID].KFGShift;
uint64_t parseGranuleLower(curPage.getGranulePosition() &
((1 << oggTracks[curPos.trackID].KFGShift) - 1));
thisSegment.time = oggTracks[curPos.trackID].msPerFrame * (parseGranuleUpper + parseGranuleLower - 1);
curPos.time = thisSegment.time;
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].codec).toNetPacked();
std::string tmpStr = thisSegment.toJSON(oggTracks[curPos.trackID].codec).toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
// INFO_MSG("thisTime: %d", thisPacket.getTime());
}
curPos.time += oggTracks[thisSegment.tid].msPerFrame;
}else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){
curPos.time += oggTracks[curPos.trackID].msPerFrame;
}else if (oggTracks[curPos.trackID].codec == OGG::OPUS){
if (thisSegment.parts.size()){
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
}
@ -366,21 +361,22 @@ namespace Mist{
if (readFullPacket){currentPositions.insert(curPos);}
}// getnext()
long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){
switch (oggTracks[tid].codec){
uint64_t inputOGG::calcGranuleTime(size_t tid, uint64_t granule){
size_t idx = M.trackIDToIndex(tid, getpid());
switch (oggTracks[idx].codec){
case OGG::VORBIS:
return granule * oggTracks[tid].msPerFrame; //= samples * samples per second
return granule * oggTracks[idx].msPerFrame; //= samples * samples per second
break;
case OGG::OPUS:
return granule / 48; // always 48kHz
break;
case OGG::THEORA:{
long long unsigned int parseGranuleUpper = granule >> oggTracks[tid].KFGShift;
long long unsigned int parseGranuleLower = (granule & ((1 << oggTracks[tid].KFGShift) - 1));
return (parseGranuleUpper + parseGranuleLower) * oggTracks[tid].msPerFrame; //= frames * msPerFrame
uint64_t parseGranuleUpper = granule >> oggTracks[idx].KFGShift;
uint64_t parseGranuleLower = (granule & ((1 << oggTracks[idx].KFGShift) - 1));
return (parseGranuleUpper + parseGranuleLower) * oggTracks[idx].msPerFrame; //= frames * msPerFrame
break;
}
default: DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule"); break;
default: WARN_MSG("Unknown codec, can not calculate time from granule"); break;
}
return 0;
}
@ -398,27 +394,28 @@ namespace Mist{
}
#endif
void inputOGG::seek(int seekTime){
void inputOGG::seek(uint64_t seekTime, size_t idx){
currentPositions.clear();
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime);
MEDIUM_MSG("Seeking to %" PRIu64 "ms", seekTime);
// for every track
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
// find first keyframe before keyframe with ms > seektime
position tmpPos;
tmpPos.trackID = *it;
tmpPos.time = myMeta.tracks[*it].keys.begin()->getTime();
tmpPos.bytepos = myMeta.tracks[*it].keys.begin()->getBpos();
for (std::deque<DTSC::Key>::iterator ot = myMeta.tracks[*it].keys.begin();
ot != myMeta.tracks[*it].keys.end(); ot++){
if (ot->getTime() > seekTime){
tmpPos.trackID = it->first;
DTSC::Keys keys(M.keys(it->first));
tmpPos.time = keys.getTime(keys.getFirstValid());
tmpPos.bytepos = keys.getBpos(keys.getFirstValid());
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
if (keys.getTime(i) > seekTime){
break;
}else{
tmpPos.time = ot->getTime();
tmpPos.bytepos = ot->getBpos();
tmpPos.time = keys.getTime(i);
tmpPos.bytepos = keys.getBpos(i);
}
}
INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos);
INFO_MSG("Found %" PRIu64 "ms for track %zu at %" PRIu64 " bytepos %" PRIu64, seekTime,
it->first, tmpPos.time, tmpPos.bytepos);
int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1);
fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET);
char buffer[300];
@ -428,28 +425,15 @@ namespace Mist{
loc = (char *)memrchr(buffer, 'O', (loc - buffer) - 1); // seek reverse
}
if (!loc){
INFO_MSG("Unable to find a page boundary starting @ %llu, track %lu", tmpPos.bytepos, *it);
INFO_MSG("Unable to find a page boundary starting @ %" PRIu64 ", track %zu", tmpPos.bytepos, it->first);
continue;
}
tmpPos.segmentNo = backChrs - (loc - buffer);
tmpPos.bytepos -= tmpPos.segmentNo;
INFO_MSG("Track %lu, segment %llu found at bytepos %llu", *it, tmpPos.segmentNo, tmpPos.bytepos);
INFO_MSG("Track %zu, segment %zu found at bytepos %" PRIu64, it->first, tmpPos.segmentNo,
tmpPos.bytepos);
currentPositions.insert(tmpPos);
}
}
void inputOGG::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
}
}// namespace Mist

View file

@ -6,7 +6,7 @@ namespace Mist{
struct segPart{
char *segData;
unsigned int len;
size_t len;
};
class segment{
@ -25,43 +25,15 @@ namespace Mist{
struct position{
bool operator<(const position &rhs) const{
if (time < rhs.time){
return true;
}else{
if (time == rhs.time){
if (trackID < rhs.trackID){return true;}
}
}
return false;
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
return trackID < rhs.trackID;
}
uint64_t trackID;
uint64_t time;
uint64_t bytepos;
uint64_t segmentNo;
};
/*
class oggTrack{
public:
oggTrack() : lastTime(0), parsedHeaders(false), lastPageOffset(0), nxtSegment(0){}
codecType codec;
std::string contBuffer;//buffer for continuing pages
segment myBuffer;
double lastTime;
long long unsigned int lastGran;
bool parsedHeaders;
double msPerFrame;
long long unsigned int lastPageOffset;
OGG::Page myPage;
unsigned int nxtSegment;
//Codec specific elements
//theora
theora::header idHeader;
//vorbis
std::deque<vorbis::mode> vModes;
char channels;
unsigned long blockSize[2];
unsigned long getBlockSize(unsigned int vModeIndex);
};*/
class inputOGG : public Input{
public:
@ -72,18 +44,17 @@ namespace Mist{
bool checkArguments();
bool preRun();
bool readHeader();
position seekFirstData(long long unsigned int tid);
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
position seekFirstData(size_t tid);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void parseBeginOfStream(OGG::Page &bosPage);
std::set<position> currentPositions;
FILE *inFile;
std::map<long unsigned int, OGG::oggTrack> oggTracks; // this remembers all metadata for every track
std::set<segment> sortedSegments; // probably not needing this
long long unsigned int calcGranuleTime(unsigned long tid, long long unsigned int granule);
long long unsigned int calcSegmentDuration(unsigned long tid, std::string &segment);
std::map<size_t, OGG::oggTrack> oggTracks; // this remembers all metadata for every track
std::set<segment> sortedSegments; // probably not needing this
uint64_t calcGranuleTime(size_t tid, uint64_t granule);
uint64_t calcSegmentDuration(size_t tid, std::string &segment);
};
}// namespace Mist

View file

@ -15,7 +15,7 @@ void insertRTP(const uint64_t track, const RTP::Packet &p){
///\param data The RTP Packet that needs to be sent
///\param len The size of data
///\param channel Not used here, but is kept for compatibility with sendTCP
void sendUDP(void *socket, char *data, unsigned int len, unsigned int channel){
void sendUDP(void *socket, const char *data, size_t len, uint8_t channel){
((Socket::UDPConnection *)socket)->SendNow(data, len);
if (mainConn){mainConn->addUp(len);}
}
@ -27,8 +27,10 @@ namespace Mist{
InputRTSP::InputRTSP(Util::Config *cfg) : Input(cfg){
needAuth = false;
setPacketOffset = false;
packetOffset = 0;
TCPmode = true;
sdpState.myMeta = &myMeta;
sdpState.myMeta = &meta;
sdpState.incomingPacketCallback = incomingPacket;
classPointer = this;
standAlone = false;
@ -153,28 +155,29 @@ namespace Mist{
}
if (sdpState.tracks.size()){
bool atLeastOne = false;
for (std::map<uint32_t, SDP::Track>::iterator it = sdpState.tracks.begin();
for (std::map<size_t, SDP::Track>::iterator it = sdpState.tracks.begin();
it != sdpState.tracks.end(); ++it){
transportSet = false;
extraHeaders.clear();
extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode);
sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders);
lastRequestedSetup = HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl();
sendCommand("SETUP", lastRequestedSetup, "", &extraHeaders);
if (tcpCon && transportSet){
atLeastOne = true;
continue;
}
if (!atLeastOne && tcpCon){
INFO_MSG("Failed to set up transport for track %s, switching transports...",
myMeta.tracks[it->first].getIdentifier().c_str());
M.getTrackIdentifier(it->first).c_str());
TCPmode = !TCPmode;
extraHeaders["Transport"] = it->second.generateTransport(it->first, url.host, TCPmode);
sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders);
sendCommand("SETUP", lastRequestedSetup, "", &extraHeaders);
}
if (tcpCon && transportSet){
atLeastOne = true;
continue;
}
FAIL_MSG("Could not setup track %s!", myMeta.tracks[it->first].getIdentifier().c_str());
FAIL_MSG("Could not setup track %s!", M.getTrackIdentifier(it->first).c_str());
tcpCon.close();
return;
}
@ -183,11 +186,7 @@ namespace Mist{
extraHeaders.clear();
extraHeaders["Range"] = "npt=0.000-";
sendCommand("PLAY", url.getUrl(), "", &extraHeaders);
if (!TCPmode){
connectedAt = Util::epoch() + 2208988800ll;
}else{
tcpCon.setBlocking(true);
}
if (TCPmode){tcpCon.setBlocking(true);}
}
void InputRTSP::closeStreamSource(){
@ -199,13 +198,9 @@ namespace Mist{
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
uint64_t startTime = Util::epoch();
uint64_t lastPing = Util::bootSecs();
uint64_t lastSecs = 0;
while (config->is_active && nProxy.userClient.isAlive() && parsePacket()){
while (keepAlive() && parsePacket()){
handleUDP();
// keep going
nProxy.userClient.keepAlive();
uint64_t currSecs = Util::bootSecs();
if (currSecs - lastPing > 30){
if (Util::bootSecs() - lastPing > 30){
sendCommand("GET_PARAMETER", url.getUrl(), "");
lastPing = Util::bootSecs();
}
@ -236,7 +231,7 @@ namespace Mist{
statsPage.finish();
if (!tcpCon){return "TCP connection closed";}
if (!config->is_active){return "received deactivate signal";}
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
if (!keepAlive()){return "buffer shutdown";}
return "Unknown";
}
@ -246,8 +241,7 @@ namespace Mist{
do{
// No new data? Sleep and retry, if connection still open
if (!tcpCon.Received().size() || !tcpCon.Received().available(1)){
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
nProxy.userClient.keepAlive();
if (!tcpCon.spool() && tcpCon && keepAlive()){
Util::sleep(waitTime);
if (!mustHave){return tcpCon;}
}
@ -288,14 +282,15 @@ namespace Mist{
seenSDP = true;
sdpState.parseSDP(recH.body);
recH.Clean();
INFO_MSG("SDP contained %llu tracks", myMeta.tracks.size());
INFO_MSG("SDP contained %zu tracks", M.getValidTracks().size());
return true;
}
if (recH.hasHeader("Transport")){
INFO_MSG("Received setup response");
uint32_t trackNo = sdpState.parseSetup(recH, url.host, "");
if (trackNo){
INFO_MSG("Parsed transport for track: %lu", trackNo);
recH.url = lastRequestedSetup;
size_t trackNo = sdpState.parseSetup(recH, url.host, "");
if (trackNo != INVALID_TRACK_ID){
INFO_MSG("Parsed transport for track: %zu", trackNo);
transportSet = true;
}else{
INFO_MSG("Could not parse transport string!");
@ -319,17 +314,11 @@ namespace Mist{
recH.Clean();
return true;
}
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
nProxy.userClient.keepAlive();
Util::sleep(waitTime);
}
if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);}
continue;
}
if (!tcpCon.Received().available(4)){
if (!tcpCon.spool() && tcpCon && config->is_active && nProxy.userClient.isAlive()){
nProxy.userClient.keepAlive();
Util::sleep(waitTime);
}
if (!tcpCon.spool() && tcpCon && keepAlive()){Util::sleep(waitTime);}
continue;
}// a TCP RTP packet, but not complete yet
@ -345,19 +334,26 @@ namespace Mist{
std::string tcpPacket = tcpCon.Received().remove(len + 4);
RTP::Packet pkt(tcpPacket.data() + 4, len);
uint8_t chan = tcpHead.data()[1];
uint32_t trackNo = sdpState.getTrackNoForChannel(chan);
EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %llu", len,
(unsigned int)pkt.getSequence(), chan, pkt.getTimeStamp());
if (!trackNo && (chan % 2) != 1){
size_t trackNo = sdpState.getTrackNoForChannel(chan);
EXTREME_MSG("Received %ub RTP packet #%u on channel %u, time %" PRIu32, len,
pkt.getSequence(), chan, pkt.getTimeStamp());
if ((trackNo == INVALID_TRACK_ID) && (chan % 2) != 1){
WARN_MSG("Received packet for unknown track number on channel %u", chan);
}
if (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();}
if (trackNo != INVALID_TRACK_ID){
sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();
}
sdpState.handleIncomingRTP(trackNo, pkt);
if (trackNo != INVALID_TRACK_ID){
if (!userSelect.count(trackNo)){
userSelect[trackNo].reload(streamName, trackNo, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
}
sdpState.handleIncomingRTP(trackNo, pkt);
}
return true;
}while (tcpCon && config->is_active && nProxy.userClient.isAlive());
}while (tcpCon && keepAlive());
return false;
}
@ -365,7 +361,7 @@ namespace Mist{
bool InputRTSP::handleUDP(){
if (TCPmode){return false;}
bool r = false;
for (std::map<uint32_t, SDP::Track>::iterator it = sdpState.tracks.begin();
for (std::map<size_t, SDP::Track>::iterator it = sdpState.tracks.begin();
it != sdpState.tracks.end(); ++it){
Socket::UDPConnection &s = it->second.data;
it->second.sorter.setCallback(it->first, insertRTP);
@ -380,14 +376,29 @@ namespace Mist{
if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();}
it->second.sorter.addPacket(pack);
}
if (Util::epoch() / 5 != it->second.rtcpSent){
it->second.rtcpSent = Util::epoch() / 5;
it->second.pack.sendRTCP_RR(connectedAt, it->second, it->first, myMeta, sendUDP);
if (Util::bootSecs() != it->second.rtcpSent){
it->second.rtcpSent = Util::bootSecs();
it->second.pack.sendRTCP_RR(it->second, sendUDP);
}
}
return r;
}
void InputRTSP::incoming(const DTSC::Packet &pkt){nProxy.bufferLivePacket(pkt, myMeta);}
void InputRTSP::incoming(const DTSC::Packet &pkt){
if (!M.getBootMsOffset()){
meta.setBootMsOffset(Util::bootMS() - pkt.getTime());
packetOffset = 0;
setPacketOffset = true;
}else if (!setPacketOffset){
packetOffset = (Util::bootMS() - pkt.getTime()) - M.getBootMsOffset();
setPacketOffset = true;
}
static DTSC::Packet newPkt;
char *pktData;
size_t pktDataLen;
pkt.getString("data", pktData, pktDataLen);
bufferLivePacket(pkt.getTime() + packetOffset, pkt.getInt("offset"), pkt.getTrackId(), pktData,
pktDataLen, 0, pkt.getFlag("keyframe"));
}
}// namespace Mist

View file

@ -22,11 +22,9 @@ namespace Mist{
bool checkArguments();
bool needHeader(){return false;}
bool readHeader(){return true;}
void getNext(bool smart = true){}
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
void seek(int seekTime){}
void sendCommand(const std::string &cmd, const std::string &cUrl, const std::string &body,
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true);
bool parsePacket(bool mustHave = false);
@ -43,8 +41,10 @@ namespace Mist{
bool TCPmode;
bool needAuth;
std::string session;
long long connectedAt; ///< The timestamp the connection was made, as reference point for RTCP
/// packets.
bool setPacketOffset;
int64_t packetOffset;
std::string lastRequestedSetup;
};
}// namespace Mist

View file

@ -47,25 +47,23 @@ namespace Mist{
bool InputSrt::readHeader(){
if (!fileSource.good()){return false;}
myMeta.tracks[1].trackID = 1;
myMeta.tracks[1].type = "meta";
myMeta.tracks[1].codec = "subtitle";
size_t idx = meta.addTrack();
meta.setID(idx, 1);
meta.setType(idx, "meta");
meta.setCodec(idx, "subtitle");
getNext();
while (thisPacket){
myMeta.update(thisPacket);
meta.update(thisPacket);
getNext();
}
// outputting dtsh file
myMeta.toFile(config->getString("input") + ".dtsh");
M.toFile(config->getString("input") + ".dtsh");
return true;
}
void InputSrt::getNext(bool smart){
bool hasPacket = false;
void InputSrt::getNext(size_t idx){
thisPacket.null();
std::string line;
@ -93,7 +91,7 @@ namespace Mist{
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = 1;
thisPack["bpos"] = (uint64_t)fileSource.tellg();
thisPack["bpos"] = fileSource.tellg();
thisPack["data"] = data;
thisPack["index"] = index;
thisPack["time"] = timestamp;
@ -133,12 +131,6 @@ namespace Mist{
thisPacket.null();
}
void InputSrt::seek(int seekTime){fileSource.seekg(0, fileSource.beg);}
void InputSrt::trackSelect(std::string trackSpec){
// we only have one track..
selectedTracks.clear();
selectedTracks.insert(1);
}
void InputSrt::seek(uint64_t seekTime, size_t idx){fileSource.seekg(0, fileSource.beg);}
}// namespace Mist

View file

@ -16,9 +16,8 @@ namespace Mist{
bool checkArguments();
bool readHeader();
bool preRun();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
bool vtt;
FILE *inFile;

View file

@ -27,82 +27,112 @@ TS::Stream liveStream(true);
Util::Config *cfgPointer = NULL;
#define THREAD_TIMEOUT 15
std::map<unsigned long long, unsigned long long> threadTimer;
std::map<size_t, uint64_t> threadTimer;
std::set<unsigned long> claimableThreads;
std::set<size_t> claimableThreads;
void parseThread(void *ignored){
void parseThread(void *mistIn){
Mist::inputTS *input = reinterpret_cast<Mist::inputTS *>(mistIn);
int tid = -1;
size_t tid = 0;
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
if (claimableThreads.size()){
tid = *claimableThreads.begin();
claimableThreads.erase(claimableThreads.begin());
}
if (tid == -1){return;}
}
if (tid == 0){return;}
Mist::negotiationProxy myProxy;
myProxy.streamName = globalStreamName;
DTSC::Meta myMeta;
Comms::Users userConn;
DTSC::Meta meta;
if (liveStream.isDataTrack(tid)){
bool dataTrack = liveStream.isDataTrack(tid);
if (dataTrack){
if (!Util::streamAlive(globalStreamName) &&
!Util::startInput(globalStreamName, "push://INTERNAL_ONLY:" + cfgPointer->getString("input"), true, true)){
FAIL_MSG("Could not start buffer for %s", globalStreamName.c_str());
return;
}
char userPageName[NAME_BUFFER_SIZE];
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, globalStreamName.c_str());
myProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
myProxy.userClient.countAsViewer = false;
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
if (!input->hasMeta()){input->reloadClientMeta();}
}
meta.reInit(globalStreamName, false);
}
size_t idx = meta.trackIDToIndex(tid, getpid());
threadTimer[tid] = Util::bootSecs();
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
(!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true))){
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer[tid] = Util::bootSecs();
}
if (liveStream.isDataTrack(tid)){myProxy.userClient.keepAlive();}
if (liveStream.isDataTrack(tid)){userConn.keepAlive();}
liveStream.parse(tid);
if (!liveStream.hasPacket(tid)){
Util::sleep(100);
continue;
}
uint64_t startSecs = Util::bootSecs();
while (liveStream.hasPacket(tid) && ((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive()))){
liveStream.initializeMetadata(myMeta, tid);
DTSC::Packet pack;
liveStream.getPacket(tid, pack);
if (!pack){
Util::sleep(100);
break;
while (liveStream.hasPacket(tid) &&
((Util::bootSecs() < startSecs + 2) && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || (userConn ? userConn.isAlive() : true)))){
liveStream.parse(tid);
if (liveStream.hasPacket(tid)){
if (idx == INVALID_TRACK_ID){
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
liveStream.initializeMetadata(meta, tid);
idx = meta.trackIDToIndex(tid, getpid());
if (idx != INVALID_TRACK_ID){
userConn.reload(globalStreamName, idx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK);
input->reloadClientMeta();
}
}
if (idx == INVALID_TRACK_ID || !meta.trackValid(idx)){continue;}
if (!meta.trackLoaded(idx)){meta.refresh();}
DTSC::Packet pack;
liveStream.getPacket(tid, pack);
if (pack){
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
if (!input->hasMeta()){input->reloadClientMeta();}
if (dataTrack){
char *data;
size_t dataLen;
pack.getString("data", data, dataLen);
input->bufferLivePacket(pack.getTime(), pack.getInt("offset"), idx, data, dataLen,
pack.getInt("bpos"), pack.getFlag("keyframe"));
}
}
}
if (myMeta.tracks.count(tid)){
myProxy.continueNegotiate(tid, myMeta, true);
myProxy.bufferLivePacket(pack, myMeta);
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer[tid] = Util::bootSecs();
}
if (!liveStream.hasPacket(tid)){
if (liveStream.isDataTrack(tid)){userConn.keepAlive();}
Util::sleep(100);
}
}
}
std::string reason = "unknown reason";
if (!(Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT)){reason = "thread timeout";}
if (!cfgPointer->is_active){reason = "input shutting down";}
if (!(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
if (!(!liveStream.isDataTrack(tid) || userConn.isAlive())){
reason = "buffer disconnect";
cfgPointer->is_active = false;
}
INFO_MSG("Shutting down thread for %d because %s", tid, reason.c_str());
INFO_MSG("Shutting down thread for %zu because %s", tid, reason.c_str());
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer.erase(tid);
}
liveStream.eraseTrack(tid);
myProxy.userClient.finish();
if (dataTrack && userConn){userConn.setStatus(COMM_STATUS_DISCONNECT);}
}
namespace Mist{
@ -117,6 +147,7 @@ namespace Mist{
"standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
capa["source_match"].append("/*.ts");
capa["source_file"] = "$source";
capa["source_match"].append("/*.m2ts");
capa["source_match"].append("stream://*.ts");
capa["source_match"].append("tsudp://*");
capa["source_match"].append("ts-exec:*");
@ -143,9 +174,9 @@ namespace Mist{
capa["codecs"][0u][1u].append("MP2");
inFile = NULL;
inputProcess = 0;
isFinished = false;
{
int fin = 0, fout = 0, ferr = 0;
pid_t srt_tx = -1;
const char *args[] ={"srt-live-transmit", 0};
srt_tx = Util::Procs::StartPiped(args, 0, 0, 0);
@ -265,23 +296,6 @@ namespace Mist{
return inFile;
}
/// Track selector of TS Input
///\arg trackSpec specifies which tracks are to be selected
///\todo test whether selecting a subset of tracks work
void inputTS::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long int index;
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
}else{
trackSpec = "";
}
}
}
bool inputTS::needHeader(){
if (!standAlone){return false;}
return Input::needHeader();
@ -295,6 +309,7 @@ namespace Mist{
///\todo Find errors, perhaps parts can be made more modular
bool inputTS::readHeader(){
if (!inFile){return false;}
meta.reInit(streamName);
TS::Packet packet; // to analyse and extract data
DTSC::Packet headerPack;
fseek(inFile, 0, SEEK_SET); // seek to beginning
@ -306,28 +321,39 @@ namespace Mist{
if (packet.getUnitStart()){
while (tsStream.hasPacketOnEachTrack()){
tsStream.getEarliestPacket(headerPack);
if (!headerPack){break;}
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
size_t pid = headerPack.getTrackId();
size_t idx = M.trackIDToIndex(pid, getpid());
if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){
tsStream.initializeMetadata(meta, pid);
idx = M.trackIDToIndex(pid, getpid());
}
myMeta.update(headerPack);
char *data;
size_t dataLen;
headerPack.getString("data", data, dataLen);
meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen,
headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen());
}
}
}
tsStream.finish();
INFO_MSG("Reached %s at %llu bytes", feof(inFile) ? "EOF" : "error", lastBpos);
INFO_MSG("Reached %s at %" PRIu64 " bytes", feof(inFile) ? "EOF" : "error", lastBpos);
while (tsStream.hasPacket()){
tsStream.getEarliestPacket(headerPack);
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
size_t pid = headerPack.getTrackId();
size_t idx = M.trackIDToIndex(pid, getpid());
if (idx == INVALID_TRACK_ID || !M.getCodec(idx).size()){
tsStream.initializeMetadata(meta, pid);
idx = M.trackIDToIndex(pid, getpid());
}
myMeta.update(headerPack);
char *data;
size_t dataLen;
headerPack.getString("data", data, dataLen);
meta.update(headerPack.getTime(), headerPack.getInt("offset"), idx, dataLen,
headerPack.getInt("bpos"), headerPack.getFlag("keyframe"), headerPack.getDataLen());
}
fseek(inFile, 0, SEEK_SET);
myMeta.toFile(config->getString("input") + ".dtsh");
meta.toFile(config->getString("input") + ".dtsh");
return true;
}
@ -335,40 +361,42 @@ namespace Mist{
/// At the moment, the logic of sending the last packet that was finished has been implemented,
/// but the seeking and finding data is not yet ready.
///\todo Finish the implementation
void inputTS::getNext(bool smart){
INSANE_MSG("Getting next");
void inputTS::getNext(size_t idx){
size_t pid = (idx == INVALID_TRACK_ID ? 0 : M.getID(idx));
INSANE_MSG("Getting next on track %zu", idx);
thisPacket.null();
bool hasPacket =
(selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
bool hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid));
while (!hasPacket && !feof(inFile) &&
(inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active){
tsBuf.FromFile(inFile);
if (selectedTracks.count(tsBuf.getPID())){
if (idx == INVALID_TRACK_ID || pid == tsBuf.getPID()){
tsStream.parse(tsBuf, 0); // bPos == 0
if (tsBuf.getUnitStart()){
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin())
: tsStream.hasPacket());
hasPacket = (idx == INVALID_TRACK_ID ? tsStream.hasPacket() : tsStream.hasPacket(pid));
}
}
}
if (feof(inFile)){
tsStream.finish();
if (!isFinished){
tsStream.finish();
isFinished = true;
}
hasPacket = true;
}
if (!hasPacket){return;}
if (selectedTracks.size() == 1){
if (tsStream.hasPacket(*selectedTracks.begin())){
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
}
}else{
if (idx == INVALID_TRACK_ID){
if (tsStream.hasPacket()){tsStream.getEarliestPacket(thisPacket);}
}else{
if (tsStream.hasPacket(pid)){tsStream.getPacket(pid, thisPacket);}
}
if (!thisPacket){
INFO_MSG("Could not getNext TS packet!");
return;
}
tsStream.initializeMetadata(myMeta);
if (!myMeta.tracks.count(thisPacket.getTrackId())){getNext();}
tsStream.initializeMetadata(meta);
size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid());
if (thisIdx == INVALID_TRACK_ID){getNext(idx);}
}
void inputTS::readPMT(){
@ -388,24 +416,31 @@ namespace Mist{
tsStream.partialClear();
// Restore original file position
if (Util::fseek(inFile, bpos, SEEK_SET)){return;}
if (Util::fseek(inFile, bpos, SEEK_SET)){
clearerr(inFile);
return;
}
}
/// Seeks to a specific time
void inputTS::seek(int seekTime){
void inputTS::seek(uint64_t seekTime, size_t idx){
tsStream.clear();
readPMT();
uint64_t seekPos = 0xFFFFFFFFFFFFFFFFull;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
unsigned long thisBPos = 0;
for (std::deque<DTSC::Key>::iterator keyIt = myMeta.tracks[*it].keys.begin();
keyIt != myMeta.tracks[*it].keys.end(); keyIt++){
if (keyIt->getTime() > seekTime){break;}
thisBPos = keyIt->getBpos();
tsStream.setLastms(*it, keyIt->getTime());
uint64_t seekPos = 0xFFFFFFFFull;
if (idx != INVALID_TRACK_ID){
DTSC::Keys keys(M.keys(idx));
uint32_t keyNum = keys.getNumForTime(seekTime);
seekPos = keys.getBpos(keyNum);
}else{
std::set<size_t> tracks = M.getValidTracks();
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
DTSC::Keys keys(M.keys(*it));
uint32_t keyNum = keys.getNumForTime(seekTime);
uint64_t thisBPos = keys.getBpos(keyNum);
if (thisBPos < seekPos){seekPos = thisBPos;}
}
if (thisBPos < seekPos){seekPos = thisBPos;}
}
clearerr(inFile);
Util::fseek(inFile, seekPos, SEEK_SET); // seek to the correct position
}
@ -424,22 +459,23 @@ namespace Mist{
}
void inputTS::parseStreamHeader(){
// Placeholder to force normal code to continue despite no tracks available
myMeta.tracks[0].type = "audio";
// Placeholder empty track to force normal code to continue despite no tracks available
tmpIdx = meta.addTrack(0, 0, 0, 0);
}
std::string inputTS::streamMainLoop(){
myMeta.tracks.clear(); // wipe the placeholder track from above
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
meta.removeTrack(tmpIdx);
INFO_MSG("Removed temptrack %zu", tmpIdx);
Comms::Statistics statComm;
uint64_t downCounter = 0;
uint64_t startTime = Util::epoch();
uint64_t startTime = Util::bootSecs();
uint64_t noDataSince = Util::bootSecs();
bool gettingData = false;
bool hasStarted = false;
cfgPointer = config;
globalStreamName = streamName;
unsigned long long threadCheckTimer = Util::bootSecs();
while (config->is_active && nProxy.userClient.isAlive()){
while (config->is_active){
if (tcpCon){
if (tcpCon.spool()){
while (tcpCon.Received().available(188)){
@ -471,7 +507,7 @@ namespace Mist{
gettingData = true;
INFO_MSG("Now receiving UDP data...");
}
int offset = 0;
size_t offset = 0;
// Try to read full TS Packets
// Watch out! We push here to a global, in order for threads to be able to access it.
while (offset < udpCon.data_len){
@ -489,7 +525,7 @@ namespace Mist{
uint32_t maxBytes =
std::min((uint32_t)(188 - leftData.size()), (uint32_t)(udpCon.data_len - offset));
uint32_t numBytes = maxBytes;
VERYHIGH_MSG("%lu bytes of non-sync-byte data received", numBytes);
VERYHIGH_MSG("%" PRIu32 " bytes of non-sync-byte data received", numBytes);
if (leftData.size()){
leftData.append(udpCon.data + offset, numBytes);
while (leftData.size() >= 188){
@ -517,28 +553,23 @@ namespace Mist{
// Check for and spawn threads here.
if (Util::bootSecs() - threadCheckTimer > 1){
// Connect to stats for INPUT detection
uint64_t now = Util::epoch();
if (!statsPage.getData()){
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
}
if (statsPage.getData()){
if (!statsPage.isAlive()){
statComm.reload();
if (statComm){
if (!statComm.isAlive()){
config->is_active = false;
statsPage.finish();
return "received shutdown request from controller";
}
IPC::statExchange tmpEx(statsPage.getData());
tmpEx.now(now);
tmpEx.crc(getpid());
tmpEx.streamName(streamName);
tmpEx.connector("INPUT");
tmpEx.up(0);
tmpEx.down(downCounter + tcpCon.dataDown());
tmpEx.time(now - startTime);
tmpEx.lastSecond(0);
statsPage.keepAlive();
uint64_t now = Util::bootSecs();
statComm.setNow(now);
statComm.setCRC(getpid());
statComm.setStream(streamName);
statComm.setConnector("INPUT");
statComm.setUp(0);
statComm.setDown(downCounter + tcpCon.dataDown());
statComm.setTime(now - startTime);
statComm.setLastSecond(0);
statComm.keepAlive();
}
nProxy.userClient.keepAlive();
std::set<size_t> activeTracks = liveStream.getActiveTracks();
{
@ -546,7 +577,6 @@ namespace Mist{
if (hasStarted && !threadTimer.size()){
if (!isAlwaysOn()){
config->is_active = false;
statsPage.finish();
return "no active threads and we had input in the past";
}else{
hasStarted = false;
@ -555,7 +585,8 @@ namespace Mist{
for (std::set<size_t>::iterator it = activeTracks.begin(); it != activeTracks.end(); it++){
if (!liveStream.isDataTrack(*it)){continue;}
if (threadTimer.count(*it) && ((Util::bootSecs() - threadTimer[*it]) > (2 * THREAD_TIMEOUT))){
WARN_MSG("Thread for track %d timed out %d seconds ago without a clean shutdown.",
WARN_MSG("Thread for track %" PRIu64 " timed out %" PRIu64
" seconds ago without a clean shutdown.",
*it, Util::bootSecs() - threadTimer[*it]);
threadTimer.erase(*it);
}
@ -566,7 +597,7 @@ namespace Mist{
claimableThreads.insert(*it);
// Spawn thread here.
tthread::thread thisThread(parseThread, 0);
tthread::thread thisThread(parseThread, this);
thisThread.detach();
}
}
@ -576,14 +607,12 @@ namespace Mist{
if (Util::bootSecs() - noDataSince > 20){
if (!isAlwaysOn()){
config->is_active = false;
statsPage.finish();
return "No packets received for 20 seconds - terminating";
}else{
noDataSince = Util::bootSecs();
}
}
}
statsPage.finish();
return "received shutdown request";
}
@ -612,9 +641,8 @@ namespace Mist{
inpt.substr(0, 7) != "http://" && inpt.substr(0, 10) != "http-ts://" &&
inpt.substr(0, 8) != "https://" && inpt.substr(0, 11) != "https-ts://"){
return Input::needsLock();
}else{
return false;
}
return false;
}
}// namespace Mist

View file

@ -20,9 +20,8 @@ namespace Mist{
bool preRun();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void getNext(size_t idx = INVALID_TRACK_ID);
void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID);
void readPMT();
bool openStreamSource();
void parseStreamHeader();
@ -34,6 +33,8 @@ namespace Mist{
Socket::Connection tcpCon;
TS::Packet tsBuf;
pid_t inputProcess;
size_t tmpIdx;
bool isFinished;
};
}// namespace Mist

File diff suppressed because it is too large Load diff

View file

@ -2,95 +2,46 @@
#include <deque>
#include <map>
#include <mist/comms.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/shared_memory.h>
#include <mist/encryption.h> //LTS
namespace Mist{
enum negotiationState{
FILL_NEW, ///< New track, just sent negotiation request
FILL_NEG, ///< Negotiating this track, written metadata
FILL_DEC, ///< Declined Track
FILL_ACC ///< Accepted Track
};
struct DTSCPageData{
DTSCPageData()
: pageNum(0), keyNum(0), partNum(0), dataSize(0), curOffset(0), firstTime(0), lastKeyTime(-5000){}
unsigned long pageNum; /// The current page number
unsigned long keyNum; ///< The number of keyframes in this page.
unsigned long partNum; ///< The number of parts in this page.
unsigned long long int dataSize; ///< The full size this page should be.
unsigned long long int curOffset; ///< The current write offset in the page.
unsigned long long int firstTime; ///< The first timestamp of the page.
unsigned long lastKeyTime; ///< The last key time encountered on this track.
};
class negotiationProxy{
public:
negotiationProxy();
void clear();
void initiateEncryption(); // LTS
bool bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta &myMeta);
void bufferNext(const DTSC::Packet &pack, DTSC::Meta &myMeta);
void bufferFinalize(unsigned long tid, DTSC::Meta &myMeta);
void bufferLivePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta);
void bufferSinglePacket(const DTSC::Packet &packet, DTSC::Meta &myMeta);
bool isBuffered(unsigned long tid, unsigned long keyNum);
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack; ///< Holds the data for all pages of a track. Based on unmapped tids
// Negotiation stuff (from unmapped tid's)
std::map<unsigned long, unsigned long> trackOffset; ///< Offset of data on user page
std::map<unsigned long, negotiationState> trackState; ///< State of the negotiation for tracks
std::map<unsigned long, unsigned long> trackMap; ///< Determines which input track maps onto which "final" track
std::map<unsigned long, IPC::sharedPage> metaPages; ///< For each track, holds the page that describes which dataPages are mapped
std::map<unsigned long, unsigned long> curPageNum; ///< For each track, holds the number page that is currently being written.
std::map<unsigned long, IPC::sharedPage> curPage; ///< For each track, holds the page that is currently being written.
std::map<unsigned long, std::deque<DTSC::Packet> > preBuffer; ///< For each track, holds to-be-buffered packets.
IPC::sharedClient userClient; ///< Shared memory used for connection to Mixer process.
std::string streamName; ///< Name of the stream to connect to
bool encrypt;
Encryption::verimatrixData vmData;
std::map<int, unsigned long long int> iVecs;
IPC::sharedPage encryptionPage;
void continueNegotiate(unsigned long tid, DTSC::Meta &myMeta, bool quickNegotiate = false);
void continueNegotiate(DTSC::Meta &myMeta);
uint32_t negTimer; ///< How long we've been negotiating, in packets.
};
///\brief Class containing all basic input and output functions.
class InOutBase{
public:
void initiateMeta();
bool bufferStart(unsigned long tid, unsigned long pageNumber);
void bufferNext(const DTSC::Packet &pack);
void bufferFinalize(unsigned long tid);
void bufferRemove(unsigned long tid, unsigned long pageNumber);
virtual void bufferLivePacket(const DTSC::Packet &packet);
long unsigned int getMainSelectedTrack();
InOutBase();
bool isBuffered(size_t idx, uint32_t keyNum);
size_t bufferedOnPage(size_t idx, size_t keyNum);
size_t getMainSelectedTrack();
bool bufferStart(size_t idx, size_t pageNumber);
void bufferFinalize(size_t idx);
void bufferRemove(size_t idx, size_t pageNumber);
void bufferLivePacket(const DTSC::Packet &packet);
void bufferNext(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData,
size_t packDataSize, uint64_t packBytePos, bool isKeyframe);
void bufferLivePacket(uint64_t packTime, int64_t packOffset, uint32_t packTrack, const char *packData,
size_t packDataSize, uint64_t packBytePos, bool isKeyframe);
protected:
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
void continueNegotiate();
bool standAlone;
negotiationProxy nProxy;
DTSC::Packet thisPacket; // The current packet that is being parsed
std::string streamName;
std::set<unsigned long> selectedTracks; ///< Stores the track id's that are either selected for playback or input
DTSC::Meta meta;
const DTSC::Meta &M;
DTSC::Meta myMeta; ///< Stores either the input or output metadata
std::map<size_t, Comms::Users> userSelect;
std::map<size_t, size_t> curPageNum; ///< For each track, holds the number page that is currently being written.
std::map<size_t, IPC::sharedPage> curPage; ///< For each track, holds the page that is currently being written.
};
}// namespace Mist

View file

@ -12,6 +12,7 @@ int spawnForked(Socket::Connection &S){
void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){
HIGH_MSG("USR1 received - triggering rolling restart");
Util::Config::is_restarting = true;
Util::Config::logExitReason("setting is_active to false because of received USR1");
Util::Config::is_active = false;
}

BIN
src/output/noffmpeg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/output/noh264.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
#include "../io.h"
#include <cstdlib>
#include <map>
#include <mist/comms.h>
#include <mist/config.h>
#include <mist/dtsc.h>
#include <mist/flv_tag.h>
@ -20,9 +21,10 @@ namespace Mist{
if (time < rhs.time){return true;}
return (time == rhs.time && tid < rhs.tid);
}
uint64_t tid;
size_t tid;
uint64_t time;
uint32_t offset;
uint64_t offset;
size_t partIndex;
};
/// The output class is intended to be inherited by MistOut process classes.
@ -44,18 +46,17 @@ namespace Mist{
// non-virtual generic functions
virtual int run();
virtual void stats(bool force = false);
void seek(unsigned long long pos, bool toKey = false);
bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false);
void seek(uint64_t pos, bool toKey = false);
bool seek(size_t tid, uint64_t pos, bool getNextKey);
void seekKeyframesIn(unsigned long long pos, unsigned long long maxDelta);
void stop();
uint64_t currentTime();
uint64_t startTime();
uint64_t endTime();
uint64_t liveTime();
void setBlocking(bool blocking);
void updateMeta();
void selectTrack(const std::string &trackType, const std::string &trackVal); /*LTS*/
bool selectDefaultTracks();
bool connectToFile(std::string file);
bool connectToFile(std::string file, bool append = false);
static bool listenMode(){return true;}
uint32_t currTrackCount() const;
virtual bool isReadyForPlay();
@ -64,11 +65,13 @@ namespace Mist{
/// This function is called whenever a packet is ready for sending.
/// Inside it, thisPacket is guaranteed to contain a valid packet.
virtual void sendNext(){}// REQUIRED! Others are optional.
bool getKeyFrame();
bool prepareNext();
virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true);
virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true);
virtual void onRequest();
static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S));
virtual void initialSeek();
uint64_t getMinKeepAway();
virtual bool liveSeek();
virtual bool onFinish(){return false;}
void reconnect();
@ -80,6 +83,8 @@ namespace Mist{
static Util::Config *config;
void playbackSleep(uint64_t millis);
void selectAllTracks();
private: // these *should* not be messed with in child classes.
/*LTS-START*/
void Log(std::string type, std::string message);
@ -90,15 +95,16 @@ namespace Mist{
std::string getCountry(std::string ip);
void doSync(bool force = false);
/*LTS-END*/
std::map<unsigned long, unsigned int> currKeyOpen;
void loadPageForKey(long unsigned int trackId, long long int keyNum);
int pageNumForKey(long unsigned int trackId, long long int keyNum);
int pageNumMax(long unsigned int trackId);
std::map<size_t, size_t> currentPage;
void loadPageForKey(size_t trackId, size_t keyNum);
uint64_t pageNumForKey(size_t trackId, size_t keyNum);
uint64_t pageNumMax(size_t trackId);
bool isRecordingToFile;
unsigned int lastStats; ///< Time of last sending of stats.
std::map<unsigned long, unsigned long> nxtKeyNum; ///< Contains the number of the next key, for page seeking purposes.
uint64_t lastStats; ///< Time of last sending of stats.
std::set<sortedPageInfo> buffer; ///< A sorted list of next-to-be-loaded packets.
bool sought; ///< If a seek has been done, this is set to true. Used for seeking on prepareNext().
bool sought; ///< If a seek has been done, this is set to true. Used for seeking on
///< prepareNext().
protected: // these are to be messed with by child classes
virtual bool inlineRestartCapable() const{
return false;
@ -106,25 +112,28 @@ namespace Mist{
bool pushing;
std::map<std::string, std::string> targetParams; /*LTS*/
std::string UA; ///< User Agent string, if known.
uint16_t uaDelay; ///< Seconds to wait before setting the UA.
uint64_t uaDelay; ///< Seconds to wait before setting the UA.
uint64_t lastRecv;
uint64_t extraKeepAway;
long long unsigned int firstTime; ///< Time of first packet after last seek. Used for real-time sending.
uint64_t firstTime; ///< Time of first packet after last seek. Used for real-time sending.
virtual std::string getConnectedHost();
virtual std::string getConnectedBinHost();
virtual std::string getStatsName();
virtual bool hasSessionIDs(){return false;}
IPC::sharedClient statsPage; ///< Shared memory used for statistics reporting.
bool isBlocking; ///< If true, indicates that myConn is blocking.
uint32_t crc; ///< Checksum, if any, for usage in the stats.
unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp);
uint64_t nextKeyTime();
std::set<size_t> getSupportedTracks(const std::string &type = "") const;
inline bool keepGoing(){return config->is_active && myConn;}
Comms::Statistics statComm;
bool isBlocking; ///< If true, indicates that myConn is blocking.
uint32_t crc; ///< Checksum, if any, for usage in the stats.
// stream delaying variables
unsigned int maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps.
unsigned int realTime; ///< Playback speed in ms of data per second. eg: 0 is infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed.
uint32_t needsLookAhead; ///< Amount of millis we need to be able to look ahead in the metadata
uint64_t maxSkipAhead; ///< Maximum ms that we will go ahead of the intended timestamps.
uint64_t realTime; ///< Playback speed in ms of wallclock time per data-second. eg: 0 is
///< infinite, 1000 real-time, 5000 is 0.2X speed, 500 = 2X speed.
uint64_t needsLookAhead; ///< Amount of millis we need to be able to look ahead in the metadata
// Read/write status variables
Socket::Connection &myConn; ///< Connection to the client.
@ -134,17 +143,17 @@ namespace Mist{
bool isInitialized; ///< If false, triggers initialization if parseData is true.
bool sentHeader; ///< If false, triggers sendHeader if parseData is true.
std::map<int, DTSCPageData> bookKeeping;
virtual bool isRecording();
virtual bool isFileTarget();
virtual bool isPushing(){return pushing;};
bool allowPush(const std::string &passwd);
void waitForStreamPushReady();
bool pushIsOngoing;
void bufferLivePacket(const DTSC::Packet &packet);
uint64_t firstPacketTime;
uint64_t lastPacketTime;
inline bool keepGoing(){return config->is_active && myConn;}
size_t thisIdx;
};
}// namespace Mist

678
src/output/output_cmaf.cpp Normal file
View file

@ -0,0 +1,678 @@
#include "output_cmaf.h"
#include <iomanip>
#include <mist/bitfields.h>
#include <mist/checksum.h>
#include <mist/cmaf.h>
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/langcodes.h> /*LTS*/
#include <mist/mp4.h>
#include <mist/mp4_dash.h>
#include <mist/mp4_encryption.h>
#include <mist/mp4_generic.h>
#include <mist/stream.h>
#include <mist/timing.h>
namespace Mist{
OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){
uaDelay = 0;
realTime = 0;
}
OutCMAF::~OutCMAF(){}
void OutCMAF::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "CMAF";
capa["friendly"] = "CMAF (fMP4) over HTTP (DASH, HLS7, HSS)";
capa["desc"] = "Segmented streaming in CMAF (fMP4) format over HTTP";
capa["url_rel"] = "/cmaf/$/";
capa["url_prefix"] = "/cmaf/$/";
capa["socket"] = "http_dash_mp4";
capa["codecs"][0u][0u].append("+H264");
capa["codecs"][0u][1u].append("+HEVC");
capa["codecs"][0u][2u].append("+AAC");
capa["codecs"][0u][3u].append("+AC3");
capa["codecs"][0u][4u].append("+MP3");
capa["codecs"][0u][5u].append("+subtitle");
capa["encryption"].append("CTR128");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "dash/video/mp4";
capa["methods"][0u]["url_rel"] = "/cmaf/$/index.mpd";
capa["methods"][0u]["priority"] = 8;
capa["methods"][1u]["handler"] = "http";
capa["methods"][1u]["type"] = "html5/application/vnd.apple.mpegurl;version=7";
capa["methods"][1u]["url_rel"] = "/cmaf/$/index.m3u8";
capa["methods"][1u]["priority"] = 8;
capa["methods"][2u]["handler"] = "http";
capa["methods"][2u]["type"] = "html5/application/vnd.ms-sstr+xml";
capa["methods"][2u]["url_rel"] = "/cmaf/$/Manifest";
capa["methods"][2u]["priority"] = 8;
// MP3 does not work in browsers
capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]");
cfg->addOption("nonchunked",
JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not "
"send chunked, but buffer whole segments.\"}"));
capa["optional"]["nonchunked"]["name"] = "Send whole segments";
capa["optional"]["nonchunked"]["help"] =
"Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance "
"significantly, but increases compatibility somewhat.";
capa["optional"]["nonchunked"]["option"] = "--nonchunked";
}
void OutCMAF::onHTTP(){
initialize();
if (H.url.size() < streamName.length() + 7){
H.Clean();
H.SendResponse("404", "Stream not found", myConn);
H.Clean();
return;
}
std::string method = H.method;
std::string url = H.url.substr(streamName.length() + 7); // Strip /cmaf/<streamname>/ from url
// Send a dash manifest for any URL with .mpd in the path
if (url.find(".mpd") != std::string::npos){
sendDashManifest();
return;
}
// Send a hls manifest for any URL with index.m3u8 in the path
if (url.find("index.m3u8") != std::string::npos){
size_t loc = url.find("index.m3u8");
if (loc == 0){
sendHlsManifest();
return;
}
size_t idx = atoll(url.c_str());
if (url.find("?") == std::string::npos){
sendHlsManifest(idx);
return;
}
return;
}
// Send a smooth manifest for any URL with .mpd in the path
if (url.find("Manifest") != std::string::npos){
sendSmoothManifest();
return;
}
H.Clean();
H.SetHeader("Content-Type", "video/mp4");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
size_t idx = atoll(url.c_str());
if (url.find("Q(") != std::string::npos){
idx = atoll(url.c_str() + url.find("Q(") + 2) % 100;
}
if (!M.getValidTracks().count(idx)){
H.Clean();
H.SendResponse("404", "Track not found", myConn);
H.Clean();
return;
}
if (url.find(".m4s") == std::string::npos){
H.Clean();
H.SendResponse("404", "File not found", myConn);
H.Clean();
return;
}
// Select the right track
userSelect.clear();
userSelect[idx].reload(streamName, idx);
H.StartResponse(H, myConn, config->getBool("nonchunked"));
if (url.find("init.m4s") != std::string::npos){
std::string headerData = CMAF::trackHeader(M, idx);
H.Chunkify(headerData.c_str(), headerData.size(), myConn);
H.Chunkify("", 0, myConn);
H.Clean();
return;
}
uint64_t startTime = atoll(url.c_str() + url.find("/chunk_") + 7);
if (M.getVod()){startTime += M.getFirstms(idx);}
uint64_t fragmentIndex = M.getFragmentIndexForTime(idx, startTime);
targetTime = M.getTimeForFragmentIndex(idx, fragmentIndex + 1);
std::string headerData = CMAF::fragmentHeader(M, idx, fragmentIndex);
H.Chunkify(headerData.c_str(), headerData.size(), myConn);
uint64_t mdatSize = 8 + CMAF::payloadSize(M, idx, fragmentIndex);
char mdatHeader[] ={0x00, 0x00, 0x00, 0x00, 'm', 'd', 'a', 't'};
Bit::htobl(mdatHeader, mdatSize);
H.Chunkify(mdatHeader, 8, myConn);
seek(startTime);
wantRequest = false;
parseData = true;
}
void OutCMAF::sendNext(){
if (thisPacket.getTime() >= targetTime){
HIGH_MSG("Finished playback to %" PRIu64, targetTime);
wantRequest = true;
parseData = false;
H.Chunkify("", 0, myConn);
H.Clean();
return;
}
char *data;
size_t dataLen;
thisPacket.getString("data", data, dataLen);
H.Chunkify(data, dataLen, myConn);
}
/***************************************************************************************************/
/* Utility */
/***************************************************************************************************/
bool OutCMAF::tracksAligned(const std::set<size_t> &trackList){
if (trackList.size() <= 1){return true;}
size_t baseTrack = *trackList.begin();
for (std::set<size_t>::iterator it = trackList.begin(); it != trackList.end(); ++it){
if (*it == baseTrack){continue;}
if (!M.tracksAlign(*it, baseTrack)){return false;}
}
return true;
}
void OutCMAF::generateSegmentlist(size_t idx, std::stringstream &s,
void callBack(uint64_t, uint64_t, std::stringstream &, bool)){
DTSC::Fragments fragments(M.fragments(idx));
uint32_t firstFragment = fragments.getFirstValid();
uint32_t endFragment = fragments.getEndValid();
bool first = true;
// skip the first two fragments if live
if (M.getLive() && (endFragment - firstFragment) > 6){firstFragment += 2;}
if (M.getType(idx) == "audio"){
uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){return;}
DTSC::Fragments f(M.fragments(mainTrack));
uint64_t firstVidTime = M.getTimeForFragmentIndex(mainTrack, f.getFirstValid());
firstFragment = M.getFragmentIndexForTime(idx, firstVidTime);
}
DTSC::Keys keys(M.keys(idx));
for (; firstFragment < endFragment; ++firstFragment){
uint32_t duration = fragments.getDuration(firstFragment);
uint64_t starttime = keys.getTime(fragments.getFirstKey(firstFragment));
if (!duration){
if (M.getLive()){continue;}// skip last fragment when live
duration = M.getLastms(idx) - starttime;
}
if (M.getVod()){starttime -= M.getFirstms(idx);}
callBack(starttime, duration, s, first);
first = false;
}
/*LTS-START
// remove lines to reduce size towards listlimit setting - but keep at least 4X target
// duration available
uint64_t listlimit = config->getInteger("listlimit");
if (listlimit){
while (lines.size() > listlimit &&
(totalDuration - durations.front()) > (targetDuration * 4000)){
lines.pop_front();
totalDuration -= durations.front();
durations.pop_front();
++skippedLines;
}
}
LTS-END*/
}
std::string OutCMAF::buildNalUnit(size_t len, const char *data){
char *res = (char *)malloc(len + 4);
Bit::htobl(res, len);
memcpy(res + 4, data, len);
return std::string(res, len + 4);
}
std::string OutCMAF::h264init(const std::string &initData){
char res[7];
snprintf(res, 7, "%.2X%.2X%.2X", initData[1], initData[2], initData[3]);
return res;
}
std::string OutCMAF::h265init(const std::string &initData){
char res[17];
snprintf(res, 17, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X", initData[1], initData[6], initData[7],
initData[8], initData[9], initData[10], initData[11], initData[12]);
return res;
}
/*********************************/
/* MPEG-DASH Manifest Generation */
/*********************************/
void OutCMAF::sendDashManifest(){
std::string method = H.method;
H.Clean();
H.SetHeader("Content-Type", "application/dash+xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
H.SetBody(dashManifest());
H.SendResponse("200", "OK", myConn);
H.Clean();
}
void dashSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){
s << "<S ";
if (first){s << "t=\"" << start << "\" ";}
s << "d=\"" << duration << "\" />" << std::endl;
}
std::string OutCMAF::dashTime(uint64_t time){
std::stringstream r;
r << "PT";
if (time >= 3600000){r << (time / 3600000) << "H";}
if (time >= 60000){r << (time / 60000) % 60 << "M";}
r << (time / 1000) % 60 << "." << std::setfill('0') << std::setw(3) << (time % 1000) << "S";
return r.str();
}
void OutCMAF::dashAdaptationSet(size_t id, size_t idx, std::stringstream &r){
std::string type = M.getType(idx);
r << "<AdaptationSet group=\"" << id << "\" mimeType=\"" << type << "/mp4\" ";
if (type == "video"){
r << "width=\"" << M.getWidth(idx) << "\" height=\"" << M.getHeight(idx) << "\" frameRate=\""
<< M.getFpks(idx) / 1000 << "\" ";
}
r << "segmentAlignment=\"true\" id=\"" << idx
<< "\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" << std::endl;
}
void OutCMAF::dashRepresentation(size_t id, size_t idx, std::stringstream &r){
std::string codec = M.getCodec(idx);
std::string type = M.getType(idx);
r << "<Representation id=\"" << idx << "\" bandwidth=\"" << M.getBps(idx) * 8 << "\" codecs=\"";
r << Util::codecString(M.getCodec(idx), M.getInit(idx));
r << "\" ";
if (type == "audio"){
r << "audioSamplingRate=\"" << M.getRate(idx)
<< "\"> <AudioChannelConfiguration "
"schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\""
<< M.getChannels(idx) << "\" /></Representation>" << std::endl;
}else{
r << "/>";
}
}
void OutCMAF::dashSegmentTemplate(std::stringstream &r){
r << "<SegmentTemplate timescale=\"1000"
"\" media=\"$RepresentationID$/chunk_$Time$.m4s\" "
"initialization=\"$RepresentationID$/init.m4s\"><SegmentTimeline>"
<< std::endl;
}
void OutCMAF::dashAdaptation(size_t id, std::set<size_t> tracks, bool aligned, std::stringstream &r){
if (!tracks.size()){return;}
if (aligned){
size_t firstTrack = *tracks.begin();
dashAdaptationSet(id, *tracks.begin(), r);
dashSegmentTemplate(r);
generateSegmentlist(firstTrack, r, dashSegment);
r << "</SegmentTimeline></SegmentTemplate>" << std::endl;
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
dashRepresentation(id, *it, r);
}
r << "</AdaptationSet>" << std::endl;
return;
}
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
std::string codec = M.getCodec(*it);
std::string type = M.getType(*it);
dashAdaptationSet(id, *tracks.begin(), r);
dashSegmentTemplate(r);
generateSegmentlist(*it, r, dashSegment);
r << "</SegmentTimeline></SegmentTemplate>" << std::endl;
dashRepresentation(id, *it, r);
r << "</AdaptationSet>" << std::endl;
}
}
/// Returns a string with the full XML DASH manifest MPD file.
std::string OutCMAF::dashManifest(bool checkAlignment){
initialize();
selectDefaultTracks();
std::set<size_t> vTracks;
std::set<size_t> aTracks;
std::set<size_t> sTracks;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (M.getType(it->first) == "video"){vTracks.insert(it->first);}
if (M.getType(it->first) == "audio"){aTracks.insert(it->first);}
if (M.getType(it->first) == "subtitle"){sTracks.insert(it->first);}
}
if (!vTracks.size() && !aTracks.size()){return "";}
bool videoAligned = checkAlignment && tracksAligned(vTracks);
bool audioAligned = checkAlignment && tracksAligned(aTracks);
std::stringstream r;
r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
r << "<MPD ";
size_t mainTrack = getMainSelectedTrack();
size_t mainDuration = M.getDuration(mainTrack);
if (M.getVod()){
r << "type=\"static\" mediaPresentationDuration=\"" << dashTime(mainDuration) << "\" minBufferTime=\"PT1.5S\" ";
}else{
r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\""
<< Util::getUTCString(Util::epoch() - M.getLastms(mainTrack) / 1000)
<< "\" timeShiftBufferDepth=\"" << dashTime(mainDuration)
<< "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\""
<< Util::getUTCString(Util::epoch()) << "\" ";
}
r << "profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"xmlns=\"urn:mpeg:dash:schema:mpd:2011\" >"
<< std::endl;
r << "<ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl;
r << "<Period " << (M.getLive() ? "start=\"0\"" : "") << ">" << std::endl;
dashAdaptation(1, vTracks, videoAligned, r);
dashAdaptation(2, aTracks, audioAligned, r);
if (sTracks.size()){
for (std::set<size_t>::iterator it = sTracks.begin(); it != sTracks.end(); it++){
std::string lang = (M.getLang(*it) == "" ? "unknown" : M.getLang(*it));
r << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang
<< "\"><Representation id=\"" << *it << "\" bandwidth=\"256\"><BaseURL>../../" << streamName
<< ".vtt?track=" << *it << "</BaseURL></Representation></AdaptationSet>" << std::endl;
}
}
r << "</Period></MPD>" << std::endl;
return r.str();
}
/******************************/
/* HLS v7 Manifest Generation */
/******************************/
void OutCMAF::sendHlsManifest(size_t idx, const std::string &sessId){
std::string method = H.method;
H.Clean();
// H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
H.SetHeader("Content-Type", "audio/mpegurl");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
if (idx == INVALID_TRACK_ID){
H.SetBody(hlsManifest());
}else{
H.SetBody(hlsManifest(idx, sessId));
}
H.SendResponse("200", "OK", myConn);
H.Clean();
}
void hlsSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){
s << "#EXTINF:" << (((double)duration) / 1000) << ",\r\nchunk_" << start << ".m4s" << std::endl;
}
///\brief Builds an index file for HTTP Live streaming.
///\return The index file for HTTP Live Streaming.
std::string OutCMAF::hlsManifest(){
std::stringstream result;
result << "#EXTM3U\r\n#EXT-X-VERSION:7\r\n#EXT-X-INDEPENDENT-SEGMENTS\r\n";
selectDefaultTracks();
std::set<size_t> vTracks;
std::set<size_t> aTracks;
std::set<size_t> sTracks;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (M.getType(it->first) == "video"){vTracks.insert(it->first);}
if (M.getType(it->first) == "audio"){aTracks.insert(it->first);}
if (M.getType(it->first) == "subtitle"){sTracks.insert(it->first);}
}
for (std::set<size_t>::iterator it = vTracks.begin(); it != vTracks.end(); it++){
std::string codec = M.getCodec(*it);
if (codec == "H264" || codec == "HEVC" || codec == "MPEG2"){
int bWidth = M.getBps(*it);
if (bWidth < 5){bWidth = 5;}
if (aTracks.size()){bWidth += M.getBps(*aTracks.begin());}
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8)
<< ",RESOLUTION=" << M.getWidth(*it) << "x" << M.getHeight(*it);
if (M.getFpks(*it)){result << ",FRAME-RATE=" << (float)M.getFpks(*it) / 1000;}
if (aTracks.size()){result << ",AUDIO=\"aud1\"";}
if (sTracks.size()){result << ",SUBTITLES=\"sub1\"";}
if (codec == "H264" || codec == "HEVC"){
result << ",CODECS=\"";
result << Util::codecString(M.getCodec(*it), M.getInit(*it));
result << "\"";
}
result << "\r\n" << *it;
if (hasSessionIDs()){
result << "/index.m3u8?sessId=" << getpid() << "\r\n";
}else{
result << "/index.m3u8\r\n";
}
}else if (codec == "subtitle"){
if (M.getLang(*it).empty()){meta.setLang(*it, "und");}
result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(*it)
<< "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it))
<< "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\""
<< "\r\n";
}
}
for (std::set<size_t>::iterator it = aTracks.begin(); it != aTracks.end(); it++){
if (M.getLang(*it).empty()){meta.setLang(*it, "und");}
result << "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"" << M.getLang(*it)
<< "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it))
<< "\",AUTOSELECT=YES,DEFAULT=YES,URI=\"" << *it << "/index.m3u8\""
<< "\r\n";
}
for (std::set<size_t>::iterator it = sTracks.begin(); it != sTracks.end(); it++){
if (M.getLang(*it).empty()){meta.setLang(*it, "und");}
result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(*it)
<< "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(*it))
<< "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\""
<< "\r\n";
}
if (aTracks.size() && !vTracks.size()){
std::string codec = M.getCodec(*aTracks.begin());
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << M.getBps(*aTracks.begin()) * 8;
result << ",CODECS=\""
<< Util::codecString(M.getCodec(*aTracks.begin()), M.getInit(*aTracks.begin())) << "\"\r\n";
result << *aTracks.begin() << "/index.m3u8\r\n";
}
HIGH_MSG("Sending this index: %s", result.str().c_str());
return result.str();
}
std::string OutCMAF::hlsManifest(size_t idx, const std::string &sessId){
std::stringstream result;
// parse single track
uint32_t targetDuration = (M.biggestFragment(idx) / 1000) + 1;
DTSC::Fragments fragments(M.fragments(idx));
uint32_t firstFragment = fragments.getFirstValid();
uint32_t endFragment = fragments.getEndValid();
// skip the first two fragments if live
if (M.getLive() && (endFragment - firstFragment) > 6){firstFragment += 2;}
if (M.getType(idx) == "audio"){
uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){return "";}
DTSC::Fragments f(M.fragments(mainTrack));
uint64_t firstVidTime = M.getTimeForFragmentIndex(mainTrack, f.getFirstValid());
firstFragment = M.getFragmentIndexForTime(idx, firstVidTime);
}
result << "#EXTM3U\r\n"
"#EXT-X-VERSION:7\r\n"
"#EXT-X-DISCONTINUITY\r\n"
"#EXT-X-TARGETDURATION:"
<< targetDuration << "\r\n";
if (M.getLive()){result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment << "\r\n";}
result << "#EXT-X-MAP:URI=\"init.m4s"
<< "\"\r\n";
generateSegmentlist(idx, result, hlsSegment);
if (M.getVod()){result << "#EXT-X-ENDLIST\r\n";}
return result.str();
}
/****************************************/
/* Smooth Streaming Manifest Generation */
/****************************************/
std::string toUTF16(const std::string &original){
std::string result;
result.append("\377\376", 2);
for (std::string::const_iterator it = original.begin(); it != original.end(); it++){
result += (*it);
result.append("\000", 1);
}
return result;
}
/// Converts bytes per second and track ID into a single bits per second value, where the last two
/// digits are the track ID. Breaks for track IDs > 99. But really, this is MS-SS, so who cares..?
uint64_t bpsAndIdToBitrate(uint32_t bps, uint64_t tid){
return ((uint64_t)((bps * 8) / 100)) * 100 + tid;
}
void smoothSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){
s << "<c ";
if (first){s << "t=\"" << start << "\" ";}
s << "d=\"" << duration << "\" />" << std::endl;
}
void OutCMAF::sendSmoothManifest(){
std::string method = H.method;
H.Clean();
H.SetHeader("Content-Type", "application/dash+xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
H.SetBody(smoothManifest());
H.SendResponse("200", "OK", myConn);
H.Clean();
}
void OutCMAF::smoothAdaptation(const std::string &type, std::set<size_t> tracks, std::stringstream &r){
if (!tracks.size()){return;}
DTSC::Keys keys(M.keys(*tracks.begin()));
r << "<StreamIndex Type=\"" << type << "\" QualityLevels=\"" << tracks.size() << "\" Name=\""
<< type << "\" Chunks=\"" << keys.getValidCount() << "\" Url=\"Q({bitrate})/"
<< "chunk_{start_time}.m4s\" ";
if (type == "video"){
size_t maxWidth = 0;
size_t maxHeight = 0;
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
size_t width = M.getWidth(*it);
size_t height = M.getHeight(*it);
if (width > maxWidth){maxWidth = width;}
if (height > maxHeight){maxHeight = height;}
}
r << "MaxWidth=\"" << maxWidth << "\" MaxHeight=\"" << maxHeight << "\" DisplayWidth=\""
<< maxWidth << "\" DisplayHeight=\"" << maxHeight << "\"";
}
r << ">\n";
size_t index = 0;
for (std::set<size_t>::iterator it = tracks.begin(); it != tracks.end(); it++){
r << "<QualityLevel Index=\"" << index++ << "\" Bitrate=\""
<< bpsAndIdToBitrate(M.getBps(*it) * 8, *it) << "\" CodecPrivateData=\"" << std::hex;
if (type == "audio"){
std::string init = M.getInit(*it);
for (unsigned int i = 0; i < init.size(); i++){
r << std::setfill('0') << std::setw(2) << std::right << (int)init[i];
}
r << std::dec << "\" SamplingRate=\"" << M.getRate(*it)
<< "\" Channels=\"2\" BitsPerSample=\"16\" PacketSize=\"4\" AudioTag=\"255\" "
"FourCC=\"AACL\" />\n";
}
if (type == "video"){
MP4::AVCC avccbox;
avccbox.setPayload(M.getInit(*it));
std::string tmpString = avccbox.asAnnexB();
for (size_t i = 0; i < tmpString.size(); i++){
r << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i];
}
r << std::dec << "\" MaxWidth=\"" << M.getWidth(*it) << "\" MaxHeight=\""
<< M.getHeight(*it) << "\" FourCC=\"AVC1\" />\n";
}
}
generateSegmentlist(*tracks.begin(), r, smoothSegment);
r << "</StreamIndex>\n";
}
/// Returns a string with the full XML DASH manifest MPD file.
std::string OutCMAF::smoothManifest(bool checkAlignment){
initialize();
std::stringstream r;
r << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n"
"<SmoothStreamingMedia MajorVersion=\"2\" MinorVersion=\"0\" TimeScale=\"1000\" ";
selectDefaultTracks();
std::set<size_t> vTracks;
std::set<size_t> aTracks;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (M.getType(it->first) == "video"){vTracks.insert(it->first);}
if (M.getType(it->first) == "audio"){aTracks.insert(it->first);}
}
if (!aTracks.size() && !vTracks.size()){
FAIL_MSG("No valid tracks found");
return "";
}
if (M.getVod()){
r << "Duration=\"" << M.getLastms(vTracks.size() ? *vTracks.begin() : *aTracks.begin()) << "\">\n";
}else{
r << "Duration=\"0\" IsLive=\"TRUE\" LookAheadFragmentCount=\"2\" DVRWindowLength=\""
<< M.getBufferWindow() << "\" CanSeek=\"TRUE\" CanPause=\"TRUE\">\n";
}
smoothAdaptation("audio", aTracks, r);
smoothAdaptation("video", vTracks, r);
r << "</SmoothStreamingMedia>\n";
return toUTF16(r.str());
}// namespace Mist
}// namespace Mist

43
src/output/output_cmaf.h Normal file
View file

@ -0,0 +1,43 @@
#include "output_http.h"
#include <mist/http_parser.h>
#include <mist/mp4_generic.h>
namespace Mist{
class OutCMAF : public HTTPOutput{
public:
OutCMAF(Socket::Connection &conn);
~OutCMAF();
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
void sendHeader(){};
protected:
void sendDashManifest();
void dashAdaptationSet(size_t id, size_t idx, std::stringstream &r);
void dashRepresentation(size_t id, size_t idx, std::stringstream &r);
void dashSegmentTemplate(std::stringstream &r);
void dashAdaptation(size_t id, std::set<size_t> tracks, bool aligned, std::stringstream &r);
std::string dashTime(uint64_t time);
std::string dashManifest(bool checkAlignment = true);
void sendHlsManifest(size_t idx = INVALID_TRACK_ID, const std::string &sessId = "");
std::string hlsManifest();
std::string hlsManifest(size_t idx, const std::string &sessId);
void sendSmoothManifest();
std::string smoothManifest(bool checkAlignment = true);
void smoothAdaptation(const std::string &type, std::set<size_t> tracks, std::stringstream &r);
void generateSegmentlist(size_t idx, std::stringstream &s,
void callBack(uint64_t, uint64_t, std::stringstream &, bool));
bool tracksAligned(const std::set<size_t> &trackList);
std::string buildNalUnit(size_t len, const char *data);
uint64_t targetTime;
std::string h264init(const std::string &initData);
std::string h265init(const std::string &initData);
};
}// namespace Mist
typedef Mist::OutCMAF mistOut;

View file

@ -1,613 +0,0 @@
#include "output_dash_mp4.h"
#include <iomanip>
#include <mist/checksum.h>
#include <mist/defines.h>
#include <mist/mp4.h>
#include <mist/mp4_dash.h>
#include <mist/mp4_generic.h>
#include <mist/stream.h>
#include <mist/timing.h>
namespace Mist{
OutDashMP4::OutDashMP4(Socket::Connection &conn) : HTTPOutput(conn){
uaDelay = 0;
realTime = 0;
}
OutDashMP4::~OutDashMP4(){}
std::string OutDashMP4::makeTime(uint64_t time){
std::stringstream r;
r << "PT";
if (time >= 3600000){r << (time / 3600000) << "H";}
if (time >= 60000){r << (time / 60000) % 60 << "M";}
r << (time / 1000) % 60 << "." << std::setfill('0') << std::setw(3) << (time % 1000) << "S";
return r.str();
}
/// Sends an empty moov box for the given track to the connected client, for following up with moof box(es).
void OutDashMP4::sendMoov(uint32_t tid){
DTSC::Track &Trk = myMeta.tracks[tid];
MP4::MOOV moovBox;
MP4::MVHD mvhdBox(0);
mvhdBox.setTrackID(1);
mvhdBox.setDuration(0xFFFFFFFF);
moovBox.setContent(mvhdBox, 0);
MP4::IODS iodsBox;
if (Trk.type == "video"){
iodsBox.setODVideoLevel(0xFE);
}else{
iodsBox.setODAudioLevel(0xFE);
}
moovBox.setContent(iodsBox, 1);
MP4::MVEX mvexBox;
MP4::MEHD mehdBox;
mehdBox.setFragmentDuration(0xFFFFFFFF);
mvexBox.setContent(mehdBox, 0);
MP4::TREX trexBox;
trexBox.setTrackID(1);
mvexBox.setContent(trexBox, 1);
moovBox.setContent(mvexBox, 2);
MP4::TRAK trakBox;
MP4::TKHD tkhdBox(1, 0, Trk.width, Trk.height);
tkhdBox.setFlags(3);
if (Trk.type == "audio"){
tkhdBox.setVolume(256);
tkhdBox.setWidth(0);
tkhdBox.setHeight(0);
}
tkhdBox.setDuration(0xFFFFFFFF);
trakBox.setContent(tkhdBox, 0);
MP4::MDIA mdiaBox;
MP4::MDHD mdhdBox(0);
mdhdBox.setLanguage(0x44);
mdhdBox.setDuration(Trk.lastms);
mdiaBox.setContent(mdhdBox, 0);
if (Trk.type == "video"){
MP4::HDLR hdlrBox(Trk.type, "VideoHandler");
mdiaBox.setContent(hdlrBox, 1);
}else{
MP4::HDLR hdlrBox(Trk.type, "SoundHandler");
mdiaBox.setContent(hdlrBox, 1);
}
MP4::MINF minfBox;
MP4::DINF dinfBox;
MP4::DREF drefBox;
dinfBox.setContent(drefBox, 0);
minfBox.setContent(dinfBox, 0);
MP4::STBL stblBox;
MP4::STSD stsdBox;
stsdBox.setVersion(0);
if (Trk.codec == "H264"){
MP4::AVC1 avc1Box;
avc1Box.setWidth(Trk.width);
avc1Box.setHeight(Trk.height);
MP4::AVCC avccBox;
avccBox.setPayload(Trk.init);
avc1Box.setCLAP(avccBox);
stsdBox.setEntry(avc1Box, 0);
}
if (Trk.codec == "HEVC"){
MP4::HEV1 hev1Box;
hev1Box.setWidth(Trk.width);
hev1Box.setHeight(Trk.height);
MP4::HVCC hvccBox;
hvccBox.setPayload(Trk.init);
hev1Box.setCLAP(hvccBox);
stsdBox.setEntry(hev1Box, 0);
}
if (Trk.codec == "AAC" || Trk.codec == "MP3"){
MP4::AudioSampleEntry ase;
ase.setCodec("mp4a");
ase.setDataReferenceIndex(1);
ase.setSampleRate(Trk.rate);
ase.setChannelCount(Trk.channels);
ase.setSampleSize(Trk.size);
MP4::ESDS esdsBox(Trk.init);
ase.setCodecBox(esdsBox);
stsdBox.setEntry(ase, 0);
}
if (Trk.codec == "AC3"){
///\todo Note: this code is copied, note for muxing seperation
MP4::AudioSampleEntry ase;
ase.setCodec("ac-3");
ase.setDataReferenceIndex(1);
ase.setSampleRate(Trk.rate);
ase.setChannelCount(Trk.channels);
ase.setSampleSize(Trk.size);
MP4::DAC3 dac3Box(Trk.rate, Trk.channels);
ase.setCodecBox(dac3Box);
stsdBox.setEntry(ase, 0);
}
stblBox.setContent(stsdBox, 0);
MP4::STTS sttsBox;
sttsBox.setVersion(0);
stblBox.setContent(sttsBox, 1);
MP4::STSC stscBox;
stscBox.setVersion(0);
stblBox.setContent(stscBox, 2);
MP4::STCO stcoBox;
stcoBox.setVersion(0);
stblBox.setContent(stcoBox, 3);
MP4::STSZ stszBox;
stszBox.setVersion(0);
stblBox.setContent(stszBox, 4);
minfBox.setContent(stblBox, 1);
if (Trk.type == "video"){
MP4::VMHD vmhdBox;
vmhdBox.setFlags(1);
minfBox.setContent(vmhdBox, 2);
}else{
MP4::SMHD smhdBox;
minfBox.setContent(smhdBox, 2);
}
mdiaBox.setContent(minfBox, 2);
trakBox.setContent(mdiaBox, 1);
moovBox.setContent(trakBox, 3);
H.Chunkify(moovBox.asBox(), moovBox.boxedSize(), myConn);
}
void OutDashMP4::sendMoof(uint32_t tid, uint32_t fragIndice){
DTSC::Track &Trk = myMeta.tracks[tid];
MP4::MOOF moofBox;
MP4::MFHD mfhdBox;
mfhdBox.setSequenceNumber(fragIndice + Trk.missedFrags);
moofBox.setContent(mfhdBox, 0);
MP4::TRAF trafBox;
MP4::TFHD tfhdBox;
tfhdBox.setTrackID(1);
if (Trk.type == "audio"){
tfhdBox.setFlags(MP4::tfhdSampleFlag);
tfhdBox.setDefaultSampleFlags(MP4::isKeySample);
}
trafBox.setContent(tfhdBox, 0);
MP4::TFDT tfdtBox;
tfdtBox.setBaseMediaDecodeTime(Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime());
trafBox.setContent(tfdtBox, 1);
MP4::TRUN trunBox;
if (Trk.type == "video"){
uint32_t headSize = 0;
if (Trk.codec == "H264"){
MP4::AVCC avccBox;
avccBox.setPayload(Trk.init);
headSize = 14 + avccBox.getSPSLen() + avccBox.getPPSLen();
}
if (Trk.codec == "HEVC"){
MP4::HVCC hvccBox;
hvccBox.setPayload(myMeta.tracks[tid].init);
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
headSize += 4 + (*it2).size();
}
}
}
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration |
MP4::trunfirstSampleFlags | MP4::trunsampleOffsets);
trunBox.setFirstSampleFlags(MP4::isKeySample);
trunBox.setDataOffset(0);
uint32_t j = 0;
for (DTSC::PartIter parts(Trk, Trk.fragments[fragIndice]); parts; ++parts){
MP4::trunSampleInformation trunEntry;
trunEntry.sampleSize = parts->getSize();
if (!j){trunEntry.sampleSize += headSize;}
trunEntry.sampleDuration = parts->getDuration();
trunEntry.sampleOffset = parts->getOffset();
trunBox.setSampleInformation(trunEntry, j);
++j;
}
trunBox.setDataOffset(92 + (12 * j) + 8);
}
if (Trk.type == "audio"){
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration);
trunBox.setDataOffset(0);
uint32_t j = 0;
for (DTSC::PartIter parts(Trk, Trk.fragments[fragIndice]); parts; ++parts){
MP4::trunSampleInformation trunEntry;
trunEntry.sampleSize = parts->getSize();
trunEntry.sampleDuration = parts->getDuration();
trunBox.setSampleInformation(trunEntry, j);
++j;
}
trunBox.setDataOffset(92 + (8 * j) + 8);
}
trafBox.setContent(trunBox, 2);
moofBox.setContent(trafBox, 1);
H.Chunkify(moofBox.asBox(), moofBox.boxedSize(), myConn);
}
std::string OutDashMP4::buildNalUnit(unsigned int len, const char *data){
std::stringstream r;
r << (char)((len >> 24) & 0xFF);
r << (char)((len >> 16) & 0xFF);
r << (char)((len >> 8) & 0xFF);
r << (char)((len)&0xFF);
r << std::string(data, len);
return r.str();
}
void OutDashMP4::sendMdat(uint32_t tid, uint32_t fragIndice){
DTSC::Track &Trk = myMeta.tracks[tid];
DTSC::Fragment &Frag = Trk.fragments[fragIndice];
uint32_t size = 8 + Frag.getSize();
if (Trk.codec == "H264"){
MP4::AVCC avccBox;
avccBox.setPayload(Trk.init);
size += 14 + avccBox.getSPSLen() + avccBox.getPPSLen();
}
if (Trk.codec == "HEVC"){
MP4::HVCC hvccBox;
hvccBox.setPayload(Trk.init);
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
size += 4 + (*it2).size();
}
}
}
char mdatstr[8] ={0, 0, 0, 0, 'm', 'd', 'a', 't'};
mdatstr[0] = (char)((size >> 24) & 0xFF);
mdatstr[1] = (char)((size >> 16) & 0xFF);
mdatstr[2] = (char)((size >> 8) & 0xFF);
mdatstr[3] = (char)((size)&0xFF);
H.Chunkify(mdatstr, 8, myConn);
std::string init;
if (Trk.codec == "H264"){
MP4::AVCC avccBox;
avccBox.setPayload(Trk.init);
init = buildNalUnit(2, "\011\340");
H.Chunkify(init, myConn); // 09E0
init = buildNalUnit(avccBox.getSPSLen(), avccBox.getSPS());
H.Chunkify(init, myConn);
init = buildNalUnit(avccBox.getPPSLen(), avccBox.getPPS());
H.Chunkify(init, myConn);
}
if (Trk.codec == "HEVC"){
MP4::HVCC hvccBox;
hvccBox.setPayload(Trk.init);
std::deque<MP4::HVCCArrayEntry> content = hvccBox.getArrays();
for (std::deque<MP4::HVCCArrayEntry>::iterator it = content.begin(); it != content.end(); it++){
for (std::deque<std::string>::iterator it2 = it->nalUnits.begin(); it2 != it->nalUnits.end(); it2++){
init = buildNalUnit((*it2).size(), (*it2).c_str());
H.Chunkify(init, myConn);
}
}
}
// we pull these values first, because seek() destroys our Trk reference
uint64_t startTime = Trk.getKey(Frag.getNumber()).getTime();
targetTime = startTime + Frag.getDuration();
HIGH_MSG("Starting playback from %llu to %llu", startTime, targetTime);
wantRequest = false;
parseData = true;
// select only the tid track, and seek to the start time
selectedTracks.clear();
selectedTracks.insert(tid);
seek(startTime);
}
void OutDashMP4::sendNext(){
if (thisPacket.getTime() >= targetTime){
HIGH_MSG("Finished playback to %llu", targetTime);
wantRequest = true;
parseData = false;
H.Chunkify("", 0, myConn);
H.Clean();
return;
}
char *data;
size_t dataLen;
thisPacket.getString("data", data, dataLen);
H.Chunkify(data, dataLen, myConn);
}
/// Examines Trk and adds playable fragments from it to r.
void OutDashMP4::addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live){
std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin();
bool first = true;
// skip the first two fragments if live
if (live && Trk.fragments.size() > 6){++(++it);}
for (; it != Trk.fragments.end(); it++){
uint64_t starttime = Trk.getKey(it->getNumber()).getTime();
uint32_t duration = it->getDuration();
if (!duration){
if (live){continue;}// skip last fragment when live
duration = Trk.lastms - starttime;
}
if (first){
r << " <S t=\"" << starttime << "\" d=\"" << duration << "\" />" << std::endl;
first = false;
}else{
r << " <S d=\"" << duration << "\" />" << std::endl;
}
}
}
/// Returns a string with the full XML DASH manifest MPD file.
std::string OutDashMP4::buildManifest(){
initialize();
selectDefaultTracks();
uint64_t lastVidTime = 0;
uint64_t vidInitTrack = 0;
uint64_t lastAudTime = 0;
uint64_t audInitTrack = 0;
uint64_t subInitTrack = 0;
/// \TODO DASH pretends there is only one audio/video track, and then prints them all using the same timing information. This is obviously wrong if the tracks are not in sync.
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (myMeta.tracks[*it].type == "video" && myMeta.tracks[*it].lastms > lastVidTime){
lastVidTime = myMeta.tracks[*it].lastms;
vidInitTrack = *it;
}
if (myMeta.tracks[*it].type == "audio" && myMeta.tracks[*it].lastms > lastAudTime){
lastAudTime = myMeta.tracks[*it].lastms;
audInitTrack = *it;
}
if (myMeta.tracks[*it].codec == "subtitle"){subInitTrack = *it;}
}
std::stringstream r;
r << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
r << "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 "
"http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/"
"DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" ";
if (myMeta.vod){
r << "type=\"static\" mediaPresentationDuration=\""
<< makeTime(myMeta.tracks[getMainSelectedTrack()].lastms -
myMeta.tracks[getMainSelectedTrack()].firstms)
<< "\" minBufferTime=\"PT1.5S\" >" << std::endl;
}else{
r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\""
<< Util::getUTCString(Util::epoch() - myMeta.tracks[getMainSelectedTrack()].lastms / 1000) << "\" "
<< "timeShiftBufferDepth=\""
<< makeTime(myMeta.tracks[getMainSelectedTrack()].lastms -
myMeta.tracks[getMainSelectedTrack()].firstms)
<< "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\""
<< Util::getUTCString(Util::epoch()) << "\" >" << std::endl;
}
r << " <ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl;
r << " <Period ";
if (myMeta.live){r << "start=\"0\" ";}
r << ">" << std::endl;
if (vidInitTrack){
DTSC::Track &trackRef = myMeta.tracks[vidInitTrack];
r << " <AdaptationSet group=\"1\" id=\"9998\" mimeType=\"video/mp4\" width=\""
<< trackRef.width << "\" height=\"" << trackRef.height << "\" frameRate=\""
<< trackRef.fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">"
<< std::endl;
r << " <SegmentTemplate timescale=\"1000\" "
"media=\"chunk_$RepresentationID$_$Time$.m4s\" "
"initialization=\"chunk_$RepresentationID$_init.m4s\">"
<< std::endl;
r << " <SegmentTimeline>" << std::endl;
addSegmentTimeline(r, trackRef, myMeta.live);
r << " </SegmentTimeline>" << std::endl;
r << " </SegmentTemplate>" << std::endl;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (myMeta.tracks[*it].codec == "H264"){
r << " <Representation id=\"" << *it << "\" ";
r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" ";
// bandwidth is in bits per seconds, we have bytes, so times 8
r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\" ";
r << "/>" << std::endl;
}
if (myMeta.tracks[*it].codec == "HEVC"){
r << " <Representation ";
r << "id=\"" << *it << "\" ";
r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" ";
// bandwidth is in bits per seconds, we have bytes, so times 8
r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\" ";
r << "/>" << std::endl;
}
}
r << " </AdaptationSet>" << std::endl;
}
if (audInitTrack){
DTSC::Track &trackRef = myMeta.tracks[audInitTrack];
r << " <AdaptationSet group=\"2\" id=\"9999\" mimeType=\"audio/mp4\" "
"segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" "
"subsegmentStartsWithSAP=\"1\" >"
<< std::endl;
r << " <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>" << std::endl;
r << " <SegmentTemplate timescale=\"1000\" "
"media=\"chunk_$RepresentationID$_$Time$.m4s\" "
"initialization=\"chunk_$RepresentationID$_init.m4s\">"
<< std::endl;
r << " <SegmentTimeline>" << std::endl;
addSegmentTimeline(r, trackRef, myMeta.live);
r << " </SegmentTimeline>" << std::endl;
r << " </SegmentTemplate>" << std::endl;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (myMeta.tracks[*it].codec == "AAC" || myMeta.tracks[*it].codec == "MP3" ||
myMeta.tracks[*it].codec == "AC3"){
r << " <Representation id=\"" << *it << "\" ";
// (see RFC6381): sample description entry , ObjectTypeIndication [MP4RA, RFC], ObjectTypeIndication [MP4A ISO/IEC 14496-3:2009]
r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" ";
r << "audioSamplingRate=\"" << myMeta.tracks[*it].rate << "\" ";
// bandwidth is in bits per seconds, we have bytes, so times 8
r << "bandwidth=\"" << (myMeta.tracks[*it].bps * 8) << "\">" << std::endl;
r << " <AudioChannelConfiguration "
"schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\""
<< myMeta.tracks[*it].channels << "\" />" << std::endl;
r << " </Representation>" << std::endl;
}
}
r << " </AdaptationSet>" << std::endl;
}
if (subInitTrack){
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (myMeta.tracks[*it].codec == "subtitle"){
subInitTrack = *it;
std::string lang = (myMeta.tracks[*it].lang == "" ? "unknown" : myMeta.tracks[*it].lang);
r << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang << "\">";
r << " <Representation id=\"" << *it << "\" bandwidth=\"256\">";
r << " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>";
r << " </Representation></AdaptationSet>" << std::endl;
}
}
}
r << " </Period>" << std::endl;
r << "</MPD>" << std::endl;
return r.str();
}
void OutDashMP4::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "DASHMP4";
capa["friendly"] = "DASH (fMP4) over HTTP";
capa["desc"] = "Segmented streaming in DASH (fMP4) format over HTTP";
capa["url_rel"] = "/dash/$/index.mpd";
capa["url_prefix"] = "/dash/$/";
capa["socket"] = "http_dash_mp4";
capa["codecs"][0u][0u].append("+H264");
capa["codecs"][0u][1u].append("+HEVC");
capa["codecs"][0u][2u].append("+AAC");
capa["codecs"][0u][3u].append("+AC3");
capa["codecs"][0u][4u].append("+MP3");
capa["codecs"][0u][5u].append("+subtitle");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "dash/video/mp4";
// MP3 does not work in browsers
capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]");
// HEVC does not work in browsers
capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]");
capa["methods"][0u]["priority"] = 8;
cfg->addOption("nonchunked",
JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not "
"send chunked, but buffer whole segments.\"}"));
capa["optional"]["nonchunked"]["name"] = "Send whole segments";
capa["optional"]["nonchunked"]["help"] =
"Disables chunked transfer encoding, forcing per-segment buffering. Reduces performance "
"significantly, but increases compatibility somewhat.";
capa["optional"]["nonchunked"]["option"] = "--nonchunked";
}
void OutDashMP4::onHTTP(){
std::string method = H.method;
initialize();
if (myMeta.live){updateMeta();}
std::string url = H.url;
// Send a manifest for any URL with .mpd in the path
if (url.find(".mpd") != std::string::npos){
H.Clean();
H.SetHeader("Content-Type", "application/dash+xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
H.SetBody(buildManifest());
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
// Not a manifest - either an init segment or data segment
size_t pos = url.find("chunk_") + 6; // find the track ID position
uint32_t tid = atoi(url.substr(pos).c_str());
if (!myMeta.tracks.count(tid)){
H.Clean();
H.SendResponse("404", "Track not found", myConn);
H.Clean();
return;
}
H.Clean();
H.SetHeader("Content-Type", "video/mp4");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
H.StartResponse(H, myConn, config->getBool("nonchunked"));
if (url.find("init.m4s") != std::string::npos){
// init segment
if (myMeta.tracks[tid].type == "video"){
H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomavc1mp42dash", 32, myConn);
}else{
H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomM4A mp42dash", 32, myConn);
}
sendMoov(tid);
H.Chunkify("", 0, myConn);
H.Clean();
return;
}
// data segment
pos = url.find("_", pos + 1) + 1;
uint64_t timeStamp = atoll(url.substr(pos).c_str());
uint32_t fragIndice = myMeta.tracks[tid].timeToFragnum(timeStamp);
uint32_t fragNum = myMeta.tracks[tid].fragments[fragIndice].getNumber();
HIGH_MSG("Getting T%llu for track %lu, indice %lu, number %lu", timeStamp, tid, fragIndice, fragNum);
if (myMeta.live && !myMeta.tracks[tid].fragments[fragIndice].getDuration()){
size_t ctr = 0;
do{
if (ctr){Util::sleep(250);}
updateMeta();
stats();
}while (!myMeta.tracks[tid].fragments[fragIndice].getDuration() && ++ctr < 120);
if (!myMeta.tracks[tid].fragments[fragIndice].getDuration()){
WARN_MSG("Sending zero-length segment. This should never happen.");
H.SendResponse("404", "Segment download error", myConn);
H.Clean();
return;
}
}
DTSC::Track &Trk = myMeta.tracks[tid];
H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn);
MP4::SIDX sidxBox;
sidxBox.setReferenceID(1);
sidxBox.setTimescale(1000);
sidxBox.setEarliestPresentationTime(Trk.getKey(fragNum).getTime());
sidxBox.setFirstOffset(0);
MP4::sidxReference refItem;
refItem.referenceType = false;
if (Trk.fragments[fragIndice].getDuration()){
refItem.subSegmentDuration = Trk.fragments[fragIndice].getDuration();
}else{
refItem.subSegmentDuration = Trk.lastms - Trk.getKey(fragNum).getTime();
}
refItem.sapStart = false;
refItem.sapType = 0;
refItem.sapDeltaTime = 0;
sidxBox.setReference(refItem, 0);
H.Chunkify(sidxBox.asBox(), sidxBox.boxedSize(), myConn);
sendMoof(tid, fragIndice);
sendMdat(tid, fragIndice);
}
}// namespace Mist

View file

@ -1,30 +0,0 @@
#include "output_http.h"
#include <mist/http_parser.h>
#include <mist/mp4_generic.h>
namespace Mist{
class OutDashMP4 : public HTTPOutput{
public:
OutDashMP4(Socket::Connection &conn);
~OutDashMP4();
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
void sendHeader(){};
protected:
void addSegmentTimeline(std::stringstream &r, DTSC::Track &Trk, bool live);
std::string makeTime(uint64_t time);
std::string buildManifest();
void sendMoov(uint32_t trackid);
void sendMoof(uint32_t trackid, uint32_t fragIndice);
void sendMdat(uint32_t trackid, uint32_t fragIndice);
std::string buildNalUnit(unsigned int len, const char *data);
uint64_t targetTime;
std::string h264init(const std::string &initData);
std::string h265init(const std::string &initData);
};
}// namespace Mist
typedef Mist::OutDashMP4 mistOut;

View file

@ -37,7 +37,6 @@ namespace Mist{
void OutDTSC::sendCmd(const JSON::Value &data){
MEDIUM_MSG("Sending DTCM: %s", data.toString().c_str());
unsigned long sendSize = data.packedSize();
myConn.SendNow("DTCM");
char sSize[4] ={0, 0, 0, 0};
Bit::htobl(sSize, data.packedSize());
@ -64,61 +63,58 @@ namespace Mist{
config = cfg;
}
std::string OutDTSC::getStatsName(){
if (pushing){
return "INPUT";
}else{
return "OUTPUT";
}
}
std::string OutDTSC::getStatsName(){return (pushing ? "INPUT" : "OUTPUT");}
/// Seeks to the first sync'ed keyframe of the main track.
/// Aborts if there is no main track or it has no keyframes.
void OutDTSC::initialSeek(){
unsigned long long seekPos = 0;
if (myMeta.live){
long unsigned int mainTrack = getMainSelectedTrack();
uint64_t seekPos = 0;
if (M.getLive()){
size_t mainTrack = getMainSelectedTrack();
// cancel if there are no keys in the main track
if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){return;}
if (mainTrack == INVALID_TRACK_ID){return;}
DTSC::Keys keys(M.keys(mainTrack));
if (!keys.getValidCount()){return;}
// seek to the oldest keyframe
for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[mainTrack].keys.begin();
it != myMeta.tracks[mainTrack].keys.end(); ++it){
seekPos = it->getTime();
std::set<size_t> validTracks = M.getValidTracks();
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
seekPos = keys.getTime(i);
bool good = true;
// check if all tracks have data for this point in time
for (std::set<unsigned long>::iterator ti = selectedTracks.begin(); ti != selectedTracks.end(); ++ti){
if (mainTrack == *ti){continue;}// skip self
if (!myMeta.tracks.count(*ti)){
HIGH_MSG("Skipping track %lu, not in tracks", *ti);
for (std::map<size_t, Comms::Users>::iterator ti = userSelect.begin(); ti != userSelect.end(); ++ti){
if (mainTrack == ti->first){continue;}// skip self
if (!validTracks.count(ti->first)){
HIGH_MSG("Skipping track %zu, not in tracks", ti->first);
continue;
}// ignore missing tracks
if (myMeta.tracks[*ti].lastms == myMeta.tracks[*ti].firstms){
HIGH_MSG("Skipping track %lu, last equals first", *ti);
if (M.getLastms(ti->first) == M.getFirstms(ti->first)){
HIGH_MSG("Skipping track %zu, last equals first", ti->first);
continue;
}// ignore point-tracks
if (myMeta.tracks[*ti].firstms > seekPos){
if (M.getFirstms(ti->first) > seekPos){
good = false;
break;
}
HIGH_MSG("Track %lu is good", *ti);
HIGH_MSG("Track %zu is good", ti->first);
}
// if yes, seek here
if (good){break;}
}
}
MEDIUM_MSG("Initial seek to %llums", seekPos);
MEDIUM_MSG("Initial seek to %" PRIu64 "ms", seekPos);
seek(seekPos);
}
void OutDTSC::sendNext(){
// If there are now more selectable tracks, select the new track and do a seek to the current
// timestamp Set sentHeader to false to force it to send init data
if (selectedTracks.size() < 2){
static unsigned long long lastMeta = 0;
if (userSelect.size() < 2){
static uint64_t lastMeta = 0;
if (Util::epoch() > lastMeta + 5){
lastMeta = Util::epoch();
updateMeta();
if (myMeta.tracks.size() > 1){
std::set<size_t> validTracks = getSupportedTracks();
if (validTracks.size() > 1){
if (selectDefaultTracks()){
INFO_MSG("Track selection changed - resending headers and continuing");
sentHeader = false;
@ -127,21 +123,24 @@ namespace Mist{
}
}
}
myConn.SendNow(thisPacket.getData(), thisPacket.getDataLen());
DTSC::Packet p(thisPacket, thisIdx + 1);
myConn.SendNow(p.getData(), p.getDataLen());
lastActive = Util::epoch();
}
void OutDTSC::sendHeader(){
sentHeader = true;
selectedTracks.clear();
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.type == "video" || it->second.type == "audio"){
selectedTracks.insert(it->first);
userSelect.clear();
std::set<size_t> validTracks = M.getValidTracks();
std::set<size_t> selectedTracks;
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == "video" || M.getType(*it) == "audio"){
userSelect[*it].reload(streamName, *it);
selectedTracks.insert(*it);
}
}
myMeta.send(myConn, true, selectedTracks);
if (myMeta.live){realTime = 0;}
M.send(myConn, true, selectedTracks, true);
if (M.getLive()){realTime = 0;}
}
void OutDTSC::onFail(const std::string &msg, bool critical){
@ -184,7 +183,7 @@ namespace Mist{
continue;
}
if (dScan.getMember("cmd").asString() == "reset"){
myMeta.reset();
meta.reInit(streamName);
sendOk("Internal state reset");
continue;
}
@ -200,9 +199,9 @@ namespace Mist{
if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
std::string dataPacket = myConn.Received().remove(8 + rSize);
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
myMeta.reinit(metaPack);
meta.reInit(streamName, metaPack.getScan());
std::stringstream rep;
rep << "DTSC_HEAD received with " << myMeta.tracks.size() << " tracks. Bring on those data packets!";
rep << "DTSC_HEAD received with " << M.getValidTracks().size() << " tracks. Bring on those data packets!";
sendOk(rep.str());
}else if (myConn.Received().copy(4) == "DTP2"){
if (!isPushing()){
@ -215,7 +214,7 @@ namespace Mist{
if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
std::string dataPacket = myConn.Received().remove(8 + rSize);
DTSC::Packet inPack(dataPacket.data(), dataPacket.size(), true);
if (!myMeta.tracks.count(inPack.getTrackId())){
if (M.trackIDToIndex(inPack.getTrackId(), getpid()) == INVALID_TRACK_ID){
onFail("DTSC_V2 received for a track that was not announced in the DTSC_HEAD!", true);
return;
}

View file

@ -22,7 +22,6 @@ namespace Mist{
std::string salt;
void handlePush(DTSC::Scan &dScan);
void handlePlay(DTSC::Scan &dScan);
unsigned long long fastAsPossibleTime;
};
}// namespace Mist

View file

@ -16,7 +16,7 @@ namespace Mist{
if (config->getString("target").size()){
if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";}
initialize();
if (myMeta.vod){calcVodSizes();}
if (M.getVod()){calcVodSizes();}
if (!streamName.size()){
WARN_MSG("Recording unconnected EBML output to file! Cancelled.");
conn.close();
@ -28,7 +28,7 @@ namespace Mist{
INFO_MSG("Outputting %s to stdout in EBML format", streamName.c_str());
return;
}
if (!myMeta.tracks.size()){
if (!M.getValidTracks().size()){
INFO_MSG("Stream not available - aborting");
conn.close();
return;
@ -106,28 +106,31 @@ namespace Mist{
bool OutEBML::isRecording(){return config->getString("target").size();}
/// 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;
/// Bases the calculation on the currently selected tracks and the given start/end time for the
/// cluster.
size_t OutEBML::clusterSize(uint64_t start, uint64_t end){
size_t sendLen = EBML::sizeElemUInt(EBML::EID_TIMECODE, start);
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
DTSC::Keys keys(M.keys(it->first));
DTSC::Parts parts(M.parts(it->first));
uint32_t firstPart = parts.getFirstValid();
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;}
for (size_t i = keys.getFirstValid(); i < keys.getEndValid(); ++i){
if (keys.getTime(i) > start && i != keys.getFirstValid()){break;}
firstPart += prevParts;
prevParts = it2->getParts();
curMS = it2->getTime();
prevParts = keys.getParts(i);
curMS = keys.getTime(i);
}
size_t maxParts = thisTrack.parts.size();
for (size_t i = firstPart; i < maxParts; i++){
for (size_t i = firstPart; i < parts.getEndValid(); ++i){
if (curMS >= end){break;}
if (curMS >= start){
uint32_t blkLen = EBML::sizeSimpleBlock(thisTrack.trackID, thisTrack.parts[i].getSize());
uint32_t blkLen = EBML::sizeSimpleBlock(it->first + 1, parts.getSize(i));
sendLen += blkLen;
}
curMS += thisTrack.parts[i].getDuration();
curMS += parts.getDuration(i);
}
}
return sendLen;
@ -137,19 +140,21 @@ namespace Mist{
if (thisPacket.getTime() >= newClusterTime){
if (liveSeek()){return;}
currentClusterTime = thisPacket.getTime();
if (myMeta.vod){
if (M.getVod()){
// In case of VoD, clusters are aligned with the main track fragments
// EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime);
newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() +
Trk.fragments[fragIndice].getDuration();
// EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32
// seconds.
size_t idx = getMainSelectedTrack();
DTSC::Fragments fragments(M.fragments(idx));
uint32_t fragIndice = M.getFragmentIndexForTime(idx, currentClusterTime);
newClusterTime = M.getTimeForFragmentIndex(idx, fragIndice) + fragments.getDuration(fragIndice);
// Limit clusters to 30s, and the last fragment should always be 30s, just in case.
if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){
if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == fragments.getEndValid() - 1)){
newClusterTime = currentClusterTime + 30000;
}
EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime,
fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime));
EXTREME_MSG("Cluster: %" PRIu64 " - %" PRIu64 " (%" PRIu32 "/%zu) = %zu",
currentClusterTime, newClusterTime, fragIndice, fragments.getEndValid(),
clusterSize(currentClusterTime, newClusterTime));
}else{
// In live, clusters are aligned with the lookAhead time
newClusterTime = currentClusterTime + (needsLookAhead ? needsLookAhead : 1);
@ -162,152 +167,167 @@ namespace Mist{
EBML::sendElemUInt(myConn, EBML::EID_TIMECODE, currentClusterTime);
}
EBML::sendSimpleBlock(myConn, thisPacket, currentClusterTime,
myMeta.tracks[thisPacket.getTrackId()].type != "video");
DTSC::Packet p(thisPacket, thisIdx + 1);
EBML::sendSimpleBlock(myConn, p, currentClusterTime, M.getType(thisIdx) != "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 == "AV1"){return "V_AV1";}
if (Trk.codec == "AAC"){return "A_AAC";}
if (Trk.codec == "vorbis"){return "A_VORBIS";}
if (Trk.codec == "theora"){return "V_THEORA";}
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";}
if (Trk.codec == "DTS"){return "A_DTS";}
if (Trk.codec == "JSON"){return "M_JSON";}
std::string OutEBML::trackCodecID(size_t idx){
std::string codec = M.getCodec(idx);
if (codec == "opus"){return "A_OPUS";}
if (codec == "H264"){return "V_MPEG4/ISO/AVC";}
if (codec == "HEVC"){return "V_MPEGH/ISO/HEVC";}
if (codec == "VP8"){return "V_VP8";}
if (codec == "VP9"){return "V_VP9";}
if (codec == "AV1"){return "V_AV1";}
if (codec == "AAC"){return "A_AAC";}
if (codec == "vorbis"){return "A_VORBIS";}
if (codec == "theora"){return "V_THEORA";}
if (codec == "MPEG2"){return "V_MPEG2";}
if (codec == "PCM"){return "A_PCM/INT/BIG";}
if (codec == "MP2"){return "A_MPEG/L2";}
if (codec == "MP3"){return "A_MPEG/L3";}
if (codec == "AC3"){return "A_AC3";}
if (codec == "ALAW"){return "A_MS/ACM";}
if (codec == "ULAW"){return "A_MS/ACM";}
if (codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";}
if (codec == "DTS"){return "A_DTS";}
if (codec == "JSON"){return "M_JSON";}
return "E_UNKNOWN";
}
void OutEBML::sendElemTrackEntry(const DTSC::Track &Trk){
void OutEBML::sendElemTrackEntry(size_t idx){
// 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");
size_t sendLen = 0;
size_t subLen = 0;
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, idx + 1);
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, idx + 1);
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(idx));
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "und");
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
std::string codec = M.getCodec(idx);
if (codec == "ALAW" || 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 (M.getInit(idx).size()){
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, M.getInit(idx));
}
}
if (Trk.codec == "opus" && Trk.init.size() > 11){
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48);
if (codec == "opus" && M.getInit(idx).size() > 11){
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48);
sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
}
if (Trk.type == "video"){
std::string type = M.getType(idx);
if (type == "video"){
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, M.getWidth(idx));
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, M.getHeight(idx));
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, M.getWidth(idx));
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, M.getHeight(idx));
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
}
if (Trk.type == "audio"){
if (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);
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, M.getChannels(idx));
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx));
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, M.getSize(idx));
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
}
if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);}
if (type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);}
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_TRACKNUMBER, idx + 1);
EBML::sendElemUInt(myConn, EBML::EID_TRACKUID, idx + 1);
EBML::sendElemStr(myConn, EBML::EID_CODECID, trackCodecID(idx));
EBML::sendElemStr(myConn, EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "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);
if (codec == "ALAW" || codec == "ULAW"){
std::string init =
RIFF::fmt::generate(((codec == "ALAW") ? 6 : 7), M.getChannels(idx), M.getRate(idx),
M.getBps(idx), M.getChannels(idx) * (M.getSize(idx) << 3), M.getSize(idx));
EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, init.substr(8));
}else{
if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);}
if (M.getInit(idx).size()){
EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, M.getInit(idx));
}
}
if (Trk.codec == "opus"){
EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48);
if (codec == "opus" && M.getInit(idx).size() > 11){
EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48);
EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000);
}
if (Trk.type == "video"){
if (type == "video"){
EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1);
EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen);
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width);
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height);
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width);
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height);
EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, M.getWidth(idx));
EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, M.getHeight(idx));
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, M.getWidth(idx));
EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, M.getHeight(idx));
}
if (Trk.type == "audio"){
if (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);
EBML::sendElemUInt(myConn, EBML::EID_CHANNELS, M.getChannels(idx));
EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx));
EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, M.getSize(idx));
}
if (Trk.type == "meta"){EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);}
if (type == "meta"){EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3);}
}
uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){
size_t OutEBML::sizeElemTrackEntry(size_t idx){
// 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");
size_t sendLen = 0;
size_t subLen = 0;
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKNUMBER, idx + 1);
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKUID, idx + 1);
sendLen += EBML::sizeElemStr(EBML::EID_CODECID, trackCodecID(idx));
sendLen += EBML::sizeElemStr(EBML::EID_LANGUAGE, M.getLang(idx).size() ? M.getLang(idx) : "und");
sendLen += EBML::sizeElemUInt(EBML::EID_FLAGLACING, 0);
if (Trk.codec == "ALAW" || Trk.codec == "ULAW"){
std::string codec = M.getCodec(idx);
if (codec == "ALAW" || 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 (M.getInit(idx).size()){
sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, M.getInit(idx));
}
}
if (Trk.codec == "opus"){
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data()) * 1000000 / 48);
std::string type = M.getType(idx);
if (codec == "opus" && M.getInit(idx).size() > 11){
sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(M.getInit(idx).data()) * 1000000 / 48);
sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000);
}
if (Trk.type == "video"){
if (type == "video"){
sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height);
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width);
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height);
subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, M.getWidth(idx));
subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, M.getHeight(idx));
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, M.getWidth(idx));
subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, M.getHeight(idx));
sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen);
}
if (Trk.type == "audio"){
if (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);
subLen += EBML::sizeElemUInt(EBML::EID_CHANNELS, M.getChannels(idx));
subLen += EBML::sizeElemDbl(EBML::EID_SAMPLINGFREQUENCY, M.getRate(idx));
subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, M.getSize(idx));
sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen);
}
if (Trk.type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);}
if (type == "meta"){sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3);}
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;}
size_t idx = getMainSelectedTrack();
if (M.getVod()){
duration = M.getLastms(idx) - M.getFirstms(idx);
}else{
needsLookAhead = 420;
}
// EBML header and Segment
EBML::sendElemEBML(myConn, doctype);
EBML::sendElemHead(myConn, EBML::EID_SEGMENT, segmentSize); // Default = Unknown size
if (myMeta.vod){
if (M.getVod()){
// SeekHead
EBML::sendElemHead(myConn, EBML::EID_SEEKHEAD, seekSize);
EBML::sendElemSeek(myConn, EBML::EID_INFO, seekheadSize);
@ -317,38 +337,39 @@ namespace Mist{
// 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]);
size_t trackSizes = 0;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
trackSizes += sizeElemTrackEntry(it->first);
}
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]);
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
sendElemTrackEntry(it->first);
}
if (myMeta.vod){
if (M.getVod()){
EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize);
uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize +
EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0);
EBML::sendElemCuePoint(myConn, it->first, idx + 1, tmpsegSize, 0);
tmpsegSize += it->second;
}
}
sentHeader = true;
}
/// Seeks to the given byte position by doing a regular seek and remembering the byte offset from that point
/// 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);
INFO_MSG("Seeking to %" PRIu64 " 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;
size_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);
@ -357,11 +378,10 @@ namespace Mist{
}
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);
VERYHIGH_MSG("Cluster %" PRIu64 " (%" PRIu64 " bytes) -> %" PRIu64 " to go", it->first, it->second, startPos);
if (startPos < it->second){
HIGH_MSG("Seek to fragment at %llu ms", it->first);
HIGH_MSG("Seek to fragment at %" PRIu64 " ms", it->first);
myConn.skipBytes(startPos);
seek(it->first);
newClusterTime = it->first;
@ -389,15 +409,15 @@ namespace Mist{
}
// Calculate the sizes of various parts, if we're VoD.
uint64_t totalSize = 0;
if (myMeta.vod){
size_t totalSize = 0;
if (M.getVod()){
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;
size_t byteEnd = totalSize - 1;
size_t byteStart = 0;
/*LTS-START*/
// allow setting of max lead time through buffer variable.
@ -424,12 +444,12 @@ namespace Mist{
/*LTS-END*/
char rangeType = ' ';
if (!myMeta.live){
if (M.getVod()){
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;
size_t idx = getMainSelectedTrack();
maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500;
}
}
rangeType = H.GetHeader("Range")[0];
@ -438,33 +458,29 @@ namespace Mist{
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 (M.getVod()){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);
H.SetBody("Requested Range Not Satisfiable");
H.SendResponse("416", "Requested Range Not Satisfiable", myConn);
return;
}
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);
byteSeek(byteStart);
}else{
if (myMeta.vod){H.SetHeader("Content-Length", byteEnd - byteStart + 1);}
if (M.getVod()){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;
@ -475,8 +491,8 @@ namespace Mist{
// Already calculated
return;
}
DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()];
double duration = Trk.lastms - Trk.firstms;
size_t idx = getMainSelectedTrack();
double duration = M.getLastms(idx) - M.getFirstms(idx);
// 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.
@ -484,8 +500,8 @@ namespace Mist{
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]);
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
tracksSize += sizeElemTrackEntry(it->first);
}
tracksSize += EBML::sizeElemHead(EBML::EID_TRACKS, tracksSize);
// Calculating SeekHead size
@ -504,16 +520,17 @@ namespace Mist{
// 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();
DTSC::Fragments fragments(M.fragments(idx));
for (size_t i = fragments.getFirstValid(); i < fragments.getEndValid(); i++){
uint64_t clusterStart = M.getTimeForFragmentIndex(idx, i);
uint64_t clusterEnd = clusterStart + fragments.getDuration(i);
// The first fragment always starts at time 0, even if the main track does not.
if (!fragNo){clusterStart = 0;}
uint64_t clusterTmpEnd = clusterEnd;
do{
clusterTmpEnd = clusterEnd;
// The last fragment always ends at the end, even if the main track does not.
if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;}
if (fragNo == fragments.getEndValid() - 1){clusterTmpEnd = clusterStart + 30000;}
// Limit clusters to 30 seconds.
if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;}
uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd);
@ -534,7 +551,7 @@ namespace Mist{
EBML::sizeElemHead(EBML::EID_CUES, cuesSize);
uint32_t cuesInside = 0;
for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){
cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0);
cuesInside += EBML::sizeElemCuePoint(it->first, idx + 1, segmentSize, 0);
segmentSize += it->second;
}
cuesSize = cuesInside;

View file

@ -8,8 +8,8 @@ namespace Mist{
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
virtual void sendHeader();
uint32_t clusterSize(uint64_t start, uint64_t end);
void sendHeader();
size_t clusterSize(uint64_t start, uint64_t end);
protected:
virtual bool inlineRestartCapable() const{return true;}
@ -17,21 +17,21 @@ namespace Mist{
private:
bool isRecording();
std::string doctype;
void sendElemTrackEntry(const DTSC::Track &Trk);
uint32_t sizeElemTrackEntry(const DTSC::Track &Trk);
std::string trackCodecID(const DTSC::Track &Trk);
void sendElemTrackEntry(size_t idx);
size_t sizeElemTrackEntry(size_t idx);
std::string trackCodecID(size_t idx);
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 by start time (incl. header)
void byteSeek(uint64_t startPos);
size_t segmentSize; // size of complete segment contents (excl. header)
size_t tracksSize; // size of Tracks (incl. header)
size_t infoSize; // size of Info (incl. header)
size_t cuesSize; // size of Cues (excl. header)
size_t seekheadSize; // size of SeekHead (incl. header)
size_t seekSize; // size of contents of SeekHead (excl. header)
std::map<size_t, size_t> clusterSizes; // sizes of Clusters (incl. header)
void byteSeek(size_t startPos);
};
}// namespace Mist

View file

@ -1,9 +1,10 @@
#include "output_progressive_flv.h"
#include "output_flv.h"
#include <mist/h264.h>
namespace Mist{
OutProgressiveFLV::OutProgressiveFLV(Socket::Connection &conn) : HTTPOutput(conn){}
OutFLV::OutFLV(Socket::Connection &conn) : HTTPOutput(conn){}
void OutProgressiveFLV::init(Util::Config *cfg){
void OutFLV::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "FLV";
capa["friendly"] = "Flash progressive over HTTP (FLV)";
@ -45,24 +46,25 @@ namespace Mist{
cfg->addOption("keyframeonly", opt);
}
bool OutProgressiveFLV::isRecording(){return config->getString("target").size();}
bool OutFLV::isRecording(){return config->getString("target").size();}
void OutProgressiveFLV::sendNext(){
// If there are now more selectable tracks, select the new track and do a seek to the current timestamp
if (myMeta.live && selectedTracks.size() < 2){
static unsigned long long lastMeta = 0;
void OutFLV::sendNext(){
// If there are now more selectable tracks, select the new track and do a seek to the current
// timestamp
if (M.getLive() && userSelect.size() < 2){
static uint64_t lastMeta = 0;
if (Util::epoch() > lastMeta + 5){
lastMeta = Util::epoch();
updateMeta();
if (myMeta.tracks.size() > 1){
std::set<size_t> validTracks = getSupportedTracks();
if (validTracks.size() > 1){
if (selectDefaultTracks()){
INFO_MSG("Track selection changed - resending headers and continuing");
for (std::set<long unsigned int>::iterator it = selectedTracks.begin();
it != selectedTracks.end(); it++){
if (myMeta.tracks[*it].type == "video" && tag.DTSCVideoInit(myMeta.tracks[*it])){
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin();
it != userSelect.end(); it++){
if (M.getType(it->first) == "video" && tag.DTSCVideoInit(meta, it->first)){
myConn.SendNow(tag.data, tag.len);
}
if (myMeta.tracks[*it].type == "audio" && tag.DTSCAudioInit(myMeta.tracks[*it])){
if (M.getType(it->first) == "audio" && tag.DTSCAudioInit(meta, it->first)){
myConn.SendNow(tag.data, tag.len);
}
}
@ -71,10 +73,8 @@ namespace Mist{
}
}
}
DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()];
tag.DTSCLoader(thisPacket, trk);
if (trk.codec == "PCM" && trk.size == 16){
tag.DTSCLoader(thisPacket, M, thisIdx);
if (M.getCodec(thisIdx) == "PCM" && M.getSize(thisIdx) == 16){
char *ptr = tag.getData();
uint32_t ptrSize = tag.getDataLen();
for (uint32_t i = 0; i < ptrSize; i += 2){
@ -87,7 +87,7 @@ namespace Mist{
if (config->getBool("keyframeonly")){config->is_active = false;}
}
void OutProgressiveFLV::sendHeader(){
void OutFLV::sendHeader(){
if (!isRecording()){
H.Clean();
H.SetHeader("Content-Type", "video/x-flv");
@ -96,38 +96,43 @@ namespace Mist{
H.SendResponse("200", "OK", myConn);
}
if (config->getBool("keyframeonly")){
selectedTracks.clear();
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.type == "video"){
selectedTracks.insert(it->first);
userSelect.clear();
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == "video"){
userSelect[*it].reload(streamName, *it);
break;
}
}
}
myConn.SendNow(FLV::Header, 13);
tag.DTSCMetaInit(myMeta, selectedTracks);
std::set<size_t> selectedTracks;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
selectedTracks.insert(it->first);
}
tag.DTSCMetaInit(M, selectedTracks);
myConn.SendNow(tag.data, tag.len);
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (myMeta.tracks[*it].type == "video" && tag.DTSCVideoInit(myMeta.tracks[*it])){
for (std::set<size_t>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (M.getType(*it) == "video" && tag.DTSCVideoInit(meta, *it)){
myConn.SendNow(tag.data, tag.len);
}
if (myMeta.tracks[*it].type == "audio" && tag.DTSCAudioInit(myMeta.tracks[*it])){
if (M.getType(*it) == "audio" && tag.DTSCAudioInit(meta, *it)){
myConn.SendNow(tag.data, tag.len);
}
}
if (config->getBool("keyframeonly")){
unsigned int tid = *selectedTracks.begin();
int keyNum = myMeta.tracks[tid].keys.rbegin()->getNumber();
int keyTime = myMeta.tracks[tid].getKey(keyNum).getTime();
INFO_MSG("Seeking for time %d on track %d key %d", keyTime, tid, keyNum);
size_t tid = userSelect.begin()->first;
DTSC::Keys keys(M.keys(tid));
uint32_t endKey = keys.getEndValid();
uint64_t keyTime = keys.getTime(endKey - 1);
INFO_MSG("Seeking for time %" PRIu64 " on track %zu key %" PRIu32, keyTime, tid, endKey - 1);
seek(keyTime);
}
sentHeader = true;
}
void OutProgressiveFLV::onHTTP(){
void OutFLV::onHTTP(){
std::string method = H.method;
H.Clean();

View file

@ -1,9 +1,9 @@
#include "output_http.h"
namespace Mist{
class OutProgressiveFLV : public HTTPOutput{
class OutFLV : public HTTPOutput{
public:
OutProgressiveFLV(Socket::Connection &conn);
OutFLV(Socket::Connection &conn);
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
@ -17,4 +17,4 @@ namespace Mist{
};
}// namespace Mist
typedef Mist::OutProgressiveFLV mistOut;
typedef Mist::OutFLV mistOut;

View file

@ -64,9 +64,9 @@ namespace Mist{
void OutH264::sendHeader(){
MP4::AVCC avccbox;
unsigned int mainTrack = getMainSelectedTrack();
if (mainTrack && myMeta.tracks.count(mainTrack)){
avccbox.setPayload(myMeta.tracks[mainTrack].init);
size_t mainTrack = getMainSelectedTrack();
if (mainTrack != INVALID_TRACK_ID){
avccbox.setPayload(M.getInit(mainTrack));
myConn.SendNow(avccbox.asAnnexB());
}
sentHeader = true;

View file

@ -7,23 +7,22 @@
namespace Mist{
void OutHDS::getTracks(){
/// \todo Why do we have only one audio track option?
videoTracks.clear();
audioTrack = 0;
audioTrack = INVALID_TRACK_ID;
JSON::Value &vidCapa = capa["codecs"][0u][0u];
JSON::Value &audCapa = capa["codecs"][0u][1u];
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
jsonForEach(vidCapa, itb){
if (it->second.codec == (*itb).asStringRef()){
videoTracks.insert(it->first);
if (M.getCodec(*it) == itb->asStringRef()){
videoTracks.insert(*it);
break;
}
}
if (!audioTrack){
if (audioTrack == INVALID_TRACK_ID){
jsonForEach(audCapa, itb){
if (it->second.codec == (*itb).asStringRef()){
audioTrack = it->first;
if (M.getCodec(*it) == itb->asStringRef()){
audioTrack = *it;
break;
}
}
@ -34,18 +33,19 @@ namespace Mist{
///\brief Builds a bootstrap for use in HTTP Dynamic streaming.
///\param tid The track this bootstrap is generated for.
///\return The generated bootstrap.
std::string OutHDS::dynamicBootstrap(int tid){
updateMeta();
std::string OutHDS::dynamicBootstrap(size_t idx){
DTSC::Fragments fragments(M.fragments(idx));
DTSC::Keys keys(M.keys(idx));
std::string empty;
MP4::ASRT asrt;
asrt.setUpdate(false);
asrt.setVersion(1);
// asrt.setQualityEntry(empty, 0);
if (myMeta.live){
if (M.getLive()){
asrt.setSegmentRun(1, 4294967295ul, 0);
}else{
asrt.setSegmentRun(1, myMeta.tracks[tid].fragments.size(), 0);
asrt.setSegmentRun(1, fragments.getValidCount(), 0);
}
MP4::AFRT afrt;
@ -54,26 +54,21 @@ namespace Mist{
afrt.setTimeScale(1000);
// afrt.setQualityEntry(empty, 0);
MP4::afrt_runtable afrtrun;
int i = 0;
int j = 0;
if (myMeta.tracks[tid].fragments.size()){
std::deque<DTSC::Fragment>::iterator fragIt = myMeta.tracks[tid].fragments.begin();
unsigned int firstTime = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime();
while (fragIt != myMeta.tracks[tid].fragments.end()){
if (myMeta.vod || fragIt->getDuration() > 0){
afrtrun.firstFragment = myMeta.tracks[tid].missedFrags + j + 1;
afrtrun.firstTimestamp = myMeta.tracks[tid].getKey(fragIt->getNumber()).getTime() - firstTime;
if (fragIt->getDuration() > 0){
afrtrun.duration = fragIt->getDuration();
}else{
afrtrun.duration = myMeta.tracks[tid].lastms - afrtrun.firstTimestamp;
}
afrt.setFragmentRun(afrtrun, i);
++i;
size_t i = 0;
size_t j = 0;
uint64_t firstTime = keys.getTime(fragments.getFirstKey(fragments.getFirstValid()));
for (size_t fragIdx = fragments.getFirstValid() + 1; fragIdx < fragments.getEndValid(); ++fragIdx){
if (M.getVod() || fragments.getDuration(fragIdx) > 0){
afrtrun.firstFragment = M.getMissedFragments(idx) + j + 1;
afrtrun.firstTimestamp = keys.getTime(fragments.getFirstKey(fragIdx)) - firstTime;
if (fragments.getDuration(fragIdx) > 0){
afrtrun.duration = fragments.getDuration(fragIdx);
}else{
afrtrun.duration = M.getLastms(idx) - afrtrun.firstTimestamp;
}
++j;
++fragIt;
afrt.setFragmentRun(afrtrun, i++);
}
++j;
}
MP4::ABST abst;
@ -82,15 +77,15 @@ namespace Mist{
abst.setProfile(0);
abst.setUpdate(false);
abst.setTimeScale(1000);
abst.setLive(myMeta.live);
abst.setCurrentMediaTime(myMeta.tracks[tid].lastms);
abst.setLive(M.getLive());
abst.setCurrentMediaTime(M.getLastms(idx));
abst.setSmpteTimeCodeOffset(0);
abst.setMovieIdentifier(streamName);
abst.setSegmentRunTable(asrt, 0);
abst.setFragmentRunTable(afrt, 0);
DEBUG_MSG(DLVL_VERYHIGH, "Sending bootstrap: %s", abst.toPrettyString(0).c_str());
return std::string((char *)abst.asBox(), (int)abst.boxedSize());
VERYHIGH_MSG("Sending bootstrap: %s", abst.toPrettyString(0).c_str());
return std::string(abst.asBox(), abst.boxedSize());
}
///\brief Builds an index file for HTTP Dynamic streaming.
@ -103,53 +98,53 @@ namespace Mist{
Result << " <id>" << streamName << "</id>" << std::endl;
Result << " <mimeType>video/mp4</mimeType>" << std::endl;
Result << " <deliveryType>streaming</deliveryType>" << std::endl;
if (myMeta.vod){
Result << " <duration>" << myMeta.tracks[*videoTracks.begin()].lastms / 1000
if (M.getVod()){
Result << " <duration>" << M.getLastms(videoTracks.size() ? *videoTracks.begin() : audioTrack) / 1000
<< ".000</duration>" << std::endl;
Result << " <streamType>recorded</streamType>" << std::endl;
}else{
Result << " <duration>0.00</duration>" << std::endl;
Result << " <streamType>live</streamType>" << std::endl;
}
for (std::set<int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){
for (std::set<size_t>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){
Result << " <bootstrapInfo "
"profile=\"named\" "
"id=\"boot"
<< (*it)
<< *it
<< "\" "
"url=\""
<< (*it)
<< *it
<< ".abst\">"
"</bootstrapInfo>"
<< std::endl;
Result << " <media "
"url=\""
<< (*it)
<< *it
<< "-\" "
// bitrate in kbit/s, we have bps so divide by 128
"bitrate=\""
<< (myMeta.tracks[(*it)].bps / 128)
<< M.getBps(*it) / 128
<< "\" "
"bootstrapInfoId=\"boot"
<< (*it)
<< *it
<< "\" "
"width=\""
<< myMeta.tracks[(*it)].width
<< M.getWidth(*it)
<< "\" "
"height=\""
<< myMeta.tracks[(*it)].height << "\">" << std::endl;
<< M.getHeight(*it) << "\">" << std::endl;
Result << " <metadata>AgAKb25NZXRhRGF0YQMAAAk=</metadata>" << std::endl;
Result << " </media>" << std::endl;
}
Result << "</manifest>" << std::endl;
DEBUG_MSG(DLVL_HIGH, "Sending manifest: %s", Result.str().c_str());
HIGH_MSG("Sending manifest: %s", Result.str().c_str());
return Result.str();
}// BuildManifest
OutHDS::OutHDS(Socket::Connection &conn) : HTTPOutput(conn){
uaDelay = 0;
realTime = 0;
audioTrack = 0;
audioTrack = INVALID_TRACK_ID;
playUntil = 0;
}
@ -186,15 +181,14 @@ namespace Mist{
void OutHDS::sendNext(){
if (thisPacket.getTime() >= playUntil){
VERYHIGH_MSG("Done sending fragment (%llu >= %llu)", thisPacket.getTime(), playUntil);
VERYHIGH_MSG("Done sending fragment (%" PRIu64 " >= %" PRIu64 ")", thisPacket.getTime(), playUntil);
stop();
wantRequest = true;
H.Chunkify("", 0, myConn);
return;
}
DTSC::Track &trk = myMeta.tracks[thisPacket.getTrackId()];
tag.DTSCLoader(thisPacket, trk);
if (trk.codec == "PCM" && trk.size == 16){
tag.DTSCLoader(thisPacket, M, thisIdx);
if (M.getCodec(thisIdx) == "PCM" && M.getSize(thisIdx) == 16){
char *ptr = tag.getData();
uint32_t ptrSize = tag.getDataLen();
for (uint32_t i = 0; i < ptrSize; i += 2){
@ -230,47 +224,45 @@ namespace Mist{
if (H.url.find("f4m") == std::string::npos){
initialize();
std::string tmp_qual = H.url.substr(H.url.find("/", 10) + 1);
unsigned int tid;
unsigned int fragNum;
tid = atoi(tmp_qual.substr(0, tmp_qual.find("Seg") - 1).c_str());
size_t idx = atoi(tmp_qual.substr(0, tmp_qual.find("Seg") - 1).c_str());
if (idx == INVALID_TRACK_ID){FAIL_MSG("Requested fragment for invalid track id");}
int temp;
temp = H.url.find("Seg") + 3;
temp = H.url.find("Frag") + 4;
fragNum = atoi(H.url.substr(temp).c_str()) - 1;
DEBUG_MSG(DLVL_MEDIUM, "Video track %d, fragment %d", tid, fragNum);
if (!audioTrack){getTracks();}
unsigned int mstime = 0;
unsigned int mslen = 0;
if (fragNum < (unsigned int)myMeta.tracks[tid].missedFrags){
size_t fragIdx = atoi(H.url.substr(temp).c_str()) - 1;
MEDIUM_MSG("Video track %zu, fragment %zu", idx, fragIdx);
if (audioTrack == INVALID_TRACK_ID){getTracks();}
uint64_t mstime = 0;
uint64_t mslen = 0;
if (fragIdx < M.getMissedFragments(idx)){
H.Clean();
H.setCORSHeaders();
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be "
"served.\n");
H.SendResponse("412", "Fragment out of range", myConn);
H.Clean(); // clean for any possible next requests
std::cout << "Fragment " << fragNum << " too old" << std::endl;
FAIL_MSG("Fragment %zu too old", fragIdx);
return;
}
// delay if we don't have the next fragment available yet
unsigned int timeout = 0;
while (myConn && fragNum >= myMeta.tracks[tid].missedFrags + myMeta.tracks[tid].fragments.size() - 1){
DTSC::Fragments fragments(M.fragments(idx));
DTSC::Keys keys(M.keys(idx));
while (myConn && fragIdx >= fragments.getEndValid() - 1){
// time out after 21 seconds
if (++timeout > 42){
myConn.close();
onFail("Timeout triggered", true);
break;
}
Util::wait(500);
updateMeta();
}
mstime = myMeta.tracks[tid]
.getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber())
.getTime();
mslen = myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getDuration();
VERYHIGH_MSG("Playing from %llu for %llu ms", mstime, mslen);
mstime = keys.getTime(fragments.getFirstKey(fragIdx));
mslen = fragments.getDuration(fragIdx);
VERYHIGH_MSG("Playing from %" PRIu64 " for %" PRIu64 " ms", mstime, mslen);
selectedTracks.clear();
selectedTracks.insert(tid);
if (audioTrack){selectedTracks.insert(audioTrack);}
userSelect.clear();
userSelect[idx].reload(streamName, idx);
if (audioTrack != INVALID_TRACK_ID){userSelect[audioTrack].reload(streamName, audioTrack);}
seek(mstime);
playUntil = mstime + mslen;
@ -284,19 +276,18 @@ namespace Mist{
}
H.StartResponse(H, myConn);
// send the bootstrap
std::string bootstrap = dynamicBootstrap(tid);
H.Chunkify(bootstrap, myConn);
H.Chunkify(dynamicBootstrap(idx), myConn);
// send a zero-size mdat, meaning it stretches until end of file.
H.Chunkify("\000\000\000\000mdat", 8, myConn);
// send init data, if needed.
if (audioTrack > 0 && myMeta.tracks[audioTrack].init != ""){
if (tag.DTSCAudioInit(myMeta.tracks[audioTrack])){
if (audioTrack != INVALID_TRACK_ID && M.getInit(audioTrack) != ""){
if (tag.DTSCAudioInit(meta, audioTrack)){
tag.tagTime(mstime);
H.Chunkify(tag.data, tag.len, myConn);
}
}
if (tid > 0){
if (tag.DTSCVideoInit(myMeta.tracks[tid])){
if (idx != INVALID_TRACK_ID){
if (tag.DTSCVideoInit(meta, idx)){
tag.tagTime(mstime);
H.Chunkify(tag.data, tag.len, myConn);
}
@ -305,8 +296,6 @@ namespace Mist{
wantRequest = false;
}else{
initialize();
std::stringstream tmpstr;
myMeta.toPrettyString(tmpstr);
H.Clean();
H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Cache-Control", "no-cache");

View file

@ -14,11 +14,11 @@ namespace Mist{
protected:
void getTracks();
std::string dynamicBootstrap(int tid);
std::string dynamicBootstrap(size_t idx);
std::string dynamicIndex();
std::set<int> videoTracks; ///<< Holds valid video tracks for playback
long long int audioTrack; ///<< Holds audio track ID for playback
long long unsigned int playUntil;
std::set<size_t> videoTracks; ///<< Holds valid video tracks for playback
size_t audioTrack; ///<< Holds audio track ID for playback
uint64_t playUntil;
FLV::Tag tag;
};
}// namespace Mist

View file

@ -6,10 +6,11 @@
namespace Mist{
bool OutHLS::isReadyForPlay(){
if (myMeta.tracks.size()){
if (myMeta.mainTrack().fragments.size() > 4){return true;}
}
return false;
if (!M.getValidTracks().size()){return false;}
uint32_t mainTrack = M.mainTrack();
if (mainTrack == INVALID_TRACK_ID){return false;}
DTSC::Fragments fragments(M.fragments(mainTrack));
return fragments.getValidCount() > 4;
}
///\brief Builds an index file for HTTP Live streaming.
@ -18,256 +19,139 @@ namespace Mist{
std::stringstream result;
selectDefaultTracks();
result << "#EXTM3U\r\n";
int audioId = -1;
unsigned int vidTracks = 0;
size_t audioId = INVALID_TRACK_ID;
size_t vidTracks = 0;
bool hasSubs = false;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (audioId == -1 && myMeta.tracks[*it].type == "audio"){audioId = *it;}
if (!hasSubs && myMeta.tracks[*it].codec == "subtitle"){hasSubs = true;}
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
if (audioId == INVALID_TRACK_ID && M.getType(it->first) == "audio"){audioId = it->first;}
if (!hasSubs && M.getCodec(it->first) == "subtitle"){hasSubs = true;}
}
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if (myMeta.tracks[*it].type == "video"){
vidTracks++;
int bWidth = myMeta.tracks[*it].bps;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); ++it){
if (M.getType(it->first) == "video"){
++vidTracks;
int bWidth = M.getBps(it->first);
if (bWidth < 5){bWidth = 5;}
if (audioId != -1){bWidth += myMeta.tracks[audioId].bps;}
if (audioId != INVALID_TRACK_ID){bWidth += M.getBps(audioId);}
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8);
result << ",RESOLUTION=" << myMeta.tracks[*it].width << "x" << myMeta.tracks[*it].height;
if (myMeta.tracks[*it].fpks){
result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000;
result << ",RESOLUTION=" << M.getWidth(it->first) << "x" << M.getHeight(it->first);
if (M.getFpks(it->first)){
result << ",FRAME-RATE=" << (float)M.getFpks(it->first) / 1000;
}
if (hasSubs){result << ",SUBTITLES=\"sub1\"";}
result << ",CODECS=\"";
result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init);
if (audioId != -1){
result << "," << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init);
result << Util::codecString(M.getCodec(it->first), M.getInit(it->first));
if (audioId != INVALID_TRACK_ID){
result << "," << Util::codecString(M.getCodec(audioId), M.getInit(audioId));
}
result << "\"";
result << "\r\n";
result << *it;
if (audioId != -1){result << "_" << audioId;}
result << "\"\r\n" << it->first;
if (audioId != INVALID_TRACK_ID){result << "_" << audioId;}
if (hasSessionIDs()){
result << "/index.m3u8?sessId=" << getpid() << "\r\n";
}else{
result << "/index.m3u8\r\n";
}
}else if (myMeta.tracks[*it].codec == "subtitle"){
}else if (M.getCodec(it->first) == "subtitle"){
if (myMeta.tracks[*it].lang.empty()){myMeta.tracks[*it].lang = "und";}
if (M.getLang(it->first).empty()){meta.setLang(it->first, "und");}
result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << myMeta.tracks[*it].lang
<< "\",NAME=\"" << Encodings::ISO639::decode(myMeta.tracks[*it].lang)
<< "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\""
result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << M.getLang(it->first)
<< "\",NAME=\"" << Encodings::ISO639::decode(M.getLang(it->first))
<< "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << it->first << "/index.m3u8\""
<< "\r\n";
}
}
if (!vidTracks && audioId){
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8);
result << ",CODECS=\""
<< Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\"";
if (!vidTracks && audioId != INVALID_TRACK_ID){
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (M.getBps(audioId) * 8);
result << ",CODECS=\"" << Util::codecString(M.getCodec(audioId), M.getInit(audioId)) << "\"";
result << "\r\n";
result << audioId << "/index.m3u8\r\n";
}
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
return result.str();
}
std::string OutHLS::pushLiveIndex(){
std::stringstream result;
result << "#EXTM3U\r\n";
std::set<unsigned int> audioTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3" ||
it->second.codec == "MP2"){
audioTracks.insert(it->first);
}
}
if (!audioTracks.size()){audioTracks.insert(-1);}
unsigned int vidTracks = 0;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.codec == "H264" || it->second.codec == "HEVC" || it->second.codec == "MPEG2"){
for (std::set<unsigned int>::iterator audIt = audioTracks.begin(); audIt != audioTracks.end(); audIt++){
vidTracks++;
int bWidth = it->second.bps;
if (bWidth < 5){bWidth = 5;}
if (*audIt != -1){bWidth += myMeta.tracks[*audIt].bps;}
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n";
result << it->first;
if (*audIt != -1){result << "_" << *audIt;}
result << "/index.m3u8\r\n";
}
}
}
if (!vidTracks && audioTracks.size()){
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[*audioTracks.begin()].bps * 8)
<< "\r\n";
result << *audioTracks.begin() << "/index.m3u8\r\n";
}
return result.str();
}
std::string OutHLS::pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime){
updateMeta();
std::string OutHLS::liveIndex(size_t tid, const std::string &sessId){
std::stringstream result;
// parse single track
result << "#EXTM3U\r\n#EXT-X-TARGETDURATION:" << (myMeta.tracks[tid].biggestFragment() / 1000) + 1 << "\r\n";
uint32_t targetDuration = (M.biggestFragment(tid) / 1000) + 1;
result << "#EXTM3U\r\n#EXT-X-VERSION:";
std::deque<std::string> lines;
unsigned int skippedLines = 0;
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin();
it != myMeta.tracks[tid].fragments.end(); it++){
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
long long duration = it->getDuration();
if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;}
if (starttime < bTime){skippedLines++;}
if (starttime >= bTime && (starttime + duration) <= eTime){
char lineBuf[400];
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n",
((duration + 500) / 1000), starttime, starttime + duration);
lines.push_back(lineBuf);
}
result << (M.getEncryption(tid) == "" ? "3" : "5");
result << "\r\n#EXT-X-TARGETDURATION:" << targetDuration << "\r\n";
if (M.getEncryption(tid) != ""){
result << "#EXT-X-KEY:METHOD=SAMPLE-AES,URI=\"";
result << "urlHere";
result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery" << std::endl;
}
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
while (lines.size()){
result << lines.front();
lines.pop_front();
}
if (!myMeta.live && eTime >= myMeta.tracks[tid].lastms){result << "#EXT-X-ENDLIST\r\n";}
return result.str();
}
std::string OutHLS::liveIndex(int tid, std::string &sessId){
updateMeta();
std::stringstream result;
// parse single track
uint32_t target_dur = (myMeta.tracks[tid].biggestFragment() / 1000) + 1;
result << "#EXTM3U\r\n#EXT-X-VERSION:3\r\n#EXT-X-TARGETDURATION:" << target_dur << "\r\n";
std::deque<std::string> lines;
std::deque<uint16_t> durs;
uint32_t total_dur = 0;
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin();
it != myMeta.tracks[tid].fragments.end(); it++){
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
long long duration = it->getDuration();
if (duration <= 0){duration = myMeta.tracks[tid].lastms - starttime;}
std::deque<uint16_t> durations;
uint32_t totalDuration = 0;
DTSC::Keys keys(M.keys(tid));
DTSC::Fragments fragments(M.fragments(tid));
uint32_t firstFragment = fragments.getFirstValid();
uint32_t endFragment = fragments.getEndValid();
for (int i = firstFragment; i < endFragment; i++){
uint64_t duration = fragments.getDuration(i);
size_t keyNumber = fragments.getFirstKey(i);
uint64_t startTime = keys.getTime(keyNumber);
if (!duration){duration = M.getLastms(tid) - startTime;}
double floatDur = (double)duration / 1000;
char lineBuf[400];
if (myMeta.tracks[tid].codec == "subtitle"){
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%d&from=%lld&to=%lld\r\n",
(double)duration / 1000, streamName.c_str(), tid, starttime, starttime + duration);
if (M.getCodec(tid) == "subtitle"){
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%zu&from=%" PRIu64 "&to=%" PRIu64 "\r\n",
(double)duration / 1000, streamName.c_str(), tid, startTime, startTime + duration);
}else{
if (sessId.size()){
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n",
(double)duration / 1000, starttime, starttime + duration, sessId.c_str());
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts?sessId=%s\r\n",
floatDur, startTime, startTime + duration, sessId.c_str());
}else{
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration / 1000,
starttime, starttime + duration);
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%" PRIu64 "_%" PRIu64 ".ts\r\n", floatDur,
startTime, startTime + duration);
}
}
durs.push_back(duration);
total_dur += duration;
totalDuration += duration;
durations.push_back(duration);
lines.push_back(lineBuf);
}
unsigned int skippedLines = 0;
if (myMeta.live && lines.size()){
size_t skippedLines = 0;
if (M.getLive() && lines.size()){
// only print the last segment when VoD
lines.pop_back();
total_dur -= durs.back();
durs.pop_back();
totalDuration -= durations.back();
durations.pop_back();
// skip the first two segments when live, unless that brings us under 4 target durations
while ((total_dur - durs.front()) > (target_dur * 4000) && skippedLines < 2){
while ((totalDuration - durations.front()) > (targetDuration * 4000) && skippedLines < 2){
lines.pop_front();
total_dur -= durs.front();
durs.pop_front();
totalDuration -= durations.front();
durations.pop_front();
++skippedLines;
}
/*LTS-START*/
// remove lines to reduce size towards listlimit setting - but keep at least 4X target duration available
if (config->getInteger("listlimit")){
unsigned long listlimit = config->getInteger("listlimit");
while (lines.size() > listlimit && (total_dur - durs.front()) > (target_dur * 4000)){
// remove lines to reduce size towards listlimit setting - but keep at least 4X target
// duration available
uint64_t listlimit = config->getInteger("listlimit");
if (listlimit){
while (lines.size() > listlimit && (totalDuration - durations.front()) > (targetDuration * 4000)){
lines.pop_front();
total_dur -= durs.front();
durs.pop_front();
totalDuration -= durations.front();
durations.pop_front();
++skippedLines;
}
}
/*LTS-END*/
}
result << "#EXT-X-MEDIA-SEQUENCE:" << myMeta.tracks[tid].missedFrags + skippedLines << "\r\n";
result << "#EXT-X-MEDIA-SEQUENCE:" << M.getMissedFragments(tid) + skippedLines << "\r\n";
while (lines.size()){
result << lines.front();
lines.pop_front();
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); it++){
result << *it;
}
if (!myMeta.live || total_dur == 0){result << "#EXT-X-ENDLIST\r\n";}
DEBUG_MSG(DLVL_HIGH, "Sending this index: %s", result.str().c_str());
if (!M.getLive() || !totalDuration){result << "#EXT-X-ENDLIST\r\n";}
HIGH_MSG("Sending this index: %s", result.str().c_str());
return result.str();
}// liveIndex
std::string OutHLS::generatePushList(){
updateMeta();
std::set<unsigned int> videoTracks;
std::set<unsigned int> audioTracks;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){
audioTracks.insert(it->first);
}
if (it->second.codec == "H264" || it->second.codec == "HEVC"){
videoTracks.insert(it->first);
}
}
JSON::Value result;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
std::stringstream tid;
tid << it->second.trackID;
result["tracks"][tid.str()] = it->second.toJSON(true);
}
for (std::set<unsigned int>::iterator it = videoTracks.begin(); it != videoTracks.end(); it++){
for (std::set<unsigned int>::iterator it2 = audioTracks.begin(); it2 != audioTracks.end(); it2++){
JSON::Value quality;
std::stringstream identifier;
identifier << "/" << *it << "_" << *it2;
quality["index"] = "/push" + identifier.str() + "/index_\%llu_\%llu.m3u8";
quality["segment"] = identifier.str() + "/\%llu_\%llu.ts";
quality["video"] = *it;
quality["audio"] = *it2;
quality["id"] = identifier.str();
std::deque<DTSC::Fragment>::iterator it3 = myMeta.tracks[*it].fragments.begin();
for (int i = 0; i < 2; i++){
if (it3 != myMeta.tracks[*it].fragments.end()){++it3;}
}
for (; it3 != myMeta.tracks[*it].fragments.end(); it3++){
if (myMeta.live && it3 == (myMeta.tracks[*it].fragments.end() - 1)){
// Skip the current last fragment if we are live
continue;
}
uint64_t starttime = myMeta.tracks[*it].getKey(it3->getNumber()).getTime();
std::stringstream line;
uint64_t duration = it3->getDuration();
if (duration <= 0){duration = myMeta.tracks[*it].lastms - starttime;}
std::stringstream segmenturl;
segmenturl << identifier.str() << "/" << starttime << "_" << duration + starttime << ".ts";
JSON::Value segment;
// segment["url"] = segmenturl.str();
segment["time"] = starttime;
segment["duration"] = duration;
segment["number"] = (uint64_t)it3->getNumber();
quality["segments"].append(segment);
}
result["qualities"].append(quality);
}
}
return result.toString();
;
}
OutHLS::OutHLS(Socket::Connection &conn) : TSOutput(conn){
@ -286,7 +170,6 @@ namespace Mist{
"Segmented streaming in Apple (TS-based) format over HTTP ( = HTTP Live Streaming)";
capa["url_rel"] = "/hls/$/index.m3u8";
capa["url_prefix"] = "/hls/$/";
capa["url_pushlist"] = "/hls/$/push/list";
capa["codecs"][0u][0u].append("+HEVC");
capa["codecs"][0u][1u].append("+H264");
capa["codecs"][0u][2u].append("+MPEG2");
@ -380,11 +263,12 @@ namespace Mist{
return;
}
std::string userAgent = H.GetHeader("User-Agent");
bool VLCworkaround = false;
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
std::string vlcver = H.GetHeader("User-Agent").substr(4);
if (userAgent.substr(0, 3) == "VLC"){
std::string vlcver = userAgent.substr(4);
if (vlcver[0] == '0' || vlcver[0] == '1' || (vlcver[0] == '2' && vlcver[2] < '2')){
DEBUG_MSG(DLVL_INFO, "Enabling VLC version < 2.2.0 bug workaround.");
INFO_MSG("Enabling VLC version < 2.2.0 bug workaround.");
VLCworkaround = true;
}
}
@ -392,82 +276,40 @@ namespace Mist{
initialize();
if (!keepGoing()){return;}
if (H.url.substr(5 + streamName.size(), 5) == "/push"){
std::string relPushUrl = H.url.substr(10 + streamName.size());
H.Clean();
if (relPushUrl == "/list"){
H.SetBody(generatePushList());
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
if (relPushUrl == "/index.m3u8"){
H.setCORSHeaders();
H.SetBody(pushLiveIndex());
H.SendResponse("200", "OK", myConn);
H.Clean(); // clean for any possible next requests
return;
}else{
unsigned int vTrack;
unsigned int aTrack;
unsigned long long bTime;
unsigned long long eTime;
if (sscanf(relPushUrl.c_str(), "/%u_%u/index_%llu_%llu.m3u", &vTrack, &aTrack, &bTime, &eTime) == 4){
if (eTime < bTime){eTime = bTime;}
H.setCORSHeaders();
H.SetBody(pushLiveIndex(vTrack, bTime, eTime));
H.SendResponse("200", "OK", myConn);
H.Clean(); // clean for any possible next requests
return;
}
}
H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
H.Clean(); // clean for any possible next requests
return;
}else if (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"){
size_t slashPos = H.getUrl().find('/', 5);
std::string tmpStr = H.getUrl().substr(slashPos);
long long unsigned int from;
if (sscanf(tmpStr.c_str(), "/%u_%u/%llu_%llu.ts", &vidTrack, &audTrack, &from, &until) != 4){
if (sscanf(tmpStr.c_str(), "/%u/%llu_%llu.ts", &vidTrack, &from, &until) != 3){
DEBUG_MSG(DLVL_MEDIUM, "Could not parse URL: %s", H.getUrl().c_str());
if (H.url.find(".m3u") == std::string::npos){
std::string tmpStr = H.getUrl().substr(5 + streamName.size());
uint64_t from;
if (sscanf(tmpStr.c_str(), "/%zu_%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &audTrack, &from, &until) != 4){
if (sscanf(tmpStr.c_str(), "/%zu/%" PRIu64 "_%" PRIu64 ".ts", &vidTrack, &from, &until) != 3){
MEDIUM_MSG("Could not parse URL: %s", H.getUrl().c_str());
H.Clean();
H.setCORSHeaders();
H.SetBody("The HLS URL wasn't understood - what did you want, exactly?\n");
myConn.SendNow(H.BuildResponse("404", "URL mismatch"));
H.Clean(); // clean for any possible next requests
return;
}else{
selectedTracks.clear();
selectedTracks.insert(vidTrack);
}
userSelect.clear();
userSelect[vidTrack].reload(streamName, vidTrack);
}else{
selectedTracks.clear();
selectedTracks.insert(vidTrack);
selectedTracks.insert(audTrack);
userSelect.clear();
userSelect[vidTrack].reload(streamName, vidTrack);
userSelect[audTrack].reload(streamName, audTrack);
}
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.codec == "ID3"){selectedTracks.insert(it->first);}
std::set<size_t> validTracks = getSupportedTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); ++it){
if (M.getCodec(*it) == "ID3"){userSelect[*it].reload(streamName, *it);}
}
// Keep a reference to the main track
// This is called vidTrack, even for audio-only streams
DTSC::Track &Trk = myMeta.tracks[vidTrack];
if (myMeta.live){
if (from < Trk.firstms){
H.Clean();
H.setCORSHeaders();
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot "
"be served.\n");
myConn.SendNow(H.BuildResponse("404", "Fragment out of range"));
H.Clean(); // clean for any possible next requests
WARN_MSG("Fragment @ %llu too old", from);
return;
}
if (M.getLive() && from < M.getFirstms(vidTrack)){
H.Clean();
H.setCORSHeaders();
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be "
"served.\n");
myConn.SendNow(H.BuildResponse("404", "Fragment out of range"));
H.Clean(); // clean for any possible next requests
WARN_MSG("Fragment @ %" PRIu64 " too old", from);
return;
}
H.SetHeader("Content-Type", "video/mp2t");
@ -487,10 +329,10 @@ namespace Mist{
H.StartResponse(H, myConn, VLCworkaround || config->getBool("nonchunked"));
// we assume whole fragments - but timestamps may be altered at will
uint32_t fragIndice = Trk.timeToFragnum(from);
contPAT = Trk.missedFrags + fragIndice; // PAT continuity counter
contPMT = Trk.missedFrags + fragIndice; // PMT continuity counter
contSDT = Trk.missedFrags + fragIndice; // SDT continuity counter
uint32_t fragIndice = M.getFragmentIndexForTime(vidTrack, from);
contPAT = M.getMissedFragments(vidTrack) + fragIndice; // PAT continuity counter
contPMT = M.getMissedFragments(vidTrack) + fragIndice; // PMT continuity counter
contSDT = M.getMissedFragments(vidTrack) + fragIndice; // SDT continuity counter
packCounter = 0;
parseData = true;
wantRequest = false;
@ -501,8 +343,7 @@ namespace Mist{
std::string request = H.url.substr(H.url.find("/", 5) + 1);
H.Clean();
H.setCORSHeaders();
H.SetHeader("Content-Type", "application/vnd.apple.mpegurl");
if (!myMeta.tracks.size()){
if (!M.getValidTracks().size()){
H.SendResponse("404", "Not online or found", myConn);
H.Clean();
return;
@ -516,8 +357,14 @@ namespace Mist{
if (request.find("/") == std::string::npos){
manifest = liveIndex();
}else{
int selectId = atoi(request.substr(0, request.find("/")).c_str());
manifest = liveIndex(selectId, sessId);
size_t idx = atoi(request.substr(0, request.find("/")).c_str());
if (!M.getValidTracks().count(idx)){
H.SendResponse("404", "No corresponding track found", myConn);
H.Clean();
return;
}
manifest = liveIndex(idx, sessId);
}
H.SetBody(manifest);
H.SendResponse("200", "OK", myConn);
@ -532,10 +379,9 @@ namespace Mist{
parseData = false;
// Ensure alignment of contCounters for selected tracks, to prevent discontinuities.
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
DTSC::Track &Trk = myMeta.tracks[*it];
uint32_t pkgPid = 255 + *it;
int &contPkg = contCounters[pkgPid];
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
uint32_t pkgPid = 255 + it->first;
uint16_t &contPkg = contCounters[pkgPid];
if (contPkg % 16 != 0){
packData.clear();
packData.setPID(pkgPid);
@ -556,7 +402,7 @@ namespace Mist{
TSOutput::sendNext();
}
void OutHLS::sendTS(const char *tsData, unsigned int len){H.Chunkify(tsData, len, myConn);}
void OutHLS::sendTS(const char *tsData, size_t len){H.Chunkify(tsData, len, myConn);}
void OutHLS::onFail(const std::string &msg, bool critical){
if (H.url.find(".m3u") == std::string::npos){

View file

@ -7,7 +7,7 @@ namespace Mist{
OutHLS(Socket::Connection &conn);
~OutHLS();
static void init(Util::Config *cfg);
void sendTS(const char *tsData, unsigned int len = 188);
void sendTS(const char *tsData, size_t len = 188);
void sendNext();
void onHTTP();
bool isReadyForPlay();
@ -19,17 +19,11 @@ namespace Mist{
bool hasSessionIDs(){return !config->getBool("mergesessions");}
std::string liveIndex();
std::string liveIndex(int tid, std::string &sessId);
std::string liveIndex(size_t tid, const std::string &sessId);
std::string pushLiveIndex();
std::string pushLiveIndex(int tid, unsigned long bTime, unsigned long eTime);
std::string generatePushList();
int canSeekms(unsigned int ms);
int keysToSend;
unsigned int vidTrack;
unsigned int audTrack;
long long unsigned int until;
size_t vidTrack;
size_t audTrack;
uint64_t until;
};
}// namespace Mist

View file

@ -1,597 +0,0 @@
#include "output_hss.h"
#include <mist/bitfields.h>
#include <mist/checksum.h>
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/http_parser.h>
#include <mist/mp4.h>
#include <mist/mp4_encryption.h> /*LTS*/
#include <mist/mp4_generic.h>
#include <mist/mp4_ms.h>
#include <mist/nal.h>/*LTS*/
#include <mist/stream.h>
#include <unistd.h>
///\todo Maybe move to util?
long long unsigned int binToInt(std::string &binary){
long long int result = 0;
for (int i = 0; i < 8; i++){
result <<= 8;
result += binary[i];
}
return result;
}
std::string intToBin(long long unsigned int number){
std::string result;
result.resize(8);
for (int i = 7; i >= 0; i--){
result[i] = number & 0xFF;
number >>= 8;
}
return result;
}
std::string toUTF16(std::string original){
std::string result;
result += (char)0xFF;
result += (char)0xFE;
for (std::string::iterator it = original.begin(); it != original.end(); it++){
result += (*it);
result += (char)0x00;
}
return result;
}
/// Converts bytes per second and track ID into a single bits per second value, where the last two
/// digits are the track ID. Breaks for track IDs > 99. But really, this is MS-SS, so who cares..?
uint64_t bpsAndIdToBitrate(uint32_t bps, uint64_t tid){
return ((uint64_t)((bps * 8) / 100)) * 100 + tid;
}
namespace Mist{
OutHSS::OutHSS(Socket::Connection &conn) : HTTPOutput(conn){
uaDelay = 0;
realTime = 0;
}
OutHSS::~OutHSS(){}
void OutHSS::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "HSS";
capa["friendly"] = "Microsoft segmented over HTTP (HSS)";
capa["desc"] = "Segmented streaming in Microsoft Silverlight (fMP4-based) format over HTTP ( = "
"HTTP Smooth Streaming)";
capa["url_rel"] = "/smooth/$.ism/Manifest";
capa["url_prefix"] = "/smooth/$.ism/";
capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][1u].append("AAC");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "silverlight";
capa["methods"][0u]["priority"] = 1;
}
void OutHSS::sendNext(){
if (thisPacket.getTime() >= playUntil){
stop();
wantRequest = true;
H.Chunkify("", 0, myConn);
H.Clean();
return;
}
char *dataPointer = 0;
size_t len = 0;
thisPacket.getString("data", dataPointer, len);
H.Chunkify(dataPointer, len, myConn);
}
int OutHSS::canSeekms(unsigned int ms){
// no tracks? Frame too new by definition.
if (!myMeta.tracks.size()){
DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because no tracks", ms);
return 1;
}
// loop trough all selected tracks
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
// return "too late" if one track is past this point
if (ms < myMeta.tracks[*it].firstms){
DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns -1 because track %lu firstms == %llu",
ms, *it, myMeta.tracks[*it].firstms);
return -1;
}
// return "too early" if one track is not yet at this point
if (ms > myMeta.tracks[*it].lastms){
DEBUG_MSG(DLVL_DONTEVEN, "HSS Canseek to %d returns 1 because track %lu lastms == %llu", ms,
*it, myMeta.tracks[*it].lastms);
return 1;
}
}
return 0;
}
void OutHSS::sendHeader(){
// We have a non-manifest request, parse it.
std::string Quality = H.url.substr(H.url.find("Q(", 2) + 2);
Quality = Quality.substr(0, Quality.find(")"));
std::string parseString = H.url.substr(H.url.find(")/") + 2);
parseString = parseString.substr(parseString.find("(") + 1);
long long int seekTime = atoll(parseString.substr(0, parseString.find(")")).c_str()) / 10000;
unsigned int tid = atoll(Quality.c_str()) % 100;
selectedTracks.clear();
selectedTracks.insert(tid);
if (myMeta.live){
updateMeta();
unsigned int timeout = 0;
int seekable;
do{
seekable = canSeekms(seekTime);
if (seekable == 0){
// iff the fragment in question is available, check if the next is available too
for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin();
it != myMeta.tracks[tid].keys.end(); it++){
if (it->getTime() >= seekTime){
if ((it + 1) == myMeta.tracks[tid].keys.end()){seekable = 1;}
break;
}
}
}
if (seekable > 0){
// time out after 21 seconds
if (++timeout > 42){
myConn.close();
break;
}
Util::wait(500);
updateMeta();
}
}while (myConn && seekable > 0);
if (seekable < 0){
H.Clean();
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be "
"served.\n");
myConn.SendNow(H.BuildResponse("412", "Fragment out of range"));
H.Clean(); // clean for any possible next requests
std::cout << "Fragment @ " << seekTime << "ms too old (" << myMeta.tracks[tid].firstms
<< " - " << myMeta.tracks[tid].lastms << " ms)" << std::endl;
stop();
wantRequest = true;
return;
}
}
seek(seekTime);
///\todo Rewrite to fragments
for (std::deque<DTSC::Key>::iterator it2 = myMeta.tracks[tid].keys.begin();
it2 != myMeta.tracks[tid].keys.end(); it2++){
if (it2->getTime() > seekTime){
playUntil = it2->getTime();
break;
}
}
myTrackStor = tid;
myKeyStor = seekTime;
keysToSend = 1;
// Seek to the right place and send a play-once for a single fragment.
std::stringstream sstream;
int partOffset = 0;
DTSC::Key keyObj;
for (std::deque<DTSC::Key>::iterator it = myMeta.tracks[tid].keys.begin();
it != myMeta.tracks[tid].keys.end(); it++){
if (it->getTime() >= seekTime){
keyObj = (*it);
std::deque<DTSC::Key>::iterator nextIt = it;
nextIt++;
if (nextIt == myMeta.tracks[tid].keys.end()){
if (myMeta.live){
H.Clean();
H.SetBody("Proxy, re-request this in a second or two.\n");
myConn.SendNow(H.BuildResponse("208", "Ask again later"));
H.Clean(); // clean for any possible next requests
std::cout << "Fragment after fragment @ " << seekTime << " not available yet" << std::endl;
}
}
break;
}
partOffset += it->getParts();
}
if (H.url == "/"){
return; // Don't continue, but continue instead.
}
/*
if (myMeta.live){
if (mstime == 0 && seekTime > 1){
H.Clean();
H.SetBody("The requested fragment is no longer kept in memory on the server and cannot be
served.\n"); myConn.SendNow(H.BuildResponse("412", "Fragment out of range")); H.Clean(); //clean
for any possible next requests std::cout << "Fragment @ " << seekTime << " too old" <<
std::endl; continue;
}
}
*/
///\todo Select correct track (tid);
// Wrap everything in mp4 boxes
MP4::MFHD mfhd_box;
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2));
MP4::TFHD tfhd_box;
tfhd_box.setFlags(MP4::tfhdSampleFlag);
tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2));
if (myMeta.tracks[tid].type == "video"){
tfhd_box.setDefaultSampleFlags(0x00004001);
}else{
tfhd_box.setDefaultSampleFlags(0x00008002);
}
MP4::TRUN trun_box;
trun_box.setDataOffset(42); ///\todo Check if this is a placeholder, or an actually correct number
unsigned int keySize = 0;
if (myMeta.tracks[tid].type == "video"){
trun_box.setFlags(MP4::trundataOffset | MP4::trunfirstSampleFlags | MP4::trunsampleDuration |
MP4::trunsampleSize | MP4::trunsampleOffsets);
}else{
trun_box.setFlags(MP4::trundataOffset | MP4::trunsampleDuration | MP4::trunsampleSize);
}
trun_box.setFirstSampleFlags(0x00004002);
for (int i = 0; i < keyObj.getParts(); i++){
MP4::trunSampleInformation trunSample;
trunSample.sampleSize = myMeta.tracks[tid].parts[i + partOffset].getSize();
keySize += myMeta.tracks[tid].parts[i + partOffset].getSize();
trunSample.sampleDuration = myMeta.tracks[tid].parts[i + partOffset].getDuration() * 10000;
if (myMeta.tracks[tid].type == "video"){
trunSample.sampleOffset = myMeta.tracks[tid].parts[i + partOffset].getOffset() * 10000;
}
trun_box.setSampleInformation(trunSample, i);
}
MP4::SDTP sdtp_box;
sdtp_box.setVersion(0);
if (myMeta.tracks[tid].type == "video"){
sdtp_box.setValue(36, 4);
for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(20, 4 + i);}
}else{
sdtp_box.setValue(40, 4);
for (int i = 1; i < keyObj.getParts(); i++){sdtp_box.setValue(40, 4 + i);}
}
MP4::TRAF traf_box;
traf_box.setContent(tfhd_box, 0);
traf_box.setContent(trun_box, 1);
traf_box.setContent(sdtp_box, 2);
// If the stream is live, we want to have a fragref box if possible
//////HEREHEREHERE
if (myMeta.live){
MP4::UUID_TFXD tfxd_box;
tfxd_box.setTime(keyObj.getTime());
tfxd_box.setDuration(keyObj.getLength());
traf_box.setContent(tfxd_box, 3);
MP4::UUID_TrackFragmentReference fragref_box;
fragref_box.setVersion(1);
fragref_box.setFragmentCount(0);
int fragCount = 0;
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++){
if (myMeta.tracks[tid].keys[i].getTime() > seekTime){
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i,
myMeta.tracks[tid].keys[i].getTime(), seekTime);
fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
fragref_box.setFragmentCount(++fragCount);
}
}
traf_box.setContent(fragref_box, 4);
}
MP4::MOOF moof_box;
moof_box.setContent(mfhd_box, 0);
moof_box.setContent(traf_box, 1);
/*LTS-START*/
///\TODO This encryption-handling section does not handle thisPacket correctly!
if (nProxy.encrypt){
MP4::UUID_SampleEncryption sEnc;
sEnc.setVersion(0);
if (myMeta.tracks[tid].type == "audio"){
sEnc.setFlags(0);
for (int i = 0; i < keyObj.getParts(); i++){
MP4::UUID_SampleEncryption_Sample newSample;
prepareNext();
thisPacket.getString("ivec", newSample.InitializationVector);
sEnc.setSample(newSample, i);
}
}else{
sEnc.setFlags(2);
std::deque<long long int> tmpParts;
for (int i = 0; i < keyObj.getParts(); i++){
// Get the correct packet
prepareNext();
MP4::UUID_SampleEncryption_Sample newSample;
thisPacket.getString("ivec", newSample.InitializationVector);
std::deque<int> nalSizes = nalu::parseNalSizes(thisPacket);
for (std::deque<int>::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){
int encrypted = (*it - 5) & ~0xF; // Bitmask to a multiple of 16
MP4::UUID_SampleEncryption_Sample_Entry newEntry;
newEntry.BytesClear = *it - encrypted; // Size + nal_unit_type
newEntry.BytesEncrypted = encrypted; // Entire NAL except nal_unit_type;
newSample.Entries.push_back(newEntry);
}
sEnc.setSample(newSample, i);
}
}
traf_box.setContent(sEnc, 3);
}
seek(seekTime);
/*LTS-END*/
// Setting the correct offsets.
moof_box.setContent(traf_box, 1);
trun_box.setDataOffset(moof_box.boxedSize() + 8);
traf_box.setContent(trun_box, 1);
moof_box.setContent(traf_box, 1);
H.Clean();
H.SetHeader("Content-Type", "video/mp4");
H.setCORSHeaders();
H.StartResponse(H, myConn);
H.Chunkify(moof_box.asBox(), moof_box.boxedSize(), myConn);
int size = htonl(keySize + 8);
H.Chunkify((char *)&size, 4, myConn);
H.Chunkify("mdat", 4, myConn);
sentHeader = true;
H.Clean();
}
/*LTS-START*/
void OutHSS::loadEncryption(){
static bool encryptionLoaded = false;
if (!encryptionLoaded){
// Load the encryption data page
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
nProxy.encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
if (nProxy.encryptionPage.mapped){
nProxy.vmData.read(nProxy.encryptionPage.mapped);
nProxy.encrypt = true;
}
encryptionLoaded = true;
}
}
std::string OutHSS::protectionHeader(){
loadEncryption();
std::string xmlGen =
"<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" "
"version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></"
"PROTECTINFO><KID>";
xmlGen += nProxy.vmData.keyid;
xmlGen += "</KID><LA_URL>";
xmlGen += nProxy.vmData.laurl;
xmlGen += "</LA_URL></DATA></WRMHEADER>";
std::string tmp = toUTF16(xmlGen);
tmp = tmp.substr(2);
std::stringstream resGen;
resGen << (char)((tmp.size() + 10) & 0xFF);
resGen << (char)(((tmp.size() + 10) >> 8) & 0xFF);
resGen << (char)(((tmp.size() + 10) >> 16) & 0xFF);
resGen << (char)(((tmp.size() + 10) >> 24) & 0xFF);
resGen << (char)0x01 << (char)0x00;
resGen << (char)0x01 << (char)0x00;
resGen << (char)((tmp.size()) & 0xFF);
resGen << (char)(((tmp.size()) >> 8) & 0xFF);
resGen << tmp;
return Encodings::Base64::encode(resGen.str());
}
/*LTS-END*/
///\brief Builds an index file for HTTP Smooth streaming.
///\param encParams The encryption parameters. /*LTS*/
///\return The index file for HTTP Smooth Streaming.
std::string OutHSS::smoothIndex(){
loadEncryption(); // LTS
updateMeta();
std::stringstream Result;
Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n";
Result << "<SmoothStreamingMedia "
"MajorVersion=\"2\" "
"MinorVersion=\"0\" "
"TimeScale=\"10000000\" ";
std::deque<std::map<unsigned int, DTSC::Track>::iterator> audioIters;
std::deque<std::map<unsigned int, DTSC::Track>::iterator> videoIters;
long long int maxWidth = 0;
long long int maxHeight = 0;
long long int minWidth = 99999999;
long long int minHeight = 99999999;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
if (it->second.codec == "AAC"){audioIters.push_back(it);}
if (it->second.codec == "H264"){
videoIters.push_back(it);
if (it->second.width > maxWidth){maxWidth = it->second.width;}
if (it->second.width < minWidth){minWidth = it->second.width;}
if (it->second.height > maxHeight){maxHeight = it->second.height;}
if (it->second.height < minHeight){minHeight = it->second.height;}
}
}
DEBUG_MSG(DLVL_DONTEVEN, "Buffer window here %lld", myMeta.bufferWindow);
if (myMeta.vod){
Result << "Duration=\""
<< ((*videoIters.begin())->second.lastms - (*videoIters.begin())->second.firstms) << "0000\"";
}else{
Result << "Duration=\"0\" "
"IsLive=\"TRUE\" "
"LookAheadFragmentCount=\"2\" "
"DVRWindowLength=\""
<< myMeta.bufferWindow
<< "0000\" "
"CanSeek=\"TRUE\" "
"CanPause=\"TRUE\" ";
}
Result << ">\n";
// Add audio entries
if (audioIters.size()){
Result << "<StreamIndex "
"Type=\"audio\" "
"QualityLevels=\""
<< audioIters.size()
<< "\" "
"Name=\"audio\" "
"Chunks=\""
<< (*audioIters.begin())->second.keys.size()
<< "\" "
"Url=\"Q({bitrate})/A({start time})\">\n";
int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin();
it != audioIters.end(); it++){
Result << "<QualityLevel "
"Index=\""
<< index
<< "\" "
"Bitrate=\""
<< bpsAndIdToBitrate((*it)->second.bps, (*it)->first)
<< "\" "
"CodecPrivateData=\""
<< std::hex;
for (unsigned int i = 0; i < (*it)->second.init.size(); i++){
Result << std::setfill('0') << std::setw(2) << std::right << (int)(*it)->second.init[i];
}
Result << std::dec
<< "\" "
"SamplingRate=\""
<< (*it)->second.rate
<< "\" "
"Channels=\"2\" "
"BitsPerSample=\"16\" "
"PacketSize=\"4\" "
"AudioTag=\"255\" "
"FourCC=\"AACL\" >\n";
Result << "</QualityLevel>\n";
index++;
}
if ((*audioIters.begin())->second.keys.size()){
for (std::deque<DTSC::Key>::iterator it = (*audioIters.begin())->second.keys.begin();
it != (((*audioIters.begin())->second.keys.end()) - 1); it++){
Result << "<c ";
if (it == (*audioIters.begin())->second.keys.begin()){
Result << "t=\"" << it->getTime() * 10000 << "\" ";
}
Result << "d=\"" << it->getLength() * 10000 << "\" />\n";
}
}
Result << "</StreamIndex>\n";
}
// Add video entries
if (videoIters.size()){
Result << "<StreamIndex "
"Type=\"video\" "
"QualityLevels=\""
<< videoIters.size()
<< "\" "
"Name=\"video\" "
"Chunks=\""
<< (*videoIters.begin())->second.keys.size()
<< "\" "
"Url=\"Q({bitrate})/V({start time})\" "
"MaxWidth=\""
<< maxWidth
<< "\" "
"MaxHeight=\""
<< maxHeight
<< "\" "
"DisplayWidth=\""
<< maxWidth
<< "\" "
"DisplayHeight=\""
<< maxHeight << "\">\n";
int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin();
it != videoIters.end(); it++){
// Add video qualities
Result << "<QualityLevel "
"Index=\""
<< index
<< "\" "
"Bitrate=\""
<< bpsAndIdToBitrate((*it)->second.bps, (*it)->first)
<< "\" "
"CodecPrivateData=\""
<< std::hex;
MP4::AVCC avccbox;
avccbox.setPayload((*it)->second.init);
std::string tmpString = avccbox.asAnnexB();
for (unsigned int i = 0; i < tmpString.size(); i++){
Result << std::setfill('0') << std::setw(2) << std::right << (int)tmpString[i];
}
Result << std::dec
<< "\" "
"MaxWidth=\""
<< (*it)->second.width
<< "\" "
"MaxHeight=\""
<< (*it)->second.height
<< "\" "
"FourCC=\"AVC1\" >\n";
Result << "</QualityLevel>\n";
index++;
}
if ((*videoIters.begin())->second.keys.size()){
for (std::deque<DTSC::Key>::iterator it = (*videoIters.begin())->second.keys.begin();
it != (((*videoIters.begin())->second.keys.end()) - 1); it++){
Result << "<c ";
if (it == (*videoIters.begin())->second.keys.begin()){
Result << "t=\"" << it->getTime() * 10000 << "\" ";
}
Result << "d=\"" << it->getLength() * 10000 << "\" />\n";
}
}
Result << "</StreamIndex>\n";
}
/*LTS-START*/
if (nProxy.encrypt){
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
Result << protectionHeader();
Result << "</ProtectionHeader></Protection>";
}
/*LTS-END*/
Result << "</SmoothStreamingMedia>\n";
#if DEBUG >= 8
std::cerr << "Sending this manifest:" << std::endl << Result << std::endl;
#endif
return toUTF16(Result.str());
}// smoothIndex
void OutHSS::onHTTP(){
if ((H.method == "OPTIONS" || H.method == "HEAD") && H.url.find("Manifest") == std::string::npos){
H.Clean();
H.SetHeader("Content-Type", "application/octet-stream");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
initialize();
loadEncryption(); // LTS
if (H.url.find("Manifest") != std::string::npos){
// Manifest, direct reply
H.Clean();
H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if (H.method == "OPTIONS" || H.method == "HEAD"){
H.SendResponse("200", "OK", myConn);
return;
}
std::string manifest = smoothIndex();
H.SetBody(manifest);
H.SendResponse("200", "OK", myConn);
H.Clean();
}else{
parseData = true;
wantRequest = false;
sendHeader();
}
}
}// namespace Mist

View file

@ -1,26 +0,0 @@
#include "output_http.h"
#include <mist/http_parser.h>
namespace Mist{
class OutHSS : public HTTPOutput{
public:
OutHSS(Socket::Connection &conn);
~OutHSS();
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
void sendHeader();
protected:
std::string protectionHeader(); /*LTS*/
std::string smoothIndex();
void loadEncryption(); /*LTS*/
int canSeekms(unsigned int ms);
int keysToSend;
int myTrackStor;
int myKeyStor;
unsigned long long playUntil;
};
}// namespace Mist
typedef Mist::OutHSS mistOut;

View file

@ -223,8 +223,8 @@ namespace Mist{
MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(),
streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
streamName = H.GetVar("stream");
nProxy.userClient.finish();
statsPage.finish();
userSelect.clear();
if (statComm){statComm.setStatus(COMM_STATUS_DISCONNECT);}
reConnector(handler);
onFail("Server error - could not start connector", true);
return;
@ -285,7 +285,6 @@ namespace Mist{
webSock = 0;
return;
}
crc = getpid();
onWebsocketConnect();
H.Clean();
return;
@ -388,7 +387,6 @@ namespace Mist{
DTSC::Scan capa = rCapa.getMember("connectors");
pipedCapa = capa.getMember(connector).asJSON();
}
// build arguments for starting output process
std::string tmparg = Util::getMyPath() + std::string("MistOut") + connector;
std::string tmpPrequest;

View file

@ -110,7 +110,6 @@ namespace Mist{
capa["desc"] = "HTTP connection handler, provides all enabled HTTP-based outputs";
capa["provides"] = "HTTP";
capa["protocol"] = "http://";
capa["codecs"][0u][0u].append("*");
capa["url_rel"] = "/$.html";
capa["url_match"].append("/crossdomain.xml");
capa["url_match"].append("/clientaccesspolicy.xml");
@ -237,6 +236,15 @@ namespace Mist{
}
}
}
bool allowBFrames = true;
if (conncapa.isMember("methods")){
jsonForEach(conncapa["methods"], mthd){
if (mthd->isMember("nobframes") && (*mthd)["nobframes"]){
allowBFrames = false;
break;
}
}
}
const std::string &rel = conncapa["url_rel"].asStringRef();
unsigned int most_simul = 0;
unsigned int total_matches = 0;
@ -250,31 +258,31 @@ namespace Mist{
jsonForEach((*itb), itc){
const std::string &strRef = (*itc).asStringRef();
bool byType = false;
bool multiSel = false;
uint8_t shift = 0;
if (strRef[shift] == '@'){
byType = true;
++shift;
}
if (strRef[shift] == '+'){
multiSel = true;
++shift;
}
jsonForEach(strmMeta["tracks"], trit){
if ((!byType && (*trit)["codec"].asStringRef() == strRef.substr(shift)) ||
(byType && (*trit)["type"].asStringRef() == strRef.substr(shift)) ||
strRef.substr(shift) == "*"){
matches++;
total_matches++;
if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() &&
conncapa["exceptions"].size()){
jsonForEach(conncapa["exceptions"], ex){
if (ex.key() == "codec:" + strRef.substr(shift)){
if (!Util::checkException(*ex, useragent)){
matches--;
total_matches--;
if (allowBFrames || !(trit->isMember("bframes") && (*trit)["bframes"])){
matches++;
total_matches++;
if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() &&
conncapa["exceptions"].size()){
jsonForEach(conncapa["exceptions"], ex){
if (ex.key() == "codec:" + strRef.substr(shift)){
if (!Util::checkException(*ex, useragent)){
matches--;
total_matches--;
}
break;
}
break;
}
}
}
@ -392,9 +400,13 @@ namespace Mist{
"'),MistVideoObject:mv" + forceType + devSkin + "});" + seekTo + "</script></div></body></html>");
if ((uAgent.find("iPad") != std::string::npos) || (uAgent.find("iPod") != std::string::npos) ||
(uAgent.find("iPhone") != std::string::npos)){
H.SetHeader("Location", hlsUrl);
H.SendResponse("307", "HLS redirect", myConn);
return;
if (uAgent.find("OS 11") == std::string::npos && uAgent.find("OS 12") == std::string::npos &&
uAgent.find("OS 13") == std::string::npos && uAgent.find("OS 14") == std::string::npos &&
uAgent.find("OS 15") == std::string::npos && uAgent.find("OS 16") == std::string::npos){
H.SetHeader("Location", hlsUrl);
H.SendResponse("307", "HLS redirect", myConn);
return;
}
}
H.SendResponse("200", "OK", myConn);
}
@ -450,39 +462,32 @@ namespace Mist{
if (!myConn){return json_resp;}
bool hasVideo = false;
for (std::map<unsigned int, DTSC::Track>::iterator trit = myMeta.tracks.begin();
trit != myMeta.tracks.end(); trit++){
if (trit->second.type == "video"){
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == "video"){
hasVideo = true;
if (trit->second.width > json_resp["width"].asInt()){
json_resp["width"] = trit->second.width;
}
if (trit->second.height > json_resp["height"].asInt()){
json_resp["height"] = trit->second.height;
if (M.getWidth(*it) > json_resp["width"].asInt()){json_resp["width"] = M.getWidth(*it);}
if (M.getHeight(*it) > json_resp["height"].asInt()){
json_resp["height"] = M.getHeight(*it);
}
}
}
if (json_resp["width"].asInt() < 1 || json_resp["height"].asInt() < 1){
json_resp["width"] = 640;
json_resp["height"] = 480;
if (!hasVideo){json_resp["height"] = 20;}
json_resp["height"] = (hasVideo ? 480 : 20);
}
if (myMeta.vod){json_resp["type"] = "vod";}
if (myMeta.live){json_resp["type"] = "live";}
json_resp["type"] = (M.getVod() ? "vod" : "live");
// show ALL the meta datas!
json_resp["meta"] = myMeta.toJSON();
M.toJSON(json_resp["meta"], true);
jsonForEach(json_resp["meta"]["tracks"], it){
if (it->isMember("lang")){
(*it)["language"] = Encodings::ISO639::decode((*it)["lang"].asStringRef());
}
it->removeMember("fragments");
it->removeMember("keys");
it->removeMember("keysizes");
it->removeMember("parts");
it->removeMember("ivecs"); /*LTS*/
if (M.hasBFrames((*it)["idx"].asInt())){(*it)["bframes"] = 1;}
}
json_resp["meta"].removeMember("source");
json_resp["meta"]["bframes"] = (M.hasBFrames() ? 1 : 0);
// Get sources/protocols information
Util::DTSCShmReader rCapa(SHM_CAPA);
@ -704,14 +709,13 @@ namespace Mist{
initialize();
if (!myConn){return;}
for (std::map<unsigned int, DTSC::Track>::iterator trit = myMeta.tracks.begin();
trit != myMeta.tracks.end(); trit++){
if (trit->second.type == "video"){
trackSources += " <video src='" + streamName +
"?track=" + JSON::Value(trit->first).asString() + "' height='" +
JSON::Value(trit->second.height).asString() + "' system-bitrate='" +
JSON::Value(trit->second.bps).asString() + "' width='" +
JSON::Value(trit->second.width).asString() + "' />\n";
std::set<size_t> validTracks = M.getValidTracks();
for (std::set<size_t>::iterator it = validTracks.begin(); it != validTracks.end(); it++){
if (M.getType(*it) == "video"){
trackSources += " <video src='" + streamName + "?track=" + JSON::Value(*it).asString() +
"' height='" + JSON::Value(M.getHeight(*it)).asString() +
"' system-bitrate='" + JSON::Value(M.getBps(*it)).asString() +
"' width='" + JSON::Value(M.getWidth(*it)).asString() + "' />\n";
}
}
}
@ -1023,7 +1027,7 @@ namespace Mist{
currStreamName = streamName;
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_STATE, streamName.c_str());
IPC::sharedPage streamStatus(pageName, 1, false, false);
uint8_t prevState, newState, metaCounter;
uint8_t prevState, newState, pingCounter = 0;
uint64_t prevTracks;
prevState = newState = STRMSTAT_INVALID;
while (keepGoing()){
@ -1034,11 +1038,10 @@ namespace Mist{
newState = streamStatus.mapped[0];
}
if (newState != prevState || (newState == STRMSTAT_READY && myMeta.tracks.size() != prevTracks)){
if (newState != prevState || (newState == STRMSTAT_READY && M.getValidTracks().size() != prevTracks)){
if (newState == STRMSTAT_READY){
reconnect();
updateMeta();
prevTracks = myMeta.tracks.size();
prevTracks = M.getValidTracks().size();
}else{
disconnect();
}
@ -1057,8 +1060,7 @@ namespace Mist{
}else{
Util::sleep(250);
}
if (newState == STRMSTAT_READY && (++metaCounter % 4) == 0){updateMeta();}
if ((metaCounter % 40) == 0){ws.sendFrame("", 0, 0x9);}
if ((++pingCounter % 40) == 0){ws.sendFrame("", 0, 0x9);}
}
}
return true;

View file

@ -95,7 +95,7 @@ namespace Mist{
C.close();
return;
}else{
Util::sleep(100);
Util::sleep(20);
}
}
HIGH_MSG("Started SSL connection handler");
@ -162,10 +162,10 @@ namespace Mist{
// We have data - pass it on
activity = true;
while (http_buf.size() && http){
int todo = http_buf.get().size();
int toSend = http_buf.get().size();
int done = 0;
while (done < todo){
ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, todo - done);
while (done < toSend){
ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, toSend - done);
if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT){
HIGH_MSG("SSL disconnect!");
http.close();
@ -174,19 +174,18 @@ namespace Mist{
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){
done += ret;
}else{
Util::sleep(50);
Util::sleep(20);
}
}
http_buf.get().clear();
}
}
if (!activity){Util::sleep(50);}
if (!activity){Util::sleep(20);}
}
// close the HTTP process (close stdio, kill its PID)
http.close();
Util::Procs::Stop(http_proc);
uint16_t waiting = 0;
while (++waiting < 100){
while (++waiting < 50){
if (!Util::Procs::isRunning(http_proc)){break;}
Util::sleep(100);
}

View file

@ -51,13 +51,7 @@ namespace Mist{
void OutHTTPTS::initialSeek(){
// Adds passthrough support to the regular initialSeek function
if (targetParams.count("passthrough")){
selectedTracks.clear();
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
selectedTracks.insert(it->first);
}
}
if (targetParams.count("passthrough")){selectAllTracks();}
Output::initialSeek();
}
@ -69,13 +63,13 @@ namespace Mist{
capa["url_rel"] = "/$.ts";
capa["url_match"] = "/$.ts";
capa["socket"] = "http_ts";
capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("HEVC");
capa["codecs"][0u][0u].append("MPEG2");
capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("AC3");
capa["codecs"][0u][1u].append("MP2");
capa["codecs"][0u][0u].append("+H264");
capa["codecs"][0u][0u].append("+HEVC");
capa["codecs"][0u][0u].append("+MPEG2");
capa["codecs"][0u][1u].append("+AAC");
capa["codecs"][0u][1u].append("+MP3");
capa["codecs"][0u][1u].append("+AC3");
capa["codecs"][0u][1u].append("+MP2");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/video/mpeg";
capa["methods"][0u]["priority"] = 1;
@ -83,7 +77,6 @@ namespace Mist{
capa["push_urls"].append("ts-exec:*");
{
int fin = 0, fout = 0, ferr = 0;
pid_t srt_tx = -1;
const char *args[] ={"srt-live-transmit", 0};
srt_tx = Util::Procs::StartPiped(args, 0, 0, 0);
@ -130,11 +123,12 @@ namespace Mist{
wantRequest = false;
}
void OutHTTPTS::sendTS(const char *tsData, unsigned int len){
if (!isRecording()){
H.Chunkify(tsData, len, myConn);
}else{
void OutHTTPTS::sendTS(const char *tsData, size_t len){
if (isRecording()){
myConn.SendNow(tsData, len);
return;
}
H.Chunkify(tsData, len, myConn);
if (targetParams.count("passthrough")){selectAllTracks();}
}
}// namespace Mist

View file

@ -8,7 +8,7 @@ namespace Mist{
~OutHTTPTS();
static void init(Util::Config *cfg);
void onHTTP();
void sendTS(const char *tsData, unsigned int len = 188);
void sendTS(const char *tsData, size_t len = 188);
void initialSeek();
private:

297
src/output/output_jpg.cpp Normal file
View file

@ -0,0 +1,297 @@
#include "output_jpg.h"
#include <fstream>
#include <mist/bitfields.h>
#include <mist/mp4_generic.h>
#include <mist/procs.h>
#include <sys/stat.h> //for stat
#include <sys/types.h> //for stat
#include <unistd.h> //for stat
namespace Mist{
OutJPG::OutJPG(Socket::Connection &conn) : HTTPOutput(conn){
HTTP = false;
cachedir = config->getString("cachedir");
if (cachedir.size()){
cachedir += "/MstJPEG" + streamName;
cachetime = config->getInteger("cachetime");
}else{
cachetime = 0;
}
if (config->getString("target").size()){
initialize();
if (!streamName.size()){
WARN_MSG("Recording unconnected JPG output to file! Cancelled.");
conn.close();
return;
}
if (!M){
INFO_MSG("Stream not available - aborting");
conn.close();
return;
}
if (!userSelect.size()){
INFO_MSG("Stream codec not supported - aborting");
conn.close();
return;
}
// We generate a thumbnail first, then output it if successful
generate();
if (!jpg_buffer.str().size()){
// On failure, report, but do not open the file or write anything
FAIL_MSG("Could not generate thumbnail for %s", streamName.c_str());
myConn.close();
return;
}
if (config->getString("target") == "-"){
INFO_MSG("Outputting %s to stdout in JPG format", streamName.c_str());
}else{
if (!connectToFile(config->getString("target"))){
myConn.close();
return;
}
INFO_MSG("Recording %s to %s in JPG format", streamName.c_str(), config->getString("target").c_str());
}
myConn.SendNow(jpg_buffer.str().c_str(), jpg_buffer.str().size());
myConn.close();
return;
}
}
/// Pretends the stream is always ready to play - we don't care about waiting times or whatever
bool OutJPG::isReadyForPlay(){return true;}
void OutJPG::initialSeek(){
size_t mainTrack = getMainSelectedTrack();
if (mainTrack == INVALID_TRACK_ID){return;}
INFO_MSG("Doing initial seek");
if (M.getLive()){
liveSeek();
uint32_t targetKey = M.getKeyIndexForTime(mainTrack, currentTime());
seek(M.getTimeForKeyIndex(mainTrack, targetKey));
return;
}
// cancel if there are no keys in the main track
if (!M.getValidTracks().count(mainTrack) || !M.getLastms(mainTrack)){
WARN_MSG("Aborted vodSeek because no tracks selected");
return;
}
uint64_t seekPos = M.getFirstms(mainTrack) + (M.getLastms(mainTrack) - M.getFirstms(mainTrack)) / 2;
MEDIUM_MSG("VoD seek to %" PRIu64 "ms", seekPos);
uint32_t targetKey = M.getKeyIndexForTime(mainTrack, seekPos);
seek(M.getTimeForKeyIndex(mainTrack, targetKey));
}
void OutJPG::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "JPG";
capa["desc"] = "Allows getting a representative key frame as JPG image. Requires ffmpeg (with "
"h264 decoding and jpeg encoding) to be "
"installed in the PATH.";
capa["url_rel"] = "/$.jpg";
capa["url_match"] = "/$.jpg";
capa["codecs"][0u][0u].append("H264");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/image/jpeg";
capa["methods"][0u]["priority"] = 0;
capa["push_urls"].append("/*.jpg");
capa["optional"]["cachedir"]["name"] = "Cache directory";
capa["optional"]["cachedir"]["help"] =
"Location to store cached images, preferably in RAM somewhere";
capa["optional"]["cachedir"]["option"] = "--cachedir";
capa["optional"]["cachedir"]["short"] = "D";
capa["optional"]["cachedir"]["default"] = "/tmp";
capa["optional"]["cachedir"]["type"] = "string";
capa["optional"]["cachetime"]["name"] = "Cache time";
capa["optional"]["cachetime"]["help"] =
"Duration in seconds to wait before refreshing cached images. Does not apply to VoD "
"streams (VoD is cached infinitely)";
capa["optional"]["cachetime"]["option"] = "--cachetime";
capa["optional"]["cachetime"]["short"] = "T";
capa["optional"]["cachetime"]["default"] = 30;
capa["optional"]["cachetime"]["type"] = "uint";
capa["optional"]["ffopts"]["name"] = "Ffmpeg arguments";
capa["optional"]["ffopts"]["help"] =
"Extra arguments to use when generating the jpg file through ffmpeg";
capa["optional"]["ffopts"]["option"] = "--ffopts";
capa["optional"]["ffopts"]["short"] = "F";
capa["optional"]["ffopts"]["default"] = "-qscale:v 4";
capa["optional"]["ffopts"]["type"] = "string";
cfg->addOptionsFromCapabilities(capa);
JSON::Value opt;
opt["arg"] = "string";
opt["default"] = "";
opt["arg_num"] = 1;
opt["help"] = "Target filename to store JPG file as, or - for stdout.";
cfg->addOption("target", opt);
}
void OutJPG::onHTTP(){
std::string method = H.method;
H.clearHeader("Range");
H.clearHeader("Icy-MetaData");
H.clearHeader("User-Agent");
H.setCORSHeaders();
if (method == "OPTIONS" || method == "HEAD"){
H.SetHeader("Content-Type", "image/jpeg");
H.protocol = "HTTP/1.1";
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
}
initialize();
if (!userSelect.size()){
H.protocol = "HTTP/1.0";
H.setCORSHeaders();
H.body.clear();
H.SendResponse("200", "Unprocessable: not H264", myConn);
#include "noh264.h"
myConn.SendNow(noh264, noh264_len);
myConn.close();
return;
}
H.SetHeader("Content-Type", "image/jpeg");
H.protocol = "HTTP/1.0";
H.setCORSHeaders();
H.StartResponse(H, myConn);
HTTP = true;
generate();
if (!jpg_buffer.str().size()){
NoFFMPEG();
}else{
H.Chunkify(jpg_buffer.str().c_str(), jpg_buffer.str().size(), myConn);
if (cachedir.size()){
std::ofstream cachefile;
cachefile.open(cachedir.c_str());
cachefile << jpg_buffer.str();
cachefile.close();
}
}
H.Chunkify("", 0, myConn);
H.Clean();
HTTP = false;
}
void OutJPG::NoFFMPEG(){
FAIL_MSG("Could not start ffmpeg! Is it installed on the system?");
#include "noffmpeg.h"
if (HTTP){
H.Chunkify(noffmpeg, noffmpeg_len, myConn);
}else{
myConn.SendNow(noffmpeg, noffmpeg_len);
}
}
void OutJPG::generate(){
// If we're caching, check if the cache hasn't expired yet...
if (cachedir.size() && cachetime){
struct stat statData;
if (stat(cachedir.c_str(), &statData) != -1){
if (Util::epoch() - statData.st_mtime <= cachetime || M.getVod()){
std::ifstream cachefile;
cachefile.open(cachedir.c_str());
char buffer[8 * 1024];
while (cachefile.good() && myConn){
cachefile.read(buffer, 8 * 1024);
uint32_t s = cachefile.gcount();
if (HTTP){
H.Chunkify(buffer, s, myConn);
}else{
myConn.SendNow(buffer, s);
}
}
cachefile.close();
return;
}
}
}
initialSeek();
int fin = -1, fout = -1, ferr = 2;
pid_t ffmpeg = -1;
// Start ffmpeg quietly if we're < MEDIUM debug level
char ffcmd[256];
ffcmd[255] = 0; // ensure there is an ending null byte
snprintf(ffcmd, 255, "ffmpeg %s -f h264 -i - %s -vframes 1 -f mjpeg -",
(Util::Config::printDebugLevel >= DLVL_MEDIUM ? "" : "-v quiet"),
config->getString("ffopts").c_str());
HIGH_MSG("Starting JPG command: %s", ffcmd);
char *args[128];
uint8_t argCnt = 0;
char *startCh = 0;
for (char *i = ffcmd; i - ffcmd < 256; ++i){
if (!*i){
if (startCh){args[argCnt++] = startCh;}
break;
}
if (*i == ' '){
if (startCh){
args[argCnt++] = startCh;
startCh = 0;
*i = 0;
}
}else{
if (!startCh){startCh = i;}
}
}
args[argCnt] = 0;
ffmpeg = Util::Procs::StartPiped(args, &fin, &fout, &ferr);
if (ffmpeg < 2){
Socket::Connection failure(fin, fout);
failure.close();
NoFFMPEG();
return;
}
VERYHIGH_MSG("Started ffmpeg, PID %" PRIu64 ", pipe %" PRIu32 "/%" PRIu32, (uint64_t)ffmpeg,
(uint32_t)fin, (uint32_t)fout);
Socket::Connection ffconn(fin, -1);
// Send H264 init data in Annex B format
MP4::AVCC avccbox;
avccbox.setPayload(M.getInit(getMainSelectedTrack()));
ffconn.SendNow(avccbox.asAnnexB());
INSANE_MSG("Sent init data to ffmpeg...");
if (ffconn && prepareNext() && thisPacket){
uint64_t keytime = thisPacket.getTime();
do{
char *p = 0;
size_t l = 0;
uint32_t o = 0;
thisPacket.getString("data", p, l);
// Send all NAL units in the key frame, in Annex B format
while (o + 4 < l){
// get NAL unit size
uint32_t s = Bit::btohl(p + o);
// make sure we don't go out of bounds of packet
if (o + s + 4 > l){break;}
// Send H264 Annex B start code
ffconn.SendNow("\000\000\000\001", 4);
// Send NAL unit
ffconn.SendNow(p + o + 4, s);
INSANE_MSG("Sent h264 %" PRIu32 "b NAL unit to ffmpeg (time: %" PRIu64 ")...", s,
thisPacket.getTime());
// Skip to next NAL unit
o += s + 4;
}
INSANE_MSG("Sent whole packet, checking next...");
}while (ffconn && prepareNext() && thisPacket && thisPacket.getTime() == keytime);
}
ffconn.close();
// Output ffmpeg result data to socket
jpg_buffer.clear();
Socket::Connection ffout(-1, fout);
while (myConn && ffout && (ffout.spool() || ffout.Received().size())){
while (myConn && ffout.Received().size()){
jpg_buffer << ffout.Received().get();
ffout.Received().get().clear();
}
}
ffout.close();
}
}// namespace Mist

22
src/output/output_jpg.h Normal file
View file

@ -0,0 +1,22 @@
#include "output_http.h"
namespace Mist{
class OutJPG : public HTTPOutput{
public:
OutJPG(Socket::Connection &conn);
static void init(Util::Config *cfg);
void onHTTP();
bool isReadyForPlay();
private:
void generate();
void initialSeek();
void NoFFMPEG();
std::string cachedir;
uint64_t cachetime;
bool HTTP;
std::stringstream jpg_buffer;
};
}// namespace Mist
typedef Mist::OutJPG mistOut;

View file

@ -9,6 +9,7 @@ namespace Mist{
keepReselecting = false;
dupcheck = false;
noReceive = false;
pushTrack = INVALID_TRACK_ID;
}
void OutJSON::init(Util::Config *cfg){
@ -36,13 +37,13 @@ namespace Mist{
}
}
JSON::Value jPack;
if (myMeta.tracks[thisPacket.getTrackId()].codec == "JSON"){
if (M.getCodec(thisIdx) == "JSON"){
char *dPtr;
size_t dLen;
thisPacket.getString("data", dPtr, dLen);
jPack["data"] = JSON::fromString(dPtr, dLen);
jPack["time"] = thisPacket.getTime();
jPack["track"] = (uint64_t)thisPacket.getTrackId();
jPack["track"] = thisIdx;
}else{
jPack = thisPacket.toJSON();
}
@ -93,7 +94,7 @@ namespace Mist{
static bool recursive = false;
if (recursive){return true;}
recursive = true;
if (keepReselecting && !isPushing() && !myMeta.vod){
if (keepReselecting && !isPushing() && !M.getVod()){
uint64_t maxTimer = 7200;
while (--maxTimer && keepGoing()){
if (!isBlocking){myConn.spool();}
@ -154,56 +155,39 @@ namespace Mist{
return;
}
}
if (!bootMsOffset){
if (myMeta.bootMsOffset){
bootMsOffset = myMeta.bootMsOffset;
}else{
bootMsOffset = Util::bootMS();
}
}
if (!M.getBootMsOffset()){meta.setBootMsOffset(Util::bootMS());}
// We now know we're allowed to push. Read a JSON object.
JSON::Value inJSON = JSON::fromString(webSock->data, webSock->data.size());
if (!inJSON || !inJSON.isObject()){
// Ignore empty and/or non-parsable JSON packets
MEDIUM_MSG("Ignoring non-JSON object: %s", webSock->data);
MEDIUM_MSG("Ignoring non-JSON object: %s", (char *)webSock->data);
return;
}
// Let's create a new track for pushing purposes, if needed
if (!pushTrack){
pushTrack = 1;
while (myMeta.tracks.count(pushTrack)){++pushTrack;}
}
myMeta.tracks[pushTrack].type = "meta";
myMeta.tracks[pushTrack].codec = "JSON";
if (pushTrack == INVALID_TRACK_ID){pushTrack = meta.addTrack();}
meta.setType(pushTrack, "meta");
meta.setCodec(pushTrack, "JSON");
meta.setID(pushTrack, pushTrack);
// We have a track set correctly. Let's attempt to buffer a frame.
lastSendTime = Util::bootMS();
if (!inJSON.isMember("unix")){
// Base timestamp on arrival time
lastOutTime = (lastSendTime - bootMsOffset);
lastOutTime = (lastSendTime - M.getBootMsOffset());
}else{
// Base timestamp on unix time
lastOutTime = (lastSendTime - bootMsOffset) + (inJSON["unix"].asInt() - Util::epoch()) * 1000;
lastOutTime = (lastSendTime - M.getBootMsOffset()) + (inJSON["unix"].asInt() - Util::epoch()) * 1000;
}
lastOutData = inJSON.toString();
static DTSC::Packet newPack;
newPack.genericFill(lastOutTime, 0, pushTrack, lastOutData.data(), lastOutData.size(), 0, true, bootMsOffset);
bufferLivePacket(newPack);
if (!idleInterval){idleInterval = 100;}
bufferLivePacket(lastOutTime, 0, pushTrack, lastOutData.data(), lastOutData.size(), 0, true);
if (!idleInterval){idleInterval = 5000;}
if (isBlocking){setBlocking(false);}
}
/// Repeats last JSON packet every 5 seconds to keep stream alive.
void OutJSON::onIdle(){
if (nProxy.trackState[pushTrack] != FILL_ACC){
continueNegotiate(pushTrack);
if (nProxy.trackState[pushTrack] == FILL_ACC){idleInterval = 5000;}
return;
}
lastOutTime += (Util::bootMS() - lastSendTime);
lastSendTime = Util::bootMS();
static DTSC::Packet newPack;
newPack.genericFill(lastOutTime, 0, pushTrack, lastOutData.data(), lastOutData.size(), 0, true, bootMsOffset);
bufferLivePacket(newPack);
bufferLivePacket(lastOutTime, 0, pushTrack, lastOutData.data(), lastOutData.size(), 0, true);
}
void OutJSON::onHTTP(){

View file

@ -1,9 +1,9 @@
#include "output_progressive_mp3.h"
#include "output_mp3.h"
namespace Mist{
OutProgressiveMP3::OutProgressiveMP3(Socket::Connection &conn) : HTTPOutput(conn){}
OutMP3::OutMP3(Socket::Connection &conn) : HTTPOutput(conn){}
void OutProgressiveMP3::init(Util::Config *cfg){
void OutMP3::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "MP3";
capa["friendly"] = "MP3 over HTTP";
@ -23,16 +23,16 @@ namespace Mist{
cfg->addOption("target", opt);
}
bool OutProgressiveMP3::isRecording(){return config->getString("target").size();}
bool OutMP3::isRecording(){return config->getString("target").size();}
void OutProgressiveMP3::sendNext(){
void OutMP3::sendNext(){
char *dataPointer = 0;
size_t len = 0;
thisPacket.getString("data", dataPointer, len);
myConn.SendNow(dataPointer, len);
}
void OutProgressiveMP3::sendHeader(){
void OutMP3::sendHeader(){
if (!isRecording()){
std::string method = H.method;
H.Clean();
@ -48,7 +48,7 @@ namespace Mist{
sentHeader = true;
}
void OutProgressiveMP3::onHTTP(){
void OutMP3::onHTTP(){
std::string method = H.method;
H.Clean();

View file

@ -1,9 +1,9 @@
#include "output_http.h"
namespace Mist{
class OutProgressiveMP3 : public HTTPOutput{
class OutMP3 : public HTTPOutput{
public:
OutProgressiveMP3(Socket::Connection &conn);
OutMP3(Socket::Connection &conn);
static void init(Util::Config *cfg);
void onHTTP();
void sendNext();
@ -15,4 +15,4 @@ namespace Mist{
};
}// namespace Mist
typedef Mist::OutProgressiveMP3 mistOut;
typedef Mist::OutMP3 mistOut;

1144
src/output/output_mp4.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@
#include <mist/http_parser.h>
namespace Mist{
struct keyPart{
class keyPart{
public:
bool operator<(const keyPart &rhs) const{
if (time < rhs.time){return true;}
@ -15,7 +15,6 @@ namespace Mist{
uint64_t time;
uint64_t byteOffset; // Stores relative bpos for fragmented MP4
uint64_t index;
uint32_t size;
};
struct fragSet{
@ -24,28 +23,27 @@ namespace Mist{
uint64_t firstTime;
uint64_t lastTime;
};
class OutProgressiveMP4 : public HTTPOutput{
class OutMP4 : public HTTPOutput{
public:
OutProgressiveMP4(Socket::Connection &conn);
~OutProgressiveMP4();
OutMP4(Socket::Connection &conn);
~OutMP4();
static void init(Util::Config *cfg);
uint64_t mp4HeaderSize(uint64_t &fileSize, int fragmented = 0);
std::string DTSCMeta2MP4Header(uint64_t &size, int fragmented = 0);
// int fragmented values: 0 = non fragmented stream, 1 = frag stream main header
void buildFragment(); // this builds the structure of the fragment header and stores it in a member variable
void sendFragmentHeader(); // this builds the moof box for fragmented MP4
uint64_t mp4HeaderSize(uint64_t &fileSize, int fragmented = 0) const;
std::string mp4Header(uint64_t &size, int fragmented = 0);
uint64_t mp4moofSize(uint64_t startFragmentTime, uint64_t endFragmentTime, uint64_t &mdatSize) const;
virtual void sendFragmentHeaderTime(uint64_t startFragmentTime,
uint64_t endFragmentTime); // this builds the moof box for fragmented MP4
void findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize);
void onHTTP();
void sendNext();
void sendHeader();
bool doesWebsockets(){return true;}
void onIdle();
bool onFinish();
virtual void onWebsocketFrame();
virtual void onWebsocketConnect();
protected:
Util::ResizeablePointer webBuf;
uint64_t fileSize;
uint64_t byteStart;
uint64_t byteEnd;
@ -53,23 +51,33 @@ namespace Mist{
uint64_t currPos;
uint64_t seekPoint;
uint64_t nextHeaderTime;
uint64_t headerSize;
// variables for standard MP4
std::set<keyPart> sortSet; // needed for unfragmented MP4, remembers the order of keyparts
// variables for fragmented
size_t fragSeqNum; // the sequence number of the next keyframe/fragment when producing fragmented MP4's
size_t fragSeqNum; // the sequence number of the next keyframe/fragment when producing
// fragmented MP4's
size_t vidTrack; // the video track we use as fragmenting base
uint64_t realBaseOffset; // base offset for every moof packet
// from sendnext
bool sending3GP;
uint64_t startTime;
uint64_t endTime;
bool chromeWorkaround;
int keysOnly;
uint64_t estimateFileSize();
uint64_t estimateFileSize() const;
// This is a dirty solution... but it prevents copying and copying and copying again
std::map<size_t, fragSet> currentPartSet;
std::string protectionHeader(size_t idx);
};
}// namespace Mist
typedef Mist::OutProgressiveMP4 mistOut;
typedef Mist::OutMP4 mistOut;

203
src/output/output_ogg.cpp Normal file
View file

@ -0,0 +1,203 @@
#include "output_ogg.h"
#include <algorithm>
#include <mist/bitfields.h>
#include <mist/bitstream.h>
#include <mist/defines.h>
namespace Mist{
OutOGG::OutOGG(Socket::Connection &conn) : HTTPOutput(conn){realTime = 0;}
OutOGG::~OutOGG(){}
void OutOGG::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "OGG";
capa["friendly"] = "OGG over HTTP";
capa["desc"] = "Pseudostreaming in OGG format over HTTP";
capa["deps"] = "HTTP";
capa["url_rel"] = "/$.ogg";
capa["url_match"] = "/$.ogg";
capa["codecs"][0u][0u].append("theora");
capa["codecs"][0u][1u].append("vorbis");
capa["codecs"][0u][1u].append("opus");
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/video/ogg";
capa["methods"][0u]["priority"] = 8u;
capa["methods"][0u]["nolive"] = 1;
}
void OutOGG::sendNext(){
OGG::oggSegment newSegment;
thisPacket.getString("data", newSegment.dataString);
pageBuffer[thisIdx].totalFrames = ((double)thisPacket.getTime() / (1000000.0f / M.getFpks(thisIdx))) +
1.5; // should start at 1. added .5 for rounding.
if (pageBuffer[thisIdx].codec == OGG::THEORA){
newSegment.isKeyframe = thisPacket.getFlag("keyframe");
if (newSegment.isKeyframe == true){
pageBuffer[thisIdx].sendTo(myConn); // send data remaining in buffer (expected to fit on a
// page), keyframe will allways start on new page
pageBuffer[thisIdx].lastKeyFrame = pageBuffer[thisIdx].totalFrames;
}
newSegment.framesSinceKeyFrame = pageBuffer[thisIdx].totalFrames - pageBuffer[thisIdx].lastKeyFrame;
newSegment.lastKeyFrameSeen = pageBuffer[thisIdx].lastKeyFrame;
}
newSegment.frameNumber = pageBuffer[thisIdx].totalFrames;
newSegment.timeStamp = thisPacket.getTime();
pageBuffer[thisIdx].oggSegments.push_back(newSegment);
if (pageBuffer[thisIdx].codec == OGG::VORBIS){
pageBuffer[thisIdx].vorbisStuff(); // this updates lastKeyFrame
}
while (pageBuffer[thisIdx].shouldSend()){pageBuffer[thisIdx].sendTo(myConn);}
}
bool OutOGG::onFinish(){
for (std::map<size_t, OGG::Page>::iterator it = pageBuffer.begin(); it != pageBuffer.end(); it++){
it->second.setHeaderType(OGG::EndOfStream);
it->second.sendTo(myConn);
}
return false;
}
bool OutOGG::parseInit(const std::string &initData, std::deque<std::string> &output){
size_t index = 0;
if (initData[0] == 0x02){//"special" case, requires interpretation similar to table
if (initData.size() < 7){
FAIL_MSG("initData size too tiny (size: %lu)", initData.size());
return false;
}
size_t len1 = 0;
size_t len2 = 0;
index = 1;
while (initData[index] == 255){// get len 1
len1 += initData[index++];
}
len1 += initData[index++];
while (initData[index] == 255){// get len 1
len2 += initData[index++];
}
len2 += initData[index++];
if (initData.size() < (len1 + len2 + 4)){
FAIL_MSG("initData size too tiny (size: %zu)", initData.size());
return false;
}
output.push_back(initData.substr(index, len1));
index += len1;
output.push_back(initData.substr(index, len2));
index += len2;
output.push_back(initData.substr(index));
}else{
if (initData.size() < 7){
FAIL_MSG("initData size too tiny (size: %lu)", initData.size());
return false;
}
unsigned int len = 0;
for (unsigned int i = 0; i < 3; i++){
std::string temp = initData.substr(index, 2);
len = Bit::btohs(temp.data());
index += 2; // start of data
if (index + len > initData.size()){
FAIL_MSG("index+len > initData size");
return false;
}
output.push_back(initData.substr(index, len)); // add data to output deque
index += len;
INFO_MSG("init data len[%d]: %d ", i, len);
}
}
return true;
}
void OutOGG::sendHeader(){
HTTP_S.Clean(); // make sure no parts of old requests are left in any buffers
HTTP_S.SetHeader("Content-Type", "video/ogg");
HTTP_S.protocol = "HTTP/1.0";
myConn.SendNow(HTTP_S.BuildResponse("200",
"OK")); // no SetBody = unknown length - this is intentional, we will stream the entire file
std::map<size_t, std::deque<std::string> > initData;
OGG::oggSegment newSegment;
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
if (M.getCodec(it->first) == "theora"){// get size and position of init data for this page.
parseInit(M.getInit(it->first), initData[it->first]);
pageBuffer[it->first].codec = OGG::THEORA;
pageBuffer[it->first].totalFrames =
1; // starts at frame number 1, according to weird offDetectMeta function.
std::string tempStr = initData[it->first][0];
theora::header tempHead((char *)tempStr.c_str(), 42);
pageBuffer[it->first].split = tempHead.getKFGShift();
INFO_MSG("got theora KFG shift: %d", pageBuffer[it->first].split); // looks OK.
}else if (M.getCodec(it->first) == "vorbis"){
parseInit(M.getInit(it->first), initData[it->first]);
pageBuffer[it->first].codec = OGG::VORBIS;
pageBuffer[it->first].totalFrames = 0;
pageBuffer[it->first].sampleRate = M.getRate(it->first);
pageBuffer[it->first].prevBlockFlag = -1;
vorbis::header tempHead((char *)initData[it->first][0].data(), initData[it->first][0].size());
pageBuffer[it->first].blockSize[0] = std::min(tempHead.getBlockSize0(), tempHead.getBlockSize1());
pageBuffer[it->first].blockSize[1] = std::max(tempHead.getBlockSize0(), tempHead.getBlockSize1());
char audioChannels = tempHead.getAudioChannels(); //?
vorbis::header tempHead2((char *)initData[it->first][2].data(), initData[it->first][2].size());
pageBuffer[it->first].vorbisModes = tempHead2.readModeDeque(audioChannels); // getting modes
}else if (M.getCodec(it->first) == "opus"){
pageBuffer[it->first].totalFrames = 0; //?
pageBuffer[it->first].codec = OGG::OPUS;
initData[it->first].push_back(M.getInit(it->first));
initData[it->first].push_back(
std::string("OpusTags\000\000\000\012MistServer\000\000\000\000", 26));
}
pageBuffer[it->first].clear(OGG::BeginOfStream, 0, it->first,
0); // CREATES a (map)pageBuffer object, *it = id, pagetype=BOS
newSegment.dataString = initData[it->first].front();
initData[it->first].pop_front();
pageBuffer[it->first].oggSegments.push_back(newSegment);
pageBuffer[it->first].sendTo(myConn, 0); // granule position of 0
}
for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){
while (initData[it->first].size()){
newSegment.dataString = initData[it->first].front();
initData[it->first].pop_front();
pageBuffer[it->first].oggSegments.push_back(newSegment);
}
while (pageBuffer[it->first].oggSegments.size()){
pageBuffer[it->first].sendTo(myConn, 0); // granule position of 0
}
}
sentHeader = true;
}
void OutOGG::onRequest(){
if (HTTP_R.Read(myConn)){
DEVEL_MSG("Received request %s", HTTP_R.getUrl().c_str());
if (HTTP_R.method == "OPTIONS" || HTTP_R.method == "HEAD"){
HTTP_S.Clean();
HTTP_S.SetHeader("Content-Type", "video/ogg");
HTTP_S.protocol = "HTTP/1.0";
HTTP_S.SendResponse("200", "OK", myConn);
HTTP_S.Clean();
return;
}
if (HTTP_R.GetVar("audio") != ""){
size_t track = atoll(HTTP_R.GetVar("audio").c_str());
userSelect[track].reload(streamName, track);
}
if (HTTP_R.GetVar("video") != ""){
size_t track = atoll(HTTP_R.GetVar("video").c_str());
userSelect[track].reload(streamName, track);
}
parseData = true;
wantRequest = false;
HTTP_R.Clean();
}
}
}// namespace Mist

24
src/output/output_ogg.h Normal file
View file

@ -0,0 +1,24 @@
#include "output_http.h"
#include <mist/http_parser.h>
#include <mist/ogg.h>
namespace Mist{
class OutOGG : public HTTPOutput{
public:
OutOGG(Socket::Connection &conn);
~OutOGG();
static void init(Util::Config *cfg);
void onRequest();
void sendNext();
void sendHeader();
bool onFinish();
bool parseInit(const std::string &initData, std::deque<std::string> &output);
protected:
HTTP::Parser HTTP_R; // Received HTTP
HTTP::Parser HTTP_S; // Sent HTTP
std::map<size_t, OGG::Page> pageBuffer; // OGG specific variables
};
}// namespace Mist
typedef Mist::OutOGG mistOut;

Some files were not shown because too many files have changed in this diff Show more