diff --git a/CMakeLists.txt b/CMakeLists.txt index d2643c1b..fd466c2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,11 +266,74 @@ macro(makeAnalyser analyserName format) ) endmacro() -makeAnalyser(RTMP rtmp) -makeAnalyser(FLV flv) -makeAnalyser(DTSC dtsc) -makeAnalyser(AMF amf) -makeAnalyser(MP4 mp4) +add_executable(MistAnalyserMP4 src/analysers/analyser_mp4.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserMP4 mist) + install( + TARGETS MistAnalyserMP4 + DESTINATION bin + ) + + add_executable(MistAnalyserFLV src/analysers/analyser_flv.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserFLV mist) + install( + TARGETS MistAnalyserFLV + DESTINATION bin + ) + +add_executable(MistAnalyserRTMP src/analysers/analyser_rtmp.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserRTMP mist) + install( + TARGETS MistAnalyserRTMP + DESTINATION bin + ) + +#niet nodig? +add_executable(MistAnalyserRTP src/analysers/analyser_rtp.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserRTP mist) + install( + TARGETS MistAnalyserRTP + DESTINATION bin + ) + +add_executable(MistAnalyserTS src/analysers/analyser_ts.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserTS mist) + install( + TARGETS MistAnalyserTS + DESTINATION bin + ) + +add_executable(MistAnalyserDASH src/analysers/analyser_dash.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserDASH mist) + install( + TARGETS MistAnalyserDASH + DESTINATION bin + ) + +add_executable(MistAnalyserHLS src/analysers/analyser_hls.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserHLS mist) + install( + TARGETS MistAnalyserHLS + DESTINATION bin + ) + +add_executable(MistAnalyserDTSC src/analysers/analyser_dtsc.cpp src/analysers/analyser.cpp) +target_link_libraries(MistAnalyserDTSC mist) + install( + TARGETS MistAnalyserDTSC + DESTINATION bin + ) + +makeAnalyser(AMF amf) #hoeft niet +makeAnalyser(OGG ogg) +#makeAnalyser(RTP rtp) #LTS +makeAnalyser(RTSP rtsp_rtp) #LTS ... +#makeAnalyser(DTSC dtsc) + +#makeAnalyser(TS ts) #LTS +#makeAnalyser(HLS hls) #LTS #url +#makeAnalyser(RTMP rtmp) +#makeAnalyser(FLV flv) +#makeAnalyser(MP4 mp4) makeAnalyser(OGG ogg) makeAnalyser(RAX rax) #makeAnalyser(RTP rtp) #LTS diff --git a/src/analysers/amf_analyser.cpp b/src/analysers/amf_analyser.cpp index ba581507..f9b70792 100644 --- a/src/analysers/amf_analyser.cpp +++ b/src/analysers/amf_analyser.cpp @@ -8,6 +8,7 @@ #include #include #include +#include ///\brief Holds everything unique to the analysers. namespace Analysers { @@ -15,7 +16,22 @@ namespace Analysers { /// /// Expects AMF data through stdin, outputs human-readable information to stderr. ///\return The return code of the analyser. - int analyseAMF(){ + int analyseAMF(Util::Config conf){ + + std::string filename = conf.getString("filename"); + + if(filename.length() > 0){ + int fp = open(filename.c_str(), O_RDONLY); + + if(fp <= 0){ + FAIL_MSG("Cannot open file: %s",filename.c_str()); + return false; + } + + dup2(fp, STDIN_FILENO); + close(fp); + } + std::string amfBuffer; //Read all of std::cin to amfBuffer while (std::cin.good()){ @@ -33,8 +49,9 @@ namespace Analysers { int main(int argc, char ** argv){ Util::Config conf = Util::Config(argv[0]); + conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the AMF file to analyse.\"}")); conf.parseArgs(argc, argv); - return Analysers::analyseAMF(); + return Analysers::analyseAMF(conf); } diff --git a/src/analysers/analyser.cpp b/src/analysers/analyser.cpp new file mode 100644 index 00000000..776841c8 --- /dev/null +++ b/src/analysers/analyser.cpp @@ -0,0 +1,104 @@ +#include "analyser.h" +#include +#include +#include + +analysers::analysers(Util::Config &config) { + conf = config; + + //set default detailLevel + detail = 2; + + if (conf.hasOption("filename")) { + fileinput = conf.getString("filename").length() > 0; + } else { + fileinput = 0; + } + + analyse = conf.getString("mode") == "analyse"; + validate = conf.getString("mode") == "validate"; + + if (validate) { + // conf.getOption("detail", true)[1] = 0ll; + detail = 0; + } + + if (conf.hasOption("detail")) { detail = conf.getInteger("detail"); } + + Prepare(); +} + +int analysers::doAnalyse() { + return 0; +} + +analysers::~analysers() { +} + +void analysers::doValidate() { + std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << endTime << std::endl; +} + +bool analysers::packetReady() { + return false; +} + +// Fileinput as stdin +void analysers::Prepare() { + + if (fileinput) { + filename = conf.getString("filename"); + int fp = open(filename.c_str(), O_RDONLY); + + if (fp <= 0) { + FAIL_MSG("Cannot open file: %s", filename.c_str()); + } + + dup2(fp, STDIN_FILENO); + close(fp); + } +} + +bool analysers::hasInput() { + // std::cout << std::cin.good() << std::endl; + + return std::cin.good(); + // return !feof(stdin); +} + +int analysers::Run() { + + if(mayExecute) + { + std::cout << "start analyser with detailLevel: " << detail << std::endl; + endTime = 0; + upTime = Util::bootSecs(); + + while (hasInput() && mayExecute) { + while (packetReady()) { + // std::cout << "in loop..." << std::endl; + endTime = doAnalyse(); + } + } + + finTime = Util::bootSecs(); + + if (validate) { + // std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl; + doValidate(); + } + } + return 0; +} + +void analysers::defaultConfig(Util::Config &conf) { + conf.addOption("filename", + JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the file to analysed.\"}")); + + 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( + "detail", + JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}")); +} diff --git a/src/analysers/analyser.h b/src/analysers/analyser.h new file mode 100644 index 00000000..b3816cf0 --- /dev/null +++ b/src/analysers/analyser.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +class analysers +{ + protected: + bool fileinput; + std::string filename; + bool analyse; + bool validate; + bool mayExecute = true; + int detail; + + long long int endTime; + long long int upTime; + long long int finTime; + + bool useBuffer; + + public: + Util::Config conf; + analysers(Util::Config &config); + ~analysers(); + + void Prepare(); + int Run(); + virtual int doAnalyse(); + virtual void doValidate(); + virtual bool hasInput(); + + static void defaultConfig(Util::Config & conf); + //int Analyse(); + + virtual bool packetReady(); +}; diff --git a/src/analysers/analyser_dash.cpp b/src/analysers/analyser_dash.cpp new file mode 100644 index 00000000..a70ef069 --- /dev/null +++ b/src/analysers/analyser_dash.cpp @@ -0,0 +1,645 @@ + +#include "analyser_dash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OTHER 0x00 +#define VIDEO 0x01 +#define AUDIO 0x02 + +// http://cattop:8080/dash/bunny/index.mpd +// http://balderlaptop:8080/dash/f+short.mp4/index.mpd +// +///\brief simple struct for storage of stream-specific data + +StreamData tempSD; // temp global + +bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim) { + size_t offset = data.find(name); + if (offset == std::string::npos) { + return false; // name string not found. + } + // expected: delim character BEFORE blockstart. + offset--; + + blockStart = data.find(delim, offset); + // DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart); + offset = blockStart + 1; // skip single character! + blockEnd = data.find(delim, offset); + + // DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd); + if (blockStart == std::string::npos || blockEnd == std::string::npos) { + return false; // no start/end quotes found + } + + blockEnd++; // include delim + // DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) ); + return true; +} + +bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim) { + size_t offset = data.find(name); + if (offset == std::string::npos) { + return false; // name string not found. + } + blockStart = data.find(delim, offset); + // DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart); + blockStart++; // clip off quote characters + offset = blockStart; // skip single character! + blockEnd = data.find(delim, offset); + // DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd); + if (blockStart == std::string::npos || blockEnd == std::string::npos) { + return false; // no start/end quotes found + } + // DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) ); + return true; +} + +bool getString(std::string &data, std::string name, std::string &output) { + size_t blockStart = 0; + size_t blockEnd = 0; + + if (!getValueBlock(data, name, blockStart, blockEnd, "\"")) { + // DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str()); + return false; // could not find value in this data block. + } + // DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) ) + output = data.substr(blockStart, (blockEnd - blockStart)); + // looks like this function is working as expected + // DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str()); + return true; +} + +bool getLong(std::string &data, std::string name, long &output) { + size_t blockStart, blockEnd; + if (!getValueBlock(data, name, blockStart, blockEnd, "\"")) { + // DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str()); + return false; // could not find value in this data block. + } + // DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str()); + output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str()); + return true; +} + +// block expecting separate name and /name occurence, or name and /> before another occurence of <. +bool getBlock(std::string &data, std::string name, int offset, size_t &blockStart, size_t &blockEnd) { + blockStart = data.find("<" + name + ">", offset); + if (blockStart == std::string::npos) { + blockStart = data.find("<" + name + " ", offset); // this considers both valid situations and + } + + 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 + DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str()); + return false; + } + // DEBUG_MSG(DLVL_FAIL, "special block end found"); + blockEnd += 2; // position after /> + } else { + blockEnd += name.size() + 2; // position after /name> + } + + // DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd); + return true; +} + +bool parseAdaptationSet(std::string &data, std::set ¤tPos) { + // DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str()); + size_t offset = 0; + size_t blockStart, blockEnd; + tempSD.trackType = OTHER; + // get value: mimetype //todo: handle this! + std::string mimeType; + if (!getString(data, "mimeType", mimeType)) { // get first occurence of mimeType. --> this will break if multiple mimetypes should be read from + // this block because no offset is provided. solution: use this on a substring containing the + // desired information. + DEBUG_MSG(DLVL_FAIL, "mimeType not found"); + return false; + } + + DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK + + if (mimeType.find("video") != std::string::npos) { tempSD.trackType = VIDEO; } + if (mimeType.find("audio") != std::string::npos) { tempSD.trackType = AUDIO; } + if (tempSD.trackType == OTHER) { + DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up."); + return false; + } + + // find an ID within this adaptationSet block. + if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)) { + DEBUG_MSG(DLVL_FAIL, "Representation not found"); + return false; + } + + // representation string + + std::string block = data.substr(blockStart, (blockEnd - blockStart)); + DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str()); + // check if block is not junk? + + if (!getLong(block, "id", tempSD.trackID)) { + DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str()); + return false; + } + DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK + + offset = 0; + // get values from SegmentTemplate + if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)) { + DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found"); + return false; + } + block = data.substr(blockStart, (blockEnd - blockStart)); + // DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK + + getLong(block, "timescale", tempSD.timeScale); + getString(block, "media", tempSD.media); + getString(block, "initialization", tempSD.initialization); + + size_t tmpBlockStart = 0; + size_t tmpBlockEnd = 0; + if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")) { + DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str()); + return false; + } + tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); + + if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")) { + DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str()); + return false; + } + tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); + + if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")) { + DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.initialization.c_str()); + return false; + } + tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d"); + + // get segment timeline block from within segment template: + size_t blockOffset = 0; // offset should be 0 because this is a new block + if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)) { + DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found"); + return false; + } + + std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part + // DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK + + int numS = 0; + offset = 0; + long long unsigned int totalDuration = 0; + long timeValue; + while (1) { + if (!getBlock(block2, "S", offset, blockStart, blockEnd)) { + if (numS == 0) { + DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline"); + return false; + } else { + DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS); + return true; // break; //escape from while loop (to return true) + } + } + numS++; + // stuff S data into: currentPos + // searching for t(start position) + std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart)); + // DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK! + if (getLong(sBlock, "t", timeValue)) { + totalDuration = timeValue; // reset totalDuration to value of t + } + if (!getLong(sBlock, "d", timeValue)) { // expected duration in every S. + DEBUG_MSG(DLVL_FAIL, "no d found within S"); + return false; + } + // stuff data with old value (start of block) + // DEBUG_MSG(DLVL_INFO, "stuffing info from S into set"); + seekPos thisPos; + thisPos.trackType = tempSD.trackType; + thisPos.trackID = tempSD.trackID; + thisPos.adaptationSet = tempSD.adaptationSet; + // thisPos.trackID=id; + thisPos.seekTime = totalDuration; // previous total duration is start time of this S. + thisPos.duration = timeValue; + thisPos.timeScale = tempSD.timeScale; + + static char charBuf[512]; + snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration); + thisPos.url.assign(charBuf); + // DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str()); + + currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct. + totalDuration += timeValue; // update totalDuration + offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant. + } + return true; +} + +bool parseXML(std::string &body, std::set ¤tPos, std::vector &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 + std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart)); + // function was verified: output as expected. + + if (!parseAdaptationSet(adaptationSet, currentPos)) { + DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype. in that case it might be + // desirable to continue searching for valid data instead of quitting. + return false; + } + streamData.push_back(tempSD); // put temp values into adaptation set vector + currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset. + } + if (numAdaptationSet == 0) { + DEBUG_MSG(DLVL_FAIL, "no adaptationSet found."); + return false; + } + DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet); + return true; +} + +dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) { + port = 80; + url = conf.getString("url"); + + 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"<(in)), std::istreambuf_iterator()); + 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; + exit(1); + } + + H.Clean(); + DEBUG_MSG(DLVL_INFO, "*********"); + DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); + DEBUG_MSG(DLVL_INFO, "*********"); + + DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size()); + for (unsigned int i = 0; i < streamData.size(); i++) { + DEBUG_MSG(DLVL_INFO, ""); + DEBUG_MSG(DLVL_INFO, "ID in vector %d", i); + DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID); + DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet); + DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); + DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale); + DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str()); + DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str()); + } + + DEBUG_MSG(DLVL_INFO, ""); + + for (unsigned int i = 0; i < streamData.size(); i++) { // get init url + static char charBuf[512]; + snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID); + streamData[i].initURL.assign(charBuf); + DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet, streamData[i].trackID, + streamData[i].initURL.c_str()); + } +} + +bool dashAnalyser::hasInput() { + return currentPos.size(); +} + +bool dashAnalyser::packetReady() { + return (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (currentPos.size() > 0); +} + +dashAnalyser::~dashAnalyser() { + INFO_MSG("stopped"); +} + +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; + } + if (!conn) { conn = Socket::Connection(server, port, false); } + HTTP::Parser H; + H.url = urlPrependStuff; + H.url.append(currentPos.begin()->url); + DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime, + currentPos.begin()->seekTime + currentPos.begin()->duration); + H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut? + H.SendRequest(conn); + // TODO: get response? + H.Clean(); + + + while (conn && (!conn.spool() || !H.Read(conn))) {} // ehm... + // std::cout << "leh vomi: "<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()); + + endTime = pos; + return pos; +} + +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. \"}")); + + conf.parseArgs(argc, argv); + conf.activate(); + + dashAnalyser A(conf); + A.Run(); + + return 0; +} + +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.parseArgs(argc, argv); + conf.activate(); + + unsigned int port = 80; + std::string url = conf.getString("url"); + + if (url.substr(0, 7) != "http://") { + DEBUG_MSG(DLVL_FAIL, "The URL must start with http://"); + return -1; + } + url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh + + std::string server = url.substr(0, url.find('/')); + url = url.substr(url.find('/')); + + if (server.find(':') != std::string::npos) { + port = atoi(server.substr(server.find(':') + 1).c_str()); + server = server.substr(0, server.find(':')); + } + + long long int startTime = Util::bootSecs(); + long long int abortTime = conf.getInteger("abort"); + + Socket::Connection conn(server, port, false); + + // url: + DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port); + std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1); + DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str()); + if (!conn) { conn = Socket::Connection(server, port, false); } + unsigned int pos = 0; + HTTP::Parser H; + H.url = url; + H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); + H.SendRequest(conn); + H.Clean(); + while (conn && (!conn.spool() || !H.Read(conn))) {} + H.BuildResponse(); + + std::set currentPos; + std::vector 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(in)), std::istreambuf_iterator()); + if (!parseXML(H.body, currentPos, streamData)) { + DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str()); + if (conf.getString("mode") == "validate") { + long long int endTime = Util::bootSecs(); + std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; + } + return -1; + } + + H.Clean(); + DEBUG_MSG(DLVL_INFO, "*********"); + DEBUG_MSG(DLVL_INFO, "*SUMMARY*"); + DEBUG_MSG(DLVL_INFO, "*********"); + + DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size()); + for (unsigned int i = 0; i < streamData.size(); i++) { + DEBUG_MSG(DLVL_INFO, ""); + DEBUG_MSG(DLVL_INFO, "ID in vector %d", i); + DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID); + DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet); + DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType); + DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale); + DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str()); + DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str()); + } + + DEBUG_MSG(DLVL_INFO, ""); + + for (unsigned int i = 0; i < streamData.size(); i++) { // get init url + static char charBuf[512]; + snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID); + streamData[i].initURL.assign(charBuf); + DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet, streamData[i].trackID, + streamData[i].initURL.c_str()); + } + + while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) { + // DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str()); + 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; + } + if (!conn) { conn = Socket::Connection(server, port, false); } + HTTP::Parser H; + H.url = urlPrependStuff; + H.url.append(currentPos.begin()->url); + DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime, + currentPos.begin()->seekTime + currentPos.begin()->duration); + H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut? + H.SendRequest(conn); + // TODO: get response? + H.Clean(); + while (conn && (!conn.spool() || !H.Read(conn))) {} // ehm... + // std::cout << "leh vomi: "<seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale; + + if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) { + Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000); + } + + currentPos.erase(currentPos.begin()); + } + + if (conf.getString("mode") == "validate") { + long long int endTime = Util::bootSecs(); + std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; + } + + return 0; +} diff --git a/src/analysers/analyser_dash.h b/src/analysers/analyser_dash.h new file mode 100644 index 00000000..f714d0d1 --- /dev/null +++ b/src/analysers/analyser_dash.h @@ -0,0 +1,78 @@ +#include +#include "analyser.h" +#include + +struct StreamData{ + long timeScale; + std::string media; + std::string initialization; + std::string initURL; + long trackID; + unsigned int adaptationSet; + unsigned char trackType; +}; + + +struct seekPos { + ///\brief Less-than comparison for seekPos structures. + ///\param rhs The seekPos to compare with. + ///\return Whether this object is smaller than rhs. + bool operator < (const seekPos & rhs) const { + if ((seekTime*rhs.timeScale) < (rhs.seekTime*timeScale)) { + return true; + } else { + if ( (seekTime*rhs.timeScale) == (rhs.seekTime*timeScale)){ + if (adaptationSet < rhs.adaptationSet){ + return true; + } else if (adaptationSet == rhs.adaptationSet){ + if (trackID < rhs.trackID) { + return true; + } + } + } + } + return false; + } + + long timeScale; + long long unsigned int bytePos; /// ? + long long unsigned int seekTime; ///start + long long unsigned int duration; ///duration + unsigned int trackID; ///stores representation ID + unsigned int adaptationSet; ///stores type + unsigned char trackType; ///stores type + std::string url; + + +}; + +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 currentPos; + std::vector streamData; + + + + +}; diff --git a/src/analysers/analyser_dtsc.cpp b/src/analysers/analyser_dtsc.cpp new file mode 100644 index 00000000..f074cda6 --- /dev/null +++ b/src/analysers/analyser_dtsc.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include + +#include "analyser_dtsc.h" +#include +#include +#include + +dtscAnalyser::dtscAnalyser(Util::Config config) : analysers(config) { + conn = Socket::Connection(fileno(stdout), fileno(stdin)); + std::cout << "connection initialized" << std::endl; + + F.reInit(conn); + totalBytes = 0; + + // F = DTSC::Packet(config.getString("filename")); + if (!F) { + std::cerr << "Not a valid DTSC file" << std::endl; + mayExecute = false; + return; + } + + if (F.getVersion() == DTSC::DTSC_HEAD) // for meta + { + DTSC::Meta m(F); + + if (detail > 0) { + JSON::Value result; + for (std::map::iterator it = m.tracks.begin(); it != m.tracks.end(); it++) { + JSON::Value track; + if (it->second.type == "video") { + std::stringstream tStream; + track["resolution"] = JSON::Value((long long)it->second.width).asString() + "x" + JSON::Value((long long)it->second.height).asString(); + track["fps"] = (long long)((double)it->second.fpks / 1000); + track["fpks"] = it->second.fpks; + tStream << it->second.bps * 8 << " b/s, " << (double)it->second.bps * 8 / 1024 << " kb/s, " << (double)it->second.bps * 8 / 1024 / 1024 + << " mb/s"; + track["bitrate"] = tStream.str(); + tStream.str(""); + track["keyframe_duration"] = (long long)((float)(it->second.lastms - it->second.firstms) / it->second.keys.size()); + tStream << ((double)(it->second.lastms - it->second.firstms) / it->second.keys.size()) / 1000; + track["keyframe_interval"] = tStream.str(); + + tStream.str(""); + if (it->second.codec == "H264") { + h264::sequenceParameterSet sps; + sps.fromDTSCInit(it->second.init); + h264::SPSMeta spsData = sps.getCharacteristics(); + track["encoding"]["width"] = spsData.width; + track["encoding"]["height"] = spsData.height; + tStream << spsData.fps; + track["encoding"]["fps"] = tStream.str(); + track["encoding"]["profile"] = spsData.profile; + track["encoding"]["level"] = spsData.level; + } + } + if (it->second.type == "audio") { + std::stringstream tStream; + tStream << it->second.bps * 8 << " b/s, " << (double)it->second.bps * 8 / 1024 << " kb/s, " << (double)it->second.bps * 8 / 1024 / 1024 + << " mb/s"; + track["bitrate"] = tStream.str(); + track["keyframe_interval"] = (long long)((float)(it->second.lastms - it->second.firstms) / it->second.keys.size()); + } + result[it->second.getWritableIdentifier()] = track; + } + std::cout << result.toString(); + } + + if (m.vod || m.live) { m.toPrettyString(std::cout, 0, 0x03); } + } +} + +bool dtscAnalyser::packetReady() { + return (F.getDataLen() > 0); +} + +bool dtscAnalyser::hasInput() { + return F; +} + +int dtscAnalyser::doAnalyse() { + if (analyse) { // always analyse..? + switch (F.getVersion()) { + case DTSC::DTSC_V1: { + std::cout << "DTSCv1 packet: " << F.getScan().toPrettyString() << std::endl; + break; + } + case DTSC::DTSC_V2: { + std::cout << "DTSCv2 packet (Track " << F.getTrackId() << ", time " << F.getTime() << "): " << F.getScan().toPrettyString() << std::endl; + break; + } + case DTSC::DTSC_HEAD: { + std::cout << "DTSC header: " << F.getScan().toPrettyString() << std::endl; + break; + } + case DTSC::DTCM: { + std::cout << "DTCM command: " << F.getScan().toPrettyString() << std::endl; + break; + } + default: DEBUG_MSG(DLVL_WARN, "Invalid dtsc packet @ bpos %llu", totalBytes); break; + } + } + + totalBytes += F.getDataLen(); + + F.reInit(conn); + return totalBytes; +} + +int main(int argc, char **argv) { + Util::Config conf = Util::Config(argv[0]); + + analysers::defaultConfig(conf); + conf.parseArgs(argc, argv); + + dtscAnalyser A(conf); + + A.Run(); + + return 0; +} diff --git a/src/analysers/analyser_dtsc.h b/src/analysers/analyser_dtsc.h new file mode 100644 index 00000000..7c1e9339 --- /dev/null +++ b/src/analysers/analyser_dtsc.h @@ -0,0 +1,19 @@ +#include +#include "analyser.h" +#include + +class dtscAnalyser : public analysers +{ + DTSC::Packet F; + Socket::Connection conn; + uint64_t totalBytes; + + public: + dtscAnalyser(Util::Config config); + bool packetReady(); + bool hasInput(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); +// void doValidate(); +}; diff --git a/src/analysers/analyser_flv.cpp b/src/analysers/analyser_flv.cpp new file mode 100644 index 00000000..2eb3c11b --- /dev/null +++ b/src/analysers/analyser_flv.cpp @@ -0,0 +1,73 @@ +#include "analyser_flv.h" +#include +#include +#include + +flvAnalyser::flvAnalyser(Util::Config config) : analysers(config) { + + if(fileinput && open(filename.c_str(), O_RDONLY) <= 0) + { + mayExecute = false; + return; + } + + filter = conf.getInteger("filter"); + FLV::Tag flvData; // Temporary storage for incoming FLV data. + + + //check for flv data + char flvHeader[3]; + std::cin.read(flvHeader,3); + + if(flvHeader[0] != 0x46 || flvHeader[1] != 0x4C || flvHeader[2] != 0x56) + { + FAIL_MSG("No FLV Signature found!"); + mayExecute = false; + return; + } + std::cin.seekg(0); +} + +/* +void flvAnalyser::doValidate() +{ + std::cout << "dfasdfsdafdsf" << std::endl; + std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl; +} +*/ + +bool flvAnalyser::hasInput() { + return !feof(stdin); +} + +bool flvAnalyser::packetReady() { + return flvData.FileLoader(stdin); +} + +int flvAnalyser::doAnalyse() { + // std::cout<< "do analyse" << std::endl; + + if (analyse) { // always analyse..? + if (!filter || filter == flvData.data[0]) { + std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; + } + } + endTime = flvData.tagTime(); + + return endTime; +} + +int main(int argc, char **argv) { + Util::Config conf = Util::Config(argv[0]); + conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info " + "about this tag type (8 = audio, 9 = video, 0 = all)\"}")); + + analysers::defaultConfig(conf); + conf.parseArgs(argc, argv); + + flvAnalyser A(conf); + + A.Run(); + + return 0; +} diff --git a/src/analysers/analyser_flv.h b/src/analysers/analyser_flv.h new file mode 100644 index 00000000..0abe16e6 --- /dev/null +++ b/src/analysers/analyser_flv.h @@ -0,0 +1,18 @@ +#include //FLV support +#include +#include "analyser.h" + +class flvAnalyser : public analysers +{ + FLV::Tag flvData; + long long filter; + + public: + flvAnalyser(Util::Config config); + bool packetReady(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); + bool hasInput(); +// void doValidate(); +}; diff --git a/src/analysers/analyser_hls.cpp b/src/analysers/analyser_hls.cpp new file mode 100644 index 00000000..e9d451a1 --- /dev/null +++ b/src/analysers/analyser_hls.cpp @@ -0,0 +1,221 @@ +#include "analyser_hls.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// http://patchy.ddvtech.com:8080/hls/bbb/index.m3u8 +// + +std::deque getParts(std::string &body, std::string &uri) { + size_t slashPos = uri.rfind('/'); + std::string uri_prefix = uri.substr(0, slashPos + 1); + std::deque out; + std::stringstream data(body); + std::string line; + unsigned int start = 0; + unsigned int durat = 0; + do { + line = ""; + std::getline(data, line); + if (line.size() && *line.rbegin() == '\r') { line.resize(line.size() - 1); } + if (line != "") { + if (line[0] != '#') { + out.push_back(HLSPart(uri_prefix + line, start, durat)); + start += durat; + } else { + if (line.substr(0, 8) == "#EXTINF:") { durat = atof(line.substr(8).c_str()) * 1000; } + } + } + } while (line != ""); + return out; +} + +hlsAnalyser::hlsAnalyser(Util::Config config) : analysers(config) { + port = 80; + url = conf.getString("url"); + if (url.substr(0, 7) != "http://") { + DEBUG_MSG(DLVL_FAIL, "The URL must start with http://"); + mayExecute = false; + return; + } + url = url.substr(7); + + //check if url ends with .m3u8? + if((url.find('/') == std::string::npos) || (url.find(".m3u") == std::string::npos && url.find(".m3u8") == std::string::npos)) + { + std::cout << "incorrect url"< 4 && (url.find(".m3u") != std::string::npos || url.find(".m3u8") != std::string::npos)) { + playlist = url; + DEBUG_MSG(DLVL_DEVEL, "Retrieving playlist: %s", url.c_str()); + if (!conn) { conn = Socket::Connection(server, port, false); } + HTTP::Parser H; + H.url = url; + H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); + H.SendRequest(conn); + H.Clean(); + while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {} + parts = getParts(H.body, url); + if (!parts.size()) { + DEBUG_MSG(DLVL_FAIL, "Playlist parsing error - cancelling. state: %s/%s body size: %u", conn ? "Conn" : "Disconn", + (Util::bootSecs() < (startTime + abortTime)) ? "NoTimeout" : "TimedOut", H.body.size()); + if (conf.getString("mode") == "validate") { + long long int endTime = Util::bootSecs(); + std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; + } + return -1; + } + H.Clean(); + url = parts.begin()->uri; + } + + if (lastDown != "") { + while (parts.size() && parts.begin()->uri != lastDown) { parts.pop_front(); } + if (parts.size() < 2) { + repeat = true; + Util::sleep(1000); + // continue; + return 0; + } + parts.pop_front(); + } + + unsigned int lastRepeat = 0; + unsigned int numRepeat = 0; + while (parts.size() > 0 && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) { + HLSPart part = *parts.begin(); + parts.pop_front(); + DEBUG_MSG(DLVL_DEVEL, "Retrieving segment: %s (%u-%u)", part.uri.c_str(), part.start, part.start + part.dur); + if (!conn) { conn = Socket::Connection(server, port, false); } + HTTP::Parser H; + H.url = part.uri; + H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); + H.SendRequest(conn); + H.Clean(); + while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {} + + if (H.GetHeader("Content-Length") != "") { + if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())) { + DEBUG_MSG(DLVL_FAIL, "Expected %s bytes of data, but only received %lu.", H.GetHeader("Content-Length").c_str(), H.body.size()); + if (lastRepeat != part.start || numRepeat < 500) { + DEBUG_MSG(DLVL_FAIL, "Retrying"); + if (lastRepeat != part.start) { + numRepeat = 0; + lastRepeat = part.start; + } else { + numRepeat++; + } + parts.push_front(part); + Util::wait(1000); + continue; + } else { + DEBUG_MSG(DLVL_FAIL, "Aborting further downloading"); + repeat = false; + break; + } + } + } + if (H.body.size() % 188) { + DEBUG_MSG(DLVL_FAIL, "Expected a multiple of 188 bytes, received %d bytes", H.body.size()); + if (lastRepeat != part.start || numRepeat < 500) { + DEBUG_MSG(DLVL_FAIL, "Retrying"); + if (lastRepeat != part.start) { + numRepeat = 0; + lastRepeat = part.start; + } else { + numRepeat++; + } + parts.push_front(part); + Util::wait(1000); + continue; + } else { + DEBUG_MSG(DLVL_FAIL, "Aborting further downloading"); + repeat = false; + break; + } + } + pos = part.start + part.dur; + if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) { + Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000); + } + lastDown = part.uri; + if (output) { std::cout << H.body; } + H.Clean(); + } + + return pos; +} + +bool hlsAnalyser::hasInput() { + return repeat; +} + +bool hlsAnalyser::packetReady() { + return repeat; +} + +hlsAnalyser::~hlsAnalyser() { + // INFO_MSG("call destructor"); + // DEBUG_MSG(DLVL_INFO, "mode: %s", conf.getString("mode").c_str()); + // + /* call doValidate() from superclass + + if (conf.getString("mode") == "validate") { + long long int endTime = Util::bootSecs(); + std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; + } + */ +} + +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. \"}")); + + conf.parseArgs(argc, argv); + conf.activate(); + + hlsAnalyser A(conf); + A.Run(); +} diff --git a/src/analysers/analyser_hls.h b/src/analysers/analyser_hls.h new file mode 100644 index 00000000..a4964986 --- /dev/null +++ b/src/analysers/analyser_hls.h @@ -0,0 +1,55 @@ +//http://cattop:8080/hls/bunny/index.m3u8 +// +#include +#include +#include +#include +#include +#include "analyser.h" + +class HLSPart { + public: + HLSPart(std::string u, unsigned int s, unsigned int d) { + uri = u; + start = s; + dur = d; + } + std::string uri; + unsigned int start; + unsigned int dur; +}; + + +class hlsAnalyser : public analysers +{ + + public: + hlsAnalyser(Util::Config config); + ~hlsAnalyser(); + 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; + + std::deque parts; + Socket::Connection conn; + + std::string playlist; + bool repeat; + std::string lastDown; + unsigned int pos; + bool output; + +}; diff --git a/src/analysers/analyser_mp4.cpp b/src/analysers/analyser_mp4.cpp new file mode 100644 index 00000000..8badb448 --- /dev/null +++ b/src/analysers/analyser_mp4.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "analyser_mp4.h" + +mp4Analyser::mp4Analyser(Util::Config config) : analysers(config) { + + curPos = 0; + dataSize = 0; +} + +int mp4Analyser::doAnalyse() { + DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); + std::cerr << mp4Data.toPrettyString(0) << std::endl; + + return dataSize; // endtime? +} + +bool mp4Analyser::hasInput() { + if (!std::cin.good()) { return false; } + mp4Buffer += std::cin.get(); + dataSize++; + + if (!std::cin.good()) { + mp4Buffer.erase(mp4Buffer.size() - 1, 1); + dataSize--; + } + + return true; +} + +bool mp4Analyser::packetReady() { + return mp4Data.read(mp4Buffer); +} + +mp4Analyser::~mp4Analyser() { + INFO_MSG("Stopped parsing at position %d", curPos); +} + +int main(int argc, char **argv) { + Util::Config conf = Util::Config(argv[0]); + conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info " + "about this tag type (8 = audio, 9 = video, 0 = all)\"}")); + 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("filename", + JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}")); + + conf.parseArgs(argc, argv); + + mp4Analyser A(conf); + // FlvAnalyser A(conf); + + A.Run(); + + return 0; +} diff --git a/src/analysers/analyser_mp4.h b/src/analysers/analyser_mp4.h new file mode 100644 index 00000000..0495430f --- /dev/null +++ b/src/analysers/analyser_mp4.h @@ -0,0 +1,23 @@ +#include +#include +#include "analyser.h" + + +class mp4Analyser : public analysers +{ + std::string mp4Buffer; + MP4::Box mp4Data; + int dataSize; + int curPos; + + public: + mp4Analyser(Util::Config config); + ~mp4Analyser(); + bool packetReady(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); +// void doValidate(); + bool hasInput(); + void PostProcessing(); +}; diff --git a/src/analysers/analyser_ogg.cpp b/src/analysers/analyser_ogg.cpp new file mode 100644 index 00000000..91f5fb37 --- /dev/null +++ b/src/analysers/analyser_ogg.cpp @@ -0,0 +1,49 @@ +#include +#include "analyser_ogg.h" + +oggAnalyser::oggAnalyser(Util::Config config) : analysers(config) +{ + std::cout << "ogg constr" << std::endl; + + filter = conf.getInteger("filter"); + FLV::Tag flvData; // Temporary storage for incoming FLV data. + endTime = 0; + +} + +void oggAnalyser::doValidate() +{ + std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl; +} + +bool oggAnalyser::packetReady() +{ + return flvData.FileLoader(stdin); +} + +int oggAnalyser::doAnalyse() +{ + if (analyse){ //always analyse..? + if (!filter || filter == flvData.data[0]){ + std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; + } + } + endTime = flvData.tagTime(); + + return endTime; +} + +int main(int argc, char ** argv){ + Util::Config conf = Util::Config(argv[0]); + conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info about this tag type (8 = audio, 9 = video, 0 = all)\"}")); + 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("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}")); + conf.parseArgs(argc, argv); + + oggAnalyser A(conf); + + A.Run(); + + return 0; +} + diff --git a/src/analysers/analyser_rtmp.cpp b/src/analysers/analyser_rtmp.cpp new file mode 100644 index 00000000..5410d254 --- /dev/null +++ b/src/analysers/analyser_rtmp.cpp @@ -0,0 +1,213 @@ +#include +#include "analyser_rtmp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DETAIL_RECONSTRUCT 1 +#define DETAIL_EXPLICIT 2 +#define DETAIL_VERBOSE 4 + +rtmpAnalyser::rtmpAnalyser(Util::Config config) : analysers(config) +{ + std::cout << "rtmp constr" << std::endl; + Detail = conf.getInteger("detail"); + //Detail = 4; + + inbuffer.reserve(3073); + + while(std::cin.good() && inbuffer.size() < 3073){ + inbuffer += std::cin.get(); + + } + inbuffer.erase(0,3073); //strip the handshake part + + AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); + AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); + + RTMPStream::rec_cnt += 3073; + + read_in = 0; + endTime = 0; +} + +bool rtmpAnalyser::packetReady() +{ + return (std::cin.good() || strbuf.size()); +} + +int rtmpAnalyser::doAnalyse() +{ +// std::cout << "do analyse" << std::endl; + + analyse=true; + if (analyse){ //always analyse..? +// std::cout << "status strbuf: " << next.Parse(strbuf) << " strbuf_size: " << strbuf.size() << std::endl; + + if (next.Parse(strbuf)){ + if (Detail & DETAIL_VERBOSE){ + fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", 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 + fprintf(stderr, "Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i\n", read_in-inbuffer.size(), next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id); + return 0; + break; //happens when connection breaks unexpectedly + case 1: //set chunk size + RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str()); + fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max); + break; + case 2: //abort message - we ignore this one + fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str())); + //4 bytes of stream id to drop + break; + case 3: //ack + RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str()); + fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at); + break; + case 4: { + short int ucmtype = ntohs(*(short int*)next.data.c_str()); + switch (ucmtype){ + case 0: + fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 1: + fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 2: + fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 3: + fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 4: + fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 6: + fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 7: + fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + case 31: + case 32: + //don't know, but not interesting anyway + break; + default: + fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2))); + break; + } + } + break; + case 5: //window size of other end + RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str()); + RTMPStream::rec_window_at = RTMPStream::rec_cnt; + fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size); + break; + case 6: + RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str()); + //4 bytes window size, 1 byte limit type (ignored) + fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size); + break; + case 8: + case 9: + if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){ + F.ChunkLoader(next); + if (Detail & DETAIL_EXPLICIT){ + std::cerr << "[" << F.tagTime() << "+" << F.offset() << "] " << F.tagType() << std::endl; + } + if (Detail & DETAIL_RECONSTRUCT){ + std::cout.write(F.data, F.len); + } + } + break; + case 15: + fprintf(stderr, "Received AFM3 data message\n"); + break; + case 16: + fprintf(stderr, "Received AFM3 shared object\n"); + break; + case 17: { + fprintf(stderr, "Received AFM3 command message:\n"); + char soort = next.data[0]; + next.data = next.data.substr(1); + if (soort == 0){ + amfdata = AMF::parse(next.data); + std::cerr << amfdata.Print() << std::endl; + }else{ + amf3data = AMF::parse3(next.data); + amf3data.Print(); + } + } + break; + case 18: { + fprintf(stderr, "Received AFM0 data message (metadata):\n"); + amfdata = AMF::parse(next.data); + amfdata.Print(); + if (Detail & DETAIL_RECONSTRUCT){ + F.ChunkLoader(next); + std::cout.write(F.data, F.len); + } + } + break; + case 19: + fprintf(stderr, "Received AFM0 shared object\n"); + break; + case 20: { //AMF0 command message + fprintf(stderr, "Received AFM0 command message:\n"); + amfdata = AMF::parse(next.data); + std::cerr << amfdata.Print() << std::endl; + } + break; + case 22: + if (Detail & DETAIL_RECONSTRUCT){ + std::cout << next.data; + } + break; + default: + fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n"); + return 1; + break; + } //switch for type of chunk + }else{ //if chunk parsed + if (std::cin.good()){ + unsigned int charCount = 0; + std::string tmpbuffer; + tmpbuffer.reserve(1024); + while (std::cin.good() && charCount < 1024){ + char newchar = std::cin.get(); + if (std::cin.good()){ + tmpbuffer += newchar; + ++read_in; + ++charCount; + } + } + strbuf.append(tmpbuffer); + }else{ + strbuf.get().clear(); + } + } + } + + return endTime; +} + +int main(int argc, char ** argv){ + Util::Config conf = Util::Config(argv[0]); + + analysers::defaultConfig(conf); + conf.parseArgs(argc, argv); + rtmpAnalyser A(conf); + + A.Run(); + + return 0; +} diff --git a/src/analysers/analyser_rtmp.h b/src/analysers/analyser_rtmp.h new file mode 100644 index 00000000..c1845ee3 --- /dev/null +++ b/src/analysers/analyser_rtmp.h @@ -0,0 +1,35 @@ +#include //FLV support +#include +#include "analyser.h" + +#include +#include +#include +#include +#include +#include +#include + +class rtmpAnalyser : public analysers +{ + FLV::Tag flvData; + long long filter; + + std::string inbuffer; + RTMPStream::Chunk next; + FLV::Tag F; //FLV holder + unsigned int read_in ; + Socket::Buffer strbuf; + AMF::Object amfdata; + AMF::Object3 amf3data; + + int Detail; + + public: + rtmpAnalyser(Util::Config config); + bool packetReady(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); +// void doValidate(); +}; diff --git a/src/analysers/analyser_rtp.cpp b/src/analysers/analyser_rtp.cpp new file mode 100644 index 00000000..5a2b2cb5 --- /dev/null +++ b/src/analysers/analyser_rtp.cpp @@ -0,0 +1,204 @@ +#include +#include "analyser_rtp.h" + +rtpAnalyser::rtpAnalyser(Util::Config config) : analysers(config) +{ + std::cout << "rtp constr" << std::endl; + Socket::Connection conn("localhost", 554, true); + step = 0; + trackIt = 0; + +} + +void rtpAnalyser::doValidate() +{ +} + +bool rtpAnalyser::packetReady() +{ + return conn.connected(); +} + +int rtpAnalyser::doAnalyse() +{ + if (analyse){ //always analyse..? + + // 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 + //HTTP_S.url = "rtsp://localhost/steers"; + 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; + 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); + 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 + trackIt++; + step++; + HTTP_S.SendRequest(conn); + std::cerr << "step " << step << "/\\"<< ss.str()<getPayloadType() == 97){ + int h264type = (int)(connections[cx].data[12] & 0x1f); + std::cout << h264type << " - "; + if(h264type == 0){ + std::cout << "unspecified - "; + }else if(h264type == 1){ + std::cout << "Coded slice of a non-IDR picture - "; + }else if(h264type == 2){ + std::cout << "Coded slice data partition A - "; + }else if(h264type == 3){ + std::cout << "Coded slice data partition B - "; + }else if(h264type == 4){ + std::cout << "Coded slice data partition C - "; + }else if(h264type == 5){ + std::cout << "Coded slice of an IDR picture - "; + }else if(h264type == 6){ + std::cout << "Supplemental enhancement information (SEI) - "; + }else if(h264type == 7){ + std::cout << "Sequence parameter set - "; + }else if(h264type == 8){ + std::cout << "Picture parameter set - "; + }else if(h264type == 9){ + std::cout << "Access unit delimiter - "; + }else if(h264type == 10){ + std::cout << "End of sequence - "; + }else if(h264type == 11){ + std::cout << "End of stream - "; + }else if(h264type == 12){ + std::cout << "Filler data - "; + }else if(h264type == 13){ + std::cout << "Sequence parameter set extension - "; + }else if(h264type == 14){ + std::cout << "Prefix NAL unit - "; + }else if(h264type == 15){ + std::cout << "Subset sequence parameter set - "; + }else if(h264type == 16){ + std::cout << "Reserved - "; + }else if(h264type == 17){ + std::cout << "Reserved - "; + }else if(h264type == 18){ + std::cout << "Reserved - "; + }else if(h264type == 19){ + std::cout << "Coded slice of an auxiliary coded picture without partitioning - "; + }else if(h264type == 20){ + std::cout << "Coded slice extension - "; + }else if(h264type == 21){ + std::cout << "Reserved - "; + }else if(h264type == 22){ + std::cout << "Reserved - "; + }else if(h264type == 23){ + std::cout << "Reserved - "; + }else if(h264type == 24){ + std::cout << "stap a - "; + }else if(h264type == 25){ + std::cout << "stap b - "; + }else if(h264type == 26){ + std::cout << "mtap16 - "; + }else if(h264type == 27){ + std::cout << "mtap24 - "; + }else if(h264type == 28){ + std::cout << "fu a - "; + }else if(h264type == 29){ + std::cout << "fu b - "; + }else if(h264type == 30){ + std::cout << "Unspecified - "; + }else if(h264type == 31){ + std::cout << "Unspecified - "; + } + + + + + for(unsigned int i = 13 ; i < connections[cx].data_len;i++){ + std::cout << std::hex < +#include "analyser.h" +#include +#include +#include +#include +#include +#include +#include + +class rtpAnalyser : public analysers +{ + Socket::Connection conn; + HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender. + int step; + std::vector tracks; + std::vector connections; + unsigned int trackIt; + + public: + rtpAnalyser(Util::Config config); + bool packetReady(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); + void doValidate(); +}; diff --git a/src/analysers/analyser_ts.cpp b/src/analysers/analyser_ts.cpp new file mode 100644 index 00000000..e2add450 --- /dev/null +++ b/src/analysers/analyser_ts.cpp @@ -0,0 +1,211 @@ +#include "analyser_ts.h" +#include "analyser.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +tsAnalyser::tsAnalyser(Util::Config config) : analysers(config) { + upTime = Util::bootSecs(); + pcr = 0; + bytes = 0; + + detailLevel = detail; + endTime = 0; + incorrectPacket = 0; +} + +void tsAnalyser::doValidate() { + long long int finTime = Util::bootSecs(); + fprintf(stdout, "time since boot,time at completion,real time duration of data receival,video duration\n"); + fprintf(stdout, "%lli000,%lli000,%lli000,%li \n", upTime, finTime, finTime - upTime, pcr / 27000); +} + +bool tsAnalyser::packetReady() { + + if(incorrectPacket > 5) + { + mayExecute = false; + //FAIL_MSG("too many incorrect ts packets!"); + return false; + } + + return std::cin.good(); +} + +int tsAnalyser::doAnalyse() { + + char tsIdentifier = std::cin.peek(); + + if(tsIdentifier != 0x47) + { + incorrectPacket++; + } + + std::cin.read(packetPtr, 188); +//0x47 + if (std::cin.gcount() != 188) { return 0; } + bytes += 188; + if (packet.FromPointer(packetPtr)) { + if (analyse) { + if (packet.getUnitStart() && payloads[packet.getPID()] != "") { + std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel); + payloads.erase(packet.getPID()); + } + if (detailLevel >= 3 || !packet.getPID() || packet.isPMT()) { + if (packet.getPID() == 0) { ((TS::ProgramAssociationTable *)&packet)->parsePIDs(); } + std::cout << packet.toPrettyString(0, detailLevel); + } + if (packet.getPID() && !packet.isPMT() && (payloads[packet.getPID()].size() || packet.getUnitStart())) { + payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength()); + } + } + if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()) { pcr = packet.getPCR(); } + } + if (bytes > 1024) { + long long int tTime = Util::bootSecs(); + if (validate && tTime - upTime > 5 && tTime - upTime > pcr / 27000000) { + std::cerr << "data received too slowly" << std::endl; + return 1; + } + bytes = 0; + } + + return endTime; +} + +int main(int argc, char **argv) { + Util::Config conf = Util::Config(argv[0]); + analysers::defaultConfig(conf); + + conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info " + "about this tag type (8 = audio, 9 = video, 0 = all)\"}")); + + // override default detail level with specific detail level for TS Analyser. + conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of " + "analysis. 1 = PES only, 2 = PAT/PMT (default), 3 = all TS packets, 9 = raw PES packet bytes, 10 = " + "raw TS packet bytes\"}")); + + conf.parseArgs(argc, argv); + tsAnalyser A(conf); + + A.Run(); + + return 0; +} + +tsAnalyser::~tsAnalyser() { + + for (std::map::iterator it = payloads.begin(); it != payloads.end(); it++) { + if (!it->first || it->first == 4096) { continue; } + std::cout << printPES(it->second, it->first, detailLevel); + } +} + +std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int detailLevel) { + unsigned int headSize = 0; + std::stringstream res; + bool known = false; + res << "[PES " << PID << "]"; + if ((d[3] & 0xF0) == 0xE0) { + res << " [Video " << (int)(d[3] & 0xF) << "]"; + known = true; + } + if (!known && (d[3] & 0xE0) == 0xC0) { + res << " [Audio " << (int)(d[3] & 0x1F) << "]"; + known = true; + } + 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] << " ]"; } + unsigned int padding = 0; + if (known) { + if ((d[6] & 0xC0) != 0x80) { res << " [!INVALID FIRST BITS!]"; } + if (d[6] & 0x30) { res << " [SCRAMBLED]"; } + if (d[6] & 0x08) { res << " [Priority]"; } + if (d[6] & 0x04) { res << " [Aligned]"; } + if (d[6] & 0x02) { res << " [Copyrighted]"; } + if (d[6] & 0x01) { + res << " [Original]"; + } else { + res << " [Copy]"; + } + + int timeFlags = ((d[7] & 0xC0) >> 6); + if (timeFlags == 2) { headSize += 5; } + if (timeFlags == 3) { headSize += 10; } + if (d[7] & 0x20) { + res << " [ESCR present, not decoded!]"; + headSize += 6; + } + if (d[7] & 0x10) { + uint32_t es_rate = (Bit::btoh24(d.data() + 9 + headSize) & 0x7FFFFF) >> 1; + res << " [ESR: " << (es_rate * 50) / 1024 << " KiB/s]"; + headSize += 3; + } + if (d[7] & 0x08) { + res << " [Trick mode present, not decoded!]"; + headSize += 1; + } + if (d[7] & 0x04) { + res << " [Add. copy present, not decoded!]"; + headSize += 1; + } + if (d[7] & 0x02) { + res << " [CRC present, not decoded!]"; + headSize += 2; + } + if (d[7] & 0x01) { + res << " [Extension present, not decoded!]"; + headSize += 0; /// \todo Implement this. Complicated field, bah. + } + if (d[8] != headSize) { + padding = d[8] - headSize; + res << " [Padding: " << padding << "b]"; + } + if (timeFlags & 0x02) { + long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1); + time <<= 15; + time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F); + time <<= 15; + time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F); + res << " [PTS " << ((double)time / 90000) << "s]"; + } + if (timeFlags & 0x01) { + long long unsigned int time = ((d[14] >> 1) & 0x07); + time <<= 15; + time |= ((int)d[15] << 7) | (d[16] >> 1); + time <<= 15; + time |= ((int)d[17] << 7) | (d[18] >> 1); + res << " [DTS " << ((double)time / 90000) << "s]"; + } + } + if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)) { res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]"; } + res << std::endl; + + if (detailLevel == 10) { + unsigned int counter = 0; + for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i) { + if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1) { + res << std::endl; + counter = 0; + } + res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i] & 0xff) << " "; + counter++; + if ((counter) % 32 == 31) { res << std::endl; } + } + res << std::endl; + } + return res.str(); +} diff --git a/src/analysers/analyser_ts.h b/src/analysers/analyser_ts.h new file mode 100644 index 00000000..73c602db --- /dev/null +++ b/src/analysers/analyser_ts.h @@ -0,0 +1,27 @@ +#include +#include +#include "analyser.h" + +class tsAnalyser : public analysers +{ + long long filter; + + std::map payloads; + TS::Packet packet; + long long int upTime; + int64_t pcr; + unsigned int bytes; + char packetPtr[188]; + int detailLevel; + int incorrectPacket; + + public: + tsAnalyser(Util::Config config); + ~tsAnalyser(); + bool packetReady(); + void PreProcessing(); + //int Analyse(); + int doAnalyse(); + void doValidate(); + std::string printPES(const std::string & d, unsigned long PID, int detailLevel); +}; diff --git a/src/analysers/flv_analyser.cpp b/src/analysers/flv_analyser.cpp index 022d7970..b5e31967 100644 --- a/src/analysers/flv_analyser.cpp +++ b/src/analysers/flv_analyser.cpp @@ -15,6 +15,54 @@ #include #include #include +#include + + +namespace Analysers { + + int analyseFLV(Util::Config conf){ + bool fileinput = conf.getString("filename").length() > 0; + bool analyse = conf.getString("mode") == "analyse"; + bool validate = conf.getString("mode") == "validate"; + + long long filter = conf.getInteger("filter"); + + if(fileinput){ + std::string filename = conf.getString("filename"); + int fp = open(filename.c_str(), O_RDONLY); + + if(fp <= 0){ + FAIL_MSG("Cannot open file: %s",filename.c_str()); + return false; + } + + dup2(fp, STDIN_FILENO); + close(fp); + } + + FLV::Tag flvData; // Temporary storage for incoming FLV data. + long long int endTime = 0; + long long int upTime = Util::bootSecs(); + + while(!feof(stdin)){ + if (flvData.FileLoader(stdin)){ + if (analyse){ + if (!filter || filter == flvData.data[0]){ + std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; + } + } + endTime = flvData.tagTime(); + } + } + + long long int finTime = Util::bootSecs(); + if (validate){ + std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl; + } + + return 0; + } +} ///Debugging tool for FLV data. /// Expects FLV data through stdin, outputs human-readable information to stderr. @@ -22,28 +70,10 @@ int main(int argc, char ** argv){ Util::Config conf = Util::Config(argv[0]); conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info about this tag type (8 = audio, 9 = video, 0 = all)\"}")); 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("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}")); conf.parseArgs(argc, argv); - bool analyse = conf.getString("mode") == "analyse"; - bool validate = conf.getString("mode") == "validate"; - long long filter = conf.getInteger("filter"); - - FLV::Tag flvData; // Temporary storage for incoming FLV data. - long long int endTime = 0; - long long int upTime = Util::bootSecs(); - while ( !feof(stdin)){ - if (flvData.FileLoader(stdin)){ - if (analyse){ - if (!filter || filter == flvData.data[0]){ - std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; - } - } - endTime = flvData.tagTime(); - } - } - long long int finTime = Util::bootSecs(); - if (validate){ - std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl; - } - return 0; + return Analysers::analyseFLV(conf); } + + diff --git a/src/analysers/mp4_analyser.cpp b/src/analysers/mp4_analyser.cpp index ca4ad72c..0e758621 100644 --- a/src/analysers/mp4_analyser.cpp +++ b/src/analysers/mp4_analyser.cpp @@ -17,7 +17,42 @@ namespace Analysers { /// /// Expects MP4 data through stdin, outputs human-readable information to stderr. ///\return The return code of the analyser. - int analyseMP4(){ + int analyseMP4(Util::Config conf){ + + std::string filename = conf.getString("filename"); + + if(filename.length() > 0){ + int fp = open(filename.c_str(), O_RDONLY); + + if(fp <= 0){ + FAIL_MSG("Cannot open file: %s",filename.c_str()); + return false; + } + + dup2(fp, STDIN_FILENO); + close(fp); + } + + /* + MP4::Box mp4Data; + int dataSize = 0;//mp4Buffer.size(); + int curPos = 0; + + while (!feof(stdin)){ + + if(mp4Data.read(stdin)) + { + DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); + std::cerr << mp4Data.toPrettyString(0) << std::endl; + //curPos += dataSize - mp4Buffer.size(); + //dataSize = mp4Buffer.size(); + } + + } + */ + + + std::string mp4Buffer; //Read all of std::cin to mp4Buffer while (std::cin.good()){ @@ -25,15 +60,19 @@ namespace Analysers { } mp4Buffer.erase(mp4Buffer.size() - 1, 1); + MP4::Box mp4Data; int dataSize = mp4Buffer.size(); int curPos = 0; + while (mp4Data.read(mp4Buffer)){ DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); std::cerr << mp4Data.toPrettyString(0) << std::endl; curPos += dataSize - mp4Buffer.size(); dataSize = mp4Buffer.size(); } + + DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos); return 0; } @@ -43,7 +82,8 @@ namespace Analysers { /// Expects MP4 data through stdin, outputs human-readable information to stderr. int main(int argc, char ** argv){ Util::Config conf = Util::Config(argv[0]); + conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the MP4 file to analyse.\"}")); conf.parseArgs(argc, argv); - return Analysers::analyseMP4(); + return Analysers::analyseMP4(conf); } diff --git a/src/analysers/ogg_analyser.cpp b/src/analysers/ogg_analyser.cpp index b14fa3d5..a57f9948 100644 --- a/src/analysers/ogg_analyser.cpp +++ b/src/analysers/ogg_analyser.cpp @@ -118,6 +118,167 @@ namespace Analysers{ return r.str(); } + int newfunc(Util::Config & conf){ + + std::map sn2Codec; + std::string oggBuffer; + OGG::Page oggPage; + int kfgshift; + + //validate variables + bool doValidate = true; + long long int lastTime =0; + double mspft = 0; + std::map oggMap; + theora::header * theader = 0; + bool seenIDheader = false; + struct sysinfo sinfo; + sysinfo(&sinfo); + long long int upTime = sinfo.uptime; + + while (oggPage.read(stdin)){ //reading ogg to string + //print the Ogg page details, if requested + if (conf.getBool("pages")){ + printf("%s", oggPage.toPrettyString().c_str()); + } + + //attempt to detect codec if this is the first page of a stream + if (oggPage.getHeaderType() & OGG::BeginOfStream){ + if (memcmp("theora", oggPage.getSegment(0) + 1, 6) == 0){ + sn2Codec[oggPage.getBitstreamSerialNumber()] = "Theora"; + + if(doValidate){ + if(!seenIDheader){ + if (theader){delete theader; theader = 0;} + theader = new theora::header((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0)); + if(theader->getHeaderType() == 0){ + seenIDheader = true; + } + mspft = (double)(theader->getFRD() * 1000) / theader->getFRN(); + } + + if(oggPage.getGranulePosition() != 0xffffffffffffffff){ + lastTime = ((oggPage.getGranulePosition()>>(int)theader->getKFGShift())*mspft); + } + } + + } + + if (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){ + sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis"; + + if(oggMap.find(oggPage.getBitstreamSerialNumber()) == oggMap.end()){ + //validate stuff + if(doValidate){ + vorbis::header vheader((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0)); + //if (vheader){ + oggMap[oggPage.getBitstreamSerialNumber()] = ntohl(vheader.getAudioSampleRate()); + //oggPage.setInternalCodec(sn2Codec[oggPage.getBitstreamSerialNumber()]); + //} + lastTime = (double)oggPage.getGranulePosition()/(double)oggMap[oggPage.getBitstreamSerialNumber()]; + } + } + } + + if(doValidate){ + + fprintf(stdout,"printing timing...."); + if (theader){delete theader; theader = 0;} + sysinfo(&sinfo); + long long int finTime = sinfo.uptime; + fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n"); + fprintf(stdout, "%lli000,%lli000,%lli000,%lli \n",upTime,finTime,finTime-upTime,lastTime); + //print last time + } + + if (memcmp("OpusHead", oggPage.getSegment(0), 8) == 0){ + sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus"; + } + if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){ + std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " recognized as " << sn2Codec[oggPage.getBitstreamSerialNumber()] << std::endl; + } else { + std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " could not be recognized as any known codec" << std::endl; + } + + } + + if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){ + 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++){ + theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size()); + if (tmpHeader.isHeader()){ + if (tmpHeader.getHeaderType() == 0){ + kfgshift = tmpHeader.getKFGShift(); + } + } else { + if (!(oggPage.getHeaderType() == OGG::Continued) && tmpHeader.getFTYPE() == 0){ //if keyframe + std::cout << "keyframe granule: " << (oggPage.getGranulePosition() >> kfgshift) << std::endl; + std::cout << "keyframe " << keyCount << " has " << numParts << " parts, yo." << std::endl; + numParts = 0; + keyCount++; + } + if (oggPage.getHeaderType() != OGG::Continued || i){ + numParts++; + } + } + std::cout << tmpHeader.toPrettyString(4); + + } + } else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){ + 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()){ + std::cout << tmpHeader.toPrettyString(4); + } + } + } else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Opus"){ + 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(); + const char * part = oggPage.getSegment(i); + if (len >= 8 && memcmp(part, "Opus", 4) == 0){ + if (memcmp(part, "OpusHead", 8) == 0){ + std::cout << " Version: " << (int)(part[8]) << std::endl; + 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; + std::cout << " Gain: " << (int)(part[16] + (part[17] << 8)) << std::endl; + std::cout << " Channel map: " << (int)(part[18]) << std::endl; + if (part[18] > 0){ + std::cout << " Channel map family " << (int)(part[18]) << " not implemented - output incomplete" << std::endl; + } + } + if (memcmp(part, "OpusTags", 8) == 0){ + 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); + std::cout << " Tags: (" << strings << ")" << std::endl; + str_data += 4; + for (unsigned int j = 0; j < strings; j++){ + unsigned int strlen = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24); + str_data += 4; + std::cout << " [" << j << "] " << std::string((char *) str_data, strlen) << std::endl; + str_data += strlen; + } + } + } else { + std::cout << " " << Opus_prettyPacket(part, len) << std::endl; + } + offset += len; + } + } + std::cout << std::endl; + } + + return 0; + } + int analyseOGG(Util::Config & conf){ std::map sn2Codec; std::string oggBuffer; @@ -227,7 +388,7 @@ namespace Analysers{ return 0; } - + int validateOGG(bool analyse){ std::map sn2Codec; std::string oggBuffer; @@ -242,7 +403,8 @@ namespace Analysers{ sysinfo(&sinfo); long long int upTime = sinfo.uptime; while (std::cin.good()){ - + oggBuffer.reserve(1024); + for (unsigned int i = 0; (i < 1024) && (std::cin.good()); i++){ oggBuffer += std::cin.get(); } @@ -286,7 +448,7 @@ namespace Analysers{ } } //while OGG::page check function read - //save last time + //save last time0 sysinfo(&sinfo); long long int tTime = sinfo.uptime; if((tTime-upTime) > 5 && (tTime-upTime)>(int)(lastTime) ){ @@ -307,15 +469,23 @@ namespace Analysers{ int main(int argc, char ** argv){ Util::Config conf = Util::Config(argv[0]); conf.addOption("pages", JSON::fromString("{\"long\":\"pages\", \"short\":\"p\", \"long_off\":\"nopages\", \"short_off\":\"P\", \"default\":0, \"help\":\"Enable/disable printing of Ogg pages\"}")); - conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":1, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}")); + conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":0, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}")); conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"x\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}")); + conf.addOption("newfunc", JSON::fromString("{\"long\":\"newfunc\", \"short\":\"N\", \"default\":0, \"long_off\":\"notnewfunc\", \"short_off\":\"x\", \"help\":\"newfuncValidate (-N) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}")); + conf.parseArgs(argc, argv); conf.activate(); if (conf.getBool("validate")){ return Analysers::validateOGG(conf.getBool("analyse")); + + //return Analysers::newfunc(conf); }else if(conf.getBool("analyse")){ return Analysers::analyseOGG(conf); + }else if(conf.getBool("newfunc")){ + fprintf(stdout, "begin newfunc\n"); + return Analysers::newfunc(conf); + fprintf(stdout, "end newfunction\n"); } }