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/util.h
|
||||
${SOURCE_DIR}/lib/vorbis.h
|
||||
${SOURCE_DIR}/lib/opus.h
|
||||
)
|
||||
|
||||
########################################
|
||||
|
@ -164,6 +165,7 @@ set(libSources
|
|||
${SOURCE_DIR}/lib/ts_packet.cpp
|
||||
${SOURCE_DIR}/lib/util.cpp
|
||||
${SOURCE_DIR}/lib/vorbis.cpp
|
||||
${SOURCE_DIR}/lib/opus.cpp
|
||||
)
|
||||
|
||||
########################################
|
||||
|
@ -202,11 +204,16 @@ add_custom_command(TARGET mist
|
|||
)
|
||||
|
||||
########################################
|
||||
# MistServer - Analysers ` #
|
||||
# MistServer - Analysers #
|
||||
########################################
|
||||
macro(makeAnalyser analyserName format)
|
||||
add_executable(MistAnalyser${analyserName}
|
||||
src/analysers/${format}_analyser.cpp
|
||||
src/analysers/mist_analyse.cpp
|
||||
src/analysers/analyser.cpp
|
||||
src/analysers/analyser_${format}.cpp
|
||||
)
|
||||
set_target_properties(MistAnalyser${analyserName}
|
||||
PROPERTIES COMPILE_DEFINITIONS "ANALYSERHEADER=\"analyser_${format}.h\"; ANALYSERTYPE=Analyser${analyserName}"
|
||||
)
|
||||
target_link_libraries(MistAnalyser${analyserName}
|
||||
mist
|
||||
|
@ -220,10 +227,27 @@ endmacro()
|
|||
makeAnalyser(RTMP rtmp)
|
||||
makeAnalyser(FLV flv)
|
||||
makeAnalyser(DTSC dtsc)
|
||||
makeAnalyser(AMF amf)
|
||||
makeAnalyser(MP4 mp4)
|
||||
makeAnalyser(OGG ogg)
|
||||
makeAnalyser(RAX rax)
|
||||
|
||||
########################################
|
||||
# MistServer - Utilities #
|
||||
########################################
|
||||
macro(makeUtil utilName utilFile)
|
||||
add_executable(MistUtil${utilName}
|
||||
src/utils/util_${utilFile}.cpp
|
||||
)
|
||||
target_link_libraries(MistUtil${utilName}
|
||||
mist
|
||||
)
|
||||
install(
|
||||
TARGETS MistUtil${utilName}
|
||||
DESTINATION bin
|
||||
)
|
||||
endmacro()
|
||||
|
||||
makeUtil(RAX rax)
|
||||
makeUtil(AMF amf)
|
||||
|
||||
########################################
|
||||
# MistServer - Inputs #
|
||||
|
|
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