Analysers rewrite, mostly by Ramkoemar, partially by myself
This commit is contained in:
parent
b4dc59d409
commit
506be4a64b
24 changed files with 890 additions and 700 deletions
|
@ -128,6 +128,7 @@ set(libHeaders
|
||||||
${SOURCE_DIR}/lib/ts_packet.h
|
${SOURCE_DIR}/lib/ts_packet.h
|
||||||
${SOURCE_DIR}/lib/util.h
|
${SOURCE_DIR}/lib/util.h
|
||||||
${SOURCE_DIR}/lib/vorbis.h
|
${SOURCE_DIR}/lib/vorbis.h
|
||||||
|
${SOURCE_DIR}/lib/opus.h
|
||||||
)
|
)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -164,6 +165,7 @@ set(libSources
|
||||||
${SOURCE_DIR}/lib/ts_packet.cpp
|
${SOURCE_DIR}/lib/ts_packet.cpp
|
||||||
${SOURCE_DIR}/lib/util.cpp
|
${SOURCE_DIR}/lib/util.cpp
|
||||||
${SOURCE_DIR}/lib/vorbis.cpp
|
${SOURCE_DIR}/lib/vorbis.cpp
|
||||||
|
${SOURCE_DIR}/lib/opus.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -202,11 +204,16 @@ add_custom_command(TARGET mist
|
||||||
)
|
)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# MistServer - Analysers ` #
|
# MistServer - Analysers #
|
||||||
########################################
|
########################################
|
||||||
macro(makeAnalyser analyserName format)
|
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
|
||||||
|
@ -220,10 +227,27 @@ endmacro()
|
||||||
makeAnalyser(RTMP rtmp)
|
makeAnalyser(RTMP rtmp)
|
||||||
makeAnalyser(FLV flv)
|
makeAnalyser(FLV flv)
|
||||||
makeAnalyser(DTSC dtsc)
|
makeAnalyser(DTSC dtsc)
|
||||||
makeAnalyser(AMF amf)
|
|
||||||
makeAnalyser(MP4 mp4)
|
makeAnalyser(MP4 mp4)
|
||||||
makeAnalyser(OGG ogg)
|
makeAnalyser(OGG ogg)
|
||||||
makeAnalyser(RAX rax)
|
|
||||||
|
########################################
|
||||||
|
# MistServer - Utilities #
|
||||||
|
########################################
|
||||||
|
macro(makeUtil utilName utilFile)
|
||||||
|
add_executable(MistUtil${utilName}
|
||||||
|
src/utils/util_${utilFile}.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(MistUtil${utilName}
|
||||||
|
mist
|
||||||
|
)
|
||||||
|
install(
|
||||||
|
TARGETS MistUtil${utilName}
|
||||||
|
DESTINATION bin
|
||||||
|
)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
makeUtil(RAX rax)
|
||||||
|
makeUtil(AMF amf)
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# MistServer - Inputs #
|
# MistServer - Inputs #
|
||||||
|
|
75
lib/opus.cpp
Normal file
75
lib/opus.cpp
Normal 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
6
lib/opus.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Opus{
|
||||||
|
std::string Opus_prettyPacket(const char *part, int len);
|
||||||
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/// \file amf_analyser.cpp
|
|
||||||
/// Debugging tool for AMF data.
|
|
||||||
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <mist/amf.h>
|
|
||||||
#include <mist/config.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(){
|
|
||||||
std::string amfBuffer;
|
|
||||||
//Read all of std::cin to amfBuffer
|
|
||||||
while (std::cin.good()){
|
|
||||||
amfBuffer += std::cin.get();
|
|
||||||
}
|
|
||||||
//Strip the invalid last character
|
|
||||||
amfBuffer.erase((amfBuffer.end() - 1));
|
|
||||||
//Parse into an AMF::Object
|
|
||||||
AMF::Object amfData = AMF::parse(amfBuffer);
|
|
||||||
//Print the output.
|
|
||||||
std::cerr << amfData.Print() << std::endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char ** argv){
|
|
||||||
Util::Config conf = Util::Config(argv[0]);
|
|
||||||
conf.parseArgs(argc, argv);
|
|
||||||
|
|
||||||
return Analysers::analyseAMF();
|
|
||||||
}
|
|
||||||
|
|
80
src/analysers/analyser.cpp
Normal file
80
src/analysers/analyser.cpp
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <mist/timing.h>
|
||||||
|
|
||||||
|
/// Reads configuration and opens a passed filename replacing standard input if needed.
|
||||||
|
Analyser::Analyser(Util::Config &conf){
|
||||||
|
detail = conf.getInteger("detail");
|
||||||
|
mediaTime = 0;
|
||||||
|
upTime = Util::bootSecs();
|
||||||
|
isActive = &conf.is_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
///Opens the filename. Supports stdin and plain files.
|
||||||
|
bool Analyser::open(const std::string & filename){
|
||||||
|
if (filename.size() && filename != "-"){
|
||||||
|
int fp = ::open(filename.c_str(), O_RDONLY);
|
||||||
|
if (fp <= 0){
|
||||||
|
FAIL_MSG("Cannot open '%s': %s", filename.c_str(), strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dup2(fp, STDIN_FILENO);
|
||||||
|
close(fp);
|
||||||
|
INFO_MSG("Parsing %s...", filename.c_str());
|
||||||
|
}else{
|
||||||
|
INFO_MSG("Parsing standard input...");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops analysis by closing the standard input
|
||||||
|
void Analyser::stop(){
|
||||||
|
close(STDIN_FILENO);
|
||||||
|
std::cin.setstate(std::ios_base::eofbit);
|
||||||
|
}
|
||||||
|
|
||||||
|
///Checks if standard input is still valid.
|
||||||
|
bool Analyser::isOpen(){
|
||||||
|
return (*isActive) && std::cin.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main loop for all analysers. Reads packets while not interrupted, parsing and/or printing them.
|
||||||
|
int Analyser::run(Util::Config &conf){
|
||||||
|
isActive = &conf.is_active;
|
||||||
|
if (!open(conf.getString("filename"))){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
while (conf.is_active && isOpen()){
|
||||||
|
if (!parsePacket()){
|
||||||
|
if (isOpen()){
|
||||||
|
FAIL_MSG("Could not parse packet!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
INFO_MSG("Reached end of file");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets options common to all analysers.
|
||||||
|
/// Should generally be called by the init function of each analyser.
|
||||||
|
void Analyser::init(Util::Config &conf){
|
||||||
|
JSON::Value opt;
|
||||||
|
|
||||||
|
opt["arg_num"] = 1ll;
|
||||||
|
opt["arg"] = "string";
|
||||||
|
opt["default"] = "-";
|
||||||
|
opt["help"] = "Filename to analyse, or - for standard input (default)";
|
||||||
|
conf.addOption("filename", opt);
|
||||||
|
opt.null();
|
||||||
|
|
||||||
|
opt["long"] = "detail";
|
||||||
|
opt["short"] = "D";
|
||||||
|
opt["arg"] = "num";
|
||||||
|
opt["default"] = 2ll;
|
||||||
|
opt["help"] = "Detail level for analysis (0 = none, 2 = default, 10 = max)";
|
||||||
|
conf.addOption("detail", opt);
|
||||||
|
opt.null();
|
||||||
|
}
|
||||||
|
|
38
src/analysers/analyser.h
Normal file
38
src/analysers/analyser.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
#include <mist/config.h>
|
||||||
|
#include <mist/defines.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define DETAIL_LOW(msg, ...) \
|
||||||
|
if (detail >= 1){printf(msg "\n", ##__VA_ARGS__);}
|
||||||
|
#define DETAIL_MED(msg, ...) \
|
||||||
|
if (detail >= 2){printf(msg "\n", ##__VA_ARGS__);}
|
||||||
|
#define DETAIL_HI(msg, ...) \
|
||||||
|
if (detail >= 3){printf(msg "\n", ##__VA_ARGS__);}
|
||||||
|
#define DETAIL_VHI(msg, ...) \
|
||||||
|
if (detail >= 4){printf(msg "\n", ##__VA_ARGS__);}
|
||||||
|
#define DETAIL_XTR(msg, ...) \
|
||||||
|
if (detail >= 5){printf(msg "\n", ##__VA_ARGS__);}
|
||||||
|
|
||||||
|
class Analyser{
|
||||||
|
public:
|
||||||
|
// These contain standard functionality
|
||||||
|
Analyser(Util::Config &conf);
|
||||||
|
int run(Util::Config &conf);
|
||||||
|
virtual void stop();
|
||||||
|
virtual bool open(const std::string &filename);
|
||||||
|
virtual bool isOpen();
|
||||||
|
|
||||||
|
// These should be provided by analysers
|
||||||
|
static void init(Util::Config &conf);
|
||||||
|
virtual bool parsePacket(){return false;}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// These hold the current state and/or config
|
||||||
|
int detail; ///< Detail level of analyser
|
||||||
|
uint64_t mediaTime; ///< Timestamp in ms of last media packet received
|
||||||
|
uint64_t upTime; ///< Unix time of analyser start
|
||||||
|
uint64_t finTime; ///< Unix time of last packet received
|
||||||
|
bool *isActive; ///< Pointer to is_active bool from config
|
||||||
|
};
|
||||||
|
|
124
src/analysers/analyser_dtsc.cpp
Normal file
124
src/analysers/analyser_dtsc.cpp
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#include "analyser_dtsc.h"
|
||||||
|
#include <mist/h264.h>
|
||||||
|
|
||||||
|
void AnalyserDTSC::init(Util::Config &conf){
|
||||||
|
Analyser::init(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){
|
||||||
|
conn = Socket::Connection(0, fileno(stdin));
|
||||||
|
totalBytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyserDTSC::parsePacket(){
|
||||||
|
P.reInit(conn);
|
||||||
|
if (conn && !P){
|
||||||
|
FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!conn && !P){
|
||||||
|
stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (P.getVersion()){
|
||||||
|
case DTSC::DTSC_V1:{
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << "DTSCv1 packet: " << P.getScan().toPrettyString() << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DTSC::DTSC_V2:{
|
||||||
|
mediaTime = P.getTime();
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << "DTSCv2 packet (Track " << P.getTrackId() << ", time " << P.getTime()
|
||||||
|
<< "): " << P.getScan().toPrettyString() << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DTSC::DTSC_HEAD:{
|
||||||
|
if (detail >= 2){std::cout << "DTSC header: " << P.getScan().toPrettyString() << std::endl;}
|
||||||
|
if (detail == 1){
|
||||||
|
bool hasH264 = false;
|
||||||
|
bool hasAAC = false;
|
||||||
|
JSON::Value result;
|
||||||
|
std::stringstream issues;
|
||||||
|
DTSC::Meta M(P);
|
||||||
|
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin();
|
||||||
|
it != M.tracks.end(); it++){
|
||||||
|
JSON::Value track;
|
||||||
|
track["kbits"] = (long long)((double)it->second.bps * 8 / 1024);
|
||||||
|
track["codec"] = it->second.codec;
|
||||||
|
uint32_t shrtest_key = 0xFFFFFFFFul;
|
||||||
|
uint32_t longest_key = 0;
|
||||||
|
uint32_t shrtest_prt = 0xFFFFFFFFul;
|
||||||
|
uint32_t longest_prt = 0;
|
||||||
|
uint32_t shrtest_cnt = 0xFFFFFFFFul;
|
||||||
|
uint32_t longest_cnt = 0;
|
||||||
|
for (std::deque<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"){
|
||||||
|
track["width"] = (long long)it->second.width;
|
||||||
|
track["height"] = (long long)it->second.height;
|
||||||
|
track["fpks"] = it->second.fpks;
|
||||||
|
if (it->second.codec == "H264"){
|
||||||
|
h264::sequenceParameterSet sps;
|
||||||
|
sps.fromDTSCInit(it->second.init);
|
||||||
|
h264::SPSMeta spsData = sps.getCharacteristics();
|
||||||
|
track["h264"]["profile"] = spsData.profile;
|
||||||
|
track["h264"]["level"] = spsData.level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[it->second.getWritableIdentifier()] = track;
|
||||||
|
}
|
||||||
|
if ((hasAAC || hasH264) && M.tracks.size() > 1){
|
||||||
|
if (!hasAAC){issues << "HLS no audio!";}
|
||||||
|
if (!hasH264){issues << "HLS no video!";}
|
||||||
|
}
|
||||||
|
if (issues.str().size()){result["issues"] = issues.str();}
|
||||||
|
std::cout << result.toString() << std::endl;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DTSC::DTCM:{
|
||||||
|
if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalBytes += P.getDataLen();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
15
src/analysers/analyser_dtsc.h
Normal file
15
src/analysers/analyser_dtsc.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <mist/dtsc.h>
|
||||||
|
|
||||||
|
class AnalyserDTSC : public Analyser{
|
||||||
|
public:
|
||||||
|
AnalyserDTSC(Util::Config &conf);
|
||||||
|
bool parsePacket();
|
||||||
|
static void init(Util::Config &conf);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DTSC::Packet P;
|
||||||
|
Socket::Connection conn;
|
||||||
|
uint64_t totalBytes;
|
||||||
|
};
|
||||||
|
|
40
src/analysers/analyser_flv.cpp
Normal file
40
src/analysers/analyser_flv.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "analyser_flv.h"
|
||||||
|
|
||||||
|
void AnalyserFLV::init(Util::Config &conf){
|
||||||
|
Analyser::init(conf);
|
||||||
|
JSON::Value opt;
|
||||||
|
opt["long"] = "filter";
|
||||||
|
opt["short"] = "F";
|
||||||
|
opt["arg"] = "num";
|
||||||
|
opt["default"] = "0";
|
||||||
|
opt["help"] =
|
||||||
|
"Only print information about this tag type (8 = audio, 9 = video, 18 = meta, 0 = all)";
|
||||||
|
conf.addOption("filter", opt);
|
||||||
|
opt.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyserFLV::AnalyserFLV(Util::Config &conf) : Analyser(conf){
|
||||||
|
filter = conf.getInteger("filter");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyserFLV::parsePacket(){
|
||||||
|
if (feof(stdin)){
|
||||||
|
stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while (!feof(stdin)){
|
||||||
|
if (flvData.FileLoader(stdin)){break;}
|
||||||
|
if (feof(stdin)){
|
||||||
|
stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we arrive here, we've loaded a FLV packet
|
||||||
|
if (!filter || filter == flvData.data[0]){
|
||||||
|
DETAIL_MED("[%llu+%llu] %s", flvData.tagTime(), flvData.offset(), flvData.tagType().c_str());
|
||||||
|
}
|
||||||
|
mediaTime = flvData.tagTime();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
14
src/analysers/analyser_flv.h
Normal file
14
src/analysers/analyser_flv.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <mist/flv_tag.h> //FLV support
|
||||||
|
|
||||||
|
class AnalyserFLV : public Analyser{
|
||||||
|
public:
|
||||||
|
AnalyserFLV(Util::Config &conf);
|
||||||
|
bool parsePacket();
|
||||||
|
static void init(Util::Config &conf);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FLV::Tag flvData;
|
||||||
|
long long filter;
|
||||||
|
};
|
||||||
|
|
45
src/analysers/analyser_mp4.cpp
Normal file
45
src/analysers/analyser_mp4.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#include "analyser_mp4.h"
|
||||||
|
|
||||||
|
void AnalyserMP4::init(Util::Config &conf){
|
||||||
|
Analyser::init(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyserMP4::AnalyserMP4(Util::Config &conf) : Analyser(conf){
|
||||||
|
curPos = prePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyserMP4::parsePacket(){
|
||||||
|
prePos = curPos;
|
||||||
|
// Read in smart bursts until we have enough data
|
||||||
|
while (isOpen() && mp4Buffer.size() < neededBytes()){
|
||||||
|
uint64_t needed = neededBytes();
|
||||||
|
mp4Buffer.reserve(needed);
|
||||||
|
for (uint64_t i = mp4Buffer.size(); i < needed; ++i){
|
||||||
|
mp4Buffer += std::cin.get();
|
||||||
|
++curPos;
|
||||||
|
if (!std::cin.good()){mp4Buffer.erase(mp4Buffer.size() - 1, 1);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mp4Data.read(mp4Buffer)){
|
||||||
|
INFO_MSG("Read a box at position %d", prePos);
|
||||||
|
if (detail >= 2){std::cout << mp4Data.toPrettyString(0) << std::endl;}
|
||||||
|
///\TODO update mediaTime with the current timestamp
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
FAIL_MSG("Could not read box at position %llu", prePos);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates how many bytes we need to read a whole box.
|
||||||
|
uint64_t AnalyserMP4::neededBytes(){
|
||||||
|
if (mp4Buffer.size() < 4){return 4;}
|
||||||
|
uint64_t size = ntohl(((int *)mp4Buffer.data())[0]);
|
||||||
|
if (size != 1){return size;}
|
||||||
|
if (mp4Buffer.size() < 16){return 16;}
|
||||||
|
size = 0 + ntohl(((int *)mp4Buffer.data())[2]);
|
||||||
|
size <<= 32;
|
||||||
|
size += ntohl(((int *)mp4Buffer.data())[3]);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
17
src/analysers/analyser_mp4.h
Normal file
17
src/analysers/analyser_mp4.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <mist/mp4.h>
|
||||||
|
|
||||||
|
class AnalyserMP4 : public Analyser{
|
||||||
|
public:
|
||||||
|
AnalyserMP4(Util::Config &conf);
|
||||||
|
static void init(Util::Config &conf);
|
||||||
|
bool parsePacket();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t neededBytes();
|
||||||
|
std::string mp4Buffer;
|
||||||
|
MP4::Box mp4Data;
|
||||||
|
uint64_t curPos;
|
||||||
|
uint64_t prePos;
|
||||||
|
};
|
||||||
|
|
121
src/analysers/analyser_ogg.cpp
Normal file
121
src/analysers/analyser_ogg.cpp
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
#include "analyser_ogg.h"
|
||||||
|
#include <mist/opus.h>
|
||||||
|
|
||||||
|
/// \TODO EW EW EW EW EW EW EW EW EW EW EW
|
||||||
|
|
||||||
|
void AnalyserOGG::init(Util::Config &conf){
|
||||||
|
Analyser::init(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyserOGG::AnalyserOGG(Util::Config &conf) : Analyser(conf){}
|
||||||
|
|
||||||
|
bool AnalyserOGG::parsePacket(){
|
||||||
|
if (!oggPage.read(stdin)){return false;}
|
||||||
|
|
||||||
|
// We now have an Ogg page
|
||||||
|
// Print it, if we're at high detail level.
|
||||||
|
DETAIL_HI("%s", oggPage.toPrettyString().c_str());
|
||||||
|
|
||||||
|
// attempt to detect codec if this is the first page of a stream
|
||||||
|
if (oggPage.getHeaderType() & OGG::BeginOfStream){
|
||||||
|
if (memcmp("theora", oggPage.getSegment(0) + 1, 6) == 0){
|
||||||
|
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Theora";
|
||||||
|
}
|
||||||
|
if (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){
|
||||||
|
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis";
|
||||||
|
}
|
||||||
|
if (memcmp("OpusHead", oggPage.getSegment(0), 8) == 0){
|
||||||
|
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus";
|
||||||
|
}
|
||||||
|
if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){
|
||||||
|
INFO_MSG("Bitstream %llu recognized as %s", oggPage.getBitstreamSerialNumber(),
|
||||||
|
sn2Codec[oggPage.getBitstreamSerialNumber()]);
|
||||||
|
}else{
|
||||||
|
WARN_MSG("Bitstream %llu not recognized!", oggPage.getBitstreamSerialNumber());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << " Theora data" << std::endl;
|
||||||
|
}
|
||||||
|
static unsigned int numParts = 0;
|
||||||
|
static unsigned int keyCount = 0;
|
||||||
|
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||||
|
theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
|
||||||
|
if (tmpHeader.isHeader()){
|
||||||
|
if (tmpHeader.getHeaderType() == 0){kfgshift = tmpHeader.getKFGShift();}
|
||||||
|
}else{
|
||||||
|
if (!(oggPage.getHeaderType() == OGG::Continued) &&
|
||||||
|
tmpHeader.getFTYPE() == 0){// if keyframe
|
||||||
|
if (detail >= 3){
|
||||||
|
std::cout << "keyframe " << keyCount << " has " << numParts << " parts and granule " << (oggPage.getGranulePosition() >> kfgshift) << std::endl;
|
||||||
|
}
|
||||||
|
numParts = 0;
|
||||||
|
keyCount++;
|
||||||
|
}
|
||||||
|
if (oggPage.getHeaderType() != OGG::Continued || i){numParts++;}
|
||||||
|
}
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << tmpHeader.toPrettyString(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << " Vorbis data" << std::endl;
|
||||||
|
}
|
||||||
|
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||||
|
int len = oggPage.getAllSegments()[i].size();
|
||||||
|
vorbis::header tmpHeader((char *)oggPage.getSegment(i), len);
|
||||||
|
if (tmpHeader.isHeader() && detail >= 2){std::cout << tmpHeader.toPrettyString(4);}
|
||||||
|
}
|
||||||
|
}else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Opus"){
|
||||||
|
if (detail >= 2){
|
||||||
|
std::cout << " Opus data" << std::endl;
|
||||||
|
}
|
||||||
|
int offset = 0;
|
||||||
|
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||||
|
int len = oggPage.getAllSegments()[i].size();
|
||||||
|
const char *part = oggPage.getSegment(i);
|
||||||
|
if (len >= 8 && memcmp(part, "Opus", 4) == 0){
|
||||||
|
if (memcmp(part, "OpusHead", 8) == 0 && detail >= 2){
|
||||||
|
std::cout << " Version: " << (int)(part[8]) << std::endl;
|
||||||
|
std::cout << " Channels: " << (int)(part[9]) << std::endl;
|
||||||
|
std::cout << " Pre-skip: " << (int)(part[10] + (part[11] << 8)) << std::endl;
|
||||||
|
std::cout << " Orig. sample rate: "
|
||||||
|
<< (int)(part[12] + (part[13] << 8) + (part[14] << 16) + (part[15] << 24))
|
||||||
|
<< std::endl;
|
||||||
|
std::cout << " Gain: " << (int)(part[16] + (part[17] << 8)) << std::endl;
|
||||||
|
std::cout << " Channel map: " << (int)(part[18]) << std::endl;
|
||||||
|
if (part[18] > 0){
|
||||||
|
std::cout << " Channel map family " << (int)(part[18])
|
||||||
|
<< " not implemented - output incomplete" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (memcmp(part, "OpusTags", 8) == 0 && detail >= 3){
|
||||||
|
unsigned int vendor_len = part[8] + (part[9] << 8) + (part[10] << 16) + (part[11] << 24);
|
||||||
|
std::cout << " Vendor: " << std::string(part + 12, vendor_len) << std::endl;
|
||||||
|
const char *str_data = part + 12 + vendor_len;
|
||||||
|
unsigned int strings =
|
||||||
|
str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||||
|
std::cout << " Tags: (" << strings << ")" << std::endl;
|
||||||
|
str_data += 4;
|
||||||
|
for (unsigned int j = 0; j < strings; j++){
|
||||||
|
unsigned int strlen =
|
||||||
|
str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||||
|
str_data += 4;
|
||||||
|
std::cout << " [" << j << "] " << std::string((char *)str_data, strlen) << std::endl;
|
||||||
|
str_data += strlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (detail >= 4){
|
||||||
|
std::cout << " " << Opus::Opus_prettyPacket(part, len) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
16
src/analysers/analyser_ogg.h
Normal file
16
src/analysers/analyser_ogg.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
186
src/analysers/analyser_rtmp.cpp
Normal file
186
src/analysers/analyser_rtmp.cpp
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
/// \file analyser_rtmp.cpp
|
||||||
|
/// Debugging tool for RTMP data.
|
||||||
|
|
||||||
|
#include "analyser_rtmp.h"
|
||||||
|
|
||||||
|
void AnalyserRTMP::init(Util::Config &conf){
|
||||||
|
Analyser::init(conf);
|
||||||
|
JSON::Value opt;
|
||||||
|
opt["long"] = "reconstruct";
|
||||||
|
opt["short"] = "R";
|
||||||
|
opt["arg"] = "string";
|
||||||
|
opt["default"] = "";
|
||||||
|
opt["help"] = "Reconstruct FLV file from RTMP stream to the given filename";
|
||||||
|
conf.addOption("reconstruct", opt);
|
||||||
|
opt.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyserRTMP::AnalyserRTMP(Util::Config &conf) : Analyser(conf){
|
||||||
|
if (conf.getString("reconstruct") != ""){
|
||||||
|
reconstruct.open(conf.getString("reconstruct").c_str());
|
||||||
|
if (reconstruct.good()){
|
||||||
|
reconstruct.write(FLV::Header, 13);
|
||||||
|
WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyserRTMP::open(const std::string & filename){
|
||||||
|
if (!Analyser::open(filename)){return false;}
|
||||||
|
// Skip the 3073 byte handshake - there is no (truly) useful data in this.
|
||||||
|
MEDIUM_MSG("Skipping handshake...");
|
||||||
|
std::string inbuffer;
|
||||||
|
inbuffer.reserve(3073);
|
||||||
|
while (std::cin.good() && inbuffer.size() < 3073){inbuffer += std::cin.get();}
|
||||||
|
RTMPStream::rec_cnt += 3073;
|
||||||
|
inbuffer.erase(0, 3073); // strip the handshake part
|
||||||
|
MEDIUM_MSG("Handshake skipped");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AnalyserRTMP::parsePacket(){
|
||||||
|
// While we can't parse a packet,
|
||||||
|
while (!next.Parse(strbuf)){
|
||||||
|
// fill our internal buffer "strbuf" in (up to) 1024 byte chunks
|
||||||
|
if (std::cin.good()){
|
||||||
|
unsigned int charCount = 0;
|
||||||
|
std::string tmpbuffer;
|
||||||
|
tmpbuffer.reserve(1024);
|
||||||
|
while (std::cin.good() && charCount < 1024){
|
||||||
|
char newchar = std::cin.get();
|
||||||
|
if (std::cin.good()){
|
||||||
|
tmpbuffer += newchar;
|
||||||
|
++read_in;
|
||||||
|
++charCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strbuf.append(tmpbuffer);
|
||||||
|
}else{
|
||||||
|
// if we can't fill the buffer, and have no parsable packet(s), return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now know for sure that we've parsed a packet
|
||||||
|
DETAIL_HI("Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u",
|
||||||
|
next.headertype, next.cs_id, next.timestamp, next.len, next.msg_type_id,
|
||||||
|
next.msg_stream_id);
|
||||||
|
switch (next.msg_type_id){
|
||||||
|
case 0: // does not exist
|
||||||
|
DETAIL_LOW("Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i", read_in - strbuf.size(),
|
||||||
|
next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
|
||||||
|
return 0;
|
||||||
|
break; // happens when connection breaks unexpectedly
|
||||||
|
case 1: // set chunk size
|
||||||
|
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
|
||||||
|
DETAIL_MED("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
|
||||||
|
break;
|
||||||
|
case 2: // abort message - we ignore this one
|
||||||
|
DETAIL_MED("CTRL: Abort message: %i", ntohl(*(int *)next.data.c_str()));
|
||||||
|
// 4 bytes of stream id to drop
|
||||||
|
break;
|
||||||
|
case 3: // ack
|
||||||
|
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
|
||||||
|
DETAIL_MED("CTRL: Acknowledgement: %i", RTMPStream::snd_window_at);
|
||||||
|
break;
|
||||||
|
case 4:{
|
||||||
|
short int ucmtype = ntohs(*(short int *)next.data.c_str());
|
||||||
|
switch (ucmtype){
|
||||||
|
case 0:
|
||||||
|
DETAIL_MED("CTRL: User control message: stream begin %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
DETAIL_MED("CTRL: User control message: stream EOF %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
DETAIL_MED("CTRL: User control message: stream dry %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
DETAIL_MED("CTRL: User control message: setbufferlen %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
DETAIL_MED("CTRL: User control message: streamisrecorded %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
DETAIL_MED("CTRL: User control message: pingrequest %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
DETAIL_MED("CTRL: User control message: pingresponse %u",
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
case 31:
|
||||||
|
case 32:
|
||||||
|
// don't know, but not interesting anyway
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DETAIL_LOW("CTRL: User control message: UNKNOWN %hu - %u", ucmtype,
|
||||||
|
ntohl(*(unsigned int *)(next.data.c_str() + 2)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}break;
|
||||||
|
case 5: // window size of other end
|
||||||
|
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
|
||||||
|
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||||
|
DETAIL_MED("CTRL: Window size: %i", RTMPStream::rec_window_size);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str());
|
||||||
|
// 4 bytes window size, 1 byte limit type (ignored)
|
||||||
|
DETAIL_MED("CTRL: Set peer bandwidth: %i", RTMPStream::snd_window_size);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
if (detail >= 4 || reconstruct.good()){
|
||||||
|
F.ChunkLoader(next);
|
||||||
|
mediaTime = F.tagTime();
|
||||||
|
DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str());
|
||||||
|
if (reconstruct.good()){reconstruct.write(F.data, F.len);}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 15: DETAIL_MED("Received AFM3 data message"); break;
|
||||||
|
case 16: DETAIL_MED("Received AFM3 shared object"); break;
|
||||||
|
case 17:{
|
||||||
|
DETAIL_MED("Received AFM3 command message:");
|
||||||
|
char soort = next.data[0];
|
||||||
|
next.data = next.data.substr(1);
|
||||||
|
if (soort == 0){
|
||||||
|
amfdata = AMF::parse(next.data);
|
||||||
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||||
|
}else{
|
||||||
|
amf3data = AMF::parse3(next.data);
|
||||||
|
DETAIL_MED("%s", amf3data.Print().c_str());
|
||||||
|
}
|
||||||
|
}break;
|
||||||
|
case 18:{
|
||||||
|
DETAIL_MED("Received AFM0 data message (metadata):");
|
||||||
|
amfdata = AMF::parse(next.data);
|
||||||
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||||
|
if (reconstruct.good()){
|
||||||
|
F.ChunkLoader(next);
|
||||||
|
reconstruct.write(F.data, F.len);
|
||||||
|
}
|
||||||
|
}break;
|
||||||
|
case 19: DETAIL_MED("Received AFM0 shared object"); break;
|
||||||
|
case 20:{// AMF0 command message
|
||||||
|
DETAIL_MED("Received AFM0 command message:");
|
||||||
|
amfdata = AMF::parse(next.data);
|
||||||
|
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||||
|
}break;
|
||||||
|
case 22:
|
||||||
|
if (reconstruct.good()){reconstruct << next.data;}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FAIL_MSG(
|
||||||
|
"Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}// switch for type of chunk
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
22
src/analysers/analyser_rtmp.h
Normal file
22
src/analysers/analyser_rtmp.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#include "analyser.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <mist/flv_tag.h> //FLV support
|
||||||
|
#include <mist/rtmpchunks.h>
|
||||||
|
|
||||||
|
class AnalyserRTMP : public Analyser{
|
||||||
|
private:
|
||||||
|
RTMPStream::Chunk next; ///< Holds the most recently parsed RTMP chunk
|
||||||
|
FLV::Tag F;///< Holds the most recently created FLV packet
|
||||||
|
unsigned int read_in; ///< Amounts of bytes read to fill 'strbuf' so far
|
||||||
|
Socket::Buffer strbuf;///< Internal buffer from where 'next' is filled
|
||||||
|
AMF::Object amfdata;///< Last read AMF object
|
||||||
|
AMF::Object3 amf3data;///<Last read AMF3 object
|
||||||
|
std::ofstream reconstruct;///< If reconstructing, a valid file handle
|
||||||
|
|
||||||
|
public:
|
||||||
|
AnalyserRTMP(Util::Config & conf);
|
||||||
|
static void init(Util::Config & conf);
|
||||||
|
bool parsePacket();
|
||||||
|
virtual bool open(const std::string &filename);
|
||||||
|
};
|
||||||
|
|
|
@ -1,119 +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>
|
|
||||||
|
|
||||||
///\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;
|
|
||||||
}
|
|
||||||
result[it->second.getWritableIdentifier()] = track;
|
|
||||||
}
|
|
||||||
if ((hasAAC || hasH264) && F.getMeta().tracks.size() > 1){
|
|
||||||
if (!hasAAC){issues << "HLS no audio!";}
|
|
||||||
if (!hasH264){issues << "HLS no video!";}
|
|
||||||
}
|
|
||||||
if (issues.str().size()){result["issues"] = issues.str();}
|
|
||||||
std::cout << result.toString();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (F.getMeta().vod || F.getMeta().live){
|
|
||||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bPos = 0;
|
|
||||||
F.seek_bpos(0);
|
|
||||||
F.parseNext();
|
|
||||||
while (F.getPacket()){
|
|
||||||
switch (F.getPacket().getVersion()){
|
|
||||||
case DTSC::DTSC_V1: {
|
|
||||||
std::cout << "DTSCv1 packet: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DTSC::DTSC_V2: {
|
|
||||||
std::cout << "DTSCv2 packet (Track " << F.getPacket().getTrackId() << ", time " << F.getPacket().getTime() << "): " << F.getPacket().getScan().toPrettyString() << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DTSC::DTSC_HEAD: {
|
|
||||||
std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DTSC::DTCM: {
|
|
||||||
std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bPos = F.getBytePos();
|
|
||||||
F.parseNext();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an DTSC file and prints all readable data about it
|
|
||||||
int main(int argc, char ** argv){
|
|
||||||
Util::Config conf = Util::Config(argv[0]);
|
|
||||||
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the DTSC file to analyse.\"}"));
|
|
||||||
conf.parseArgs(argc, argv);
|
|
||||||
return Analysers::analyseDTSC(conf);
|
|
||||||
} //main
|
|
|
@ -1,35 +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>
|
|
||||||
|
|
||||||
///Debugging tool for FLV data.
|
|
||||||
/// Expects FLV data through stdin, outputs human-readable information to stderr.
|
|
||||||
int main(int argc, char ** argv){
|
|
||||||
Util::Config conf = Util::Config(argv[0]);
|
|
||||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
|
||||||
conf.parseArgs(argc, argv);
|
|
||||||
|
|
||||||
long long filter = conf.getInteger("filter");
|
|
||||||
|
|
||||||
FLV::Tag flvData; // Temporary storage for incoming FLV data.
|
|
||||||
while ( !feof(stdin)){
|
|
||||||
if (flvData.FileLoader(stdin)){
|
|
||||||
if (!filter || filter == flvData.data[0]){
|
|
||||||
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
16
src/analysers/mist_analyse.cpp
Normal file
16
src/analysers/mist_analyse.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
/// \file mp4_analyser.cpp
|
|
||||||
/// Debugging tool for MP4 data.
|
|
||||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
|
||||||
|
|
||||||
#include <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(){
|
|
||||||
std::string mp4Buffer;
|
|
||||||
//Read all of std::cin to mp4Buffer
|
|
||||||
while (std::cin.good()){
|
|
||||||
mp4Buffer += std::cin.get();
|
|
||||||
}
|
|
||||||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
|
||||||
|
|
||||||
MP4::Box mp4Data;
|
|
||||||
int dataSize = mp4Buffer.size();
|
|
||||||
int curPos = 0;
|
|
||||||
while (mp4Data.read(mp4Buffer)){
|
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
|
||||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
|
||||||
curPos += dataSize - mp4Buffer.size();
|
|
||||||
dataSize = mp4Buffer.size();
|
|
||||||
}
|
|
||||||
DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Debugging tool for MP4 data.
|
|
||||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
|
||||||
int main(int argc, char ** argv){
|
|
||||||
Util::Config conf = Util::Config(argv[0]);
|
|
||||||
conf.parseArgs(argc, argv);
|
|
||||||
return Analysers::analyseMP4();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,234 +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>
|
|
||||||
///\todo rewrite this analyser.
|
|
||||||
namespace Analysers {
|
|
||||||
std::string Opus_prettyPacket(const char * part, int len){
|
|
||||||
if (len < 1){
|
|
||||||
return "Invalid packet (0 byte length)";
|
|
||||||
}
|
|
||||||
std::stringstream r;
|
|
||||||
char config = part[0] >> 3;
|
|
||||||
char code = part[0] & 3;
|
|
||||||
if ((part[0] & 4) == 4){
|
|
||||||
r << "Stereo, ";
|
|
||||||
} else {
|
|
||||||
r << "Mono, ";
|
|
||||||
}
|
|
||||||
if (config < 14){
|
|
||||||
r << "SILK, ";
|
|
||||||
if (config < 4){
|
|
||||||
r << "NB, ";
|
|
||||||
}
|
|
||||||
if (config < 8 && config > 3){
|
|
||||||
r << "MB, ";
|
|
||||||
}
|
|
||||||
if (config < 14 && config > 7){
|
|
||||||
r << "WB, ";
|
|
||||||
}
|
|
||||||
if (config % 4 == 0){
|
|
||||||
r << "10ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 1){
|
|
||||||
r << "20ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 2){
|
|
||||||
r << "40ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 3){
|
|
||||||
r << "60ms";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (config < 16 && config > 13){
|
|
||||||
r << "Hybrid, ";
|
|
||||||
if (config < 14){
|
|
||||||
r << "SWB, ";
|
|
||||||
} else {
|
|
||||||
r << "FB, ";
|
|
||||||
}
|
|
||||||
if (config % 2 == 0){
|
|
||||||
r << "10ms";
|
|
||||||
} else {
|
|
||||||
r << "20ms";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (config > 15){
|
|
||||||
r << "CELT, ";
|
|
||||||
if (config < 20){
|
|
||||||
r << "NB, ";
|
|
||||||
}
|
|
||||||
if (config < 24 && config > 19){
|
|
||||||
r << "WB, ";
|
|
||||||
}
|
|
||||||
if (config < 28 && config > 23){
|
|
||||||
r << "SWB, ";
|
|
||||||
}
|
|
||||||
if (config > 27){
|
|
||||||
r << "FB, ";
|
|
||||||
}
|
|
||||||
if (config % 4 == 0){
|
|
||||||
r << "2.5ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 1){
|
|
||||||
r << "5ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 2){
|
|
||||||
r << "10ms";
|
|
||||||
}
|
|
||||||
if (config % 4 == 3){
|
|
||||||
r << "20ms";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (code == 0){
|
|
||||||
r << ": 1 packet (" << (len - 1) << "b)";
|
|
||||||
return r.str();
|
|
||||||
}
|
|
||||||
if (code == 1){
|
|
||||||
r << ": 2 packets (" << ((len - 1) / 2) << "b / " << ((len - 1) / 2) << "b)";
|
|
||||||
return r.str();
|
|
||||||
}
|
|
||||||
if (code == 2){
|
|
||||||
if (len < 2){
|
|
||||||
return "Invalid packet (code 2 must be > 1 byte long)";
|
|
||||||
}
|
|
||||||
if (part[1] < 252){
|
|
||||||
r << ": 2 packets (" << (int)part[1] << "b / " << (int)(len - 2 - part[1]) << "b)";
|
|
||||||
} else {
|
|
||||||
int ilen = part[1] + part[2] * 4;
|
|
||||||
r << ": 2 packets (" << ilen << "b / " << (int)(len - 3 - ilen) << "b)";
|
|
||||||
}
|
|
||||||
return r.str();
|
|
||||||
}
|
|
||||||
//code 3
|
|
||||||
bool VBR = (part[1] & 128) == 128;
|
|
||||||
bool pad = (part[1] & 64) == 64;
|
|
||||||
bool packets = (part[1] & 63);
|
|
||||||
r << ": " << packets << " packets (VBR = " << VBR << ", padding = " << pad << ")";
|
|
||||||
return r.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
int analyseOGG(int argc, char ** argv){
|
|
||||||
Util::Config conf = Util::Config(argv[0]);
|
|
||||||
conf.addOption("pages", JSON::fromString("{\"long\":\"pages\", \"short\":\"p\", \"long_off\":\"nopages\", \"short_off\":\"P\", \"default\":0, \"help\":\"Enable/disable printing of Ogg pages\"}"));
|
|
||||||
conf.parseArgs(argc, argv);
|
|
||||||
|
|
||||||
std::map<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 main(int argc, char ** argv){
|
|
||||||
return Analysers::analyseOGG(argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
47
src/utils/util_amf.cpp
Normal file
47
src/utils/util_amf.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue