Analysers rewrite, mostly by Ramkoemar, partially by myself

This commit is contained in:
Thulinma 2017-05-02 11:43:25 +02:00
parent b4dc59d409
commit 506be4a64b
24 changed files with 890 additions and 700 deletions

View file

@ -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
View file

@ -0,0 +1,75 @@
#include "opus.h"
#include <sstream>
namespace Opus{
std::string Opus_prettyPacket(const char *part, int len){
if (len < 1){return "Invalid packet (0 byte length)";}
std::stringstream r;
char config = part[0] >> 3;
char code = part[0] & 3;
if ((part[0] & 4) == 4){
r << "Stereo, ";
}else{
r << "Mono, ";
}
if (config < 14){
r << "SILK, ";
if (config < 4){r << "NB, ";}
if (config < 8 && config > 3){r << "MB, ";}
if (config < 14 && config > 7){r << "WB, ";}
if (config % 4 == 0){r << "10ms";}
if (config % 4 == 1){r << "20ms";}
if (config % 4 == 2){r << "40ms";}
if (config % 4 == 3){r << "60ms";}
}
if (config < 16 && config > 13){
r << "Hybrid, ";
if (config < 14){
r << "SWB, ";
}else{
r << "FB, ";
}
if (config % 2 == 0){
r << "10ms";
}else{
r << "20ms";
}
}
if (config > 15){
r << "CELT, ";
if (config < 20){r << "NB, ";}
if (config < 24 && config > 19){r << "WB, ";}
if (config < 28 && config > 23){r << "SWB, ";}
if (config > 27){r << "FB, ";}
if (config % 4 == 0){r << "2.5ms";}
if (config % 4 == 1){r << "5ms";}
if (config % 4 == 2){r << "10ms";}
if (config % 4 == 3){r << "20ms";}
}
if (code == 0){
r << ": 1 packet (" << (len - 1) << "b)";
return r.str();
}
if (code == 1){
r << ": 2 packets (" << ((len - 1) / 2) << "b / " << ((len - 1) / 2) << "b)";
return r.str();
}
if (code == 2){
if (len < 2){return "Invalid packet (code 2 must be > 1 byte long)";}
if (part[1] < 252){
r << ": 2 packets (" << (int)part[1] << "b / " << (int)(len - 2 - part[1]) << "b)";
}else{
int ilen = part[1] + part[2] * 4;
r << ": 2 packets (" << ilen << "b / " << (int)(len - 3 - ilen) << "b)";
}
return r.str();
}
// code 3
bool VBR = (part[1] & 128) == 128;
bool pad = (part[1] & 64) == 64;
bool packets = (part[1] & 63);
r << ": " << packets << " packets (VBR = " << VBR << ", padding = " << pad << ")";
return r.str();
}
}

6
lib/opus.h Normal file
View file

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

View file

@ -1,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();
}

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

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

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

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

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

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

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

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

View file

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

View file

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

View 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);
};

View file

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

View file

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

View file

@ -0,0 +1,16 @@
#include ANALYSERHEADER
#include <mist/config.h>
#include <mist/defines.h>
int main(int argc, char *argv[]){
Util::Config conf(argv[0]);
ANALYSERTYPE::init(conf);
if (conf.parseArgs(argc, argv)){
conf.activate();
ANALYSERTYPE A(conf);
return A.run(conf);
}else{
return 2;
}
}

View file

@ -1,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();
}

View file

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

View file

@ -1,219 +0,0 @@
/// \file rtmp_analyser.cpp
/// Debugging tool for RTMP data.
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <mist/flv_tag.h>
#include <mist/amf.h>
#include <mist/rtmpchunks.h>
#include <mist/config.h>
#include <mist/socket.h>
#define DETAIL_RECONSTRUCT 1
#define DETAIL_EXPLICIT 2
#define DETAIL_VERBOSE 4
///\brief Holds everything unique to the analysers.
namespace Analysers {
///\brief Debugging tool for RTMP data.
///
///Expects RTMP data of one side of the conversation through stdin, outputs human-readable information to stderr.
///
///Will output FLV file to stdout, if available.
///
///Automatically skips the handshake data.
///\param conf The configuration parsed from the commandline.
///\return The return code of the analyser.
int analyseRTMP(Util::Config conf){
int Detail = conf.getInteger("detail");
if (Detail > 0){
fprintf(stderr, "Detail level set:\n");
if (Detail & DETAIL_RECONSTRUCT){
fprintf(stderr, " - Will reconstuct FLV file to stdout\n");
std::cout.write(FLV::Header, 13);
}
if (Detail & DETAIL_EXPLICIT){
fprintf(stderr, " - Will list explicit video/audio data information\n");
}
if (Detail & DETAIL_VERBOSE){
fprintf(stderr, " - Will list verbose chunk information\n");
}
}
std::string inbuffer;
inbuffer.reserve(3073);
while (std::cin.good() && inbuffer.size() < 3073){
inbuffer += std::cin.get();
} //read all of std::cin to temp
inbuffer.erase(0, 3073); //strip the handshake part
RTMPStream::rec_cnt += 3073;
RTMPStream::Chunk next;
FLV::Tag F; //FLV holder
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
unsigned int read_in = 0;
Socket::Buffer strbuf;
while (std::cin.good() || strbuf.size()){
if (next.Parse(strbuf)){
if (Detail & DETAIL_VERBOSE){
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp,
next.len, next.msg_type_id, next.msg_stream_id);
}
switch (next.msg_type_id){
case 0: //does not exist
fprintf(stderr, "Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i\n", read_in-inbuffer.size(), next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
return 0;
break; //happens when connection breaks unexpectedly
case 1: //set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
break;
case 2: //abort message - we ignore this one
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
//4 bytes of stream id to drop
break;
case 3: //ack
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
break;
case 4: {
short int ucmtype = ntohs(*(short int*)next.data.c_str());
switch (ucmtype){
case 0:
fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 1:
fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 2:
fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 3:
fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 4:
fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 6:
fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 7:
fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
case 31:
case 32:
//don't know, but not interesting anyway
break;
default:
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
break;
}
}
break;
case 5: //window size of other end
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
break;
case 6:
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
//4 bytes window size, 1 byte limit type (ignored)
fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size);
break;
case 8:
case 9:
if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){
F.ChunkLoader(next);
if (Detail & DETAIL_EXPLICIT){
std::cerr << "[" << F.tagTime() << "+" << F.offset() << "] " << F.tagType() << std::endl;
}
if (Detail & DETAIL_RECONSTRUCT){
std::cout.write(F.data, F.len);
}
}
break;
case 15:
fprintf(stderr, "Received AFM3 data message\n");
break;
case 16:
fprintf(stderr, "Received AFM3 shared object\n");
break;
case 17: {
fprintf(stderr, "Received AFM3 command message:\n");
char soort = next.data[0];
next.data = next.data.substr(1);
if (soort == 0){
amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl;
}else{
amf3data = AMF::parse3(next.data);
amf3data.Print();
}
}
break;
case 18: {
fprintf(stderr, "Received AFM0 data message (metadata):\n");
amfdata = AMF::parse(next.data);
amfdata.Print();
if (Detail & DETAIL_RECONSTRUCT){
F.ChunkLoader(next);
std::cout.write(F.data, F.len);
}
}
break;
case 19:
fprintf(stderr, "Received AFM0 shared object\n");
break;
case 20: { //AMF0 command message
fprintf(stderr, "Received AFM0 command message:\n");
amfdata = AMF::parse(next.data);
std::cerr << amfdata.Print() << std::endl;
}
break;
case 22:
if (Detail & DETAIL_RECONSTRUCT){
std::cout << next.data;
}
break;
default:
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
return 1;
break;
} //switch for type of chunk
}else{ //if chunk parsed
if (std::cin.good()){
unsigned int charCount = 0;
std::string tmpbuffer;
tmpbuffer.reserve(1024);
while (std::cin.good() && charCount < 1024){
char newchar = std::cin.get();
if (std::cin.good()){
tmpbuffer += newchar;
++read_in;
++charCount;
}
}
strbuf.append(tmpbuffer);
}else{
strbuf.get().clear();
}
}
}//while std::cin.good()
fprintf(stderr, "No more readable data\n");
return 0;
}
}
int main(int argc, char ** argv){
Util::Config conf = Util::Config(argv[0]);
conf.addOption("detail",
JSON::fromString(
"{\"arg_num\":1, \"arg\":\"integer\", \"default\":0, \"help\":\"Bitmask, 1 = Reconstruct, 2 = Explicit media info, 4 = Verbose chunks\"}"));
conf.parseArgs(argc, argv);
return Analysers::analyseRTMP(conf);
} //main

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

@ -0,0 +1,47 @@
/// \file util_amf.cpp
/// Debugging tool for AMF data.
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <mist/amf.h>
#include <mist/config.h>
#include <mist/defines.h>
#include <string>
int main(int argc, char **argv){
Util::Config conf(argv[0]);
JSON::Value opt;
opt["arg_num"] = 1ll;
opt["arg"] = "string";
opt["default"] = "-";
opt["help"] = "Filename to analyse, or - for standard input (default)";
conf.addOption("filename", opt);
conf.parseArgs(argc, argv);
std::string filename = conf.getString("filename");
if (filename.size() && filename != "-"){
int fp = open(filename.c_str(), O_RDONLY);
if (fp <= 0){
FAIL_MSG("Cannot open '%s': %s", filename.c_str(), strerror(errno));
return 1;
}
dup2(fp, STDIN_FILENO);
close(fp);
INFO_MSG("Parsing %s...", filename.c_str());
}else{
INFO_MSG("Parsing standard input...");
}
std::string amfBuffer;
// Read all of std::cin to amfBuffer
while (std::cin.good()){amfBuffer += std::cin.get();}
// Strip the invalid last character
amfBuffer.erase((amfBuffer.end() - 1));
// Parse into an AMF::Object
AMF::Object amfData = AMF::parse(amfBuffer);
// Print the output.
std::cout << amfData.Print() << std::endl;
return 0;
}