From 945e6f2d1a03359cc62a7ddc1cbfbecbbf6f0ade Mon Sep 17 00:00:00 2001 From: Thulinma Date: Sun, 16 Apr 2017 16:20:12 +0200 Subject: [PATCH] Analyser unification finished --- CMakeLists.txt | 112 ++-- lib/h264.cpp | 75 ++- lib/h264.h | 3 +- lib/http_parser.cpp | 29 ++ lib/http_parser.h | 3 +- lib/opus.cpp | 75 +++ lib/opus.h | 6 + src/analysers/amf_analyser.cpp | 57 -- src/analysers/analyser.cpp | 151 +++--- src/analysers/analyser.h | 64 +-- src/analysers/analyser_dtsc.cpp | 200 +++---- src/analysers/analyser_dtsc.h | 22 +- src/analysers/analyser_flv.cpp | 91 ++-- src/analysers/analyser_flv.h | 22 +- src/analysers/analyser_h264.cpp | 58 +++ src/analysers/analyser_h264.h | 15 + src/analysers/analyser_hls.cpp | 340 ++++++------ src/analysers/analyser_hls.h | 74 +-- src/analysers/analyser_mp4.cpp | 92 ++-- src/analysers/analyser_mp4.h | 28 +- src/analysers/analyser_ogg.cpp | 146 ++++-- src/analysers/analyser_ogg.h | 16 + src/analysers/analyser_rtmp.cpp | 369 ++++++------- src/analysers/analyser_rtmp.h | 45 +- src/analysers/analyser_rtp.cpp | 71 +-- ...tsp_rtp_analyser.cpp => analyser_rtsp.cpp} | 0 .../{analyser_rtp.h => analyser_rtsp.h} | 0 src/analysers/analyser_ts.cpp | 203 ++++---- src/analysers/analyser_ts.h | 33 +- src/analysers/dtsc_analyser.cpp | 128 ----- src/analysers/flv_analyser.cpp | 79 --- src/analysers/h264_analyser.cpp | 30 -- src/analysers/hls_analyser.cpp | 199 ------- src/analysers/mist_analyse.cpp | 16 + src/analysers/mp4_analyser.cpp | 89 ---- src/analysers/ogg_analyser.cpp | 492 ------------------ src/analysers/rtmp_analyser.cpp | 219 -------- src/analysers/rtp_analyser.cpp | 209 -------- src/analysers/ts_analyser.cpp | 204 -------- src/analysers/tsstream_analyser.cpp | 55 -- src/utils/util_amf.cpp | 47 ++ .../load_analyser.cpp => utils/util_load.cpp} | 0 .../rax_analyser.cpp => utils/util_rax.cpp} | 0 .../util_stats.cpp} | 0 44 files changed, 1264 insertions(+), 2903 deletions(-) create mode 100644 lib/opus.cpp create mode 100644 lib/opus.h delete mode 100644 src/analysers/amf_analyser.cpp create mode 100644 src/analysers/analyser_h264.cpp create mode 100644 src/analysers/analyser_h264.h create mode 100644 src/analysers/analyser_ogg.h rename src/analysers/{rtsp_rtp_analyser.cpp => analyser_rtsp.cpp} (100%) rename src/analysers/{analyser_rtp.h => analyser_rtsp.h} (100%) delete mode 100644 src/analysers/dtsc_analyser.cpp delete mode 100644 src/analysers/flv_analyser.cpp delete mode 100644 src/analysers/h264_analyser.cpp delete mode 100644 src/analysers/hls_analyser.cpp create mode 100644 src/analysers/mist_analyse.cpp delete mode 100644 src/analysers/mp4_analyser.cpp delete mode 100644 src/analysers/ogg_analyser.cpp delete mode 100644 src/analysers/rtmp_analyser.cpp delete mode 100644 src/analysers/rtp_analyser.cpp delete mode 100755 src/analysers/ts_analyser.cpp delete mode 100755 src/analysers/tsstream_analyser.cpp create mode 100644 src/utils/util_amf.cpp rename src/{analysers/load_analyser.cpp => utils/util_load.cpp} (100%) rename src/{analysers/rax_analyser.cpp => utils/util_rax.cpp} (100%) rename src/{analysers/stats_analyser.cpp => utils/util_stats.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd466c2c..f8da7e32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,7 @@ set(libHeaders ${SOURCE_DIR}/lib/util.h ${SOURCE_DIR}/lib/vorbis.h ${SOURCE_DIR}/lib/triggers.h + ${SOURCE_DIR}/lib/opus.h ) ######################################## @@ -213,6 +214,7 @@ set(libSources ${SOURCE_DIR}/lib/util.cpp ${SOURCE_DIR}/lib/vorbis.cpp ${SOURCE_DIR}/lib/triggers.cpp + ${SOURCE_DIR}/lib/opus.cpp ) ######################################## @@ -251,11 +253,16 @@ add_custom_command(TARGET mist ) ######################################## -# MistServer - Analysers ` # +# MistServer - Analysers # ######################################## macro(makeAnalyser analyserName format) add_executable(MistAnalyser${analyserName} - src/analysers/${format}_analyser.cpp + src/analysers/mist_analyse.cpp + src/analysers/analyser.cpp + src/analysers/analyser_${format}.cpp + ) + set_target_properties(MistAnalyser${analyserName} + PROPERTIES COMPILE_DEFINITIONS "ANALYSERHEADER=\"analyser_${format}.h\"; ANALYSERTYPE=Analyser${analyserName}" ) target_link_libraries(MistAnalyser${analyserName} mist @@ -266,89 +273,40 @@ macro(makeAnalyser analyserName format) ) endmacro() -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(RTMP rtmp) +makeAnalyser(FLV flv) +makeAnalyser(DTSC dtsc) 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 -makeAnalyser(RTSP rtsp_rtp) #LTS +#makeAnalyser(RTSP rtsp) #LTS #Currently broken. Horribly. makeAnalyser(TS ts) #LTS +makeAnalyser(MP4 mp4) #LTS makeAnalyser(H264 h264) #LTS makeAnalyser(HLS hls) #LTS -makeAnalyser(DASH dash) #LTS -makeAnalyser(TSStream tsstream) #LTS -makeAnalyser(Stats stats) #LTS +#LTS_START +######################################## +# MistServer - Utilities # +######################################## +macro(makeUtil utilName utilFile) + add_executable(MistUtil${utilName} + src/utils/util_${utilFile}.cpp + ) + target_link_libraries(MistUtil${utilName} + mist + ) + install( + TARGETS MistUtil${utilName} + DESTINATION bin + ) +endmacro() +makeUtil(Stats stats) +makeUtil(RAX rax) +makeUtil(AMF amf) if (DEFINED LOAD_BALANCE ) - makeAnalyser(Load load) #LTS + makeUtil(Load load) endif() +#LTS_END ######################################## # MistServer - Inputs # diff --git a/lib/h264.cpp b/lib/h264.cpp index e949424f..0807b0b4 100644 --- a/lib/h264.cpp +++ b/lib/h264.cpp @@ -810,41 +810,66 @@ namespace h264 { out << " Message of type " << payloadType << ", " << payloadSize << " bytes long" << std::endl; } - nalUnit * nalFactory(char * _data, size_t _len, size_t & offset, bool annexb) { - char * data = _data + offset; - size_t len = _len - offset; - nalUnit * result = NULL; - if (len < 4){ - return result; - } + nalUnit * nalFactory(const char * _data, size_t _len, size_t & offset, bool annexb) { if (annexb){ - FAIL_MSG("Not supported in annexb mode yet"); - return result; + //check if we have a start marker at the beginning, if so, move the offset over + if (_len > offset && !_data[offset]){ + for (size_t i = offset+1; i < _len; ++i){ + if (_data[i] > 1){ + FAIL_MSG("Encountered bullshit AnnexB data..?"); + return 0; + } + if (_data[i] == 1){offset = i+1; break;} + } + } + //now we know we're starting at real data. Yay! } - uint32_t pktLen = Bit::btohl(data); - if (len < 4 + pktLen){ - return result; + if (_len < offset + 4){ + WARN_MSG("Not at least 4 bytes available - cancelling"); + return 0; } - switch (data[5] & 0x1F){ + uint32_t pktLen = 0; + if (!annexb){ + //read the 4b size in front + pktLen = Bit::btohl(_data+offset); + if (_len < 4 + pktLen){ + WARN_MSG("Not at least 4+%lu bytes available - cancelling", pktLen); + return 0; + } + offset += 4; + } + const char * data = _data + offset; + size_t len = _len - offset; + if (annexb){ + //search for the next start marker + for (size_t i = 1; i < len-2; ++i){ + if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1){ + offset += i+2; + while (i && !data[i]){--i;} + pktLen = i; + break; + } + } + }else{ + offset += pktLen; + } + if (!pktLen){ + WARN_MSG("Cannot determine packet length - cancelling"); + return 0; + } + switch (data[0] & 0x1F){ case 1: case 5: - result = new codedSliceUnit(data + 4, pktLen); - break; + return new codedSliceUnit(data, pktLen); case 6: - result = new seiUnit(data + 4, pktLen); - break; + return new seiUnit(data, pktLen); case 7: - result = new spsUnit(data + 4, pktLen); - break; + return new spsUnit(data, pktLen); case 8: - result = new ppsUnit(data + 4, pktLen); - break; + return new ppsUnit(data, pktLen); default: - result = new nalUnit(data + 4, pktLen); - break; + return new nalUnit(data, pktLen); } - offset += 4 + pktLen; - return result; } nalUnit * nalFactory(FILE * in, bool annexb) { diff --git a/lib/h264.h b/lib/h264.h index a31930c9..922c4c2c 100644 --- a/lib/h264.h +++ b/lib/h264.h @@ -76,6 +76,7 @@ namespace h264 { public: nalUnit(const char * data, size_t len) : payload(data, len) {} uint8_t getType() { return payload[0] & 0x1F; } + uint32_t getSize(){return payload.size();} virtual void toPrettyString(std::ostream & out){ out << "Nal unit of type " << (((uint8_t)payload[0]) & 0x1F) << ", " << payload.size() << " bytes long" << std::endl; } @@ -296,5 +297,5 @@ namespace h264 { nalUnit * nalFactory(FILE * in, bool annexb = true); - nalUnit * nalFactory(char * data, size_t len, size_t & offset, bool annexb = true); + nalUnit * nalFactory(const char * data, size_t len, size_t & offset, bool annexb = true); } diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 61fbbf7c..142c8f1b 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -96,6 +96,35 @@ std::string HTTP::URL::getUrl() const{ return ret; } +///Returns a URL object for the given link, resolved relative to the current URL object. +HTTP::URL HTTP::URL::link(const std::string &l){ + //Full link + if (l.find("://") < l.find('/')){return URL(l);} + //Absolute link + if (l[0] == '/'){ + if (l.size() > 1 && l[1] == '/'){ + //Same-protocol full link + return URL(protocol+":"+l); + }else{ + //Same-domain/port absolute link + URL tmp = *this; + tmp.args.clear(); + tmp.path = l.substr(1); + //Abuse the fact that we don't check for arguments in getUrl() + return URL(tmp.getUrl()); + } + } + //Relative link + std::string tmpUrl = getUrl(); + size_t slashPos = tmpUrl.rfind('/'); + if (slashPos == std::string::npos){ + tmpUrl += "/"; + }else{ + tmpUrl.erase(slashPos+1); + } + return URL(tmpUrl+l); +} + /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// All this constructor does is call HTTP::Parser::Clean(). HTTP::Parser::Parser() { diff --git a/lib/http_parser.h b/lib/http_parser.h index 89b1d3fb..ddded232 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -72,7 +72,7 @@ namespace HTTP { ///URL parsing class. Parses full URL into its subcomponents class URL { public: - URL(const std::string & url); + URL(const std::string & url = ""); uint32_t getPort() const; std::string getUrl() const; std::string host;///< Hostname or IP address of URL @@ -80,6 +80,7 @@ namespace HTTP { std::string port;/// + +namespace Opus{ + std::string Opus_prettyPacket(const char *part, int len){ + if (len < 1){return "Invalid packet (0 byte length)";} + std::stringstream r; + char config = part[0] >> 3; + char code = part[0] & 3; + if ((part[0] & 4) == 4){ + r << "Stereo, "; + }else{ + r << "Mono, "; + } + if (config < 14){ + r << "SILK, "; + if (config < 4){r << "NB, ";} + if (config < 8 && config > 3){r << "MB, ";} + if (config < 14 && config > 7){r << "WB, ";} + if (config % 4 == 0){r << "10ms";} + if (config % 4 == 1){r << "20ms";} + if (config % 4 == 2){r << "40ms";} + if (config % 4 == 3){r << "60ms";} + } + if (config < 16 && config > 13){ + r << "Hybrid, "; + if (config < 14){ + r << "SWB, "; + }else{ + r << "FB, "; + } + if (config % 2 == 0){ + r << "10ms"; + }else{ + r << "20ms"; + } + } + if (config > 15){ + r << "CELT, "; + if (config < 20){r << "NB, ";} + if (config < 24 && config > 19){r << "WB, ";} + if (config < 28 && config > 23){r << "SWB, ";} + if (config > 27){r << "FB, ";} + if (config % 4 == 0){r << "2.5ms";} + if (config % 4 == 1){r << "5ms";} + if (config % 4 == 2){r << "10ms";} + if (config % 4 == 3){r << "20ms";} + } + if (code == 0){ + r << ": 1 packet (" << (len - 1) << "b)"; + return r.str(); + } + if (code == 1){ + r << ": 2 packets (" << ((len - 1) / 2) << "b / " << ((len - 1) / 2) << "b)"; + return r.str(); + } + if (code == 2){ + if (len < 2){return "Invalid packet (code 2 must be > 1 byte long)";} + if (part[1] < 252){ + r << ": 2 packets (" << (int)part[1] << "b / " << (int)(len - 2 - part[1]) << "b)"; + }else{ + int ilen = part[1] + part[2] * 4; + r << ": 2 packets (" << ilen << "b / " << (int)(len - 3 - ilen) << "b)"; + } + return r.str(); + } + // code 3 + bool VBR = (part[1] & 128) == 128; + bool pad = (part[1] & 64) == 64; + bool packets = (part[1] & 63); + r << ": " << packets << " packets (VBR = " << VBR << ", padding = " << pad << ")"; + return r.str(); + } +} + diff --git a/lib/opus.h b/lib/opus.h new file mode 100644 index 00000000..0557225f --- /dev/null +++ b/lib/opus.h @@ -0,0 +1,6 @@ +#include + +namespace Opus{ + std::string Opus_prettyPacket(const char *part, int len); +} + diff --git a/src/analysers/amf_analyser.cpp b/src/analysers/amf_analyser.cpp deleted file mode 100644 index f9b70792..00000000 --- a/src/analysers/amf_analyser.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/// \file amf_analyser.cpp -/// Debugging tool for AMF data. -/// Expects AMF data through stdin, outputs human-readable information to stderr. - -#include -#include -#include -#include -#include -#include -#include - -///\brief Holds everything unique to the analysers. -namespace Analysers { - ///\brief Debugging tool for AMF data. - /// - /// Expects AMF data through stdin, outputs human-readable information to stderr. - ///\return The return code of the analyser. - 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()){ - amfBuffer += std::cin.get(); - } - //Strip the invalid last character - amfBuffer.erase((amfBuffer.end() - 1)); - //Parse into an AMF::Object - AMF::Object amfData = AMF::parse(amfBuffer); - //Print the output. - std::cerr << amfData.Print() << std::endl; - return 0; - } -} - -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(conf); -} - diff --git a/src/analysers/analyser.cpp b/src/analysers/analyser.cpp index 776841c8..86c10a02 100644 --- a/src/analysers/analyser.cpp +++ b/src/analysers/analyser.cpp @@ -1,104 +1,99 @@ #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(); +/// Reads configuration and opens a passed filename replacing standard input if needed. +Analyser::Analyser(Util::Config &conf){ + validate = conf.getBool("validate"); + detail = conf.getInteger("detail"); + mediaTime = 0; + upTime = Util::bootSecs(); + isActive = &conf.is_active; } -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()); +///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){ + FAIL_MSG("Cannot open '%s': %s", filename.c_str(), strerror(errno)); + return false; } - dup2(fp, STDIN_FILENO); close(fp); + INFO_MSG("Parsing %s...", filename.c_str()); + }else{ + INFO_MSG("Parsing standard input..."); } + return true; } -bool analysers::hasInput() { - // std::cout << std::cin.good() << std::endl; - - return std::cin.good(); - // return !feof(stdin); +/// Stops analysis by closing the standard input +void Analyser::stop(){ + close(STDIN_FILENO); + std::cin.setstate(std::ios_base::eofbit); } -int analysers::Run() { +/// Prints validation message if needed +Analyser::~Analyser(){ + if (validate){std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << mediaTime << std::endl;} +} - if(mayExecute) - { - std::cout << "start analyser with detailLevel: " << detail << std::endl; - endTime = 0; - upTime = Util::bootSecs(); +///Checks if standard input is still valid. +bool Analyser::isOpen(){ + return (*isActive) && std::cin.good(); +} - while (hasInput() && mayExecute) { - while (packetReady()) { - // std::cout << "in loop..." << std::endl; - endTime = doAnalyse(); +/// 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; + } + while (conf.is_active && isOpen()){ + if (!parsePacket()){ + if (isOpen()){ + FAIL_MSG("Could not parse packet!"); + return 1; } + INFO_MSG("Reached end of file"); + return 0; } - - finTime = Util::bootSecs(); - - if (validate) { - // std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl; - doValidate(); + if (validate){ + finTime = Util::bootSecs(); + if ((finTime - upTime) > (mediaTime / 1000) + 2){ + FAIL_MSG("Media time more than 2 seconds behind!"); + return 1; + } } } 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.\"}")); +/// Sets options common to all analysers. +/// Should generally be called by the init function of each analyser. +void Analyser::init(Util::Config &conf){ + JSON::Value opt; - 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'.\"}")); + opt["arg_num"] = 1ll; + opt["arg"] = "string"; + opt["default"] = "-"; + opt["help"] = "Filename to analyse, or - for standard input (default)"; + conf.addOption("filename", opt); + opt.null(); - conf.addOption( - "detail", - JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}")); + opt["long"] = "validate"; + opt["short"] = "V"; + opt["help"] = "Enable validation mode (default off)"; + conf.addOption("validate", opt); + opt.null(); + + opt["long"] = "detail"; + opt["short"] = "D"; + opt["arg"] = "num"; + opt["default"] = 2ll; + opt["help"] = "Detail level for analysis (0 = none, 2 = default, 10 = max)"; + conf.addOption("detail", opt); + opt.null(); } + diff --git a/src/analysers/analyser.h b/src/analysers/analyser.h index b3816cf0..a60b2b2e 100644 --- a/src/analysers/analyser.h +++ b/src/analysers/analyser.h @@ -1,36 +1,40 @@ #pragma once #include +#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(); +#define DETAIL_LOW(msg, ...) \ + if (detail >= 1){printf(msg "\n", ##__VA_ARGS__);} +#define DETAIL_MED(msg, ...) \ + if (detail >= 2){printf(msg "\n", ##__VA_ARGS__);} +#define DETAIL_HI(msg, ...) \ + if (detail >= 3){printf(msg "\n", ##__VA_ARGS__);} +#define DETAIL_VHI(msg, ...) \ + if (detail >= 4){printf(msg "\n", ##__VA_ARGS__);} +#define DETAIL_XTR(msg, ...) \ + if (detail >= 5){printf(msg "\n", ##__VA_ARGS__);} - static void defaultConfig(Util::Config & conf); - //int Analyse(); - - virtual bool packetReady(); +class Analyser{ +public: + // These contain standard functionality + Analyser(Util::Config &conf); + ~Analyser(); + int run(Util::Config &conf); + virtual void stop(); + virtual bool open(const std::string &filename); + virtual bool isOpen(); + + // These should be provided by analysers + static void init(Util::Config &conf); + virtual bool parsePacket(){return false;} + +protected: + // These hold the current state and/or config + bool validate; ///< True of validation mode is enabled + int detail; ///< Detail level of analyser + uint64_t mediaTime; ///< Timestamp in ms of last media packet received + uint64_t upTime; ///< Unix time of analyser start + uint64_t finTime; ///< Unix time of last packet received + bool *isActive; ///< Pointer to is_active bool from config }; + diff --git a/src/analysers/analyser_dtsc.cpp b/src/analysers/analyser_dtsc.cpp index f074cda6..2804eb1e 100644 --- a/src/analysers/analyser_dtsc.cpp +++ b/src/analysers/analyser_dtsc.cpp @@ -1,124 +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; +void AnalyserDTSC::init(Util::Config &conf){ + Analyser::init(conf); +} - F.reInit(conn); +AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){ + conn = Socket::Connection(0, fileno(stdin)); totalBytes = 0; +} - // F = DTSC::Packet(config.getString("filename")); - if (!F) { - std::cerr << "Not a valid DTSC file" << std::endl; - mayExecute = false; - return; +bool AnalyserDTSC::parsePacket(){ + P.reInit(conn); + if (conn && !P){ + FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes) + return false; + } + if (!conn && !P){ + stop(); + return false; } - if (F.getVersion() == DTSC::DTSC_HEAD) // for meta - { - DTSC::Meta m(F); - - if (detail > 0) { + switch (P.getVersion()){ + case DTSC::DTSC_V1:{ + if (detail >= 2){ + std::cout << "DTSCv1 packet: " << P.getScan().toPrettyString() << std::endl; + } + break; + } + case DTSC::DTSC_V2:{ + mediaTime = P.getTime(); + if (detail >= 2){ + std::cout << "DTSCv2 packet (Track " << P.getTrackId() << ", time " << P.getTime() + << "): " << P.getScan().toPrettyString() << std::endl; + } + break; + } + case DTSC::DTSC_HEAD:{ + if (detail >= 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;} + if (detail == 1){ + bool hasH264 = false; + bool hasAAC = false; JSON::Value result; - for (std::map::iterator it = m.tracks.begin(); it != m.tracks.end(); it++) { + std::stringstream issues; + DTSC::Meta M(P); + 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["kbits"] = (long long)((double)it->second.bps * 8 / 1024); + track["codec"] = it->second.codec; + uint32_t shrtest_key = 0xFFFFFFFFul; + uint32_t longest_key = 0; + uint32_t shrtest_prt = 0xFFFFFFFFul; + uint32_t longest_prt = 0; + uint32_t shrtest_cnt = 0xFFFFFFFFul; + uint32_t longest_cnt = 0; + for (std::deque::iterator k = it->second.keys.begin(); + k != it->second.keys.end(); k++){ + if (!k->getLength()){continue;} + if (k->getLength() > longest_key){longest_key = k->getLength();} + if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} + if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} + if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} + if (k->getParts()){ + if ((k->getLength() / k->getParts()) > longest_prt){ + longest_prt = (k->getLength() / k->getParts()); + } + if ((k->getLength() / k->getParts()) < shrtest_prt){ + shrtest_prt = (k->getLength() / k->getParts()); + } + } + } + track["keys"]["ms_min"] = (long long)shrtest_key; + track["keys"]["ms_max"] = (long long)longest_key; + track["keys"]["frame_ms_min"] = (long long)shrtest_prt; + track["keys"]["frame_ms_max"] = (long long)longest_prt; + track["keys"]["frames_min"] = (long long)shrtest_cnt; + track["keys"]["frames_max"] = (long long)longest_cnt; + if (longest_prt > 500){ + 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)! "; + } + if (it->second.codec == "AAC"){hasAAC = true;} + if (it->second.codec == "H264"){hasH264 = true;} + if (it->second.type == "video"){ + track["width"] = (long long)it->second.width; + track["height"] = (long long)it->second.height; 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") { + 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; + track["h264"]["profile"] = spsData.profile; + track["h264"]["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 ((hasAAC || hasH264) && M.tracks.size() > 1){ + if (!hasAAC){issues << "HLS no audio!";} + if (!hasH264){issues << "HLS no video!";} + } + if (issues.str().size()){result["issues"] = issues.str();} + std::cout << result.toString() << std::endl; + stop(); } - - if (m.vod || m.live) { m.toPrettyString(std::cout, 0, 0x03); } + break; } -} - -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; - } + case DTSC::DTCM:{ + if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;} + break; + } + default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break; } - totalBytes += F.getDataLen(); - - F.reInit(conn); - return totalBytes; + totalBytes += P.getDataLen(); + return true; } -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 index 7c1e9339..53ead4c5 100644 --- a/src/analysers/analyser_dtsc.h +++ b/src/analysers/analyser_dtsc.h @@ -1,19 +1,15 @@ -#include #include "analyser.h" #include -class dtscAnalyser : public analysers -{ - DTSC::Packet F; +class AnalyserDTSC : public Analyser{ +public: + AnalyserDTSC(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + +private: + DTSC::Packet P; 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 index 2eb3c11b..a65a346b 100644 --- a/src/analysers/analyser_flv.cpp +++ b/src/analysers/analyser_flv.cpp @@ -1,73 +1,40 @@ #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; - } +void AnalyserFLV::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "filter"; + opt["short"] = "F"; + opt["arg"] = "num"; + opt["default"] = "0"; + opt["help"] = + "Only print information about this tag type (8 = audio, 9 = video, 18 = meta, 0 = all)"; + conf.addOption("filter", opt); + opt.null(); +} +AnalyserFLV::AnalyserFLV(Util::Config &conf) : Analyser(conf){ 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; +bool AnalyserFLV::parsePacket(){ + if (feof(stdin)){ + stop(); + return false; } - 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; + while (!feof(stdin)){ + if (flvData.FileLoader(stdin)){break;} + if (feof(stdin)){ + stop(); + return false; } } - endTime = flvData.tagTime(); - return endTime; + // If we arrive here, we've loaded a FLV packet + if (!filter || filter == flvData.data[0]){ + DETAIL_MED("[%llu+%llu] %s", flvData.tagTime(), flvData.offset(), flvData.tagType().c_str()); + } + mediaTime = flvData.tagTime(); + return true; } -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 index 0abe16e6..940e6b40 100644 --- a/src/analysers/analyser_flv.h +++ b/src/analysers/analyser_flv.h @@ -1,18 +1,14 @@ -#include //FLV support -#include #include "analyser.h" +#include //FLV support -class flvAnalyser : public analysers -{ +class AnalyserFLV : public Analyser{ +public: + AnalyserFLV(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + +private: 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_h264.cpp b/src/analysers/analyser_h264.cpp new file mode 100644 index 00000000..dc6f55f3 --- /dev/null +++ b/src/analysers/analyser_h264.cpp @@ -0,0 +1,58 @@ +/// \file analyser_h264.cpp +/// Reads H264 data and prints it in human-readable format. + +#include "analyser_h264.h" +#include +#include +#include + +void AnalyserH264::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "size-prepended"; + opt["short"] = "S"; + opt["help"] = "Parse size-prepended style instead of Annex B style"; + conf.addOption("size-prepended", opt); + opt.null(); +} + +AnalyserH264::AnalyserH264(Util::Config &conf) : Analyser(conf){ + curPos = prePos = 0; + sizePrepended = conf.getBool("size-prepended"); +} + +bool AnalyserH264::parsePacket(){ + prePos = curPos; + // Read in smart bursts until we have enough data + while (isOpen() && dataBuffer.size() < neededBytes()){ + uint64_t needed = neededBytes(); + dataBuffer.reserve(needed); + for (uint64_t i = dataBuffer.size(); i < needed; ++i){ + dataBuffer += std::cin.get(); + ++curPos; + if (!std::cin.good()){dataBuffer.erase(dataBuffer.size() - 1, 1);} + } + } + + size_t size = 0; + h264::nalUnit *nalPtr = + h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended); + if (!nalPtr){ + FAIL_MSG("Could not read a NAL unit at position %llu", prePos); + return false; + } + HIGH_MSG("Read a %lu-byte NAL unit at position %llu", size, prePos); + dataBuffer.erase(0, size); // erase the NAL unit we just read + if (detail >= 2){nalPtr->toPrettyString(std::cout);} + ///\TODO update mediaTime with current timestamp + return true; +} + +uint64_t AnalyserH264::neededBytes(){ + // We buffer a megabyte if AnnexB + if (!sizePrepended){return 1024 * 1024;} + // otherwise, buffer the exact size needed + if (dataBuffer.size() < 4){return 4;} + return Bit::btohl(dataBuffer.data())+4; +} + diff --git a/src/analysers/analyser_h264.h b/src/analysers/analyser_h264.h new file mode 100644 index 00000000..ffbc0b8f --- /dev/null +++ b/src/analysers/analyser_h264.h @@ -0,0 +1,15 @@ +#include "analyser.h" + +class AnalyserH264 : public Analyser{ +public: + AnalyserH264(Util::Config &conf); + static void init(Util::Config &conf); + bool parsePacket(); + +private: + std::string dataBuffer; + uint64_t prePos, curPos; + uint64_t neededBytes(); + bool sizePrepended; +}; + diff --git a/src/analysers/analyser_hls.cpp b/src/analysers/analyser_hls.cpp index e9d451a1..a0cc641b 100644 --- a/src/analysers/analyser_hls.cpp +++ b/src/analysers/analyser_hls.cpp @@ -9,213 +9,175 @@ #include #include -// http://patchy.ddvtech.com:8080/hls/bbb/index.m3u8 -// +void AnalyserHLS::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "reconstruct"; + opt["short"] = "R"; + opt["arg"] = "string"; + opt["default"] = ""; + opt["help"] = "Reconstruct TS file from HLS to the given filename"; + conf.addOption("reconstruct", opt); + opt.null(); +} -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; +void AnalyserHLS::getParts(const std::string &body){ std::stringstream data(body); std::string line; - unsigned int start = 0; - unsigned int durat = 0; - do { - line = ""; + uint64_t no = 0; + float durat = 0; + refreshAt = Util::bootSecs() + 10; + while (data.good()){ 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; } + if (line.size() && *line.rbegin() == '\r'){line.resize(line.size() - 1);} + if (!line.size()){continue;} + if (line[0] != '#'){ + if (line.find("m3u") != std::string::npos){ + root = root.link(line); + INFO_MSG("Found a sub-playlist, re-targeting %s", root.getUrl().c_str()); + refreshAt = Util::bootSecs(); + return; + } + if (!parsedPart || no > parsedPart){ + HTTP::URL newURL = root.link(line); + INFO_MSG("Discovered: %s", newURL.getUrl().c_str()); + parts.push_back(HLSPart(newURL, no, durat)); + } + ++no; + }else{ + if (line.substr(0, 8) == "#EXTINF:"){durat = atof(line.c_str() + 8) * 1000;} + if (line.substr(0, 22) == "#EXT-X-MEDIA-SEQUENCE:"){no = atoll(line.c_str() + 22);} + if (line.substr(0, 14) == "#EXT-X-ENDLIST"){refreshAt = 0;} + if (line.substr(0, 22) == "#EXT-X-TARGETDURATION:" && refreshAt){ + refreshAt = Util::bootSecs() + atoll(line.c_str() + 22) / 2; } } - } 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()); +bool AnalyserHLS::open(const std::string &url){ + root = HTTP::URL(url); + if (root.protocol != "http"){ + FAIL_MSG("Only http protocol is supported (%s not supported)", root.protocol.c_str()); + return false; + } + return true; +} + +AnalyserHLS::AnalyserHLS(Util::Config &conf) : Analyser(conf){ + if (conf.getString("reconstruct") != ""){ + reconstruct.open(conf.getString("reconstruct").c_str()); + if (reconstruct.good()){ + WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str()); + } + } + hlsTime = 0; + parsedPart = 0; + refreshAt = Util::bootSecs(); +} + +/// Downloads the given URL into 'H', returns true on success. +/// Makes at most 5 attempts, and will wait no longer than 5 seconds without receiving data. +bool AnalyserHLS::download(const HTTP::URL &link){ + if (!link.host.size()){return false;} + INFO_MSG("Retrieving %s", link.getUrl().c_str()); + unsigned int loop = 6; // max 5 attempts + while (--loop){// loop while we are unsuccessful + H.Clean(); + // Reconnect if needed + if (!conn || link.host != connectedHost || link.getPort() != connectedPort){ + conn.close(); + connectedHost = link.host; + connectedPort = link.getPort(); + conn = Socket::Connection(connectedHost, connectedPort, true); + } + H.url = "/" + link.path; + if (link.port.size()){ + H.SetHeader("Host", link.host + ":" + link.port); + }else{ + H.SetHeader("Host", link.host); + } 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); + uint64_t reqTime = Util::bootSecs(); + while (conn && Util::bootSecs() < reqTime + 5){ + // No data? Wait for a second or so. + if (!conn.spool()){ + Util::sleep(1000); continue; - } else { - DEBUG_MSG(DLVL_FAIL, "Aborting further downloading"); - repeat = false; - break; + } + // Data! Check if we can parse it... + if (H.Read(conn)){ + return true; // Success! + } + // reset the 5 second timeout + reqTime = Util::bootSecs(); + } + if (conn){ + FAIL_MSG("Timeout while retrieving %s", link.getUrl().c_str()); + return false; + } + Util::sleep(500); // wait a bit before retrying + } + FAIL_MSG("Could not retrieve %s", link.getUrl().c_str()); + return false; +} + +bool AnalyserHLS::parsePacket(){ + while (isOpen()){ + // If needed, refresh the playlist + if (refreshAt && Util::bootSecs() >= refreshAt){ + if (download(root)){ + getParts(H.body); + }else{ + FAIL_MSG("Could not refresh playlist!"); + return false; } } - pos = part.start + part.dur; - if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) { - Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000); + + // If there are parts to download, get one. + if (parts.size()){ + HLSPart part = *parts.begin(); + parts.pop_front(); + if (!download(part.uri)){return false;} + if (H.GetHeader("Content-Length") != ""){ + if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())){ + FAIL_MSG("Expected %s bytes of data, but only received %lu.", + H.GetHeader("Content-Length").c_str(), H.body.size()); + return false; + } + } + if (H.body.size() % 188){ + FAIL_MSG("Expected a multiple of 188 bytes, received %d bytes", H.body.size()); + return false; + } + parsedPart = part.no; + hlsTime += part.dur; + mediaTime = (uint64_t)hlsTime; + if (reconstruct.good()){reconstruct << H.body;} + H.Clean(); + return true; } - lastDown = part.uri; - if (output) { std::cout << H.body; } - H.Clean(); + + // Hm. I guess we had no parts to get. + if (refreshAt && refreshAt > Util::bootSecs()){ + // We're getting a live stream. Let's wait and check again. + uint32_t sleepSecs = (refreshAt - Util::bootSecs()); + INFO_MSG("Sleeping for %lu seconds", sleepSecs); + Util::sleep(sleepSecs * 1000); + } + //The non-live case is already handled in isOpen() } - - return pos; + return false; } -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 index a4964986..b347f1c4 100644 --- a/src/analysers/analyser_hls.h +++ b/src/analysers/analyser_hls.h @@ -1,55 +1,37 @@ -//http://cattop:8080/hls/bunny/index.m3u8 -// -#include -#include -#include -#include -#include #include "analyser.h" +#include +#include -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 HLSPart{ +public: + HLSPart(const HTTP::URL &u, uint64_t n, float d) : uri(u), no(n), dur(d){} + HTTP::URL uri; + uint64_t no; + float dur; }; +class AnalyserHLS : public Analyser{ -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; +public: + AnalyserHLS(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + bool isOpen(); + bool open(const std::string &filename); + void stop(); +private: std::deque parts; + void getParts(const std::string &body); + HTTP::URL root; + float hlsTime; + uint64_t parsedPart; + uint64_t refreshAt; + HTTP::Parser H; + std::string connectedHost; + uint32_t connectedPort; + bool download(const HTTP::URL &link); Socket::Connection conn; - - std::string playlist; - bool repeat; - std::string lastDown; - unsigned int pos; - bool output; - + std::ofstream reconstruct; }; + diff --git a/src/analysers/analyser_mp4.cpp b/src/analysers/analyser_mp4.cpp index 8badb448..751d9ecc 100644 --- a/src/analysers/analyser_mp4.cpp +++ b/src/analysers/analyser_mp4.cpp @@ -1,71 +1,45 @@ -#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; +void AnalyserMP4::init(Util::Config &conf){ + Analyser::init(conf); } -int mp4Analyser::doAnalyse() { - DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); - std::cerr << mp4Data.toPrettyString(0) << std::endl; - - return dataSize; // endtime? +AnalyserMP4::AnalyserMP4(Util::Config &conf) : Analyser(conf){ + curPos = prePos = 0; } -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--; +bool AnalyserMP4::parsePacket(){ + prePos = curPos; + // Read in smart bursts until we have enough data + while (isOpen() && mp4Buffer.size() < neededBytes()){ + uint64_t needed = neededBytes(); + mp4Buffer.reserve(needed); + for (uint64_t i = mp4Buffer.size(); i < needed; ++i){ + mp4Buffer += std::cin.get(); + ++curPos; + if (!std::cin.good()){mp4Buffer.erase(mp4Buffer.size() - 1, 1);} + } } - return true; + if (mp4Data.read(mp4Buffer)){ + INFO_MSG("Read a box at position %d", prePos); + if (detail >= 2){std::cout << mp4Data.toPrettyString(0) << std::endl;} + ///\TODO update mediaTime with the current timestamp + return true; + } + FAIL_MSG("Could not read box at position %llu", prePos); + return false; } -bool mp4Analyser::packetReady() { - return mp4Data.read(mp4Buffer); +/// Calculates how many bytes we need to read a whole box. +uint64_t AnalyserMP4::neededBytes(){ + if (mp4Buffer.size() < 4){return 4;} + uint64_t size = ntohl(((int *)mp4Buffer.data())[0]); + if (size != 1){return size;} + if (mp4Buffer.size() < 16){return 16;} + size = 0 + ntohl(((int *)mp4Buffer.data())[2]); + size <<= 32; + size += ntohl(((int *)mp4Buffer.data())[3]); + return size; } -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 index 0495430f..b7171a98 100644 --- a/src/analysers/analyser_mp4.h +++ b/src/analysers/analyser_mp4.h @@ -1,23 +1,17 @@ -#include -#include #include "analyser.h" +#include +class AnalyserMP4 : public Analyser{ +public: + AnalyserMP4(Util::Config &conf); + static void init(Util::Config &conf); + bool parsePacket(); -class mp4Analyser : public analysers -{ +private: + uint64_t neededBytes(); 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(); + uint64_t curPos; + uint64_t prePos; }; + diff --git a/src/analysers/analyser_ogg.cpp b/src/analysers/analyser_ogg.cpp index 91f5fb37..72c45722 100644 --- a/src/analysers/analyser_ogg.cpp +++ b/src/analysers/analyser_ogg.cpp @@ -1,49 +1,121 @@ -#include #include "analyser_ogg.h" +#include -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; +/// \TODO EW EW EW EW EW EW EW EW EW EW EW +void AnalyserOGG::init(Util::Config &conf){ + Analyser::init(conf); } -void oggAnalyser::doValidate() -{ - std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl; -} +AnalyserOGG::AnalyserOGG(Util::Config &conf) : Analyser(conf){} -bool oggAnalyser::packetReady() -{ - return flvData.FileLoader(stdin); -} +bool AnalyserOGG::parsePacket(){ + if (!oggPage.read(stdin)){return false;} -int oggAnalyser::doAnalyse() -{ - if (analyse){ //always analyse..? - if (!filter || filter == flvData.data[0]){ - std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; + // We now have an Ogg page + // Print it, if we're at high detail level. + DETAIL_HI("%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 (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){ + sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis"; + } + if (memcmp("OpusHead", oggPage.getSegment(0), 8) == 0){ + sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus"; + } + if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){ + INFO_MSG("Bitstream %llu recognized as %s", oggPage.getBitstreamSerialNumber(), + sn2Codec[oggPage.getBitstreamSerialNumber()]); + }else{ + WARN_MSG("Bitstream %llu not recognized!", oggPage.getBitstreamSerialNumber()); } } - 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; + if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){ + if (detail >= 2){ + std::cout << " Theora data" << std::endl; + } + static unsigned int numParts = 0; + static unsigned int keyCount = 0; + for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){ + 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 + if (detail >= 3){ + 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); + } + } + }else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){ + 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; + } + 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 && detail >= 2){ + 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 && detail >= 3){ + 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{ + if (detail >= 4){ + std::cout << " " << Opus::Opus_prettyPacket(part, len) << std::endl; + } + } + offset += len; + } + } + return true; } diff --git a/src/analysers/analyser_ogg.h b/src/analysers/analyser_ogg.h new file mode 100644 index 00000000..a6ddfde8 --- /dev/null +++ b/src/analysers/analyser_ogg.h @@ -0,0 +1,16 @@ +#include "analyser.h" +#include + +class AnalyserOGG : public Analyser{ +public: + AnalyserOGG(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + +private: + std::map sn2Codec; + std::string oggBuffer; + OGG::Page oggPage; + int kfgshift; +}; + diff --git a/src/analysers/analyser_rtmp.cpp b/src/analysers/analyser_rtmp.cpp index 5410d254..c0bb7ce5 100644 --- a/src/analysers/analyser_rtmp.cpp +++ b/src/analysers/analyser_rtmp.cpp @@ -1,213 +1,186 @@ -#include +/// \file analyser_rtmp.cpp +/// Debugging tool for RTMP data. + #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(); +void AnalyserRTMP::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "reconstruct"; + opt["short"] = "R"; + opt["arg"] = "string"; + opt["default"] = ""; + opt["help"] = "Reconstruct FLV file from RTMP stream to the given filename"; + conf.addOption("reconstruct", opt); + opt.null(); +} +AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){ + if (conf.getString("reconstruct") != ""){ + reconstruct.open(conf.getString("reconstruct").c_str()); + if (reconstruct.good()){ + reconstruct.write(FLV::Header, 13); + WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str()); + } } - inbuffer.erase(0,3073); //strip the handshake part - - AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); - AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); +} +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..."); + std::string inbuffer; + inbuffer.reserve(3073); + while (std::cin.good() && inbuffer.size() < 3073){inbuffer += std::cin.get();} RTMPStream::rec_cnt += 3073; - - read_in = 0; - endTime = 0; + inbuffer.erase(0, 3073); // strip the handshake part + MEDIUM_MSG("Handshake skipped"); + return true; } -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 +bool AnalyserRTMP::parsePacket(){ + // While we can't parse a packet, + while (!next.Parse(strbuf)){ + // fill our internal buffer "strbuf" in (up to) 1024 byte chunks + if (std::cin.good()){ + unsigned int charCount = 0; + std::string tmpbuffer; + tmpbuffer.reserve(1024); + while (std::cin.good() && charCount < 1024){ + char newchar = std::cin.get(); if (std::cin.good()){ - 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(); + tmpbuffer += newchar; + ++read_in; + ++charCount; } } + strbuf.append(tmpbuffer); + }else{ + // if we can't fill the buffer, and have no parsable packet(s), return false + return false; + } } - return endTime; + // 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); + 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(), + 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()); + DETAIL_MED("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max); + break; + case 2: // abort message - we ignore this one + DETAIL_MED("CTRL: Abort message: %i", ntohl(*(int *)next.data.c_str())); + // 4 bytes of stream id to drop + break; + case 3: // ack + RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str()); + DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at); + break; + case 4:{ + short int ucmtype = ntohs(*(short int *)next.data.c_str()); + switch (ucmtype){ + case 0: + DETAIL_MED("CTRL: User control message: stream begin %u", + 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))); + break; + case 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", + ntohl(*(unsigned int *)(next.data.c_str() + 2))); + break; + case 4: + DETAIL_MED("CTRL: User control message: streamisrecorded %u", + ntohl(*(unsigned int *)(next.data.c_str() + 2))); + break; + case 6: + DETAIL_MED("CTRL: User control message: pingrequest %u", + ntohl(*(unsigned int *)(next.data.c_str() + 2))); + break; + case 7: + DETAIL_MED("CTRL: User control message: pingresponse %u", + ntohl(*(unsigned int *)(next.data.c_str() + 2))); + break; + case 31: + case 32: + // don't know, but not interesting anyway + break; + default: + DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", 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; + DETAIL_MED("CTRL: Window size: %i", 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) + DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size); + break; + case 8: + case 9: + if (detail >= 4 || reconstruct.good() || validate){ + F.ChunkLoader(next); + mediaTime = F.tagTime(); + DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str()); + if (reconstruct.good()){reconstruct.write(F.data, F.len);} + } + break; + case 15: DETAIL_MED("Received AFM3 data message"); break; + case 16: DETAIL_MED("Received AFM3 shared object"); break; + case 17:{ + DETAIL_MED("Received AFM3 command message:"); + char soort = next.data[0]; + next.data = next.data.substr(1); + if (soort == 0){ + amfdata = AMF::parse(next.data); + DETAIL_MED("%s", amfdata.Print().c_str()); + }else{ + amf3data = AMF::parse3(next.data); + DETAIL_MED("%s", amf3data.Print().c_str()); + } + }break; + case 18:{ + DETAIL_MED("Received AFM0 data message (metadata):"); + amfdata = AMF::parse(next.data); + DETAIL_MED("%s", amfdata.Print().c_str()); + if (reconstruct.good()){ + F.ChunkLoader(next); + reconstruct.write(F.data, F.len); + } + }break; + case 19: DETAIL_MED("Received AFM0 shared object"); break; + case 20:{// AMF0 command message + DETAIL_MED("Received AFM0 command message:"); + amfdata = AMF::parse(next.data); + DETAIL_MED("%s", amfdata.Print().c_str()); + }break; + case 22: + if (reconstruct.good()){reconstruct << next.data;} + break; + default: + FAIL_MSG( + "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data."); + return false; + break; + }// switch for type of chunk + return true; } -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 index c1845ee3..91a020a0 100644 --- a/src/analysers/analyser_rtmp.h +++ b/src/analysers/analyser_rtmp.h @@ -1,35 +1,22 @@ -#include //FLV support -#include #include "analyser.h" - -#include #include -#include -#include +#include //FLV support #include -#include -#include -class rtmpAnalyser : public analysers -{ - FLV::Tag flvData; - long long filter; +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;/// +#include +#include +#include #include -#include "analyser_rtp.h" +#include +#include +#include +#include +#include +#include +#include -rtpAnalyser::rtpAnalyser(Util::Config config) : analysers(config) -{ - std::cout << "rtp constr" << std::endl; - Socket::Connection conn("localhost", 554, true); - step = 0; - trackIt = 0; +//rtsp://krabs:1935/vod/gear1.mp4 -} - -void rtpAnalyser::doValidate() -{ -} - -bool rtpAnalyser::packetReady() -{ - return conn.connected(); -} - -int rtpAnalyser::doAnalyse() -{ - if (analyse){ //always analyse..? - +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. + int step = 0; + /*1 = sent describe + 2 = recd describe + 3 = sent setup + 4 = received setup + 5 = sent play"*/ + std::vector tracks; + std::vector connections; + unsigned int trackIt = 0; + while (conn.connected()){ // 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.url = "rtsp://localhost/steers"; + //HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4"; HTTP_S.SetHeader("CSeq",1); HTTP_S.SendRequest(conn); step++; @@ -186,19 +190,20 @@ int rtpAnalyser::doAnalyse() } } } + } + return 666; } - return endTime; + + + + } + + 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.parseArgs(argc, argv); - - rtpAnalyser A(conf); - - A.Run(); - - return 0; + return Analysers::analyseRTP(); } diff --git a/src/analysers/rtsp_rtp_analyser.cpp b/src/analysers/analyser_rtsp.cpp similarity index 100% rename from src/analysers/rtsp_rtp_analyser.cpp rename to src/analysers/analyser_rtsp.cpp diff --git a/src/analysers/analyser_rtp.h b/src/analysers/analyser_rtsp.h similarity index 100% rename from src/analysers/analyser_rtp.h rename to src/analysers/analyser_rtsp.h diff --git a/src/analysers/analyser_ts.cpp b/src/analysers/analyser_ts.cpp index e2add450..1a1216bb 100644 --- a/src/analysers/analyser_ts.cpp +++ b/src/analysers/analyser_ts.cpp @@ -9,172 +9,144 @@ #include #include #include +#include #include #include #include #include #include #include -#include -tsAnalyser::tsAnalyser(Util::Config config) : analysers(config) { - upTime = Util::bootSecs(); - pcr = 0; +void AnalyserTS::init(Util::Config &conf){ + Analyser::init(conf); + JSON::Value opt; + opt["long"] = "detail"; + opt["short"] = "D"; + opt["arg"] = "num"; + opt["default"] = 3ll; + opt["help"] = "Detail level of analysis bitmask (default=3). 1 = PES, 2 = TS non-stream pkts, 4 " + "= TS stream pkts, 32 = raw PES packet bytes, 64 = raw TS packet bytes"; + conf.addOption("detail", opt); + opt.null(); + opt["long"] = "pid"; + opt["short"] = "P"; + opt["arg"] = "num"; + opt["default"] = 0ll; + opt["help"] = "Only use the given PID, ignore others"; + conf.addOption("pid", opt); + opt.null(); +} + +AnalyserTS::AnalyserTS(Util::Config &conf) : Analyser(conf){ + pidOnly = conf.getInteger("pid"); 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++; - } - +bool AnalyserTS::parsePacket(){ + static char packetPtr[188]; std::cin.read(packetPtr, 188); -//0x47 - if (std::cin.gcount() != 188) { return 0; } + if (std::cin.gcount() != 188){return false;} + DONTEVEN_MSG("Reading from position %llu", bytes); 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.FromPointer(packetPtr)){return false;} + if (detail){ + 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()); } + payloads.erase(packet.getPID()); } - 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; + if (packet.getPID() == 0){((TS::ProgramAssociationTable *)&packet)->parsePIDs();} + if (packet.isPMT()){((TS::ProgramMappingTable *)&packet)->parseStreams();} + if ((((detail & 2) && !packet.isStream()) || ((detail & 4) && packet.isStream())) && + (!pidOnly || packet.getPID() == pidOnly)){ + std::cout << packet.toPrettyString(0, detail); + } + if (packet.getPID() >= 0x10 && !packet.isPMT() && packet.getPID() != 17 && + (payloads[packet.getPID()].size() || packet.getUnitStart())){ + payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength()); } - bytes = 0; } - - return endTime; + if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()){ + mediaTime = packet.getPCR() / 27000; + } + return true; } -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); +AnalyserTS::~AnalyserTS(){ + for (std::map::iterator it = payloads.begin(); + it != payloads.end(); it++){ + if ((detail & 1) && (!pidOnly || it->first == pidOnly)){ + std::cout << printPES(it->second, it->first); + } } } -std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int detailLevel) { +std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){ unsigned int headSize = 0; std::stringstream res; bool known = false; res << "[PES " << PID << "]"; - if ((d[3] & 0xF0) == 0xE0) { + if ((d[3] & 0xF0) == 0xE0){ res << " [Video " << (int)(d[3] & 0xF) << "]"; known = true; } - if (!known && (d[3] & 0xE0) == 0xC0) { + 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] << " ]"; } + 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) { + 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 { + }else{ res << " [Copy]"; } int timeFlags = ((d[7] & 0xC0) >> 6); - if (timeFlags == 2) { headSize += 5; } - if (timeFlags == 3) { headSize += 10; } - if (d[7] & 0x20) { + 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) { + 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) { + if (d[7] & 0x08){ res << " [Trick mode present, not decoded!]"; headSize += 1; } - if (d[7] & 0x04) { + if (d[7] & 0x04){ res << " [Add. copy present, not decoded!]"; headSize += 1; } - if (d[7] & 0x02) { + if (d[7] & 0x02){ res << " [CRC present, not decoded!]"; headSize += 2; } - if (d[7] & 0x01) { + if (d[7] & 0x01){ res << " [Extension present, not decoded!]"; headSize += 0; /// \todo Implement this. Complicated field, bah. } - if (d[8] != headSize) { + if (d[8] != headSize){ padding = d[8] - headSize; res << " [Padding: " << padding << "b]"; } - if (timeFlags & 0x02) { + 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); @@ -182,7 +154,7 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F); res << " [PTS " << ((double)time / 90000) << "s]"; } - if (timeFlags & 0x01) { + if (timeFlags & 0x01){ long long unsigned int time = ((d[14] >> 1) & 0x07); time <<= 15; time |= ((int)d[15] << 7) | (d[16] >> 1); @@ -191,21 +163,26 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de 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) << "]"; } + if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){ + res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]"; + }else{ + res << " [Size " << (d.size() - 6) << "]"; + } res << std::endl; - if (detailLevel == 10) { + if (detail & 32){ 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) { + 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; } + 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 index 73c602db..0d060c21 100644 --- a/src/analysers/analyser_ts.h +++ b/src/analysers/analyser_ts.h @@ -1,27 +1,18 @@ +#include "analyser.h" #include #include -#include "analyser.h" -class tsAnalyser : public analysers -{ - long long filter; - +class AnalyserTS : public Analyser{ +public: + AnalyserTS(Util::Config &conf); + ~AnalyserTS(); + bool parsePacket(); + static void init(Util::Config &conf); + std::string printPES(const std::string &d, unsigned long PID); +private: std::map payloads; + uint32_t pidOnly; 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); + uint64_t bytes; }; + diff --git a/src/analysers/dtsc_analyser.cpp b/src/analysers/dtsc_analyser.cpp deleted file mode 100644 index 19cad83d..00000000 --- a/src/analysers/dtsc_analyser.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/// \file dtsc_analyser.cpp -/// Reads an DTSC file and prints all readable data about it - -#include -#include -#include - -#include -#include -#include -#include -#include - -///\brief Holds everything unique to the analysers. -namespace Analysers { - ///\brief Debugging tool for DTSC data. - /// - /// Expects DTSC data in a file given on the command line, outputs human-readable information to stderr. - ///\param conf The configuration parsed from the commandline. - ///\return The return code of the analyser. - int analyseDTSC(Util::Config conf){ - DTSC::File F(conf.getString("filename")); - if (!F){ - std::cerr << "Not a valid DTSC file" << std::endl; - return 1; - } - - if (conf.getBool("compact")){ - bool hasH264 = false; - bool hasAAC = false; - JSON::Value result; - std::stringstream issues; - for (std::map::iterator it = F.getMeta().tracks.begin(); it != F.getMeta().tracks.end(); it++){ - JSON::Value track; - track["kbits"] = (long long)((double)it->second.bps * 8 / 1024); - track["codec"] = it->second.codec; - uint32_t shrtest_key = 0xFFFFFFFFul; - uint32_t longest_key = 0; - uint32_t shrtest_prt = 0xFFFFFFFFul; - uint32_t longest_prt = 0; - uint32_t shrtest_cnt = 0xFFFFFFFFul; - uint32_t longest_cnt = 0; - for (std::deque::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){ - if (!k->getLength()){continue;} - if (k->getLength() > longest_key){longest_key = k->getLength();} - if (k->getLength() < shrtest_key){shrtest_key = k->getLength();} - if (k->getParts() > longest_cnt){longest_cnt = k->getParts();} - if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();} - if ((k->getLength()/k->getParts()) > longest_prt){longest_prt = (k->getLength()/k->getParts());} - if ((k->getLength()/k->getParts()) < shrtest_prt){shrtest_prt = (k->getLength()/k->getParts());} - } - track["keys"]["ms_min"] = (long long)shrtest_key; - track["keys"]["ms_max"] = (long long)longest_key; - track["keys"]["frame_ms_min"] = (long long)shrtest_prt; - track["keys"]["frame_ms_max"] = (long long)longest_prt; - track["keys"]["frames_min"] = (long long)shrtest_cnt; - track["keys"]["frames_max"] = (long long)longest_cnt; - if (longest_prt > 500){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)! ";} - if (it->second.codec == "AAC"){hasAAC = true;} - if (it->second.codec == "H264"){hasH264 = true;} - if (it->second.type=="video"){ - track["width"] = (long long)it->second.width; - track["height"] = (long long)it->second.height; - track["fpks"] = it->second.fpks; - if (it->second.codec == "H264"){ - h264::sequenceParameterSet sps; - sps.fromDTSCInit(it->second.init); - h264::SPSMeta spsData = sps.getCharacteristics(); - track["h264"]["profile"] = spsData.profile; - track["h264"]["level"] = spsData.level; - } - } - result[it->second.getWritableIdentifier()] = track; - } - if ((hasAAC || hasH264) && F.getMeta().tracks.size() > 1){ - if (!hasAAC){issues << "HLS no audio!";} - if (!hasH264){issues << "HLS no video!";} - } - if (issues.str().size()){result["issues"] = issues.str();} - std::cout << result.toString(); - return 0; - } - - if (F.getMeta().vod || F.getMeta().live){ - F.getMeta().toPrettyString(std::cout,0, 0x03); - } - - int bPos = 0; - F.seek_bpos(0); - F.parseNext(); - while (F.getPacket()){ - switch (F.getPacket().getVersion()){ - case DTSC::DTSC_V1: { - std::cout << "DTSCv1 packet: " << F.getPacket().getScan().toPrettyString() << std::endl; - break; - } - case DTSC::DTSC_V2: { - std::cout << "DTSCv2 packet (Track " << F.getPacket().getTrackId() << ", time " << F.getPacket().getTime() << "): " << F.getPacket().getScan().toPrettyString() << std::endl; - break; - } - case DTSC::DTSC_HEAD: { - std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl; - break; - } - case DTSC::DTCM: { - std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl; - break; - } - default: - DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos); - break; - } - bPos = F.getBytePos(); - F.parseNext(); - } - return 0; - } -} - -/// Reads an DTSC file and prints all readable data about it -int main(int argc, char ** argv){ - Util::Config conf = Util::Config(argv[0]); - conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the DTSC file to analyse.\"}")); - conf.addOption("compact", JSON::fromString("{\"short\": \"c\", \"long\": \"compact\", \"help\":\"Filename of the DTSC file to analyse.\"}")); - conf.parseArgs(argc, argv); - return Analysers::analyseDTSC(conf); -} //main diff --git a/src/analysers/flv_analyser.cpp b/src/analysers/flv_analyser.cpp deleted file mode 100644 index b5e31967..00000000 --- a/src/analysers/flv_analyser.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/// \file flv_analyser.cpp -/// Contains the code for the FLV Analysing tool. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include //FLV support -#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. -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); - - return Analysers::analyseFLV(conf); -} - - diff --git a/src/analysers/h264_analyser.cpp b/src/analysers/h264_analyser.cpp deleted file mode 100644 index 1f308a5f..00000000 --- a/src/analysers/h264_analyser.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/// \file h264_analyser.cpp -/// Reads an H264 file and prints all readable data about it - -#include -#include -#include - -#include -#include -#include -#include -#include - -int main(int argc, char ** argv){ - Util::Config conf = Util::Config(argv[0]); - conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Full path of the file to analyse.\"}")); - conf.parseArgs(argc, argv); - FILE * F = fopen(conf.getString("filename").c_str(), "r+b"); - if (!F){ - FAIL_MSG("No such file"); - } - - h264::nalUnit * nalPtr = h264::nalFactory(F); - while (nalPtr){ - nalPtr->toPrettyString(std::cout); - nalPtr = h264::nalFactory(F); - } - return 0; -} - diff --git a/src/analysers/hls_analyser.cpp b/src/analysers/hls_analyser.cpp deleted file mode 100644 index 0539bd5f..00000000 --- a/src/analysers/hls_analyser.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/// \file hls_analyser.cpp -/// Contains the code for the HLS Analysing tool. - -#include -#include -#include -#include -#include - -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; -}; - -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; -} - -int main(int argc, char ** argv) { - Util::Config conf = Util::Config(argv[0]); - conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}")); - conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to HLS stream index file to retrieve.\"}")); - conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", \"arg\":\"integer\", \"default\":-1, \"help\":\"Abort after this many seconds of downloading. Negative values mean unlimited, which is the default.\"}")); - conf.parseArgs(argc, argv); - conf.activate(); - - unsigned int port = 80; - std::string url = conf.getString("url"); - if (url.substr(0, 7) != "http://") { - DEBUG_MSG(DLVL_FAIL, "The URL must start with http://"); - return -1; - } - url = url.substr(7); - - 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"); - - std::deque parts; - Socket::Connection conn; - - std::string playlist = url; - bool repeat = false; - std::string lastDown = ""; - unsigned int pos = 0; - bool output = (conf.getString("mode") == "output"); - - do { - repeat = false; - while (url.size() > 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; - } - 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(); - } - } while (repeat); - DEBUG_MSG(DLVL_INFO, "mode: %s", conf.getString("mode").c_str()); - 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/mist_analyse.cpp b/src/analysers/mist_analyse.cpp new file mode 100644 index 00000000..1957b2dd --- /dev/null +++ b/src/analysers/mist_analyse.cpp @@ -0,0 +1,16 @@ +#include ANALYSERHEADER +#include +#include + +int main(int argc, char *argv[]){ + Util::Config conf(argv[0]); + ANALYSERTYPE::init(conf); + if (conf.parseArgs(argc, argv)){ + conf.activate(); + ANALYSERTYPE A(conf); + return A.run(conf); + }else{ + return 2; + } +} + diff --git a/src/analysers/mp4_analyser.cpp b/src/analysers/mp4_analyser.cpp deleted file mode 100644 index 0e758621..00000000 --- a/src/analysers/mp4_analyser.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/// \file mp4_analyser.cpp -/// Debugging tool for MP4 data. -/// Expects MP4 data through stdin, outputs human-readable information to stderr. - -#include -#include -#include -#include -#include -#include -#include -#include - -///\brief Holds everything unique to the analysers. -namespace Analysers { - ///\brief Debugging tool for MP4 data. - /// - /// Expects MP4 data through stdin, outputs human-readable information to stderr. - ///\return The return code of the analyser. - 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()){ - mp4Buffer += std::cin.get(); - } - 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; - } -} - -/// Debugging tool for MP4 data. -/// 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(conf); -} - diff --git a/src/analysers/ogg_analyser.cpp b/src/analysers/ogg_analyser.cpp deleted file mode 100644 index a57f9948..00000000 --- a/src/analysers/ogg_analyser.cpp +++ /dev/null @@ -1,492 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -///\todo rewrite this analyser. -namespace Analysers{ - std::string Opus_prettyPacket(const char * part,int len){ - if (len < 1){ - return "Invalid packet (0 byte length)"; - } - std::stringstream r; - char config = part[0] >> 3; - char code = part[0] & 3; - if ((part[0] & 4) == 4){ - r << "Stereo, "; - } else { - r << "Mono, "; - } - if (config < 14){ - r << "SILK, "; - if (config < 4){ - r << "NB, "; - } - if (config < 8 && config > 3){ - r << "MB, "; - } - if (config < 14 && config > 7){ - r << "WB, "; - } - if (config % 4 == 0){ - r << "10ms"; - } - if (config % 4 == 1){ - r << "20ms"; - } - if (config % 4 == 2){ - r << "40ms"; - } - if (config % 4 == 3){ - r << "60ms"; - } - } - if (config < 16 && config > 13){ - r << "Hybrid, "; - if (config < 14){ - r << "SWB, "; - } else { - r << "FB, "; - } - if (config % 2 == 0){ - r << "10ms"; - } else { - r << "20ms"; - } - } - if (config > 15){ - r << "CELT, "; - if (config < 20){ - r << "NB, "; - } - if (config < 24 && config > 19){ - r << "WB, "; - } - if (config < 28 && config > 23){ - r << "SWB, "; - } - if (config > 27){ - r << "FB, "; - } - if (config % 4 == 0){ - r << "2.5ms"; - } - if (config % 4 == 1){ - r << "5ms"; - } - if (config % 4 == 2){ - r << "10ms"; - } - if (config % 4 == 3){ - r << "20ms"; - } - } - if (code == 0){ - r << ": 1 packet (" << (len - 1) << "b)"; - return r.str(); - } - if (code == 1){ - r << ": 2 packets (" << ((len - 1) / 2) << "b / " << ((len - 1) / 2) << "b)"; - return r.str(); - } - if (code == 2){ - if (len < 2){ - return "Invalid packet (code 2 must be > 1 byte long)"; - } - if (part[1] < 252){ - r << ": 2 packets (" << (int)part[1] << "b / " << (int)(len - 2 - part[1]) << "b)"; - } else { - int ilen = part[1] + part[2] * 4; - r << ": 2 packets (" << ilen << "b / " << (int)(len - 3 - ilen) << "b)"; - } - return r.str(); - } - //code 3 - bool VBR = (part[1] & 128) == 128; - bool pad = (part[1] & 64) == 64; - bool packets = (part[1] & 63); - r << ": " << packets << " packets (VBR = " << VBR << ", padding = " << pad << ")"; - 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; - OGG::Page oggPage; - int kfgshift; - //Read all of std::cin to oggBuffer - //while OGG::page check function read - 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 (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){ - sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis"; - } - 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 validateOGG(bool analyse){ - std::map sn2Codec; - std::string oggBuffer; - OGG::Page oggPage; - - 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 (std::cin.good()){ - oggBuffer.reserve(1024); - - for (unsigned int i = 0; (i < 1024) && (std::cin.good()); i++){ - oggBuffer += std::cin.get(); - } - - while (oggPage.read(oggBuffer)){//reading ogg to ogg::page - if(oggMap.find(oggPage.getBitstreamSerialNumber()) == oggMap.end()){ - //checking header - //check if vorbis or theora - if (memcmp(oggPage.getSegment(0)+1, "vorbis", 6) == 0){ - sn2Codec[oggPage.getBitstreamSerialNumber()] = "vorbis"; - vorbis::header vheader((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0)); - //if (vheader){ - oggMap[oggPage.getBitstreamSerialNumber()] = ntohl(vheader.getAudioSampleRate()); - //oggPage.setInternalCodec(sn2Codec[oggPage.getBitstreamSerialNumber()]); - //} - }else if(memcmp(oggPage.getSegment(0)+1, "theora", 6) == 0){ - sn2Codec[oggPage.getBitstreamSerialNumber()] = "theora"; - 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 (sn2Codec[oggPage.getBitstreamSerialNumber()] == "vorbis"){ - // std::cout <>(int)theader->getKFGShift())*mspft); - } - } - if(analyse){ - std::cout << oggPage.toPrettyString() << std::endl; - } - } - //while OGG::page check function read - //save last time0 - sysinfo(&sinfo); - long long int tTime = sinfo.uptime; - if((tTime-upTime) > 5 && (tTime-upTime)>(int)(lastTime) ){ - std::cerr << "data received too slowly" << std::endl; - return 42; - } - } - 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 - return 0; - } -} - -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\":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"); - } -} - - diff --git a/src/analysers/rtmp_analyser.cpp b/src/analysers/rtmp_analyser.cpp deleted file mode 100644 index 766d0de7..00000000 --- a/src/analysers/rtmp_analyser.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/// \file rtmp_analyser.cpp -/// Debugging tool for RTMP data. -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#define DETAIL_RECONSTRUCT 1 -#define DETAIL_EXPLICIT 2 -#define DETAIL_VERBOSE 4 - -///\brief Holds everything unique to the analysers. -namespace Analysers { - ///\brief Debugging tool for RTMP data. - /// - ///Expects RTMP data of one side of the conversation through stdin, outputs human-readable information to stderr. - /// - ///Will output FLV file to stdout, if available. - /// - ///Automatically skips the handshake data. - ///\param conf The configuration parsed from the commandline. - ///\return The return code of the analyser. - int analyseRTMP(Util::Config conf){ - int Detail = conf.getInteger("detail"); - if (Detail > 0){ - fprintf(stderr, "Detail level set:\n"); - if (Detail & DETAIL_RECONSTRUCT){ - fprintf(stderr, " - Will reconstuct FLV file to stdout\n"); - std::cout.write(FLV::Header, 13); - } - if (Detail & DETAIL_EXPLICIT){ - fprintf(stderr, " - Will list explicit video/audio data information\n"); - } - if (Detail & DETAIL_VERBOSE){ - fprintf(stderr, " - Will list verbose chunk information\n"); - } - } - - std::string inbuffer; - inbuffer.reserve(3073); - while (std::cin.good() && inbuffer.size() < 3073){ - inbuffer += std::cin.get(); - } //read all of std::cin to temp - inbuffer.erase(0, 3073); //strip the handshake part - RTMPStream::rec_cnt += 3073; - RTMPStream::Chunk next; - FLV::Tag F; //FLV holder - AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER); - AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER); - unsigned int read_in = 0; - Socket::Buffer strbuf; - - while (std::cin.good() || strbuf.size()){ - 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(); - } - } - }//while std::cin.good() - fprintf(stderr, "No more readable data\n"); - return 0; - } -} - -int main(int argc, char ** argv){ - Util::Config conf = Util::Config(argv[0]); - conf.addOption("detail", - JSON::fromString( - "{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}")); - conf.parseArgs(argc, argv); - - return Analysers::analyseRTMP(conf); -} //main diff --git a/src/analysers/rtp_analyser.cpp b/src/analysers/rtp_analyser.cpp deleted file mode 100644 index 801a232b..00000000 --- a/src/analysers/rtp_analyser.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//rtsp://krabs:1935/vod/gear1.mp4 - -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. - int step = 0; - /*1 = sent describe - 2 = recd describe - 3 = sent setup - 4 = received setup - 5 = sent play"*/ - std::vector tracks; - std::vector connections; - unsigned int trackIt = 0; - while (conn.connected()){ - // 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace Analysers { - std::string 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) << "]"; - }else{ - res << " [Size " << (d.size() - 6) << "]"; - } - res << std::endl; - - if(detailLevel&32){ - unsigned int counter = 0; - for (unsigned int i = 9+headSize+padding; i payloads; - TS::Packet packet; - long long int upTime = Util::bootSecs(); - int64_t pcr = 0; - uint64_t bytes = 0; - char packetPtr[188]; - while (std::cin.good()){ - std::cin.read(packetPtr,188); - if(std::cin.gcount() != 188){break;} - DONTEVEN_MSG("Reading from position %llu", bytes); - bytes += 188; - if(packet.FromPointer(packetPtr)){ - if(analyse){ - if (packet.getUnitStart() && payloads.count(packet.getPID()) && payloads[packet.getPID()] != ""){ - if ((detailLevel&1) && (!pidOnly || packet.getPID() == pidOnly)){ - std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel); - } - payloads.erase(packet.getPID()); - } - if (packet.getPID() == 0){ - ((TS::ProgramAssociationTable*)&packet)->parsePIDs(); - } - if (packet.isPMT()){ - ((TS::ProgramMappingTable*)&packet)->parseStreams(); - } - if ((((detailLevel & 2) && !packet.isStream()) || ((detailLevel & 4) && packet.isStream())) && (!pidOnly || packet.getPID() == pidOnly)){ - std::cout << packet.toPrettyString(0, detailLevel); - } - if (packet.getPID() >= 0x10 && !packet.isPMT() && packet.getPID()!=17 && (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; - } - } - for (std::map::iterator it = payloads.begin(); it != payloads.end(); it++){ - if ((detailLevel&1) && (!pidOnly || it->first == pidOnly)){ - std::cout << printPES(it->second, it->first, detailLevel); - } - } - long long int finTime = Util::bootSecs(); - if(validate){ - 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); - } - return 0; - } -} - -int main(int argc, char ** argv){ - Util::Config conf = Util::Config(argv[0]); - 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("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("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis bitmask (default=3). 1 = PES, 2 = TS non-stream pkts, 4 = TS stream pkts, 32 = raw PES packet bytes, 64 = raw TS packet bytes\"}")); - conf.addOption("pid", JSON::fromString("{\"long\":\"pid\", \"short\":\"P\", \"arg\":\"num\", \"default\":0, \"help\":\"Only use at given PID, ignore others\"}")); - conf.parseArgs(argc, argv); - return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail"),conf.getInteger("pid")); -} diff --git a/src/analysers/tsstream_analyser.cpp b/src/analysers/tsstream_analyser.cpp deleted file mode 100755 index a0f7da0a..00000000 --- a/src/analysers/tsstream_analyser.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace Analysers { - /// Debugging tool for TS data. - /// Expects TS data through stdin, outputs human-readable information to stderr. - /// \return The return code of the analyser. - int analyseTS(bool validate, bool analyse, int detailLevel){ - TS::Stream tsStream; - std::map payloads; - TS::Packet packet; - long long int upTime = Util::bootSecs(); - int64_t pcr = 0; - unsigned int bytes = 0; - char packetPtr[188]; - while (std::cin.good()){ - std::cin.read(packetPtr,188); - if(std::cin.gcount() != 188){break;} - bytes += 188; - if(packet.FromPointer(packetPtr)){ - //std::cout << packet.toPrettyString(); - tsStream.parse(packet, bytes); - if (tsStream.hasPacket(packet.getPID())){ - DTSC::Packet dtscPack; - tsStream.getPacket(packet.getPID(), dtscPack); - std::cout << dtscPack.toJSON().toPrettyString(); - } - } - } - return 0; - } -} - -int main(int argc, char ** argv){ - Util::Config conf = Util::Config(argv[0]); - 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("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("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis.\"}")); - conf.parseArgs(argc, argv); - return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail")); -} diff --git a/src/utils/util_amf.cpp b/src/utils/util_amf.cpp new file mode 100644 index 00000000..b7b44e4e --- /dev/null +++ b/src/utils/util_amf.cpp @@ -0,0 +1,47 @@ +/// \file util_amf.cpp +/// Debugging tool for AMF data. + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv){ + Util::Config conf(argv[0]); + JSON::Value opt; + opt["arg_num"] = 1ll; + opt["arg"] = "string"; + opt["default"] = "-"; + opt["help"] = "Filename to analyse, or - for standard input (default)"; + conf.addOption("filename", opt); + conf.parseArgs(argc, argv); + + std::string filename = conf.getString("filename"); + if (filename.size() && filename != "-"){ + int fp = open(filename.c_str(), O_RDONLY); + if (fp <= 0){ + FAIL_MSG("Cannot open '%s': %s", filename.c_str(), strerror(errno)); + return 1; + } + dup2(fp, STDIN_FILENO); + close(fp); + INFO_MSG("Parsing %s...", filename.c_str()); + }else{ + INFO_MSG("Parsing standard input..."); + } + + std::string amfBuffer; + // Read all of std::cin to amfBuffer + while (std::cin.good()){amfBuffer += std::cin.get();} + // Strip the invalid last character + amfBuffer.erase((amfBuffer.end() - 1)); + // Parse into an AMF::Object + AMF::Object amfData = AMF::parse(amfBuffer); + // Print the output. + std::cout << amfData.Print() << std::endl; + return 0; +} + diff --git a/src/analysers/load_analyser.cpp b/src/utils/util_load.cpp similarity index 100% rename from src/analysers/load_analyser.cpp rename to src/utils/util_load.cpp diff --git a/src/analysers/rax_analyser.cpp b/src/utils/util_rax.cpp similarity index 100% rename from src/analysers/rax_analyser.cpp rename to src/utils/util_rax.cpp diff --git a/src/analysers/stats_analyser.cpp b/src/utils/util_stats.cpp similarity index 100% rename from src/analysers/stats_analyser.cpp rename to src/utils/util_stats.cpp