Analyser unification finished

This commit is contained in:
Thulinma 2017-04-16 16:20:12 +02:00
parent 051a8c826b
commit 945e6f2d1a
44 changed files with 1264 additions and 2903 deletions

View file

@ -168,6 +168,7 @@ set(libHeaders
${SOURCE_DIR}/lib/util.h ${SOURCE_DIR}/lib/util.h
${SOURCE_DIR}/lib/vorbis.h ${SOURCE_DIR}/lib/vorbis.h
${SOURCE_DIR}/lib/triggers.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/util.cpp
${SOURCE_DIR}/lib/vorbis.cpp ${SOURCE_DIR}/lib/vorbis.cpp
${SOURCE_DIR}/lib/triggers.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) macro(makeAnalyser analyserName format)
add_executable(MistAnalyser${analyserName} 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} target_link_libraries(MistAnalyser${analyserName}
mist mist
@ -266,89 +273,40 @@ macro(makeAnalyser analyserName format)
) )
endmacro() endmacro()
add_executable(MistAnalyserMP4 src/analysers/analyser_mp4.cpp src/analysers/analyser.cpp) makeAnalyser(RTMP rtmp)
target_link_libraries(MistAnalyserMP4 mist) makeAnalyser(FLV flv)
install( makeAnalyser(DTSC dtsc)
TARGETS MistAnalyserMP4
DESTINATION bin
)
add_executable(MistAnalyserFLV src/analysers/analyser_flv.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserFLV mist)
install(
TARGETS MistAnalyserFLV
DESTINATION bin
)
add_executable(MistAnalyserRTMP src/analysers/analyser_rtmp.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserRTMP mist)
install(
TARGETS MistAnalyserRTMP
DESTINATION bin
)
#niet nodig?
add_executable(MistAnalyserRTP src/analysers/analyser_rtp.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserRTP mist)
install(
TARGETS MistAnalyserRTP
DESTINATION bin
)
add_executable(MistAnalyserTS src/analysers/analyser_ts.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserTS mist)
install(
TARGETS MistAnalyserTS
DESTINATION bin
)
add_executable(MistAnalyserDASH src/analysers/analyser_dash.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserDASH mist)
install(
TARGETS MistAnalyserDASH
DESTINATION bin
)
add_executable(MistAnalyserHLS src/analysers/analyser_hls.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserHLS mist)
install(
TARGETS MistAnalyserHLS
DESTINATION bin
)
add_executable(MistAnalyserDTSC src/analysers/analyser_dtsc.cpp src/analysers/analyser.cpp)
target_link_libraries(MistAnalyserDTSC mist)
install(
TARGETS MistAnalyserDTSC
DESTINATION bin
)
makeAnalyser(AMF amf) #hoeft niet
makeAnalyser(OGG ogg) makeAnalyser(OGG ogg)
#makeAnalyser(RTP rtp) #LTS #makeAnalyser(RTSP rtsp) #LTS #Currently broken. Horribly.
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(TS ts) #LTS makeAnalyser(TS ts) #LTS
makeAnalyser(MP4 mp4) #LTS
makeAnalyser(H264 h264) #LTS makeAnalyser(H264 h264) #LTS
makeAnalyser(HLS hls) #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 ) if (DEFINED LOAD_BALANCE )
makeAnalyser(Load load) #LTS makeUtil(Load load)
endif() endif()
#LTS_END
######################################## ########################################
# MistServer - Inputs # # MistServer - Inputs #

View file

@ -810,41 +810,66 @@ namespace h264 {
out << " Message of type " << payloadType << ", " << payloadSize << " bytes long" << std::endl; out << " Message of type " << payloadType << ", " << payloadSize << " bytes long" << std::endl;
} }
nalUnit * nalFactory(char * _data, size_t _len, size_t & offset, bool annexb) { nalUnit * nalFactory(const 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;
}
if (annexb){ if (annexb){
FAIL_MSG("Not supported in annexb mode yet"); //check if we have a start marker at the beginning, if so, move the offset over
return result; 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;
} }
uint32_t pktLen = Bit::btohl(data); if (_data[i] == 1){offset = i+1; break;}
if (len < 4 + pktLen){
return result;
} }
switch (data[5] & 0x1F){ }
//now we know we're starting at real data. Yay!
}
if (_len < offset + 4){
WARN_MSG("Not at least 4 bytes available - cancelling");
return 0;
}
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 1:
case 5: case 5:
result = new codedSliceUnit(data + 4, pktLen); return new codedSliceUnit(data, pktLen);
break;
case 6: case 6:
result = new seiUnit(data + 4, pktLen); return new seiUnit(data, pktLen);
break;
case 7: case 7:
result = new spsUnit(data + 4, pktLen); return new spsUnit(data, pktLen);
break;
case 8: case 8:
result = new ppsUnit(data + 4, pktLen); return new ppsUnit(data, pktLen);
break;
default: default:
result = new nalUnit(data + 4, pktLen); return new nalUnit(data, pktLen);
break;
} }
offset += 4 + pktLen;
return result;
} }
nalUnit * nalFactory(FILE * in, bool annexb) { nalUnit * nalFactory(FILE * in, bool annexb) {

View file

@ -76,6 +76,7 @@ namespace h264 {
public: public:
nalUnit(const char * data, size_t len) : payload(data, len) {} nalUnit(const char * data, size_t len) : payload(data, len) {}
uint8_t getType() { return payload[0] & 0x1F; } uint8_t getType() { return payload[0] & 0x1F; }
uint32_t getSize(){return payload.size();}
virtual void toPrettyString(std::ostream & out){ virtual void toPrettyString(std::ostream & out){
out << "Nal unit of type " << (((uint8_t)payload[0]) & 0x1F) << ", " << payload.size() << " bytes long" << std::endl; 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(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);
} }

View file

@ -96,6 +96,35 @@ std::string HTTP::URL::getUrl() const{
return ret; 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. /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
/// All this constructor does is call HTTP::Parser::Clean(). /// All this constructor does is call HTTP::Parser::Clean().
HTTP::Parser::Parser() { HTTP::Parser::Parser() {

View file

@ -72,7 +72,7 @@ namespace HTTP {
///URL parsing class. Parses full URL into its subcomponents ///URL parsing class. Parses full URL into its subcomponents
class URL { class URL {
public: public:
URL(const std::string & url); URL(const std::string & url = "");
uint32_t getPort() const; uint32_t getPort() const;
std::string getUrl() const; std::string getUrl() const;
std::string host;///< Hostname or IP address of URL std::string host;///< Hostname or IP address of URL
@ -80,6 +80,7 @@ namespace HTTP {
std::string port;///<Port of URL std::string port;///<Port of URL
std::string path;///<Path after the first slash (not inclusive) but before any question mark std::string path;///<Path after the first slash (not inclusive) but before any question mark
std::string args;///<Everything after the question mark in the path, if it was present std::string args;///<Everything after the question mark in the path, if it was present
URL link(const std::string &l);
}; };
}//HTTP namespace }//HTTP namespace

75
lib/opus.cpp Normal file
View file

@ -0,0 +1,75 @@
#include "opus.h"
#include <sstream>
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();
}
}

6
lib/opus.h Normal file
View file

@ -0,0 +1,6 @@
#include <string>
namespace Opus{
std::string Opus_prettyPacket(const char *part, int len);
}

View file

@ -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 <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <mist/amf.h>
#include <mist/config.h>
#include <mist/defines.h>
///\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);
}

View file

@ -1,104 +1,99 @@
#include "analyser.h" #include "analyser.h"
#include <iostream> #include <iostream>
#include <mist/defines.h>
#include <mist/timing.h> #include <mist/timing.h>
analysers::analysers(Util::Config &config) { /// Reads configuration and opens a passed filename replacing standard input if needed.
conf = config; Analyser::Analyser(Util::Config &conf){
validate = conf.getBool("validate");
//set default detailLevel detail = conf.getInteger("detail");
detail = 2; mediaTime = 0;
upTime = Util::bootSecs();
if (conf.hasOption("filename")) { isActive = &conf.is_active;
fileinput = conf.getString("filename").length() > 0;
} else {
fileinput = 0;
} }
analyse = conf.getString("mode") == "analyse"; ///Opens the filename. Supports stdin and plain files.
validate = conf.getString("mode") == "validate"; bool Analyser::open(const std::string & filename){
if (filename.size() && filename != "-"){
if (validate) { int fp = ::open(filename.c_str(), O_RDONLY);
// conf.getOption("detail", true)[1] = 0ll; if (fp <= 0){
detail = 0; FAIL_MSG("Cannot open '%s': %s", filename.c_str(), strerror(errno));
}
if (conf.hasOption("detail")) { detail = conf.getInteger("detail"); }
Prepare();
}
int analysers::doAnalyse() {
return 0;
}
analysers::~analysers() {
}
void analysers::doValidate() {
std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << endTime << std::endl;
}
bool analysers::packetReady() {
return false; return false;
} }
// Fileinput as stdin
void analysers::Prepare() {
if (fileinput) {
filename = conf.getString("filename");
int fp = open(filename.c_str(), O_RDONLY);
if (fp <= 0) {
FAIL_MSG("Cannot open file: %s", filename.c_str());
}
dup2(fp, STDIN_FILENO); dup2(fp, STDIN_FILENO);
close(fp); close(fp);
INFO_MSG("Parsing %s...", filename.c_str());
}else{
INFO_MSG("Parsing standard input...");
} }
return true;
} }
bool analysers::hasInput() { /// Stops analysis by closing the standard input
// std::cout << std::cin.good() << std::endl; void Analyser::stop(){
close(STDIN_FILENO);
return std::cin.good(); std::cin.setstate(std::ios_base::eofbit);
// return !feof(stdin);
} }
int analysers::Run() { /// Prints validation message if needed
Analyser::~Analyser(){
if(mayExecute) if (validate){std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << mediaTime << std::endl;}
{
std::cout << "start analyser with detailLevel: " << detail << std::endl;
endTime = 0;
upTime = Util::bootSecs();
while (hasInput() && mayExecute) {
while (packetReady()) {
// std::cout << "in loop..." << std::endl;
endTime = doAnalyse();
}
} }
finTime = Util::bootSecs(); ///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;
}
if (validate){ if (validate){
// std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl; finTime = Util::bootSecs();
doValidate(); if ((finTime - upTime) > (mediaTime / 1000) + 2){
FAIL_MSG("Media time more than 2 seconds behind!");
return 1;
}
} }
} }
return 0; return 0;
} }
void analysers::defaultConfig(Util::Config &conf) { /// Sets options common to all analysers.
conf.addOption("filename", /// Should generally be called by the init function of each analyser.
JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the file to analysed.\"}")); 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 " opt["arg_num"] = 1ll;
"do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}")); opt["arg"] = "string";
opt["default"] = "-";
opt["help"] = "Filename to analyse, or - for standard input (default)";
conf.addOption("filename", opt);
opt.null();
conf.addOption( opt["long"] = "validate";
"detail", opt["short"] = "V";
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}")); 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();
} }

View file

@ -1,36 +1,40 @@
#pragma once #pragma once
#include <mist/config.h> #include <mist/config.h>
#include <mist/defines.h>
#include <string> #include <string>
class analysers #define DETAIL_LOW(msg, ...) \
{ if (detail >= 1){printf(msg "\n", ##__VA_ARGS__);}
protected: #define DETAIL_MED(msg, ...) \
bool fileinput; if (detail >= 2){printf(msg "\n", ##__VA_ARGS__);}
std::string filename; #define DETAIL_HI(msg, ...) \
bool analyse; if (detail >= 3){printf(msg "\n", ##__VA_ARGS__);}
bool validate; #define DETAIL_VHI(msg, ...) \
bool mayExecute = true; if (detail >= 4){printf(msg "\n", ##__VA_ARGS__);}
int detail; #define DETAIL_XTR(msg, ...) \
if (detail >= 5){printf(msg "\n", ##__VA_ARGS__);}
long long int endTime;
long long int upTime;
long long int finTime;
bool useBuffer;
class Analyser{
public: public:
Util::Config conf; // These contain standard functionality
analysers(Util::Config &config); Analyser(Util::Config &conf);
~analysers(); ~Analyser();
int run(Util::Config &conf);
virtual void stop();
virtual bool open(const std::string &filename);
virtual bool isOpen();
void Prepare(); // These should be provided by analysers
int Run(); static void init(Util::Config &conf);
virtual int doAnalyse(); virtual bool parsePacket(){return false;}
virtual void doValidate();
virtual bool hasInput();
static void defaultConfig(Util::Config & conf); protected:
//int Analyse(); // These hold the current state and/or config
bool validate; ///< True of validation mode is enabled
virtual bool packetReady(); 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
}; };

View file

@ -1,124 +1,124 @@
#include <iostream>
#include <mist/defines.h>
#include <sstream>
#include <string>
#include <unistd.h>
#include "analyser_dtsc.h" #include "analyser_dtsc.h"
#include <mist/config.h>
#include <mist/h264.h> #include <mist/h264.h>
#include <mist/json.h>
dtscAnalyser::dtscAnalyser(Util::Config config) : analysers(config) { void AnalyserDTSC::init(Util::Config &conf){
conn = Socket::Connection(fileno(stdout), fileno(stdin)); Analyser::init(conf);
std::cout << "connection initialized" << std::endl;
F.reInit(conn);
totalBytes = 0;
// F = DTSC::Packet(config.getString("filename"));
if (!F) {
std::cerr << "Not a valid DTSC file" << std::endl;
mayExecute = false;
return;
} }
if (F.getVersion() == DTSC::DTSC_HEAD) // for meta AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){
{ conn = Socket::Connection(0, fileno(stdin));
DTSC::Meta m(F); totalBytes = 0;
}
if (detail > 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; JSON::Value result;
for (std::map<unsigned int, DTSC::Track>::iterator it = m.tracks.begin(); it != m.tracks.end(); it++) { std::stringstream issues;
DTSC::Meta M(P);
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin();
it != M.tracks.end(); it++){
JSON::Value track; 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<DTSC::Key>::iterator k = it->second.keys.begin();
k != it->second.keys.end(); k++){
if (!k->getLength()){continue;}
if (k->getLength() > longest_key){longest_key = k->getLength();}
if (k->getLength() < shrtest_key){shrtest_key = k->getLength();}
if (k->getParts() > longest_cnt){longest_cnt = k->getParts();}
if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();}
if (k->getParts()){
if ((k->getLength() / k->getParts()) > longest_prt){
longest_prt = (k->getLength() / k->getParts());
}
if ((k->getLength() / k->getParts()) < shrtest_prt){
shrtest_prt = (k->getLength() / k->getParts());
}
}
}
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"){ if (it->second.type == "video"){
std::stringstream tStream; track["width"] = (long long)it->second.width;
track["resolution"] = JSON::Value((long long)it->second.width).asString() + "x" + JSON::Value((long long)it->second.height).asString(); track["height"] = (long long)it->second.height;
track["fps"] = (long long)((double)it->second.fpks / 1000);
track["fpks"] = it->second.fpks; 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; h264::sequenceParameterSet sps;
sps.fromDTSCInit(it->second.init); sps.fromDTSCInit(it->second.init);
h264::SPSMeta spsData = sps.getCharacteristics(); h264::SPSMeta spsData = sps.getCharacteristics();
track["encoding"]["width"] = spsData.width; track["h264"]["profile"] = spsData.profile;
track["encoding"]["height"] = spsData.height; track["h264"]["level"] = spsData.level;
tStream << spsData.fps;
track["encoding"]["fps"] = tStream.str();
track["encoding"]["profile"] = spsData.profile;
track["encoding"]["level"] = spsData.level;
} }
} }
if (it->second.type == "audio") {
std::stringstream tStream;
tStream << it->second.bps * 8 << " b/s, " << (double)it->second.bps * 8 / 1024 << " kb/s, " << (double)it->second.bps * 8 / 1024 / 1024
<< " mb/s";
track["bitrate"] = tStream.str();
track["keyframe_interval"] = (long long)((float)(it->second.lastms - it->second.firstms) / it->second.keys.size());
}
result[it->second.getWritableIdentifier()] = track; 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();}
if (m.vod || m.live) { m.toPrettyString(std::cout, 0, 0x03); } std::cout << result.toString() << std::endl;
stop();
} }
}
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; break;
} }
case DTSC::DTCM:{ case DTSC::DTCM:{
std::cout << "DTCM command: " << F.getScan().toPrettyString() << std::endl; if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;}
break; break;
} }
default: DEBUG_MSG(DLVL_WARN, "Invalid dtsc packet @ bpos %llu", totalBytes); break; default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break;
}
} }
totalBytes += F.getDataLen(); totalBytes += P.getDataLen();
return true;
F.reInit(conn);
return totalBytes;
} }
int main(int argc, char **argv) {
Util::Config conf = Util::Config(argv[0]);
analysers::defaultConfig(conf);
conf.parseArgs(argc, argv);
dtscAnalyser A(conf);
A.Run();
return 0;
}

View file

@ -1,19 +1,15 @@
#include <mist/config.h>
#include "analyser.h" #include "analyser.h"
#include <mist/dtsc.h> #include <mist/dtsc.h>
class dtscAnalyser : public analysers class AnalyserDTSC : public Analyser{
{ public:
DTSC::Packet F; AnalyserDTSC(Util::Config &conf);
bool parsePacket();
static void init(Util::Config &conf);
private:
DTSC::Packet P;
Socket::Connection conn; Socket::Connection conn;
uint64_t totalBytes; uint64_t totalBytes;
public:
dtscAnalyser(Util::Config config);
bool packetReady();
bool hasInput();
void PreProcessing();
//int Analyse();
int doAnalyse();
// void doValidate();
}; };

View file

@ -1,73 +1,40 @@
#include "analyser_flv.h" #include "analyser_flv.h"
#include <mist/config.h>
#include <mist/defines.h>
#include <string>
flvAnalyser::flvAnalyser(Util::Config config) : analysers(config) { void AnalyserFLV::init(Util::Config &conf){
Analyser::init(conf);
if(fileinput && open(filename.c_str(), O_RDONLY) <= 0) JSON::Value opt;
{ opt["long"] = "filter";
mayExecute = false; opt["short"] = "F";
return; 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"); filter = conf.getInteger("filter");
FLV::Tag flvData; // Temporary storage for incoming FLV data.
//check for flv data
char flvHeader[3];
std::cin.read(flvHeader,3);
if(flvHeader[0] != 0x46 || flvHeader[1] != 0x4C || flvHeader[2] != 0x56)
{
FAIL_MSG("No FLV Signature found!");
mayExecute = false;
return;
}
std::cin.seekg(0);
} }
/* bool AnalyserFLV::parsePacket(){
void flvAnalyser::doValidate() if (feof(stdin)){
{ stop();
std::cout << "dfasdfsdafdsf" << std::endl; return false;
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl; }
while (!feof(stdin)){
if (flvData.FileLoader(stdin)){break;}
if (feof(stdin)){
stop();
return false;
} }
*/
bool flvAnalyser::hasInput() {
return !feof(stdin);
} }
bool flvAnalyser::packetReady() { // If we arrive here, we've loaded a FLV packet
return flvData.FileLoader(stdin);
}
int flvAnalyser::doAnalyse() {
// std::cout<< "do analyse" << std::endl;
if (analyse) { // always analyse..?
if (!filter || filter == flvData.data[0]){ if (!filter || filter == flvData.data[0]){
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; DETAIL_MED("[%llu+%llu] %s", flvData.tagTime(), flvData.offset(), flvData.tagType().c_str());
} }
} mediaTime = flvData.tagTime();
endTime = flvData.tagTime(); return true;
return endTime;
} }
int main(int argc, char **argv) {
Util::Config conf = Util::Config(argv[0]);
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info "
"about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
analysers::defaultConfig(conf);
conf.parseArgs(argc, argv);
flvAnalyser A(conf);
A.Run();
return 0;
}

View file

@ -1,18 +1,14 @@
#include <mist/flv_tag.h> //FLV support
#include <mist/config.h>
#include "analyser.h" #include "analyser.h"
#include <mist/flv_tag.h> //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; FLV::Tag flvData;
long long filter; long long filter;
public:
flvAnalyser(Util::Config config);
bool packetReady();
void PreProcessing();
//int Analyse();
int doAnalyse();
bool hasInput();
// void doValidate();
}; };

View file

@ -0,0 +1,58 @@
/// \file analyser_h264.cpp
/// Reads H264 data and prints it in human-readable format.
#include "analyser_h264.h"
#include <mist/bitfields.h>
#include <mist/bitstream.h>
#include <mist/h264.h>
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;
}

View file

@ -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;
};

View file

@ -9,213 +9,175 @@
#include <string.h> #include <string.h>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
// 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<HLSPart> getParts(std::string &body, std::string &uri) { void AnalyserHLS::getParts(const std::string &body){
size_t slashPos = uri.rfind('/');
std::string uri_prefix = uri.substr(0, slashPos + 1);
std::deque<HLSPart> out;
std::stringstream data(body); std::stringstream data(body);
std::string line; std::string line;
unsigned int start = 0; uint64_t no = 0;
unsigned int durat = 0; float durat = 0;
do { refreshAt = Util::bootSecs() + 10;
line = ""; while (data.good()){
std::getline(data, line); std::getline(data, line);
if (line.size() && *line.rbegin() == '\r'){line.resize(line.size() - 1);} if (line.size() && *line.rbegin() == '\r'){line.resize(line.size() - 1);}
if (line != "") { if (!line.size()){continue;}
if (line[0] != '#'){ if (line[0] != '#'){
out.push_back(HLSPart(uri_prefix + line, start, durat)); if (line.find("m3u") != std::string::npos){
start += durat; 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{ }else{
if (line.substr(0, 8) == "#EXTINF:") { durat = atof(line.substr(8).c_str()) * 1000; } 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) { /// Returns true if we either still have parts to download, or are still refreshing the playlist.
port = 80; bool AnalyserHLS::isOpen(){
url = conf.getString("url"); return (*isActive) && (parts.size() || refreshAt);
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"<<std::endl;
mayExecute = false;
return;
} }
server = url.substr(0, url.find('/')); void AnalyserHLS::stop(){
url = url.substr(url.find('/')); parts.clear();
refreshAt = 0;
if (server.find(':') != std::string::npos) {
port = atoi(server.substr(server.find(':') + 1).c_str());
server = server.substr(0, server.find(':'));
} }
startTime = Util::bootSecs(); bool AnalyserHLS::open(const std::string &url){
abortTime = conf.getInteger("abort"); root = HTTP::URL(url);
if (root.protocol != "http"){
playlist = url; FAIL_MSG("Only http protocol is supported (%s not supported)", root.protocol.c_str());
repeat = true; // init to true, analyse at least one time return false;
lastDown = ""; }
pos = 0; return true;
output = (conf.getString("mode") == "output");
} }
void hlsAnalyser::doValidate() { AnalyserHLS::AnalyserHLS(Util::Config &conf) : Analyser(conf){
long long int endTime = Util::bootSecs(); if (conf.getString("reconstruct") != ""){
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl; 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();
} }
int hlsAnalyser::doAnalyse() { /// Downloads the given URL into 'H', returns true on success.
repeat = false; /// Makes at most 5 attempts, and will wait no longer than 5 seconds without receiving data.
while (url.size() > 4 && (url.find(".m3u") != std::string::npos || url.find(".m3u8") != std::string::npos)) { bool AnalyserHLS::download(const HTTP::URL &link){
playlist = url; if (!link.host.size()){return false;}
DEBUG_MSG(DLVL_DEVEL, "Retrieving playlist: %s", url.c_str()); INFO_MSG("Retrieving %s", link.getUrl().c_str());
if (!conn) { conn = Socket::Connection(server, port, false); } unsigned int loop = 6; // max 5 attempts
HTTP::Parser H; while (--loop){// loop while we are unsuccessful
H.url = url; H.Clean();
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // 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.SendRequest(conn);
H.Clean(); H.Clean();
while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {} uint64_t reqTime = Util::bootSecs();
parts = getParts(H.body, url); while (conn && Util::bootSecs() < reqTime + 5){
if (!parts.size()) { // No data? Wait for a second or so.
DEBUG_MSG(DLVL_FAIL, "Playlist parsing error - cancelling. state: %s/%s body size: %u", conn ? "Conn" : "Disconn", if (!conn.spool()){
(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); Util::sleep(1000);
// continue; continue;
return 0;
} }
parts.pop_front(); // 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;
} }
unsigned int lastRepeat = 0; bool AnalyserHLS::parsePacket(){
unsigned int numRepeat = 0; while (isOpen()){
while (parts.size() > 0 && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) { // 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;
}
}
// If there are parts to download, get one.
if (parts.size()){
HLSPart part = *parts.begin(); HLSPart part = *parts.begin();
parts.pop_front(); parts.pop_front();
DEBUG_MSG(DLVL_DEVEL, "Retrieving segment: %s (%u-%u)", part.uri.c_str(), part.start, part.start + part.dur); if (!download(part.uri)){return false;}
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.GetHeader("Content-Length") != ""){
if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())){ 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()); FAIL_MSG("Expected %s bytes of data, but only received %lu.",
if (lastRepeat != part.start || numRepeat < 500) { H.GetHeader("Content-Length").c_str(), H.body.size());
DEBUG_MSG(DLVL_FAIL, "Retrying"); return false;
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){ if (H.body.size() % 188){
DEBUG_MSG(DLVL_FAIL, "Expected a multiple of 188 bytes, received %d bytes", H.body.size()); FAIL_MSG("Expected a multiple of 188 bytes, received %d bytes", H.body.size());
if (lastRepeat != part.start || numRepeat < 500) { return false;
DEBUG_MSG(DLVL_FAIL, "Retrying");
if (lastRepeat != part.start) {
numRepeat = 0;
lastRepeat = part.start;
} else {
numRepeat++;
} }
parts.push_front(part); parsedPart = part.no;
Util::wait(1000); hlsTime += part.dur;
continue; mediaTime = (uint64_t)hlsTime;
} else { if (reconstruct.good()){reconstruct << H.body;}
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(); H.Clean();
return true;
} }
return pos; // 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 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();
}

View file

@ -1,55 +1,37 @@
//http://cattop:8080/hls/bunny/index.m3u8
//
#include <mist/config.h>
#include <mist/timing.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <iostream>
#include "analyser.h" #include "analyser.h"
#include <fstream>
#include <mist/http_parser.h>
class HLSPart{ class HLSPart{
public: public:
HLSPart(std::string u, unsigned int s, unsigned int d) { HLSPart(const HTTP::URL &u, uint64_t n, float d) : uri(u), no(n), dur(d){}
uri = u; HTTP::URL uri;
start = s; uint64_t no;
dur = d; float dur;
}
std::string uri;
unsigned int start;
unsigned int dur;
}; };
class AnalyserHLS : public Analyser{
class hlsAnalyser : public analysers
{
public: public:
hlsAnalyser(Util::Config config); AnalyserHLS(Util::Config &conf);
~hlsAnalyser(); bool parsePacket();
bool packetReady(); static void init(Util::Config &conf);
void PreProcessing(); bool isOpen();
//int Analyse(); bool open(const std::string &filename);
int doAnalyse(); void stop();
void doValidate();
bool hasInput();
void PostProcessing();
private: private:
unsigned int port;
std::string url;
std::string server;
long long int startTime;
long long int abortTime;
std::deque<HLSPart> parts; std::deque<HLSPart> 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; Socket::Connection conn;
std::ofstream reconstruct;
std::string playlist;
bool repeat;
std::string lastDown;
unsigned int pos;
bool output;
}; };

View file

@ -1,71 +1,45 @@
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <signal.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <vector>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/mp4.h>
#include <mist/timing.h>
#include <sys/sysinfo.h>
#include "analyser_mp4.h" #include "analyser_mp4.h"
mp4Analyser::mp4Analyser(Util::Config config) : analysers(config) { void AnalyserMP4::init(Util::Config &conf){
Analyser::init(conf);
curPos = 0;
dataSize = 0;
} }
int mp4Analyser::doAnalyse() { AnalyserMP4::AnalyserMP4(Util::Config &conf) : Analyser(conf){
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); curPos = prePos = 0;
std::cerr << mp4Data.toPrettyString(0) << std::endl;
return dataSize; // endtime?
} }
bool mp4Analyser::hasInput() { bool AnalyserMP4::parsePacket(){
if (!std::cin.good()) { return false; } 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(); mp4Buffer += std::cin.get();
dataSize++; ++curPos;
if (!std::cin.good()){mp4Buffer.erase(mp4Buffer.size() - 1, 1);}
if (!std::cin.good()) { }
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
dataSize--;
} }
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; return true;
} }
FAIL_MSG("Could not read box at position %llu", prePos);
bool mp4Analyser::packetReady() { return false;
return mp4Data.read(mp4Buffer);
} }
mp4Analyser::~mp4Analyser() { /// Calculates how many bytes we need to read a whole box.
INFO_MSG("Stopped parsing at position %d", curPos); 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;
} }
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;
}

View file

@ -1,23 +1,17 @@
#include <mist/config.h>
#include <mist/mp4.h>
#include "analyser.h" #include "analyser.h"
#include <mist/mp4.h>
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; std::string mp4Buffer;
MP4::Box mp4Data; MP4::Box mp4Data;
int dataSize; uint64_t curPos;
int curPos; uint64_t prePos;
public:
mp4Analyser(Util::Config config);
~mp4Analyser();
bool packetReady();
void PreProcessing();
//int Analyse();
int doAnalyse();
// void doValidate();
bool hasInput();
void PostProcessing();
}; };

View file

@ -1,49 +1,121 @@
#include <string>
#include "analyser_ogg.h" #include "analyser_ogg.h"
#include <mist/opus.h>
oggAnalyser::oggAnalyser(Util::Config config) : analysers(config) /// \TODO EW EW EW EW EW EW EW EW EW EW EW
{
std::cout << "ogg constr" << std::endl;
filter = conf.getInteger("filter");
FLV::Tag flvData; // Temporary storage for incoming FLV data.
endTime = 0;
void AnalyserOGG::init(Util::Config &conf){
Analyser::init(conf);
} }
void oggAnalyser::doValidate() AnalyserOGG::AnalyserOGG(Util::Config &conf) : Analyser(conf){}
{
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl;
}
bool oggAnalyser::packetReady() bool AnalyserOGG::parsePacket(){
{ if (!oggPage.read(stdin)){return false;}
return flvData.FileLoader(stdin);
}
int oggAnalyser::doAnalyse() // We now have an Ogg page
{ // Print it, if we're at high detail level.
if (analyse){ //always analyse..? DETAIL_HI("%s", oggPage.toPrettyString().c_str());
if (!filter || filter == flvData.data[0]){
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl; // 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; if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
} if (detail >= 2){
std::cout << " Theora data" << std::endl;
int main(int argc, char ** argv){ }
Util::Config conf = Util::Config(argv[0]); static unsigned int numParts = 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)\"}")); static unsigned int keyCount = 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'.\"}")); for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}")); theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
conf.parseArgs(argc, argv); if (tmpHeader.isHeader()){
if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();}
oggAnalyser A(conf); }else{
if (!(oggPage.getHeaderType() == OGG::Continued) &&
A.Run(); tmpHeader.getFTYPE() == 0){// if keyframe
if (detail >= 3){
return 0; 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;
} }

View file

@ -0,0 +1,16 @@
#include "analyser.h"
#include <mist/ogg.h>
class AnalyserOGG : public Analyser{
public:
AnalyserOGG(Util::Config &conf);
bool parsePacket();
static void init(Util::Config &conf);
private:
std::map<int, std::string> sn2Codec;
std::string oggBuffer;
OGG::Page oggPage;
int kfgshift;
};

View file

@ -1,183 +1,47 @@
#include <string> /// \file analyser_rtmp.cpp
/// Debugging tool for RTMP data.
#include "analyser_rtmp.h" #include "analyser_rtmp.h"
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <mist/flv_tag.h>
#include <mist/amf.h>
#include <mist/rtmpchunks.h>
#include <mist/config.h>
#include <mist/socket.h>
#include <sys/sysinfo.h>
#include <signal.h>
#define DETAIL_RECONSTRUCT 1 void AnalyserRTMP::init(Util::Config &conf){
#define DETAIL_EXPLICIT 2 Analyser::init(conf);
#define DETAIL_VERBOSE 4 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();
}
rtmpAnalyser::rtmpAnalyser(Util::Config config) : analysers(config) AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){
{ if (conf.getString("reconstruct") != ""){
std::cout << "rtmp constr" << std::endl; reconstruct.open(conf.getString("reconstruct").c_str());
Detail = conf.getInteger("detail"); if (reconstruct.good()){
//Detail = 4; 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); inbuffer.reserve(3073);
while (std::cin.good() && inbuffer.size() < 3073){inbuffer += std::cin.get();}
while(std::cin.good() && inbuffer.size() < 3073){
inbuffer += std::cin.get();
}
inbuffer.erase(0,3073); //strip the handshake part
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
RTMPStream::rec_cnt += 3073; RTMPStream::rec_cnt += 3073;
inbuffer.erase(0, 3073); // strip the handshake part
read_in = 0; MEDIUM_MSG("Handshake skipped");
endTime = 0; return true;
} }
bool rtmpAnalyser::packetReady() bool AnalyserRTMP::parsePacket(){
{ // While we can't parse a packet,
return (std::cin.good() || strbuf.size()); while (!next.Parse(strbuf)){
} // fill our internal buffer "strbuf" in (up to) 1024 byte chunks
int rtmpAnalyser::doAnalyse()
{
// std::cout << "do analyse" << std::endl;
analyse=true;
if (analyse){ //always analyse..?
// std::cout << "status strbuf: " << next.Parse(strbuf) << " strbuf_size: " << strbuf.size() << std::endl;
if (next.Parse(strbuf)){
if (Detail & DETAIL_VERBOSE){
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp,
next.len, next.msg_type_id, next.msg_stream_id);
}
switch (next.msg_type_id){
case 0: //does not exist
fprintf(stderr, "Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i\n", read_in-inbuffer.size(), next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
return 0;
break; //happens when connection breaks unexpectedly
case 1: //set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
break;
case 2: //abort message - we ignore this one
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
//4 bytes of stream id to drop
break;
case 3: //ack
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
break;
case 4: {
short int ucmtype = ntohs(*(short int*)next.data.c_str());
switch (ucmtype){
case 0:
fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 1:
fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 2:
fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 3:
fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 4:
fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 6:
fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 7:
fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 31:
case 32:
//don't know, but not interesting anyway
break;
default:
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
}
}
break;
case 5: //window size of other end
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
break;
case 6:
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
//4 bytes window size, 1 byte limit type (ignored)
fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size);
break;
case 8:
case 9:
if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){
F.ChunkLoader(next);
if (Detail & DETAIL_EXPLICIT){
std::cerr << "[" << F.tagTime() << "+" << F.offset() << "] " << F.tagType() << std::endl;
}
if (Detail & DETAIL_RECONSTRUCT){
std::cout.write(F.data, F.len);
}
}
break;
case 15:
fprintf(stderr, "Received AFM3 data message\n");
break;
case 16:
fprintf(stderr, "Received AFM3 shared object\n");
break;
case 17: {
fprintf(stderr, "Received AFM3 command message:\n");
char soort = next.data[0];
next.data = next.data.substr(1);
if (soort == 0){
amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl;
}else{
amf3data = AMF::parse3(next.data);
amf3data.Print();
}
}
break;
case 18: {
fprintf(stderr, "Received AFM0 data message (metadata):\n");
amfdata = AMF::parse(next.data);
amfdata.Print();
if (Detail & DETAIL_RECONSTRUCT){
F.ChunkLoader(next);
std::cout.write(F.data, F.len);
}
}
break;
case 19:
fprintf(stderr, "Received AFM0 shared object\n");
break;
case 20: { //AMF0 command message
fprintf(stderr, "Received AFM0 command message:\n");
amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl;
}
break;
case 22:
if (Detail & DETAIL_RECONSTRUCT){
std::cout << next.data;
}
break;
default:
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
return 1;
break;
} //switch for type of chunk
}else{ //if chunk parsed
if (std::cin.good()){ if (std::cin.good()){
unsigned int charCount = 0; unsigned int charCount = 0;
std::string tmpbuffer; std::string tmpbuffer;
@ -192,22 +56,131 @@ int rtmpAnalyser::doAnalyse()
} }
strbuf.append(tmpbuffer); strbuf.append(tmpbuffer);
}else{ }else{
strbuf.get().clear(); // 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,
int main(int argc, char ** argv){ next.msg_stream_id);
Util::Config conf = Util::Config(argv[0]); switch (next.msg_type_id){
case 0: // does not exist
analysers::defaultConfig(conf); DETAIL_LOW("Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i", read_in - strbuf.size(),
conf.parseArgs(argc, argv); next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
rtmpAnalyser A(conf);
A.Run();
return 0; 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;
}

View file

@ -1,35 +1,22 @@
#include <mist/flv_tag.h> //FLV support
#include <mist/config.h>
#include "analyser.h" #include "analyser.h"
#include <string>
#include <fstream> #include <fstream>
#include <iostream> #include <mist/flv_tag.h> //FLV support
#include <mist/amf.h>
#include <mist/rtmpchunks.h> #include <mist/rtmpchunks.h>
#include <mist/config.h>
#include <mist/socket.h>
class rtmpAnalyser : public analysers class AnalyserRTMP : public Analyser{
{ private:
FLV::Tag flvData; RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk
long long filter; FLV::Tag F;///< Holds the most recently created FLV packet
unsigned int read_in; ///< Amounts of bytes read to fill 'strbuf' so far
std::string inbuffer; Socket::Buffer strbuf;///< Internal buffer from where 'next' is filled
RTMPStream::Chunk next; AMF::Object amfdata;///< Last read AMF object
FLV::Tag F; //FLV holder AMF::Object3 amf3data;///<Last read AMF3 object
unsigned int read_in ; std::ofstream reconstruct;///< If reconstructing, a valid file handle
Socket::Buffer strbuf;
AMF::Object amfdata;
AMF::Object3 amf3data;
int Detail;
public: public:
rtmpAnalyser(Util::Config config); AnalyserRTMP(Util::Config & conf);
bool packetReady(); static void init(Util::Config & conf);
void PreProcessing(); bool parsePacket();
//int Analyse(); virtual bool open(const std::string &filename);
int doAnalyse();
// void doValidate();
}; };

View file

@ -1,37 +1,41 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string> #include <string>
#include "analyser_rtp.h" #include <string.h>
#include <vector>
#include <sstream>
#include <mist/socket.h>
#include <mist/config.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
rtpAnalyser::rtpAnalyser(Util::Config config) : analysers(config) //rtsp://krabs:1935/vod/gear1.mp4
{
std::cout << "rtp constr" << std::endl; namespace Analysers {
int analyseRTP(){
Socket::Connection conn("localhost", 554, true); Socket::Connection conn("localhost", 554, true);
step = 0; //Socket::Connection conn("krabs", 1935, true);
trackIt = 0; HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
int step = 0;
} /*1 = sent describe
2 = recd describe
void rtpAnalyser::doValidate() 3 = sent setup
{ 4 = received setup
} 5 = sent play"*/
std::vector<std::string> tracks;
bool rtpAnalyser::packetReady() std::vector<Socket::UDPConnection> connections;
{ unsigned int trackIt = 0;
return conn.connected(); while (conn.connected()){
}
int rtpAnalyser::doAnalyse()
{
if (analyse){ //always analyse..?
// std::cerr << "loopy" << std::endl; // std::cerr << "loopy" << std::endl;
if(step == 0){ if(step == 0){
HTTP_S.protocol = "RTSP/1.0"; HTTP_S.protocol = "RTSP/1.0";
HTTP_S.method = "DESCRIBE"; HTTP_S.method = "DESCRIBE";
//rtsp://krabs:1935/vod/gear1.mp4 //rtsp://krabs:1935/vod/gear1.mp4
//rtsp://localhost/g1 //rtsp://localhost/g1
//HTTP_S.url = "rtsp://localhost/steers"; HTTP_S.url = "rtsp://localhost/steers";
HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4"; //HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("CSeq",1); HTTP_S.SetHeader("CSeq",1);
HTTP_S.SendRequest(conn); HTTP_S.SendRequest(conn);
step++; step++;
@ -187,18 +191,19 @@ int rtpAnalyser::doAnalyse()
} }
} }
} }
return 666;
return endTime;
} }
}
int main(int argc, char ** argv){ int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0]); 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); conf.parseArgs(argc, argv);
return Analysers::analyseRTP();
rtpAnalyser A(conf);
A.Run();
return 0;
} }

View file

@ -9,112 +9,81 @@
#include <map> #include <map>
#include <mist/bitfields.h> #include <mist/bitfields.h>
#include <mist/config.h> #include <mist/config.h>
#include <mist/defines.h>
#include <mist/ts_packet.h> #include <mist/ts_packet.h>
#include <signal.h> #include <signal.h>
#include <sstream> #include <sstream>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
#include <mist/defines.h>
tsAnalyser::tsAnalyser(Util::Config config) : analysers(config) { void AnalyserTS::init(Util::Config &conf){
upTime = Util::bootSecs(); Analyser::init(conf);
pcr = 0; 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; 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); std::cin.read(packetPtr, 188);
//0x47 if (std::cin.gcount() != 188){return false;}
if (std::cin.gcount() != 188) { return 0; } DONTEVEN_MSG("Reading from position %llu", bytes);
bytes += 188; bytes += 188;
if (packet.FromPointer(packetPtr)) { if (!packet.FromPointer(packetPtr)){return false;}
if (analyse) { if (detail){
if (packet.getUnitStart() && payloads[packet.getPID()] != "") { if (packet.getUnitStart() && payloads.count(packet.getPID()) &&
std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel); payloads[packet.getPID()] != ""){
if ((detail & 1) && (!pidOnly || packet.getPID() == pidOnly)){
std::cout << printPES(payloads[packet.getPID()], packet.getPID());
}
payloads.erase(packet.getPID()); payloads.erase(packet.getPID());
} }
if (detailLevel >= 3 || !packet.getPID() || packet.isPMT()) {
if (packet.getPID() == 0){((TS::ProgramAssociationTable *)&packet)->parsePIDs();} if (packet.getPID() == 0){((TS::ProgramAssociationTable *)&packet)->parsePIDs();}
std::cout << packet.toPrettyString(0, detailLevel); 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() && !packet.isPMT() && (payloads[packet.getPID()].size() || packet.getUnitStart())) { if (packet.getPID() >= 0x10 && !packet.isPMT() && packet.getPID() != 17 &&
(payloads[packet.getPID()].size() || packet.getUnitStart())){
payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength()); payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength());
} }
} }
if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()) { pcr = packet.getPCR(); } if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()){
mediaTime = packet.getPCR() / 27000;
} }
if (bytes > 1024) { return true;
long long int tTime = Util::bootSecs();
if (validate && tTime - upTime > 5 && tTime - upTime > pcr / 27000000) {
std::cerr << "data received too slowly" << std::endl;
return 1;
}
bytes = 0;
} }
return endTime; AnalyserTS::~AnalyserTS(){
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin();
it != payloads.end(); it++){
if ((detail & 1) && (!pidOnly || it->first == pidOnly)){
std::cout << printPES(it->second, it->first);
} }
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<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++) {
if (!it->first || it->first == 4096) { continue; }
std::cout << printPES(it->second, it->first, detailLevel);
} }
} }
std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int detailLevel) { std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
unsigned int headSize = 0; unsigned int headSize = 0;
std::stringstream res; std::stringstream res;
bool known = false; bool known = false;
@ -128,7 +97,10 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de
known = true; known = true;
} }
if (!known){res << " [Unknown stream ID: " << (int)d[3] << "]";} 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 (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; unsigned int padding = 0;
if (known){ if (known){
if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";} if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";}
@ -191,10 +163,14 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de
res << " [DTS " << ((double)time / 90000) << "s]"; 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; res << std::endl;
if (detailLevel == 10) { if (detail & 32){
unsigned int counter = 0; unsigned int counter = 0;
for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i){ 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){ if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1){
@ -209,3 +185,4 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de
} }
return res.str(); return res.str();
} }

View file

@ -1,27 +1,18 @@
#include "analyser.h"
#include <mist/config.h> #include <mist/config.h>
#include <mist/ts_packet.h> #include <mist/ts_packet.h>
#include "analyser.h"
class tsAnalyser : public analysers
{
long long filter;
std::map<unsigned long long, std::string> payloads;
TS::Packet packet;
long long int upTime;
int64_t pcr;
unsigned int bytes;
char packetPtr[188];
int detailLevel;
int incorrectPacket;
class AnalyserTS : public Analyser{
public: public:
tsAnalyser(Util::Config config); AnalyserTS(Util::Config &conf);
~tsAnalyser(); ~AnalyserTS();
bool packetReady(); bool parsePacket();
void PreProcessing(); static void init(Util::Config &conf);
//int Analyse(); std::string printPES(const std::string &d, unsigned long PID);
int doAnalyse(); private:
void doValidate(); std::map<unsigned long long, std::string> payloads;
std::string printPES(const std::string & d, unsigned long PID, int detailLevel); uint32_t pidOnly;
TS::Packet packet;
uint64_t bytes;
}; };

View file

@ -1,128 +0,0 @@
/// \file dtsc_analyser.cpp
/// Reads an DTSC file and prints all readable data about it
#include <string>
#include <iostream>
#include <sstream>
#include <mist/dtsc.h>
#include <mist/json.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/h264.h>
///\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<unsigned int, DTSC::Track>::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<DTSC::Key>::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){
if (!k->getLength()){continue;}
if (k->getLength() > longest_key){longest_key = k->getLength();}
if (k->getLength() < shrtest_key){shrtest_key = k->getLength();}
if (k->getParts() > longest_cnt){longest_cnt = k->getParts();}
if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();}
if ((k->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

View file

@ -1,79 +0,0 @@
/// \file flv_analyser.cpp
/// Contains the code for the FLV Analysing tool.
#include <fcntl.h>
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <fstream>
#include <unistd.h>
#include <signal.h>
#include <mist/flv_tag.h> //FLV support
#include <mist/config.h>
#include <mist/timing.h>
#include <sys/sysinfo.h>
#include <mist/defines.h>
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);
}

View file

@ -1,30 +0,0 @@
/// \file h264_analyser.cpp
/// Reads an H264 file and prints all readable data about it
#include <string>
#include <iostream>
#include <cstdio>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/h264.h>
#include <mist/bitfields.h>
#include <mist/bitstream.h>
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;
}

View file

@ -1,199 +0,0 @@
/// \file hls_analyser.cpp
/// Contains the code for the HLS Analysing tool.
#include <mist/config.h>
#include <mist/timing.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <iostream>
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<HLSPart> getParts(std::string & body, std::string & uri) {
size_t slashPos = uri.rfind('/');
std::string uri_prefix = uri.substr(0, slashPos + 1);
std::deque<HLSPart> 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<HLSPart> 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;
}

View file

@ -0,0 +1,16 @@
#include ANALYSERHEADER
#include <mist/config.h>
#include <mist/defines.h>
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;
}
}

View file

@ -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 <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <string.h>
#include <mist/mp4.h>
#include <mist/config.h>
#include <mist/defines.h>
///\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);
}

View file

@ -1,492 +0,0 @@
#include <cstdlib>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <string.h>
#include <mist/ogg.h>
#include <mist/config.h>
#include <mist/theora.h>
#include <mist/defines.h>
#include <sys/sysinfo.h>
#include <cmath>
///\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<int,std::string> sn2Codec;
std::string oggBuffer;
OGG::Page oggPage;
int kfgshift;
//validate variables
bool doValidate = true;
long long int lastTime =0;
double mspft = 0;
std::map<long long unsigned int,long long int> 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<int,std::string> 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<int,std::string> sn2Codec;
std::string oggBuffer;
OGG::Page oggPage;
long long int lastTime =0;
double mspft = 0;
std::map<long long unsigned int,long long int> 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 <<oggPage.toPrettyString() << std::endl<< "--------------------------------" << std::endl;
lastTime = (double)oggPage.getGranulePosition()/(double)oggMap[oggPage.getBitstreamSerialNumber()];
}else if(sn2Codec[oggPage.getBitstreamSerialNumber()] == "theora"){
//theora getKFGShift()
if(oggPage.getGranulePosition() != 0xffffffffffffffff){
lastTime = ((oggPage.getGranulePosition()>>(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");
}
}

View file

@ -1,219 +0,0 @@
/// \file rtmp_analyser.cpp
/// Debugging tool for RTMP data.
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <mist/flv_tag.h>
#include <mist/amf.h>
#include <mist/rtmpchunks.h>
#include <mist/config.h>
#include <mist/socket.h>
#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

View file

@ -1,209 +0,0 @@
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <string.h>
#include <vector>
#include <sstream>
#include <mist/socket.h>
#include <mist/config.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
//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<std::string> tracks;
std::vector<Socket::UDPConnection> 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()<<std::endl;
}else if(step == 4){
std::cerr << "Play!!!1" << std::endl;
HTTP_S.method = "PLAY";
HTTP_S.url = "rtsp://localhost/steers";
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
HTTP_S.SetHeader("Range","npt=0.000-");
HTTP_S.SendRequest(conn);
step++;
std::cerr << "step for play.." << step << std::endl;
}
if (conn.Received().size() || conn.spool()){
if (HTTP_R.Read(conn)){
if(step == 1){
std::cerr << "recvd desc" << std::endl;
for(size_t ml = HTTP_R.body.find("a=control:",HTTP_R.body.find("m=")); ml != std::string::npos; ml = HTTP_R.body.find("a=control:",ml+1)){
std::cerr << "found trekk" << std::endl;
tracks.push_back(HTTP_R.body.substr(ml+10,HTTP_R.body.find_first_of("\r\n",ml)-(ml+10)));
connections.push_back(Socket::UDPConnection());
}
for(unsigned int x = 0; x < connections.size();x++){
connections[x].SetDestination("127.0.0.1",666);
connections[x].bind(20000+2*x);
connections[x].setBlocking(true);
}
step++;
}else if(step == 3){
std::cerr << "recvd setup" << std::endl;
std::cerr << "trackIt: " << trackIt << " size " << tracks.size() << std::endl;
if(trackIt < tracks.size())
step--;
else
step++;
std::cerr << HTTP_R.GetHeader("Transport");
}
HTTP_R.Clean();
}
}//!
if(step == 5){
for(unsigned int cx = 0; cx < connections.size(); cx++){
// std::cerr <<"PLAY MF" << std::endl;
if(connections[cx].Receive()){
RTP::Packet* pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
/*std::cout << "Version = " << pakketje->getVersion() << std::endl;
std::cout << "Padding = " << pakketje->getPadding() << std::endl;
std::cout << "Extension = " << pakketje->getExtension() << std::endl;
std::cout << "Contributing sources = " << pakketje->getContribCount() << std::endl;
std::cout << "Marker = " << pakketje->getMarker() << std::endl;
std::cout << "Payload Type = " << pakketje->getPayloadType() << std::endl;
std::cout << "Sequence = " << pakketje->getSequence() << std::endl;
std::cout << "Timestamp = " << pakketje->getTimeStamp() << std::endl;
std::cout << "SSRC = " << pakketje->getSSRC() << std::endl;
std::cout << "datalen: " << connections[cx].data_len << std::endl;
std::cout << "payload:" << std::endl;*/
if(pakketje->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 <<std::setw(2) << std::setfill('0') << (int)connections[cx].data[i]<< std::dec;
}
std::cout << std::endl<<std::endl;
}
delete pakketje;
}
}
}
}
return 666;
}
}
int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0]);
conf.parseArgs(argc, argv);
return Analysers::analyseRTP();
}

View file

@ -1,204 +0,0 @@
#include <fcntl.h>
#include <iostream>
#include <map>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <fstream>
#include <unistd.h>
#include <sstream>
#include <signal.h>
#include <mist/ts_packet.h>
#include <mist/defines.h>
#include <mist/bitfields.h>
#include <mist/config.h>
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<d.size(); ++i){
if ((i < d.size() - 4) && d[i] == 0 && d[i+1] == 0 && d[i+2] == 0 && d[i+3] == 1){res << std::endl; counter = 0;}
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i]&0xff) << " ";
counter++;
if ((counter) % 32 == 31){res << std::endl;}
}
res << std::endl;
}
return res.str();
}
/// 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, uint32_t pidOnly){
std::map<unsigned long long, std::string> 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<unsigned long long, std::string>::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"));
}

View file

@ -1,55 +0,0 @@
#include <fcntl.h>
#include <iostream>
#include <map>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <string.h>
#include <fstream>
#include <unistd.h>
#include <sstream>
#include <signal.h>
#include <mist/ts_packet.h>
#include <mist/ts_stream.h>
#include <mist/config.h>
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<unsigned long long, std::string> 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"));
}

47
src/utils/util_amf.cpp Normal file
View file

@ -0,0 +1,47 @@
/// \file util_amf.cpp
/// Debugging tool for AMF data.
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <mist/amf.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <string>
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;
}