New Meta commit
This commit is contained in:
parent
fccf66fba2
commit
2b99f2f5ea
183 changed files with 13333 additions and 14421 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> ¤tPos){
|
||||
// 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> ¤tPos){
|
|||
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> ¤tPos){
|
|||
// 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> ¤tPos){
|
|||
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> ¤tPos){
|
|||
// 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> ¤tPos){
|
|||
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> ¤tPos, 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;}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -9,5 +9,5 @@ public:
|
|||
|
||||
private:
|
||||
FLV::Tag flvData;
|
||||
long long filter;
|
||||
int64_t filter;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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] << " ";
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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> ¤tPos){
|
||||
// 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> ¤tPos, 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;
|
||||
}
|
||||
78
src/analysers/h264_translate.cpp
Normal file
78
src/analysers/h264_translate.cpp
Normal 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);
|
||||
}
|
||||
|
|
@ -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 == ""){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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++){
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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("+ "));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
1027
src/input/input.cpp
1027
src/input/input.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
839
src/io.cpp
839
src/io.cpp
File diff suppressed because it is too large
Load diff
95
src/io.h
95
src/io.h
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
BIN
src/output/noffmpeg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
src/output/noh264.jpg
Normal file
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
|
|
@ -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
678
src/output/output_cmaf.cpp
Normal 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
43
src/output/output_cmaf.h
Normal 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;
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
297
src/output/output_jpg.cpp
Normal 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
22
src/output/output_jpg.h
Normal 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;
|
||||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -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
1144
src/output/output_mp4.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
203
src/output/output_ogg.cpp
Normal 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
24
src/output/output_ogg.h
Normal 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
Loading…
Add table
Add a link
Reference in a new issue