diff --git a/CMakeLists.txt b/CMakeLists.txt index a2905ae4..02f30eab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ set(libHeaders ${SOURCE_DIR}/lib/ts_packet.h ${SOURCE_DIR}/lib/util.h ${SOURCE_DIR}/lib/vorbis.h + ${SOURCE_DIR}/lib/opus.h ) ######################################## @@ -164,6 +165,7 @@ set(libSources ${SOURCE_DIR}/lib/ts_packet.cpp ${SOURCE_DIR}/lib/util.cpp ${SOURCE_DIR}/lib/vorbis.cpp + ${SOURCE_DIR}/lib/opus.cpp ) ######################################## @@ -202,11 +204,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 @@ -220,10 +227,27 @@ endmacro() makeAnalyser(RTMP rtmp) makeAnalyser(FLV flv) makeAnalyser(DTSC dtsc) -makeAnalyser(AMF amf) makeAnalyser(MP4 mp4) makeAnalyser(OGG ogg) -makeAnalyser(RAX rax) + +######################################## +# 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(RAX rax) +makeUtil(AMF amf) ######################################## # MistServer - Inputs # diff --git a/lib/opus.cpp b/lib/opus.cpp new file mode 100644 index 00000000..d1ad58f5 --- /dev/null +++ b/lib/opus.cpp @@ -0,0 +1,75 @@ +#include "opus.h" +#include + +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 ba581507..00000000 --- a/src/analysers/amf_analyser.cpp +++ /dev/null @@ -1,40 +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 - -///\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(){ - 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.parseArgs(argc, argv); - - return Analysers::analyseAMF(); -} - diff --git a/src/analysers/analyser.cpp b/src/analysers/analyser.cpp new file mode 100644 index 00000000..79aa02ee --- /dev/null +++ b/src/analysers/analyser.cpp @@ -0,0 +1,80 @@ +#include "analyser.h" +#include +#include + +/// Reads configuration and opens a passed filename replacing standard input if needed. +Analyser::Analyser(Util::Config &conf){ + detail = conf.getInteger("detail"); + mediaTime = 0; + upTime = Util::bootSecs(); + isActive = &conf.is_active; +} + +///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; +} + +/// Stops analysis by closing the standard input +void Analyser::stop(){ + close(STDIN_FILENO); + std::cin.setstate(std::ios_base::eofbit); +} + +///Checks if standard input is still valid. +bool Analyser::isOpen(){ + return (*isActive) && std::cin.good(); +} + +/// 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; + } + } + return 0; +} + +/// 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; + + 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(); + + 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 new file mode 100644 index 00000000..6c2cbca1 --- /dev/null +++ b/src/analysers/analyser.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include +#include + +#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__);} + +class Analyser{ +public: + // These contain standard functionality + Analyser(Util::Config &conf); + 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 + 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 new file mode 100644 index 00000000..2804eb1e --- /dev/null +++ b/src/analysers/analyser_dtsc.cpp @@ -0,0 +1,124 @@ +#include "analyser_dtsc.h" +#include + +void AnalyserDTSC::init(Util::Config &conf){ + Analyser::init(conf); +} + +AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){ + conn = Socket::Connection(0, fileno(stdin)); + totalBytes = 0; +} + +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; + } + + 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; + std::stringstream issues; + DTSC::Meta M(P); + for (std::map::iterator it = M.tracks.begin(); + it != M.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->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) && 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(); + } + 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 += P.getDataLen(); + return true; +} + diff --git a/src/analysers/analyser_dtsc.h b/src/analysers/analyser_dtsc.h new file mode 100644 index 00000000..53ead4c5 --- /dev/null +++ b/src/analysers/analyser_dtsc.h @@ -0,0 +1,15 @@ +#include "analyser.h" +#include + +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; +}; + diff --git a/src/analysers/analyser_flv.cpp b/src/analysers/analyser_flv.cpp new file mode 100644 index 00000000..a65a346b --- /dev/null +++ b/src/analysers/analyser_flv.cpp @@ -0,0 +1,40 @@ +#include "analyser_flv.h" + +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"); +} + +bool AnalyserFLV::parsePacket(){ + if (feof(stdin)){ + stop(); + return false; + } + while (!feof(stdin)){ + if (flvData.FileLoader(stdin)){break;} + if (feof(stdin)){ + stop(); + return false; + } + } + + // 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; +} + diff --git a/src/analysers/analyser_flv.h b/src/analysers/analyser_flv.h new file mode 100644 index 00000000..940e6b40 --- /dev/null +++ b/src/analysers/analyser_flv.h @@ -0,0 +1,14 @@ +#include "analyser.h" +#include //FLV support + +class AnalyserFLV : public Analyser{ +public: + AnalyserFLV(Util::Config &conf); + bool parsePacket(); + static void init(Util::Config &conf); + +private: + FLV::Tag flvData; + long long filter; +}; + diff --git a/src/analysers/analyser_mp4.cpp b/src/analysers/analyser_mp4.cpp new file mode 100644 index 00000000..751d9ecc --- /dev/null +++ b/src/analysers/analyser_mp4.cpp @@ -0,0 +1,45 @@ +#include "analyser_mp4.h" + +void AnalyserMP4::init(Util::Config &conf){ + Analyser::init(conf); +} + +AnalyserMP4::AnalyserMP4(Util::Config &conf) : Analyser(conf){ + curPos = prePos = 0; +} + +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);} + } + } + + 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; +} + +/// 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; +} + diff --git a/src/analysers/analyser_mp4.h b/src/analysers/analyser_mp4.h new file mode 100644 index 00000000..b7171a98 --- /dev/null +++ b/src/analysers/analyser_mp4.h @@ -0,0 +1,17 @@ +#include "analyser.h" +#include + +class AnalyserMP4 : public Analyser{ +public: + AnalyserMP4(Util::Config &conf); + static void init(Util::Config &conf); + bool parsePacket(); + +private: + uint64_t neededBytes(); + std::string mp4Buffer; + MP4::Box mp4Data; + uint64_t curPos; + uint64_t prePos; +}; + diff --git a/src/analysers/analyser_ogg.cpp b/src/analysers/analyser_ogg.cpp new file mode 100644 index 00000000..72c45722 --- /dev/null +++ b/src/analysers/analyser_ogg.cpp @@ -0,0 +1,121 @@ +#include "analyser_ogg.h" +#include + +/// \TODO EW EW EW EW EW EW EW EW EW EW EW + +void AnalyserOGG::init(Util::Config &conf){ + Analyser::init(conf); +} + +AnalyserOGG::AnalyserOGG(Util::Config &conf) : Analyser(conf){} + +bool AnalyserOGG::parsePacket(){ + if (!oggPage.read(stdin)){return false;} + + // 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()); + } + } + + 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 new file mode 100644 index 00000000..9d7ac5b2 --- /dev/null +++ b/src/analysers/analyser_rtmp.cpp @@ -0,0 +1,186 @@ +/// \file analyser_rtmp.cpp +/// Debugging tool for RTMP data. + +#include "analyser_rtmp.h" + +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()); + } + } +} + +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; + inbuffer.erase(0, 3073); // strip the handshake part + MEDIUM_MSG("Handshake skipped"); + return true; +} + +bool AnalyserRTMP::parsePacket(){ + // While we can't parse a packet, + while (!next.Parse(strbuf)){ + // fill our internal buffer "strbuf" in (up to) 1024 byte chunks + if (std::cin.good()){ + unsigned int charCount = 0; + std::string tmpbuffer; + tmpbuffer.reserve(1024); + while (std::cin.good() && charCount < 1024){ + char newchar = std::cin.get(); + if (std::cin.good()){ + tmpbuffer += newchar; + ++read_in; + ++charCount; + } + } + strbuf.append(tmpbuffer); + }else{ + // if we can't fill the buffer, and have no parsable packet(s), return false + return false; + } + } + + // 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()){ + 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; +} + diff --git a/src/analysers/analyser_rtmp.h b/src/analysers/analyser_rtmp.h new file mode 100644 index 00000000..91a020a0 --- /dev/null +++ b/src/analysers/analyser_rtmp.h @@ -0,0 +1,22 @@ +#include "analyser.h" +#include +#include //FLV support +#include + +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 -#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; - } - 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.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 7a8b5f6d..00000000 --- a/src/analysers/flv_analyser.cpp +++ /dev/null @@ -1,35 +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 - -///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.parseArgs(argc, argv); - - long long filter = conf.getInteger("filter"); - - FLV::Tag flvData; // Temporary storage for incoming FLV data. - while ( !feof(stdin)){ - if (flvData.FileLoader(stdin)){ - if (!filter || filter == flvData.data[0]){ - std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << 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 ca4ad72c..00000000 --- a/src/analysers/mp4_analyser.cpp +++ /dev/null @@ -1,49 +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(){ - 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.parseArgs(argc, argv); - return Analysers::analyseMP4(); -} - diff --git a/src/analysers/ogg_analyser.cpp b/src/analysers/ogg_analyser.cpp deleted file mode 100644 index 9150c0a8..00000000 --- a/src/analysers/ogg_analyser.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#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 analyseOGG(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.parseArgs(argc, argv); - - 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 main(int argc, char ** argv){ - return Analysers::analyseOGG(argc, argv); -} - - 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/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/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