This commit is contained in:
DDVTech 2021-09-10 23:44:31 +02:00 committed by Thulinma
parent 5b79f296d6
commit fccf66fba2
280 changed files with 56975 additions and 71885 deletions

View file

@ -12,8 +12,8 @@ Analyser::Analyser(Util::Config &conf){
isActive = &conf.is_active;
}
///Opens the filename. Supports stdin and plain files.
bool Analyser::open(const std::string & filename){
/// Opens the filename. Supports stdin and plain files.
bool Analyser::open(const std::string &filename){
if (filename.size() && filename != "-"){
int fp = ::open(filename.c_str(), O_RDONLY);
if (fp <= 0){
@ -37,10 +37,12 @@ void Analyser::stop(){
/// Prints validation message if needed
Analyser::~Analyser(){
if (validate){std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << mediaTime << std::endl;}
if (validate){
std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << mediaTime << std::endl;
}
}
///Checks if standard input is still valid.
/// Checks if standard input is still valid.
bool Analyser::isOpen(){
return (*isActive) && std::cin.good();
}
@ -48,9 +50,7 @@ bool Analyser::isOpen(){
/// Main loop for all analysers. Reads packets while not interrupted, parsing and/or printing them.
int Analyser::run(Util::Config &conf){
isActive = &conf.is_active;
if (!open(conf.getString("filename"))){
return 1;
}
if (!open(conf.getString("filename"))){return 1;}
while (conf.is_active && isOpen()){
if (!parsePacket()){
if (isOpen()){
@ -63,7 +63,7 @@ int Analyser::run(Util::Config &conf){
if (validate){
finTime = Util::bootSecs();
//slow down to realtime + 10s
// slow down to realtime + 10s
if (validate && ((finTime - upTime + 10) * 1000 < mediaTime)){
uint32_t sleepMs = mediaTime - (Util::bootSecs() - upTime + 10) * 1000;
if ((finTime - upTime + sleepMs / 1000) >= timeOut){
@ -122,4 +122,3 @@ void Analyser::init(Util::Config &conf){
conf.addOption("detail", opt);
opt.null();
}

View file

@ -38,4 +38,3 @@ protected:
uint64_t finTime; ///< Unix time of last packet received
bool *isActive; ///< Pointer to is_active bool from config
};

View file

@ -21,9 +21,9 @@
StreamData tempSD; // temp global
bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim) {
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) {
if (offset == std::string::npos){
return false; // name string not found.
}
// expected: delim character BEFORE blockstart.
@ -35,18 +35,18 @@ bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos) {
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) );
// 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) {
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) {
if (offset == std::string::npos){
return false; // name string not found.
}
blockStart = data.find(delim, offset);
@ -55,31 +55,31 @@ bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size
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) {
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) );
// 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) {
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, "\"")) {
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) )
// 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) {
bool getLong(std::string &data, std::string name, long &output){
size_t blockStart, blockEnd;
if (!getValueBlock(data, name, 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.
}
@ -89,32 +89,32 @@ bool getLong(std::string &data, std::string name, long &output) {
}
// 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) {
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) {
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) {
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) {
if (blockEnd == std::string::npos){
blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos) {
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 ... />
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 {
}else{
blockEnd += name.size() + 2; // position after /name>
}
@ -122,31 +122,33 @@ bool getBlock(std::string &data, std::string name, int offset, size_t &blockStar
return true;
}
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if (!getString(data, "mimeType", mimeType)) { // get first occurence of mimeType. --> this will break if multiple mimetypes should be read from
// this block because no offset is provided. solution: use this on a substring containing the
// desired information.
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) {
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)) {
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "Representation not found");
return false;
}
@ -157,7 +159,7 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str());
// check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)) {
if (!getLong(block, "id", tempSD.trackID)){
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str());
return false;
}
@ -165,7 +167,7 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
offset = 0;
// get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)) {
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found");
return false;
}
@ -178,27 +180,28 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")) {
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, "$")) {
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());
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)) {
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)){
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found");
return false;
}
@ -210,12 +213,12 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
offset = 0;
long long unsigned int totalDuration = 0;
long timeValue;
while (1) {
if (!getBlock(block2, "S", offset, blockStart, blockEnd)) {
if (numS == 0) {
while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
return false;
} else {
}else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true)
}
@ -225,10 +228,10 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &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)) {
if (getLong(sBlock, "t", timeValue)){
totalDuration = timeValue; // reset totalDuration to value of t
}
if (!getLong(sBlock, "d", timeValue)) { // expected duration in every S.
if (!getLong(sBlock, "d", timeValue)){// expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S");
return false;
}
@ -250,12 +253,12 @@ bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
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.
offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant.
}
return true;
}
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData) {
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData){
// for all adaptation sets
// representation ID
int numAdaptationSet = 0;
@ -264,24 +267,24 @@ bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<Stre
size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)) {
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));
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)) {
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
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) {
if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
return false;
}
@ -289,44 +292,38 @@ bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<Stre
return true;
}
dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf){
port = 80;
url = conf.getString("url");
if (url.substr(0, 7) != "http://") {
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
// return -1;
exit(1);
}
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
if((url.find('/') == std::string::npos) || (url.find(".mpd") == std::string::npos))
{
std::cout << "incorrect url"<<std::endl;
if ((url.find('/') == std::string::npos) || (url.find(".mpd") == std::string::npos)){
std::cout << "incorrect url" << std::endl;
mayExecute = false;
return;
}
server = url.substr(0, url.find('/'));
url = url.substr(url.find('/'));
if (server.find(':') != std::string::npos) {
if (server.find(':') != std::string::npos){
port = atoi(server.substr(server.find(':') + 1).c_str());
server = server.substr(0, server.find(':'));
}
startTime = Util::bootSecs();
abortTime = conf.getInteger("abort");
conn.open(server, port, false);
if(!conn.connected())
{
if (!conn.connected()){
mayExecute = false;
return;
}
@ -335,9 +332,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
DEBUG_MSG(DLVL_INFO, "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());
if (!conn) {
conn.open(server, port, false);
}
if (!conn){conn.open(server, port, false);}
pos = 0;
HTTP::Parser H;
@ -345,7 +340,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SendRequest(conn);
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))) {}
while (conn && (!conn.spool() || !H.Read(conn))){}
H.BuildResponse();
currentPos;
@ -356,9 +351,9 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
// 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)) {
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") {
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
@ -372,7 +367,7 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++) {
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);
@ -385,75 +380,74 @@ dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++) { // get init url
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());
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ",
streamData[i].adaptationSet, streamData[i].trackID, streamData[i].initURL.c_str());
}
}
bool dashAnalyser::hasInput() {
bool dashAnalyser::hasInput(){
return currentPos.size();
}
bool dashAnalyser::packetReady() {
bool dashAnalyser::packetReady(){
return (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (currentPos.size() > 0);
}
dashAnalyser::~dashAnalyser() {
dashAnalyser::~dashAnalyser(){
INFO_MSG("stopped");
}
int dashAnalyser::doAnalyse() {
int dashAnalyser::doAnalyse(){
// 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;
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); }
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);
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...
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()) {
if (!H.body.size()){
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
// break;
return 0;
}
size_t beforeParse = H.body.size();
MP4::Box mp4Data;
bool mdatSeen = false;
while (mp4Data.read(H.body)) {
if (mp4Data.isType("mdat")) { mdatSeen = true; }
while (mp4Data.read(H.body)){
if (mp4Data.isType("mdat")){mdatSeen = true;}
}
if (!mdatSeen) {
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
// break;
return 0;
}
if (H.body.size()) {
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()) {
if (beforeParse == H.body.size()){
// break;
return 0;
}
@ -462,7 +456,7 @@ int dashAnalyser::doAnalyse() {
pos = 1000 * (currentPos.begin()->seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale;
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) {
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos){
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
}
@ -472,18 +466,25 @@ int dashAnalyser::doAnalyse() {
return pos;
}
int main(int argc, char **argv) {
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.addOption(
"detail",
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}"));
"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.addOption("detail",
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", "
"\"default\":2, \"help\":\"Detail level of analysis. \"}"));
conf.parseArgs(argc, argv);
conf.activate();
@ -494,20 +495,27 @@ int main(int argc, char **argv) {
return 0;
}
int main2(int argc, char **argv) {
int main2(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.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://") {
if (url.substr(0, 7) != "http://"){
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
return -1;
}
@ -516,7 +524,7 @@ int main2(int argc, char **argv) {
std::string server = url.substr(0, url.find('/'));
url = url.substr(url.find('/'));
if (server.find(':') != std::string::npos) {
if (server.find(':') != std::string::npos){
port = atoi(server.substr(server.find(':') + 1).c_str());
server = server.substr(0, server.find(':'));
}
@ -530,14 +538,14 @@ int main2(int argc, char **argv) {
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); }
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))) {}
while (conn && (!conn.spool() || !H.Read(conn))){}
H.BuildResponse();
std::set<seekPos> currentPos;
@ -548,9 +556,9 @@ int main2(int argc, char **argv) {
// 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)) {
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") {
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
@ -563,7 +571,7 @@ int main2(int argc, char **argv) {
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++) {
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);
@ -576,67 +584,69 @@ int main2(int argc, char **argv) {
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++) { // get init url
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());
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)) {
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?
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;
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); }
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);
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...
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()) {
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; }
while (mp4Data.read(H.body)){
if (mp4Data.isType("mdat")){mdatSeen = true;}
}
if (!mdatSeen) {
if (!mdatSeen){
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
break;
}
if (H.body.size()) {
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; }
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) {
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") {
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}

View file

@ -1,5 +1,5 @@
#include <mist/config.h>
#include "analyser.h"
#include <mist/config.h>
#include <set>
struct StreamData{
@ -7,72 +7,62 @@ struct StreamData{
std::string media;
std::string initialization;
std::string initURL;
long trackID;
long trackID;
unsigned int adaptationSet;
unsigned char trackType;
unsigned char trackType;
};
struct seekPos {
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)) {
bool operator<(const seekPos &rhs) const{
if ((seekTime * rhs.timeScale) < (rhs.seekTime * timeScale)){
return true;
} else {
if ( (seekTime*rhs.timeScale) == (rhs.seekTime*timeScale)){
}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;
}
}
}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
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;
};
class dashAnalyser : public analysers
{
public:
dashAnalyser(Util::Config config);
~dashAnalyser();
bool packetReady();
void PreProcessing();
//int Analyse();
int doAnalyse();
// void doValidate();
bool hasInput();
void PostProcessing();
private:
unsigned int port;
std::string url;
std::string server;
long long int startTime;
long long int abortTime;
Socket::Connection conn;
std::string urlPrependStuff;
unsigned int pos;
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
class dashAnalyser : public analysers{
public:
dashAnalyser(Util::Config config);
~dashAnalyser();
bool packetReady();
void PreProcessing();
// int Analyse();
int doAnalyse();
// void doValidate();
bool hasInput();
void PostProcessing();
private:
unsigned int port;
std::string url;
std::string server;
long long int startTime;
long long int abortTime;
Socket::Connection conn;
std::string urlPrependStuff;
unsigned int pos;
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
};

View file

@ -1,6 +1,6 @@
#include "analyser_dtsc.h"
#include <mist/h264.h>
#include <iomanip>
#include <mist/h264.h>
void AnalyserDTSC::init(Util::Config &conf){
Analyser::init(conf);
@ -29,7 +29,7 @@ bool AnalyserDTSC::parsePacket(){
if (!conn.spool()){Util::sleep(50);}
}
std::string dataBuf = conn.Received().remove(conn.Received().bytes(0xFFFFFFFFul));
DTSC::Scan S((char*)dataBuf.data(), dataBuf.size());
DTSC::Scan S((char *)dataBuf.data(), dataBuf.size());
std::cout << S.toPrettyString() << std::endl;
return false;
}
@ -57,7 +57,7 @@ bool AnalyserDTSC::parsePacket(){
<< "): " << P.getScan().toPrettyString() << std::endl;
}
if (detail >= 8){
char * payDat;
char *payDat;
size_t payLen;
P.getString("data", payDat, payLen);
for (uint64_t i = 0; i < payLen; ++i){
@ -70,7 +70,8 @@ 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))));
DTSC::Meta(P).toPrettyString(
std::cout, 0, (detail == 3 ? 0 : (detail == 4 ? 0x01 : (detail == 5 ? 0x03 : 0x07))));
}
if (detail == 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;}
if (detail == 1){
@ -79,8 +80,7 @@ bool AnalyserDTSC::parsePacket(){
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++){
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin(); it != M.tracks.end(); it++){
JSON::Value track;
track["kbits"] = (uint64_t)((double)it->second.bps * 8 / 1024);
track["codec"] = it->second.codec;
@ -90,8 +90,7 @@ bool AnalyserDTSC::parsePacket(){
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++){
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();}
@ -113,12 +112,10 @@ 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 " << it->second.codec << " frame)! ";
}
if (shrtest_cnt < 6){
issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec
<< " frames in key)! ";
issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec << " frames in key)! ";
}
if (it->second.codec == "AAC"){hasAAC = true;}
if (it->second.codec == "H264"){hasH264 = true;}
@ -156,4 +153,3 @@ bool AnalyserDTSC::parsePacket(){
totalBytes += P.getDataLen();
return true;
}

View file

@ -14,4 +14,3 @@ private:
Socket::Connection conn;
uint64_t totalBytes;
};

View file

@ -17,7 +17,7 @@ bool AnalyserEBML::parsePacket(){
// Read in smart bursts until we have enough data
while (isOpen() && dataBuffer.size() < neededBytes()){
uint64_t needed = neededBytes();
if (needed > 1024*1024){
if (needed > 1024 * 1024){
dataBuffer.erase(0, 1);
continue;
}
@ -38,38 +38,29 @@ bool AnalyserEBML::parsePacket(){
HIGH_MSG("Read an element at position %d", prePos);
if (detail >= 2){std::cout << E.toPrettyString(depthStash.size() * 2, detail);}
switch (E.getID()){
case EBML::EID_SEGMENT:
segmentOffset = prePos + E.getHeaderLen();
std::cout << "[OFFSET INFORMATION] Segment offset is " << segmentOffset << std::endl;
break;
case EBML::EID_CLUSTER:
std::cout << "[OFFSET INFORMATION] Cluster at " << (prePos-segmentOffset) << std::endl;
break;
case EBML::EID_SEEKID:
lastSeekId = E.getValUInt();
break;
case EBML::EID_SEEKPOSITION:
lastSeekPos = E.getValUInt();
break;
case EBML::EID_INFO:
case EBML::EID_TRACKS:
case EBML::EID_TAGS:
case EBML::EID_CUES:
{
uint32_t sID = E.getID();
std::cout << "Encountered " << sID << std::endl;
if (seekChecks.count(sID)){
std::cout << "[OFFSET INFORMATION] Segment " << EBML::Element::getIDString(sID) << " is at " << prePos << ", expected was " << seekChecks[sID] << std::endl;
}
}
break;
}
if (depthStash.size()){
depthStash.front() -= E.getOuterLen();
}
if (E.getType() == EBML::ELEM_MASTER){
depthStash.push_front(E.getPayloadLen());
case EBML::EID_SEGMENT:
segmentOffset = prePos + E.getHeaderLen();
std::cout << "[OFFSET INFORMATION] Segment offset is " << segmentOffset << std::endl;
break;
case EBML::EID_CLUSTER:
std::cout << "[OFFSET INFORMATION] Cluster at " << (prePos - segmentOffset) << std::endl;
break;
case EBML::EID_SEEKID: lastSeekId = E.getValUInt(); break;
case EBML::EID_SEEKPOSITION: lastSeekPos = E.getValUInt(); break;
case EBML::EID_INFO:
case EBML::EID_TRACKS:
case EBML::EID_TAGS:
case EBML::EID_CUES:{
uint32_t sID = E.getID();
std::cout << "Encountered " << sID << std::endl;
if (seekChecks.count(sID)){
std::cout << "[OFFSET INFORMATION] Segment " << EBML::Element::getIDString(sID) << " is at "
<< prePos << ", expected was " << seekChecks[sID] << std::endl;
}
}break;
}
if (depthStash.size()){depthStash.front() -= E.getOuterLen();}
if (E.getType() == EBML::ELEM_MASTER){depthStash.push_front(E.getPayloadLen());}
while (depthStash.size() && !depthStash.front()){
depthStash.pop_front();
if (lastSeekId){
@ -86,8 +77,9 @@ bool AnalyserEBML::parsePacket(){
}
}
}
seekChecks[lastSeekId] = segmentOffset+lastSeekPos;
std::cout << "[OFFSET INFORMATION] Segment offset for " << EBML::Element::getIDString(lastSeekId) << " (" << lastSeekId << ") is " << (segmentOffset+lastSeekPos) << std::endl;
seekChecks[lastSeekId] = segmentOffset + lastSeekPos;
std::cout << "[OFFSET INFORMATION] Segment offset for " << EBML::Element::getIDString(lastSeekId)
<< " (" << lastSeekId << ") is " << (segmentOffset + lastSeekPos) << std::endl;
lastSeekId = 0;
lastSeekPos = 0;
}
@ -101,4 +93,3 @@ bool AnalyserEBML::parsePacket(){
uint64_t AnalyserEBML::neededBytes(){
return EBML::Element::needBytes(dataBuffer.data(), dataBuffer.size(), true);
}

View file

@ -16,6 +16,5 @@ private:
uint32_t lastSeekId;
uint64_t lastSeekPos;
std::map<uint32_t, uint64_t> seekChecks;
std::deque<uint64_t> depthStash;///<Contains bytes to read to go up a level in the element depth.
std::deque<uint64_t> depthStash; ///< Contains bytes to read to go up a level in the element depth.
};

View file

@ -37,4 +37,3 @@ bool AnalyserFLV::parsePacket(){
mediaTime = flvData.tagTime();
return true;
}

View file

@ -11,4 +11,3 @@ private:
FLV::Tag flvData;
long long filter;
};

View file

@ -35,7 +35,7 @@ bool AnalyserH264::parsePacket(){
size_t size = 0;
h264::nalUnit *nalPtr;
do {
do{
size = 0;
nalPtr = h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended);
if (nalPtr){
@ -44,8 +44,8 @@ bool AnalyserH264::parsePacket(){
dataBuffer.erase(0, size); // erase the NAL unit we just read
prePos += size;
}
///\TODO update mediaTime with current timestamp
} while(nalPtr);
///\TODO update mediaTime with current timestamp
}while (nalPtr);
if (!nalPtr){
FAIL_MSG("Could not read a NAL unit at position %llu", prePos);
return false;
@ -58,6 +58,5 @@ uint64_t AnalyserH264::neededBytes(){
if (!sizePrepended){return 1024 * 1024;}
// otherwise, buffer the exact size needed
if (dataBuffer.size() < 4){return 4;}
return Bit::btohl(dataBuffer.data())+4;
return Bit::btohl(dataBuffer.data()) + 4;
}

View file

@ -12,4 +12,3 @@ private:
uint64_t neededBytes();
bool sizePrepended;
};

View file

@ -132,4 +132,3 @@ bool AnalyserHLS::parsePacket(){
}
return false;
}

View file

@ -1,7 +1,7 @@
#include "analyser.h"
#include <fstream>
#include <mist/http_parser.h>
#include <mist/downloader.h>
#include <mist/http_parser.h>
class HLSPart{
public:
@ -30,4 +30,3 @@ private:
std::ofstream reconstruct;
HTTP::Downloader DL;
};

View file

@ -42,4 +42,3 @@ uint64_t AnalyserMP4::neededBytes(){
size += ntohl(((int *)mp4Buffer.data())[3]);
return size;
}

View file

@ -14,4 +14,3 @@ private:
uint64_t curPos;
uint64_t prePos;
};

View file

@ -36,9 +36,7 @@ bool AnalyserOGG::parsePacket(){
}
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
if (detail >= 2){
std::cout << " Theora data" << std::endl;
}
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++){
@ -46,33 +44,27 @@ bool AnalyserOGG::parsePacket(){
if (tmpHeader.isHeader()){
if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();}
}else{
if (!(oggPage.getHeaderType() == OGG::Continued) &&
tmpHeader.getFTYPE() == 0){// if keyframe
if (!(oggPage.getHeaderType() == OGG::Continued) && tmpHeader.getFTYPE() == 0){// if keyframe
if (detail >= 3){
std::cout << "keyframe " << keyCount << " has " << numParts << " parts and granule " << (oggPage.getGranulePosition() >> kfgshift) << std::endl;
std::cout << "keyframe " << keyCount << " has " << numParts << " parts and granule "
<< (oggPage.getGranulePosition() >> kfgshift) << std::endl;
}
numParts = 0;
keyCount++;
}
if (oggPage.getHeaderType() != OGG::Continued || i){numParts++;}
}
if (detail >= 2){
std::cout << tmpHeader.toPrettyString(4);
}
if (detail >= 2){std::cout << tmpHeader.toPrettyString(4);}
}
}else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){
if (detail >= 2){
std::cout << " Vorbis data" << std::endl;
}
if (detail >= 2){std::cout << " Vorbis data" << std::endl;}
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
int len = oggPage.getAllSegments()[i].size();
vorbis::header tmpHeader((char *)oggPage.getSegment(i), len);
if (tmpHeader.isHeader() && detail >= 2){std::cout << tmpHeader.toPrettyString(4);}
}
}else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Opus"){
if (detail >= 2){
std::cout << " Opus data" << std::endl;
}
if (detail >= 2){std::cout << " Opus data" << std::endl;}
int offset = 0;
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
int len = oggPage.getAllSegments()[i].size();
@ -83,8 +75,7 @@ bool AnalyserOGG::parsePacket(){
std::cout << " Channels: " << (int)(part[9]) << std::endl;
std::cout << " Pre-skip: " << (int)(part[10] + (part[11] << 8)) << std::endl;
std::cout << " Orig. sample rate: "
<< (int)(part[12] + (part[13] << 8) + (part[14] << 16) + (part[15] << 24))
<< std::endl;
<< (int)(part[12] + (part[13] << 8) + (part[14] << 16) + (part[15] << 24)) << std::endl;
std::cout << " Gain: " << (int)(part[16] + (part[17] << 8)) << std::endl;
std::cout << " Channel map: " << (int)(part[18]) << std::endl;
if (part[18] > 0){
@ -96,8 +87,7 @@ bool AnalyserOGG::parsePacket(){
unsigned int vendor_len = part[8] + (part[9] << 8) + (part[10] << 16) + (part[11] << 24);
std::cout << " Vendor: " << std::string(part + 12, vendor_len) << std::endl;
const char *str_data = part + 12 + vendor_len;
unsigned int strings =
str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
unsigned int strings = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
std::cout << " Tags: (" << strings << ")" << std::endl;
str_data += 4;
for (unsigned int j = 0; j < strings; j++){
@ -109,13 +99,10 @@ bool AnalyserOGG::parsePacket(){
}
}
}else{
if (detail >= 4){
std::cout << " " << Opus::Opus_prettyPacket(part, len) << std::endl;
}
if (detail >= 4){std::cout << " " << Opus::Opus_prettyPacket(part, len) << std::endl;}
}
offset += len;
}
}
return true;
}

View file

@ -13,4 +13,3 @@ private:
OGG::Page oggPage;
int kfgshift;
};

View file

@ -30,7 +30,7 @@ bool AnalyserRIFF::parsePacket(){
if (detail >= 2){C.toPrettyString(std::cout);}
///\TODO update mediaTime with the current timestamp
if (C){
dataBuffer.erase(0, C.getPayloadSize()+8);
dataBuffer.erase(0, C.getPayloadSize() + 8);
return true;
}
return false;
@ -39,6 +39,5 @@ bool AnalyserRIFF::parsePacket(){
/// Calculates how many bytes we need to read a whole box.
uint64_t AnalyserRIFF::neededBytes(){
if (dataBuffer.size() < 8){return 8;}
return RIFF::Chunk(dataBuffer.data()).getPayloadSize()+8;
return RIFF::Chunk(dataBuffer.data()).getPayloadSize() + 8;
}

View file

@ -12,4 +12,3 @@ private:
uint64_t curPos;
uint64_t prePos;
};

View file

@ -25,7 +25,7 @@ AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){
}
}
bool AnalyserRTMP::open(const std::string & filename){
bool AnalyserRTMP::open(const std::string &filename){
if (!Analyser::open(filename)){return false;}
// Skip the 3073 byte handshake - there is no (truly) useful data in this.
MEDIUM_MSG("Skipping handshake...");
@ -63,8 +63,7 @@ bool AnalyserRTMP::parsePacket(){
// We now know for sure that we've parsed a packet
DETAIL_HI("Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u",
next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id,
next.msg_stream_id);
next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id, next.msg_stream_id);
switch (next.msg_type_id){
case 0: // does not exist
DETAIL_LOW("Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i", read_in - strbuf.size(),
@ -91,12 +90,10 @@ bool AnalyserRTMP::parsePacket(){
ntohl(*(unsigned int *)(next.data.c_str() + 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 %u", ntohl(*(unsigned int *)(next.data.c_str() + 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 %u", ntohl(*(unsigned int *)(next.data.c_str() + 2)));
break;
case 3:
DETAIL_MED("CTRL: User control message: setbufferlen %u",
@ -183,4 +180,3 @@ bool AnalyserRTMP::parsePacket(){
}// switch for type of chunk
return true;
}

View file

@ -5,18 +5,17 @@
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
Socket::Buffer strbuf;///< Internal buffer from where 'next' is filled
AMF::Object amfdata;///< Last read AMF object
AMF::Object3 amf3data;///<Last read AMF3 object
std::ofstream reconstruct;///< If reconstructing, a valid file handle
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
Socket::Buffer strbuf; ///< Internal buffer from where 'next' is filled
AMF::Object amfdata; ///< Last read AMF object
AMF::Object3 amf3data; ///< Last read AMF3 object
std::ofstream reconstruct; ///< If reconstructing, a valid file handle
public:
AnalyserRTMP(Util::Config & conf);
static void init(Util::Config & conf);
AnalyserRTMP(Util::Config &conf);
static void init(Util::Config &conf);
bool parsePacket();
virtual bool open(const std::string &filename);
};

View file

@ -1,23 +1,23 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <string.h>
#include <vector>
#include <sstream>
#include <mist/socket.h>
#include <iostream>
#include <mist/config.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
#include <mist/rtp.h>
#include <mist/socket.h>
#include <sstream>
#include <string.h>
#include <string>
#include <vector>
//rtsp://krabs:1935/vod/gear1.mp4
// rtsp://krabs:1935/vod/gear1.mp4
namespace Analysers {
namespace Analysers{
int analyseRTP(){
Socket::Connection conn("localhost", 554, true);
//Socket::Connection conn("krabs", 1935, true);
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
// Socket::Connection conn("krabs", 1935, true);
HTTP::Parser HTTP_R, HTTP_S; // HTTP Receiver en HTTP Sender.
int step = 0;
/*1 = sent describe
2 = recd describe
@ -28,61 +28,62 @@ namespace Analysers {
std::vector<Socket::UDPConnection> connections;
unsigned int trackIt = 0;
while (conn.connected()){
// std::cerr << "loopy" << std::endl;
if(step == 0){
// std::cerr << "loopy" << std::endl;
if (step == 0){
HTTP_S.protocol = "RTSP/1.0";
HTTP_S.method = "DESCRIBE";
//rtsp://krabs:1935/vod/gear1.mp4
//rtsp://localhost/g1
// rtsp://krabs:1935/vod/gear1.mp4
// rtsp://localhost/g1
HTTP_S.url = "rtsp://localhost/steers";
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("CSeq",1);
HTTP_S.SendRequest(conn);
// HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("CSeq", 1);
HTTP_S.SendRequest(conn);
step++;
}else if(step == 2){
std::cerr <<"setup " << tracks[trackIt] << std::endl;
}else if (step == 2){
std::cerr << "setup " << tracks[trackIt] << std::endl;
HTTP_S.method = "SETUP";
HTTP_S.url = "rtsp://localhost/steers/" + tracks[trackIt];
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4/" + tracks[trackIt];
HTTP_S.SetHeader("CSeq",2+trackIt);
// HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4/" + tracks[trackIt];
HTTP_S.SetHeader("CSeq", 2 + trackIt);
std::stringstream ss;
ss << "RTP/steersVP;unicast;client_port="<< 20000 + 2*trackIt<<"-"<< 20001 + 2*trackIt;
HTTP_S.SetHeader("Transport",ss.str());//make client ports, 4200 + 2*offset
ss << "RTP/steersVP;unicast;client_port=" << 20000 + 2 * trackIt << "-" << 20001 + 2 * trackIt;
HTTP_S.SetHeader("Transport", ss.str()); // make client ports, 4200 + 2*offset
trackIt++;
step++;
HTTP_S.SendRequest(conn);
std::cerr << "step " << step << "/\\"<< ss.str()<<std::endl;
}else if(step == 4){
std::cerr << "step " << step << "/\\" << ss.str() << std::endl;
}else if (step == 4){
std::cerr << "Play!!!1" << std::endl;
HTTP_S.method = "PLAY";
HTTP_S.url = "rtsp://localhost/steers";
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("Range","npt=0.000-");
// HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("Range", "npt=0.000-");
HTTP_S.SendRequest(conn);
step++;
std::cerr << "step for play.." << step << std::endl;
}
if (conn.Received().size() || conn.spool()){
}
if (conn.Received().size() || conn.spool()){
if (HTTP_R.Read(conn)){
if(step == 1){
std::cerr << "recvd desc" << std::endl;
for(size_t ml = HTTP_R.body.find("a=control:",HTTP_R.body.find("m=")); ml != std::string::npos; ml = HTTP_R.body.find("a=control:",ml+1)){
if (step == 1){
std::cerr << "recvd desc" << std::endl;
for (size_t ml = HTTP_R.body.find("a=control:", HTTP_R.body.find("m="));
ml != std::string::npos; ml = HTTP_R.body.find("a=control:", ml + 1)){
std::cerr << "found trekk" << std::endl;
tracks.push_back(HTTP_R.body.substr(ml+10,HTTP_R.body.find_first_of("\r\n",ml)-(ml+10)));
tracks.push_back(HTTP_R.body.substr(ml + 10, HTTP_R.body.find_first_of("\r\n", ml) - (ml + 10)));
connections.push_back(Socket::UDPConnection());
}
for(unsigned int x = 0; x < connections.size();x++){
connections[x].SetDestination("127.0.0.1",666);
connections[x].bind(20000+2*x);
for (unsigned int x = 0; x < connections.size(); x++){
connections[x].SetDestination("127.0.0.1", 666);
connections[x].bind(20000 + 2 * x);
connections[x].setBlocking(true);
}
step++;
}else if(step == 3){
}else if (step == 3){
std::cerr << "recvd setup" << std::endl;
std::cerr << "trackIt: " << trackIt << " size " << tracks.size() << std::endl;
if(trackIt < tracks.size())
if (trackIt < tracks.size())
step--;
else
step++;
@ -91,12 +92,12 @@ namespace Analysers {
HTTP_R.Clean();
}
}//!
if(step == 5){
if (step == 5){
for(unsigned int cx = 0; cx < connections.size(); cx++){
// std::cerr <<"PLAY MF" << std::endl;
if(connections[cx].Receive()){
RTP::Packet* pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
for (unsigned int cx = 0; cx < connections.size(); cx++){
// std::cerr <<"PLAY MF" << std::endl;
if (connections[cx].Receive()){
RTP::Packet *pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
/*std::cout << "Version = " << pakketje->getVersion() << std::endl;
std::cout << "Padding = " << pakketje->getPadding() << std::endl;
std::cout << "Extension = " << pakketje->getExtension() << std::endl;
@ -106,103 +107,95 @@ namespace Analysers {
std::cout << "Sequence = " << pakketje->getSequence() << std::endl;
std::cout << "Timestamp = " << pakketje->getTimeStamp() << std::endl;
std::cout << "SSRC = " << pakketje->getSSRC() << std::endl;
std::cout << "datalen: " << connections[cx].data_len << std::endl;
std::cout << "datalen: " << connections[cx].data_len << std::endl;
std::cout << "payload:" << std::endl;*/
if(pakketje->getPayloadType() == 97){
if (pakketje->getPayloadType() == 97){
int h264type = (int)(connections[cx].data[12] & 0x1f);
std::cout << h264type << " - ";
if(h264type == 0){
if (h264type == 0){
std::cout << "unspecified - ";
}else if(h264type == 1){
}else if (h264type == 1){
std::cout << "Coded slice of a non-IDR picture - ";
}else if(h264type == 2){
}else if (h264type == 2){
std::cout << "Coded slice data partition A - ";
}else if(h264type == 3){
}else if (h264type == 3){
std::cout << "Coded slice data partition B - ";
}else if(h264type == 4){
}else if (h264type == 4){
std::cout << "Coded slice data partition C - ";
}else if(h264type == 5){
}else if (h264type == 5){
std::cout << "Coded slice of an IDR picture - ";
}else if(h264type == 6){
}else if (h264type == 6){
std::cout << "Supplemental enhancement information (SEI) - ";
}else if(h264type == 7){
}else if (h264type == 7){
std::cout << "Sequence parameter set - ";
}else if(h264type == 8){
}else if (h264type == 8){
std::cout << "Picture parameter set - ";
}else if(h264type == 9){
}else if (h264type == 9){
std::cout << "Access unit delimiter - ";
}else if(h264type == 10){
}else if (h264type == 10){
std::cout << "End of sequence - ";
}else if(h264type == 11){
}else if (h264type == 11){
std::cout << "End of stream - ";
}else if(h264type == 12){
}else if (h264type == 12){
std::cout << "Filler data - ";
}else if(h264type == 13){
}else if (h264type == 13){
std::cout << "Sequence parameter set extension - ";
}else if(h264type == 14){
}else if (h264type == 14){
std::cout << "Prefix NAL unit - ";
}else if(h264type == 15){
}else if (h264type == 15){
std::cout << "Subset sequence parameter set - ";
}else if(h264type == 16){
}else if (h264type == 16){
std::cout << "Reserved - ";
}else if(h264type == 17){
}else if (h264type == 17){
std::cout << "Reserved - ";
}else if(h264type == 18){
}else if (h264type == 18){
std::cout << "Reserved - ";
}else if(h264type == 19){
}else if (h264type == 19){
std::cout << "Coded slice of an auxiliary coded picture without partitioning - ";
}else if(h264type == 20){
}else if (h264type == 20){
std::cout << "Coded slice extension - ";
}else if(h264type == 21){
}else if (h264type == 21){
std::cout << "Reserved - ";
}else if(h264type == 22){
}else if (h264type == 22){
std::cout << "Reserved - ";
}else if(h264type == 23){
}else if (h264type == 23){
std::cout << "Reserved - ";
}else if(h264type == 24){
}else if (h264type == 24){
std::cout << "stap a - ";
}else if(h264type == 25){
}else if (h264type == 25){
std::cout << "stap b - ";
}else if(h264type == 26){
}else if (h264type == 26){
std::cout << "mtap16 - ";
}else if(h264type == 27){
}else if (h264type == 27){
std::cout << "mtap24 - ";
}else if(h264type == 28){
}else if (h264type == 28){
std::cout << "fu a - ";
}else if(h264type == 29){
}else if (h264type == 29){
std::cout << "fu b - ";
}else if(h264type == 30){
}else if (h264type == 30){
std::cout << "Unspecified - ";
}else if(h264type == 31){
}else if (h264type == 31){
std::cout << "Unspecified - ";
}
for(unsigned int i = 13 ; i < connections[cx].data_len;i++){
std::cout << std::hex <<std::setw(2) << std::setfill('0') << (int)connections[cx].data[i]<< std::dec;
for (unsigned int i = 13; i < connections[cx].data_len; i++){
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< (int)connections[cx].data[i] << std::dec;
}
std::cout << std::endl<<std::endl;
std::cout << std::endl << std::endl;
}
delete pakketje;
}
delete pakketje;
}
}
}
}
return 666;
}
}// namespace Analysers
}
int main(int argc, char ** argv){
int main(int argc, char **argv){
Util::Config conf = Util::Config(argv[0]);
conf.parseArgs(argc, argv);
return Analysers::analyseRTP();

View file

@ -14,7 +14,8 @@ 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, pkt.getFlag("keyframe")?"keyframe ":"", pkt.getTrackId(),
DETAIL_MED("Received %ub %sfor track %lu (%s) @ %llums", dataSize,
pkt.getFlag("keyframe") ? "keyframe " : "", pkt.getTrackId(),
myMeta.tracks[pkt.getTrackId()].getIdentifier().c_str(), pkt.getTime());
if (detail >= 8){
for (uint32_t i = 0; i < dataSize; ++i){
@ -99,9 +100,7 @@ bool AnalyserRTSP::parsePacket(){
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 (trackNo){sdpState.tracks[trackNo].sorter.rtpSeq = pkt.getSequence();}
if (detail >= 10){
char *pl = pkt.getPayload();
@ -120,4 +119,3 @@ bool AnalyserRTSP::parsePacket(){
}while (isOpen());
return false;
}

View file

@ -21,4 +21,3 @@ private:
DTSC::Meta myMeta;
SDP::State sdpState;
};

View file

@ -1,5 +1,5 @@
#include "analyser_ts.h"
#include "analyser.h"
#include "analyser_ts.h"
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
@ -50,8 +50,7 @@ bool AnalyserTS::parsePacket(){
bytes += 188;
if (!packet.FromPointer(packetPtr)){return false;}
if (detail){
if (packet.getUnitStart() && payloads.count(packet.getPID()) &&
payloads[packet.getPID()] != ""){
if (packet.getUnitStart() && payloads.count(packet.getPID()) && payloads[packet.getPID()] != ""){
if ((detail & 1) && (!pidOnly || packet.getPID() == pidOnly)){
std::cout << printPES(payloads[packet.getPID()], packet.getPID());
}
@ -75,8 +74,7 @@ bool AnalyserTS::parsePacket(){
}
AnalyserTS::~AnalyserTS(){
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin();
it != payloads.end(); it++){
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
if ((detail & 1) && (!pidOnly || it->first == pidOnly)){
std::cout << printPES(it->second, it->first);
}
@ -98,8 +96,7 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
}
if (!known){res << " [Unknown stream ID: " << (int)d[3] << "]";}
if (d[0] != 0 || d[1] != 0 || d[2] != 1){
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2]
<< " ]";
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]";
}
unsigned int padding = 0;
if (known){
@ -164,7 +161,8 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
}
}
if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){
res << " [Size " << (((int)d[4]) << 8 | d[5]) + 6 << " => " << (d.size()) << "] [Payload " << (d.size() - 9 - headSize) << "]";
res << " [Size " << (((int)d[4]) << 8 | d[5]) + 6 << " => " << (d.size()) << "] [Payload "
<< (d.size() - 9 - headSize) << "]";
}else{
res << " [Size " << (d.size()) << "] [Payload " << (d.size() - 9 - headSize) << "]";
}
@ -185,4 +183,3 @@ std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
}
return res.str();
}

View file

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

View file

@ -2,409 +2,407 @@
/// 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/timing.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <iostream>
#include <fstream>
#include <set>
#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;
long trackID;
unsigned int adaptationSet;
unsigned char trackType;
unsigned char trackType;
};
StreamData tempSD; //temp global
StreamData tempSD; // temp global
///\brief another simple structure used for ordering byte seek positions.
struct seekPos {
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)) {
bool operator<(const seekPos &rhs) const{
if ((seekTime * rhs.timeScale) < (rhs.seekTime * timeScale)){
return true;
} else {
if ( (seekTime*rhs.timeScale) == (rhs.seekTime*timeScale)){
}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;
}
}
}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
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
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.
}
blockEnd++; //include delim
//DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
// 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) );
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());
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() );
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;
}
// 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"/>
}
blockEnd=data.find("/" + name+ ">", blockStart);
if(blockEnd==std::string::npos){
blockEnd=data.find("/>", blockStart);
if(blockEnd==std::string::npos){
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 ... />
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);
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after />
}else{
blockEnd += name.size() + 2; // position after /name>
}
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true;
}
bool parseAdaptationSet(std::string & data, std::set<seekPos> &currentPos){
//DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset =0;
size_t blockStart, blockEnd;
tempSD.trackType=OTHER;
//get value: mimetype //todo: handle this!
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos){
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if(!getString(data,"mimeType", mimeType)){ //get first occurence of mimeType. --> this will break if multiple mimetypes should be read from this block because no offset is provided. solution: use this on a substring containing the desired information.
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
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){
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)){
// 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());
// 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
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)){
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
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");
getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media);
getString(block, "initialization", tempSD.initialization);
if(!getDelimBlock(tempSD.media,"Time",tmpBlockStart,tmpBlockEnd, "$")){
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s",tempSD.media.c_str());
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.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");
}
tempSD.media.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)){
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;
}
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){
while (1){
if (!getBlock(block2, "S", offset, blockStart, blockEnd)){
if (numS == 0){
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
return false;
} else {
}else{
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
return true; //break; //escape from while loop (to return true)
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.
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");
// 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;
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.
}
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration
offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant.
}
return true;
}
bool parseXML(std::string & body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData){
//for all adaptation sets
//representation ID
int numAdaptationSet=0;
size_t currentOffset=0;
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData){
// for all adaptation sets
// representation ID
int numAdaptationSet = 0;
size_t currentOffset = 0;
size_t adaptationSetStart;
size_t adaptationSetEnd;
//DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while(getBlock(body,"AdaptationSet",currentOffset, adaptationSetStart, adaptationSetEnd)){
tempSD.adaptationSet=numAdaptationSet;
numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ",adaptationSetStart,adaptationSetEnd,(adaptationSetEnd-adaptationSetStart));
//get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet=body.substr(adaptationSetStart,(adaptationSetEnd-adaptationSetStart));
//function was verified: output as expected.
if(!parseAdaptationSet(adaptationSet, currentPos)){
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); //this also happens in the case of OTHER mimetype. in that case it might be desirable to continue searching for valid data instead of quitting.
return false;
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.
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){
if (numAdaptationSet == 0){
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
return false;
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
return true;
}
int main(int argc, char ** argv) {
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.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://") {
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
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) {
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:
// 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);
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);
}
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))) {}
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") {
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++){
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);
@ -414,77 +412,74 @@ int main(int argc, char ** argv) {
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
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());
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);
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.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?
// 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());
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;
}
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());
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);
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") {
if (conf.getString("mode") == "validate"){
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return 0;
}

View file

@ -15,4 +15,3 @@ int main(int argc, char *argv[]){
return 2;
}
}

View file

@ -1,7 +1,6 @@
/// \file controller.cpp
/// Contains all code for the controller executable.
#include <mist/util.h>
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
@ -10,6 +9,7 @@
#include "controller_storage.h"
#include "controller_streams.h"
#include <ctime>
#include <fstream> //for ram space check
#include <iostream>
#include <mist/auth.h>
#include <mist/config.h>
@ -21,12 +21,12 @@
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <mist/util.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/statvfs.h> //for shm space check
#include <sys/wait.h>
#include <vector>
#include <sys/statvfs.h> //for shm space check
#include <fstream> //for ram space check
/*LTS-START*/
#include "controller_license.h"
#include "controller_limits.h"
@ -79,8 +79,7 @@ void statusMonitor(void *np){
{
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
// checks online protocols, reports changes to status
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"],
Controller::capabilities)){
if (Controller::CheckProtocols(Controller::Storage["config"]["protocols"], Controller::capabilities)){
Controller::writeProtocols();
}
// checks stream statuses, reports changes to status
@ -181,16 +180,12 @@ int main_loop(int argc, char **argv){
"uplink",
JSON::fromString("{\"default\":\"\", \"arg\":\"string\", \"help\":\"MistSteward uplink host "
"and port.\", \"short\":\"U\", \"long\":\"uplink\"}")); /*LTS*/
Controller::conf.addOption("uplink-name",
JSON::fromString("{\"default\":\"" COMPILED_USERNAME
"\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink username.\", \"short\":\"N\", "
"\"long\":\"uplink-name\"}")); /*LTS*/
Controller::conf.addOption("uplink-pass",
JSON::fromString("{\"default\":\"" COMPILED_PASSWORD
"\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink password.\", \"short\":\"P\", "
"\"long\":\"uplink-pass\"}")); /*LTS*/
Controller::conf.addOption("uplink-name", JSON::fromString("{\"default\":\"" COMPILED_USERNAME "\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink username.\", \"short\":\"N\", "
"\"long\":\"uplink-name\"}")); /*LTS*/
Controller::conf.addOption("uplink-pass", JSON::fromString("{\"default\":\"" COMPILED_PASSWORD "\", \"arg\":\"string\", \"help\":\"MistSteward "
"uplink password.\", \"short\":\"P\", "
"\"long\":\"uplink-pass\"}")); /*LTS*/
Controller::conf.addOption(
"prometheus",
JSON::fromString("{\"long\":\"prometheus\", \"short\":\"S\", \"arg\":\"string\" "
@ -199,8 +194,7 @@ int main_loop(int argc, char **argv){
Controller::conf.parseArgs(argc, argv);
if (Controller::conf.getString("logfile") != ""){
// open logfile, dup stdout to logfile
int output =
open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
int output = open(Controller::conf.getString("logfile").c_str(), O_APPEND | O_CREAT | O_WRONLY, S_IRWXU);
if (output < 0){
DEBUG_MSG(DLVL_ERROR, "Could not redirect output to %s: %s",
Controller::conf.getString("logfile").c_str(), strerror(errno));
@ -225,7 +219,7 @@ int main_loop(int argc, char **argv){
Controller::Storage = JSON::fromFile(Controller::conf.getString("configFile"));
{// spawn thread that reads stderr of process
std::string logPipe = Util::getTmpFolder()+"MstLog";
std::string logPipe = Util::getTmpFolder() + "MstLog";
if (mkfifo(logPipe.c_str(), S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH) != 0){
if (errno != EEXIST){
ERROR_MSG("Could not create log message pipe %s: %s", logPipe.c_str(), strerror(errno));
@ -233,34 +227,37 @@ int main_loop(int argc, char **argv){
}
int inFD = -1;
if ((inFD = open(logPipe.c_str(), O_RDONLY | O_NONBLOCK)) == -1){
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe", logPipe.c_str(), strerror(errno));
ERROR_MSG("Could not open log message pipe %s: %s; falling back to unnamed pipe",
logPipe.c_str(), strerror(errno));
int pipeErr[2];
if (pipe(pipeErr) >= 0){
dup2(pipeErr[1], STDERR_FILENO); // cause stderr to write to the pipe
close(pipeErr[1]); // close the unneeded pipe file descriptor
//Start reading log messages from the unnamed pipe
Util::Procs::socketList.insert(pipeErr[0]); //Mark this FD as needing to be closed before forking
// Start reading log messages from the unnamed pipe
Util::Procs::socketList.insert(pipeErr[0]); // Mark this FD as needing to be closed before forking
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + pipeErr[0]));
msghandler.detach();
}
}else{
//Set the read end to blocking mode
// Set the read end to blocking mode
int inFDflags = fcntl(inFD, F_GETFL, 0);
fcntl(inFD, F_SETFL, inFDflags & (~O_NONBLOCK));
//Start reading log messages from the named pipe
Util::Procs::socketList.insert(inFD); //Mark this FD as needing to be closed before forking
// Start reading log messages from the named pipe
Util::Procs::socketList.insert(inFD); // Mark this FD as needing to be closed before forking
tthread::thread msghandler(Controller::handleMsg, (void *)(((char *)0) + inFD));
msghandler.detach();
//Attempt to open and redirect log messages to named pipe
// Attempt to open and redirect log messages to named pipe
int outFD = -1;
if ((outFD = open(logPipe.c_str(), O_WRONLY)) == -1){
ERROR_MSG("Could not open log message pipe %s for writing! %s; falling back to standard error", logPipe.c_str(), strerror(errno));
ERROR_MSG(
"Could not open log message pipe %s for writing! %s; falling back to standard error",
logPipe.c_str(), strerror(errno));
}else{
dup2(outFD, STDERR_FILENO); // cause stderr to write to the pipe
close(outFD); // close the unneeded pipe file descriptor
}
}
setenv("MIST_CONTROL", "1", 0);//Signal in the environment that the controller handles all children
setenv("MIST_CONTROL", "1", 0); // Signal in the environment that the controller handles all children
}
if (Controller::conf.getOption("debug", true).size() > 1){
@ -277,12 +274,10 @@ int main_loop(int argc, char **argv){
Controller::Storage["config"]["controller"]["port"];
}
if (Controller::Storage["config"]["controller"]["interface"]){
Controller::conf.getOption("interface", true)[0u] =
Controller::Storage["config"]["controller"]["interface"];
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
}
if (Controller::Storage["config"]["controller"]["username"]){
Controller::conf.getOption("username", true)[0u] =
Controller::Storage["config"]["controller"]["username"];
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
}
if (Controller::Storage["config"]["controller"].isMember("prometheus")){
if (Controller::Storage["config"]["controller"]["prometheus"]){
@ -313,7 +308,6 @@ int main_loop(int argc, char **argv){
createAccount(Controller::conf.getString("account"));
Controller::conf.activate(); // activate early, so threads aren't killed.
#if !defined(__CYGWIN__) && !defined(_WIN32)
{
uint64_t mem_total = 0, mem_free = 0, mem_bufcache = 0, shm_total = 0, shm_free = 0;
@ -323,8 +317,8 @@ int main_loop(int argc, char **argv){
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !meminfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
@ -341,26 +335,47 @@ int main_loop(int argc, char **argv){
IPC::sharedPage tmpCapa(SHM_CAPA, DEFAULT_CONF_PAGE_SIZE, false, false);
if (tmpCapa.mapped && tmpCapa.handle){
fstatvfs(tmpCapa.handle, &shmd);
shm_free = (shmd.f_bfree*shmd.f_frsize)/1024;
shm_total = (shmd.f_blocks*shmd.f_frsize)/1024;
shm_free = (shmd.f_bfree * shmd.f_frsize) / 1024;
shm_total = (shmd.f_blocks * shmd.f_frsize) / 1024;
}
if (mem_free+mem_bufcache < 1024*1024){
WARN_MSG("You have very little free RAM available (%" PRIu64 " MiB). While Mist will run just fine with this amount, do note that random crashes may occur should you ever run out of free RAM. Please be pro-active and keep an eye on the RAM usage!");
if (mem_free + mem_bufcache < 1024 * 1024){
WARN_MSG("You have very little free RAM available (%" PRIu64
" MiB). While Mist will run just fine with this amount, do note that random crashes "
"may occur should you ever run out of free RAM. Please be pro-active and keep an "
"eye on the RAM usage!");
}
if (shm_free < 1024*1024 && mem_total > 1024*1024*1.12){
WARN_MSG("You have very little shared memory available (%" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_free/1024);
if (shm_free < 1024 * 1024 && mem_total > 1024 * 1024 * 1.12){
WARN_MSG("You have very little shared memory available (%" PRIu64
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
"set to a high value, preferably ~95%% of your total available RAM.",
shm_free / 1024);
if (shm_total == 65536){
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
"m` parameter to your `docker run` command to fix this.",
(uint64_t)(mem_total * 0.95 / 1024));
}else{
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
"remount,size=%" PRIu64
"m /dev/shm` as root. Doing this automatically every boot depends on your "
"distribution: please check your distro's documentation for instructions.",
(uint64_t)(mem_total * 0.95 / 1024));
}
}else if (shm_total <= mem_total/2){
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64 " MiB). Mist heavily relies on shared memory: please ensure your shared memory is set to a high value, preferably ~95%% of your total available RAM.", shm_total/1024, mem_total/1024);
}else if (shm_total <= mem_total / 2){
WARN_MSG("Your shared memory is half or less of your RAM (%" PRIu64 " / %" PRIu64
" MiB). Mist heavily relies on shared memory: please ensure your shared memory is "
"set to a high value, preferably ~95%% of your total available RAM.",
shm_total / 1024, mem_total / 1024);
if (shm_total == 65536){
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64 "m` parameter to your `docker run` command to fix this.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: If you are using docker, e.g. add the `--shm-size=%" PRIu64
"m` parameter to your `docker run` command to fix this.",
(uint64_t)(mem_total * 0.95 / 1024));
}else{
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o remount,size=%" PRIu64 "m /dev/shm` as root. Doing this automatically every boot depends on your distribution: please check your distro's documentation for instructions.", (uint64_t)(mem_total*0.95/1024));
WARN_MSG("Tip: In most cases, you can change the shared memory size by running `mount -o "
"remount,size=%" PRIu64
"m /dev/shm` as root. Doing this automatically every boot depends on your "
"distribution: please check your distro's documentation for instructions.",
(uint64_t)(mem_total * 0.95 / 1024));
}
}
}
@ -379,8 +394,7 @@ int main_loop(int argc, char **argv){
case 'y':{
// create account
std::string usr_string = "";
while (!(Controller::Storage.isMember("account") &&
Controller::Storage["account"].size() > 0) &&
while (!(Controller::Storage.isMember("account") && Controller::Storage["account"].size() > 0) &&
Controller::conf.is_active){
std::cout << "Please type in the username, a colon and a password in the following "
"format; username:password"
@ -391,13 +405,11 @@ int main_loop(int argc, char **argv){
createAccount(usr_string);
}
}break;
case 'a':
return 0; // abort bootup
case 'a': return 0; // abort bootup
case 't':{
createAccount("test:test");
if ((Controller::capabilities["connectors"].size()) &&
(!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1)){
// create protocols
jsonForEach(Controller::capabilities["connectors"], it){
@ -414,13 +426,12 @@ int main_loop(int argc, char **argv){
}
// check for protocols
if ((Controller::capabilities["connectors"].size()) &&
(!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
(!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1)){
std::string in_string = "";
while (yna(in_string) == 'x' && Controller::conf.is_active){
std::cout
<< "Protocols not set, do you want to enable default protocols? (y)es, (n)o, (a)bort: ";
std::cout << "Protocols not set, do you want to enable default protocols? (y)es, (n)o, "
"(a)bort: ";
std::cout.flush();
std::getline(std::cin, in_string);
if (yna(in_string) == 'y'){
@ -451,20 +462,17 @@ int main_loop(int argc, char **argv){
web_port + " and follow the instructions.");
}
// check for protocols
if (!Controller::Storage.isMember("config") ||
!Controller::Storage["config"].isMember("protocols") ||
if (!Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") ||
Controller::Storage["config"]["protocols"].size() < 1){
Controller::Log(
"CONF",
"No protocols enabled, remember to set them up through the web interface on port " +
web_port + " or API.");
Controller::Log("CONF", "No protocols enabled, remember to set them up through the web "
"interface on port " +
web_port + " or API.");
}
// check for streams - regardless of logfile setting
if (!Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
Controller::Log(
"CONF",
"No streams configured, remember to set up streams through the web interface on port " +
web_port + " or API.");
Controller::Log("CONF", "No streams configured, remember to set up streams through the web "
"interface on port " +
web_port + " or API.");
}
}
@ -646,4 +654,3 @@ int main(int argc, char **argv){
}
return 0;
}

View file

@ -1,48 +1,46 @@
#include <dirent.h> //for browse API call
#include <sys/stat.h> //for browse API call
#include <fstream>
#include <mist/http_parser.h>
#include <mist/url.h>
#include <mist/auth.h>
#include <mist/stream.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/timing.h>
#include <mist/procs.h>
#include <mist/bitfields.h>
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include "controller_connectors.h"
#include "controller_capabilities.h"
#include "controller_statistics.h"
#include <dirent.h> //for browse API call
#include <fstream>
#include <mist/auth.h>
#include <mist/bitfields.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/procs.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/url.h>
#include <sys/stat.h> //for browse API call
/*LTS-START*/
#include "controller_updater.h"
#include "controller_license.h"
#include "controller_limits.h"
#include "controller_push.h"
#include "controller_license.h"
#include "controller_updater.h"
/*LTS-END*/
/// Returns the challenge string for authentication, given the socket connection.
std::string getChallenge(Socket::Connection & conn){
std::string getChallenge(Socket::Connection &conn){
time_t Time = time(0);
tm tmptime;
tm * TimeInfo = localtime_r( &Time, &tmptime);
tm *TimeInfo = localtime_r(&Time, &tmptime);
std::stringstream Date;
Date << TimeInfo->tm_mday << "-" << TimeInfo->tm_mon << "-" << TimeInfo->tm_year + 1900;
return Secure::md5(Date.str().c_str() + conn.getHost());
}
/// Executes a single Playlist-based API command. Recurses if necessary.
static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines){
static void executePlsCommand(JSON::Value &cmd, std::deque<std::string> &lines){
if (!cmd.isArray() || !cmd.size()){
FAIL_MSG("Not a valid playlist API command: %s", cmd.toString().c_str());
return;
}
if (cmd[0u].isArray()){
jsonForEach(cmd, it){
executePlsCommand(*it, lines);
}
jsonForEach(cmd, it){executePlsCommand(*it, lines);}
return;
}
if (!cmd[0u].isString()){
@ -58,11 +56,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
return;
}
if (cmd[0u].asStringRef() == "remove" && cmd.size() == 2 && cmd[1u].isString()){
const std::string & toRemove = cmd[1u].asStringRef();
const std::string &toRemove = cmd[1u].asStringRef();
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
if ((*it) == toRemove){
(*it) = "";
}
if ((*it) == toRemove){(*it) = "";}
}
return;
}
@ -75,11 +71,9 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
return;
}
if (cmd[0u].asStringRef() == "replace" && cmd.size() == 3 && cmd[1u].isString() && cmd[2u].isString()){
const std::string & toReplace = cmd[1u].asStringRef();
const std::string &toReplace = cmd[1u].asStringRef();
for (std::deque<std::string>::iterator it = lines.begin(); it != lines.end(); ++it){
if ((*it) == toReplace){
(*it) = cmd[2u].asStringRef();
}
if ((*it) == toReplace){(*it) = cmd[2u].asStringRef();}
}
return;
}
@ -93,11 +87,13 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
///\return True on successfull authorization, false otherwise.
///
/// \api
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections, you are required to re-sent authentication with every API request made. To prevent plaintext sending of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
/// To login, an `"authorize"` request must be sent. Since HTTP does not use persistent connections,
/// you are required to re-sent authentication with every API request made. To prevent plaintext sending
/// of the password, a random challenge string is sent first, and then the password is hashed together with this challenge string to create a one-time-use string to login with.
/// If the user is not authorized, this request is the only request the server will respond to until properly authorized.
/// `"authorize"` requests take the form of:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //username to login as
/// "username": "test",
/// //hash of password to login with. Send empty value when no challenge for the hash is known yet.
@ -105,38 +101,40 @@ static void executePlsCommand(JSON::Value & cmd, std::deque<std::string> & lines
/// // MD5( MD5("secret") + challenge)
/// //Where "secret" is the plaintext password.
/// "password": ""
/// }
///}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //current login status. Either "OK", "CHALL", "NOACC" or "ACC_MADE".
/// "status": "CHALL",
/// //Random value to be used in hashing the password.
/// "challenge": "abcdef1234567890"
/// }
///}
/// ~~~~~~~~~~~~~~~
/// The challenge string is sent for all statuses, except `"NOACC"`, where it is left out.
/// A status of `"OK"` means you are currently logged in and have access to all other API requests.
/// A status of `"CHALL"` means you are not logged in, and a challenge has been provided to login with.
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
/// A status of `"NOACC"` means there are no valid accounts to login with. In this case - and ONLY
/// in this case - it is possible to create a initial login through the API itself. To do so, send a request as follows:
/// ~~~~~~~~~~~~~~~{.js}
/// {
///{
/// //username to create, as plain text
/// "new_username": "test",
/// //password to set, as plain text
/// "new_password": "secret"
/// }
///}
/// ~~~~~~~~~~~~~~~
/// Please note that this is NOT secure. At all. Never use this mechanism over a public network!
/// A status of `"ACC_MADE"` indicates the account was created successfully and can now be used to login as normal.
bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn){
bool Controller::authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn){
std::string Challenge = getChallenge(conn);
std::string retval;
if (Request.isMember("authorize") && Request["authorize"]["username"].asString() != ""){
std::string UserID = Request["authorize"]["username"];
if (Storage["account"].isMember(UserID)){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) == Request["authorize"]["password"].asString()){
if (Secure::md5(Storage["account"][UserID]["password"].asString() + Challenge) ==
Request["authorize"]["password"].asString()){
Response["authorize"]["status"] = "OK";
return true;
}
@ -147,57 +145,56 @@ bool Controller::authorize(JSON::Value & Request, JSON::Value & Response, Socket
}
Response["authorize"]["status"] = "CHALL";
Response["authorize"]["challenge"] = Challenge;
//the following is used to add the first account through the LSP
// the following is used to add the first account through the LSP
if (!Storage["account"]){
Response["authorize"]["status"] = "NOACC";
if (Request["authorize"]["new_username"] && Request["authorize"]["new_password"]){
//create account
// create account
Controller::Log("CONF", "Created account " + Request["authorize"]["new_username"].asString() + " through API");
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] = Secure::md5(Request["authorize"]["new_password"].asString());
Controller::Storage["account"][Request["authorize"]["new_username"].asString()]["password"] =
Secure::md5(Request["authorize"]["new_password"].asString());
Response["authorize"]["status"] = "ACC_MADE";
}else{
Response["authorize"].removeMember("challenge");
}
}
return false;
}//Authorize
}// Authorize
class streamStat{
public:
streamStat(){
status = 0;
viewers = 0;
inputs = 0;
outputs = 0;
}
streamStat(const Util::RelAccX & rlx, uint64_t entry){
status = rlx.getInt("status", entry);
viewers = rlx.getInt("viewers", entry);
inputs = rlx.getInt("inputs", entry);
outputs = rlx.getInt("outputs", entry);
}
bool operator ==(const streamStat &b) const{
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
}
bool operator !=(const streamStat &b) const{
return !(*this == b);
}
uint8_t status;
uint64_t viewers;
uint64_t inputs;
uint64_t outputs;
public:
streamStat(){
status = 0;
viewers = 0;
inputs = 0;
outputs = 0;
}
streamStat(const Util::RelAccX &rlx, uint64_t entry){
status = rlx.getInt("status", entry);
viewers = rlx.getInt("viewers", entry);
inputs = rlx.getInt("inputs", entry);
outputs = rlx.getInt("outputs", entry);
}
bool operator==(const streamStat &b) const{
return (status == b.status && viewers == b.viewers && inputs == b.inputs && outputs == b.outputs);
}
bool operator!=(const streamStat &b) const{return !(*this == b);}
uint8_t status;
uint64_t viewers;
uint64_t inputs;
uint64_t outputs;
};
void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
void Controller::handleWebSocket(HTTP::Parser &H, Socket::Connection &C){
std::string logs = H.GetVar("logs");
std::string accs = H.GetVar("accs");
bool doStreams = H.GetVar("streams").size();
HTTP::Websocket W(C, H);
if (!W){return;}
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024*1024);
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024*1024);
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024*1024);
IPC::sharedPage shmLogs(SHM_STATE_LOGS, 1024 * 1024);
IPC::sharedPage shmAccs(SHM_STATE_ACCS, 1024 * 1024);
IPC::sharedPage shmStreams(SHM_STATE_STREAMS, 1024 * 1024);
Util::RelAccX rlxStreams(shmStreams.mapped);
Util::RelAccX rlxLog(shmLogs.mapped);
Util::RelAccX rlxAccs(shmAccs.mapped);
@ -228,7 +225,9 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
if (accs.substr(0, 6) == "since:"){
uint64_t startAccs = JSON::Value(accs.substr(6)).asInt();
accsPos = rlxAccs.getDeleted();
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){++accsPos;}
while (accsPos < rlxAccs.getEndPos() && rlxAccs.getInt("time", accsPos) < startAccs){
++accsPos;
}
}else{
uint64_t numAccs = JSON::Value(accs).asInt();
if (accsPos <= numAccs){
@ -270,7 +269,8 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
accsPos++;
}
if (doStreams){
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin(); it != lastStrmStat.end(); ++it){
for (std::map<std::string, streamStat>::iterator it = lastStrmStat.begin();
it != lastStrmStat.end(); ++it){
strmRemove.insert(it->first);
}
uint64_t startPos = rlxStreams.getDeleted();
@ -307,33 +307,31 @@ void Controller::handleWebSocket(HTTP::Parser & H, Socket::Connection & C){
lastStrmStat.erase(strm);
}
}
if (!sent){
Util::sleep(500);
}
if (!sent){Util::sleep(500);}
}
}
/// Handles a single incoming API connection.
/// Assumes the connection is unauthorized and will allow for 4 requests without authorization before disconnecting.
int Controller::handleAPIConnection(Socket::Connection & conn){
//set up defaults
int Controller::handleAPIConnection(Socket::Connection &conn){
// set up defaults
unsigned int logins = 0;
bool authorized = false;
bool isLocal = false;
HTTP::Parser H;
//while connected and not past login attempt limit
// while connected and not past login attempt limit
while (conn && logins < 4){
if ((conn.spool() || conn.Received().size()) && H.Read(conn)){
//Are we local and not forwarded? Instant-authorized.
// Are we local and not forwarded? Instant-authorized.
if (!authorized && !H.hasHeader("X-Real-IP") && conn.isLocal()){
MEDIUM_MSG("Local API access automatically authorized");
isLocal = true;
authorized = true;
}
#ifdef NOAUTH
//If auth is disabled, always allow access.
#ifdef NOAUTH
// If auth is disabled, always allow access.
authorized = true;
#endif
#endif
if (!authorized && H.hasHeader("Authorization")){
std::string auth = H.GetHeader("Authorization");
if (auth.substr(0, 5) == "json "){
@ -347,7 +345,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
H.Clean();
H.body = "Please login first or provide a valid token authentication.";
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("WWW-Authenticate", "json "+req["authorize"].toString());
H.SetHeader("WWW-Authenticate", "json " + req["authorize"].toString());
H.SendResponse("403", "Not authorized", conn);
H.Clean();
continue;
@ -355,7 +353,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
}
}
//Catch websocket requests
// Catch websocket requests
if (H.url == "/ws"){
if (!authorized){
H.Clean();
@ -369,14 +367,14 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
H.Clean();
continue;
}
//Catch prometheus requests
// Catch prometheus requests
if (Controller::prometheus.size()){
if (H.url == "/"+Controller::prometheus){
if (H.url == "/" + Controller::prometheus){
handlePrometheus(H, conn, PROMETHEUS_TEXT);
H.Clean();
continue;
}
if (H.url.substr(0, Controller::prometheus.size()+6) == "/"+Controller::prometheus+".json"){
if (H.url.substr(0, Controller::prometheus.size() + 6) == "/" + Controller::prometheus + ".json"){
handlePrometheus(H, conn, PROMETHEUS_JSON);
H.Clean();
continue;
@ -384,26 +382,24 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
JSON::Value Response;
JSON::Value Request = JSON::fromString(H.GetVar("command"));
//invalid request? send the web interface, unless requested as "/api"
if ( !Request.isObject() && H.url != "/api" && H.url != "/api2"){
#include "server.html.h"
// invalid request? send the web interface, unless requested as "/api"
if (!Request.isObject() && H.url != "/api" && H.url != "/api2"){
#include "server.html.h"
H.Clean();
H.SetHeader("Content-Type", "text/html");
H.SetHeader("X-Info", "To force an API response, request the file /api");
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("Content-Length", server_html_len);
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
H.SetHeader("X-UA-Compatible", "IE=edge;chrome=1");
H.SendResponse("200", "OK", conn);
conn.SendNow(server_html, server_html_len);
H.Clean();
break;
}
if (H.url == "/api2"){
Request["minimal"] = true;
}
{//lock the config mutex here - do not unlock until done processing
if (H.url == "/api2"){Request["minimal"] = true;}
{// lock the config mutex here - do not unlock until done processing
tthread::lock_guard<tthread::mutex> guard(configMutex);
//if already authorized, do not re-check for authorization
// if already authorized, do not re-check for authorization
if (authorized && Storage["account"]){
Response["authorize"]["status"] = "OK";
if (isLocal){Response["authorize"]["local"] = true;}
@ -412,20 +408,16 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
if (authorized){
handleAPICommands(Request, Response);
}else{//unauthorized
Util::sleep(1000);//sleep a second to prevent bruteforcing
}else{// unauthorized
Util::sleep(1000); // sleep a second to prevent bruteforcing
logins++;
}
Controller::checkServerLimits(); /*LTS*/
}//config mutex lock
//send the response, either normally or through JSONP callback.
}// config mutex lock
// send the response, either normally or through JSONP callback.
std::string jsonp = "";
if (H.GetVar("callback") != ""){
jsonp = H.GetVar("callback");
}
if (H.GetVar("jsonp") != ""){
jsonp = H.GetVar("jsonp");
}
if (H.GetVar("callback") != ""){jsonp = H.GetVar("callback");}
if (H.GetVar("jsonp") != ""){jsonp = H.GetVar("jsonp");}
H.Clean();
H.SetHeader("Content-Type", "text/javascript");
H.setCORSHeaders();
@ -436,12 +428,12 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
}
H.SendResponse("200", "OK", conn);
H.Clean();
}//if HTTP request received
}//while connected
}// if HTTP request received
}// while connected
return 0;
}
void Controller::handleUDPAPI(void * np){
void Controller::handleUDPAPI(void *np){
Socket::UDPConnection uSock(true);
if (!uSock.bind(UDP_API_PORT, UDP_API_HOST)){
FAIL_MSG("Could not open local API UDP socket - not all functionality will be available");
@ -472,10 +464,8 @@ void Controller::handleUDPAPI(void * np){
/// Local-only helper function that checks for duplicate protocols and removes them
static void removeDuplicateProtocols(){
JSON::Value & P = Controller::Storage["config"]["protocols"];
jsonForEach(P, it){
it->removeNullMembers();
}
JSON::Value &P = Controller::Storage["config"]["protocols"];
jsonForEach(P, it){it->removeNullMembers();}
std::set<std::string> ignores;
ignores.insert("online");
bool reloop = true;
@ -495,13 +485,13 @@ static void removeDuplicateProtocols(){
}
}
void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response){
void Controller::handleAPICommands(JSON::Value &Request, JSON::Value &Response){
/*LTS-START*/
//These are only used internally. We abort further processing if encountered.
// These are only used internally. We abort further processing if encountered.
if (Request.isMember("trigger_stat")){
JSON::Value & tStat = Request["trigger_stat"];
JSON::Value &tStat = Request["trigger_stat"];
if (tStat.isMember("name") && tStat.isMember("ms")){
Controller::triggerLog & tLog = Controller::triggerStats[tStat["name"].asStringRef()];
Controller::triggerLog &tLog = Controller::triggerStats[tStat["name"].asStringRef()];
tLog.totalCount++;
tLog.ms += tStat["ms"].asInt();
if (!tStat.isMember("ok") || !tStat["ok"].asBool()){tLog.failCount++;}
@ -513,14 +503,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
return;
}
/*LTS-END*/
//Parse config and streams from the request.
// Parse config and streams from the request.
if (Request.isMember("config") && Request["config"].isObject()){
const JSON::Value & in = Request["config"];
JSON::Value & out = Controller::Storage["config"];
const JSON::Value &in = Request["config"];
JSON::Value &out = Controller::Storage["config"];
if (in.isMember("debug")){
out["debug"] = in["debug"];
if (Util::Config::printDebugLevel != (out["debug"].isInt()?out["debug"].asInt():DEBUG)){
Util::Config::printDebugLevel = (out["debug"].isInt()?out["debug"].asInt():DEBUG);
if (Util::Config::printDebugLevel != (out["debug"].isInt() ? out["debug"].asInt() : DEBUG)){
Util::Config::printDebugLevel = (out["debug"].isInt() ? out["debug"].asInt() : DEBUG);
INFO_MSG("Debug level set to %u", Util::Config::printDebugLevel);
}
}
@ -532,12 +522,8 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
out["trustedproxy"] = in["trustedproxy"];
Controller::normalizeTrustedProxies(out["trustedproxy"]);
}
if (in.isMember("controller")){
out["controller"] = in["controller"];
}
if (in.isMember("serverid")){
out["serverid"] = in["serverid"];
}
if (in.isMember("controller")){out["controller"] = in["controller"];}
if (in.isMember("serverid")){out["serverid"] = in["serverid"];}
if (in.isMember("triggers")){
out["triggers"] = in["triggers"];
if (!out["triggers"].isObject()){
@ -545,9 +531,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}else{
jsonForEach(out["triggers"], it){
if (it->isArray()){
jsonForEach((*it), jt){
jt->removeNullMembers();
}
jsonForEach((*it), jt){jt->removeNullMembers();}
}
}
}
@ -560,9 +544,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
out["prometheus"] = in["prometheus"];
Controller::prometheus = out["prometheus"].asStringRef();
}
if (in.isMember("defaultStream")){
out["defaultStream"] = in["defaultStream"];
}
if (in.isMember("defaultStream")){out["defaultStream"] = in["defaultStream"];}
}
if (Request.isMember("bandwidth")){
if (Request["bandwidth"].isObject()){
@ -583,94 +565,72 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Controller::AddStreams(Request["addstream"], Controller::Storage["streams"]);
}
if (Request.isMember("deletestream")){
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (Request["deletestream"].isString()){
Controller::deleteStream(Request["deletestream"].asStringRef(), Controller::Storage["streams"]);
Controller::deleteStream(Request["deletestream"].asStringRef(),
Controller::Storage["streams"]);
}
if (Request["deletestream"].isArray()){
jsonForEach(Request["deletestream"], it){
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"]);
}
}
if (Request["deletestream"].isObject()){
jsonForEach(Request["deletestream"], it){
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
Controller::deleteStream(it.key(), Controller::Storage["streams"]);
}
}
}
if (Request.isMember("deletestreamsource")){
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (Request["deletestreamsource"].isString()){
switch (Controller::deleteStream(Request["deletestreamsource"].asStringRef(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"] = "-3: Stream, source file and dtsh deleted";
break;
switch (Controller::deleteStream(Request["deletestreamsource"].asStringRef(),
Controller::Storage["streams"], true)){
case 0: Response["deletestreamsource"] = "0: No action taken"; break;
case 1: Response["deletestreamsource"] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"] = "2: Source file and dtsh deleted"; break;
case -1: Response["deletestreamsource"] = "-1: Stream deleted, source remains"; break;
case -2: Response["deletestreamsource"] = "-2: Stream and source file deleted"; break;
case -3: Response["deletestreamsource"] = "-3: Stream, source file and dtsh deleted"; break;
}
}
if (Request["deletestreamsource"].isArray()){
jsonForEach(Request["deletestreamsource"], it){
switch (Controller::deleteStream(it->asStringRef(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"][it.num()] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"][it.num()] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"][it.num()] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"][it.num()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.num()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.num()] = "-3: Stream, source file and dtsh deleted";
break;
case 0: Response["deletestreamsource"][it.num()] = "0: No action taken"; break;
case 1: Response["deletestreamsource"][it.num()] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"][it.num()] = "2: Source file and dtsh deleted"; break;
case -1:
Response["deletestreamsource"][it.num()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.num()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.num()] = "-3: Stream, source file and dtsh deleted";
break;
}
}
}
if (Request["deletestreamsource"].isObject()){
jsonForEach(Request["deletestreamsource"], it){
switch (Controller::deleteStream(it.key(), Controller::Storage["streams"], true)){
case 0:
Response["deletestreamsource"][it.key()] = "0: No action taken";
break;
case 1:
Response["deletestreamsource"][it.key()] = "1: Source file deleted";
break;
case 2:
Response["deletestreamsource"][it.key()] = "2: Source file and dtsh deleted";
break;
case -1:
Response["deletestreamsource"][it.key()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.key()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.key()] = "-3: Stream, source file and dtsh deleted";
break;
case 0: Response["deletestreamsource"][it.key()] = "0: No action taken"; break;
case 1: Response["deletestreamsource"][it.key()] = "1: Source file deleted"; break;
case 2: Response["deletestreamsource"][it.key()] = "2: Source file and dtsh deleted"; break;
case -1:
Response["deletestreamsource"][it.key()] = "-1: Stream deleted, source remains";
break;
case -2:
Response["deletestreamsource"][it.key()] = "-2: Stream and source file deleted";
break;
case -3:
Response["deletestreamsource"][it.key()] = "-3: Stream, source file and dtsh deleted";
break;
}
}
}
@ -699,18 +659,14 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
break;
}
}
if (add){
newProtocols.append(*it);
}
if (add){newProtocols.append(*it);}
}
Controller::Storage["config"]["protocols"] = newProtocols;
}
if (Request["deleteprotocol"].isObject()){
JSON::Value newProtocols;
jsonForEach(Controller::Storage["config"]["protocols"], it){
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){
newProtocols.append(*it);
}
if (!(*it).compareExcept(Request["deleteprotocol"], ignores)){newProtocols.append(*it);}
}
Controller::Storage["config"]["protocols"] = newProtocols;
}
@ -721,7 +677,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request["updateprotocol"].isArray() && Request["updateprotocol"].size() == 2){
jsonForEach(Controller::Storage["config"]["protocols"], it){
if ((*it).compareExcept(Request["updateprotocol"][0u], ignores)){
//If the connector type didn't change, mark it as needing a reload
// If the connector type didn't change, mark it as needing a reload
if ((*it)["connector"] == Request["updateprotocol"][1u]["connector"]){
reloadProtocol(it.num());
}
@ -739,32 +695,29 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Response["capabilities"] = capabilities;
}
if(Request.isMember("browse")){
if(Request["browse"] == ""){
Request["browse"] = ".";
}
if (Request.isMember("browse")){
if (Request["browse"] == ""){Request["browse"] = ".";}
DIR *dir;
struct dirent *ent;
struct stat filestat;
char* rpath = realpath(Request["browse"].asString().c_str(),0);
if(rpath == NULL){
char *rpath = realpath(Request["browse"].asString().c_str(), 0);
if (rpath == NULL){
Response["browse"]["path"].append(Request["browse"].asString());
}else{
Response["browse"]["path"].append(rpath);//Request["browse"].asString());
if ((dir = opendir (Request["browse"].asString().c_str())) != NULL) {
while ((ent = readdir (dir)) != NULL) {
if(strcmp(ent->d_name,".")!=0 && strcmp(ent->d_name,"..")!=0 ){
Response["browse"]["path"].append(rpath); // Request["browse"].asString());
if ((dir = opendir(Request["browse"].asString().c_str())) != NULL){
while ((ent = readdir(dir)) != NULL){
if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0){
std::string filepath = Request["browse"].asString() + "/" + std::string(ent->d_name);
if (stat( filepath.c_str(), &filestat )) continue;
if (S_ISDIR( filestat.st_mode)){
if (stat(filepath.c_str(), &filestat)) continue;
if (S_ISDIR(filestat.st_mode)){
Response["browse"]["subdirectories"].append(ent->d_name);
}else{
Response["browse"]["files"].append(ent->d_name);
}
}
}
closedir (dir);
closedir(dir);
}
}
free(rpath);
@ -781,18 +734,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
ERROR_MSG("Playlist API call requires object payload, no object given");
}else{
jsonForEach(Request["playlist"], it){
if (!Controller::Storage["streams"].isMember(it.key()) || !Controller::Storage["streams"][it.key()].isMember("source")){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' not configured", it.key().c_str());
if (!Controller::Storage["streams"].isMember(it.key()) ||
!Controller::Storage["streams"][it.key()].isMember("source")){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' not configured",
it.key().c_str());
}else{
std::string src = Controller::Storage["streams"][it.key()]["source"].asString();
if (src.substr(src.size() - 4) != ".pls"){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' is not playlist-based", it.key().c_str());
FAIL_MSG(
"Playlist API call (partially) not executed: stream '%s' is not playlist-based",
it.key().c_str());
}else{
bool readFirst = true;
struct stat fileinfo;
if (stat(src.c_str(), &fileinfo) != 0){
if (errno == EACCES){
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' playlist '%s' cannot be accessed (no file permissions)", it.key().c_str(), src.c_str());
FAIL_MSG("Playlist API call (partially) not executed: stream '%s' playlist '%s' "
"cannot be accessed (no file permissions)",
it.key().c_str(), src.c_str());
break;
}
if (errno == ENOENT){
@ -804,24 +763,24 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (readFirst){
std::ifstream plsRead(src.c_str());
if (!plsRead.good()){
FAIL_MSG("Playlist (%s) for stream '%s' could not be opened for reading; aborting command(s)", src.c_str(), it.key().c_str());
FAIL_MSG("Playlist (%s) for stream '%s' could not be opened for reading; aborting "
"command(s)",
src.c_str(), it.key().c_str());
break;
}
std::string line;
do {
do{
std::getline(plsRead, line);
if (line.size() || plsRead.good()){lines.push_back(line);}
} while(plsRead.good());
}while (plsRead.good());
}
unsigned int plsNo = 0;
for (std::deque<std::string>::iterator plsIt = lines.begin(); plsIt != lines.end(); ++plsIt){
MEDIUM_MSG("Before playlist command item %u: %s", plsNo, plsIt->c_str());
++plsNo;
}
if (!it->isBool()){
executePlsCommand(*it, lines);
}
JSON::Value & outPls = Response["playlist"][it.key()];
if (!it->isBool()){executePlsCommand(*it, lines);}
JSON::Value &outPls = Response["playlist"][it.key()];
std::ofstream plsOutFile(src.c_str(), std::ios_base::trunc);
if (!plsOutFile.good()){
FAIL_MSG("Could not open playlist for writing: %s", src.c_str());
@ -832,9 +791,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
MEDIUM_MSG("After playlist command item %u: %s", plsNo, plsIt->c_str());
++plsNo;
outPls.append(*plsIt);
if (plsNo < lines.size() || (*plsIt).size()){
plsOutFile << (*plsIt) << "\n";
}
if (plsNo < lines.size() || (*plsIt).size()){plsOutFile << (*plsIt) << "\n";}
}
}
}
@ -848,32 +805,30 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
if (Request.isMember("ui_settings")){
if (Request["ui_settings"].isObject()){
Storage["ui_settings"] = Request["ui_settings"];
}
if (Request["ui_settings"].isObject()){Storage["ui_settings"] = Request["ui_settings"];}
Response["ui_settings"] = Storage["ui_settings"];
}
/*LTS-START*/
///
///
/// \api
/// LTS builds will always include an `"LTS"` response, set to 1.
///
///
Response["LTS"] = 1;
///
/// \api
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
///
#ifdef UPDATER
if (Request.isMember("autoupdate")){
Controller::checkUpdates();
}
///
/// \api
/// `"autoupdate"` requests (LTS-only) will cause MistServer to apply a rolling update to itself, and are not responded to.
///
#ifdef UPDATER
if (Request.isMember("autoupdate")){Controller::checkUpdates();}
if (Request.isMember("update") || Request.isMember("checkupdate") || Request.isMember("autoupdate")){
Controller::insertUpdateInfo(Response["update"]);
}
#endif
#endif
/*LTS-END*/
if (!Request.isMember("minimal") || Request.isMember("streams") || Request.isMember("addstream") || Request.isMember("deletestream")){
if (!Request.isMember("streams") && (Request.isMember("addstream") || Request.isMember("deletestream"))){
if (!Request.isMember("minimal") || Request.isMember("streams") ||
Request.isMember("addstream") || Request.isMember("deletestream")){
if (!Request.isMember("streams") &&
(Request.isMember("addstream") || Request.isMember("deletestream"))){
Response["streams"]["incomplete list"] = 1u;
if (Request.isMember("addstream")){
jsonForEach(Request["addstream"], jit){
@ -886,24 +841,22 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Response["streams"] = Controller::Storage["streams"];
}
}
//sent current configuration, if not minimal or was changed/requested
// sent current configuration, if not minimal or was changed/requested
if (!Request.isMember("minimal") || Request.isMember("config")){
Response["config"] = Controller::Storage["config"];
Response["config"]["iid"] = instanceId;
Response["config"]["version"] = PACKAGE_VERSION " " RELEASE;
/*LTS-START*/
#ifdef LICENSING
/*LTS-START*/
#ifdef LICENSING
Response["config"]["license"] = getLicense();
#endif
#endif
/*LTS-END*/
//add required data to the current unix time to the config, for syncing reasons
// add required data to the current unix time to the config, for syncing reasons
Response["config"]["time"] = Util::epoch();
if ( !Response["config"].isMember("serverid")){
Response["config"]["serverid"] = "";
}
if (!Response["config"].isMember("serverid")){Response["config"]["serverid"] = "";}
}
//sent any available logs and statistics
///
// sent any available logs and statistics
///
/// \api
/// `"log"` responses are always sent, and cannot be requested:
/// ~~~~~~~~~~~~~~~{.js}
@ -911,22 +864,20 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
/// [
/// 1398978357, //unix timestamp of this log message
/// "CONF", //shortcode indicating the type of log message
/// "Starting connector: {\"connector\":\"HTTP\"}" //string containing the log message itself
/// "Starting connector:{\"connector\":\"HTTP\"}" //string containing the log message itself
/// ],
/// //the above structure repeated for all logs
/// ]
/// ~~~~~~~~~~~~~~~
/// It's possible to clear the stored logs by sending an empty `"clearstatlogs"` request.
///
///
if (Request.isMember("clearstatlogs") || Request.isMember("log") || !Request.isMember("minimal")){
tthread::lock_guard<tthread::mutex> guard(logMutex);
if (!Request.isMember("minimal") || Request.isMember("log")){
Response["log"] = Controller::Storage["log"];
}
//clear log if requested
if (Request.isMember("clearstatlogs")){
Controller::Storage["log"].null();
}
// clear log if requested
if (Request.isMember("clearstatlogs")){Controller::Storage["log"].null();}
}
if (Request.isMember("clients")){
if (Request["clients"].isArray()){
@ -991,9 +942,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_sessions")){
if (Request["stop_sessions"].isArray() || Request["stop_sessions"].isObject()){
jsonForEach(Request["stop_sessions"], it){
Controller::sessions_shutdown(it);
}
jsonForEach(Request["stop_sessions"], it){Controller::sessions_shutdown(it);}
}else{
Controller::sessions_shutdown(Request["stop_sessions"].asStringRef());
}
@ -1001,9 +950,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_sessid")){
if (Request["stop_sessid"].isArray() || Request["stop_sessid"].isObject()){
jsonForEach(Request["stop_sessid"], it){
Controller::sessId_shutdown(it->asStringRef());
}
jsonForEach(Request["stop_sessid"], it){Controller::sessId_shutdown(it->asStringRef());}
}else{
Controller::sessId_shutdown(Request["stop_sessid"].asStringRef());
}
@ -1011,9 +958,7 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
if (Request.isMember("stop_tag")){
if (Request["stop_tag"].isArray() || Request["stop_tag"].isObject()){
jsonForEach(Request["stop_tag"], it){
Controller::tag_shutdown(it->asStringRef());
}
jsonForEach(Request["stop_tag"], it){Controller::tag_shutdown(it->asStringRef());}
}else{
Controller::tag_shutdown(Request["stop_tag"].asStringRef());
}
@ -1027,7 +972,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
}
if (Request.isMember("push_start")){
std::string stream;
std::string target;
@ -1053,29 +997,21 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
}
}
if (Request.isMember("push_list")){
Controller::listPush(Response["push_list"]);
}
if (Request.isMember("push_list")){Controller::listPush(Response["push_list"]);}
if (Request.isMember("push_stop")){
if (Request["push_stop"].isArray()){
jsonForEach(Request["push_stop"], it){
Controller::stopPush(it->asInt());
}
jsonForEach(Request["push_stop"], it){Controller::stopPush(it->asInt());}
}else{
Controller::stopPush(Request["push_stop"].asInt());
}
}
if (Request.isMember("push_auto_add")){
Controller::addPush(Request["push_auto_add"]);
}
if (Request.isMember("push_auto_add")){Controller::addPush(Request["push_auto_add"]);}
if (Request.isMember("push_auto_remove")){
if (Request["push_auto_remove"].isArray()){
jsonForEach(Request["push_auto_remove"], it){
Controller::removePush(*it);
}
jsonForEach(Request["push_auto_remove"], it){Controller::removePush(*it);}
}else{
Controller::removePush(Request["push_auto_remove"]);
}
@ -1089,8 +1025,6 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
Controller::pushSettings(Request["push_settings"], Response["push_settings"]);
}
Controller::writeConfig();
Controller::configChanged = false;
}

View file

@ -1,12 +1,12 @@
#include <mist/socket.h>
#include <mist/json.h>
#include <mist/websocket.h>
#include <mist/http_parser.h>
#include <mist/json.h>
#include <mist/socket.h>
#include <mist/websocket.h>
namespace Controller {
bool authorize(JSON::Value & Request, JSON::Value & Response, Socket::Connection & conn);
int handleAPIConnection(Socket::Connection & conn);
void handleAPICommands(JSON::Value & Request, JSON::Value & Response);
void handleWebSocket(HTTP::Parser & H, Socket::Connection & C);
void handleUDPAPI(void * np);
}
namespace Controller{
bool authorize(JSON::Value &Request, JSON::Value &Response, Socket::Connection &conn);
int handleAPIConnection(Socket::Connection &conn);
void handleAPICommands(JSON::Value &Request, JSON::Value &Response);
void handleWebSocket(HTTP::Parser &H, Socket::Connection &C);
void handleUDPAPI(void *np);
}// namespace Controller

View file

@ -1,21 +1,21 @@
#include "controller_capabilities.h"
#include <fstream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/procs.h>
#include <set>
#include <stdio.h>
#include <string.h>
#include <fstream>
#include <set>
#include <mist/defines.h>
#include <mist/config.h>
#include <mist/procs.h>
#include "controller_capabilities.h"
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
JSON::Value capabilities;
//Converter::Converter * myConverter = 0;
///Generate list of available triggers, storing in global 'capabilities' JSON::Value.
// Converter::Converter * myConverter = 0;
/// Generate list of available triggers, storing in global 'capabilities' JSON::Value.
void checkAvailTriggers(){
JSON::Value & trgs = capabilities["triggers"];
JSON::Value &trgs = capabilities["triggers"];
trgs["SYSTEM_START"]["when"] = "After MistServer boot";
trgs["SYSTEM_START"]["stream_specific"] = false;
trgs["SYSTEM_START"]["payload"] = "";
@ -50,19 +50,24 @@ namespace Controller {
trgs["STREAM_CONFIG"]["stream_specific"] = true;
trgs["STREAM_CONFIG"]["payload"] = "stream name (string)\nnew stream configuration (JSON)";
trgs["STREAM_CONFIG"]["response"] = "always";
trgs["STREAM_CONFIG"]["response_action"] = "If false, rejects new configuration and reverts to current configuration.";
trgs["STREAM_CONFIG"]["response_action"] =
"If false, rejects new configuration and reverts to current configuration.";
trgs["STREAM_REMOVE"]["when"] = "Before an existing stream is removed";
trgs["STREAM_REMOVE"]["stream_specific"] = true;
trgs["STREAM_REMOVE"]["payload"] = "stream name (string)";
trgs["STREAM_REMOVE"]["response"] = "always";
trgs["STREAM_REMOVE"]["response_action"] = "If false, prevents removal and reverts to current configuration.";
trgs["STREAM_REMOVE"]["response_action"] =
"If false, prevents removal and reverts to current configuration.";
trgs["STREAM_SOURCE"]["when"] = "When a stream's source setting is loaded";
trgs["STREAM_SOURCE"]["stream_specific"] = true;
trgs["STREAM_SOURCE"]["payload"] = "stream name (string)";
trgs["STREAM_SOURCE"]["response"] = "when-blocking";
trgs["STREAM_SOURCE"]["response_action"] = "A non-empty response will set the stream source to the response value. An empty response will cause the stream source to not be changed from the normally configured stream source.";
trgs["STREAM_SOURCE"]["response_action"] =
"A non-empty response will set the stream source to the response value. An empty response "
"will cause the stream source to not be changed from the normally configured stream "
"source.";
trgs["STREAM_LOAD"]["when"] = "Before a stream input is loaded";
trgs["STREAM_LOAD"]["stream_specific"] = true;
@ -80,11 +85,13 @@ namespace Controller {
trgs["STREAM_UNLOAD"]["stream_specific"] = true;
trgs["STREAM_UNLOAD"]["payload"] = "stream name (string)\ninput type (string)";
trgs["STREAM_UNLOAD"]["response"] = "always";
trgs["STREAM_UNLOAD"]["response_action"] = "If false, aborts the unload and keeps the stream loaded.";
trgs["STREAM_UNLOAD"]["response_action"] =
"If false, aborts the unload and keeps the stream loaded.";
trgs["STREAM_PUSH"]["when"] = "Before an incoming push is accepted";
trgs["STREAM_PUSH"]["stream_specific"] = true;
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["STREAM_PUSH"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["STREAM_PUSH"]["response"] = "always";
trgs["STREAM_PUSH"]["response_action"] = "If false, rejects the incoming push.";
@ -102,55 +109,79 @@ namespace Controller {
trgs["STREAM_BUFFER"]["when"] = "Every time a live stream buffer changes state";
trgs["STREAM_BUFFER"]["stream_specific"] = true;
trgs["STREAM_BUFFER"]["payload"] = "stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health information (only if not EMPTY) (JSON)";
trgs["STREAM_BUFFER"]["payload"] =
"stream name (string)\nbuffer state: EMPTY, FULL, DRY or RECOVER (string)\nbuffer health "
"information (only if not EMPTY) (JSON)";
trgs["STREAM_BUFFER"]["response"] = "ignored";
trgs["STREAM_BUFFER"]["response_action"] = "None.";
trgs["RTMP_PUSH_REWRITE"]["when"] = "On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
trgs["RTMP_PUSH_REWRITE"]["when"] =
"On incoming RTMP pushes, allows rewriting the RTMP URL to/from custom formatting";
trgs["RTMP_PUSH_REWRITE"]["stream_specific"] = false;
trgs["RTMP_PUSH_REWRITE"]["payload"] = "full current RTMP url (string)\nconnection hostname (string)";
trgs["RTMP_PUSH_REWRITE"]["payload"] =
"full current RTMP url (string)\nconnection hostname (string)";
trgs["RTMP_PUSH_REWRITE"]["response"] = "when-blocking";
trgs["RTMP_PUSH_REWRITE"]["response_action"] = "If non-empty, overrides the full RTMP url to the response value. If empty, denies the incoming RTMP push.";
trgs["RTMP_PUSH_REWRITE"]["response_action"] =
"If non-empty, overrides the full RTMP url to the response value. If empty, denies the "
"incoming RTMP push.";
trgs["PUSH_OUT_START"]["when"] = "Before a push out (to file or other target type) is started";
trgs["PUSH_OUT_START"]["stream_specific"] = true;
trgs["PUSH_OUT_START"]["payload"] = "stream name (string)\npush target (string)";
trgs["PUSH_OUT_START"]["response"] = "when-blocking";
trgs["PUSH_OUT_START"]["response_action"] = "A non-empty response will set the push target to the response value. An empty response will abort the push. Variable substitution will still take place.";
trgs["PUSH_OUT_START"]["response_action"] =
"A non-empty response will set the push target to the response value. An empty response "
"will abort the push. Variable substitution will still take place.";
trgs["RECORDING_END"]["when"] = "When a push to file finishes";
trgs["RECORDING_END"]["stream_specific"] = true;
trgs["RECORDING_END"]["payload"] = "stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded (integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix time recording stopped (integer)\ntotal milliseconds of media data recorded (integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp of last media packet (integer)\n";
trgs["RECORDING_END"]["payload"] =
"stream name (string)\npush target (string)\nconnector / filetype (string)\nbytes recorded "
"(integer)\nseconds spent recording (integer)\nunix time recording started (integer)\nunix "
"time recording stopped (integer)\ntotal milliseconds of media data recorded "
"(integer)\nmillisecond timestamp of first media packet (integer)\nmillisecond timestamp "
"of last media packet (integer)\n";
trgs["RECORDING_END"]["response"] = "ignored";
trgs["RECORDING_END"]["response_action"] = "None.";
trgs["CONN_OPEN"]["when"] = "After a new connection is accepted";
trgs["CONN_OPEN"]["stream_specific"] = true;
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_OPEN"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_OPEN"]["response"] = "always";
trgs["CONN_OPEN"]["response_action"] = "If false, rejects the connection.";
trgs["CONN_CLOSE"]["when"] = "After a new connection is closed";
trgs["CONN_CLOSE"]["stream_specific"] = true;
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_CLOSE"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_CLOSE"]["response"] = "ignored";
trgs["CONN_CLOSE"]["response_action"] = "None.";
trgs["CONN_PLAY"]["when"] = "Before a connection first starts playback";
trgs["CONN_PLAY"]["stream_specific"] = true;
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector (string)\nrequest url (string)";
trgs["CONN_PLAY"]["payload"] = "stream name (string)\nconnection address (string)\nconnector "
"(string)\nrequest url (string)";
trgs["CONN_PLAY"]["response"] = "always";
trgs["CONN_PLAY"]["response_action"] = "If false, rejects the playback attempt.";
trgs["USER_NEW"]["when"] = "Every time a new session is added to the session cache";
trgs["USER_NEW"]["stream_specific"] = true;
trgs["USER_NEW"]["payload"] = "stream name (string)\nconnection address (string)\nconnection identifier (integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
trgs["USER_NEW"]["payload"] =
"stream name (string)\nconnection address (string)\nconnection identifier "
"(integer)\nconnector (string)\nrequest url (string)\nsession identifier (integer)";
trgs["USER_NEW"]["response"] = "always";
trgs["USER_NEW"]["response_action"] = "If false, denies the session while it remains in the cache. If true, accepts the session while it remains in the cache.";
trgs["USER_NEW"]["response_action"] =
"If false, denies the session while it remains in the cache. If true, accepts the session "
"while it remains in the cache.";
trgs["USER_END"]["when"] = "Every time a session ends (same time it is written to the access log)";
trgs["USER_END"]["when"] =
"Every time a session ends (same time it is written to the access log)";
trgs["USER_END"]["stream_specific"] = true;
trgs["USER_END"]["payload"] = "session identifier (hexadecimal string)\nstream name (string)\nconnector (string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes total (integer)\ndownloaded bytes total (integer)\ntags (string)";
trgs["USER_END"]["payload"] =
"session identifier (hexadecimal string)\nstream name (string)\nconnector "
"(string)\nconnection address (string)\nduration in seconds (integer)\nuploaded bytes "
"total (integer)\ndownloaded bytes total (integer)\ntags (string)";
trgs["USER_END"]["response"] = "ignored";
trgs["USER_END"]["response_action"] = "None.";
@ -159,31 +190,38 @@ namespace Controller {
trgs["LIVE_BANDWIDTH"]["payload"] = "stream name (string)\ncurrent bytes per second (integer)";
trgs["LIVE_BANDWIDTH"]["response"] = "always";
trgs["LIVE_BANDWIDTH"]["response_action"] = "If false, shuts down the stream buffer.";
trgs["LIVE_BANDWIDTH"]["argument"] = "Triggers only if current bytes per second exceeds this amount (integer)";
trgs["LIVE_BANDWIDTH"]["argument"] =
"Triggers only if current bytes per second exceeds this amount (integer)";
trgs["DEFAULT_STREAM"]["when"] = "When any user attempts to open a stream that cannot be opened (because it is either offline or not configured), allows rewriting the stream to a different one as fallback. Supports variable substitution.";
trgs["DEFAULT_STREAM"]["when"] =
"When any user attempts to open a stream that cannot be opened (because it is either "
"offline or not configured), allows rewriting the stream to a different one as fallback. "
"Supports variable substitution.";
trgs["DEFAULT_STREAM"]["stream_specific"] = true;
trgs["DEFAULT_STREAM"]["payload"] = "current defaultStream setting (string)\nrequested stream name (string)\nviewer host (string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based requests!)";
trgs["DEFAULT_STREAM"]["payload"] =
"current defaultStream setting (string)\nrequested stream name (string)\nviewer host "
"(string)\noutput type (string)\nfull request URL (string, may be blank for non-URL-based "
"requests!)";
trgs["DEFAULT_STREAM"]["response"] = "always";
trgs["DEFAULT_STREAM"]["response_action"] = "Overrides the default stream setting (for this view) to the response value. If empty, fails loading the stream and returns an error to the viewer/user.";
trgs["DEFAULT_STREAM"]["response_action"] =
"Overrides the default stream setting (for this view) to the response value. If empty, "
"fails loading the stream and returns an error to the viewer/user.";
}
///Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
/// Aquire list of available protocols, storing in global 'capabilities' JSON::Value.
void checkAvailProtocols(){
std::deque<std::string> execs;
Util::getMyExec(execs);
std::string arg_one;
char const * conn_args[] = {0, "-j", 0};
char const *conn_args[] ={0, "-j", 0};
for (std::deque<std::string>::iterator it = execs.begin(); it != execs.end(); it++){
if ((*it).substr(0, 8) == "MistConn"){
//skip if an MistOut already existed - MistOut takes precedence!
if (capabilities["connectors"].isMember((*it).substr(8))){
continue;
}
// skip if an MistOut already existed - MistOut takes precedence!
if (capabilities["connectors"].isMember((*it).substr(8))){continue;}
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
capabilities["connectors"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["connectors"][(*it).substr(8)] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["connectors"][(*it).substr(8)].size() < 1){
capabilities["connectors"].removeMember((*it).substr(8));
}
@ -192,18 +230,21 @@ namespace Controller {
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
std::string entryName = (*it).substr(7);
capabilities["connectors"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["connectors"][entryName] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["connectors"][entryName].size() < 1){
capabilities["connectors"].removeMember(entryName);
}else if (capabilities["connectors"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["connectors"][entryName]["version"].asStringRef().c_str());
WARN_MSG("Output %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
capabilities["connectors"][entryName]["version"].asStringRef().c_str());
capabilities["connectors"].removeMember(entryName);
}
}
if ((*it).substr(0, 8) == "MistProc"){
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
capabilities["processes"][(*it).substr(8)] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["processes"][(*it).substr(8)] =
JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["processes"][(*it).substr(8)].size() < 1){
capabilities["processes"].removeMember((*it).substr(7));
}
@ -212,58 +253,47 @@ namespace Controller {
arg_one = Util::getMyPath() + (*it);
conn_args[0] = arg_one.c_str();
std::string entryName = (*it).substr(6);
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char**)conn_args));
capabilities["inputs"][entryName] = JSON::fromString(Util::Procs::getOutputOf((char **)conn_args));
if (capabilities["inputs"][entryName].size() < 1){
capabilities["inputs"].removeMember((*it).substr(6));
}else if (capabilities["inputs"][entryName]["version"].asStringRef() != PACKAGE_VERSION){
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(), capabilities["inputs"][entryName]["version"].asStringRef().c_str());
WARN_MSG("Input %s version mismatch (%s != " PACKAGE_VERSION ")", entryName.c_str(),
capabilities["inputs"][entryName]["version"].asStringRef().c_str());
capabilities["inputs"].removeMember(entryName);
}
}
}
}
///\brief A class storing information about the cpu the server is running on.
class cpudata{
public:
std::string model;///<A string describing the model of the cpu.
int cores;///<The amount of cores in the cpu.
int threads;///<The amount of threads this cpu can run.
int mhz;///<The speed of the cpu in mhz.
int id;///<The id of the cpu in the system.
///\brief The default constructor
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
}
;
///\brief Fills the structure by parsing a given description.
///\param data A description of the cpu.
void fill(char * data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){
model = (data + i);
}
if (sscanf(data, "cpu cores : %d", &i) == 1){
cores = i;
}
if (sscanf(data, "siblings : %d", &i) == 1){
threads = i;
}
if (sscanf(data, "physical id : %d", &i) == 1){
id = i;
}
if (sscanf(data, "cpu MHz : %d", &i) == 1){
mhz = i;
}
}
;
public:
std::string model; ///< A string describing the model of the cpu.
int cores; ///< The amount of cores in the cpu.
int threads; ///< The amount of threads this cpu can run.
int mhz; ///< The speed of the cpu in mhz.
int id; ///< The id of the cpu in the system.
///\brief The default constructor
cpudata(){
model = "Unknown";
cores = 1;
threads = 1;
mhz = 0;
id = 0;
};
///\brief Fills the structure by parsing a given description.
///\param data A description of the cpu.
void fill(char *data){
int i;
i = 0;
if (sscanf(data, "model name : %n", &i) != EOF && i > 0){model = (data + i);}
if (sscanf(data, "cpu cores : %d", &i) == 1){cores = i;}
if (sscanf(data, "siblings : %d", &i) == 1){threads = i;}
if (sscanf(data, "physical id : %d", &i) == 1){id = i;}
if (sscanf(data, "cpu MHz : %d", &i) == 1){mhz = i;}
};
};
///\brief Checks the capabilities of the system.
@ -272,73 +302,78 @@ namespace Controller {
/// \api
/// `"capabilities"` requests are always empty:
/// ~~~~~~~~~~~~~~~{.js}
/// {}
///{}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "connectors": { // a list of installed connectors
/// "FLV": { //name of the connector. This is based on the executable filename, with the "MistIn" / "MistConn" prefix stripped.
///{
/// "connectors":{// a list of installed connectors
/// "FLV":{//name of the connector. This is based on the executable filename, with the
/// "MistIn" / "MistConn" prefix stripped.
/// "codecs": [ //supported combinations of codecs.
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously available channels and the codec options for those channels
/// [["H264","H263","VP6"],["AAC","MP3"]] //one such combination, listing simultaneously
/// available channels and the codec options for those channels
/// ],
/// "deps": "HTTP", //dependencies on other connectors, if any.
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of this connector
/// "methods": [ //list of supported request methods
/// {
/// "handler": "http", //what handler to use for this request method. The "http://" part of a URL, without the "://".
/// "priority": 5, // priority of this request method, higher is better.
/// "type": "flash/7" //type of request method - usually name of plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
/// }
/// "desc": "Enables HTTP protocol progressive streaming.", //human-friendly description of
/// this connector "methods": [ //list of supported request methods
///{
/// "handler": "http", //what handler to use for this request method. The "http://" part
/// of a URL, without the "://". "priority": 5, // priority of this request method,
/// higher is better. "type": "flash/7" //type of request method - usually name of
/// plugin followed by the minimal plugin version, or 'HTML5' for pluginless.
///}
/// ],
/// "name": "HTTP_Progressive_FLV", //Full name of this connector.
/// "optional": { //optional parameters
/// "username": { //name of the parameter
/// "help": "Username to drop privileges to - default if unprovided means do not drop privileges", //human-readable help text
/// "name": "Username", //human-readable name of parameter
/// "option": "--username", //command-line option to use
/// "type": "str" //type of option - "str" or "num"
/// }
/// "optional":{//optional parameters
/// "username":{//name of the parameter
/// "help": "Username to drop privileges to - default if unprovided means do not drop
/// privileges", //human-readable help text "name": "Username", //human-readable name of
/// parameter "option": "--username", //command-line option to use "type": "str" //type
/// of option - "str" or "num"
///}
/// //above structure repeated for all (optional) parameters
/// },
///},
/// //above structure repeated, as "required" for required parameters, if any.
/// "socket": "http_progressive_flv", //unix socket this connector listens on, if any
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name and may not be the first or last character.
/// "url_prefix": "/progressive/$/", //URL prefix to match, if any. The $ substitutes the stream name and may not be the first or last character.
/// "url_rel": "/$.flv" //relative URL where to access a stream through this connector.
/// }
/// "url_match": "/$.flv", //URL pattern to match, if any. The $ substitutes the stream name
/// and may not be the first or last character. "url_prefix": "/progressive/$/", //URL
/// prefix to match, if any. The $ substitutes the stream name and may not be the first or
/// last character. "url_rel": "/$.flv" //relative URL where to access a stream through this
/// connector.
///}
/// //... above structure repeated for all installed connectors.
/// },
///},
/// "cpu": [ //a list of installed CPUs
/// {
///{
/// "cores": 4, //amount of cores for this CPU
/// "mhz": 1645, //speed in MHz for this CPU
/// "model": "Intel(R) Core(TM) i7-2630QM CPU @ 2.00GHz", //model identifier, for humans
/// "threads": 8 //amount of simultaneously executing threads that are supported on this CPU
/// }
///}
/// //above structure repeated for all installed CPUs
/// ],
/// "load": {
/// "load":{
/// "fifteen": 72,
/// "five": 81,
/// "memory": 42,
/// "one": 124
/// },
/// "mem": {
///},
/// "mem":{
/// "cached": 1989, //current memory usage of system caches, in MiB
/// "free": 2539, //free memory, in MiB
/// "swapfree": 0, //free swap space, in MiB
/// "swaptotal": 0, //total swap space, in MiB
/// "total": 7898, //total memory, in MiB
/// "used": 3370 //used memory, in MiB (excluding system caches, listed separately)
/// },
///},
/// "speed": 6580, //total speed in MHz of all CPUs cores summed together
/// "threads": 8 //total count of all threads of all CPUs summed together
/// "cpu_use": 105 //Tenths of percent CPU usage - i.e. 105 = 10.5%
/// }
///}
/// ~~~~~~~~~~~~~~~
void checkCapable(JSON::Value & capa){
//capa.null();
void checkCapable(JSON::Value &capa){
// capa.null();
capa.removeMember("cpu");
std::ifstream cpuinfo("/proc/cpuinfo");
if (cpuinfo){
@ -348,29 +383,25 @@ namespace Controller {
while (cpuinfo.good()){
cpuinfo.getline(line, 300);
if (cpuinfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !cpuinfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!cpuinfo.eof()){
cpuinfo.ignore();
cpuinfo.clear();
}
continue;
}
if (memcmp(line, "processor", 9) == 0){
proccount++;
}
if (memcmp(line, "processor", 9) == 0){proccount++;}
cpus[proccount].fill(line);
}
//fix wrong core counts
// fix wrong core counts
std::map<int, int> corecounts;
for (int i = 0; i <= proccount; ++i){
corecounts[cpus[i].id]++;
}
//remove double physical IDs - we only want real CPUs.
for (int i = 0; i <= proccount; ++i){corecounts[cpus[i].id]++;}
// remove double physical IDs - we only want real CPUs.
std::set<int> used_physids;
int total_speed = 0;
int total_threads = 0;
for (int i = 0; i <= proccount; ++i){
if ( !used_physids.count(cpus[i].id)){
if (!used_physids.count(cpus[i].id)){
used_physids.insert(cpus[i].id);
JSON::Value thiscpu;
thiscpu["model"] = cpus[i].model;
@ -398,43 +429,37 @@ namespace Controller {
while (meminfo.good()){
meminfo.getline(line, 300);
if (meminfo.fail()){
//empty lines? ignore them, clear flags, continue
if ( !meminfo.eof()){
// empty lines? ignore them, clear flags, continue
if (!meminfo.eof()){
meminfo.ignore();
meminfo.clear();
}
continue;
}
uint64_t i;
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){
capa["mem"]["total"] = i / 1024;
}
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){
capa["mem"]["free"] = i / 1024;
}
if (sscanf(line, "MemTotal : %" PRIu64 " kB", &i) == 1){capa["mem"]["total"] = i / 1024;}
if (sscanf(line, "MemFree : %" PRIu64 " kB", &i) == 1){capa["mem"]["free"] = i / 1024;}
if (sscanf(line, "SwapTotal : %" PRIu64 " kB", &i) == 1){
capa["mem"]["swaptotal"] = i / 1024;
}
if (sscanf(line, "SwapFree : %" PRIu64 " kB", &i) == 1){
capa["mem"]["swapfree"] = i / 1024;
}
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){
bufcache += i / 1024;
}
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){
bufcache += i / 1024;
}
if (sscanf(line, "Buffers : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
if (sscanf(line, "Cached : %" PRIu64 " kB", &i) == 1){bufcache += i / 1024;}
}
capa["mem"]["used"] = capa["mem"]["total"].asInt() - capa["mem"]["free"].asInt() - bufcache;
capa["mem"]["cached"] = bufcache;
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() + (capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) * 100)
/ capa["mem"]["total"].asInt();
capa["load"]["memory"] = ((capa["mem"]["used"].asInt() +
(capa["mem"]["swaptotal"].asInt() - capa["mem"]["swapfree"].asInt())) *
100) /
capa["mem"]["total"].asInt();
}
std::ifstream loadavg("/proc/loadavg");
if (loadavg){
char line[300];
loadavg.getline(line, 300);
//parse lines here
// parse lines here
float onemin, fivemin, fifteenmin;
if (sscanf(line, "%f %f %f", &onemin, &fivemin, &fifteenmin) == 3){
capa["load"]["one"] = uint64_t(onemin * 100);
@ -448,7 +473,8 @@ namespace Controller {
while (cpustat.getline(line, 300)){
static uint64_t cl_total = 0, cl_idle = 0;
uint64_t c_user, c_nice, c_syst, c_idle, c_total;
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &c_user, &c_nice, &c_syst, &c_idle) == 4){
if (sscanf(line, "cpu %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64, &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){
capa["cpu_use"] = (1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
@ -461,8 +487,6 @@ namespace Controller {
}
}
}
}
}
}// namespace Controller

View file

@ -6,4 +6,3 @@ namespace Controller{
void checkAvailProtocols();
void checkAvailTriggers(); /*LTS*/
}// namespace Controller

View file

@ -1,32 +1,29 @@
#include <stdio.h> // cout, cerr
#include <string>
#include <cstring> // strcpy
#include <sys/stat.h> //stat
#include <mist/json.h>
#include "controller_connectors.h"
#include "controller_storage.h"
#include <cstring> // strcpy
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/json.h>
#include <mist/procs.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <mist/defines.h>
#include "controller_storage.h"
#include "controller_connectors.h"
#include <mist/triggers.h>
#include <mist/shared_memory.h>
#include <mist/util.h>
#include <stdio.h> // cout, cerr
#include <string>
#include <sys/stat.h> //stat
#include <iostream>
#include <unistd.h>
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
static std::set<size_t> needsReload; ///< List of connector indices that needs a reload
static std::map<std::string, pid_t> currentConnectors; ///<The currently running connectors.
static std::map<std::string, pid_t> currentConnectors; ///< The currently running connectors.
void reloadProtocol(size_t indice){
needsReload.insert(indice);
}
void reloadProtocol(size_t indice){needsReload.insert(indice);}
/// Updates the shared memory page with active connectors
void saveActiveConnectors(bool forceOverride){
@ -56,7 +53,7 @@ namespace Controller {
++count;
}
A.setRCount(count);
f.master = false;//Keep the shm page around, don't kill it
f.master = false; // Keep the shm page around, don't kill it
}
/// Reads active connectors from the shared memory pages
@ -66,7 +63,7 @@ namespace Controller {
if (A.isReady()){
INFO_MSG("Reloading existing connectors to complete rolling restart");
for (uint32_t i = 0; i < A.getRCount(); ++i){
char * p = A.getPointer("cmd", i);
char *p = A.getPointer("cmd", i);
if (p != 0 && p[0] != 0){
currentConnectors[p] = A.getInt("pid", i);
Util::Procs::remember(A.getInt("pid", i));
@ -80,9 +77,7 @@ namespace Controller {
/// in preparation of shutdown.
void prepareActiveConnectorsForShutdown(){
IPC::sharedPage f("MstCnns", 4096, false, false);
if (f){
f.master = true;
}
if (f){f.master = true;}
}
/// Forgets all active connectors, preventing them from being killed,
@ -96,45 +91,46 @@ namespace Controller {
currentConnectors.clear();
}
static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, const JSON::Value & argset){
jsonForEachConst(argset, it) {
static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, const JSON::Value &argset){
jsonForEachConst(argset, it){
if (it->isMember("option") && p.isMember(it.key())){
if (it->isMember("type")){
if ((*it)["type"].asStringRef() == "str" && !p[it.key()].isString()){
p[it.key()] = p[it.key()].asString();
}
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "debug"){
if ((*it)["type"].asStringRef() == "uint" || (*it)["type"].asStringRef() == "int" ||
(*it)["type"].asStringRef() == "debug"){
p[it.key()] = JSON::Value(p[it.key()].asInt()).asString();
}
if ((*it)["type"].asStringRef() == "inputlist" && p[it.key()].isArray()){
jsonForEach(p[it.key()], iVal){
(*iVal) = iVal->asString();
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char*)((*iVal).c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
argarr[argnum++] = (char *)((*iVal).c_str());
}
continue;
}
}
if (p[it.key()].asStringRef().size() > 0){
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char*)(p[it.key()].c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
argarr[argnum++] = (char *)(p[it.key()].c_str());
}else{
if (it.key() == "debug"){
if (Util::Config::printDebugLevel != DEBUG){
static std::string debugLvlStr;
debugLvlStr = JSON::Value(Util::Config::printDebugLevel).asString();
argarr[argnum++] = (char*)((*it)["option"].asStringRef().c_str());
argarr[argnum++] = (char*)debugLvlStr.c_str();
argarr[argnum++] = (char *)((*it)["option"].asStringRef().c_str());
argarr[argnum++] = (char *)debugLvlStr.c_str();
}
continue;
}
argarr[argnum++] = (char*)((*it)["option"].c_str());
argarr[argnum++] = (char *)((*it)["option"].c_str());
}
}
}
}
static inline void buildPipedArguments(JSON::Value & p, char * argarr[], const JSON::Value & capabilities){
static inline void buildPipedArguments(JSON::Value &p, char *argarr[], const JSON::Value &capabilities){
int argnum = 0;
static std::string tmparg;
tmparg = Util::getMyPath() + std::string("MistOut") + p["connector"].asStringRef();
@ -142,21 +138,19 @@ namespace Controller {
if (::stat(tmparg.c_str(), &buf) != 0){
tmparg = Util::getMyPath() + std::string("MistConn") + p["connector"].asStringRef();
}
if (::stat(tmparg.c_str(), &buf) != 0){
return;
}
argarr[argnum++] = (char*)tmparg.c_str();
const JSON::Value & pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
if (::stat(tmparg.c_str(), &buf) != 0){return;}
argarr[argnum++] = (char *)tmparg.c_str();
const JSON::Value &pipedCapa = capabilities["connectors"][p["connector"].asStringRef()];
if (pipedCapa.isMember("required")){builPipedPart(p, argarr, argnum, pipedCapa["required"]);}
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
///
/// \triggers
///
/// \triggers
/// 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
@ -165,48 +159,49 @@ namespace Controller {
/// ~~~~~~~~~~~~~~~
/// output listener commandline
/// ~~~~~~~~~~~~~~~
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities){
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities){
std::set<std::string> runningConns;
// used for building args
int zero = 0;
int out = fileno(stdout);
int err = fileno(stderr);
char * argarr[15]; // approx max # of args (with a wide margin)
char *argarr[15]; // approx max # of args (with a wide margin)
int i;
std::string tmp;
jsonForEach(p, ait) {
jsonForEach(p, ait){
std::string prevOnline = (*ait)["online"].asString();
const std::string & connName = (*ait)["connector"].asStringRef();
//do not further parse if there's no connector name
if ( !(*ait).isMember("connector") || connName == ""){
( *ait)["online"] = "Missing connector name";
const std::string &connName = (*ait)["connector"].asStringRef();
// do not further parse if there's no connector name
if (!(*ait).isMember("connector") || connName == ""){
(*ait)["online"] = "Missing connector name";
continue;
}
//ignore connectors that are not installed
// ignore connectors that are not installed
if (!capabilities.isMember("connectors") || !capabilities["connectors"].isMember(connName)){
( *ait)["online"] = "Not installed";
if (( *ait)["online"].asString() != prevOnline){
Log("WARN", connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
(*ait)["online"] = "Not installed";
if ((*ait)["online"].asString() != prevOnline){
Log("WARN",
connName + " connector is enabled but doesn't exist on system! Ignoring connector.");
}
continue;
}
//list connectors that go through HTTP as 'enabled' without actually running them.
const JSON::Value & connCapa = capabilities["connectors"][connName];
// list connectors that go through HTTP as 'enabled' without actually running them.
const JSON::Value &connCapa = capabilities["connectors"][connName];
if (connCapa.isMember("socket") || (connCapa.isMember("deps") && connCapa["deps"].asStringRef() == "HTTP")){
( *ait)["online"] = "Enabled";
(*ait)["online"] = "Enabled";
continue;
}
//check required parameters, skip if anything is missing
// check required parameters, skip if anything is missing
if (connCapa.isMember("required")){
bool gotAll = true;
jsonForEachConst(connCapa["required"], it) {
if ( !(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
jsonForEachConst(connCapa["required"], it){
if (!(*ait).isMember(it.key()) || (*ait)[it.key()].asStringRef().size() < 1){
gotAll = false;
( *ait)["online"] = "Invalid configuration";
if (( *ait)["online"].asString() != prevOnline){
(*ait)["online"] = "Invalid configuration";
if ((*ait)["online"].asString() != prevOnline){
Log("WARN", connName + " connector is missing required parameter " + it.key() + "! Ignoring connector.");
}
break;
@ -214,26 +209,26 @@ namespace Controller {
}
if (!gotAll){continue;}
}
//remove current online status
( *ait).removeMember("online");
// remove current online status
(*ait).removeMember("online");
/// \todo Check dependencies?
//set current online status
// set current online status
std::string myCmd = (*ait).toString();
runningConns.insert(myCmd);
if (currentConnectors.count(myCmd) && Util::Procs::isActive(currentConnectors[myCmd])){
( *ait)["online"] = 1;
//Reload connectors that need it
(*ait)["online"] = 1;
// Reload connectors that need it
if (needsReload.count(ait.num())){
kill(currentConnectors[myCmd], SIGUSR1);
needsReload.erase(ait.num());
}
}else{
( *ait)["online"] = 0;
(*ait)["online"] = 0;
}
}
bool action = false;
//shut down deleted/changed connectors
// shut down deleted/changed connectors
std::map<std::string, pid_t>::iterator it;
if (currentConnectors.size()){
for (it = currentConnectors.begin(); it != currentConnectors.end(); it++){
@ -242,30 +237,29 @@ namespace Controller {
Log("CONF", "Stopping connector " + it->first);
action = true;
Util::Procs::Stop(it->second);
Triggers::doTrigger("OUTPUT_STOP",it->first); //LTS
Triggers::doTrigger("OUTPUT_STOP", it->first); // LTS
}
currentConnectors.erase(it);
if (!currentConnectors.size()){
break;
}
if (!currentConnectors.size()){break;}
it = currentConnectors.begin();
}
}
}
//start up new/changed connectors
// start up new/changed connectors
while (runningConns.size() && conf.is_active){
if (!currentConnectors.count(*runningConns.begin()) || !Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
if (!currentConnectors.count(*runningConns.begin()) ||
!Util::Procs::isActive(currentConnectors[*runningConns.begin()])){
Log("CONF", "Starting connector: " + *runningConns.begin());
action = true;
// clear out old args
for (i=0; i<15; i++){argarr[i] = 0;}
for (i = 0; i < 15; i++){argarr[i] = 0;}
// get args for this connector
JSON::Value p = JSON::fromString(*runningConns.begin());
buildPipedArguments(p, (char **)&argarr, capabilities);
// start piped w/ generated args
currentConnectors[*runningConns.begin()] = Util::Procs::StartPiped(argarr, &zero, &out, &err);
Triggers::doTrigger("OUTPUT_START", *runningConns.begin());//LTS
Triggers::doTrigger("OUTPUT_START", *runningConns.begin()); // LTS
}
runningConns.erase(runningConns.begin());
}
@ -273,5 +267,4 @@ namespace Controller {
return action;
}
}
}// namespace Controller

View file

@ -1,12 +1,12 @@
#include <mist/json.h>
namespace Controller {
namespace Controller{
/// Marks the given protocol as needing a reload (signal USR1) on next check
void reloadProtocol(size_t indice);
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities);
bool CheckProtocols(JSON::Value &p, const JSON::Value &capabilities);
/// Updates the shared memory page with active connectors
void saveActiveConnectors(bool forceOverride = false);
@ -14,7 +14,6 @@ namespace Controller {
/// Reads active connectors from the shared memory pages
void loadActiveConnectors();
/// Deletes the shared memory page with connector information
/// in preparation of shutdown.
void prepareActiveConnectorsForShutdown();
@ -23,5 +22,4 @@ namespace Controller {
/// in preparation of reload.
void prepareActiveConnectorsForReload();
}
}// namespace Controller

View file

@ -1,32 +1,29 @@
#include "controller_license.h"
#include "controller_storage.h"
#include <iostream>
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/encode.h>
#include <mist/encryption.h>
#include <mist/http_parser.h>
#include <mist/socket.h>
#include <mist/auth.h>
#include <mist/timing.h>
#include <mist/config.h>
#include <mist/encryption.h>
#include <mist/encode.h>
#include <mist/downloader.h>
namespace Controller{
uint64_t exitDelay = 0;
static JSON::Value currentLicense;
static uint64_t lastCheck = 0;
static int32_t timeOffset = 0;
static bool everContactedServer = false;
const JSON::Value & getLicense(){
return currentLicense;
}
//PACKAGE_VERSION = MistServer version
//RELEASE = OS + user_ID
const JSON::Value &getLicense(){return currentLicense;}
// PACKAGE_VERSION = MistServer version
// RELEASE = OS + user_ID
void initLicense(){
if (Storage.isMember("license") && Storage.isMember("license_id")){
INFO_MSG("Reading license from storage")
@ -48,30 +45,30 @@ namespace Controller{
#if DEBUG >= DLVL_DEVEL
INFO_MSG("Verifying license against %" PRIu64 ": %s", now, currentLicense.toString().c_str());
#endif
//Print messages for user, if any
// Print messages for user, if any
if (currentLicense.isMember("user_msg") && currentLicense["user_msg"].asStringRef().size()){
WARN_MSG("%s", currentLicense["user_msg"].asStringRef().c_str());
}
//Check time
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") || now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
return false;//license is expired
// Check time
if (!currentLicense.isMember("valid_from") || !currentLicense.isMember("valid_till") ||
now < currentLicense["valid_from"].asInt() || now > currentLicense["valid_till"].asInt()){
return false; // license is expired
}
//Check release/version
if (RELEASE != currentLicense["release"].asStringRef() || PACKAGE_VERSION != currentLicense["version"].asStringRef()){
// Check release/version
if (RELEASE != currentLicense["release"].asStringRef() ||
PACKAGE_VERSION != currentLicense["version"].asStringRef()){
FAIL_MSG("Could not verify license");
return false;
}
//everything seems okay
// everything seems okay
return true;
}
bool checkLicense(){
if (!conf.is_active){return true;}
INFO_MSG("Checking license validity");
if(!everContactedServer && !isLicensed()){
updateLicense("&expired=1");
}
if(!isLicensed()){
if (!everContactedServer && !isLicensed()){updateLicense("&expired=1");}
if (!isLicensed()){
FAIL_MSG("Not licensed, shutting down");
if (currentLicense.isMember("delay") && currentLicense["delay"].asInt()){
exitDelay = currentLicense["delay"].asInt();
@ -83,79 +80,84 @@ namespace Controller{
lastCheck = Util::epoch();
return true;
}
void parseKey(std::string key, char * newKey, unsigned int len){
void parseKey(std::string key, char *newKey, unsigned int len){
memset(newKey, 0, len);
for (size_t i = 0; i < key.size() && i < (len << 1); ++i){
char c = key[i];
newKey[i>>1] |= ((c&15) + (((c&64)>>6) | ((c&64)>>3))) << ((~i&1) << 2);
newKey[i >> 1] |= ((c & 15) + (((c & 64) >> 6) | ((c & 64) >> 3))) << ((~i & 1) << 2);
}
}
void updateLicense(const std::string & extra){
void updateLicense(const std::string &extra){
INFO_MSG("Running license updater %s", extra.c_str());
JSON::Value response;
HTTP::Downloader dl;
dl.dataTimeout = 25;//25-second data timeout, increased from 5s default
dl.dataTimeout = 25; // 25-second data timeout, increased from 5s default
#ifdef SSL
HTTP::URL url("https://releases.mistserver.org/license.php");
if (dl.isProxied()){url.protocol = "http";}
#else
HTTP::URL url("http://releases.mistserver.org/license.php");
#endif
url.args = "release="+Encodings::URL::encode(RELEASE)+"&version="+Encodings::URL::encode(PACKAGE_VERSION)+"&iid="+Encodings::URL::encode(instanceId)+"&hrn="+Encodings::URL::encode(Storage["config"]["serverid"])+"&lid="+currentLicense["lic_id"].asString() + extra;
url.args = "release=" + Encodings::URL::encode(RELEASE) +
"&version=" + Encodings::URL::encode(PACKAGE_VERSION) +
"&iid=" + Encodings::URL::encode(instanceId) +
"&hrn=" + Encodings::URL::encode(Storage["config"]["serverid"]) +
"&lid=" + currentLicense["lic_id"].asString() + extra;
long long currID = currentLicense["lic_id"].asInt();
if (currID){
char aesKey[16];
if (strlen(SUPER_SECRET) >= 32){
parseKey((SUPER_SECRET SUPER_SECRET)+7,aesKey,16);
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
}else{
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
}
for (unsigned int i = 0; i < 8; ++i){
aesKey[15-i] = ((currID >> (i*8)) + aesKey[15-i]) & 0xFF;
aesKey[15 - i] = ((currID >> (i * 8)) + aesKey[15 - i]) & 0xFF;
}
char ivec[16];
memset(ivec, 0, 16);
dl.setHeader("X-IRDGAF", Encodings::Base64::encode(Encryption::AES_Crypt(RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
}
if (!dl.get(url) || !dl.isOk()){
return;
dl.setHeader("X-IRDGAF",
Encodings::Base64::encode(Encryption::AES_Crypt(
RELEASE "|" PACKAGE_VERSION, sizeof(RELEASE "|" PACKAGE_VERSION), aesKey, ivec)));
}
if (!dl.get(url) || !dl.isOk()){return;}
response = JSON::fromString(dl.data());
everContactedServer = true;
//read license
// read license
readLicense(response["lic_id"].asInt(), response["license"].asStringRef(), true);
}
void readLicense(uint64_t licID, const std::string & input, bool fromServer){
void readLicense(uint64_t licID, const std::string &input, bool fromServer){
char aesKey[16];
if (strlen(SUPER_SECRET) >= 32){
parseKey((SUPER_SECRET SUPER_SECRET)+ 7,aesKey,16);
parseKey((SUPER_SECRET SUPER_SECRET) + 7, aesKey, 16);
}else{
parseKey("4E56721C67306E1F473156F755FF5570",aesKey,16);
parseKey("4E56721C67306E1F473156F755FF5570", aesKey, 16);
}
for (unsigned int i = 0; i < 8; ++i){
aesKey[15-i] = ((licID >> (i*8)) + aesKey[15-i]) & 0xFF;
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.
// 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);
//get time stamps and license.
//verify checksum
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0,32)){
// get time stamps and license.
// verify checksum
if (deCrypted.size() < 33 || Secure::md5(deCrypted.substr(32)) != deCrypted.substr(0, 32)){
WARN_MSG("Could not decode license");
return;
}
JSON::Value newLicense = JSON::fromString(deCrypted.substr(32));
if (RELEASE != newLicense["release"].asStringRef() || PACKAGE_VERSION != newLicense["version"].asStringRef()){
if (RELEASE != newLicense["release"].asStringRef() ||
PACKAGE_VERSION != newLicense["version"].asStringRef()){
FAIL_MSG("Could not verify license");
return;
}
@ -164,10 +166,14 @@ namespace Controller{
uint64_t localTime = Util::epoch();
uint64_t remoteTime = newLicense["time"].asInt();
if (localTime > remoteTime + 60){
WARN_MSG("Your computer clock is %" PRIu64 " seconds ahead! Please ensure your computer clock is set correctly.", localTime - remoteTime);
WARN_MSG("Your computer clock is %" PRIu64
" seconds ahead! Please ensure your computer clock is set correctly.",
localTime - remoteTime);
}
if (localTime < remoteTime - 60){
WARN_MSG("Your computer clock is %" PRIu64 " seconds late! Please ensure your computer clock is set correctly.", remoteTime - localTime);
WARN_MSG("Your computer clock is %" PRIu64
" seconds late! Please ensure your computer clock is set correctly.",
remoteTime - localTime);
}
timeOffset = remoteTime - localTime;
@ -179,7 +185,7 @@ namespace Controller{
currentLicense = newLicense;
//Store license here.
// Store license here.
if (currentLicense["store"].asBool()){
if (Storage["license"].asStringRef() != input){
Storage["license"] = input;
@ -188,21 +194,16 @@ namespace Controller{
}
}
}
void licenseLoop(void * np){
void licenseLoop(void *np){
while (conf.is_active){
uint64_t interval = currentLicense["interval"].asInt();
if (Util::epoch() - lastCheck > (interval?interval:3600)){
if (interval){
updateLicense();
}
if (Util::epoch() - lastCheck > (interval ? interval : 3600)){
if (interval){updateLicense();}
checkLicense();
}
Util::sleep(1000);//sleep a bit
}
if (everContactedServer){
updateLicense("&shutdown=1");
Util::sleep(1000); // sleep a bit
}
if (everContactedServer){updateLicense("&shutdown=1");}
}
}
}// namespace Controller

View file

@ -3,16 +3,13 @@
namespace Controller{
extern uint64_t exitDelay;
const JSON::Value & getLicense();
const JSON::Value &getLicense();
void initLicense();
bool isLicensed(); //checks/verifies license time
bool checkLicense(); //Call from Mainloop.
void updateLicense(const std::string & extra = ""); //retrieves update from license server
void licenseLoop(void * np);
void readLicense(uint64_t licId, const std::string & input, bool fromServer = false); //checks/interprets license
}
bool isLicensed(); // checks/verifies license time
bool checkLicense(); // Call from Mainloop.
void updateLicense(const std::string &extra = ""); // retrieves update from license server
void licenseLoop(void *np);
void readLicense(uint64_t licId, const std::string &input, bool fromServer = false); // checks/interprets license
}// namespace Controller

View file

@ -2,39 +2,35 @@
#include "controller_statistics.h"
#include "controller_storage.h"
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <netdb.h>
#include <sys/socket.h>
namespace Controller{
void checkStreamLimits(std::string streamName, long long currentKbps, long long connectedUsers){
if( !Storage["streams"].isMember(streamName)){
return;
}
if( !Storage["streams"][streamName].isMember("limits")){
return;
}
if( !Storage["streams"][streamName]["limits"]){
return;
}
if (!Storage["streams"].isMember(streamName)){return;}
if (!Storage["streams"][streamName].isMember("limits")){return;}
if (!Storage["streams"][streamName]["limits"]){return;}
Storage["streams"][streamName].removeMember("hardlimit_active");
if (Storage["streams"][streamName]["online"].asInt() != 1){
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
if ((*limitIt).isMember("triggered")){
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " reset - stream unavailable.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset - stream unavailable.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " reset - stream unavailable.");
}
(*limitIt).removeMember("triggered");
}
}
return;
}
//run over all limits.
// run over all limits.
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
bool triggerLimit = false;
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
@ -47,23 +43,23 @@ namespace Controller{
if ((*limitIt)["type"].asString() == "hard"){
Storage["streams"][streamName]["hardlimit_active"] = true;
}
if ((*limitIt).isMember("triggered")){
continue;
}
if ((*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " triggered.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " triggered.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() +
" for stream " + streamName + " triggered.");
}
(*limitIt)["triggered"] = true;
}else{
if ( !(*limitIt).isMember("triggered")){
continue;
}
if (!(*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
Log("SLIM", "Softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
}else{
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
Log("HLIM", "Hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " for stream " + streamName + " reset.");
}
(*limitIt).removeMember("triggered");
}
@ -71,12 +67,12 @@ namespace Controller{
}
void checkServerLimits(){
int currentKbps = 0;
int connectedUsers = 0;
std::map<std::string, long long> strmUsers;
std::map<std::string, long long> strmBandw;
/*
if (curConns.size()){
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); it++){
@ -91,22 +87,18 @@ namespace Controller{
}
}
*/
//check stream limits
// check stream limits
if (Storage["streams"].size()){
jsonForEach(Storage["streams"], strmIt){
checkStreamLimits(strmIt.key(), strmBandw[strmIt.key()], strmUsers[strmIt.key()]);
}
}
Storage["config"].removeMember("hardlimit_active");
if ( !Storage["config"]["limits"].size()){
return;
}
if ( !Storage["streams"].size()){
return;
}
if (!Storage["config"]["limits"].size()){return;}
if (!Storage["streams"].size()){return;}
jsonForEach(Storage["config"]["limits"], limitIt){
bool triggerLimit = false;
if ((*limitIt)["name"].asString() == "users" && connectedUsers >= (*limitIt)["value"].asInt()){
@ -119,66 +111,52 @@ namespace Controller{
if ((*limitIt)["type"].asString() == "hard"){
Storage["config"]["hardlimit_active"] = true;
}
if ((*limitIt).isMember("triggered")){
continue;
}
if ((*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " triggered.");
}else{
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " triggered.");
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " triggered.");
}
(*limitIt)["triggered"] = true;
}else{
if ( !(*limitIt).isMember("triggered")){
continue;
}
if (!(*limitIt).isMember("triggered")){continue;}
if ((*limitIt)["type"].asString() == "soft"){
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
Log("SLIM", "Serverwide softlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " reset.");
}else{
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() + " <= " + (*limitIt)["value"].asString() + " reset.");
Log("HLIM", "Serverwide hardlimit " + (*limitIt)["name"].asString() +
" <= " + (*limitIt)["value"].asString() + " reset.");
}
(*limitIt).removeMember("triggered");
}
}
}
bool onList(std::string ip, std::string list){
if (list == ""){
return false;
}
if (list == ""){return false;}
std::string entry;
std::string lowerIpv6;//lower-case
std::string upperIpv6;//full-caps
std::string lowerIpv6; // lower-case
std::string upperIpv6; // full-caps
do{
entry = list.substr(0,list.find(" "));//make sure we have a single entry
entry = list.substr(0, list.find(" ")); // make sure we have a single entry
lowerIpv6 = "::ffff:" + entry;
upperIpv6 = "::FFFF:" + entry;
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){
return true;
}
if (entry == ip || lowerIpv6 == ip || upperIpv6 == ip){return true;}
long long unsigned int starPos = entry.find("*");
if (starPos == std::string::npos){
if (ip == entry){
return true;
}
if (ip == entry){return true;}
}else{
if (starPos == 0){//beginning of the filter
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){
return true;
}
if (starPos == 0){// beginning of the filter
if (ip.substr(ip.length() - entry.size() - 1) == entry.substr(1)){return true;}
}else{
if (starPos == entry.size() - 1){//end of the filter
if (ip.find(entry.substr(0, entry.size() - 1)) == 0 ){
return true;
}
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0 ){
return true;
}
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0 ){
return true;
}
if (starPos == entry.size() - 1){// end of the filter
if (ip.find(entry.substr(0, entry.size() - 1)) == 0){return true;}
if (ip.find(entry.substr(0, lowerIpv6.size() - 1)) == 0){return true;}
if (ip.find(entry.substr(0, upperIpv6.size() - 1)) == 0){return true;}
}else{
Log("CONF","Invalid list entry detected: " + entry);
Log("CONF", "Invalid list entry detected: " + entry);
}
}
}
@ -186,44 +164,38 @@ namespace Controller{
}while (list != "");
return false;
}
std::string hostLookup(std::string ip){
struct sockaddr_in6 sa;
char hostName[1024];
char service[20];
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){
return "\n";
}
if (inet_pton(AF_INET6, ip.c_str(), &(sa.sin6_addr)) != 1){return "\n";}
sa.sin6_family = AF_INET6;
sa.sin6_port = 0;
sa.sin6_flowinfo = 0;
sa.sin6_scope_id = 0;
int tmpRet = getnameinfo((struct sockaddr*)&sa, sizeof sa, hostName, sizeof hostName, service, sizeof service, NI_NAMEREQD );
if ( tmpRet == 0){
return hostName;
}
int tmpRet = getnameinfo((struct sockaddr *)&sa, sizeof sa, hostName, sizeof hostName, service,
sizeof service, NI_NAMEREQD);
if (tmpRet == 0){return hostName;}
return "";
}
bool isBlacklisted(std::string host, std::string streamName, int timeConnected){
std::string myHostName = hostLookup(host);
if (myHostName == "\n"){
return false;
}
if (myHostName == "\n"){return false;}
bool hasWhitelist = false;
bool hostOnWhitelist = false;
if (Storage["streams"].isMember(streamName)){
if (Storage["streams"][streamName].isMember("limits") && Storage["streams"][streamName]["limits"].size()){
if (Storage["streams"][streamName].isMember("limits") &&
Storage["streams"][streamName]["limits"].size()){
jsonForEach(Storage["streams"][streamName]["limits"], limitIt){
if ((*limitIt)["name"].asString() == "host"){
if ((*limitIt)["value"].asString()[0] == '+'){
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
if (myHostName == ""){
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
return true;
}
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
}else{
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if ((*limitIt)["type"].asString() == "hard"){
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
return true;
@ -263,11 +235,9 @@ namespace Controller{
if ((*limitIt)["value"].asString()[0] == '+'){
if (!onList(host, (*limitIt)["value"].asString().substr(1))){
if (myHostName == ""){
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){
return true;
}
if (timeConnected > Storage["config"]["limit_timeout"].asInt()){return true;}
}else{
if ( !onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if (!onList(myHostName, (*limitIt)["value"].asString().substr(1))){
if ((*limitIt)["type"].asString() == "hard"){
Log("HLIM", "Host " + host + " not whitelisted for stream " + streamName);
return true;
@ -310,4 +280,4 @@ namespace Controller{
return false;
}
}
}// namespace Controller

View file

@ -1,6 +1,6 @@
#pragma once
#include <mist/json.h>
#include <map>
#include <mist/json.h>
#include <string>
/*LTS-START*/
@ -18,4 +18,4 @@ namespace Controller{
std::string hostLookup(std::string ip);
bool onList(std::string ip, std::string list);
std::string getCountry(std::string ip);
}
}// namespace Controller

View file

@ -23,7 +23,7 @@ namespace Controller{
/// Immediately starts a push for the given stream to the given target.
/// Simply calls Util::startPush and stores the resulting PID in the local activePushes map.
void startPush(const std::string &stream, std::string &target){
//Cancel if already active
// Cancel if already active
if (isPushActive(stream, target)){return;}
std::string originalTarget = target;
pid_t ret = Util::startPush(stream, target);
@ -40,13 +40,13 @@ namespace Controller{
/// Returns true if the push is currently active, false otherwise.
bool isPushActive(const std::string &streamname, const std::string &target){
while (Controller::conf.is_active && !pushListRead){
Util::sleep(100);
}
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
std::set<pid_t> toWipe;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
if (Util::Procs::isActive(it->first)){
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){return true;}
if (it->second[1u].asStringRef() == streamname && it->second[2u].asStringRef() == target){
return true;
}
}else{
toWipe.insert(it->first);
}
@ -61,13 +61,13 @@ namespace Controller{
/// Stops any pushes matching the stream name (pattern) and target
void stopActivePushes(const std::string &streamname, const std::string &target){
while (Controller::conf.is_active && !pushListRead){
Util::sleep(100);
}
while (Controller::conf.is_active && !pushListRead){Util::sleep(100);}
std::set<pid_t> toWipe;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
if (Util::Procs::isActive(it->first)){
if (it->second[2u].asStringRef() == target && (it->second[1u].asStringRef() == streamname || (*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
if (it->second[2u].asStringRef() == target &&
(it->second[1u].asStringRef() == streamname ||
(*streamname.rbegin() == '+' && it->second[1u].asStringRef().substr(0, streamname.size()) == streamname))){
Util::Procs::Stop(it->first);
}
}else{
@ -87,31 +87,30 @@ namespace Controller{
}
/// Compactly writes the list of pushes to a pointer, assumed to be 8MiB in size
static void writePushList(char * pwo){
char * max = pwo + 8*1024*1024 - 4;
static void writePushList(char *pwo){
char *max = pwo + 8 * 1024 * 1024 - 4;
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
//check if the whole entry will fit
unsigned int entrylen = 4+2+it->second[1u].asStringRef().size()+2+it->second[2u].asStringRef().size()+2+it->second[3u].asStringRef().size();
if (pwo+entrylen >= max){return;}
//write the pid as a 32 bits unsigned integer
// check if the whole entry will fit
unsigned int entrylen = 4 + 2 + it->second[1u].asStringRef().size() + 2 +
it->second[2u].asStringRef().size() + 2 + it->second[3u].asStringRef().size();
if (pwo + entrylen >= max){return;}
// write the pid as a 32 bits unsigned integer
Bit::htobl(pwo, it->first);
pwo += 4;
//write the streamname, original target and target, 2-byte-size-prepended
// write the streamname, original target and target, 2-byte-size-prepended
for (unsigned int i = 1; i < 4; ++i){
const std::string &itm = it->second[i].asStringRef();
Bit::htobs(pwo, itm.size());
memcpy(pwo+2, itm.data(), itm.size());
pwo += 2+itm.size();
memcpy(pwo + 2, itm.data(), itm.size());
pwo += 2 + itm.size();
}
}
//if it fits, write an ending zero to indicate end of page
if (pwo <= max){
Bit::htobl(pwo, 0);
}
// if it fits, write an ending zero to indicate end of page
if (pwo <= max){Bit::htobl(pwo, 0);}
}
///Reads the list of pushes from a pointer, assumed to end in four zeroes
static void readPushList(char * pwo){
/// Reads the list of pushes from a pointer, assumed to end in four zeroes
static void readPushList(char *pwo){
activePushes.clear();
pid_t p = Bit::btohl(pwo);
HIGH_MSG("Recovering pushes: %" PRIu32, (uint32_t)p);
@ -121,8 +120,8 @@ namespace Controller{
pwo += 4;
for (uint8_t i = 0; i < 3; ++i){
uint16_t l = Bit::btohs(pwo);
push.append(std::string(pwo+2, l));
pwo += 2+l;
push.append(std::string(pwo + 2, l));
pwo += 2 + l;
}
INFO_MSG("Recovered push: %s", push.toString().c_str());
Util::Procs::remember(p);
@ -135,11 +134,11 @@ namespace Controller{
/// Loops, checking every second if any pushes need restarting.
void pushCheckLoop(void *np){
{
IPC::sharedPage pushReadPage("MstPush", 8*1024*1024, false, false);
IPC::sharedPage pushReadPage("MstPush", 8 * 1024 * 1024, false, false);
if (pushReadPage.mapped){readPushList(pushReadPage.mapped);}
}
pushListRead = true;
IPC::sharedPage pushPage("MstPush", 8*1024*1024, true, false);
IPC::sharedPage pushPage("MstPush", 8 * 1024 * 1024, true, false);
while (Controller::conf.is_active){
// this scope prevents the configMutex from being locked constantly
{
@ -149,7 +148,8 @@ namespace Controller{
long long curCount = 0;
jsonForEach(Controller::Storage["autopushes"], it){
if (it->size() > 3 && (*it)[3u].asInt() < Util::epoch()){
INFO_MSG("Deleting autopush from %s to %s because end time passed", (*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
INFO_MSG("Deleting autopush from %s to %s because end time passed",
(*it)[0u].asStringRef().c_str(), (*it)[1u].asStringRef().c_str());
stopActivePushes((*it)[0u], (*it)[1u]);
removePush(*it);
break;
@ -173,7 +173,8 @@ namespace Controller{
const std::string &pStr = (*it)[0u].asStringRef();
std::set<std::string> activeStreams = Controller::getActiveStreams(pStr);
if (activeStreams.size()){
for (std::set<std::string>::iterator jt = activeStreams.begin(); jt != activeStreams.end(); ++jt){
for (std::set<std::string>::iterator jt = activeStreams.begin();
jt != activeStreams.end(); ++jt){
std::string streamname = *jt;
std::string target = (*it)[1u];
if (pStr == streamname || (*pStr.rbegin() == '+' && streamname.substr(0, pStr.size()) == pStr)){
@ -201,10 +202,10 @@ namespace Controller{
}
Util::wait(1000); // wait at least a second
}
//keep the pushPage if we are restarting, so we can restore state from it
// keep the pushPage if we are restarting, so we can restore state from it
if (Util::Config::is_restarting){
pushPage.master = false;
//forget about all pushes, so they keep running
// forget about all pushes, so they keep running
for (std::map<pid_t, JSON::Value>::iterator it = activePushes.begin(); it != activePushes.end(); ++it){
Util::Procs::forget(it->first);
}
@ -250,7 +251,8 @@ namespace Controller{
}
long long epo = Util::epoch();
if (newPush.size() > 3 && newPush[3u].asInt() <= epo){
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")", newPush[3u].asInt(), Util::epoch());
WARN_MSG("Automatic push not added: removal time is in the past! (%" PRId64 " <= %" PRIu64 ")",
newPush[3u].asInt(), Util::epoch());
return;
}
bool edited = false;
@ -333,10 +335,13 @@ namespace Controller{
void pushSettings(const JSON::Value &request, JSON::Value &response){
if (request.isObject()){
if (request.isMember("wait")){Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();}
if (request.isMember("maxspeed")){Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();}
if (request.isMember("wait")){
Controller::Storage["push_settings"]["wait"] = request["wait"].asInt();
}
if (request.isMember("maxspeed")){
Controller::Storage["push_settings"]["maxspeed"] = request["maxspeed"].asInt();
}
}
response = Controller::Storage["push_settings"];
}
}
}// namespace Controller

View file

@ -22,5 +22,4 @@ namespace Controller{
// for storing/retrieving settings
void pushSettings(const JSON::Value &request, JSON::Value &response);
}
}// namespace Controller

File diff suppressed because it is too large Load diff

View file

@ -1,126 +1,117 @@
#pragma once
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/defines.h>
#include <mist/json.h>
#include <mist/tinythread.h>
#include <mist/http_parser.h>
#include <mist/socket.h>
#include <string>
#include <map>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/socket.h>
#include <mist/timing.h>
#include <mist/tinythread.h>
#include <string>
/// The STAT_CUTOFF define sets how many seconds of statistics history is kept.
#define STAT_CUTOFF 600
namespace Controller{
namespace Controller {
extern bool killOnExit;
extern unsigned int maxConnsPerIP;
///This function is ran whenever a stream becomes active.
/// This function is ran whenever a stream becomes active.
void streamStarted(std::string stream);
///This function is ran whenever a stream becomes inactive.
/// This function is ran whenever a stream becomes inactive.
void streamStopped(std::string stream);
void updateBandwidthConfig();
struct statLog {
struct statLog{
uint64_t time;
uint64_t lastSecond;
uint64_t down;
uint64_t up;
};
enum sessType {
SESS_UNSET = 0,
SESS_INPUT,
SESS_OUTPUT,
SESS_VIEWER
};
enum sessType{SESS_UNSET = 0, SESS_INPUT, SESS_OUTPUT, SESS_VIEWER};
/// This is a comparison and storage class that keeps sessions apart from each other.
/// 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();
std::string ID;
std::string host;
unsigned int crc;
std::string streamName;
std::string connector;
bool operator== (const sessIndex &o) const;
bool operator!= (const sessIndex &o) const;
bool operator> (const sessIndex &o) const;
bool operator<= (const sessIndex &o) const;
bool operator< (const sessIndex &o) const;
bool operator>= (const sessIndex &o) const;
std::string toStr();
};
class statStorage {
public:
void update(IPC::statExchange & data);
bool hasDataFor(unsigned long long);
statLog & getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
};
/// A session class that keeps track of both current and archived connections.
/// Allows for moving of connections to another session.
class statSession {
private:
uint64_t firstActive;
uint64_t firstSec;
uint64_t lastSec;
uint64_t wipedUp;
uint64_t wipedDown;
std::deque<statStorage> oldConns;
sessType sessionType;
bool tracked;
uint8_t noBWCount;///<Set to 2 when not to count for external bandwidth
public:
statSession();
uint32_t invalidate();
uint32_t kill();
char sync;
std::map<uint64_t, statStorage> curConns;
std::set<std::string> tags;
sessType getSessType();
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);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
uint64_t getLastSecond(uint64_t time);
uint64_t getDown(uint64_t time);
uint64_t getUp();
uint64_t getDown();
uint64_t getUp(uint64_t time);
uint64_t getBpsDown(uint64_t time);
uint64_t getBpsUp(uint64_t time);
uint64_t getBpsDown(uint64_t start, uint64_t end);
uint64_t getBpsUp(uint64_t start, uint64_t end);
class sessIndex{
public:
sessIndex(std::string host, unsigned int crc, std::string streamName, std::string connector);
sessIndex(IPC::statExchange &data);
sessIndex();
std::string ID;
std::string host;
unsigned int crc;
std::string streamName;
std::string connector;
bool operator==(const sessIndex &o) const;
bool operator!=(const sessIndex &o) const;
bool operator>(const sessIndex &o) const;
bool operator<=(const sessIndex &o) const;
bool operator<(const sessIndex &o) const;
bool operator>=(const sessIndex &o) const;
std::string toStr();
};
class statStorage{
public:
void update(IPC::statExchange &data);
bool hasDataFor(unsigned long long);
statLog &getDataFor(unsigned long long);
std::map<unsigned long long, statLog> log;
};
/// A session class that keeps track of both current and archived connections.
/// Allows for moving of connections to another session.
class statSession{
private:
uint64_t firstActive;
uint64_t firstSec;
uint64_t lastSec;
uint64_t wipedUp;
uint64_t wipedDown;
std::deque<statStorage> oldConns;
sessType sessionType;
bool tracked;
uint8_t noBWCount; ///< Set to 2 when not to count for external bandwidth
public:
statSession();
uint32_t invalidate();
uint32_t kill();
char sync;
std::map<uint64_t, statStorage> curConns;
std::set<std::string> tags;
sessType getSessType();
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);
uint64_t getStart();
uint64_t getEnd();
bool isViewerOn(uint64_t time);
bool isViewer();
bool hasDataFor(uint64_t time);
bool hasData();
uint64_t getConnTime(uint64_t time);
uint64_t getLastSecond(uint64_t time);
uint64_t getDown(uint64_t time);
uint64_t getUp();
uint64_t getDown();
uint64_t getUp(uint64_t time);
uint64_t getBpsDown(uint64_t time);
uint64_t getBpsUp(uint64_t time);
uint64_t getBpsDown(uint64_t start, uint64_t end);
uint64_t getBpsUp(uint64_t start, uint64_t end);
};
extern std::map<sessIndex, statSession> sessions;
extern std::map<unsigned long, sessIndex> connToSession;
extern tthread::mutex statsMutex;
struct triggerLog {
struct triggerLog{
uint64_t totalCount;
uint64_t failCount;
uint64_t ms;
@ -128,24 +119,23 @@ namespace Controller {
extern std::map<std::string, triggerLog> triggerStats;
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);
void fillTotals(JSON::Value & req, JSON::Value & rep);
void SharedMemStats(void * config);
void sessions_invalidate(const std::string & streamname);
void sessions_shutdown(JSON::Iter & i);
void sessId_shutdown(const std::string & sessId);
void tag_shutdown(const std::string & tag);
void sessId_tag(const std::string & sessId, const std::string & tag);
void sessions_shutdown(const std::string & streamname, const std::string & protocol = "");
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);
void fillTotals(JSON::Value &req, JSON::Value &rep);
void SharedMemStats(void *config);
void sessions_invalidate(const std::string &streamname);
void sessions_shutdown(JSON::Iter &i);
void sessId_shutdown(const std::string &sessId);
void tag_shutdown(const std::string &tag);
void sessId_tag(const std::string &sessId, const std::string &tag);
void sessions_shutdown(const std::string &streamname, const std::string &protocol = "");
bool hasViewers(std::string streamName);
void writeSessionCache(); /*LTS*/
#define PROMETHEUS_TEXT 0
#define PROMETHEUS_JSON 1
void handlePrometheus(HTTP::Parser & H, Socket::Connection & conn, int mode);
}
void handlePrometheus(HTTP::Parser &H, Socket::Connection &conn, int mode);
}// namespace Controller

View file

@ -1,14 +1,13 @@
#include <sys/stat.h>
#include <iostream>
#include <fstream>
#include "controller_capabilities.h"
#include "controller_storage.h"
#include <algorithm>
#include <mist/timing.h>
#include <mist/shared_memory.h>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <mist/triggers.h> //LTS
#include "controller_storage.h"
#include "controller_capabilities.h"
#include <sys/stat.h>
///\brief Holds everything unique to the controller.
namespace Controller{
@ -26,29 +25,23 @@ namespace Controller{
uint32_t maxLogsRecs = 0;
uint32_t maxAccsRecs = 0;
uint64_t firstLog = 0;
IPC::sharedPage * shmLogs = 0;
Util::RelAccX * rlxLogs = 0;
IPC::sharedPage * shmAccs = 0;
Util::RelAccX * rlxAccs = 0;
IPC::sharedPage * shmStrm = 0;
Util::RelAccX * rlxStrm = 0;
IPC::sharedPage *shmLogs = 0;
Util::RelAccX *rlxLogs = 0;
IPC::sharedPage *shmAccs = 0;
Util::RelAccX *rlxAccs = 0;
IPC::sharedPage *shmStrm = 0;
Util::RelAccX *rlxStrm = 0;
Util::RelAccX * logAccessor(){
return rlxLogs;
}
Util::RelAccX *logAccessor(){return rlxLogs;}
Util::RelAccX * accesslogAccessor(){
return rlxAccs;
}
Util::RelAccX *accesslogAccessor(){return rlxAccs;}
Util::RelAccX * streamsAccessor(){
return rlxStrm;
}
Util::RelAccX *streamsAccessor(){return rlxStrm;}
///\brief Store and print a log message.
///\param kind The type of message.
///\param message The message to be logged.
void Log(const std::string & kind, const std::string & message, const std::string & stream, bool noWriteToLog){
void Log(const std::string &kind, const std::string &message, const std::string &stream, bool noWriteToLog){
if (noWriteToLog){
tthread::lock_guard<tthread::mutex> guard(logMutex);
JSON::Value m;
@ -61,15 +54,13 @@ namespace Controller{
Storage["log"].shrink(100); // limit to 100 log messages
logCounter++;
if (rlxLogs && rlxLogs->isReady()){
if (!firstLog){
firstLog = logCounter;
}
if (!firstLog){firstLog = logCounter;}
rlxLogs->setRCount(logCounter > maxLogsRecs ? maxLogsRecs : logCounter);
rlxLogs->setDeleted(logCounter > rlxLogs->getRCount() ? logCounter - rlxLogs->getRCount() : firstLog);
rlxLogs->setInt("time", logTime, logCounter-1);
rlxLogs->setString("kind", kind, logCounter-1);
rlxLogs->setString("msg", message, logCounter-1);
rlxLogs->setString("strm", stream, logCounter-1);
rlxLogs->setInt("time", logTime, logCounter - 1);
rlxLogs->setString("kind", kind, logCounter - 1);
rlxLogs->setString("msg", message, logCounter - 1);
rlxLogs->setString("strm", stream, logCounter - 1);
rlxLogs->setEndPos(logCounter);
}
}else{
@ -77,10 +68,12 @@ namespace Controller{
}
}
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags){
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
const std::string &tags){
if (rlxAccs && rlxAccs->isReady()){
uint64_t newEndPos = rlxAccs->getEndPos();
rlxAccs->setRCount(newEndPos+1 > maxLogsRecs ? maxAccsRecs : newEndPos+1);
rlxAccs->setRCount(newEndPos + 1 > maxLogsRecs ? maxAccsRecs : newEndPos + 1);
rlxAccs->setDeleted(newEndPos + 1 > maxAccsRecs ? newEndPos + 1 - maxAccsRecs : 0);
rlxAccs->setInt("time", Util::epoch(), newEndPos);
rlxAccs->setString("session", sessId, newEndPos);
@ -95,16 +88,23 @@ namespace Controller{
}
if (Triggers::shouldTrigger("USER_END", strm)){
std::stringstream plgen;
plgen << sessId << "\n" << strm << "\n" << conn << "\n" << host << "\n" << duration << "\n" << up << "\n" << down << "\n" << tags;
plgen << sessId << "\n"
<< strm << "\n"
<< conn << "\n"
<< host << "\n"
<< duration << "\n"
<< up << "\n"
<< down << "\n"
<< tags;
std::string payload = plgen.str();
Triggers::doTrigger("USER_END", payload, strm);
}
}
void normalizeTrustedProxies(JSON::Value & tp){
//First normalize to arrays
void normalizeTrustedProxies(JSON::Value &tp){
// First normalize to arrays
if (!tp.isArray()){tp.append(tp.asString());}
//Now, wipe any empty entries, and convert spaces to array entries
// Now, wipe any empty entries, and convert spaces to array entries
std::set<std::string> n;
jsonForEach(tp, jit){
if (!jit->isString()){*jit = jit->asString();}
@ -116,16 +116,14 @@ namespace Controller{
while (tmp.find(' ') != std::string::npos){
size_t p = tmp.find(' ');
n.insert(tmp.substr(0, p));
tmp.erase(0, p+1);
tmp.erase(0, p + 1);
}
if (tmp.size()){n.insert(tmp);}
}
n.erase("");
//Re-write the entire array, which is now normalized
// Re-write the entire array, which is now normalized
tp.shrink(0);
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){
tp.append(*it);
}
for (std::set<std::string>::iterator it = n.begin(); it != n.end(); ++it){tp.append(*it);}
}
///\brief Write contents to Filename
@ -141,7 +139,7 @@ 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, true); // max 1M of logs cached
if (!shmLogs->mapped){
FAIL_MSG("Could not open memory page for logs buffer");
return;
@ -156,9 +154,9 @@ namespace Controller{
rlxLogs->addField("strm", RAX_128STRING);
rlxLogs->setReady();
}
maxLogsRecs = (1024*1024 - rlxLogs->getOffset()) / rlxLogs->getRSize();
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, true); // max 1M of accesslogs cached
if (!shmAccs->mapped){
FAIL_MSG("Could not open memory page for access logs buffer");
return;
@ -176,9 +174,9 @@ namespace Controller{
rlxAccs->addField("tags", RAX_256STRING);
rlxAccs->setReady();
}
maxAccsRecs = (1024*1024 - rlxAccs->getOffset()) / rlxAccs->getRSize();
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, true); // max 1M of stream data
if (!shmStrm->mapped){
FAIL_MSG("Could not open memory page for stream data");
return;
@ -192,7 +190,7 @@ namespace Controller{
rlxStrm->addField("outputs", RAX_64UINT);
rlxStrm->setReady();
}
rlxStrm->setRCount((1024*1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
rlxStrm->setRCount((1024 * 1024 - rlxStrm->getOffset()) / rlxStrm->getRSize());
}
void deinitState(bool leaveBehind){
@ -209,7 +207,7 @@ namespace Controller{
shmAccs->master = false;
shmStrm->master = false;
}
Util::RelAccX * tmp = rlxLogs;
Util::RelAccX *tmp = rlxLogs;
rlxLogs = 0;
delete tmp;
delete shmLogs;
@ -239,7 +237,7 @@ namespace Controller{
skip.insert("online");
skip.insert("error");
tmp.assignFrom(Controller::Storage, skip);
if ( !Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
if (!Controller::WriteFile(Controller::conf.getString("configFile"), tmp.toString())){
ERROR_MSG("Error writing config to %s", Controller::conf.getString("configFile").c_str());
std::cout << "**Config**" << std::endl;
std::cout << tmp.toString() << std::endl;
@ -247,12 +245,12 @@ namespace Controller{
}
}
void writeCapabilities(){
std::string temp = capabilities.toPacked();
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size()+100, true, false);
static IPC::sharedPage mistCapaOut(SHM_CAPA, temp.size() + 100, true, false);
if (!mistCapaOut.mapped){
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your system?");
FAIL_MSG("Could not open capabilities config for writing! Is shared memory enabled on your "
"system?");
return;
}
Util::RelAccX A(mistCapaOut.mapped, false);
@ -270,7 +268,7 @@ namespace Controller{
if (Storage["config"]["trustedproxy"].isArray()){
jsonForEachConst(Storage["config"]["trustedproxy"], jit){
if (tmpProxy.size()){
tmpProxy += " "+jit->asString();
tmpProxy += " " + jit->asString();
}else{
tmpProxy = jit->asString();
}
@ -280,11 +278,12 @@ namespace Controller{
}
if (proxy_written != tmpProxy){
proxy_written = tmpProxy;
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size()+100, true, false);
static IPC::sharedPage mistProxOut(SHM_PROXY, proxy_written.size() + 100, true, false);
mistProxOut.close();
mistProxOut.init(SHM_PROXY, proxy_written.size()+100, true, false);
mistProxOut.init(SHM_PROXY, proxy_written.size() + 100, true, false);
if (!mistProxOut.mapped){
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on your system?");
FAIL_MSG("Could not open trusted proxy config for writing! Is shared memory enabled on "
"your system?");
return;
}else{
Util::RelAccX A(mistProxOut.mapped, false);
@ -303,11 +302,12 @@ namespace Controller{
if (Storage["config"]["protocols"].compareExcept(proto_written, skip)){return;}
proto_written.assignFrom(Storage["config"]["protocols"], skip);
std::string temp = proto_written.toPacked();
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size()+100, true, false);
static IPC::sharedPage mistProtoOut(SHM_PROTO, temp.size() + 100, true, false);
mistProtoOut.close();
mistProtoOut.init(SHM_PROTO, temp.size()+100, true, false);
mistProtoOut.init(SHM_PROTO, temp.size() + 100, true, false);
if (!mistProtoOut.mapped){
FAIL_MSG("Could not open protocol config for writing! Is shared memory enabled on your system?");
FAIL_MSG(
"Could not open protocol config for writing! Is shared memory enabled on your system?");
return;
}
// write config
@ -322,7 +322,7 @@ namespace Controller{
}
}
void writeStream(const std::string & sName, const JSON::Value & sConf){
void writeStream(const std::string &sName, const JSON::Value &sConf){
static std::map<std::string, JSON::Value> writtenStrms;
static std::map<std::string, IPC::sharedPage> pages;
static std::set<std::string> skip;
@ -338,12 +338,12 @@ namespace Controller{
}
if (!writtenStrms.count(sName) || !writtenStrms[sName].compareExcept(sConf, skip)){
writtenStrms[sName].assignFrom(sConf, skip);
IPC::sharedPage & P = pages[sName];
IPC::sharedPage &P = pages[sName];
std::string temp = writtenStrms[sName].toPacked();
P.close();
char tmpBuf[NAME_BUFFER_SIZE];
snprintf(tmpBuf, NAME_BUFFER_SIZE, SHM_STREAM_CONF, sName.c_str());
P.init(tmpBuf, temp.size()+100, true, false);
P.init(tmpBuf, temp.size() + 100, true, false);
if (!P){
writtenStrms.erase(sName);
pages.erase(sName);
@ -374,12 +374,10 @@ namespace Controller{
}
{
//Global configuration options, if any
// Global configuration options, if any
IPC::sharedPage globCfg;
globCfg.init(SHM_GLOBAL_CONF, 4096, false, false);
if (!globCfg.mapped){
globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);
}
if (!globCfg.mapped){globCfg.init(SHM_GLOBAL_CONF, 4096, true, false);}
if (globCfg.mapped){
Util::RelAccX globAccX(globCfg.mapped, false);
if (!globAccX.isReady()){
@ -389,11 +387,10 @@ namespace Controller{
globAccX.setReady();
}
globAccX.setString("defaultStream", Storage["config"]["defaultStream"].asStringRef());
globCfg.master = false;//leave the page after closing
globCfg.master = false; // leave the page after closing
}
}
/*LTS-START*/
static std::map<std::string, IPC::sharedPage> pageForType; // should contain one page for every trigger type
static JSON::Value writtenTrigs;
@ -438,7 +435,9 @@ namespace Controller{
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
if (namesArray.size()){
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
}
}
}
}
@ -457,7 +456,9 @@ namespace Controller{
namesArray.append(tmpBuf, 4);
namesArray.append(shIt->asString());
}
if (namesArray.size()){memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));}
if (namesArray.size()){
memcpy(strmP, namesArray.data(), std::min(namesArray.size(), (size_t)256));
}
}
}
if (triggIt->isMember("params") && !(*triggIt)["params"].isNull()){
@ -492,5 +493,4 @@ namespace Controller{
}
/*LTS-END*/
}
}
}// namespace Controller

View file

@ -1,44 +1,45 @@
#include <string>
#include <mist/json.h>
#include <mist/config.h>
#include <mist/json.h>
#include <mist/tinythread.h>
#include <mist/util.h>
#include <string>
namespace Controller {
extern std::string instanceId; ///<global storage of instanceId (previously uniqID) is set in controller.cpp
extern std::string prometheus; ///< Prometheus access string
extern std::string accesslog; ///< Where to write the access log
extern Util::Config conf;///< Global storage of configuration.
extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex;///< Mutex for log thread.
extern tthread::mutex configMutex;///< Mutex for server config access.
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
extern bool isTerminal;///< True if connected to a terminal and not a log file.
extern bool isColorized;///< True if we colorize the output
extern uint64_t logCounter; ///<Count of logged messages since boot
Util::RelAccX * logAccessor();
Util::RelAccX * accesslogAccessor();
Util::RelAccX * streamsAccessor();
namespace Controller{
extern std::string instanceId; ///< global storage of instanceId (previously uniqID) is set in controller.cpp
extern std::string prometheus; ///< Prometheus access string
extern std::string accesslog; ///< Where to write the access log
extern Util::Config conf; ///< Global storage of configuration.
extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex; ///< Mutex for log thread.
extern tthread::mutex configMutex; ///< Mutex for server config access.
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
extern bool isTerminal; ///< True if connected to a terminal and not a log file.
extern bool isColorized; ///< True if we colorize the output
extern uint64_t logCounter; ///< Count of logged messages since boot
Util::RelAccX *logAccessor();
Util::RelAccX *accesslogAccessor();
Util::RelAccX *streamsAccessor();
/// Store and print a log message.
void Log(const std::string & kind, const std::string & message, const std::string & stream = "", bool noWriteToLog = false);
void logAccess(const std::string & sessId, const std::string & strm, const std::string & conn, const std::string & host, uint64_t duration, uint64_t up, uint64_t down, const std::string & tags);
void normalizeTrustedProxies(JSON::Value & tp);
void Log(const std::string &kind, const std::string &message, const std::string &stream = "",
bool noWriteToLog = false);
void logAccess(const std::string &sessId, const std::string &strm, const std::string &conn,
const std::string &host, uint64_t duration, uint64_t up, uint64_t down,
const std::string &tags);
void normalizeTrustedProxies(JSON::Value &tp);
/// Write contents to Filename.
bool WriteFile(std::string Filename, std::string contents);
void writeConfigToDisk();
void handleMsg(void * err);
void handleMsg(void *err);
void initState();
void deinitState(bool leaveBehind);
void writeConfig();
void writeStream(const std::string & sName, const JSON::Value & sConf);
void writeStream(const std::string &sName, const JSON::Value &sConf);
void writeCapabilities();
void writeProtocols();
}
}// namespace Controller

View file

@ -1,87 +1,83 @@
#include <mist/procs.h>
#include <mist/config.h>
#include <mist/timing.h>
#include <mist/stream.h>
#include <mist/dtsc.h>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include "controller_streams.h"
#include "controller_capabilities.h"
#include "controller_storage.h"
#include "controller_statistics.h"
#include "controller_limits.h" /*LTS*/
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include <map>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/procs.h>
#include <mist/shared_memory.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/triggers.h> //LTS
#include <sys/stat.h>
#include <map>
///\brief Holds everything unique to the controller.
namespace Controller {
namespace Controller{
std::map<std::string, pid_t> inputProcesses;
///\brief Checks whether two streams are equal.
///\param one The first stream for the comparison.
///\param two The second stream for the comparison.
///\return True if the streams are equal, false otherwise.
bool streamsEqual(JSON::Value & one, JSON::Value & two){
bool streamsEqual(JSON::Value &one, JSON::Value &two){
if (one.isMember("source") != two.isMember("source") || one["source"] != two["source"]){
return false;
}
/// \todo Change this to use capabilities["inputs"] and only compare required/optional parameters.
/// \todo Maybe change this to check for correct source and/or required parameters.
//temporary: compare the two JSON::Value objects.
return one==two;
//nothing different? return true by default
//return true;
/// \todo Change this to use capabilities["inputs"] and only compare required/optional
/// parameters. \todo Maybe change this to check for correct source and/or required parameters.
// temporary: compare the two JSON::Value objects.
return one == two;
// nothing different? return true by default
// return true;
}
///\brief Checks the validity of a stream, updates internal stream status.
///\param name The name of the stream
///\param data The corresponding configuration values.
void checkStream(std::string name, JSON::Value & data){
void checkStream(std::string name, JSON::Value &data){
if (!data.isMember("name")){data["name"] = name;}
std::string prevState = data["error"].asStringRef();
data["online"] = (std::string)"Checking...";
data["online"] = (std::string) "Checking...";
data.removeMember("error");
data.removeNullMembers();
switch (Util::getStreamStatus(name)){
case STRMSTAT_OFF:
//Do nothing
break;
case STRMSTAT_INIT:
data["online"] = 2;
data["error"] = "Initializing...";
return;
case STRMSTAT_BOOT:
data["online"] = 2;
data["error"] = "Loading...";
return;
case STRMSTAT_WAIT:
data["online"] = 2;
data["error"] = "Waiting for data...";
return;
case STRMSTAT_READY:
data["online"] = 1;
return;
case STRMSTAT_SHUTDOWN:
data["online"] = 2;
data["error"] = "Shutting down...";
return;
default:
//Unknown state?
data["error"] = "Unrecognized stream state";
break;
case STRMSTAT_OFF:
// Do nothing
break;
case STRMSTAT_INIT:
data["online"] = 2;
data["error"] = "Initializing...";
return;
case STRMSTAT_BOOT:
data["online"] = 2;
data["error"] = "Loading...";
return;
case STRMSTAT_WAIT:
data["online"] = 2;
data["error"] = "Waiting for data...";
return;
case STRMSTAT_READY: data["online"] = 1; return;
case STRMSTAT_SHUTDOWN:
data["online"] = 2;
data["error"] = "Shutting down...";
return;
default:
// Unknown state?
data["error"] = "Unrecognized stream state";
break;
}
data["online"] = 0;
std::string URL;
if (data.isMember("channel") && data["channel"].isMember("URL")){
URL = data["channel"]["URL"].asString();
}
if (data.isMember("source")){
URL = data["source"].asString();
}
if (data.isMember("source")){URL = data["source"].asString();}
if (!URL.size()){
data["error"] = "Stream offline: Missing source parameter!";
if (data["error"].asStringRef() != prevState){
@ -89,14 +85,13 @@ namespace Controller {
}
return;
}
//Old style always on
if (data.isMember("udpport") && data["udpport"].asStringRef().size() && (!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
const std::string & udpPort = data["udpport"].asStringRef();
const std::string & multicast = data["multicastinterface"].asStringRef();
URL = "tsudp://"+udpPort;
if (multicast.size()){
URL.append("/"+multicast);
}
// Old style always on
if (data.isMember("udpport") && data["udpport"].asStringRef().size() &&
(!inputProcesses.count(name) || !Util::Procs::isRunning(inputProcesses[name]))){
const std::string &udpPort = data["udpport"].asStringRef();
const std::string &multicast = data["multicastinterface"].asStringRef();
URL = "tsudp://" + udpPort;
if (multicast.size()){URL.append("/" + multicast);}
// False: start TS input
INFO_MSG("No TS input for stream %s, starting it: %s", name.c_str(), URL.c_str());
std::deque<std::string> command;
@ -108,35 +103,29 @@ namespace Controller {
int stdOut = 1;
int stdErr = 2;
pid_t program = Util::Procs::StartPiped(command, &stdIn, &stdOut, &stdErr);
if (program){
inputProcesses[name] = program;
}
if (program){inputProcesses[name] = program;}
}
//new style always on
// new style always on
if (data.isMember("always_on") && data["always_on"].asBool()){
INFO_MSG("Starting always-on input %s: %s", name.c_str(), URL.c_str());
std::map<std::string, std::string> empty_overrides;
pid_t program = 0;
Util::startInput(name, URL, true, false, empty_overrides, &program);
if (program){
inputProcesses[name] = program;
}
if (program){inputProcesses[name] = program;}
}
//non-VoD stream
// non-VoD stream
if (URL.substr(0, 1) != "/"){return;}
Util::streamVariables(URL, name, "");
//VoD-style stream
// VoD-style stream
struct stat fileinfo;
if (stat(URL.c_str(), &fileinfo) != 0){
if (stat(URL.c_str(), &fileinfo) != 0){
data["error"] = "Stream offline: Not found: " + URL;
if (data["error"].asStringRef() != prevState){
Log("BUFF", "Warning for VoD stream " + name + "! File not found: " + URL);
}
return;
}
if ( !data.isMember("error")){
data["error"] = "Available";
}
if (!data.isMember("error")){data["error"] = "Available";}
data["online"] = 2;
return;
}
@ -144,12 +133,10 @@ namespace Controller {
///\brief Checks all streams, restoring if needed.
///\param data The stream configuration for the server.
///\returns True if the server status changed
bool CheckAllStreams(JSON::Value & data){
jsonForEach(data, jit) {
checkStream(jit.key(), (*jit));
}
bool CheckAllStreams(JSON::Value &data){
jsonForEach(data, jit){checkStream(jit.key(), (*jit));}
//check for changes in config or streams
// check for changes in config or streams
static JSON::Value strlist;
if (strlist["config"] != Storage["config"] || strlist["streams"] != Storage["streams"]){
strlist["config"] = Storage["config"];
@ -158,31 +145,31 @@ namespace Controller {
}
return false;
}
///
/// \triggers
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to the server configuration. If cancelled, the stream is not added. Its payload is:
/// \triggers
/// The `"STREAM_ADD"` trigger is stream-specific, and is ran whenever a new stream is added to
/// the server configuration. If cancelled, the stream is not added. Its payload is:
/// ~~~~~~~~~~~~~~~
/// streamname
/// configuration in JSON format
/// ~~~~~~~~~~~~~~~
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration is changed. If cancelled, the configuration is not changed. Its payload is:
/// The `"STREAM_CONFIG"` trigger is stream-specific, and is ran whenever a stream's configuration
/// is changed. If cancelled, the configuration is not changed. Its payload is:
/// ~~~~~~~~~~~~~~~
/// streamname
/// configuration in JSON format
/// ~~~~~~~~~~~~~~~
///
void AddStreams(JSON::Value & in, JSON::Value & out){
//check for new streams and updates
jsonForEach(in, jit) {
///
void AddStreams(JSON::Value &in, JSON::Value &out){
// check for new streams and updates
jsonForEach(in, jit){
if (out.isMember(jit.key())){
if ( !streamsEqual((*jit), out[jit.key()])){
if (!streamsEqual((*jit), out[jit.key()])){
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_CONFIG")){
std::string payload = jit.key()+"\n"+jit->toString();
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){
continue;
}
if (Triggers::shouldTrigger("STREAM_CONFIG")){
std::string payload = jit.key() + "\n" + jit->toString();
if (!Triggers::doTrigger("STREAM_CONFIG", payload, jit.key())){continue;}
}
/*LTS-END*/
out[jit.key()] = (*jit);
@ -198,16 +185,15 @@ namespace Controller {
if (!checked.size()){
FAIL_MSG("Invalid stream name '%s'", jit.key().c_str());
}else{
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(), checked.c_str());
FAIL_MSG("Invalid stream name '%s'. Suggested alternative: '%s'", jit.key().c_str(),
checked.c_str());
}
continue;
}
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_ADD")){
std::string payload = jit.key()+"\n"+jit->toString();
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){
continue;
}
if (Triggers::shouldTrigger("STREAM_ADD")){
std::string payload = jit.key() + "\n" + jit->toString();
if (!Triggers::doTrigger("STREAM_ADD", payload, jit.key())){continue;}
}
/*LTS-END*/
out[jit.key()] = (*jit);
@ -227,25 +213,28 @@ namespace Controller {
/// \api
/// `"streams"` requests take the form of:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "streamname_here": { //name of the stream
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP or hostname of the machine allowed to push live data. Empty means everyone is allowed to push live data.
/// "DVR": 30000 //optional. For live streams, indicates the requested minimum size of the available DVR buffer in milliseconds.
/// },
///{
/// "streamname_here":{//name of the stream
/// "source": "/mnt/media/a.dtsc" //full path to a VoD file, or "push://" followed by the IP
/// or hostname of the machine allowed to push live data. Empty means everyone is allowed to
/// push live data. "DVR": 30000 //optional. For live streams, indicates the requested minimum
/// size of the available DVR buffer in milliseconds.
///},
/// //the above structure repeated for all configured streams
/// }
///}
/// ~~~~~~~~~~~~~~~
/// and are responded to as:
/// ~~~~~~~~~~~~~~~{.js}
/// {
/// "streamname_here": { //name of the configured stream
/// "error": "Available", //error state, if any. "Available" is a special value for VoD streams, indicating it has no current viewers (is not active), but is available for activation.
/// "h_meta": 1398113185, //unix time the stream header (if any) was last processed for metadata
/// "l_meta": 1398115447, //unix time the stream itself was last processed for metadata
/// "meta": { //available metadata for this stream, if any
///{
/// "streamname_here":{//name of the configured stream
/// "error": "Available", //error state, if any. "Available" is a special value for VoD
/// streams, indicating it has no current viewers (is not active), but is available for
/// activation. "h_meta": 1398113185, //unix time the stream header (if any) was last
/// processed for metadata "l_meta": 1398115447, //unix time the stream itself was last
/// processed for metadata "meta":{//available metadata for this stream, if any
/// "format": "dtsc", //detected media source format
/// "tracks": { //list of tracks in this stream
/// "audio_AAC_2ch_48000hz_2": {//human-readable track name
/// "tracks":{//list of tracks in this stream
/// "audio_AAC_2ch_48000hz_2":{//human-readable track name
/// "bps": 16043,
/// "channels": 2,
/// "codec": "AAC",
@ -256,54 +245,50 @@ namespace Controller {
/// "size": 16,
/// "trackid": 2,
/// "type": "audio"
/// },
///},
/// //the above structure repeated for all tracks
/// },
///},
/// "vod": 1 //indicates VoD stream, or "live" to indicated live stream.
/// },
///},
/// "name": "a", //the stream name, guaranteed to be equal to the object name.
/// "online": 2, //online state. 0 = error, 1 = active, 2 = inactive.
/// "source": "/home/thulinma/a.dtsc" //source for this stream, as configured.
/// },
///},
/// //the above structure repeated for all configured streams
/// }
///}
/// ~~~~~~~~~~~~~~~
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave it out of the request. To add a stream, simply add it to the request. To edit a stream, simply edit it in the request. The LTS edition has additional requests that allow per-stream changing of the configuration.
void CheckStreams(JSON::Value & in, JSON::Value & out){
//check for new streams and updates
/// Through this request, ALL streams must always be configured. To remove a stream, simply leave
/// it out of the request. To add a stream, simply add it to the request. To edit a stream, simply
/// edit it in the request. The LTS edition has additional requests that allow per-stream changing
/// of the configuration.
void CheckStreams(JSON::Value &in, JSON::Value &out){
// check for new streams and updates
AddStreams(in, out);
//check for deleted streams
// check for deleted streams
std::set<std::string> toDelete;
jsonForEach(out, jit) {
if ( !in.isMember(jit.key())){
toDelete.insert(jit.key());
}
jsonForEach(out, jit){
if (!in.isMember(jit.key())){toDelete.insert(jit.key());}
}
//actually delete the streams
// actually delete the streams
while (toDelete.size() > 0){
std::string deleting = *(toDelete.begin());
deleteStream(deleting, out);
toDelete.erase(deleting);
}
//update old-style configurations to new-style
jsonForEach(in, jit) {
// update old-style configurations to new-style
jsonForEach(in, jit){
if (jit->isMember("channel")){
if ( !jit->isMember("source")){
(*jit)["source"] = (*jit)["channel"]["URL"];
}
if (!jit->isMember("source")){(*jit)["source"] = (*jit)["channel"]["URL"];}
jit->removeMember("channel");
}
if (jit->isMember("preset")){
jit->removeMember("preset");
}
if (jit->isMember("preset")){jit->removeMember("preset");}
}
}
/// Deletes the stream (name) from the config (out), optionally also deleting the VoD source file if sourceFileToo is true.
int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo) {
int deleteStream(const std::string &name, JSON::Value &out, bool sourceFileToo){
int ret = 0;
if (sourceFileToo){
std::string cleaned = name;
@ -311,9 +296,7 @@ namespace Controller {
std::string strmSource;
if (Util::getStreamStatus(cleaned) != STRMSTAT_OFF){
DTSC::Meta mData = Util::getStreamMeta(cleaned);
if (mData.sourceURI.size()){
strmSource = mData.sourceURI;
}
if (mData.sourceURI.size()){strmSource = mData.sourceURI;}
}
if (!strmSource.size()){
std::string smp = cleaned.substr(0, cleaned.find_first_of("+ "));
@ -338,60 +321,57 @@ namespace Controller {
}
}
if (noFile){
WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous source file.", cleaned.c_str());
WARN_MSG("Not deleting source for stream %s, since the stream does not have an unambiguous "
"source file.",
cleaned.c_str());
}else{
Util::streamVariables(strmSource, cleaned);
if (!strmSource.size()){
FAIL_MSG("Could not delete source for stream %s: unable to detect stream source URI using any method", cleaned.c_str());
FAIL_MSG("Could not delete source for stream %s: unable to detect stream source URI "
"using any method",
cleaned.c_str());
}else{
if (unlink(strmSource.c_str())){
FAIL_MSG("Could not delete source %s for %s: %s (%d)", strmSource.c_str(), cleaned.c_str(), strerror(errno), errno);
FAIL_MSG("Could not delete source %s for %s: %s (%d)", strmSource.c_str(),
cleaned.c_str(), strerror(errno), errno);
}else{
++ret;
Log("STRM", "Deleting source file for stream "+cleaned+": "+strmSource);
//Delete dtsh, ignore failures
if (!unlink((strmSource+".dtsh").c_str())){
++ret;
}
Log("STRM", "Deleting source file for stream " + cleaned + ": " + strmSource);
// Delete dtsh, ignore failures
if (!unlink((strmSource + ".dtsh").c_str())){++ret;}
}
}
}
}
if (!out.isMember(name)){
return ret;
}
if (!out.isMember(name)){return ret;}
/*LTS-START*/
if(Triggers::shouldTrigger("STREAM_REMOVE")){
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){
return ret;
}
if (Triggers::shouldTrigger("STREAM_REMOVE")){
if (!Triggers::doTrigger("STREAM_REMOVE", name, name)){return ret;}
}
/*LTS-END*/
Log("STRM", "Deleted stream " + name);
out.removeMember(name);
Controller::writeStream(name, JSON::Value());//Null JSON value = delete
Controller::writeStream(name, JSON::Value()); // Null JSON value = delete
++ret;
ret *= -1;
if (inputProcesses.count(name)){
pid_t procId = inputProcesses[name];
if (Util::Procs::isRunning(procId)){
Util::Procs::Stop(procId);
}
if (Util::Procs::isRunning(procId)){Util::Procs::Stop(procId);}
inputProcesses.erase(name);
}
return ret;
}
bool isMatch(const std::string & source, const std::string & match){
std::string front = match.substr(0,match.find('*'));
std::string back = match.substr(match.find('*')+1);
//if the length of the source is smaller than the front and back matching parts together, it can never match
if (source.size() < front.size()+back.size()){return false;}
return (source.substr(0,front.size()) == front && source.substr(source.size()-back.size()) == back);
bool isMatch(const std::string &source, const std::string &match){
std::string front = match.substr(0, match.find('*'));
std::string back = match.substr(match.find('*') + 1);
// if the length of the source is smaller than the front and back matching parts together, it can never match
if (source.size() < front.size() + back.size()){return false;}
return (source.substr(0, front.size()) == front && source.substr(source.size() - back.size()) == back);
}
void checkParameters(JSON::Value & streamObj){
JSON::Value & inpt = Controller::capabilities["inputs"];
void checkParameters(JSON::Value &streamObj){
JSON::Value &inpt = Controller::capabilities["inputs"];
std::string match;
jsonForEach(inpt, it){
if ((*it)["source_match"].isArray()){
@ -408,11 +388,8 @@ namespace Controller {
}
}
if (match != ""){
jsonForEach(inpt[match]["hardcoded"], it){
streamObj[it.key()] = *it;
}
jsonForEach(inpt[match]["hardcoded"], it){streamObj[it.key()] = *it;}
}
}
} //Controller namespace
}// namespace Controller

View file

@ -1,17 +1,17 @@
#include <mist/json.h>
namespace Controller {
bool streamsEqual(JSON::Value & one, JSON::Value & two);
void checkStream(std::string name, JSON::Value & data);
bool CheckAllStreams(JSON::Value & data);
void CheckStreams(JSON::Value & in, JSON::Value & out);
void AddStreams(JSON::Value & in, JSON::Value & out);
int deleteStream(const std::string & name, JSON::Value & out, bool sourceFileToo = false);
void checkParameters(JSON::Value & stream);
namespace Controller{
bool streamsEqual(JSON::Value &one, JSON::Value &two);
void checkStream(std::string name, JSON::Value &data);
bool CheckAllStreams(JSON::Value &data);
void CheckStreams(JSON::Value &in, JSON::Value &out);
void AddStreams(JSON::Value &in, JSON::Value &out);
int deleteStream(const std::string &name, JSON::Value &out, bool sourceFileToo = false);
void checkParameters(JSON::Value &stream);
struct liveCheck {
struct liveCheck{
long long int lastms;
long long int last_active;
};
} //Controller namespace
}// namespace Controller

View file

@ -1,19 +1,19 @@
/// \file controller_updater.cpp
/// Contains all code for the controller updater.
#include "controller_updater.h"
#include "controller_connectors.h"
#include "controller_storage.h"
#include "controller_updater.h"
#include <fstream> //for files
#include <iostream> //for stdio
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/http_parser.h>
#include <mist/timing.h>
#include <mist/encode.h>
#include <mist/http_parser.h>
#include <mist/procs.h>
#include <mist/timing.h>
#include <signal.h> //for raise
#include <sys/stat.h> //for chmod
#include <time.h> //for time
@ -66,8 +66,7 @@ namespace Controller{
if (Util::epoch() - updateChecker > UPDATE_INTERVAL || updatePerc){
JSON::Value result = Controller::checkUpdateInfo();
if (result.isMember("error")){
FAIL_MSG("Error retrieving update information: %s",
result["error"].asStringRef().c_str());
FAIL_MSG("Error retrieving update information: %s", result["error"].asStringRef().c_str());
}
{// Lock the mutex, update the updates object
tthread::lock_guard<tthread::mutex> guard(updaterMutex);
@ -75,7 +74,8 @@ namespace Controller{
}
if (!result["uptodate"] && updatePerc){
if (result["url"].asStringRef().find(".zip") != std::string::npos){
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by hand.");
FAIL_MSG("Cannot auto-install update for this platform. Please download and install by "
"hand.");
updatePerc = 0;
continue;
}
@ -86,7 +86,7 @@ namespace Controller{
#else
HTTP::URL url("http://releases.mistserver.org/update.php");
#endif
DL.dataTimeout = 50;//only timeout if no data received for 50 seconds
DL.dataTimeout = 50; // only timeout if no data received for 50 seconds
DL.progressCallback = updaterProgressCallback;
if (!DL.get(url.link(result["url"].asStringRef())) || !DL.isOk() || !DL.data().size()){
FAIL_MSG("Download failed - aborting update");
@ -94,13 +94,13 @@ namespace Controller{
continue;
}
updatePerc = 50;
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size()/1024);
INFO_MSG("Downloaded update archive of %zuKiB", DL.data().size() / 1024);
Log("UPDR", "Installing update...");
std::string tmpDir = Util::getMyPath();
char * tarArgs[4];
tarArgs[0] = (char*)"tar";
tarArgs[1] = (char*)"-xzC";
tarArgs[2] = (char*)tmpDir.c_str();
char *tarArgs[4];
tarArgs[0] = (char *)"tar";
tarArgs[1] = (char *)"-xzC";
tarArgs[2] = (char *)tmpDir.c_str();
tarArgs[3] = 0;
int tarIn = -1;
pid_t tarPid = Util::Procs::StartPiped(tarArgs, &tarIn, 0, 0);
@ -111,13 +111,14 @@ namespace Controller{
}
size_t tarProgress = 0;
while (tarProgress < DL.data().size()){
int written = write(tarIn, DL.data().data()+tarProgress, std::min((size_t)4096, DL.data().size() - tarProgress));
int written = write(tarIn, DL.data().data() + tarProgress,
std::min((size_t)4096, DL.data().size() - tarProgress));
if (written < 0){
FAIL_MSG("Could not (fully) extract update! Aborting.");
break;
}
tarProgress += written;
updatePerc = 95 + (5*tarProgress)/DL.data().size();
updatePerc = 95 + (5 * tarProgress) / DL.data().size();
}
close(tarIn);
uint64_t waitCount = 0;
@ -176,12 +177,13 @@ namespace Controller{
#else
HTTP::URL url("http://releases.mistserver.org/update.php");
#endif
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) + "&iid=" + Encodings::URL::encode(instanceId);
url.args = "rel=" + Encodings::URL::encode(RELEASE) + "&pass=" + Encodings::URL::encode(SHARED_SECRET) +
"&iid=" + Encodings::URL::encode(instanceId);
if (DL.get(url) && DL.isOk()){
updrInfo = JSON::fromString(DL.data());
}else{
Log("UPDR", "Error getting update info: "+DL.getStatusText());
ret["error"] = "Error getting update info: "+DL.getStatusText();
Log("UPDR", "Error getting update info: " + DL.getStatusText());
ret["error"] = "Error getting update info: " + DL.getStatusText();
return ret;
}
if (!updrInfo){
@ -213,5 +215,4 @@ namespace Controller{
/// Causes the updater thread to download an update, if available
void checkUpdates(){updatePerc = 1;}// CheckUpdates
}
}// namespace Controller

View file

@ -10,5 +10,4 @@ namespace Controller{
JSON::Value checkUpdateInfo();
void checkUpdates();
void insertUpdateInfo(JSON::Value &ret);
}
}// namespace Controller

View file

@ -1,100 +1,97 @@
#include <stdlib.h>
#include <mist/auth.h>
#include <mist/dtsc.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/timing.h>
#include "controller_uplink.h"
#include "controller_api.h"
#include "controller_capabilities.h"
#include "controller_connectors.h"
#include "controller_limits.h"
#include "controller_statistics.h"
#include "controller_storage.h"
#include "controller_streams.h"
#include "controller_connectors.h"
#include "controller_capabilities.h"
#include "controller_statistics.h"
#include "controller_updater.h"
#include "controller_limits.h"
#include "controller_api.h"
#include "controller_uplink.h"
#include <mist/auth.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/timing.h>
#include <stdlib.h>
void Controller::uplinkConnection(void * np) {
void Controller::uplinkConnection(void *np){
std::string uplink_name = Controller::conf.getString("uplink-name");
std::string uplink_pass = Controller::conf.getString("uplink-pass");
std::string uplink_addr = Controller::conf.getString("uplink");
std::string uplink_host = "";
std::string uplink_chal = "";
int uplink_port = 0;
if (uplink_addr.size() > 0) {
if (uplink_addr.size() > 0){
size_t colon = uplink_addr.find(':');
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()) {
if (colon != std::string::npos && colon != 0 && colon != uplink_addr.size()){
uplink_host = uplink_addr.substr(0, colon);
uplink_port = atoi(uplink_addr.substr(colon + 1, std::string::npos).c_str());
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " + uplink_addr.substr(colon + 1, std::string::npos));
Controller::Log("CONF", "Connection to uplink enabled on host " + uplink_host + " and port " +
uplink_addr.substr(colon + 1, std::string::npos));
}
}
//cancel the whole thread if no uplink is set
if (!uplink_port) {
return;
}
// cancel the whole thread if no uplink is set
if (!uplink_port){return;}
uint64_t lastSend = Util::epoch() - 5;
Socket::Connection uplink;
while (Controller::conf.is_active) {
if (!uplink) {
while (Controller::conf.is_active){
if (!uplink){
INFO_MSG("Connecting to uplink at %s:%u", uplink_host.c_str(), uplink_port);
uplink.open(uplink_host, uplink_port, true);
}
if (uplink) {
if (uplink.spool()) {
if (uplink.Received().available(9)) {
if (uplink){
if (uplink.spool()){
if (uplink.Received().available(9)){
std::string data = uplink.Received().copy(8);
if (data.substr(0, 4) != "DTSC") {
if (data.substr(0, 4) != "DTSC"){
uplink.Received().clear();
continue;
}
unsigned int size = ntohl(*(const unsigned int *)(data.data() + 4));
if (uplink.Received().available(8 + size)) {
if (uplink.Received().available(8 + size)){
std::string packet = uplink.Received().remove(8 + size);
DTSC::Scan inScan = DTSC::Packet(packet.data(), packet.size()).getScan();
if (!inScan){continue;}
JSON::Value curVal;
//Parse config and streams from the request.
// Parse config and streams from the request.
if (inScan.hasMember("authorize") && inScan.getMember("authorize").hasMember("challenge")){
uplink_chal = inScan.getMember("authorize").getMember("challenge").asString();
}
if (inScan.hasMember("streams")) {
if (inScan.hasMember("streams")){
curVal = inScan.getMember("streams").asJSON();
Controller::CheckStreams(curVal, Controller::Storage["streams"]);
}
if (inScan.hasMember("addstream")) {
if (inScan.hasMember("addstream")){
curVal = inScan.getMember("addstream").asJSON();
Controller::AddStreams(curVal, Controller::Storage["streams"]);
}
if (inScan.hasMember("deletestream")) {
if (inScan.hasMember("deletestream")){
curVal = inScan.getMember("deletestream").asJSON();
//if array, delete all elements
//if object, delete all entries
//if string, delete just the one
if (curVal.isString()) {
// if array, delete all elements
// if object, delete all entries
// if string, delete just the one
if (curVal.isString()){
Controller::Storage["streams"].removeMember(curVal.asStringRef());
}
if (curVal.isArray()) {
jsonForEach(curVal, it) {
if (curVal.isArray()){
jsonForEach(curVal, it){
Controller::Storage["streams"].removeMember(it->asString());
}
}
if (curVal.isObject()) {
jsonForEach(curVal, it) {
Controller::Storage["streams"].removeMember(it.key());
}
if (curVal.isObject()){
jsonForEach(curVal, it){Controller::Storage["streams"].removeMember(it.key());}
}
}
}
}
}
if (Util::epoch() - lastSend >= 2) {
if (Util::epoch() - lastSend >= 2){
JSON::Value data;
data["tracks"].null();//make sure the data is encoded as DTSC
data["tracks"].null(); // make sure the data is encoded as DTSC
if (uplink_chal.size()){
data["authorize"]["username"] = uplink_name;
data["authorize"]["password"] = Secure::md5( Secure::md5(uplink_pass) + uplink_chal);
data["authorize"]["password"] = Secure::md5(Secure::md5(uplink_pass) + uplink_chal);
}
JSON::Value totalsRequest;
Controller::fillClients(totalsRequest, data["clients"]);
@ -115,9 +112,9 @@ void Controller::uplinkConnection(void * np) {
data.sendTo(uplink);
lastSend = Util::epoch();
}
} else {
}else{
Controller::Log("UPLK", "Could not connect to uplink.");
}
Util::wait(2000);//wait for 2.5 seconds
Util::wait(2000); // wait for 2.5 seconds
}
}

View file

@ -1,3 +1,3 @@
namespace Controller {
void uplinkConnection(void * np);
namespace Controller{
void uplinkConnection(void *np);
}

File diff suppressed because it is too large Load diff

View file

@ -1,98 +1,98 @@
#include <set>
#include <map>
#include <cstdlib>
#include <mist/config.h>
#include <mist/json.h>
#include <mist/timing.h>
#include <mist/dtsc.h>
#include <mist/defines.h>
#include <mist/shared_memory.h>
#include <fstream>
#include <map>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include <mist/timing.h>
#include <set>
#include "../io.h"
namespace Mist {
struct booking {
namespace Mist{
struct booking{
int first;
int curKey;
int curPart;
};
class Input : public InOutBase {
public:
Input(Util::Config * cfg);
virtual int run();
virtual void onCrash(){}
virtual int boot(int argc, char * argv[]);
virtual ~Input() {};
class Input : public InOutBase{
public:
Input(Util::Config *cfg);
virtual int run();
virtual void onCrash(){}
virtual int boot(int argc, char *argv[]);
virtual ~Input(){};
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 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 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 std::string streamMainLoop();
virtual std::string realtimeMainLoop();
bool isAlwaysOn();
static Util::Config *config;
virtual bool needsLock(){return !config->getBool("realtime");}
virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum);
protected:
static void callbackWrapper(char *data, size_t len, unsigned int id);
virtual bool checkArguments() = 0;
virtual bool readHeader() = 0;
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 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 std::string streamMainLoop();
virtual std::string realtimeMainLoop();
bool isAlwaysOn();
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;
virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum);
bool isBuffer;
uint64_t activityCounter;
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;
JSON::Value capa;
std::map<int,std::set<int> > keyTimes;
int64_t timeOffset;
bool isBuffer;
uint64_t activityCounter;
//Create server for user pages
IPC::sharedServer userPage;
IPC::sharedPage streamStatus;
JSON::Value capa;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
std::map<int, std::set<int> > keyTimes;
int64_t timeOffset;
static Input * singleton;
// Create server for user pages
IPC::sharedServer userPage;
IPC::sharedPage streamStatus;
bool hasSrt;
std::ifstream srtSource;
unsigned int srtTrack;
std::map<unsigned int, std::map<unsigned int, unsigned int> > pageCounter;
void readSrtHeader();
void getNextSrt(bool smart = true);
DTSC::Packet srtPack;
static Input *singleton;
uint64_t simStartTime;
bool hasSrt;
std::ifstream srtSource;
unsigned int srtTrack;
void readSrtHeader();
void getNextSrt(bool smart = true);
DTSC::Packet srtPack;
uint64_t simStartTime;
};
}
}// namespace Mist

View file

@ -1,20 +1,22 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include "input_av.h"
namespace Mist {
inputAV::inputAV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputAV::inputAV(Util::Config *cfg) : Input(cfg){
pFormatCtx = 0;
capa["name"] = "AV";
capa["desc"] = "This input uses libavformat to read any type of file. Unfortunately this input cannot be redistributed, but it is a great tool for testing the other file-based inputs against.";
capa["desc"] =
"This input uses libavformat to read any type of file. Unfortunately this input cannot be "
"redistributed, but it is a great tool for testing the other file-based inputs against.";
capa["source_match"] = "/*";
capa["source_file"] = "$source";
capa["priority"] = 1ll;
@ -22,38 +24,30 @@ namespace Mist {
capa["codecs"][0u][1u].null();
capa["codecs"][0u][2u].null();
av_register_all();
AVCodec * cInfo = 0;
AVCodec *cInfo = 0;
while ((cInfo = av_codec_next(cInfo)) != 0){
if (cInfo->type == AVMEDIA_TYPE_VIDEO){
capa["codecs"][0u][0u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_AUDIO){
capa["codecs"][0u][1u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){
capa["codecs"][0u][3u].append(cInfo->name);
}
if (cInfo->type == AVMEDIA_TYPE_VIDEO){capa["codecs"][0u][0u].append(cInfo->name);}
if (cInfo->type == AVMEDIA_TYPE_AUDIO){capa["codecs"][0u][1u].append(cInfo->name);}
if (cInfo->type == AVMEDIA_TYPE_SUBTITLE){capa["codecs"][0u][3u].append(cInfo->name);}
}
}
inputAV::~inputAV(){
if (pFormatCtx){
avformat_close_input(&pFormatCtx);
}
if (pFormatCtx){avformat_close_input(&pFormatCtx);}
}
bool inputAV::checkArguments() {
if (config->getString("input") == "-") {
bool inputAV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
@ -62,29 +56,29 @@ namespace Mist {
}
bool inputAV::preRun(){
//make sure all av inputs are registered properly, just in case
//the constructor already does this, but under windows it doesn't remember that it has.
//Very sad, that. We may need to get windows some medication for it.
// make sure all av inputs are registered properly, just in case
// the constructor already does this, but under windows it doesn't remember that it has.
// Very sad, that. We may need to get windows some medication for it.
av_register_all();
//close any already open files
// close any already open files
if (pFormatCtx){
avformat_close_input(&pFormatCtx);
pFormatCtx = 0;
}
//Open video file
// Open video file
int ret = avformat_open_input(&pFormatCtx, config->getString("input").c_str(), NULL, NULL);
if(ret != 0){
if (ret != 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not open file: %s", errstr);
return false; // Couldn't open file
}
//Retrieve stream information
// Retrieve stream information
ret = avformat_find_stream_info(pFormatCtx, NULL);
if(ret < 0){
if (ret < 0){
char errstr[300];
av_strerror(ret, errstr, 300);
DEBUG_MSG(DLVL_FAIL, "Could not find stream info: %s", errstr);
@ -93,63 +87,39 @@ namespace Mist {
return true;
}
bool inputAV::readHeader() {
bool inputAV::readHeader(){
myMeta.tracks.clear();
for(unsigned int i=0; i < pFormatCtx->nb_streams; ){
AVStream * strm = pFormatCtx->streams[i++];
for (unsigned int i = 0; i < pFormatCtx->nb_streams;){
AVStream *strm = pFormatCtx->streams[i++];
myMeta.tracks[i].trackID = i;
switch (strm->codecpar->codec_id){
case AV_CODEC_ID_HEVC:
myMeta.tracks[i].codec = "HEVC";
break;
case AV_CODEC_ID_MPEG1VIDEO:
case AV_CODEC_ID_MPEG2VIDEO:
myMeta.tracks[i].codec = "MPEG2";
break;
case AV_CODEC_ID_MP2:
myMeta.tracks[i].codec = "MP2";
break;
case AV_CODEC_ID_H264:
myMeta.tracks[i].codec = "H264";
break;
case AV_CODEC_ID_THEORA:
myMeta.tracks[i].codec = "theora";
break;
case AV_CODEC_ID_VORBIS:
myMeta.tracks[i].codec = "vorbis";
break;
case AV_CODEC_ID_OPUS:
myMeta.tracks[i].codec = "opus";
break;
case AV_CODEC_ID_VP8:
myMeta.tracks[i].codec = "VP8";
break;
case AV_CODEC_ID_VP9:
myMeta.tracks[i].codec = "VP9";
break;
case AV_CODEC_ID_AAC:
myMeta.tracks[i].codec = "AAC";
break;
case AV_CODEC_ID_MP3:
myMeta.tracks[i].codec = "MP3";
break;
case AV_CODEC_ID_AC3:
case AV_CODEC_ID_EAC3:
myMeta.tracks[i].codec = "AC3";
break;
default:
const AVCodecDescriptor *desc = avcodec_descriptor_get(strm->codecpar->codec_id);
if (desc && desc->name){
myMeta.tracks[i].codec = desc->name;
}else{
myMeta.tracks[i].codec = "?";
}
break;
case AV_CODEC_ID_HEVC: myMeta.tracks[i].codec = "HEVC"; break;
case AV_CODEC_ID_MPEG1VIDEO:
case AV_CODEC_ID_MPEG2VIDEO: myMeta.tracks[i].codec = "MPEG2"; break;
case AV_CODEC_ID_MP2: myMeta.tracks[i].codec = "MP2"; break;
case AV_CODEC_ID_H264: myMeta.tracks[i].codec = "H264"; break;
case AV_CODEC_ID_THEORA: myMeta.tracks[i].codec = "theora"; break;
case AV_CODEC_ID_VORBIS: myMeta.tracks[i].codec = "vorbis"; break;
case AV_CODEC_ID_OPUS: myMeta.tracks[i].codec = "opus"; break;
case AV_CODEC_ID_VP8: myMeta.tracks[i].codec = "VP8"; break;
case AV_CODEC_ID_VP9: myMeta.tracks[i].codec = "VP9"; break;
case AV_CODEC_ID_AAC: myMeta.tracks[i].codec = "AAC"; break;
case AV_CODEC_ID_MP3: myMeta.tracks[i].codec = "MP3"; break;
case AV_CODEC_ID_AC3:
case AV_CODEC_ID_EAC3: myMeta.tracks[i].codec = "AC3"; break;
default:
const AVCodecDescriptor *desc = avcodec_descriptor_get(strm->codecpar->codec_id);
if (desc && desc->name){
myMeta.tracks[i].codec = desc->name;
}else{
myMeta.tracks[i].codec = "?";
}
break;
}
if (strm->codecpar->extradata_size){
myMeta.tracks[i].init = std::string((char*)strm->codecpar->extradata, strm->codecpar->extradata_size);
myMeta.tracks[i].init = std::string((char *)strm->codecpar->extradata, strm->codecpar->extradata_size);
}
if(strm->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
if (strm->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
myMeta.tracks[i].type = "video";
if (strm->avg_frame_rate.den && strm->avg_frame_rate.num){
myMeta.tracks[i].fpks = (strm->avg_frame_rate.num * 1000) / strm->avg_frame_rate.den;
@ -159,23 +129,21 @@ namespace Mist {
myMeta.tracks[i].width = strm->codecpar->width;
myMeta.tracks[i].height = strm->codecpar->height;
}
if(strm->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
if (strm->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){
myMeta.tracks[i].type = "audio";
myMeta.tracks[i].rate = strm->codecpar->sample_rate;
myMeta.tracks[i].size = strm->codecpar->frame_size;
myMeta.tracks[i].channels = strm->codecpar->channels;
}
}
AVPacket packet;
while(av_read_frame(pFormatCtx, &packet)>=0){
AVStream * strm = pFormatCtx->streams[packet.stream_index];
while (av_read_frame(pFormatCtx, &packet) >= 0){
AVStream *strm = pFormatCtx->streams[packet.stream_index];
long long packTime = (packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
long long packOffset = 0;
bool isKey = false;
if (packTime < 0){
packTime = 0;
}
if (packTime < 0){packTime = 0;}
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
isKey = true;
}
@ -185,48 +153,49 @@ namespace Mist {
myMeta.update(packTime, packOffset, packet.stream_index + 1, packet.size, packet.pos, isKey);
av_packet_unref(&packet);
}
myMeta.toFile(config->getString("input") + ".dtsh");
seek(0);
return true;
}
void inputAV::getNext(bool smart) {
void inputAV::getNext(bool smart){
AVPacket packet;
while (av_read_frame(pFormatCtx, &packet)>=0){
//filter tracks we don't care about
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);
continue;
}
AVStream * strm = pFormatCtx->streams[packet.stream_index];
AVStream *strm = pFormatCtx->streams[packet.stream_index];
long long packTime = (packet.dts * 1000 * strm->time_base.num / strm->time_base.den);
long long packOffset = 0;
bool isKey = false;
if (packTime < 0){
packTime = 0;
}
if (packTime < 0){packTime = 0;}
if (packet.flags & AV_PKT_FLAG_KEY && myMeta.tracks[(long long)packet.stream_index + 1].type != "audio"){
isKey = true;
}
if (packet.pts != AV_NOPTS_VALUE && packet.pts != packet.dts){
packOffset = ((packet.pts - packet.dts) * 1000 * strm->time_base.num / strm->time_base.den);
}
thisPacket.genericFill(packTime, packOffset, packet.stream_index + 1, (const char*)packet.data, packet.size, 0, isKey);
thisPacket.genericFill(packTime, packOffset, packet.stream_index + 1,
(const char *)packet.data, packet.size, 0, isKey);
av_packet_unref(&packet);
return;//success!
return; // success!
}
thisPacket.null();
preRun();
//failure :-(
// failure :-(
DEBUG_MSG(DLVL_FAIL, "getNext failed");
}
void inputAV::seek(int seekTime) {
void inputAV::seek(int seekTime){
int stream_index = av_find_default_stream_index(pFormatCtx);
//Convert ts to frame
unsigned long long reseekTime = av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den, pFormatCtx->streams[stream_index]->time_base.num);
// Convert ts to frame
unsigned long long reseekTime =
av_rescale(seekTime, pFormatCtx->streams[stream_index]->time_base.den,
pFormatCtx->streams[stream_index]->time_base.num);
reseekTime /= 1000;
unsigned long long seekStreamDuration = pFormatCtx->streams[stream_index]->duration;
int flags = AVSEEK_FLAG_BACKWARD;
@ -234,24 +203,21 @@ namespace Mist {
flags |= AVSEEK_FLAG_ANY; // H.264 I frames don't always register as "key frames" in FFmpeg
}
int ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, flags);
if (ret < 0){
ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);
}
if (ret < 0){ret = av_seek_frame(pFormatCtx, stream_index, reseekTime, AVSEEK_FLAG_ANY);}
}
void inputAV::trackSelect(std::string trackSpec) {
void inputAV::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
//inFile.selectTracks(selectedTracks);
// inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,33 +1,35 @@
#ifndef INT64_MIN
#define INT64_MIN (-(9223372036854775807 ## LL)-1)
#define INT64_MIN (-(9223372036854775807##LL) - 1)
#endif
#ifndef INT64_MAX
#define INT64_MAX ((9223372036854775807 ## LL))
#define INT64_MAX ((9223372036854775807##LL))
#endif
#include "input.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
extern "C"{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
namespace Mist {
class inputAV : public Input {
public:
inputAV(Util::Config * cfg);
~inputAV();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
private:
AVFormatContext *pFormatCtx;
namespace Mist{
class inputAV : public Input{
public:
inputAV(Util::Config *cfg);
~inputAV();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
private:
AVFormatContext *pFormatCtx;
};
}
}// namespace Mist
typedef Mist::inputAV mistIn;

View file

@ -1,25 +1,29 @@
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/http_parser.h>
#include <mist/encode.h>
#include <mist/url.h>
#include "input_balancer.h"
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/http_parser.h>
#include <mist/stream.h>
#include <mist/url.h>
namespace Mist {
inputBalancer::inputBalancer(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputBalancer::inputBalancer(Util::Config *cfg) : Input(cfg){
capa["name"] = "Balancer";
capa["desc"] = "The load balancer input restarts itself as the input a load balancer tells it it should be. The syntax is in the form 'balance:http://HOST:PORT[?fallback=FALLBACK]', where HOST and PORT are the host and port of the load balancer and the FALLBACK is the full source URL that should be used if the load balancer cannot be reached.";
capa["desc"] =
"The load balancer input restarts itself as the input a load balancer tells it it should "
"be. The syntax is in the form 'balance:http://HOST:PORT[?fallback=FALLBACK]', where HOST "
"and PORT are the host and port of the load balancer and the FALLBACK is the full source "
"URL that should be used if the load balancer cannot be reached.";
capa["source_match"] = "balance:*";
capa["priority"] = 9;
capa["morphic"] = 1;
}
int inputBalancer::boot(int argc, char * argv[]){
int inputBalancer::boot(int argc, char *argv[]){
if (!config->parseArgs(argc, argv)){return 1;}
if (config->getBool("json")){return Input::boot(argc, argv);}
streamName = config->getString("streamname");
std::string blncr = config->getString("input");
if (blncr.substr(0, 8) != "balance:"){
FAIL_MSG("Input must start with \"balance:\"");
@ -32,16 +36,15 @@ namespace Mist {
return 1;
}
std::string source; //empty by default
std::string source; // empty by default
//Parse fallback from URL arguments, if possible.
// Parse fallback from URL arguments, if possible.
if (url.args.size()){
std::map<std::string, std::string> args;
HTTP::parseVars(url.args, args);
if (args.count("fallback")){source = args.at("fallback");}
}
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());
@ -49,9 +52,7 @@ namespace Mist {
HTTP::Parser http;
http.url = "/" + url.path;
http.SetVar("source", streamName);
if (source.size()){
http.SetVar("fallback", source);
}
if (source.size()){http.SetVar("fallback", source);}
http.method = "GET";
http.SetHeader("Host", url.host);
http.SetHeader("X-MistServer", PACKAGE_VERSION);
@ -66,29 +67,28 @@ namespace Mist {
if (Socket::isLocalhost(newUrl.host)){
WARN_MSG("Load balancer returned a local address - ignoring");
startTime = 0;
break;//break out of while loop, ignore return value - it's local.
break; // break out of while loop, ignore return value - it's local.
}
source = http.body;
startTime = 0;//note success
break;//break out of while loop
startTime = 0; // note success
break; // break out of while loop
}
}
}
if (startTime){
FAIL_MSG("Timeout while trying to contact load balancer at %s!", blncr.c_str()+8);
FAIL_MSG("Timeout while trying to contact load balancer at %s!", blncr.c_str() + 8);
}
balConn.close();
}
if (!source.size()){
FAIL_MSG("Could not determine source to use for %s", streamName.c_str());
return 1;
}
//Attempt to boot the source we got
// Attempt to boot the source we got
Util::startInput(streamName, source, false, getenv("MISTPROVIDER"));
return 1;
}
}
}// namespace Mist

View file

@ -1,17 +1,17 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputBalancer : public Input {
public:
inputBalancer(Util::Config * cfg);
int boot(int argc, char * argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
namespace Mist{
class inputBalancer : public Input{
public:
inputBalancer(Util::Config *cfg);
int boot(int argc, char *argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
};
}
}// namespace Mist
typedef Mist::inputBalancer mistIn;

File diff suppressed because it is too large Load diff

View file

@ -4,54 +4,54 @@
#include <mist/dtsc.h>
#include <mist/shared_memory.h>
namespace Mist {
class inputBuffer : public Input {
public:
inputBuffer(Util::Config * cfg);
~inputBuffer();
void onCrash();
private:
void fillBufferDetails(JSON::Value & details);
unsigned int bufferTime;
unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
bool hasPush;
bool resumeMode;
IPC::semaphore * liveMeta;
protected:
//Private Functions
bool preRun();
bool checkArguments(){return true;}
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 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;
//This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, 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
namespace Mist{
class inputBuffer : public Input{
public:
inputBuffer(Util::Config *cfg);
~inputBuffer();
void onCrash();
private:
void fillBufferDetails(JSON::Value &details);
unsigned int bufferTime;
unsigned int cutTime;
unsigned int segmentSize; /*LTS*/
unsigned int lastReTime; /*LTS*/
bool hasPush;
bool resumeMode;
IPC::semaphore *liveMeta;
protected:
// Private Functions
bool preRun();
bool checkArguments(){return true;}
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 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;
// This is used for an ugly fix to prevent metadata from disappearing in some cases.
std::map<unsigned long, 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
};
}
}// namespace Mist
typedef Mist::inputBuffer mistIn;

View file

@ -1,25 +1,27 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include <mist/util.h>
#include <mist/bitfields.h>
#include <mist/util.h>
#include "input_dtsc.h"
namespace Mist {
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputDTSC::inputDTSC(Util::Config *cfg) : Input(cfg){
capa["name"] = "DTSC";
capa["desc"] = "Load DTSC files as Video on Demand sources, or dtsc:// URLs from other MistServer instances for live sources. This is the optimal method to pull live sources from other MistServer (or compatible) instances.";
capa["desc"] = "Load DTSC files as Video on Demand sources, or dtsc:// URLs from other "
"MistServer instances for live sources. This is the optimal method to pull live "
"sources from other MistServer (or compatible) instances.";
capa["priority"] = 9;
capa["source_match"].append("/*.dtsc");
capa["source_match"].append("dtsc://*");
capa["always_match"].append("dtsc://*");//can be said to always-on mode
capa["always_match"].append("dtsc://*"); // can be said to always-on mode
capa["source_file"] = "$source";
capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("H263");
@ -29,7 +31,6 @@ namespace Mist {
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("vorbis");
JSON::Value option;
option["arg"] = "integer";
option["long"] = "buffer";
@ -38,7 +39,10 @@ namespace Mist {
option["value"].append(50000);
config->addOption("bufferTime", option);
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
capa["optional"]["DVR"]["help"] = "The target available buffer time for this live stream, in milliseconds. This is the time available to seek around in, and will automatically be extended to fit whole keyframes as well as the minimum duration needed for stable playback.";
capa["optional"]["DVR"]["help"] =
"The target available buffer time for this live stream, in milliseconds. This is the time "
"available to seek around in, and will automatically be extended to fit whole keyframes as "
"well as the minimum duration needed for stable playback.";
capa["optional"]["DVR"]["option"] = "--buffer";
capa["optional"]["DVR"]["type"] = "uint";
capa["optional"]["DVR"]["default"] = 50000;
@ -56,93 +60,94 @@ namespace Mist {
capa["optional"]["segmentsize"]["type"] = "uint";
capa["optional"]["segmentsize"]["default"] = 1900;
/*LTS-END*/
}
bool inputDTSC::needsLock(){
return config->getString("input").substr(0, 7) != "dtsc://" && config->getString("input") != "-";
}
void parseDTSCURI(const std::string & src, std::string & host, uint16_t & port, std::string & password, std::string & streamName) {
void parseDTSCURI(const std::string &src, std::string &host, uint16_t &port,
std::string &password, std::string &streamName){
host = "";
port = 4200;
password = "";
streamName = "";
std::deque<std::string> matches;
if (Util::stringScan(src, "%s:%s@%s/%s", matches)) {
if (Util::stringScan(src, "%s:%s@%s/%s", matches)){
host = matches[0];
port = atoi(matches[1].c_str());
password = matches[2];
streamName = matches[3];
return;
}
//Using default streamname
if (Util::stringScan(src, "%s:%s@%s", matches)) {
// Using default streamname
if (Util::stringScan(src, "%s:%s@%s", matches)){
host = matches[0];
port = atoi(matches[1].c_str());
password = matches[2];
return;
}
//Without password
if (Util::stringScan(src, "%s:%s/%s", matches)) {
// Without password
if (Util::stringScan(src, "%s:%s/%s", matches)){
host = matches[0];
port = atoi(matches[1].c_str());
streamName = matches[2];
return;
}
//Using default port
if (Util::stringScan(src, "%s@%s/%s", matches)) {
// Using default port
if (Util::stringScan(src, "%s@%s/%s", matches)){
host = matches[0];
password = matches[1];
streamName = matches[2];
return;
}
//Default port, no password
if (Util::stringScan(src, "%s/%s", matches)) {
// Default port, no password
if (Util::stringScan(src, "%s/%s", matches)){
host = matches[0];
streamName = matches[1];
return;
}
//No password, default streamname
if (Util::stringScan(src, "%s:%s", matches)) {
// No password, default streamname
if (Util::stringScan(src, "%s:%s", matches)){
host = matches[0];
port = atoi(matches[1].c_str());
return;
}
//Default port and streamname
if (Util::stringScan(src, "%s@%s", matches)) {
// Default port and streamname
if (Util::stringScan(src, "%s@%s", matches)){
host = matches[0];
password = matches[1];
return;
}
//Default port and streamname, no password
if (Util::stringScan(src, "%s", matches)) {
// Default port and streamname, no password
if (Util::stringScan(src, "%s", matches)){
host = matches[0];
return;
}
}
void inputDTSC::parseStreamHeader() {
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") {
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)) {
if (!srcConn.Received().available(8 + rSize)){
nProxy.userClient.keepAlive();
Util::sleep(100);
continue; //abort - not enough data yet
continue; // abort - not enough data yet
}
//Ignore initial DTCM message, as this is a "hi" message from the server
// 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);
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++){
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin();
it != myMeta.tracks.end(); it++){
continueNegotiate(it->first, true);
}
break;
@ -158,65 +163,55 @@ namespace Mist {
}
}
bool inputDTSC::openStreamSource() {
bool inputDTSC::openStreamSource(){
std::string source = config->getString("input");
if (source == "-"){
srcConn.open(fileno(stdout),fileno(stdin));
srcConn.open(fileno(stdout), fileno(stdin));
return true;
}
if (source.find("dtsc://") == 0) {
source.erase(0, 7);
}
if (source.find("dtsc://") == 0){source.erase(0, 7);}
std::string host;
uint16_t port;
std::string password;
std::string streamName;
parseDTSCURI(source, host, port, password, streamName);
std::string givenStream = config->getString("streamname");
if (streamName == "") {
streamName = givenStream;
}
if (streamName == ""){streamName = givenStream;}
srcConn.open(host, port, true);
if (!srcConn.connected()){
return false;
}
if (!srcConn.connected()){return false;}
JSON::Value prep;
prep["cmd"] = "play";
prep["version"] = "MistServer " PACKAGE_VERSION;
prep["stream"] = streamName;
srcConn.SendNow("DTCM");
char sSize[4] = {0, 0, 0, 0};
char sSize[4] ={0, 0, 0, 0};
Bit::htobl(sSize, prep.packedSize());
srcConn.SendNow(sSize, 4);
prep.sendTo(srcConn);
return true;
}
void inputDTSC::closeStreamSource(){
srcConn.close();
}
void inputDTSC::closeStreamSource(){srcConn.close();}
bool inputDTSC::checkArguments() {
if (!needsLock()) {
bool inputDTSC::checkArguments(){
if (!needsLock()){
return true;
} else {
if (!config->getString("streamname").size()) {
if (config->getString("output") == "-") {
}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") != "-") {
}else{
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
//open File
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile) {
return false;
}
if (!inFile){return false;}
}
return true;
}
@ -226,11 +221,9 @@ namespace Mist {
return Input::needHeader();
}
bool inputDTSC::readHeader() {
if (!inFile) {
return false;
}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
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;
}
@ -239,7 +232,7 @@ namespace Mist {
return true;
}
void inputDTSC::getNext(bool smart) {
void inputDTSC::getNext(bool smart){
if (!needsLock()){
thisPacket.reInit(srcConn);
while (config->is_active){
@ -248,17 +241,16 @@ namespace Mist {
std::string cmd;
thisPacket.getString("cmd", cmd);
if (cmd == "reset"){
//Read next packet
// Read next packet
thisPacket.reInit(srcConn);
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
DTSC::Meta newMeta;
newMeta.reinit(thisPacket);
//Detect new tracks
// 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::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++){
@ -267,34 +259,33 @@ namespace Mist {
continueNegotiate(*it, true);
}
//Detect removed tracks
// 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::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++){
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
thisPacket.reInit(srcConn); // read the next packet before continuing
}else{
myMeta = DTSC::Meta();
}
}else{
thisPacket.reInit(srcConn);//read the next packet before continuing
thisPacket.reInit(srcConn); // read the next packet before continuing
}
continue;//parse the next packet before returning
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::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++){
@ -302,49 +293,49 @@ namespace Mist {
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
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.
// 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());
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
// 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
return; // we have a packet
}
}else{
if (smart) {
if (smart){
inFile.seekNext();
} else {
}else{
inFile.parseNext();
}
thisPacket = inFile.getPacket();
}
}
void inputDTSC::seek(int seekTime) {
void inputDTSC::seek(int seekTime){
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec) {
void inputDTSC::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,29 +1,28 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputDTSC : public Input {
public:
inputDTSC(Util::Config * cfg);
bool needsLock();
protected:
//Private Functions
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
bool checkArguments();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
namespace Mist{
class inputDTSC : public Input{
public:
inputDTSC(Util::Config *cfg);
bool needsLock();
DTSC::File inFile;
protected:
// Private Functions
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
bool checkArguments();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
Socket::Connection srcConn;
DTSC::File inFile;
Socket::Connection srcConn;
};
}
}// namespace Mist
typedef Mist::inputDTSC mistIn;

View file

@ -1,20 +1,20 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/encode.h>
#include <mist/defines.h>
#include <mist/encryption.h>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/encode.h>
#include <mist/encryption.h>
#include <mist/stream.h>
#include <string>
#include "input_dtsccrypt.h"
#include <ctime>
namespace Mist {
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputDTSC::inputDTSC(Util::Config *cfg) : Input(cfg){
capa["name"] = "DTSC";
capa["desc"] = "Enables DTSC Input";
capa["priority"] = 9;
@ -26,7 +26,7 @@ namespace Mist {
capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("vorbis");
JSON::Value option;
option["long"] = "key";
option["short"] = "k";
@ -34,7 +34,7 @@ namespace Mist {
option["help"] = "The key to en/decrypt the current file with";
config->addOption("key", option);
option.null();
option["long"] = "keyseed";
option["short"] = "s";
option["arg"] = "string";
@ -52,7 +52,7 @@ namespace Mist {
srand(time(NULL));
}
bool inputDTSC::checkArguments() {
bool inputDTSC::checkArguments(){
key = Encodings::Base64::decode(config->getString("key"));
if (key == ""){
if (config->getString("keyseed") == "" || config->getString("keyid") == ""){
@ -65,59 +65,55 @@ namespace Mist {
key = Encryption::PR_GenerateContentKey(tmpSeed, guid);
}
if (config->getString("input") == "-") {
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
//open File
// open File
inFile = DTSC::File(config->getString("input"));
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputDTSC::readHeader() {
if (!inFile) {
return false;
}
bool inputDTSC::readHeader(){
if (!inFile){return false;}
DTSC::File tmp(config->getString("input") + ".dtsh");
if (tmp) {
if (tmp){
myMeta = tmp.getMeta();
DEBUG_MSG(DLVL_HIGH,"Meta read in with %lu tracks", myMeta.tracks.size());
DEBUG_MSG(DLVL_HIGH, "Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
DEBUG_MSG(DLVL_FAIL,"Missing external header file");
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0){
DEBUG_MSG(DLVL_FAIL, "Missing external header file");
return false;
}
myMeta = DTSC::Meta(inFile.getMeta());
DEBUG_MSG(DLVL_DEVEL,"Meta read in with %lu tracks", myMeta.tracks.size());
DEBUG_MSG(DLVL_DEVEL, "Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
void inputDTSC::getNext(bool smart) {
void inputDTSC::getNext(bool smart){
if (smart){
inFile.seekNext();
}else{
inFile.parseNext();
}
thisPacket = inFile.getPacket();
//Do encryption/decryption here
// Do encryption/decryption here
int tid = thisPacket.getTrackId();
char * ivec;
char *ivec;
size_t ivecLen;
thisPacket.getString("ivec", ivec, ivecLen);
char iVec[16];
@ -125,34 +121,32 @@ namespace Mist {
memcpy(iVec, ivec, 8);
}else{
if (iVecs.find(tid) == iVecs.end()){
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
}
Bit::htobll(iVec, iVecs[tid]);
iVecs[tid] ++;
iVecs[tid]++;
}
Encryption::encryptPlayReady(thisPacket, myMeta.tracks[tid].codec, iVec, key.data());
}
void inputDTSC::seek(int seekTime) {
void inputDTSC::seek(int seekTime){
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec) {
void inputDTSC::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
inFile.selectTracks(selectedTracks);
}
}
}// namespace Mist

View file

@ -1,25 +1,24 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputDTSC : public Input {
public:
inputDTSC(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
namespace Mist{
class inputDTSC : public Input{
public:
inputDTSC(Util::Config *cfg);
DTSC::File inFile;
protected:
// Private Functions
bool checkArguments();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
std::map<int,unsigned long long int> iVecs;
std::string key;
DTSC::File inFile;
std::map<int, unsigned long long int> iVecs;
std::string key;
};
}
}// namespace Mist
typedef Mist::inputDTSC mistIn;

View file

@ -1,14 +1,15 @@
#include "input_ebml.h"
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/ebml.h>
#include <mist/bitfields.h>
namespace Mist{
InputEBML::InputEBML(Util::Config *cfg) : Input(cfg){
timeScale = 1.0;
capa["name"] = "EBML";
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or accepts live streams in those formats over standard input.";
capa["desc"] = "Allows loading MKV, MKA, MK3D, MKS and WebM files for Video on Demand, or "
"accepts live streams in those formats over standard input.";
capa["source_match"].append("/*.mkv");
capa["source_match"].append("/*.mka");
capa["source_match"].append("/*.mk3d");
@ -42,21 +43,27 @@ namespace Mist{
wantBlocks = true;
}
std::string ASStoSRT(const char * ptr, uint32_t len){
std::string ASStoSRT(const char *ptr, uint32_t len){
uint16_t commas = 0;
uint16_t brackets = 0;
std::string tmpStr;
tmpStr.reserve(len);
for (uint32_t i = 0; i < len; ++i){
//Skip everything until the 8th comma
// Skip everything until the 8th comma
if (commas < 8){
if (ptr[i] == ','){commas++;}
continue;
}
if (ptr[i] == '{'){brackets++; continue;}
if (ptr[i] == '}'){brackets--; continue;}
if (ptr[i] == '{'){
brackets++;
continue;
}
if (ptr[i] == '}'){
brackets--;
continue;
}
if (!brackets){
if (ptr[i] == '\\' && i < len-1 && (ptr[i+1] == 'N' || ptr[i+1] == 'n')){
if (ptr[i] == '\\' && i < len - 1 && (ptr[i + 1] == 'N' || ptr[i + 1] == 'n')){
tmpStr += '\n';
++i;
continue;
@ -67,7 +74,6 @@ namespace Mist{
return tmpStr;
}
bool InputEBML::checkArguments(){
if (!config->getString("streamname").size()){
if (config->getString("output") == "-"){
@ -83,8 +89,8 @@ namespace Mist{
return true;
}
bool InputEBML::needsLock() {
//Standard input requires no lock, otherwise default behaviour.
bool InputEBML::needsLock(){
// Standard input requires no lock, otherwise default behaviour.
if (config->getString("input") == "-"){return false;}
return Input::needsLock();
}
@ -107,7 +113,7 @@ namespace Mist{
while (ptr.size() < needed){
if (!ptr.allocate(needed)){return false;}
if (!fread(ptr + ptr.size(), needed - ptr.size(), 1, inFile)){
//We assume if there is no current data buffered, that we are at EOF and don't print a warning
// 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);
}
@ -129,7 +135,7 @@ namespace Mist{
lastClusterBPos = 0;
}else{
int64_t bp = Util::ftell(inFile);
if(bp == -1 && errno == ESPIPE){
if (bp == -1 && errno == ESPIPE){
lastClusterBPos = 0;
}else{
lastClusterBPos = bp;
@ -154,7 +160,7 @@ namespace Mist{
}
}
if (myMeta.inputLocalVars.isMember("timescale")){
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
timeScale = ((double)myMeta.inputLocalVars["timescale"].asInt()) / 1000000.0;
}
return true;
}
@ -279,25 +285,23 @@ namespace Mist{
std::string WAVEFORMATEX = tmpElem.getValStringUntrimmed();
unsigned int formatTag = Bit::btohs_le(WAVEFORMATEX.data());
switch (formatTag){
case 3:
trueCodec = "FLOAT";
trueType = "audio";
break;
case 6:
trueCodec = "ALAW";
trueType = "audio";
break;
case 7:
trueCodec = "ULAW";
trueType = "audio";
break;
case 85:
trueCodec = "MP3";
trueType = "audio";
break;
default:
ERROR_MSG("Unimplemented A_MS/ACM formatTag: %u", formatTag);
break;
case 3:
trueCodec = "FLOAT";
trueType = "audio";
break;
case 6:
trueCodec = "ALAW";
trueType = "audio";
break;
case 7:
trueCodec = "ULAW";
trueType = "audio";
break;
case 85:
trueCodec = "MP3";
trueType = "audio";
break;
default: ERROR_MSG("Unimplemented A_MS/ACM formatTag: %u", formatTag); break;
}
}
}
@ -335,7 +339,7 @@ namespace Mist{
myMeta.inputLocalVars["timescale"] = timeScaleVal;
timeScale = ((double)timeScaleVal) / 1000000.0;
}
//Live streams stop parsing the header as soon as the first Cluster is encountered
// Live streams stop parsing the header as soon as the first Cluster is encountered
if (E.getID() == EBML::EID_CLUSTER && !needsLock()){return true;}
if (E.getType() == EBML::ELEM_BLOCK){
EBML::Block B(ptr);
@ -346,7 +350,7 @@ namespace Mist{
bool isVideo = (Trk.type == "video");
bool isAudio = (Trk.type == "audio");
bool isASS = (Trk.codec == "subtitle" && Trk.init.size());
//If this is a new video keyframe, flush the corresponding trackPredictor
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe()){
while (TP.hasPackets(true)){
packetData &C = TP.getPacketData(true);
@ -358,29 +362,28 @@ 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"){
//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 += (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"){
// 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;
}else{
newTime += 1/timeScale;
newTime += 1 / timeScale;
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
if (isASS){
char * ptr = (char *)B.getFrameData(frameNo);
char *ptr = (char *)B.getFrameData(frameNo);
std::string assStr = ASStoSRT(ptr, frameSize);
frameSize = assStr.size();
}
if (frameSize){
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo);
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos, B.isKeyframe() && !isAudio, isVideo);
}
}
while (TP.hasPackets()){
@ -392,8 +395,7 @@ namespace Mist{
}
if (packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end();
++it){
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");
@ -459,8 +461,7 @@ namespace Mist{
void InputEBML::getNext(bool smart){
// Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin();
it != packBuf.end(); ++it){
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");
@ -477,8 +478,7 @@ namespace Mist{
if (!readElement()){
// Make sure we empty our buffer first
if (bufferedPacks && packBuf.size()){
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin();
it != packBuf.end(); ++it){
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");
@ -502,12 +502,12 @@ namespace Mist{
uint64_t tNum = B.getTrackNum();
uint64_t newTime = lastClusterTime + B.getTimecode();
trackPredictor &TP = packBuf[tNum];
DTSC::Track & Trk = myMeta.tracks[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());
//If this is a new video keyframe, flush the corresponding trackPredictor
// If this is a new video keyframe, flush the corresponding trackPredictor
if (isVideo && B.isKeyframe() && bufferedPacks){
if (TP.hasPackets(true)){
wantBlocks = false;
@ -518,39 +518,36 @@ namespace Mist{
return;
}
}
if (isVideo && B.isKeyframe()){
TP.flush();
}
if (isVideo && B.isKeyframe()){TP.flush();}
wantBlocks = true;
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"){
//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 += (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"){
// 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;
}else{
ERROR_MSG("Unknown frame duration for codec %s - timestamps WILL be wrong!", Trk.codec.c_str());
}
}
uint32_t frameSize = B.getFrameSize(frameNo);
if (frameSize){
char * ptr = (char *)B.getFrameData(frameNo);
char *ptr = (char *)B.getFrameData(frameNo);
if (isASS){
std::string assStr = ASStoSRT(ptr, frameSize);
frameSize = assStr.size();
memcpy(ptr, assStr.data(), frameSize);
}
if (frameSize){
TP.add(newTime*timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
TP.add(newTime * timeScale, 0, tNum, frameSize, lastClusterBPos,
B.isKeyframe() && !isAudio, isVideo, (void *)ptr);
++bufferedPacks;
}
}
@ -583,7 +580,7 @@ namespace Mist{
Util::fseek(inFile, seekPos, SEEK_SET);
}
///Flushes all trackPredictors without deleting permanent data from them.
/// Flushes all trackPredictors without deleting permanent data from them.
void InputEBML::clearPredictors(){
if (!packBuf.size()){return;}
for (std::map<uint64_t, trackPredictor>::iterator it = packBuf.begin(); it != packBuf.end(); ++it){
@ -592,4 +589,3 @@ namespace Mist{
}
}// namespace Mist

View file

@ -4,11 +4,10 @@
namespace Mist{
#define PKT_COUNT 64
class packetData{
public:
public:
uint64_t time, offset, track, dsize, bpos;
bool key;
Util::ResizeablePointer ptr;
@ -20,137 +19,143 @@ namespace Mist{
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){
void set(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){
time = packTime;
offset = packOffset;
track = packTrack;
dsize = packDataSize;
bpos = packBytePos;
key = isKeyframe;
if (dataPtr){
ptr.assign(dataPtr, packDataSize);
}
if (dataPtr){ptr.assign(dataPtr, packDataSize);}
}
packetData(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize, uint64_t packBytePos, bool isKeyframe, void * dataPtr = 0){
packetData(uint64_t packTime, uint64_t packOffset, uint64_t packTrack, uint64_t packDataSize,
uint64_t packBytePos, bool isKeyframe, void *dataPtr = 0){
set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
}
};
class trackPredictor{
public:
packetData pkts[PKT_COUNT];
uint64_t frameOffset;///The static average offset between transmit time and display time
bool frameOffsetKnown;///Whether the average frame offset is known
uint16_t smallestFrame;///low-ball estimate of time per frame
uint64_t lastTime;///last send transmit timestamp
uint64_t ctr;///ingested frame count
uint64_t rem;///removed frame count
uint64_t maxOffset;///maximum offset for this track
uint64_t lowestTime;///First timestamp to enter the buffer
trackPredictor(){
smallestFrame = 0xFFFF;
frameOffsetKnown = false;
frameOffset = 0;
maxOffset = 0;
flush();
public:
packetData pkts[PKT_COUNT];
uint64_t frameOffset; /// The static average offset between transmit time and display time
bool frameOffsetKnown; /// Whether the average frame offset is known
uint16_t smallestFrame; /// low-ball estimate of time per frame
uint64_t lastTime; /// last send transmit timestamp
uint64_t ctr; /// ingested frame count
uint64_t rem; /// removed frame count
uint64_t maxOffset; /// maximum offset for this track
uint64_t lowestTime; /// First timestamp to enter the buffer
trackPredictor(){
smallestFrame = 0xFFFF;
frameOffsetKnown = false;
frameOffset = 0;
maxOffset = 0;
flush();
}
bool hasPackets(bool finished = false){
if (finished || frameOffsetKnown){
return (ctr - rem > 0);
}else{
return (ctr - rem > 12);
}
bool hasPackets(bool finished = false){
if (finished || frameOffsetKnown){
return (ctr - rem > 0);
}else{
return (ctr - rem > 12);
}
}
/// Clears all internal values, for reuse as-new.
void flush(){
lastTime = 0;
ctr = 0;
rem = 0;
lowestTime = 0;
}
packetData & getPacketData(bool mustCalcOffsets){
//grab the next packet to output
packetData & p = pkts[rem % PKT_COUNT];
if (!mustCalcOffsets){
frameOffsetKnown = true;
return p;
}
if (rem && !p.key){
uint64_t dispTime = p.time;
if (p.time + frameOffset < lastTime + smallestFrame){
uint32_t shift = (uint32_t)((((lastTime+smallestFrame)-(p.time+frameOffset)) + (smallestFrame-1)) / smallestFrame) * smallestFrame;
if (shift < smallestFrame){shift = smallestFrame;}
VERYHIGH_MSG("Offset negative, shifting original time forward by %" PRIu32, shift);
p.time += shift;
}
p.offset = p.time - (lastTime + smallestFrame) + frameOffset;
if (p.offset > maxOffset){
uint64_t diff = p.offset - maxOffset;
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (maxOffset reached: %" PRIu64 " > %" PRIu64 ")", diff, p.offset, maxOffset);
p.offset -= diff;
lastTime += diff;
}
p.time = (lastTime + smallestFrame);
//If we calculate an offset less than a frame away,
//we assume it's just time stamp drift due to lack of precision.
p.offset = ((uint32_t)((p.offset + (smallestFrame/2)) / smallestFrame)) * smallestFrame;
//Shift the time forward if needed, but never backward
if (p.offset + p.time < dispTime){
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (time drift)", dispTime - (p.offset + p.time));
p.time += dispTime - (p.offset + p.time);
}
}else{
if (!frameOffsetKnown){
//Check the first few timestamps against each other, find the smallest distance.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t t1 = pkts[i%PKT_COUNT].time;
for (uint64_t j = 0; j < ctr; ++j){
if (i == j){continue;}
uint64_t t2 = pkts[j%PKT_COUNT].time;
uint64_t tDiff = (t1<t2)?(t2-t1):(t1-t2);
if (tDiff < smallestFrame){smallestFrame = tDiff;}
}
}
//Cool, now we're pretty sure we know the frame rate. Let's calculate some offsets.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t timeDiff = pkts[i%PKT_COUNT].time - lowestTime;
uint64_t timeExpt = smallestFrame*i;
if (timeDiff > timeExpt && maxOffset < timeDiff-timeExpt){
maxOffset = timeDiff-timeExpt;
}
if (timeDiff < timeExpt && frameOffset < timeExpt-timeDiff){
frameOffset = timeExpt - timeDiff;
}
}
maxOffset += frameOffset;
//Print for debugging purposes, and consider them gospel from here on forward. Yay!
HIGH_MSG("smallestFrame=%" PRIu16 ", frameOffset=%" PRIu64 ", maxOffset=%" PRIu64, smallestFrame, frameOffset, maxOffset);
frameOffsetKnown = true;
}
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);
}
/// Clears all internal values, for reuse as-new.
void flush(){
lastTime = 0;
ctr = 0;
rem = 0;
lowestTime = 0;
}
packetData &getPacketData(bool mustCalcOffsets){
// grab the next packet to output
packetData &p = pkts[rem % PKT_COUNT];
if (!mustCalcOffsets){
frameOffsetKnown = true;
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;}
if (packTime > lowestTime && packTime - lowestTime < smallestFrame){smallestFrame = packTime - lowestTime;}
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
++ctr;
if (ctr == PKT_COUNT-1){frameOffsetKnown = true;}
if (rem && !p.key){
uint64_t dispTime = p.time;
if (p.time + frameOffset < lastTime + smallestFrame){
uint32_t shift =
(uint32_t)((((lastTime + smallestFrame) - (p.time + frameOffset)) + (smallestFrame - 1)) / smallestFrame) *
smallestFrame;
if (shift < smallestFrame){shift = smallestFrame;}
VERYHIGH_MSG("Offset negative, shifting original time forward by %" PRIu32, shift);
p.time += shift;
}
p.offset = p.time - (lastTime + smallestFrame) + frameOffset;
if (p.offset > maxOffset){
uint64_t diff = p.offset - maxOffset;
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (maxOffset reached: %" PRIu64 " > %" PRIu64 ")",
diff, p.offset, maxOffset);
p.offset -= diff;
lastTime += diff;
}
p.time = (lastTime + smallestFrame);
// If we calculate an offset less than a frame away,
// we assume it's just time stamp drift due to lack of precision.
p.offset = ((uint32_t)((p.offset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
// Shift the time forward if needed, but never backward
if (p.offset + p.time < dispTime){
VERYHIGH_MSG("Shifting forward %" PRIu64 "ms (time drift)", dispTime - (p.offset + p.time));
p.time += dispTime - (p.offset + p.time);
}
}else{
if (!frameOffsetKnown){
// Check the first few timestamps against each other, find the smallest distance.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t t1 = pkts[i % PKT_COUNT].time;
for (uint64_t j = 0; j < ctr; ++j){
if (i == j){continue;}
uint64_t t2 = pkts[j % PKT_COUNT].time;
uint64_t tDiff = (t1 < t2) ? (t2 - t1) : (t1 - t2);
if (tDiff < smallestFrame){smallestFrame = tDiff;}
}
}
// Cool, now we're pretty sure we know the frame rate. Let's calculate some offsets.
for (uint64_t i = 1; i < ctr; ++i){
uint64_t timeDiff = pkts[i % PKT_COUNT].time - lowestTime;
uint64_t timeExpt = smallestFrame * i;
if (timeDiff > timeExpt && maxOffset < timeDiff - timeExpt){
maxOffset = timeDiff - timeExpt;
}
if (timeDiff < timeExpt && frameOffset < timeExpt - timeDiff){
frameOffset = timeExpt - timeDiff;
}
}
maxOffset += frameOffset;
// Print for debugging purposes, and consider them gospel from here on forward. Yay!
HIGH_MSG("smallestFrame=%" PRIu16 ", frameOffset=%" PRIu64 ", maxOffset=%" PRIu64,
smallestFrame, frameOffset, maxOffset);
frameOffsetKnown = true;
}
p.offset = ((uint32_t)((frameOffset + (smallestFrame / 2)) / smallestFrame)) * smallestFrame;
}
void remove(){
++rem;
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);
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;}
if (packTime > lowestTime && packTime - lowestTime < smallestFrame){
smallestFrame = packTime - lowestTime;
}
pkts[ctr % PKT_COUNT].set(packTime, packOffset, packTrack, packDataSize, packBytePos, isKeyframe, dataPtr);
++ctr;
if (ctr == PKT_COUNT - 1){frameOffsetKnown = true;}
}
void remove(){++rem;}
};
class InputEBML : public Input{
public:
InputEBML(Util::Config *cfg);
bool needsLock();
protected:
void fillPacket(packetData & C);
void fillPacket(packetData &C);
bool checkArguments();
bool preRun();
bool readHeader();
@ -167,15 +172,12 @@ namespace Mist{
std::map<uint64_t, trackPredictor> packBuf;
std::set<uint64_t> swapEndianness;
bool readExistingHeader();
void parseStreamHeader(){
readHeader();
}
void parseStreamHeader(){readHeader();}
bool openStreamSource(){return true;}
bool needHeader(){return needsLock() && !readExistingHeader();}
double timeScale;
bool wantBlocks;
};
}
}// namespace Mist
typedef Mist::InputEBML mistIn;

View file

@ -1,21 +1,21 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <sys/types.h>//for stat
#include <sys/stat.h>//for stat
#include <unistd.h>//for stat
#include <mist/util.h>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/util.h>
#include <string>
#include <sys/stat.h> //for stat
#include <sys/types.h> //for stat
#include <unistd.h> //for stat
#include "input_flv.h"
namespace Mist {
inputFLV::inputFLV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputFLV::inputFLV(Util::Config *cfg) : Input(cfg){
capa["name"] = "FLV";
capa["desc"] = "Allows loading FLV files for Video on Demand.";
capa["source_match"] = "/*.flv";
@ -28,31 +28,29 @@ namespace Mist {
capa["codecs"][0u][1u].append("MP3");
}
bool inputFLV::checkArguments() {
if (config->getString("input") == "-") {
bool inputFLV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputFLV::preRun() {
//open File
bool inputFLV::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
struct stat statData;
lastModTime = 0;
if (stat(config->getString("input").c_str(), &statData) != -1){
@ -77,9 +75,9 @@ namespace Mist {
return Input::keepRunning();
}
bool inputFLV::readHeader() {
bool inputFLV::readHeader(){
if (!inFile){return false;}
//Create header file from FLV data
// Create header file from FLV data
Util::fseek(inFile, 13, SEEK_SET);
AMF::Object amf_storage;
long long int lastBytePos = 13;
@ -89,12 +87,14 @@ namespace Mist {
tmpTag.toMeta(myMeta, 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);
myMeta.update(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), 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 %llu ms: @%lld, %s, %s", bench / 1000, lastBytePos,
myMeta.vod ? "VoD" : "NOVoD", myMeta.live ? "Live" : "NOLive");
if (FLV::Parse_Error){
tmpTag = FLV::Tag();
FLV::Parse_Error = false;
@ -104,8 +104,8 @@ namespace Mist {
Util::fseek(inFile, 13, SEEK_SET);
return true;
}
void inputFLV::getNext(bool smart) {
void inputFLV::getNext(bool smart){
long long int lastBytePos = Util::ftell(inFile);
if (selectedTracks.size() == 1){
uint8_t targetTag = 0x08;
@ -115,7 +115,7 @@ namespace Mist {
}
while (!feof(inFile) && !FLV::Parse_Error){
if (tmpTag.FileLoader(inFile)){
if ( !selectedTracks.count(tmpTag.getTrackID())){
if (!selectedTracks.count(tmpTag.getTrackID())){
lastBytePos = Util::ftell(inFile);
continue;
}
@ -136,47 +136,45 @@ namespace Mist {
if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){
return getNext();
}
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(), tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); //init packet from tmpTags data
thisPacket.genericFill(tmpTag.tagTime(), tmpTag.offset(), tmpTag.getTrackID(), tmpTag.getData(),
tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); // init packet from tmpTags data
DTSC::Track & trk = myMeta.tracks[tmpTag.getTrackID()];
DTSC::Track &trk = myMeta.tracks[tmpTag.getTrackID()];
if (trk.codec == "PCM" && trk.size == 16){
char * ptr = 0;
char *ptr = 0;
size_t ptrSize = 0;
thisPacket.getString("data", ptr, ptrSize);
for (uint32_t i = 0; i < ptrSize; i+=2){
for (uint32_t i = 0; i < ptrSize; i += 2){
char tmpchar = ptr[i];
ptr[i] = ptr[i+1];
ptr[i+1] = tmpchar;
ptr[i] = ptr[i + 1];
ptr[i + 1] = tmpchar;
}
}
}
void inputFLV::seek(int seekTime) {
//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.
void inputFLV::seek(int seekTime){
// 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;
}
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) {
void inputFLV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
}
}// namespace Mist

View file

@ -2,24 +2,24 @@
#include <mist/dtsc.h>
#include <mist/flv_tag.h>
namespace Mist {
class inputFLV : public Input {
public:
inputFLV(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool keepRunning();
FLV::Tag tmpTag;
uint64_t lastModTime;
FILE * inFile;
namespace Mist{
class inputFLV : public Input{
public:
inputFLV(Util::Config *cfg);
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool keepRunning();
FLV::Tag tmpTag;
uint64_t lastModTime;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::inputFLV mistIn;

View file

@ -1,31 +1,36 @@
#include <mist/stream.h>
#include <mist/defines.h>
#include <sys/types.h>
#include <mist/stream.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "input_folder.h"
namespace Mist {
inputFolder::inputFolder(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputFolder::inputFolder(Util::Config *cfg) : Input(cfg){
capa["name"] = "Folder";
capa["desc"] = "The folder input will make available all supported files in the given folder as streams under this stream name, in the format STREAMNAME+FILENAME. For example, if your stream is called 'files' and you have a file called 'movie.flv', you could access this file streamed as 'files+movie.flv'. This input does not support subdirectories. To support more complex libraries, look into the documentation for the STREAM_SOURCE trigger.";
capa["desc"] =
"The folder input will make available all supported files in the given folder as streams "
"under this stream name, in the format STREAMNAME+FILENAME. For example, if your stream is "
"called 'files' and you have a file called 'movie.flv', you could access this file "
"streamed as 'files+movie.flv'. This input does not support subdirectories. To support "
"more complex libraries, look into the documentation for the STREAM_SOURCE trigger.";
capa["source_match"] = "/*/";
capa["source_file"] = "$source/$wildcard";
capa["priority"] = 9;
capa["morphic"] = 1;
}
int inputFolder::boot(int argc, char * argv[]){
int inputFolder::boot(int argc, char *argv[]){
if (!config->parseArgs(argc, argv)){return 1;}
if (config->getBool("json")){return Input::boot(argc,argv);}
if (config->getBool("json")){return Input::boot(argc, argv);}
streamName = config->getString("streamname");
if (streamName.find_first_of("+ ") == std::string::npos){
FAIL_MSG("Folder input requires a + or space in the stream name.");
return 1;
}
std::string folder = config->getString("input");
if (folder[folder.size() - 1] != '/'){
FAIL_MSG("Input path must end in a forward slash.");
@ -38,16 +43,15 @@ namespace Mist {
FAIL_MSG("Folder input requires a folder as input.");
return 1;
}
std::string path = folder + streamName.substr(streamName.find_first_of("+ ")+1);
std::string path = folder + streamName.substr(streamName.find_first_of("+ ") + 1);
if (stat(path.c_str(), &fileCheck) != 0 || S_ISDIR(fileCheck.st_mode)){
FAIL_MSG("File not found: %s", path.c_str());
return 1;
}
Util::startInput(streamName, path, false);
return 1;
}
}
}// namespace Mist

View file

@ -1,16 +1,17 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputFolder : public Input {
public:
inputFolder(Util::Config * cfg);
int boot(int argc, char * argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
namespace Mist{
class inputFolder : public Input{
public:
inputFolder(Util::Config *cfg);
int boot(int argc, char *argv[]);
protected:
bool checkArguments(){return false;};
bool readHeader(){return false;};
bool needHeader(){return false;};
};
}
}// namespace Mist
typedef Mist::inputFolder mistIn;

View file

@ -5,9 +5,10 @@
namespace Mist{
InputH264::InputH264(Util::Config *cfg) : Input(cfg){
capa["name"] = "H264";
capa["desc"] = "This input allows you to take raw H264 Annex B data over a standard input pipe, and turn it into a live stream.";
capa["desc"] = "This input allows you to take raw H264 Annex B data over a standard input "
"pipe, and turn it into a live stream.";
capa["source_match"] = "h264-exec:*";
//May be set to always-on mode
// May be set to always-on mode
capa["always_match"].append("h264-exec:*");
capa["priority"] = 0;
capa["codecs"][0u][0u].append("H264");
@ -25,7 +26,7 @@ namespace Mist{
char *args[128];
uint8_t argCnt = 0;
char *startCh = 0;
for (char *i = (char*)input.c_str(); i <= input.data() + input.size(); ++i){
for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){
if (!*i){
if (startCh){args[argCnt++] = startCh;}
break;
@ -123,5 +124,4 @@ namespace Mist{
}while (myConn && (inputProcess == 0 || Util::Procs::childRunning(inputProcess)));
if (inputProcess){myConn.close();}
}
}
}// namespace Mist

View file

@ -27,7 +27,6 @@ namespace Mist{
pid_t inputProcess;
uint32_t waitsSinceData;
};
}
}// namespace Mist
typedef Mist::InputH264 mistIn;

View file

@ -1,17 +1,17 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <string>
#include "input_ismv.h"
namespace Mist {
inputISMV::inputISMV(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputISMV::inputISMV(Util::Config *cfg) : Input(cfg){
capa["name"] = "ISMV";
capa["desc"] = "This input allows you to stream ISMV Video on Demand files.";
capa["source_match"] = "/*.ismv";
@ -22,49 +22,41 @@ namespace Mist {
inFile = 0;
}
bool inputISMV::checkArguments() {
if (config->getString("input") == "-") {
bool inputISMV::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputISMV::preRun() {
//open File
bool inputISMV::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputISMV::readHeader() {
if (!inFile) {
return false;
}
//parse ismv header
bool inputISMV::readHeader(){
if (!inFile){return false;}
// parse ismv header
fseek(inFile, 0, SEEK_SET);
std::string ftyp;
readBox("ftyp", ftyp);
if (ftyp == ""){
return false;
}
if (ftyp == ""){return false;}
std::string boxRes;
readBox("moov", boxRes);
if (boxRes == ""){
return false;
}
if (boxRes == ""){return false;}
MP4::MOOV hdrBox;
hdrBox.read(boxRes);
parseMoov(hdrBox);
@ -77,34 +69,30 @@ namespace Mist {
unsigned int lastBytePos = 0;
std::map<int, unsigned int> currentDuration;
unsigned int curBytePos = ftell(inFile);
//parse fragments form here
while (parseFrag(tId, trunSamples, initVecs, mdat)) {
if (!currentDuration.count(tId)) {
currentDuration[tId] = 0;
}
// parse fragments form here
while (parseFrag(tId, trunSamples, initVecs, mdat)){
if (!currentDuration.count(tId)){currentDuration[tId] = 0;}
currOffset = 8;
int i = 0;
while (currOffset < mdat.size()) {
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];
}
if (initVecs.size() == trunSamples.size()){lastPack["ivec"] = initVecs[i];}
lastPack["duration"] = trunSamples[i].sampleDuration;
if (myMeta.tracks[tId].type == "video") {
if (i) {
lastBytePos ++;
} else {
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;
} else {
if (i == 0) {
}else{
if (i == 0){
lastPack["keyframe"] = 1;
lastPack["bpos"] = curBytePos;
}
@ -112,7 +100,7 @@ namespace Mist {
myMeta.update(lastPack);
currentDuration[tId] += trunSamples[i].sampleDuration;
currOffset += trunSamples[i].sampleSize;
i ++;
i++;
}
curBytePos = ftell(inFile);
}
@ -120,7 +108,7 @@ namespace Mist {
return true;
}
void inputISMV::getNext(bool smart) {
void inputISMV::getNext(bool smart){
static JSON::Value thisPack;
thisPack.null();
if (!buffered.size()){
@ -131,22 +119,16 @@ namespace Mist {
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));
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;
}
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;
}
}else{
if (buffered.begin()->isKeyFrame){thisPack["keyframe"] = 1;}
}
thisPack["bpos"] = (uint64_t)buffered.begin()->position;
buffered.erase(buffered.begin());
@ -159,95 +141,96 @@ namespace Mist {
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
}
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
bool inputISMV::atKeyFrame(){
return thisPacket.getFlag("keyframe");
}
void inputISMV::seek(int seekTime) {
///\brief Overloads Input::atKeyFrame, for ISMV always sets the keyframe number
bool inputISMV::atKeyFrame(){return thisPacket.getFlag("keyframe");}
void inputISMV::seek(int seekTime){
buffered.clear();
//Seek to corresponding keyframes on EACH track
// 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?
if (myMeta.tracks[*it].keys[i].getTime() > seekTime && i > 0){// Ehh, whut?
break;
}
}
i --;
i--;
DEBUG_MSG(DLVL_DEVEL, "ISMV seek frag %d:%d", *it, i);
parseFragHeader(*it, i);
lastKeyNum[*it] = i + 1;
}
}
void inputISMV::trackSelect(std::string trackSpec) {
void inputISMV::trackSelect(std::string trackSpec){
selectedTracks.clear();
size_t index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
seek(0);
}
void inputISMV::parseMoov(MP4::MOOV & moovBox) {
for (unsigned int i = 0; i < moovBox.getContentCount(); i++) {
if (moovBox.getContent(i).isType("mvhd")) {
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")) {
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")) {
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")) {
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")) {
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") {
if (subsubContent.getHandlerType() == "soun"){
myMeta.tracks[trackId].type = "audio";
}
if (subsubContent.getHandlerType() == "vide") {
if (subsubContent.getHandlerType() == "vide"){
myMeta.tracks[trackId].type = "video";
}
}
if (subContent.getContent(k).isType("minf")) {
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")) {
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")) {
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")) {
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].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")) {
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].init =
std::string(avc1Box.getCLAP().payload(), avc1Box.getCLAP().payloadSize());
myMeta.tracks[trackId].codec = "H264";
}
}
@ -263,36 +246,36 @@ namespace Mist {
}
}
bool inputISMV::parseFrag(int & tId, std::vector<MP4::trunSampleInformation> & trunSamples, std::vector<std::string> & initVecs, std::string & mdat) {
bool inputISMV::parseFrag(int &tId, std::vector<MP4::trunSampleInformation> &trunSamples,
std::vector<std::string> &initVecs, std::string &mdat){
tId = -1;
trunSamples.clear();
initVecs.clear();
mdat.clear();
std::string boxRes;
readBox("moof", boxRes);
if (boxRes == ""){
return false;
}
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")) {
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")) {
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++) {
for (unsigned int i = 0; i < trunBox.getSampleInformationCount(); i++){
trunSamples.push_back(trunBox.getSampleInformation(i));
}
}
if (trafBox.getContent(j).isType("tfhd")) {
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") {
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++) {
for (unsigned int i = 0; i < uuidBox.getSampleCount(); i++){
initVecs.push_back(uuidBox.getSample(i).InitializationVector);
}
}
@ -302,44 +285,39 @@ namespace Mist {
}
}
readBox("mdat", mdat);
if (mdat ==""){
return false;
}
if (mdat == ""){return false;}
return true;
}
void inputISMV::parseFragHeader(const unsigned int & trackId, const unsigned int & keyNum) {
if (!myMeta.tracks.count(trackId) || (myMeta.tracks[trackId].keys.size() <= keyNum)) {
return;
}
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;
}
if (boxRes == ""){return;}
MP4::MOOF moof;
moof.read(boxRes);
MP4::TRUN trunBox;
MP4::UUID_SampleEncryption uuidBox; /*LTS*/
for (unsigned int i = 0; i < moof.getContentCount(); i++) {
if (moof.getContent(i).isType("traf")) {
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")) {
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 (trafBox.getContent(j).isType("tfhd")){
if (trackId != ((MP4::TFHD &)trafBox.getContent(j)).getTrackID()){
DEBUG_MSG(DLVL_FAIL,"Trackids do not match");
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") {
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);
}
}
@ -355,17 +333,15 @@ namespace Mist {
myPos.time = lastTime;
myPos.duration = trunBox.getSampleInformation(i).sampleDuration;
myPos.size = trunBox.getSampleInformation(i).sampleSize;
if( trunBox.getFlags() & MP4::trunsampleOffsets){
if (trunBox.getFlags() & MP4::trunsampleOffsets){
unsigned int offsetConv = trunBox.getSampleInformation(i).sampleOffset;
myPos.offset = *(int*)&offsetConv;
myPos.offset = *(int *)&offsetConv;
}else{
myPos.offset = 0;
}
myPos.isKeyFrame = (i == 0);
/*LTS-START*/
if (i <= uuidBox.getSampleCount()){
myPos.iVec = uuidBox.getSample(i).InitializationVector;
}
if (i <= uuidBox.getSampleCount()){myPos.iVec = uuidBox.getSample(i).InitializationVector;}
/*LTS-END*/
lastTime += trunBox.getSampleInformation(i).sampleDuration;
lastPos += trunBox.getSampleInformation(i).sampleSize;
@ -373,25 +349,20 @@ namespace Mist {
}
}
void inputISMV::readBox(const char * type, std::string & result) {
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)) {
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));
char *tmpBox = (char *)malloc(boxSize * sizeof(char));
fread(tmpBox, boxSize, 1, inFile);
result = std::string(tmpBox, boxSize);
free(tmpBox);
}
}
}// namespace Mist

View file

@ -1,16 +1,14 @@
#include "input.h"
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/mp4_encryption.h>
#include <mist/mp4_generic.h>
#include <set>
namespace Mist {
struct seekPos {
bool operator < (const seekPos & rhs) const {
if (time < rhs.time){
return true;
}
namespace Mist{
struct seekPos{
bool operator<(const seekPos &rhs) const{
if (time < rhs.time){return true;}
return (time == rhs.time && trackId < rhs.trackId);
}
long long int position;
@ -23,30 +21,30 @@ namespace Mist {
std::string iVec;
};
class inputISMV : public Input{
public:
inputISMV(Util::Config *cfg);
class inputISMV : public Input {
public:
inputISMV(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool atKeyFrame();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool atKeyFrame();
FILE * inFile;
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);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
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);
std::set<seekPos> buffered;
std::map<int, int> lastKeyNum;
};
}
}// namespace Mist
typedef Mist::inputISMV mistIn;

View file

@ -1,19 +1,19 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/flv_tag.h>
#include <mist/mpeg.h>
#include <mist/stream.h>
#include <string>
#include "input_mp3.h"
namespace Mist {
inputMP3::inputMP3(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputMP3::inputMP3(Util::Config *cfg) : Input(cfg){
capa["name"] = "MP3";
capa["desc"] = "This input allows you to stream MP3 Video on Demand files.";
capa["source_match"] = "/*.mp3";
@ -23,51 +23,51 @@ namespace Mist {
timestamp = 0;
}
bool inputMP3::checkArguments() {
if (config->getString("input") == "-") {
bool inputMP3::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
return true;
}
bool inputMP3::preRun() {
//open File
bool inputMP3::preRun(){
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) {
return false;
}
if (!inFile){return false;}
return true;
}
bool inputMP3::readHeader() {
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";
//Create header file from MP3 data
// Create header file from MP3 data
char header[10];
fread(header, 10, 1, inFile);//Read a 10 byte header
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);
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);
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
// Read the first mp3 header for bitrate and such
size_t filePos = ftell(inFile);
fread(header, 4, 1, inFile);
fseek(inFile, filePos, SEEK_SET);
@ -87,25 +87,21 @@ namespace Mist {
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP3::getNext(bool smart) {
void inputMP3::getNext(bool smart){
thisPacket.null();
static char packHeader[3000];
size_t filePos = ftell(inFile);
size_t read = fread(packHeader, 1, 3000, inFile);
if (!read) {
return;
}
if (!read){return;}
if (packHeader[0] != 0xFF || (packHeader[1] & 0xE0) != 0xE0){
//Find the first occurence of sync byte
char* i = (char*)memchr(packHeader, (char)0xFF, read);
if (!i) {
return;
}
// Find the first occurence of sync byte
char *i = (char *)memchr(packHeader, (char)0xFF, read);
if (!i){return;}
size_t offset = i - packHeader;
while (offset && (i[1] & 0xE0) != 0xE0){
i = (char*)memchr(i + 1, (char)0xFF, read - (offset + 1));
if (!i) {
i = (char *)memchr(i + 1, (char)0xFF, read - (offset + 1));
if (!i){
offset = 0;
break;
}
@ -118,60 +114,52 @@ namespace Mist {
fseek(inFile, filePos, SEEK_SET);
read = fread(packHeader, 1, 3000, inFile);
}
//We now have a sync byte for sure
// We now have a sync byte for sure
//mpeg version is on the bits 0x18 of packHeader[1], but only 0x08 is important --> 0 is version 2, 1 is version 1
//leads to 2 - value == version, -1 to get the right index for the array
// mpeg version is on the bits 0x18 of packHeader[1], but only 0x08 is important --> 0 is version 2, 1 is version 1
// leads to 2 - value == version, -1 to get the right index for the array
int mpegVersion = 1 - ((packHeader[1] >> 3) & 0x01);
//mpeg layer is on the bits 0x06 of packHeader[1] --> 1 is layer 3, 2 is layer 2, 3 is layer 1
//leads to 4 - value == layer, -1 to get the right index for the array
// mpeg layer is on the bits 0x06 of packHeader[1] --> 1 is layer 3, 2 is layer 2, 3 is layer 1
// leads to 4 - value == layer, -1 to get the right index for the array
int mpegLayer = 3 - ((packHeader[1] >> 1) & 0x03);
int sampleCount = sampleCounts[mpegVersion][mpegLayer];
//samplerate is encoded in bits 0x0C of packHeader[2];
int sampleCount = sampleCounts[mpegVersion][mpegLayer];
// samplerate is encoded in bits 0x0C of packHeader[2];
int sampleRate = sampleRates[mpegVersion][((packHeader[2] >> 2) & 0x03)] * 1000;
int bitRate = bitRates[mpegVersion][mpegLayer][((packHeader[2] >> 4) & 0x0F)] * 1000;
size_t dataSize = 0;
if (mpegLayer == 0){ //layer 1
//Layer 1: dataSize = (12 * BitRate / SampleRate + Padding) * 4
if (mpegLayer == 0){// layer 1
// Layer 1: dataSize = (12 * BitRate / SampleRate + Padding) * 4
dataSize = (12 * ((double)bitRate / sampleRate) + ((packHeader[2] >> 1) & 0x01)) * 4;
}else{//Layer 2 or 3
//Layer 2, 3: dataSize = 144 * BitRate / SampleRate + Padding
}else{// Layer 2 or 3
// Layer 2, 3: dataSize = 144 * BitRate / SampleRate + Padding
dataSize = 144 * ((double)bitRate / sampleRate) + ((packHeader[2] >> 1) & 0x01);
}
if (!dataSize){
return;
}
if (!dataSize){return;}
fseek(inFile, filePos + dataSize, SEEK_SET);
//Create a json value with the right data
// 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
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
//Update the internal timestamp
// Update the internal timestamp
timestamp += (sampleCount / (sampleRate / 1000));
}
void inputMP3::seek(int seekTime) {
std::deque<DTSC::Key> & keys = myMeta.tracks[1].keys;
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;
}
if (keys[i].getTime() > seekTime){break;}
seekPos = keys[i].getBpos();
timestamp = keys[i].getTime();
}
@ -179,9 +167,8 @@ namespace Mist {
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::trackSelect(std::string trackSpec){
// Ignore, do nothing
// MP3 Always has only 1 track, so we can't select much else..
}
}
}// namespace Mist

View file

@ -1,32 +1,33 @@
#include "input.h"
#include <mist/dtsc.h>
#include <deque>
#include <mist/dtsc.h>
namespace Mist {
const static double sampleRates[2][3] = {{44.1, 48.0, 32.0}, {22.05, 24.0, 16.0}};
const static int sampleCounts[2][3] = {{374, 1152, 1152}, {384, 1152, 576}};
const static int bitRates[2][3][16] = {{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1}},
{{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1}}};
class inputMP3 : public Input {
public:
inputMP3(Util::Config * cfg);
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
double timestamp;
namespace Mist{
const static double sampleRates[2][3] ={{44.1, 48.0, 32.0},{22.05, 24.0, 16.0}};
const static int sampleCounts[2][3] ={{374, 1152, 1152},{384, 1152, 576}};
const static int bitRates[2][3][16] ={
{{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1},
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1}},
{{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1}}};
class inputMP3 : public Input{
public:
inputMP3(Util::Config *cfg);
FILE * inFile;
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
double timestamp;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::inputMP3 mistIn;

View file

@ -1,16 +1,16 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <inttypes.h>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#include <mist/h264.h>
#include <iostream>
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/flv_tag.h>
#include <mist/h264.h>
#include <mist/stream.h>
#include <string>
#include "input_mp4.h"
@ -34,13 +34,11 @@ namespace Mist{
stco64 = false;
}
uint64_t mp4TrackHeader::size(){
return (stszBox.asBox() ? stszBox.getSampleCount() : 0);
}
uint64_t mp4TrackHeader::size(){return (stszBox.asBox() ? stszBox.getSampleCount() : 0);}
void mp4TrackHeader::read(MP4::TRAK & trakBox){
void mp4TrackHeader::read(MP4::TRAK &trakBox){
initialised = false;
std::string tmp;//temporary string for copying box data
std::string tmp; // temporary string for copying box data
MP4::Box trakLoopPeek;
timeScale = 1;
@ -60,26 +58,26 @@ namespace Mist{
hasCTTS = cttsBox.isType("ctts");
}
void mp4TrackHeader::getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset, uint64_t & duration){
void mp4TrackHeader::getPart(uint64_t index, uint64_t &offset, uint32_t &size,
uint64_t &timestamp, int32_t &timeOffset, uint64_t &duration){
if (index < sampleIndex){
sampleIndex = 0;
stscStart = 0;
}
uint64_t stscCount = stscBox.getEntryCount();
MP4::STSCEntry stscEntry;
while (stscStart < stscCount){
stscEntry = stscBox.getSTSCEntry(stscStart);
//check where the next index starts
// check where the next index starts
uint64_t nextSampleIndex;
if (stscStart + 1 < stscCount){
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart+1).firstChunk - stscEntry.firstChunk) * stscEntry.samplesPerChunk;
nextSampleIndex = sampleIndex + (stscBox.getSTSCEntry(stscStart + 1).firstChunk - stscEntry.firstChunk) *
stscEntry.samplesPerChunk;
}else{
nextSampleIndex = stszBox.getSampleCount();
}
if (nextSampleIndex > index){
break;
}
if (nextSampleIndex > index){break;}
sampleIndex = nextSampleIndex;
++stscStart;
}
@ -88,13 +86,11 @@ namespace Mist{
FAIL_MSG("Could not complete seek - not in file (%" PRIu64 " > %" PRIu64 ")", sampleIndex, index);
}
uint64_t stcoPlace = (stscEntry.firstChunk - 1 ) + ((index - sampleIndex) / stscEntry.samplesPerChunk);
uint64_t stcoPlace = (stscEntry.firstChunk - 1) + ((index - sampleIndex) / stscEntry.samplesPerChunk);
uint64_t stszStart = sampleIndex + (stcoPlace - (stscEntry.firstChunk - 1)) * stscEntry.samplesPerChunk;
offset = (stco64 ? co64Box.getChunkOffset(stcoPlace) : stcoBox.getChunkOffset(stcoPlace));
for (int j = stszStart; j < index; j++){
offset += stszBox.getEntrySize(j);
}
for (int j = stszStart; j < index; j++){offset += stszBox.getEntrySize(j);}
if (index < deltaPos){
deltaIndex = 0;
@ -106,24 +102,22 @@ namespace Mist{
uint64_t sttsCount = sttsBox.getEntryCount();
while (deltaIndex < sttsCount){
tmpSTTS = sttsBox.getSTTSEntry(deltaIndex);
if ((index - deltaPos) < tmpSTTS.sampleCount){
break;
}
if ((index - deltaPos) < tmpSTTS.sampleCount){break;}
deltaTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
deltaPos += tmpSTTS.sampleCount;
++deltaIndex;
}
timestamp = ((deltaTotal + ((index-deltaPos) * tmpSTTS.sampleDelta))*1000) / timeScale;
timestamp = ((deltaTotal + ((index - deltaPos) * tmpSTTS.sampleDelta)) * 1000) / timeScale;
duration = 0;
{
uint64_t tmpIndex = deltaIndex;
uint64_t tmpPos = deltaPos;
uint64_t tmpTotal = deltaTotal;
while (tmpIndex < sttsCount){
tmpSTTS = sttsBox.getSTTSEntry(tmpIndex);
if ((index+1 - tmpPos) < tmpSTTS.sampleCount){
duration = (((tmpTotal + ((index+1-tmpPos) * tmpSTTS.sampleDelta))*1000) / timeScale) - timestamp;
if ((index + 1 - tmpPos) < tmpSTTS.sampleCount){
duration = (((tmpTotal + ((index + 1 - tmpPos) * tmpSTTS.sampleDelta)) * 1000) / timeScale) - timestamp;
break;
}
tmpTotal += tmpSTTS.sampleCount * tmpSTTS.sampleDelta;
@ -144,7 +138,7 @@ namespace Mist{
while (offsetIndex < cttsCount){
tmpCTTS = cttsBox.getCTTSEntry(offsetIndex);
if ((index - offsetPos) < tmpCTTS.sampleCount){
timeOffset = (tmpCTTS.sampleOffset*1000)/timeScale;
timeOffset = (tmpCTTS.sampleOffset * 1000) / timeScale;
break;
}
offsetPos += tmpCTTS.sampleCount;
@ -153,10 +147,10 @@ namespace Mist{
}
size = stszBox.getEntrySize(index);
}
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg){
malSize = 4;//initialise data read buffer to 0;
data = (char*)malloc(malSize);
inputMP4::inputMP4(Util::Config *cfg) : Input(cfg){
malSize = 4; // initialise data read buffer to 0;
data = (char *)malloc(malSize);
capa["name"] = "MP4";
capa["desc"] = "This input allows streaming of MP4 files as Video on Demand.";
capa["source_match"] = "/*.mp4";
@ -170,11 +164,9 @@ namespace Mist{
capa["codecs"][0u][1u].append("AC3");
capa["codecs"][0u][1u].append("MP3");
}
inputMP4::~inputMP4(){
free(data);
}
inputMP4::~inputMP4(){free(data);}
bool inputMP4::checkArguments(){
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl;
@ -194,15 +186,12 @@ namespace Mist{
}
return true;
}
bool inputMP4::preRun(){
//open File
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){
return false;
}
if (!inFile){return false;}
return true;
}
bool inputMP4::readHeader(){
@ -210,19 +199,17 @@ namespace Mist{
INFO_MSG("inFile failed!");
return false;
}
uint32_t trackNo = 0;
//first we get the necessary header parts
while(!feof(inFile)){
// first we get the necessary header parts
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
if (boxType == "erro"){
break;
}
if (boxType=="moov"){
if (boxType == "erro"){break;}
if (boxType == "moov"){
MP4::MOOV moovBox;
moovBox.read(inFile);
//for all box in moov
// for all box in moov
std::deque<MP4::TRAK> trak = moovBox.getChildren<MP4::TRAK>();
for (std::deque<MP4::TRAK>::iterator trakIt = trak.begin(); trakIt != trak.end(); trakIt++){
@ -230,25 +217,23 @@ namespace Mist{
}
continue;
}
if (!MP4::skipBox(inFile)){//moving on to next box
if (!MP4::skipBox(inFile)){// moving on to next box
FAIL_MSG("Error in skipping box, exiting");
return false;
}
}
fseeko(inFile,0,SEEK_SET);
//See whether a separate header file exists.
fseeko(inFile, 0, SEEK_SET);
// See whether a separate header file exists.
if (readExistingHeader()){return true;}
HIGH_MSG("Not read existing header");
trackNo = 0;
//Create header file from MP4 data
while(!feof(inFile)){
// Create header file from MP4 data
while (!feof(inFile)){
std::string boxType = MP4::readBoxType(inFile);
if (boxType=="erro"){
break;
}
if (boxType=="moov"){
if (boxType == "erro"){break;}
if (boxType == "moov"){
MP4::MOOV moovBox;
moovBox.read(inFile);
@ -256,7 +241,7 @@ 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;
uint64_t trackNo = myMeta.tracks.size() + 1;
myMeta.tracks[trackNo].trackID = trackNo;
MP4::TKHD tkhdBox = trakIt->getChild<MP4::TKHD>();
@ -283,10 +268,10 @@ namespace Mist{
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", trackNo, sType.c_str());
if (sType == "avc1" || sType == "h264" || sType == "mp4v"){
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "H264";
@ -301,7 +286,7 @@ namespace Mist{
if (initBox.isType("avcC")){
myMeta.tracks[trackNo].init.assign(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...
/// 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){
h264::sequenceParameterSet sps;
sps.fromDTSCInit(myMeta.tracks[trackNo].init);
@ -311,7 +296,7 @@ namespace Mist{
}
}
if (sType == "hev1" || sType == "hvc1"){
MP4::VisualSampleEntry & vEntryBox = (MP4::VisualSampleEntry&)sEntryBox;
MP4::VisualSampleEntry &vEntryBox = (MP4::VisualSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "video";
myMeta.tracks[trackNo].codec = "HEVC";
if (!myMeta.tracks[trackNo].width){
@ -328,7 +313,7 @@ namespace Mist{
}
}
if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){
MP4::AudioSampleEntry & aEntryBox = (MP4::AudioSampleEntry&)sEntryBox;
MP4::AudioSampleEntry &aEntryBox = (MP4::AudioSampleEntry &)sEntryBox;
myMeta.tracks[trackNo].type = "audio";
myMeta.tracks[trackNo].channels = aEntryBox.getChannelCount();
myMeta.tracks[trackNo].rate = aEntryBox.getSampleRate();
@ -336,14 +321,14 @@ namespace Mist{
if (sType == "ac-3"){
myMeta.tracks[trackNo].codec = "AC3";
}else{
MP4::ESDS esdsBox = (MP4::ESDS&)(aEntryBox.getCodecBox());
MP4::ESDS esdsBox = (MP4::ESDS &)(aEntryBox.getCodecBox());
myMeta.tracks[trackNo].codec = esdsBox.getCodec();
myMeta.tracks[trackNo].init = esdsBox.getInitData();
}
myMeta.tracks[trackNo].size = 16;///\todo this might be nice to calculate from mp4 file;
myMeta.tracks[trackNo].size = 16; ///\todo this might be nice to calculate from mp4 file;
}
if (sType == "tx3g"){//plain text subtitles
if (sType == "tx3g"){// plain text subtitles
myMeta.tracks[trackNo].type = "meta";
myMeta.tracks[trackNo].codec = "subtitle";
}
@ -354,12 +339,12 @@ namespace Mist{
MP4::STCO stcoBox = stblBox.getChild<MP4::STCO>();
MP4::CO64 co64Box = stblBox.getChild<MP4::CO64>();
MP4::STSC stscBox = stblBox.getChild<MP4::STSC>();
MP4::CTTS cttsBox = stblBox.getChild<MP4::CTTS>();//optional ctts box
MP4::CTTS cttsBox = stblBox.getChild<MP4::CTTS>(); // optional ctts box
bool stco64 = co64Box.isType("co64");
bool hasCTTS = cttsBox.isType("ctts");
uint64_t totaldur = 0;///\todo note: set this to begin time
uint64_t totaldur = 0; ///\todo note: set this to begin time
mp4PartBpos BsetPart;
uint64_t entryNo = 0;
@ -368,8 +353,8 @@ namespace Mist{
uint64_t stssIndex = 0;
uint64_t stcoIndex = 0;
uint64_t stscIndex = 0;
uint64_t cttsIndex = 0;//current ctts Index we are reading
uint64_t cttsEntryRead = 0;//current part of ctts we are reading
uint64_t cttsIndex = 0; // current ctts Index we are reading
uint64_t cttsEntryRead = 0; // current part of ctts we are reading
uint64_t stssCount = stssBox.getEntryCount();
uint64_t stscCount = stscBox.getEntryCount();
@ -386,35 +371,33 @@ namespace Mist{
for (uint64_t stszIndex = 0; stszIndex < stszCount; ++stszIndex){
if (stcoIndex >= nextFirstChunk){
++stscIndex;
nextFirstChunk = (stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
nextFirstChunk =
(stscIndex + 1 < stscCount ? stscBox.getSTSCEntry(stscIndex + 1).firstChunk - 1 : stcoCount);
}
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount && stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
if (BsetPart.keyframe){
++stssIndex;
}
//in bpos set
BsetPart.keyframe = (myMeta.tracks[trackNo].type == "video" && stssIndex < stssCount &&
stszIndex + 1 == stssBox.getSampleNumber(stssIndex));
if (BsetPart.keyframe){++stssIndex;}
// in bpos set
BsetPart.stcoNr = stcoIndex;
//bpos = chunkoffset[samplenr] in stco
// bpos = chunkoffset[samplenr] in stco
BsetPart.bpos = tmpOffset;
++fromSTCOinSTSC;
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){//as long as we are still in this chunk
if (fromSTCOinSTSC < stscBox.getSTSCEntry(stscIndex).samplesPerChunk){// as long as we are still in this chunk
tmpOffset += stszBox.getEntrySize(stszIndex);
}else{
++stcoIndex;
fromSTCOinSTSC = 0;
tmpOffset = (stco64 ? co64Box.getChunkOffset(stcoIndex) : stcoBox.getChunkOffset(stcoIndex));
}
BsetPart.time = (totaldur*1000)/timescale;
BsetPart.time = (totaldur * 1000) / timescale;
totaldur += sttsEntry.sampleDelta;
sampleNo++;
if (sampleNo >= sttsEntry.sampleCount){
++entryNo;
sampleNo = 0;
if (entryNo < sttsBox.getEntryCount()){
sttsEntry = sttsBox.getSTTSEntry(entryNo);
}
if (entryNo < sttsBox.getEntryCount()){sttsEntry = sttsBox.getSTTSEntry(entryNo);}
}
if (hasCTTS){
MP4::CTTSEntry cttsEntry = cttsBox.getCTTSEntry(cttsIndex);
cttsEntryRead++;
@ -422,71 +405,74 @@ namespace Mist{
++cttsIndex;
cttsEntryRead = 0;
}
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000)/timescale;
BsetPart.timeOffset = (cttsEntry.sampleOffset * 1000) / timescale;
}else{
BsetPart.timeOffset = 0;
}
if(sType == "tx3g"){
if(stszBox.getEntrySize(stszIndex) <=2 && false){
if (sType == "tx3g"){
if (stszBox.getEntrySize(stszIndex) <= 2 && false){
FAIL_MSG("size <=2");
}else{
long long packSendSize = 0;
packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
stszBox.getEntrySize(stszIndex) + 11-2 + 19;
stszBox.getEntrySize(stszIndex) + 11 - 2 + 19;
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex) -2 , BsetPart.bpos, true,
packSendSize);
stszBox.getEntrySize(stszIndex) - 2, BsetPart.bpos, true, packSendSize);
}
}else{
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
}
}
}
continue;
}
if (!MP4::skipBox(inFile)){//moving on to next box
if (!MP4::skipBox(inFile)){// moving on to next box
FAIL_MSG("Error in Skipping box, exiting");
return false;
}
}
clearerr(inFile);
//outputting dtsh file
// outputting dtsh file
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
void inputMP4::getNext(bool smart){//get next part from track in stream
void inputMP4::getNext(bool smart){// get next part from track in stream
if (curPositions.empty()){
thisPacket.null();
return;
}
//pop uit set
// pop uit set
mp4PartTime curPart = *curPositions.begin();
curPositions.erase(curPositions.begin());
bool isKeyframe = false;
if(nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
//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 (nextKeyframe[curPart.trackID] < myMeta.tracks[curPart.trackID].keys.size()){
// 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()){
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 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 (fseeko(inFile,curPart.bpos,SEEK_SET)){
FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s",curPart.bpos, strerror(errno));
if (fseeko(inFile, curPart.bpos, SEEK_SET)){
FAIL_MSG("seek unsuccessful @bpos %" PRIu64 ": %s", curPart.bpos, strerror(errno));
thisPacket.null();
return;
}
if (curPart.size > malSize){
data = (char*)realloc(data, curPart.size);
data = (char *)realloc(data, curPart.size);
malSize = curPart.size;
}
if (fread(data, curPart.size, 1, inFile)!=1){
if (fread(data, curPart.size, 1, inFile) != 1){
FAIL_MSG("read unsuccessful at %" PRIu64, ftell(inFile));
thisPacket.null();
return;
@ -494,44 +480,44 @@ namespace Mist{
if (myMeta.tracks[curPart.trackID].codec == "subtitle"){
unsigned int txtLen = Bit::btohs(data);
if (!txtLen && false ){
curPart.index ++;
if (!txtLen && false){
curPart.index++;
return getNext(smart);
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe);
// 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["data"] = std::string(data + 2, txtLen);
thisPack["time"] = curPart.time;
if (curPart.duration){
thisPack["duration"] = curPart.duration;
}
thisPack["keyframe"] = true;
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;
// return;
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
// thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
}
}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 /*Note: no bpos*/, isKeyframe);
}
//get the next part for this track
curPart.index ++;
// 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);
headerData[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(int seekTime){// seek to a point
nextKeyframe.clear();
//for all tracks
// for all tracks
curPositions.clear();
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
nextKeyframe[*it] = 0;
@ -540,23 +526,23 @@ namespace Mist{
addPart.size = 0;
addPart.time = 0;
addPart.trackID = *it;
//for all indexes in those tracks
// for all indexes in those tracks
for (unsigned int i = 0; i < headerData[*it].size(); i++){
//if time > seekTime
// 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
// check for keyframe time in myMeta and update nextKeyframe
//
if (myMeta.tracks[*it].keys[(nextKeyframe[*it])].getTime() < addPart.time){
nextKeyframe[*it] ++;
nextKeyframe[*it]++;
}
if (addPart.time >= seekTime){
addPart.index = i;
//use addPart thingy in time set and break
// use addPart thingy in time set and break
curPositions.insert(addPart);
break;
}//end if time > seektime
}//end for all indexes
}//rof all tracks
}// end if time > seektime
}// end for all indexes
}// rof all tracks
}
void inputMP4::trackSelect(std::string trackSpec){
@ -565,7 +551,8 @@ namespace Mist{
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);
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{
@ -573,5 +560,4 @@ namespace Mist{
}
}
}
}
}// namespace Mist

View file

@ -2,42 +2,30 @@
#include <mist/dtsc.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
namespace Mist {
namespace Mist{
class mp4PartTime{
public:
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0) {}
bool operator < (const mp4PartTime & rhs) const {
if (time < rhs.time){
return true;
}
if (time > rhs.time){
return false;
}
if (trackID < rhs.trackID){
return true;
}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
uint64_t duration;
int32_t offset;
size_t trackID;
uint64_t bpos;
uint32_t size;
uint64_t index;
public:
mp4PartTime() : time(0), offset(0), trackID(0), bpos(0), size(0), index(0), duration(0){}
bool operator<(const mp4PartTime &rhs) const{
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
if (trackID < rhs.trackID){return true;}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
uint64_t duration;
int32_t offset;
size_t trackID;
uint64_t bpos;
uint32_t size;
uint64_t index;
};
struct mp4PartBpos{
bool operator < (const mp4PartBpos & rhs) const {
if (time < rhs.time){
return true;
}
if (time > rhs.time){
return false;
}
if (trackID < rhs.trackID){
return true;
}
bool operator<(const mp4PartBpos &rhs) const{
if (time < rhs.time){return true;}
if (time > rhs.time){return false;}
if (trackID < rhs.trackID){return true;}
return (trackID == rhs.trackID && bpos < rhs.bpos);
}
uint64_t time;
@ -50,61 +38,64 @@ namespace Mist {
};
class mp4TrackHeader{
public:
mp4TrackHeader();
void read(MP4::TRAK & trakBox);
MP4::STCO stcoBox;
MP4::CO64 co64Box;
MP4::STSZ stszBox;
MP4::STTS sttsBox;
bool hasCTTS;
MP4::CTTS cttsBox;
MP4::STSC stscBox;
uint64_t timeScale;
void getPart(uint64_t index, uint64_t & offset, uint32_t & size, uint64_t & timestamp, int32_t & timeOffset, uint64_t & duration);
uint64_t size();
private:
bool initialised;
//next variables are needed for the stsc/stco loop
uint64_t stscStart;
uint64_t sampleIndex;
//next variables are needed for the stts loop
uint64_t deltaIndex;///< Index in STTS box
uint64_t deltaPos;///< Sample counter for STTS box
uint64_t deltaTotal;///< Total timestamp for STTS box
//for CTTS box loop
uint64_t offsetIndex;///< Index in CTTS box
uint64_t offsetPos;///< Sample counter for CTTS box
public:
mp4TrackHeader();
void read(MP4::TRAK &trakBox);
MP4::STCO stcoBox;
MP4::CO64 co64Box;
MP4::STSZ stszBox;
MP4::STTS sttsBox;
bool hasCTTS;
MP4::CTTS cttsBox;
MP4::STSC stscBox;
uint64_t timeScale;
void getPart(uint64_t index, uint64_t &offset, uint32_t &size, uint64_t &timestamp,
int32_t &timeOffset, uint64_t &duration);
uint64_t size();
bool stco64;
private:
bool initialised;
// next variables are needed for the stsc/stco loop
uint64_t stscStart;
uint64_t sampleIndex;
// next variables are needed for the stts loop
uint64_t deltaIndex; ///< Index in STTS box
uint64_t deltaPos; ///< Sample counter for STTS box
uint64_t deltaTotal; ///< Total timestamp for STTS box
// for CTTS box loop
uint64_t offsetIndex; ///< Index in CTTS box
uint64_t offsetPos; ///< Sample counter for CTTS box
bool stco64;
};
class inputMP4 : public Input {
public:
inputMP4(Util::Config * cfg);
~inputMP4();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
class inputMP4 : public Input{
public:
inputMP4(Util::Config *cfg);
~inputMP4();
FILE * inFile;
std::map<unsigned int, mp4TrackHeader> headerData;
std::set<mp4PartTime> curPositions;
//remember last seeked keyframe;
std::map <unsigned int, unsigned int> 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
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader(){return true;}
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
FILE *inFile;
std::map<unsigned int, mp4TrackHeader> headerData;
std::set<mp4PartTime> curPositions;
// remember last seeked keyframe;
std::map<unsigned int, unsigned int> 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
};
}
}// namespace Mist
typedef Mist::inputMP4 mistIn;

View file

@ -1,50 +1,46 @@
#include <iostream>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/ogg.h>
#include <mist/defines.h>
#include <mist/bitstream.h>
#include <mist/opus.h>
#include "input_ogg.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/bitstream.h>
#include <mist/defines.h>
#include <mist/ogg.h>
#include <mist/opus.h>
#include <mist/stream.h>
#include <string>
///\todo Whar be Opus support?
namespace Mist {
namespace Mist{
JSON::Value segment::toJSON(OGG::oggCodec myCodec){
JSON::Value retval;
retval["time"] = time;
retval["trackid"] = tid;
std::string tmpString = "";
for (unsigned int i = 0; i < parts.size(); i++){
tmpString += parts[i];
}
for (unsigned int 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);
// 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())){
theora::header tmpHeader((char*)tmpString.data(), tmpString.size());
if (tmpHeader.getFTYPE() == 0){
retval["keyframe"] = 1;
}
theora::header tmpHeader((char *)tmpString.data(), tmpString.size());
if (tmpHeader.getFTYPE() == 0){retval["keyframe"] = 1;}
}
}
return retval;
}
/*
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){ //WTF!!?
return blockSize[vModes[vModeIndex].blockFlag];
/*
unsigned long oggTrack::getBlockSize(unsigned int vModeIndex){//WTF!!?
return blockSize[vModes[vModeIndex].blockFlag];
}
*/
inputOGG::inputOGG(Util::Config * cfg) : Input(cfg){
}
*/
inputOGG::inputOGG(Util::Config *cfg) : Input(cfg){
capa["name"] = "OGG";
capa["desc"] = "This input allows streaming of OGG files as Video on Demand.";
capa["source_match"] = "/*.ogg";
@ -63,23 +59,21 @@ namespace Mist {
}
bool inputOGG::preRun(){
//open File
// open File
inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile){
return false;
}
if (!inFile){return false;}
return true;
}
///\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;
void inputOGG::parseBeginOfStream(OGG::Page &bosPage){
// long long int tid = snum2tid.size() + 1;
unsigned int tid = bosPage.getBitstreamSerialNumber();
if (memcmp(bosPage.getSegment(0) + 1, "theora", 6) == 0){
theora::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(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
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;
@ -94,15 +88,15 @@ namespace Mist {
INFO_MSG("Track %lu is %s", tid, myMeta.tracks[tid].codec.c_str());
}
if (memcmp(bosPage.getSegment(0) + 1, "vorbis", 6) == 0){
vorbis::header tmpHead((char*)bosPage.getSegment(0), bosPage.getSegmentLen(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();
//Abusing .contBuffer for temporarily storing the idHeader
oggTracks[tid].blockSize[0] = 1 << tmpHead.getBlockSize0();
oggTracks[tid].blockSize[1] = 1 << tmpHead.getBlockSize1();
// Abusing .contBuffer for temporarily storing the idHeader
bosPage.getSegment(0, oggTracks[tid].contBuffer);
myMeta.tracks[tid].type = "audio";
@ -127,52 +121,51 @@ namespace Mist {
bool inputOGG::readHeader(){
OGG::Page myPage;
fseek(inFile, 0, SEEK_SET);
while (myPage.read(inFile)){ //assumes all headers are sent before any data
while (myPage.read(inFile)){// assumes all headers are sent before any data
unsigned int tid = myPage.getBitstreamSerialNumber();
if (myPage.getHeaderType() & OGG::BeginOfStream){
parseBeginOfStream(myPage);
INFO_MSG("Read BeginOfStream for track %d", tid);
continue; //Continue reading next pages
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<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin();
it != oggTracks.end(); it++){
if (!it->second.parsedHeaders){
readAllHeaders = false;
break;
}
}
if (readAllHeaders){
break;
}
if (readAllHeaders){break;}
// INFO_MSG("tid: %d",tid);
//Parsing headers
// Parsing headers
if (myMeta.tracks[tid].codec == "theora"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
theora::header tmpHead((char*)myPage.getSegment(i),len);
if (!tmpHead.isHeader()){ //not copying the header anymore, should this check isHeader?
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!");
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);
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;
break;
}
// 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);
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;
break;
}
}
}
}
@ -180,52 +173,51 @@ namespace Mist {
if (myMeta.tracks[tid].codec == "vorbis"){
for (unsigned int i = 0; i < myPage.getAllSegments().size(); i++){
unsigned long len = myPage.getSegmentLen(i);
vorbis::header tmpHead((char*)myPage.getSegment(i), len);
vorbis::header tmpHead((char *)myPage.getSegment(i), len);
if (!tmpHead.isHeader()){
DEBUG_MSG(DLVL_FAIL, "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;
//ID header size
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(oggTracks[tid].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);
break;
}
case 5: {
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
oggTracks[tid].parsedHeaders = true;
break;
}
// Case 1 is being handled by parseBeginOfStream
case 3:{
// we have the first header stored in contBuffer
myMeta.tracks[tid].init += (char)0x02;
// ID header size
for (unsigned int j = 0; j < (oggTracks[tid].contBuffer.size() / 255); j++){
myMeta.tracks[tid].init += (char)0xFF;
}
myMeta.tracks[tid].init += (char)(oggTracks[tid].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);
break;
}
case 5:{
myMeta.tracks[tid].init.append(myPage.getSegment(i), len);
oggTracks[tid].vModes = tmpHead.readModeDeque(oggTracks[tid].channels);
oggTracks[tid].parsedHeaders = true;
break;
}
}
}
}
if (myMeta.tracks[tid].codec == "opus"){
oggTracks[tid].parsedHeaders = true;
}
if (myMeta.tracks[tid].codec == "opus"){oggTracks[tid].parsedHeaders = true;}
}
for (std::map<long unsigned int, OGG::oggTrack>::iterator it = oggTracks.begin(); it != oggTracks.end(); it++){
for (std::map<long unsigned int, 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);
position tmp = seekFirstData(it->first);
if (tmp.trackID){
currentPositions.insert(tmp);
} else {
}else{
INFO_MSG("missing track: %lu", it->first);
}
}
@ -252,7 +244,7 @@ namespace Mist {
res.bytepos = ftell(inFile);
readSuccesfull = oggTracks[tid].myPage.read(inFile);
if (!readSuccesfull){
quitloop = true; //break :(
quitloop = true; // break :(
break;
}
if (oggTracks[tid].myPage.getBitstreamSerialNumber() != tid){
@ -264,27 +256,22 @@ namespace Mist {
continue;
}
if (oggTracks[tid].codec == OGG::OPUS){
if (std::string(oggTracks[tid].myPage.getSegment(0), 2) == "Op"){
quitloop = false;
}
if (std::string(oggTracks[tid].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 (tmpHead.isHeader()){
quitloop = false;
}
vorbis::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].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 (tmpHead.isHeader()){
quitloop = false;
}
theora::header tmpHead((char *)oggTracks[tid].myPage.getSegment(0),
oggTracks[tid].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());
if (!readSuccesfull){
res.trackID = 0;
}
INFO_MSG("seek first bytepos: %llu tid: %llu oggTracks[tid].myPage.getHeaderType(): %d ",
res.bytepos, tid, oggTracks[tid].myPage.getHeaderType());
if (!readSuccesfull){res.trackID = 0;}
return res;
}
@ -304,41 +291,39 @@ namespace Mist {
fseek(inFile, curPos.bytepos, SEEK_SET);
OGG::Page curPage;
curPage.read(inFile);
thisSegment.parts.push_back(std::string(curPage.getSegment(curPos.segmentNo), curPage.getSegmentLen(curPos.segmentNo)));
thisSegment.parts.push_back(
std::string(curPage.getSegment(curPos.segmentNo), curPage.getSegmentLen(curPos.segmentNo)));
bool readFullPacket = false;
if (curPos.segmentNo == curPage.getAllSegments().size() - 1){
OGG::Page tmpPage;
unsigned int bPos;
while (!readFullPacket){
bPos = ftell(inFile);//<-- :(
if (!tmpPage.read(inFile)){
break;
}
if (tmpPage.getBitstreamSerialNumber() != thisSegment.tid){
continue;
}
bPos = ftell(inFile); //<-- :(
if (!tmpPage.read(inFile)){break;}
if (tmpPage.getBitstreamSerialNumber() != thisSegment.tid){continue;}
curPos.bytepos = bPos;
curPos.segmentNo = 0;
if (tmpPage.getHeaderType() == OGG::Continued){
thisSegment.parts.push_back(std::string(tmpPage.getSegment(0), tmpPage.getSegmentLen(0)));
curPos.segmentNo = 1;
if (tmpPage.getAllSegments().size() == 1){
continue;
}
} else {
lastCompleteSegment = true; //if this segment ends on the page, use granule to sync video time
if (tmpPage.getAllSegments().size() == 1){continue;}
}else{
lastCompleteSegment = true; // if this segment ends on the page, use granule to sync video time
}
readFullPacket = true;
}
} else {
}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)&& 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 && 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) &&
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
OGG::Page tmpPage;
while (tmpPage.read(inFile) && tmpPage.getBitstreamSerialNumber() != thisSegment.tid){}
if ((tmpPage.getBitstreamSerialNumber() == thisSegment.tid) && tmpPage.getHeaderType() == OGG::Continued){
lastCompleteSegment = true; //this segment should be used to sync time using granule
lastCompleteSegment = true; // this segment should be used to sync time using granule
}
}
readFullPacket = true;
@ -349,101 +334,101 @@ namespace Mist {
if (oggTracks[thisSegment.tid].codec == OGG::VORBIS){
unsigned long blockSize = 0;
Utils::bitstreamLSBF packet;
packet.append((char*)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
packet.append((char *)curPage.getSegment(oldSegNo), curPage.getSegmentLen(oldSegNo));
if (!packet.get(1)){
//Read index first
// Read index first
unsigned long vModeIndex = packet.get(vorbis::ilog(oggTracks[thisSegment.tid].vModes.size() - 1));
blockSize= oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; //almost readable.
} else {
blockSize =
oggTracks[thisSegment.tid].blockSize[oggTracks[thisSegment.tid].vModes[vModeIndex].blockFlag]; // almost readable.
}else{
DEBUG_MSG(DLVL_WARN, "Packet type != 0");
}
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
} else if (oggTracks[thisSegment.tid].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);
curPos.time += oggTracks[thisSegment.tid].msPerFrame * (blockSize / oggTracks[thisSegment.tid].channels);
}else if (oggTracks[thisSegment.tid].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);
curPos.time = thisSegment.time;
std::string tmpStr = thisSegment.toJSON(oggTracks[thisSegment.tid].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){
}else if (oggTracks[thisSegment.tid].codec == OGG::OPUS){
if (thisSegment.parts.size()){
curPos.time += Opus::Opus_getDuration(thisSegment.parts.front().data());
}
}
if (readFullPacket){
currentPositions.insert(curPos);
}
}//getnext()
if (readFullPacket){currentPositions.insert(curPos);}
}// getnext()
long long unsigned int inputOGG::calcGranuleTime(unsigned long tid, long long unsigned int granule){
switch (oggTracks[tid].codec){
case OGG::VORBIS:
return granule * oggTracks[tid].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
break;
}
default:
DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule");
break;
case OGG::VORBIS:
return granule * oggTracks[tid].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
break;
}
default: DEBUG_MSG(DLVL_WARN, "Unknown codec, can not calculate time from granule"); break;
}
return 0;
}
#ifndef _GNU_SOURCE
void * memrchr(const void *s, int c, size_t n){
const unsigned char *cp;
if (n != 0) {
cp = (unsigned char *)s + n;
do {
if (*(--cp) == (unsigned char)c)
return((void *)cp);
} while (--n != 0);
}
return((void *)0);
}
#endif
#ifndef _GNU_SOURCE
void *memrchr(const void *s, int c, size_t n){
const unsigned char *cp;
if (n != 0){
cp = (unsigned char *)s + n;
do{
if (*(--cp) == (unsigned char)c) return ((void *)cp);
}while (--n != 0);
}
return ((void *)0);
}
#endif
void inputOGG::seek(int seekTime){
currentPositions.clear();
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %dms", seekTime);
//for every track
// for every track
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
//find first keyframe before keyframe with ms > seektime
// 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++){
for (std::deque<DTSC::Key>::iterator ot = myMeta.tracks[*it].keys.begin();
ot != myMeta.tracks[*it].keys.end(); ot++){
if (ot->getTime() > seekTime){
break;
} else {
}else{
tmpPos.time = ot->getTime();
tmpPos.bytepos = ot->getBpos();
}
}
INFO_MSG("Found %dms for track %lu at %llu bytepos %llu", seekTime, *it, tmpPos.time, tmpPos.bytepos);
int backChrs=std::min((uint64_t)280, tmpPos.bytepos - 1);
int backChrs = std::min((uint64_t)280, tmpPos.bytepos - 1);
fseek(inFile, tmpPos.bytepos - backChrs, SEEK_SET);
char buffer[300];
fread(buffer, 300, 1, inFile);
char * loc = buffer + backChrs + 2; //start at tmpPos.bytepos+2
char *loc = buffer + backChrs + 2; // start at tmpPos.bytepos+2
while (loc && !(loc[0] == 'O' && loc[1] == 'g' && loc[2] == 'g' && loc[3] == 'S')){
loc = (char *)memrchr(buffer, 'O', (loc-buffer) -1 );//seek reverse
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 @ %llu, track %lu", tmpPos.bytepos, *it);
continue;
}
tmpPos.segmentNo = backChrs - (loc - buffer);
@ -462,17 +447,9 @@ namespace Mist {
selectedTracks.insert(atoll(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
}
}// namespace Mist

View file

@ -2,36 +2,34 @@
#include <mist/dtsc.h>
#include <mist/ogg.h>
namespace Mist {
namespace Mist{
struct segPart {
char * segData;
struct segPart{
char *segData;
unsigned int len;
};
class segment {
public:
segment() : time(0), tid(0), bytepos(0), keyframe(0){}
bool operator < (const segment & rhs) const {
return time < rhs.time || (time == rhs.time && tid < rhs.tid);
}
std::vector<std::string> parts;
uint64_t time;
uint64_t tid;
uint64_t bytepos;
bool keyframe;
JSON::Value toJSON(OGG::oggCodec myCodec);
class segment{
public:
segment() : time(0), tid(0), bytepos(0), keyframe(0){}
bool operator<(const segment &rhs) const{
return time < rhs.time || (time == rhs.time && tid < rhs.tid);
}
std::vector<std::string> parts;
uint64_t time;
uint64_t tid;
uint64_t bytepos;
bool keyframe;
JSON::Value toJSON(OGG::oggCodec myCodec);
};
struct position {
bool operator < (const position & rhs) const {
struct position{
bool operator<(const position &rhs) const{
if (time < rhs.time){
return true;
} else {
}else{
if (time == rhs.time){
if (trackID < rhs.trackID){
return true;
}
if (trackID < rhs.trackID){return true;}
}
}
return false;
@ -41,54 +39,52 @@ namespace Mist {
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 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:
inputOGG(Util::Config * cfg);
protected:
//Private Functions
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);
class inputOGG : public Input{
public:
inputOGG(Util::Config *cfg);
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);
protected:
// Private Functions
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);
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);
};
}
}// namespace Mist
typedef Mist::inputOGG mistIn;

View file

@ -1,14 +1,14 @@
#include "input_playlist.h"
#include <algorithm>
#include <mist/stream.h>
#include <mist/procs.h>
#include <mist/stream.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
namespace Mist {
inputPlaylist::inputPlaylist(Util::Config * cfg) : Input(cfg) {
namespace Mist{
inputPlaylist::inputPlaylist(Util::Config *cfg) : Input(cfg){
capa["name"] = "Playlist";
capa["desc"] = "Enables Playlist Input";
capa["source_match"] = "*.pls";
@ -17,21 +17,21 @@ namespace Mist {
capa["priority"] = 9;
capa["hardcoded"]["resume"] = 1;
playlistIndex = 0xFFFFFFFEull;//Not FFFFFFFF on purpose!
playlistIndex = 0xFFFFFFFEull; // Not FFFFFFFF on purpose!
}
bool inputPlaylist::checkArguments(){
if (config->getString("input") == "-") {
if (config->getString("input") == "-"){
std::cerr << "Input from stdin not supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
if (config->getString("output") == "-"){
std::cerr << "Output to stdout not supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
if (config->getString("output") != "-"){
std::cerr << "File output not supported" << std::endl;
return false;
}
@ -43,48 +43,47 @@ namespace Mist {
bool seenValidEntry = true;
uint64_t startTime = Util::bootMS();
while (config->is_active && nProxy.userClient.isAlive()){
struct tm * wTime;
struct tm *wTime;
time_t nowTime = time(0);
wTime = localtime(&nowTime);
wallTime = wTime->tm_hour*60+wTime->tm_min;
wallTime = wTime->tm_hour * 60 + wTime->tm_min;
nProxy.userClient.keepAlive();
reloadPlaylist();
if (!playlist.size()){
return "No entries in playlist";
}
if (!playlist.size()){return "No entries in playlist";}
++playlistIndex;
if (playlistIndex >= playlist.size()){
if (!seenValidEntry){
HIGH_MSG("Parsed entire playlist without seeing a valid entry, waiting for any entry to become available");
HIGH_MSG("Parsed entire playlist without seeing a valid entry, waiting for any entry to "
"become available");
Util::sleep(1000);
}
playlistIndex = 0;
seenValidEntry = false;
}
if (minIndex != std::string::npos && playlistIndex < minIndex){
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule", playlistIndex, minIndex);
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule",
playlistIndex, minIndex);
playlistIndex = minIndex;
}
if (maxIndex != std::string::npos && playlistIndex > maxIndex){
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule", playlistIndex, maxIndex);
INFO_MSG("Clipping playlist index from %zu to %zu to stay within playback timing schedule",
playlistIndex, maxIndex);
playlistIndex = maxIndex;
}
currentSource = playlist.at(playlistIndex);
std::map<std::string, std::string> overrides;
overrides["realtime"] = "1";
overrides["alwaysStart"] = "";//Just making this value "available" is enough
overrides["alwaysStart"] = ""; // Just making this value "available" is enough
overrides["simulated-starttime"] = JSON::Value(startTime).asString();
std::string srcPath = config->getString("input");
if ((currentSource.size() && currentSource[0] == '/') || srcPath.rfind('/') == std::string::npos){
srcPath = currentSource;
} else {
}else{
srcPath = srcPath.substr(0, srcPath.rfind("/") + 1) + currentSource;
}
char * workingDir = getcwd(NULL, 0);
if (srcPath[0] != '/'){
srcPath = std::string(workingDir) + "/" + srcPath;
}
char *workingDir = getcwd(NULL, 0);
if (srcPath[0] != '/'){srcPath = std::string(workingDir) + "/" + srcPath;}
free(workingDir);
Util::streamVariables(srcPath, streamName, "");
@ -98,7 +97,7 @@ namespace Mist {
continue;
}
pid_t spawn_pid = 0;
//manually override stream url to start the correct input
// manually override stream url to start the correct input
if (!Util::startInput(streamName, srcPath, true, true, overrides, &spawn_pid)){
FAIL_MSG("Could not start input for source %s", srcPath.c_str());
continue;
@ -109,31 +108,29 @@ namespace Mist {
if (reloadOn != 0xFFFF){
time_t nowTime = time(0);
wTime = localtime(&nowTime);
wallTime = wTime->tm_hour*60+wTime->tm_min;
if (wallTime >= reloadOn){
reloadPlaylist();
}
if ((minIndex != std::string::npos && playlistIndex < minIndex) || (maxIndex != std::string::npos && playlistIndex > maxIndex)){
INFO_MSG("Killing current playback to stay within min/max playlist entry for current time of day");
wallTime = wTime->tm_hour * 60 + wTime->tm_min;
if (wallTime >= reloadOn){reloadPlaylist();}
if ((minIndex != std::string::npos && playlistIndex < minIndex) ||
(maxIndex != std::string::npos && playlistIndex > maxIndex)){
INFO_MSG("Killing current playback to stay within min/max playlist entry for current "
"time of day");
Util::Procs::Stop(spawn_pid);
}
}
nProxy.userClient.keepAlive();
}
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){
Util::Procs::Stop(spawn_pid);
}
if (!config->is_active && Util::Procs::isRunning(spawn_pid)){Util::Procs::Stop(spawn_pid);}
}
if (!config->is_active){return "received deactivate signal";}
if (!nProxy.userClient.isAlive()){return "buffer shutdown";}
return "Unknown";
}
void inputPlaylist::reloadPlaylist(){
minIndex = std::string::npos;
maxIndex = std::string::npos;
std::string playlistFile;
char * origSource = getenv("MIST_ORIGINAL_SOURCE");
char *origSource = getenv("MIST_ORIGINAL_SOURCE");
if (origSource){
playlistFile = origSource;
}else{
@ -157,9 +154,9 @@ namespace Mist {
playlist.push_back(line);
playlist_startTime.push_back(plsStartTime);
if (plsStartTime != 0xFFFF){
//If the newest entry has a time under the current time, we know we should never play earlier than this
// If the newest entry has a time under the current time, we know we should never play earlier than this
if (plsStartTime <= wallTime){minIndex = playlist.size() - 1;}
//If the newest entry has a time above the current time, we know we should never play it
// If the newest entry has a time above the current time, we know we should never play it
if (plsStartTime > wallTime && maxIndex == std::string::npos){
maxIndex = playlist.size() - 2;
reloadOn = plsStartTime;
@ -170,12 +167,13 @@ namespace Mist {
}else{
if (line.size() > 13 && line.at(0) == '#' && line.substr(0, 13) == "#X-STARTTIME:"){
int hour, min;
if (sscanf(line.c_str()+13, "%d:%d", &hour, &min) == 2){plsStartTime = hour*60+min;}
if (sscanf(line.c_str() + 13, "%d:%d", &hour, &min) == 2){
plsStartTime = hour * 60 + min;
}
}
}
}
inFile.close();
}
}
}// namespace Mist

View file

@ -1,30 +1,31 @@
#include "input.h"
#include <mist/dtsc.h>
#include <deque>
#include <mist/dtsc.h>
namespace Mist {
class inputPlaylist : public Input {
public:
inputPlaylist(Util::Config * cfg);
bool needsLock(){return false;}
protected:
bool checkArguments();
bool readHeader() { return true; }
virtual void parseStreamHeader() {myMeta.tracks[1].codec = "PLACEHOLDER";}
std::string streamMainLoop();
virtual bool needHeader(){return false;}
private:
void reloadPlaylist();
std::deque<std::string> playlist;
std::deque<uint16_t> playlist_startTime;
std::string currentSource;
size_t playlistIndex;
size_t minIndex, maxIndex;
bool seenValidEntry;
uint32_t wallTime;
uint32_t reloadOn;
namespace Mist{
class inputPlaylist : public Input{
public:
inputPlaylist(Util::Config *cfg);
bool needsLock(){return false;}
protected:
bool checkArguments();
bool readHeader(){return true;}
virtual void parseStreamHeader(){myMeta.tracks[1].codec = "PLACEHOLDER";}
std::string streamMainLoop();
virtual bool needHeader(){return false;}
private:
void reloadPlaylist();
std::deque<std::string> playlist;
std::deque<uint16_t> playlist_startTime;
std::string currentSource;
size_t playlistIndex;
size_t minIndex, maxIndex;
bool seenValidEntry;
uint32_t wallTime;
uint32_t reloadOn;
};
}
}// namespace Mist
typedef Mist::inputPlaylist mistIn;

49
src/input/input_rtsp.cpp Executable file → Normal file
View file

@ -3,8 +3,12 @@
Mist::InputRTSP *classPointer = 0;
Socket::Connection *mainConn = 0;
void incomingPacket(const DTSC::Packet &pkt){classPointer->incoming(pkt);}
void insertRTP(const uint64_t track, const RTP::Packet &p){classPointer->incomingRTP(track, p);}
void incomingPacket(const DTSC::Packet &pkt){
classPointer->incoming(pkt);
}
void insertRTP(const uint64_t track, const RTP::Packet &p){
classPointer->incomingRTP(track, p);
}
/// Function used to send RTP packets over UDP
///\param socket A UDP Connection pointer, sent as a void*, to keep portability.
@ -17,7 +21,9 @@ void sendUDP(void *socket, char *data, unsigned int len, unsigned int channel){
}
namespace Mist{
void InputRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){sdpState.handleIncomingRTP(track, p);}
void InputRTSP::incomingRTP(const uint64_t track, const RTP::Packet &p){
sdpState.handleIncomingRTP(track, p);
}
InputRTSP::InputRTSP(Util::Config *cfg) : Input(cfg){
needAuth = false;
@ -79,10 +85,8 @@ namespace Mist{
capa["optional"]["transport"]["default"] = "TCP";
}
void InputRTSP::sendCommand(const std::string &cmd, const std::string &cUrl,
const std::string &body,
const std::map<std::string, std::string> *extraHeaders,
bool reAuth){
void InputRTSP::sendCommand(const std::string &cmd, const std::string &cUrl, const std::string &body,
const std::map<std::string, std::string> *extraHeaders, bool reAuth){
++cSeq;
sndH.Clean();
sndH.protocol = "RTSP/1.0";
@ -107,9 +111,7 @@ namespace Mist{
if (reAuth && needAuth && authRequest.size() && (username.size() || password.size()) && tcpCon){
INFO_MSG("Authenticating %s...", cmd.c_str());
sendCommand(cmd, cUrl, body, extraHeaders, false);
if (needAuth){
FAIL_MSG("Authentication failed! Are the provided credentials correct?");
}
if (needAuth){FAIL_MSG("Authentication failed! Are the provided credentials correct?");}
}
}
@ -156,16 +158,17 @@ namespace Mist{
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);
sendCommand("SETUP", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &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());
INFO_MSG("Failed to set up transport for track %s, switching transports...",
myMeta.tracks[it->first].getIdentifier().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", HTTP::URL(url.getUrl() + "/").link(it->second.control).getUrl(), "", &extraHeaders);
}
if (tcpCon && transportSet){
atLeastOne = true;
@ -265,8 +268,10 @@ namespace Mist{
if (recH.hasHeader("Content-Location")){
url = HTTP::URL(recH.GetHeader("Content-Location"));
}
if (recH.hasHeader("Content-Base") && recH.GetHeader("Content-Base") != "" && recH.GetHeader("Content-Base") != url.getUrl()){
INFO_MSG("Changing base URL from %s to %s", url.getUrl().c_str(), recH.GetHeader("Content-Base").c_str());
if (recH.hasHeader("Content-Base") && recH.GetHeader("Content-Base") != "" &&
recH.GetHeader("Content-Base") != url.getUrl()){
INFO_MSG("Changing base URL from %s to %s", url.getUrl().c_str(),
recH.GetHeader("Content-Base").c_str());
url = HTTP::URL(recH.GetHeader("Content-Base"));
}
if (recH.hasHeader("Session")){
@ -276,8 +281,9 @@ namespace Mist{
}
}
if ((recH.hasHeader("Content-Type") &&
recH.GetHeader("Content-Type") == "application/sdp") || (recH.hasHeader("Content-type") &&
recH.GetHeader("Content-type") == "application/sdp")){
recH.GetHeader("Content-Type") == "application/sdp") ||
(recH.hasHeader("Content-type") &&
recH.GetHeader("Content-type") == "application/sdp")){
INFO_MSG("Received SDP");
seenSDP = true;
sdpState.parseSDP(recH.body);
@ -308,8 +314,8 @@ namespace Mist{
return true;
}
//DO NOT Print anything possibly interesting to cerr
//std::cerr << recH.BuildRequest() << std::endl;
// DO NOT Print anything possibly interesting to cerr
// std::cerr << recH.BuildRequest() << std::endl;
recH.Clean();
return true;
}
@ -371,9 +377,7 @@ namespace Mist{
//}
tcpCon.addDown(s.data_len);
RTP::Packet pack(s.data, s.data_len);
if (!it->second.theirSSRC){
it->second.theirSSRC = pack.getSSRC();
}
if (!it->second.theirSSRC){it->second.theirSSRC = pack.getSSRC();}
it->second.sorter.addPacket(pack);
}
if (Util::epoch() / 5 != it->second.rtcpSent){
@ -387,4 +391,3 @@ namespace Mist{
void InputRTSP::incoming(const DTSC::Packet &pkt){nProxy.bufferLivePacket(pkt, myMeta);}
}// namespace Mist

3
src/input/input_rtsp.h Executable file → Normal file
View file

@ -28,7 +28,7 @@ namespace Mist{
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);
const std::map<std::string, std::string> *extraHeaders = 0, bool reAuth = true);
bool parsePacket(bool mustHave = false);
bool handleUDP();
std::string streamMainLoop();
@ -49,4 +49,3 @@ namespace Mist{
}// namespace Mist
typedef Mist::InputRTSP mistIn;

View file

@ -5,7 +5,8 @@ namespace Mist{
InputSrt::InputSrt(Util::Config *cfg) : Input(cfg){
vtt = false;
capa["name"] = "SRT";
capa["decs"] = "This input allows streaming of SRT and WebVTT subtitle files as Video on Demand.";
capa["decs"] =
"This input allows streaming of SRT and WebVTT subtitle files as Video on Demand.";
capa["source_match"].append("/*.srt");
capa["source_match"].append("/*.vtt");
capa["priority"] = 9;
@ -120,10 +121,8 @@ namespace Mist{
int to_ms = 0;
sscanf(line.c_str(), "%d:%d:%d,%d --> %d:%d:%d,%d", &from_hour, &from_min, &from_sec,
&from_ms, &to_hour, &to_min, &to_sec, &to_ms);
timestamp =
(from_hour * 60 * 60 * 1000) + (from_min * 60 * 1000) + (from_sec * 1000) + from_ms;
duration = ((to_hour * 60 * 60 * 1000) + (to_min * 60 * 1000) + (to_sec * 1000) + to_ms) -
timestamp;
timestamp = (from_hour * 60 * 60 * 1000) + (from_min * 60 * 1000) + (from_sec * 1000) + from_ms;
duration = ((to_hour * 60 * 60 * 1000) + (to_min * 60 * 1000) + (to_sec * 1000) + to_ms) - timestamp;
}else{
// subtitle
if (data.size() > 1){data.append("\n");}
@ -143,4 +142,3 @@ namespace Mist{
}
}// namespace Mist

View file

@ -4,31 +4,26 @@
#include <mist/dtsc.h>
#include <string>
namespace Mist{
class InputSrt : public Input{
public:
InputSrt(Util::Config *cfg);
public:
InputSrt(Util::Config *cfg);
protected:
std::ifstream fileSource;
protected:
std::ifstream fileSource;
bool checkArguments();
bool readHeader();
bool preRun();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool vtt;
FILE * inFile;
bool checkArguments();
bool readHeader();
bool preRun();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
bool vtt;
FILE *inFile;
};
}
}// namespace Mist
typedef Mist::InputSrt mistIn;

View file

@ -1,48 +1,46 @@
#include <mist/util.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/flv_tag.h>
#include <mist/defines.h>
#include <mist/ts_packet.h>
#include <mist/timing.h>
#include <mist/mp4_generic.h>
#include <mist/http_parser.h>
#include <mist/downloader.h>
#include "input_ts.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <mist/defines.h>
#include <mist/downloader.h>
#include <mist/flv_tag.h>
#include <mist/http_parser.h>
#include <mist/mp4_generic.h>
#include <mist/stream.h>
#include <mist/timing.h>
#include <mist/ts_packet.h>
#include <mist/util.h>
#include <string>
#include <mist/tinythread.h>
#include <mist/procs.h>
#include <mist/tinythread.h>
#include <sys/stat.h>
tthread::mutex threadClaimMutex;
std::string globalStreamName;
TS::Stream liveStream(true);
Util::Config * cfgPointer = NULL;
Util::Config *cfgPointer = NULL;
#define THREAD_TIMEOUT 15
std::map<unsigned long long, unsigned long long> threadTimer;
std::set<unsigned long> claimableThreads;
void parseThread(void * ignored) {
void parseThread(void *ignored){
int tid = -1;
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
if (claimableThreads.size()) {
if (claimableThreads.size()){
tid = *claimableThreads.begin();
claimableThreads.erase(claimableThreads.begin());
}
if (tid == -1) {
return;
}
if (tid == -1){return;}
}
Mist::negotiationProxy myProxy;
@ -50,7 +48,8 @@ void parseThread(void * ignored) {
DTSC::Meta myMeta;
if (liveStream.isDataTrack(tid)){
if (!Util::streamAlive(globalStreamName) && !Util::startInput(globalStreamName, "push://INTERNAL_ONLY:"+cfgPointer->getString("input"), true, true)) {
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;
}
@ -62,21 +61,21 @@ void parseThread(void * ignored) {
}
threadTimer[tid] = Util::bootSecs();
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active && (!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())) {
while (Util::bootSecs() - threadTimer[tid] < THREAD_TIMEOUT && cfgPointer->is_active &&
(!liveStream.isDataTrack(tid) || myProxy.userClient.isAlive())){
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer[tid] = Util::bootSecs();
}
if (liveStream.isDataTrack(tid)){
myProxy.userClient.keepAlive();
}
if (liveStream.isDataTrack(tid)){myProxy.userClient.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()))){
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);
@ -106,13 +105,16 @@ void parseThread(void * ignored) {
myProxy.userClient.finish();
}
namespace Mist {
namespace Mist{
/// Constructor of TS Input
/// \arg cfg Util::Config that contains all current configurations.
inputTS::inputTS(Util::Config * cfg) : Input(cfg) {
inputTS::inputTS(Util::Config *cfg) : Input(cfg){
capa["name"] = "TS";
capa["desc"] = "This input allows you to stream MPEG2-TS data from static files (/*.ts), streamed files or named pipes (stream://*.ts), streamed over HTTP (http(s)://*.ts, http(s)-ts://*), standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
capa["desc"] =
"This input allows you to stream MPEG2-TS data from static files (/*.ts), streamed files "
"or named pipes (stream://*.ts), streamed over HTTP (http(s)://*.ts, http(s)-ts://*), "
"standard input (ts-exec:*), or multicast/unicast UDP sockets (tsudp://*).";
capa["source_match"].append("/*.ts");
capa["source_file"] = "$source";
capa["source_match"].append("stream://*.ts");
@ -122,7 +124,7 @@ namespace Mist {
capa["source_match"].append("http-ts://*");
capa["source_match"].append("https://*.ts");
capa["source_match"].append("https-ts://*");
//These can/may be set to always-on mode
// These can/may be set to always-on mode
capa["always_match"].append("stream://*.ts");
capa["always_match"].append("tsudp://*");
capa["always_match"].append("ts-exec:*");
@ -145,14 +147,16 @@ namespace Mist {
{
int fin = 0, fout = 0, ferr = 0;
pid_t srt_tx = -1;
const char *args[] = {"srt-live-transmit", 0};
const char *args[] ={"srt-live-transmit", 0};
srt_tx = Util::Procs::StartPiped(args, 0, 0, 0);
if (srt_tx > 1){
capa["source_match"].append("srt://*");
capa["always_match"].append("srt://*");
capa["desc"] = capa["desc"].asStringRef() + " SRT support (srt://*) is installed and available.";
capa["desc"] =
capa["desc"].asStringRef() + " SRT support (srt://*) is installed and available.";
}else{
capa["desc"] = capa["desc"].asStringRef() + " To enable SRT support, please install the srt-live-transmit binary.";
capa["desc"] = capa["desc"].asStringRef() +
" To enable SRT support, please install the srt-live-transmit binary.";
}
}
@ -164,19 +168,18 @@ namespace Mist {
option["value"].append(50000);
config->addOption("bufferTime", option);
capa["optional"]["DVR"]["name"] = "Buffer time (ms)";
capa["optional"]["DVR"]["help"] = "The target available buffer time for this live stream, in milliseconds. This is the time available to seek around in, and will automatically be extended to fit whole keyframes as well as the minimum duration needed for stable playback.";
capa["optional"]["DVR"]["help"] =
"The target available buffer time for this live stream, in milliseconds. This is the time "
"available to seek around in, and will automatically be extended to fit whole keyframes as "
"well as the minimum duration needed for stable playback.";
capa["optional"]["DVR"]["option"] = "--buffer";
capa["optional"]["DVR"]["type"] = "uint";
capa["optional"]["DVR"]["default"] = 50000;
}
inputTS::~inputTS() {
if (inFile) {
fclose(inFile);
}
if (tcpCon){
tcpCon.close();
}
inputTS::~inputTS(){
if (inFile){fclose(inFile);}
if (tcpCon){tcpCon.close();}
if (!standAlone){
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadTimer.clear();
@ -184,46 +187,46 @@ namespace Mist {
}
}
bool inputTS::checkArguments(){
if (config->getString("input").substr(0, 6) == "srt://"){
std::string source = config->getString("input");
HTTP::URL srtUrl(source);
config->getOption("input", true).append("ts-exec:srt-live-transmit "+srtUrl.getUrl()+" file://con");
config->getOption("input", true).append("ts-exec:srt-live-transmit " + srtUrl.getUrl() + " file://con");
INFO_MSG("Rewriting SRT source '%s' to '%s'", source.c_str(), config->getString("input").c_str());
}
return true;
}
///Live Setup of TS Input
bool inputTS::preRun() {
//streamed standard input
if (config->getString("input") == "-") {
/// Live Setup of TS Input
bool inputTS::preRun(){
// streamed standard input
if (config->getString("input") == "-"){
standAlone = false;
tcpCon.open(fileno(stdout), fileno(stdin));
return true;
}
if (config->getString("input").substr(0, 7) == "http://" || config->getString("input").substr(0, 10) == "http-ts://" || config->getString("input").substr(0, 8) == "https://" || config->getString("input").substr(0, 11) == "https-ts://"){
if (config->getString("input").substr(0, 7) == "http://" ||
config->getString("input").substr(0, 10) == "http-ts://" ||
config->getString("input").substr(0, 8) == "https://" ||
config->getString("input").substr(0, 11) == "https-ts://"){
standAlone = false;
HTTP::URL url(config->getString("input"));
if (url.protocol == "http-ts"){url.protocol = "http";}
if (url.protocol == "https-ts"){url.protocol = "https";}
HTTP::Downloader DL;
DL.getHTTP().headerOnly = true;
if (!DL.get(url)){
return false;
}
if (!DL.get(url)){return false;}
tcpCon = DL.getSocket();
DL.getSocket().drop();//Prevent shutdown of connection, keeping copy of socket open
DL.getSocket().drop(); // Prevent shutdown of connection, keeping copy of socket open
return true;
}
if (config->getString("input").substr(0, 8) == "ts-exec:") {
if (config->getString("input").substr(0, 8) == "ts-exec:"){
standAlone = false;
std::string input = config->getString("input").substr(8);
char *args[128];
uint8_t argCnt = 0;
char *startCh = 0;
for (char *i = (char*)input.c_str(); i <= input.data() + input.size(); ++i){
for (char *i = (char *)input.c_str(); i <= input.data() + input.size(); ++i){
if (!*i){
if (startCh){args[argCnt++] = startCh;}
break;
@ -245,70 +248,67 @@ namespace Mist {
tcpCon.open(-1, fout);
return true;
}
//streamed file
if (config->getString("input").substr(0,9) == "stream://"){
inFile = fopen(config->getString("input").c_str()+9, "r");
// streamed file
if (config->getString("input").substr(0, 9) == "stream://"){
inFile = fopen(config->getString("input").c_str() + 9, "r");
tcpCon.open(-1, fileno(inFile));
standAlone = false;
return inFile;
}
//UDP input (tsudp://[host:]port[/iface[,iface[,...]]])
// UDP input (tsudp://[host:]port[/iface[,iface[,...]]])
if (config->getString("input").substr(0, 8) == "tsudp://"){
standAlone = false;
return true;
}
//plain VoD file
// plain VoD file
inFile = fopen(config->getString("input").c_str(), "r");
return inFile;
}
///Track selector of TS Input
/// 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) {
void inputTS::trackSelect(std::string trackSpec){
selectedTracks.clear();
long long int index;
while (trackSpec != "") {
while (trackSpec != ""){
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
if (index != std::string::npos){
trackSpec.erase(0, index + 1);
} else {
}else{
trackSpec = "";
}
}
}
bool inputTS::needHeader(){
if (!standAlone){return false;}
return Input::needHeader();
}
///Reads headers from a TS stream, and saves them into metadata
///It works by going through the entire TS stream, and every time
///It encounters a new PES start, it writes the currently found PES data
///for a specific track to metadata. After the entire stream has been read,
///it writes the remaining metadata.
/// Reads headers from a TS stream, and saves them into metadata
/// It works by going through the entire TS stream, and every time
/// It encounters a new PES start, it writes the currently found PES data
/// for a specific track to metadata. After the entire stream has been read,
/// it writes the remaining metadata.
///\todo Find errors, perhaps parts can be made more modular
bool inputTS::readHeader() {
bool inputTS::readHeader(){
if (!inFile){return false;}
TS::Packet packet;//to analyse and extract data
TS::Packet packet; // to analyse and extract data
DTSC::Packet headerPack;
fseek(inFile, 0, SEEK_SET);//seek to beginning
fseek(inFile, 0, SEEK_SET); // seek to beginning
uint64_t lastBpos = 0;
while (packet.FromFile(inFile) && !feof(inFile)) {
while (packet.FromFile(inFile) && !feof(inFile)){
tsStream.parse(packet, lastBpos);
lastBpos = Util::ftell(inFile);
if (packet.getUnitStart()){
while (tsStream.hasPacketOnEachTrack()) {
while (tsStream.hasPacketOnEachTrack()){
tsStream.getEarliestPacket(headerPack);
if (!headerPack){
break;
}
if (!myMeta.tracks.count(headerPack.getTrackId()) || !myMeta.tracks[headerPack.getTrackId()].codec.size()) {
if (!headerPack){break;}
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
}
myMeta.update(headerPack);
@ -316,34 +316,38 @@ namespace Mist {
}
}
tsStream.finish();
INFO_MSG("Reached %s at %llu bytes", feof(inFile)?"EOF":"error", lastBpos);
while (tsStream.hasPacket()) {
INFO_MSG("Reached %s at %llu bytes", feof(inFile) ? "EOF" : "error", lastBpos);
while (tsStream.hasPacket()){
tsStream.getEarliestPacket(headerPack);
if (!myMeta.tracks.count(headerPack.getTrackId()) || !myMeta.tracks[headerPack.getTrackId()].codec.size()) {
if (!myMeta.tracks.count(headerPack.getTrackId()) ||
!myMeta.tracks[headerPack.getTrackId()].codec.size()){
tsStream.initializeMetadata(myMeta, headerPack.getTrackId());
}
myMeta.update(headerPack);
}
fseek(inFile, 0, SEEK_SET);
myMeta.toFile(config->getString("input") + ".dtsh");
return true;
}
///Gets the next packet that is to be sent
///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.
/// Gets the next packet that is to be sent
/// 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) {
void inputTS::getNext(bool smart){
INSANE_MSG("Getting next");
thisPacket.null();
bool hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
while (!hasPacket && !feof(inFile) && (inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active) {
bool hasPacket =
(selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
while (!hasPacket && !feof(inFile) &&
(inputProcess == 0 || Util::Procs::childRunning(inputProcess)) && config->is_active){
tsBuf.FromFile(inFile);
if (selectedTracks.count(tsBuf.getPID())) {
tsStream.parse(tsBuf, 0);//bPos == 0
if (selectedTracks.count(tsBuf.getPID())){
tsStream.parse(tsBuf, 0); // bPos == 0
if (tsBuf.getUnitStart()){
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin()) : tsStream.hasPacket());
hasPacket = (selectedTracks.size() == 1 ? tsStream.hasPacket(*selectedTracks.begin())
: tsStream.hasPacket());
}
}
}
@ -351,73 +355,62 @@ namespace Mist {
tsStream.finish();
hasPacket = true;
}
if (!hasPacket) {
return;
}
if (selectedTracks.size() == 1) {
if (!hasPacket){return;}
if (selectedTracks.size() == 1){
if (tsStream.hasPacket(*selectedTracks.begin())){
tsStream.getPacket(*selectedTracks.begin(), thisPacket);
}
} else {
if (tsStream.hasPacket()){
tsStream.getEarliestPacket(thisPacket);
}
}else{
if (tsStream.hasPacket()){tsStream.getEarliestPacket(thisPacket);}
}
if (!thisPacket){
INFO_MSG("Could not getNext TS packet!");
return;
}
tsStream.initializeMetadata(myMeta);
if (!myMeta.tracks.count(thisPacket.getTrackId())) {
getNext();
}
if (!myMeta.tracks.count(thisPacket.getTrackId())){getNext();}
}
void inputTS::readPMT() {
//save current file position
void inputTS::readPMT(){
// save current file position
uint64_t bpos = Util::ftell(inFile);
if (fseek(inFile, 0, SEEK_SET)) {
if (fseek(inFile, 0, SEEK_SET)){
FAIL_MSG("Seek to 0 failed");
return;
}
TS::Packet tsBuffer;
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)) {
while (!tsStream.hasPacketOnEachTrack() && tsBuffer.FromFile(inFile)){
tsStream.parse(tsBuffer, 0);
}
//Clear leaves the PMT in place
// Clear leaves the PMT in place
tsStream.partialClear();
//Restore original file position
if (Util::fseek(inFile, bpos, SEEK_SET)) {
return;
}
// Restore original file position
if (Util::fseek(inFile, bpos, SEEK_SET)){return;}
}
///Seeks to a specific time
void inputTS::seek(int seekTime) {
/// Seeks to a specific time
void inputTS::seek(int seekTime){
tsStream.clear();
readPMT();
uint64_t seekPos = 0xFFFFFFFFFFFFFFFFull;
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++) {
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;
}
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());
}
if (thisBPos < seekPos) {
seekPos = thisBPos;
}
if (thisBPos < seekPos){seekPos = thisBPos;}
}
Util::fseek(inFile, seekPos, SEEK_SET);//seek to the correct position
Util::fseek(inFile, seekPos, SEEK_SET); // seek to the correct position
}
bool inputTS::openStreamSource(){
const std::string & inpt = config->getString("input");
const std::string &inpt = config->getString("input");
if (inpt.substr(0, 8) == "tsudp://"){
HTTP::URL input_url(inpt);
udpCon.setBlocking(false);
@ -431,12 +424,12 @@ namespace Mist {
}
void inputTS::parseStreamHeader(){
//Placeholder to force normal code to continue despite no tracks available
// Placeholder to force normal code to continue despite no tracks available
myMeta.tracks[0].type = "audio";
}
std::string inputTS::streamMainLoop() {
myMeta.tracks.clear();//wipe the placeholder track from above
std::string inputTS::streamMainLoop(){
myMeta.tracks.clear(); // wipe the placeholder track from above
IPC::sharedClient statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
uint64_t downCounter = 0;
uint64_t startTime = Util::epoch();
@ -446,8 +439,8 @@ namespace Mist {
cfgPointer = config;
globalStreamName = streamName;
unsigned long long threadCheckTimer = Util::bootSecs();
while (config->is_active && nProxy.userClient.isAlive()) {
if (tcpCon) {
while (config->is_active && nProxy.userClient.isAlive()){
if (tcpCon){
if (tcpCon.spool()){
while (tcpCon.Received().available(188)){
while (tcpCon.Received().get()[0] != 0x47 && tcpCon.Received().available(188)){
@ -457,9 +450,7 @@ namespace Mist {
std::string newData = tcpCon.Received().remove(188);
tsBuf.FromPointer(newData.data());
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
}
}
noDataSince = Util::bootSecs();
@ -470,10 +461,10 @@ namespace Mist {
config->is_active = false;
return "end of streamed input";
}
} else {
}else{
std::string leftData;
bool received = false;
while (udpCon.Receive()) {
while (udpCon.Receive()){
downCounter += udpCon.data_len;
received = true;
if (!gettingData){
@ -481,34 +472,31 @@ namespace Mist {
INFO_MSG("Now receiving UDP data...");
}
int 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) {
if (udpCon.data[offset] == 0x47){//check for sync byte
// 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){
if (udpCon.data[offset] == 0x47){// check for sync byte
if (offset + 188 <= udpCon.data_len){
tsBuf.FromPointer(udpCon.data + offset);
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
leftData.clear();
}else{
leftData.append(udpCon.data + offset, udpCon.data_len - offset);
}
offset += 188;
}else{
uint32_t maxBytes = std::min((uint32_t)(188 - leftData.size()), (uint32_t)(udpCon.data_len - offset));
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);
if (leftData.size()){
leftData.append(udpCon.data + offset, numBytes);
while (leftData.size() >= 188){
VERYHIGH_MSG("Assembled scrap packet");
tsBuf.FromPointer((char*)leftData.data());
tsBuf.FromPointer((char *)leftData.data());
liveStream.add(tsBuf);
if (!liveStream.isDataTrack(tsBuf.getPID())){
liveStream.parse(tsBuf.getPID());
}
if (!liveStream.isDataTrack(tsBuf.getPID())){liveStream.parse(tsBuf.getPID());}
leftData.erase(0, 188);
}
}
@ -526,9 +514,9 @@ namespace Mist {
gettingData = false;
INFO_MSG("No longer receiving data.");
}
//Check for and spawn threads here.
if (Util::bootSecs() - threadCheckTimer > 1) {
//Connect to stats for INPUT detection
// 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);
@ -564,21 +552,20 @@ namespace Mist {
hasStarted = false;
}
}
for (std::set<size_t>::iterator it = activeTracks.begin(); it != activeTracks.end(); it++) {
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.", *it, Util::bootSecs() - threadTimer[*it]);
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.",
*it, Util::bootSecs() - threadTimer[*it]);
threadTimer.erase(*it);
}
if (!hasStarted){
hasStarted = true;
}
if (!threadTimer.count(*it)) {
if (!hasStarted){hasStarted = true;}
if (!threadTimer.count(*it)){
//Add to list of unclaimed threads
// Add to list of unclaimed threads
claimableThreads.insert(*it);
//Spawn thread here.
// Spawn thread here.
tthread::thread thisThread(parseThread, 0);
thisThread.detach();
}
@ -600,34 +587,34 @@ namespace Mist {
return "received shutdown request";
}
void inputTS::finish() {
void inputTS::finish(){
if (standAlone){
Input::finish();
return;
}
int threadCount = 0;
do {
do{
{
tthread::lock_guard<tthread::mutex> guard(threadClaimMutex);
threadCount = threadTimer.size();
}
if (threadCount){
Util::sleep(100);
}
} while (threadCount);
if (threadCount){Util::sleep(100);}
}while (threadCount);
}
bool inputTS::needsLock() {
//we already know no lock will be needed
bool inputTS::needsLock(){
// we already know no lock will be needed
if (!standAlone){return false;}
//otherwise, check input param
const std::string & inpt = config->getString("input");
if (inpt.size() && inpt != "-" && inpt.substr(0,9) != "stream://" && inpt.substr(0,8) != "tsudp://" && inpt.substr(0, 8) != "ts-exec:" && inpt.substr(0, 6) != "srt://" && inpt.substr(0, 7) != "http://" && inpt.substr(0, 10) != "http-ts://" && inpt.substr(0, 8) != "https://" && inpt.substr(0, 11) != "https-ts://"){
// otherwise, check input param
const std::string &inpt = config->getString("input");
if (inpt.size() && inpt != "-" && inpt.substr(0, 9) != "stream://" && inpt.substr(0, 8) != "tsudp://" &&
inpt.substr(0, 8) != "ts-exec:" && inpt.substr(0, 6) != "srt://" &&
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;
}
}
}
}// namespace Mist

61
src/input/input_ts.h Executable file → Normal file
View file

@ -1,41 +1,40 @@
#include "input.h"
#include <mist/nal.h>
#include <mist/dtsc.h>
#include <mist/nal.h>
#include <mist/ts_packet.h>
#include <mist/ts_stream.h>
#include <string>
#include <set>
#include <string>
namespace Mist {
namespace Mist{
/// This class contains all functions needed to implement TS Input
class inputTS : public Input {
public:
inputTS(Util::Config * cfg);
~inputTS();
bool needsLock();
protected:
//Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void readPMT();
bool openStreamSource();
void parseStreamHeader();
std::string streamMainLoop();
void finish();
FILE * inFile;///<The input file with ts data
TS::Stream tsStream;///<Used for parsing the incoming ts stream
Socket::UDPConnection udpCon;
Socket::Connection tcpCon;
TS::Packet tsBuf;
pid_t inputProcess;
class inputTS : public Input{
public:
inputTS(Util::Config *cfg);
~inputTS();
bool needsLock();
protected:
// Private Functions
bool checkArguments();
bool preRun();
bool readHeader();
bool needHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
void readPMT();
bool openStreamSource();
void parseStreamHeader();
std::string streamMainLoop();
void finish();
FILE *inFile; ///< The input file with ts data
TS::Stream tsStream; ///< Used for parsing the incoming ts stream
Socket::UDPConnection udpCon;
Socket::Connection tcpCon;
TS::Packet tsBuf;
pid_t inputProcess;
};
}
}// namespace Mist
typedef Mist::inputTS mistIn;

View file

@ -1,10 +1,9 @@
#include INPUTTYPE
#include INPUTTYPE
#include <mist/util.h>
int main(int argc, char * argv[]) {
int main(int argc, char *argv[]){
Util::redirectLogsIfNeeded();
Util::Config conf(argv[0]);
mistIn conv(&conf);
return conv.boot(argc, argv);
}

File diff suppressed because it is too large Load diff

140
src/io.h
View file

@ -1,100 +1,96 @@
#pragma once
#include <map>
#include <deque>
#include <mist/shared_memory.h>
#include <map>
#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
#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.
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);
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::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack;///<Holds the data for all pages of a track. Based on unmapped tids
std::string streamName; ///< Name of the stream to connect to
//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.
bool encrypt;
Encryption::verimatrixData vmData;
std::map<int, unsigned long long int> iVecs;
IPC::sharedPage encryptionPage;
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
void continueNegotiate(unsigned long tid, DTSC::Meta &myMeta, bool quickNegotiate = false);
void continueNegotiate(DTSC::Meta &myMeta);
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.
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();
protected:
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
void continueNegotiate();
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();
protected:
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
void continueNegotiate();
bool standAlone;
bool standAlone;
negotiationProxy nProxy;
negotiationProxy nProxy;
DTSC::Packet thisPacket; // The current packet that is being parsed
DTSC::Packet thisPacket;//The current packet that is being parsed
std::string streamName;
std::string streamName;
std::set<unsigned long> selectedTracks; ///< Stores the track id's that are either selected for playback or input
std::set<unsigned long> selectedTracks;///< Stores the track id's that are either selected for playback or input
DTSC::Meta myMeta;///< Stores either the input or output metadata
DTSC::Meta myMeta; ///< Stores either the input or output metadata
};
}
}// namespace Mist

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
#include OUTPUTTYPE
#include <mist/config.h>
#include <mist/socket.h>
#include <mist/defines.h>
#include <mist/socket.h>
#include <mist/util.h>
int spawnForked(Socket::Connection & S){
int spawnForked(Socket::Connection &S){
mistOut tmp(S);
return tmp.run();
}
@ -15,12 +15,12 @@ void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){
Util::Config::is_active = false;
}
int main(int argc, char * argv[]) {
int main(int argc, char *argv[]){
Util::redirectLogsIfNeeded();
Util::Config conf(argv[0]);
mistOut::init(&conf);
if (conf.parseArgs(argc, argv)) {
if (conf.getBool("json")) {
if (conf.parseArgs(argc, argv)){
if (conf.getBool("json")){
mistOut::capa["version"] = PACKAGE_VERSION;
std::cout << mistOut::capa.toString() << std::endl;
return -1;
@ -41,7 +41,7 @@ int main(int argc, char * argv[]) {
FAIL_MSG("Error reloading: %s", strerror(errno));
}
}else{
Socket::Connection S(fileno(stdout),fileno(stdin) );
Socket::Connection S(fileno(stdout), fileno(stdin));
mistOut tmp(S);
return tmp.run();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,23 @@
#pragma once
#include <set>
#include "../io.h"
#include <cstdlib>
#include <map>
#include <mist/config.h>
#include <mist/json.h>
#include <mist/flv_tag.h>
#include <mist/timing.h>
#include <mist/dtsc.h>
#include <mist/socket.h>
#include <mist/flv_tag.h>
#include <mist/json.h>
#include <mist/shared_memory.h>
#include "../io.h"
#include <mist/socket.h>
#include <mist/timing.h>
#include <set>
namespace Mist {
namespace Mist{
/// This struct keeps packet information sorted in playback order, so the
/// Mist::Output class knows when to buffer which packet.
struct sortedPageInfo {
bool operator < (const sortedPageInfo & rhs) const {
if (time < rhs.time) {
return true;
}
struct sortedPageInfo{
bool operator<(const sortedPageInfo &rhs) const{
if (time < rhs.time){return true;}
return (time == rhs.time && tid < rhs.tid);
}
uint64_t tid;
@ -33,122 +31,120 @@ namespace Mist {
/// It contains several virtual functions, that may be overridden to "hook" into
/// the streaming process at those particular points, simplifying child class
/// logic and implementation details.
class Output : public InOutBase {
public:
//constructor and destructor
Output(Socket::Connection & conn);
//static members for initialization and capabilities
static void init(Util::Config * cfg);
static JSON::Value capa;
/*LTS-START*/
std::string reqUrl;
/*LTS-END*/
//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 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);
static bool listenMode(){return true;}
uint32_t currTrackCount() const;
virtual bool isReadyForPlay();
virtual bool reachedPlannedStop();
//virtuals. The optional virtuals have default implementations that do as little as possible.
/// 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 prepareNext();
virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true);
virtual void onRequest();
static void listener(Util::Config & conf, int (*callback)(Socket::Connection & S));
virtual void initialSeek();
virtual bool liveSeek();
virtual bool onFinish() {
return false;
}
void reconnect();
void disconnect();
virtual void initialize();
virtual void sendHeader();
virtual void onFail(const std::string & msg, bool critical = false);
virtual void requestHandler();
static Util::Config * config;
void playbackSleep(uint64_t millis);
private://these *should* not be messed with in child classes.
/*LTS-START*/
void Log(std::string type, std::string message);
bool checkLimits();
bool isBlacklisted(std::string host, std::string streamName, int timeConnected);
std::string hostLookup(std::string ip);
bool onList(std::string ip, std::string list);
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);
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.
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().
protected://these are to be messed with by child classes
virtual bool inlineRestartCapable() const{return false;}///< True if the output is capable of restarting mid-stream. This is used for swapping recording files
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 lastRecv;
uint64_t extraKeepAway;
long long unsigned int 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;}
class Output : public InOutBase{
public:
// constructor and destructor
Output(Socket::Connection &conn);
// static members for initialization and capabilities
static void init(Util::Config *cfg);
static JSON::Value capa;
/*LTS-START*/
std::string reqUrl;
/*LTS-END*/
// 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 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);
static bool listenMode(){return true;}
uint32_t currTrackCount() const;
virtual bool isReadyForPlay();
virtual bool reachedPlannedStop();
// virtuals. The optional virtuals have default implementations that do as little as possible.
/// 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 prepareNext();
virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true);
virtual void onRequest();
static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S));
virtual void initialSeek();
virtual bool liveSeek();
virtual bool onFinish(){return false;}
void reconnect();
void disconnect();
virtual void initialize();
virtual void sendHeader();
virtual void onFail(const std::string &msg, bool critical = false);
virtual void requestHandler();
static Util::Config *config;
void playbackSleep(uint64_t millis);
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();
//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
private: // these *should* not be messed with in child classes.
/*LTS-START*/
void Log(std::string type, std::string message);
bool checkLimits();
bool isBlacklisted(std::string host, std::string streamName, int timeConnected);
std::string hostLookup(std::string ip);
bool onList(std::string ip, std::string list);
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);
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.
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().
protected: // these are to be messed with by child classes
virtual bool inlineRestartCapable() const{
return false;
}///< True if the output is capable of restarting mid-stream. This is used for swapping recording files
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 lastRecv;
uint64_t extraKeepAway;
long long unsigned int 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;}
//Read/write status variables
Socket::Connection & myConn;///< Connection to the client.
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();
bool wantRequest;///< If true, waits for a request.
bool parseData;///< If true, triggers initalization if not already done, sending of header, sending of packets.
bool isInitialized;///< If false, triggers initialization if parseData is true.
bool sentHeader;///< If false, triggers sendHeader if parseData is true.
// 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
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;
}
// Read/write status variables
Socket::Connection &myConn; ///< Connection to the client.
bool wantRequest; ///< If true, waits for a request.
bool parseData; ///< If true, triggers initalization if not already done, sending of header, sending of packets.
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;}
};
}
}// namespace Mist

View file

@ -1,20 +1,20 @@
#include "output_dash_mp4.h"
#include <iomanip>
#include <mist/checksum.h>
#include <mist/defines.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/mp4_dash.h>
#include <mist/checksum.h>
#include <mist/timing.h>
#include <mist/mp4_generic.h>
#include <mist/stream.h>
#include <iomanip>
#include <mist/timing.h>
namespace Mist{
OutDashMP4::OutDashMP4(Socket::Connection & conn) : HTTPOutput(conn){
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";
@ -23,10 +23,10 @@ namespace Mist{
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];
DTSC::Track &Trk = myMeta.tracks[tid];
MP4::MOOV moovBox;
MP4::MVHD mvhdBox(0);
@ -41,8 +41,7 @@ namespace Mist{
iodsBox.setODAudioLevel(0xFE);
}
moovBox.setContent(iodsBox, 1);
MP4::MVEX mvexBox;
MP4::MEHD mehdBox;
mehdBox.setFragmentDuration(0xFFFFFFFF);
@ -51,7 +50,7 @@ namespace Mist{
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);
@ -62,18 +61,18 @@ namespace Mist{
}
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");
MP4::HDLR hdlrBox(Trk.type, "VideoHandler");
mdiaBox.setContent(hdlrBox, 1);
}else{
MP4::HDLR hdlrBox(Trk.type,"SoundHandler");
MP4::HDLR hdlrBox(Trk.type, "SoundHandler");
mdiaBox.setContent(hdlrBox, 1);
}
@ -82,16 +81,16 @@ namespace Mist{
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);
@ -101,7 +100,7 @@ namespace Mist{
MP4::HEV1 hev1Box;
hev1Box.setWidth(Trk.width);
hev1Box.setHeight(Trk.height);
MP4::HVCC hvccBox;
hvccBox.setPayload(Trk.init);
hev1Box.setCLAP(hvccBox);
@ -116,7 +115,7 @@ namespace Mist{
ase.setSampleSize(Trk.size);
MP4::ESDS esdsBox(Trk.init);
ase.setCodecBox(esdsBox);
stsdBox.setEntry(ase,0);
stsdBox.setEntry(ase, 0);
}
if (Trk.codec == "AC3"){
///\todo Note: this code is copied, note for muxing seperation
@ -128,29 +127,29 @@ namespace Mist{
ase.setSampleSize(Trk.size);
MP4::DAC3 dac3Box(Trk.rate, Trk.channels);
ase.setCodecBox(dac3Box);
stsdBox.setEntry(ase,0);
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);
@ -163,12 +162,12 @@ namespace Mist{
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];
DTSC::Track &Trk = myMeta.tracks[tid];
MP4::MOOF moofBox;
MP4::MFHD mfhdBox;
mfhdBox.setSequenceNumber(fragIndice + Trk.missedFrags);
@ -203,16 +202,15 @@ namespace Mist{
}
}
}
trunBox.setFlags(MP4::trundataOffset | MP4::trunsampleSize | MP4::trunsampleDuration | MP4::trunfirstSampleFlags | MP4::trunsampleOffsets);
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;
}
if (!j){trunEntry.sampleSize += headSize;}
trunEntry.sampleDuration = parts->getDuration();
trunEntry.sampleOffset = parts->getOffset();
trunBox.setSampleInformation(trunEntry, j);
@ -237,20 +235,20 @@ namespace Mist{
moofBox.setContent(trafBox, 1);
H.Chunkify(moofBox.asBox(), moofBox.boxedSize(), myConn);
}
std::string OutDashMP4::buildNalUnit(unsigned int len, const char * data){
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 << (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];
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;
@ -271,14 +269,14 @@ namespace Mist{
mdatstr[0] = (char)((size >> 24) & 0xFF);
mdatstr[1] = (char)((size >> 16) & 0xFF);
mdatstr[2] = (char)((size >> 8) & 0xFF);
mdatstr[3] = (char)((size) & 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
H.Chunkify(init, myConn); // 09E0
init = buildNalUnit(avccBox.getSPSLen(), avccBox.getSPS());
H.Chunkify(init, myConn);
init = buildNalUnit(avccBox.getPPSLen(), avccBox.getPPS());
@ -295,13 +293,13 @@ namespace Mist{
}
}
}
//we pull these values first, because seek() destroys our Trk reference
// 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
// select only the tid track, and seek to the start time
selectedTracks.clear();
selectedTracks.insert(tid);
seek(startTime);
@ -316,23 +314,23 @@ namespace Mist{
H.Clean();
return;
}
char * data;
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){
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
// 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
if (live){continue;}// skip last fragment when live
duration = Trk.lastms - starttime;
}
if (first){
@ -364,29 +362,44 @@ namespace Mist{
lastAudTime = myMeta.tracks[*it].lastms;
audInitTrack = *it;
}
if(myMeta.tracks[*it].codec == "subtitle"){
subInitTrack = *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\" ";
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;
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 << "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\" ";
}
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;
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;
@ -395,55 +408,64 @@ namespace Mist{
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) << "\" ";
// 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) << "\" ";
// 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;
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 << " <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"){
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;
// 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){
if (subInitTrack){
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){
if(myMeta.tracks[*it].codec == "subtitle"){
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 << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang << "\">";
r << " <Representation id=\"" << *it << "\" bandwidth=\"256\">";
r << " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>";
r << " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>";
r << " </Representation></AdaptationSet>" << std::endl;
}
}
@ -454,8 +476,8 @@ namespace Mist{
return r.str();
}
void OutDashMP4::init(Util::Config * cfg){
void OutDashMP4::init(Util::Config *cfg){
HTTPOutput::init(cfg);
capa["name"] = "DASHMP4";
capa["friendly"] = "DASH (fMP4) over HTTP";
@ -473,25 +495,27 @@ namespace Mist{
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "dash/video/mp4";
//MP3 does not work in browsers
// MP3 does not work in browsers
capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]");
//HEVC does not work in browsers
// 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.\"}"));
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"]["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();
}
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){
@ -499,7 +523,7 @@ namespace Mist{
H.SetHeader("Content-Type", "application/dash+xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if(method == "OPTIONS" || method == "HEAD"){
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
@ -510,8 +534,8 @@ namespace Mist{
return;
}
//Not a manifest - either an init segment or data segment
size_t pos = url.find("chunk_") + 6;//find the track ID position
// 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();
@ -523,7 +547,7 @@ namespace Mist{
H.SetHeader("Content-Type", "video/mp4");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
if(method == "OPTIONS" || method == "HEAD"){
if (method == "OPTIONS" || method == "HEAD"){
H.SendResponse("200", "OK", myConn);
H.Clean();
return;
@ -531,7 +555,7 @@ namespace Mist{
H.StartResponse(H, myConn, config->getBool("nonchunked"));
if (url.find("init.m4s") != std::string::npos){
//init segment
// init segment
if (myMeta.tracks[tid].type == "video"){
H.Chunkify("\000\000\000\040ftypisom\000\000\000\000isomavc1mp42dash", 32, myConn);
}else{
@ -543,19 +567,19 @@ namespace Mist{
return;
}
//data segment
// 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()){
if (myMeta.live && !myMeta.tracks[tid].fragments[fragIndice].getDuration()){
size_t ctr = 0;
do {
do{
if (ctr){Util::sleep(250);}
updateMeta();
stats();
}while(!myMeta.tracks[tid].fragments[fragIndice].getDuration() && ++ctr < 120);
}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);
@ -563,7 +587,7 @@ namespace Mist{
return;
}
}
DTSC::Track & Trk = myMeta.tracks[tid];
DTSC::Track &Trk = myMeta.tracks[tid];
H.Chunkify("\000\000\000\030stypmsdh\000\000\000\000msdhmsix", 24, myConn);
MP4::SIDX sidxBox;
sidxBox.setReferenceID(1);
@ -585,6 +609,5 @@ namespace Mist{
sendMoof(tid, fragIndice);
sendMdat(tid, fragIndice);
}
}
}// namespace Mist

View file

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

View file

@ -1,29 +1,28 @@
#include "output_dtsc.h"
#include <cstdlib>
#include <cstring>
#include <mist/auth.h>
#include <mist/bitfields.h>
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/triggers.h>
#include <mist/auth.h>
#include <mist/bitfields.h>
#include <sys/stat.h>
#include <cstring>
#include <cstdlib>
namespace Mist {
OutDTSC::OutDTSC(Socket::Connection & conn) : Output(conn) {
namespace Mist{
OutDTSC::OutDTSC(Socket::Connection &conn) : Output(conn){
setBlocking(true);
JSON::Value prep;
prep["cmd"] = "hi";
prep["version"] = "MistServer " PACKAGE_VERSION;
prep["pack_method"] = 2;
salt = Secure::md5("mehstuff"+JSON::Value((uint64_t)time(0)).asString());
salt = Secure::md5("mehstuff" + JSON::Value((uint64_t)time(0)).asString());
prep["salt"] = salt;
/// \todo Make this securererer.
sendCmd(prep);
lastActive = Util::epoch();
}
OutDTSC::~OutDTSC() {}
OutDTSC::~OutDTSC(){}
void OutDTSC::stats(bool force){
unsigned long long int now = Util::epoch();
@ -40,7 +39,7 @@ namespace Mist {
MEDIUM_MSG("Sending DTCM: %s", data.toString().c_str());
unsigned long sendSize = data.packedSize();
myConn.SendNow("DTCM");
char sSize[4] = {0, 0, 0, 0};
char sSize[4] ={0, 0, 0, 0};
Bit::htobl(sSize, data.packedSize());
myConn.SendNow(sSize, 4);
data.sendTo(myConn);
@ -53,17 +52,18 @@ namespace Mist {
sendCmd(err);
}
void OutDTSC::init(Util::Config * cfg){
void OutDTSC::init(Util::Config *cfg){
Output::init(cfg);
capa["name"] = "DTSC";
capa["friendly"] = "DTSC";
capa["desc"] = "Real time streaming over DTSC (MistServer proprietary protocol, for efficient inter-server streaming)";
capa["desc"] = "Real time streaming over DTSC (MistServer proprietary protocol, for efficient "
"inter-server streaming)";
capa["deps"] = "";
capa["codecs"][0u][0u].append("+*");
cfg->addConnectorOptions(4200, capa);
config = cfg;
}
std::string OutDTSC::getStatsName(){
if (pushing){
return "INPUT";
@ -78,27 +78,31 @@ namespace Mist {
unsigned long long seekPos = 0;
if (myMeta.live){
long unsigned int mainTrack = getMainSelectedTrack();
//cancel if there are no keys in the main track
// cancel if there are no keys in the main track
if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){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){
// 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();
bool good = true;
//check if all tracks have data for this point in time
// 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 (mainTrack == *ti){continue;}// skip self
if (!myMeta.tracks.count(*ti)){
HIGH_MSG("Skipping track %lu, not in tracks", *ti);
continue;
}//ignore missing tracks
}// ignore missing tracks
if (myMeta.tracks[*ti].lastms == myMeta.tracks[*ti].firstms){
HIGH_MSG("Skipping track %lu, last equals first", *ti);
continue;
}//ignore point-tracks
if (myMeta.tracks[*ti].firstms > seekPos){good = false; break;}
}// ignore point-tracks
if (myMeta.tracks[*ti].firstms > seekPos){
good = false;
break;
}
HIGH_MSG("Track %lu is good", *ti);
}
//if yes, seek here
// if yes, seek here
if (good){break;}
}
}
@ -107,8 +111,8 @@ namespace Mist {
}
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 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 (Util::epoch() > lastMeta + 5){
@ -130,18 +134,17 @@ namespace Mist {
void OutDTSC::sendHeader(){
sentHeader = true;
selectedTracks.clear();
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
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);
}
}
myMeta.send(myConn, true, selectedTracks);
if (myMeta.live){
realTime = 0;
}
if (myMeta.live){realTime = 0;}
}
void OutDTSC::onFail(const std::string & msg, bool critical){
void OutDTSC::onFail(const std::string &msg, bool critical){
JSON::Value err;
err["cmd"] = "error";
err["msg"] = msg;
@ -154,29 +157,48 @@ namespace Mist {
if (myConn.Received().copy(4) == "DTCM"){
// Command message
std::string toRec = myConn.Received().copy(8);
unsigned long rSize = Bit::btohl(toRec.c_str()+4);
if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
if (!myConn.Received().available(8 + rSize)){return;}// abort - not enough data yet
myConn.Received().remove(8);
std::string dataPacket = myConn.Received().remove(rSize);
DTSC::Scan dScan((char*)dataPacket.data(), rSize);
DTSC::Scan dScan((char *)dataPacket.data(), rSize);
INFO_MSG("Received DTCM: %s", dScan.asJSON().toString().c_str());
if (dScan.getMember("cmd").asString() == "push"){handlePush(dScan); continue;}
if (dScan.getMember("cmd").asString() == "play"){handlePlay(dScan); continue;}
if (dScan.getMember("cmd").asString() == "ping"){sendOk("Pong!"); continue;}
if (dScan.getMember("cmd").asString() == "ok"){INFO_MSG("Ok: %s", dScan.getMember("msg").asString().c_str()); continue;}
if (dScan.getMember("cmd").asString() == "error"){ERROR_MSG("%s", dScan.getMember("msg").asString().c_str()); continue;}
if (dScan.getMember("cmd").asString() == "reset"){myMeta.reset(); sendOk("Internal state reset"); continue;}
if (dScan.getMember("cmd").asString() == "push"){
handlePush(dScan);
continue;
}
if (dScan.getMember("cmd").asString() == "play"){
handlePlay(dScan);
continue;
}
if (dScan.getMember("cmd").asString() == "ping"){
sendOk("Pong!");
continue;
}
if (dScan.getMember("cmd").asString() == "ok"){
INFO_MSG("Ok: %s", dScan.getMember("msg").asString().c_str());
continue;
}
if (dScan.getMember("cmd").asString() == "error"){
ERROR_MSG("%s", dScan.getMember("msg").asString().c_str());
continue;
}
if (dScan.getMember("cmd").asString() == "reset"){
myMeta.reset();
sendOk("Internal state reset");
continue;
}
WARN_MSG("Unhandled DTCM command: '%s'", dScan.getMember("cmd").asString().c_str());
}else if (myConn.Received().copy(4) == "DTSC"){
//Header packet
// Header packet
if (!isPushing()){
onFail("DTSC_HEAD ignored: you are not cleared for pushing data!", true);
return;
}
std::string toRec = myConn.Received().copy(8);
unsigned long rSize = Bit::btohl(toRec.c_str()+4);
if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
std::string dataPacket = myConn.Received().remove(8+rSize);
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
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);
std::stringstream rep;
@ -189,9 +211,9 @@ namespace Mist {
}
// Data packet
std::string toRec = myConn.Received().copy(8);
unsigned long rSize = Bit::btohl(toRec.c_str()+4);
if (!myConn.Received().available(8+rSize)){return;}//abort - not enough data yet
std::string dataPacket = myConn.Received().remove(8+rSize);
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
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())){
onFail("DTSC_V2 received for a track that was not announced in the DTSC_HEAD!", true);
@ -199,14 +221,14 @@ namespace Mist {
}
bufferLivePacket(inPack);
}else{
//Invalid
// Invalid
onFail("Invalid packet header received. Aborting.", true);
return;
}
}
}
void OutDTSC::handlePlay(DTSC::Scan & dScan){
void OutDTSC::handlePlay(DTSC::Scan &dScan){
streamName = dScan.getMember("stream").asString();
Util::sanitizeName(streamName);
Util::Config::streamName = streamName;
@ -215,7 +237,7 @@ namespace Mist {
setBlocking(false);
}
void OutDTSC::handlePush(DTSC::Scan & dScan){
void OutDTSC::handlePush(DTSC::Scan &dScan){
streamName = dScan.getMember("stream").asString();
std::string passString = dScan.getMember("password").asString();
Util::sanitizeName(streamName);
@ -227,6 +249,4 @@ namespace Mist {
sendOk("You're cleared for pushing! DTSC_HEAD please?");
}
}
}// namespace Mist

View file

@ -1,29 +1,29 @@
#include "output.h"
namespace Mist {
namespace Mist{
class OutDTSC : public Output {
public:
OutDTSC(Socket::Connection & conn);
~OutDTSC();
static void init(Util::Config * cfg);
void onRequest();
void sendNext();
void sendHeader();
void initialSeek();
void onFail(const std::string & msg, bool critical = false);
void stats(bool force = false);
void sendCmd(const JSON::Value &data);
void sendOk(const std::string &msg);
private:
unsigned int lastActive;///<Time of last sending of data.
std::string getStatsName();
std::string salt;
void handlePush(DTSC::Scan & dScan);
void handlePlay(DTSC::Scan & dScan);
unsigned long long fastAsPossibleTime;
class OutDTSC : public Output{
public:
OutDTSC(Socket::Connection &conn);
~OutDTSC();
static void init(Util::Config *cfg);
void onRequest();
void sendNext();
void sendHeader();
void initialSeek();
void onFail(const std::string &msg, bool critical = false);
void stats(bool force = false);
void sendCmd(const JSON::Value &data);
void sendOk(const std::string &msg);
private:
unsigned int lastActive; ///< Time of last sending of data.
std::string getStatsName();
std::string salt;
void handlePush(DTSC::Scan &dScan);
void handlePlay(DTSC::Scan &dScan);
unsigned long long fastAsPossibleTime;
};
}
}// namespace Mist
typedef Mist::OutDTSC mistOut;

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