Analyser unification finished
This commit is contained in:
parent
051a8c826b
commit
945e6f2d1a
44 changed files with 1264 additions and 2903 deletions
112
CMakeLists.txt
112
CMakeLists.txt
|
@ -168,6 +168,7 @@ set(libHeaders
|
|||
${SOURCE_DIR}/lib/util.h
|
||||
${SOURCE_DIR}/lib/vorbis.h
|
||||
${SOURCE_DIR}/lib/triggers.h
|
||||
${SOURCE_DIR}/lib/opus.h
|
||||
)
|
||||
|
||||
########################################
|
||||
|
@ -213,6 +214,7 @@ set(libSources
|
|||
${SOURCE_DIR}/lib/util.cpp
|
||||
${SOURCE_DIR}/lib/vorbis.cpp
|
||||
${SOURCE_DIR}/lib/triggers.cpp
|
||||
${SOURCE_DIR}/lib/opus.cpp
|
||||
)
|
||||
|
||||
########################################
|
||||
|
@ -251,11 +253,16 @@ add_custom_command(TARGET mist
|
|||
)
|
||||
|
||||
########################################
|
||||
# MistServer - Analysers ` #
|
||||
# MistServer - Analysers #
|
||||
########################################
|
||||
macro(makeAnalyser analyserName format)
|
||||
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
|
||||
|
@ -266,89 +273,40 @@ macro(makeAnalyser analyserName format)
|
|||
)
|
||||
endmacro()
|
||||
|
||||
add_executable(MistAnalyserMP4 src/analysers/analyser_mp4.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserMP4 mist)
|
||||
install(
|
||||
TARGETS MistAnalyserMP4
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserFLV src/analysers/analyser_flv.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserFLV mist)
|
||||
install(
|
||||
TARGETS MistAnalyserFLV
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserRTMP src/analysers/analyser_rtmp.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserRTMP mist)
|
||||
install(
|
||||
TARGETS MistAnalyserRTMP
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
#niet nodig?
|
||||
add_executable(MistAnalyserRTP src/analysers/analyser_rtp.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserRTP mist)
|
||||
install(
|
||||
TARGETS MistAnalyserRTP
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserTS src/analysers/analyser_ts.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserTS mist)
|
||||
install(
|
||||
TARGETS MistAnalyserTS
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserDASH src/analysers/analyser_dash.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserDASH mist)
|
||||
install(
|
||||
TARGETS MistAnalyserDASH
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserHLS src/analysers/analyser_hls.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserHLS mist)
|
||||
install(
|
||||
TARGETS MistAnalyserHLS
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
add_executable(MistAnalyserDTSC src/analysers/analyser_dtsc.cpp src/analysers/analyser.cpp)
|
||||
target_link_libraries(MistAnalyserDTSC mist)
|
||||
install(
|
||||
TARGETS MistAnalyserDTSC
|
||||
DESTINATION bin
|
||||
)
|
||||
|
||||
makeAnalyser(AMF amf) #hoeft niet
|
||||
makeAnalyser(RTMP rtmp)
|
||||
makeAnalyser(FLV flv)
|
||||
makeAnalyser(DTSC dtsc)
|
||||
makeAnalyser(OGG ogg)
|
||||
#makeAnalyser(RTP rtp) #LTS
|
||||
makeAnalyser(RTSP rtsp_rtp) #LTS ...
|
||||
#makeAnalyser(DTSC dtsc)
|
||||
|
||||
#makeAnalyser(TS ts) #LTS
|
||||
#makeAnalyser(HLS hls) #LTS #url
|
||||
#makeAnalyser(RTMP rtmp)
|
||||
#makeAnalyser(FLV flv)
|
||||
#makeAnalyser(MP4 mp4)
|
||||
makeAnalyser(OGG ogg)
|
||||
makeAnalyser(RAX rax)
|
||||
#makeAnalyser(RTP rtp) #LTS
|
||||
makeAnalyser(RTSP rtsp_rtp) #LTS
|
||||
#makeAnalyser(RTSP rtsp) #LTS #Currently broken. Horribly.
|
||||
makeAnalyser(TS ts) #LTS
|
||||
makeAnalyser(MP4 mp4) #LTS
|
||||
makeAnalyser(H264 h264) #LTS
|
||||
makeAnalyser(HLS hls) #LTS
|
||||
makeAnalyser(DASH dash) #LTS
|
||||
makeAnalyser(TSStream tsstream) #LTS
|
||||
makeAnalyser(Stats stats) #LTS
|
||||
|
||||
#LTS_START
|
||||
########################################
|
||||
# MistServer - Utilities #
|
||||
########################################
|
||||
macro(makeUtil utilName utilFile)
|
||||
add_executable(MistUtil${utilName}
|
||||
src/utils/util_${utilFile}.cpp
|
||||
)
|
||||
target_link_libraries(MistUtil${utilName}
|
||||
mist
|
||||
)
|
||||
install(
|
||||
TARGETS MistUtil${utilName}
|
||||
DESTINATION bin
|
||||
)
|
||||
endmacro()
|
||||
|
||||
makeUtil(Stats stats)
|
||||
makeUtil(RAX rax)
|
||||
makeUtil(AMF amf)
|
||||
if (DEFINED LOAD_BALANCE )
|
||||
makeAnalyser(Load load) #LTS
|
||||
makeUtil(Load load)
|
||||
endif()
|
||||
#LTS_END
|
||||
|
||||
########################################
|
||||
# MistServer - Inputs #
|
||||
|
|
75
lib/h264.cpp
75
lib/h264.cpp
|
@ -810,41 +810,66 @@ namespace h264 {
|
|||
out << " Message of type " << payloadType << ", " << payloadSize << " bytes long" << std::endl;
|
||||
}
|
||||
|
||||
nalUnit * nalFactory(char * _data, size_t _len, size_t & offset, bool annexb) {
|
||||
char * data = _data + offset;
|
||||
size_t len = _len - offset;
|
||||
nalUnit * result = NULL;
|
||||
if (len < 4){
|
||||
return result;
|
||||
}
|
||||
nalUnit * nalFactory(const char * _data, size_t _len, size_t & offset, bool annexb) {
|
||||
if (annexb){
|
||||
FAIL_MSG("Not supported in annexb mode yet");
|
||||
return result;
|
||||
//check if we have a start marker at the beginning, if so, move the offset over
|
||||
if (_len > offset && !_data[offset]){
|
||||
for (size_t i = offset+1; i < _len; ++i){
|
||||
if (_data[i] > 1){
|
||||
FAIL_MSG("Encountered bullshit AnnexB data..?");
|
||||
return 0;
|
||||
}
|
||||
uint32_t pktLen = Bit::btohl(data);
|
||||
if (len < 4 + pktLen){
|
||||
return result;
|
||||
if (_data[i] == 1){offset = i+1; break;}
|
||||
}
|
||||
switch (data[5] & 0x1F){
|
||||
}
|
||||
//now we know we're starting at real data. Yay!
|
||||
}
|
||||
if (_len < offset + 4){
|
||||
WARN_MSG("Not at least 4 bytes available - cancelling");
|
||||
return 0;
|
||||
}
|
||||
uint32_t pktLen = 0;
|
||||
if (!annexb){
|
||||
//read the 4b size in front
|
||||
pktLen = Bit::btohl(_data+offset);
|
||||
if (_len < 4 + pktLen){
|
||||
WARN_MSG("Not at least 4+%lu bytes available - cancelling", pktLen);
|
||||
return 0;
|
||||
}
|
||||
offset += 4;
|
||||
}
|
||||
const char * data = _data + offset;
|
||||
size_t len = _len - offset;
|
||||
if (annexb){
|
||||
//search for the next start marker
|
||||
for (size_t i = 1; i < len-2; ++i){
|
||||
if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 1){
|
||||
offset += i+2;
|
||||
while (i && !data[i]){--i;}
|
||||
pktLen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
offset += pktLen;
|
||||
}
|
||||
if (!pktLen){
|
||||
WARN_MSG("Cannot determine packet length - cancelling");
|
||||
return 0;
|
||||
}
|
||||
switch (data[0] & 0x1F){
|
||||
case 1:
|
||||
case 5:
|
||||
result = new codedSliceUnit(data + 4, pktLen);
|
||||
break;
|
||||
return new codedSliceUnit(data, pktLen);
|
||||
case 6:
|
||||
result = new seiUnit(data + 4, pktLen);
|
||||
break;
|
||||
return new seiUnit(data, pktLen);
|
||||
case 7:
|
||||
result = new spsUnit(data + 4, pktLen);
|
||||
break;
|
||||
return new spsUnit(data, pktLen);
|
||||
case 8:
|
||||
result = new ppsUnit(data + 4, pktLen);
|
||||
break;
|
||||
return new ppsUnit(data, pktLen);
|
||||
default:
|
||||
result = new nalUnit(data + 4, pktLen);
|
||||
break;
|
||||
return new nalUnit(data, pktLen);
|
||||
}
|
||||
offset += 4 + pktLen;
|
||||
return result;
|
||||
}
|
||||
|
||||
nalUnit * nalFactory(FILE * in, bool annexb) {
|
||||
|
|
|
@ -76,6 +76,7 @@ namespace h264 {
|
|||
public:
|
||||
nalUnit(const char * data, size_t len) : payload(data, len) {}
|
||||
uint8_t getType() { return payload[0] & 0x1F; }
|
||||
uint32_t getSize(){return payload.size();}
|
||||
virtual void toPrettyString(std::ostream & out){
|
||||
out << "Nal unit of type " << (((uint8_t)payload[0]) & 0x1F) << ", " << payload.size() << " bytes long" << std::endl;
|
||||
}
|
||||
|
@ -296,5 +297,5 @@ namespace h264 {
|
|||
|
||||
|
||||
nalUnit * nalFactory(FILE * in, bool annexb = true);
|
||||
nalUnit * nalFactory(char * data, size_t len, size_t & offset, bool annexb = true);
|
||||
nalUnit * nalFactory(const char * data, size_t len, size_t & offset, bool annexb = true);
|
||||
}
|
||||
|
|
|
@ -96,6 +96,35 @@ std::string HTTP::URL::getUrl() const{
|
|||
return ret;
|
||||
}
|
||||
|
||||
///Returns a URL object for the given link, resolved relative to the current URL object.
|
||||
HTTP::URL HTTP::URL::link(const std::string &l){
|
||||
//Full link
|
||||
if (l.find("://") < l.find('/')){return URL(l);}
|
||||
//Absolute link
|
||||
if (l[0] == '/'){
|
||||
if (l.size() > 1 && l[1] == '/'){
|
||||
//Same-protocol full link
|
||||
return URL(protocol+":"+l);
|
||||
}else{
|
||||
//Same-domain/port absolute link
|
||||
URL tmp = *this;
|
||||
tmp.args.clear();
|
||||
tmp.path = l.substr(1);
|
||||
//Abuse the fact that we don't check for arguments in getUrl()
|
||||
return URL(tmp.getUrl());
|
||||
}
|
||||
}
|
||||
//Relative link
|
||||
std::string tmpUrl = getUrl();
|
||||
size_t slashPos = tmpUrl.rfind('/');
|
||||
if (slashPos == std::string::npos){
|
||||
tmpUrl += "/";
|
||||
}else{
|
||||
tmpUrl.erase(slashPos+1);
|
||||
}
|
||||
return URL(tmpUrl+l);
|
||||
}
|
||||
|
||||
/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing.
|
||||
/// All this constructor does is call HTTP::Parser::Clean().
|
||||
HTTP::Parser::Parser() {
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace HTTP {
|
|||
///URL parsing class. Parses full URL into its subcomponents
|
||||
class URL {
|
||||
public:
|
||||
URL(const std::string & url);
|
||||
URL(const std::string & url = "");
|
||||
uint32_t getPort() const;
|
||||
std::string getUrl() const;
|
||||
std::string host;///< Hostname or IP address of URL
|
||||
|
@ -80,6 +80,7 @@ namespace HTTP {
|
|||
std::string port;///<Port of URL
|
||||
std::string path;///<Path after the first slash (not inclusive) but before any question mark
|
||||
std::string args;///<Everything after the question mark in the path, if it was present
|
||||
URL link(const std::string &l);
|
||||
};
|
||||
|
||||
}//HTTP namespace
|
||||
|
|
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,57 +0,0 @@
|
|||
/// \file amf_analyser.cpp
|
||||
/// Debugging tool for AMF data.
|
||||
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
///\brief Debugging tool for AMF data.
|
||||
///
|
||||
/// Expects AMF data through stdin, outputs human-readable information to stderr.
|
||||
///\return The return code of the analyser.
|
||||
int analyseAMF(Util::Config conf){
|
||||
|
||||
std::string filename = conf.getString("filename");
|
||||
|
||||
if(filename.length() > 0){
|
||||
int fp = open(filename.c_str(), O_RDONLY);
|
||||
|
||||
if(fp <= 0){
|
||||
FAIL_MSG("Cannot open file: %s",filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
dup2(fp, STDIN_FILENO);
|
||||
close(fp);
|
||||
}
|
||||
|
||||
std::string amfBuffer;
|
||||
//Read all of std::cin to amfBuffer
|
||||
while (std::cin.good()){
|
||||
amfBuffer += std::cin.get();
|
||||
}
|
||||
//Strip the invalid last character
|
||||
amfBuffer.erase((amfBuffer.end() - 1));
|
||||
//Parse into an AMF::Object
|
||||
AMF::Object amfData = AMF::parse(amfBuffer);
|
||||
//Print the output.
|
||||
std::cerr << amfData.Print() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the AMF file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
return Analysers::analyseAMF(conf);
|
||||
}
|
||||
|
|
@ -1,104 +1,99 @@
|
|||
#include "analyser.h"
|
||||
#include <iostream>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/timing.h>
|
||||
|
||||
analysers::analysers(Util::Config &config) {
|
||||
conf = config;
|
||||
|
||||
//set default detailLevel
|
||||
detail = 2;
|
||||
|
||||
if (conf.hasOption("filename")) {
|
||||
fileinput = conf.getString("filename").length() > 0;
|
||||
} else {
|
||||
fileinput = 0;
|
||||
}
|
||||
|
||||
analyse = conf.getString("mode") == "analyse";
|
||||
validate = conf.getString("mode") == "validate";
|
||||
|
||||
if (validate) {
|
||||
// conf.getOption("detail", true)[1] = 0ll;
|
||||
detail = 0;
|
||||
}
|
||||
|
||||
if (conf.hasOption("detail")) { detail = conf.getInteger("detail"); }
|
||||
|
||||
Prepare();
|
||||
/// Reads configuration and opens a passed filename replacing standard input if needed.
|
||||
Analyser::Analyser(Util::Config &conf){
|
||||
validate = conf.getBool("validate");
|
||||
detail = conf.getInteger("detail");
|
||||
mediaTime = 0;
|
||||
upTime = Util::bootSecs();
|
||||
isActive = &conf.is_active;
|
||||
}
|
||||
|
||||
int analysers::doAnalyse() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
analysers::~analysers() {
|
||||
}
|
||||
|
||||
void analysers::doValidate() {
|
||||
std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << endTime << std::endl;
|
||||
}
|
||||
|
||||
bool analysers::packetReady() {
|
||||
///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;
|
||||
}
|
||||
|
||||
// Fileinput as stdin
|
||||
void analysers::Prepare() {
|
||||
|
||||
if (fileinput) {
|
||||
filename = conf.getString("filename");
|
||||
int fp = open(filename.c_str(), O_RDONLY);
|
||||
|
||||
if (fp <= 0) {
|
||||
FAIL_MSG("Cannot open file: %s", filename.c_str());
|
||||
}
|
||||
|
||||
dup2(fp, STDIN_FILENO);
|
||||
close(fp);
|
||||
INFO_MSG("Parsing %s...", filename.c_str());
|
||||
}else{
|
||||
INFO_MSG("Parsing standard input...");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool analysers::hasInput() {
|
||||
// std::cout << std::cin.good() << std::endl;
|
||||
|
||||
return std::cin.good();
|
||||
// return !feof(stdin);
|
||||
/// Stops analysis by closing the standard input
|
||||
void Analyser::stop(){
|
||||
close(STDIN_FILENO);
|
||||
std::cin.setstate(std::ios_base::eofbit);
|
||||
}
|
||||
|
||||
int analysers::Run() {
|
||||
/// Prints validation message if needed
|
||||
Analyser::~Analyser(){
|
||||
if (validate){std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << mediaTime << std::endl;}
|
||||
}
|
||||
|
||||
if(mayExecute)
|
||||
{
|
||||
std::cout << "start analyser with detailLevel: " << detail << std::endl;
|
||||
endTime = 0;
|
||||
upTime = Util::bootSecs();
|
||||
///Checks if standard input is still valid.
|
||||
bool Analyser::isOpen(){
|
||||
return (*isActive) && std::cin.good();
|
||||
}
|
||||
|
||||
while (hasInput() && mayExecute) {
|
||||
while (packetReady()) {
|
||||
// std::cout << "in loop..." << std::endl;
|
||||
endTime = doAnalyse();
|
||||
/// Main loop for all analysers. Reads packets while not interrupted, parsing and/or printing them.
|
||||
int Analyser::run(Util::Config &conf){
|
||||
isActive = &conf.is_active;
|
||||
if (!open(conf.getString("filename"))){
|
||||
return 1;
|
||||
}
|
||||
while (conf.is_active && isOpen()){
|
||||
if (!parsePacket()){
|
||||
if (isOpen()){
|
||||
FAIL_MSG("Could not parse packet!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
INFO_MSG("Reached end of file");
|
||||
return 0;
|
||||
}
|
||||
if (validate){
|
||||
finTime = Util::bootSecs();
|
||||
|
||||
if (validate) {
|
||||
// std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl;
|
||||
doValidate();
|
||||
if ((finTime - upTime) > (mediaTime / 1000) + 2){
|
||||
FAIL_MSG("Media time more than 2 seconds behind!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void analysers::defaultConfig(Util::Config &conf) {
|
||||
conf.addOption("filename",
|
||||
JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the file to analysed.\"}"));
|
||||
/// 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;
|
||||
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to "
|
||||
"do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
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();
|
||||
|
||||
conf.addOption(
|
||||
"detail",
|
||||
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}"));
|
||||
opt["long"] = "validate";
|
||||
opt["short"] = "V";
|
||||
opt["help"] = "Enable validation mode (default off)";
|
||||
conf.addOption("validate", opt);
|
||||
opt.null();
|
||||
|
||||
opt["long"] = "detail";
|
||||
opt["short"] = "D";
|
||||
opt["arg"] = "num";
|
||||
opt["default"] = 2ll;
|
||||
opt["help"] = "Detail level for analysis (0 = none, 2 = default, 10 = max)";
|
||||
conf.addOption("detail", opt);
|
||||
opt.null();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,40 @@
|
|||
#pragma once
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <string>
|
||||
|
||||
class analysers
|
||||
{
|
||||
protected:
|
||||
bool fileinput;
|
||||
std::string filename;
|
||||
bool analyse;
|
||||
bool validate;
|
||||
bool mayExecute = true;
|
||||
int detail;
|
||||
#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__);}
|
||||
|
||||
long long int endTime;
|
||||
long long int upTime;
|
||||
long long int finTime;
|
||||
class Analyser{
|
||||
public:
|
||||
// These contain standard functionality
|
||||
Analyser(Util::Config &conf);
|
||||
~Analyser();
|
||||
int run(Util::Config &conf);
|
||||
virtual void stop();
|
||||
virtual bool open(const std::string &filename);
|
||||
virtual bool isOpen();
|
||||
|
||||
bool useBuffer;
|
||||
// These should be provided by analysers
|
||||
static void init(Util::Config &conf);
|
||||
virtual bool parsePacket(){return false;}
|
||||
|
||||
public:
|
||||
Util::Config conf;
|
||||
analysers(Util::Config &config);
|
||||
~analysers();
|
||||
|
||||
void Prepare();
|
||||
int Run();
|
||||
virtual int doAnalyse();
|
||||
virtual void doValidate();
|
||||
virtual bool hasInput();
|
||||
|
||||
static void defaultConfig(Util::Config & conf);
|
||||
//int Analyse();
|
||||
|
||||
virtual bool packetReady();
|
||||
protected:
|
||||
// These hold the current state and/or config
|
||||
bool validate; ///< True of validation mode is enabled
|
||||
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
|
||||
};
|
||||
|
||||
|
|
|
@ -1,124 +1,124 @@
|
|||
#include <iostream>
|
||||
#include <mist/defines.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "analyser_dtsc.h"
|
||||
#include <mist/config.h>
|
||||
#include <mist/h264.h>
|
||||
#include <mist/json.h>
|
||||
|
||||
dtscAnalyser::dtscAnalyser(Util::Config config) : analysers(config) {
|
||||
conn = Socket::Connection(fileno(stdout), fileno(stdin));
|
||||
std::cout << "connection initialized" << std::endl;
|
||||
void AnalyserDTSC::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
}
|
||||
|
||||
F.reInit(conn);
|
||||
AnalyserDTSC::AnalyserDTSC(Util::Config &conf) : Analyser(conf){
|
||||
conn = Socket::Connection(0, fileno(stdin));
|
||||
totalBytes = 0;
|
||||
}
|
||||
|
||||
// F = DTSC::Packet(config.getString("filename"));
|
||||
if (!F) {
|
||||
std::cerr << "Not a valid DTSC file" << std::endl;
|
||||
mayExecute = false;
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
if (F.getVersion() == DTSC::DTSC_HEAD) // for meta
|
||||
{
|
||||
DTSC::Meta m(F);
|
||||
|
||||
if (detail > 0) {
|
||||
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;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = m.tracks.begin(); it != m.tracks.end(); it++) {
|
||||
std::stringstream issues;
|
||||
DTSC::Meta M(P);
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = M.tracks.begin();
|
||||
it != M.tracks.end(); it++){
|
||||
JSON::Value track;
|
||||
if (it->second.type == "video") {
|
||||
std::stringstream tStream;
|
||||
track["resolution"] = JSON::Value((long long)it->second.width).asString() + "x" + JSON::Value((long long)it->second.height).asString();
|
||||
track["fps"] = (long long)((double)it->second.fpks / 1000);
|
||||
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;
|
||||
tStream << it->second.bps * 8 << " b/s, " << (double)it->second.bps * 8 / 1024 << " kb/s, " << (double)it->second.bps * 8 / 1024 / 1024
|
||||
<< " mb/s";
|
||||
track["bitrate"] = tStream.str();
|
||||
tStream.str("");
|
||||
track["keyframe_duration"] = (long long)((float)(it->second.lastms - it->second.firstms) / it->second.keys.size());
|
||||
tStream << ((double)(it->second.lastms - it->second.firstms) / it->second.keys.size()) / 1000;
|
||||
track["keyframe_interval"] = tStream.str();
|
||||
|
||||
tStream.str("");
|
||||
if (it->second.codec == "H264") {
|
||||
if (it->second.codec == "H264"){
|
||||
h264::sequenceParameterSet sps;
|
||||
sps.fromDTSCInit(it->second.init);
|
||||
h264::SPSMeta spsData = sps.getCharacteristics();
|
||||
track["encoding"]["width"] = spsData.width;
|
||||
track["encoding"]["height"] = spsData.height;
|
||||
tStream << spsData.fps;
|
||||
track["encoding"]["fps"] = tStream.str();
|
||||
track["encoding"]["profile"] = spsData.profile;
|
||||
track["encoding"]["level"] = spsData.level;
|
||||
track["h264"]["profile"] = spsData.profile;
|
||||
track["h264"]["level"] = spsData.level;
|
||||
}
|
||||
}
|
||||
if (it->second.type == "audio") {
|
||||
std::stringstream tStream;
|
||||
tStream << it->second.bps * 8 << " b/s, " << (double)it->second.bps * 8 / 1024 << " kb/s, " << (double)it->second.bps * 8 / 1024 / 1024
|
||||
<< " mb/s";
|
||||
track["bitrate"] = tStream.str();
|
||||
track["keyframe_interval"] = (long long)((float)(it->second.lastms - it->second.firstms) / it->second.keys.size());
|
||||
}
|
||||
result[it->second.getWritableIdentifier()] = track;
|
||||
}
|
||||
std::cout << result.toString();
|
||||
if ((hasAAC || hasH264) && M.tracks.size() > 1){
|
||||
if (!hasAAC){issues << "HLS no audio!";}
|
||||
if (!hasH264){issues << "HLS no video!";}
|
||||
}
|
||||
|
||||
if (m.vod || m.live) { m.toPrettyString(std::cout, 0, 0x03); }
|
||||
if (issues.str().size()){result["issues"] = issues.str();}
|
||||
std::cout << result.toString() << std::endl;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool dtscAnalyser::packetReady() {
|
||||
return (F.getDataLen() > 0);
|
||||
}
|
||||
|
||||
bool dtscAnalyser::hasInput() {
|
||||
return F;
|
||||
}
|
||||
|
||||
int dtscAnalyser::doAnalyse() {
|
||||
if (analyse) { // always analyse..?
|
||||
switch (F.getVersion()) {
|
||||
case DTSC::DTSC_V1: {
|
||||
std::cout << "DTSCv1 packet: " << F.getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTSC_V2: {
|
||||
std::cout << "DTSCv2 packet (Track " << F.getTrackId() << ", time " << F.getTime() << "): " << F.getScan().toPrettyString() << std::endl;
|
||||
case DTSC::DTCM:{
|
||||
if (detail >= 2){std::cout << "DTCM command: " << P.getScan().toPrettyString() << std::endl;}
|
||||
break;
|
||||
}
|
||||
case DTSC::DTSC_HEAD: {
|
||||
std::cout << "DTSC header: " << F.getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTCM: {
|
||||
std::cout << "DTCM command: " << F.getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
default: DEBUG_MSG(DLVL_WARN, "Invalid dtsc packet @ bpos %llu", totalBytes); break;
|
||||
}
|
||||
default: FAIL_MSG("Invalid DTSC packet @ byte %llu", totalBytes); break;
|
||||
}
|
||||
|
||||
totalBytes += F.getDataLen();
|
||||
|
||||
F.reInit(conn);
|
||||
return totalBytes;
|
||||
totalBytes += P.getDataLen();
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
|
||||
analysers::defaultConfig(conf);
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
dtscAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
#include <mist/config.h>
|
||||
#include "analyser.h"
|
||||
#include <mist/dtsc.h>
|
||||
|
||||
class dtscAnalyser : public analysers
|
||||
{
|
||||
DTSC::Packet F;
|
||||
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;
|
||||
|
||||
public:
|
||||
dtscAnalyser(Util::Config config);
|
||||
bool packetReady();
|
||||
bool hasInput();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
// void doValidate();
|
||||
};
|
||||
|
||||
|
|
|
@ -1,73 +1,40 @@
|
|||
#include "analyser_flv.h"
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <string>
|
||||
|
||||
flvAnalyser::flvAnalyser(Util::Config config) : analysers(config) {
|
||||
|
||||
if(fileinput && open(filename.c_str(), O_RDONLY) <= 0)
|
||||
{
|
||||
mayExecute = false;
|
||||
return;
|
||||
}
|
||||
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");
|
||||
FLV::Tag flvData; // Temporary storage for incoming FLV data.
|
||||
}
|
||||
|
||||
|
||||
//check for flv data
|
||||
char flvHeader[3];
|
||||
std::cin.read(flvHeader,3);
|
||||
|
||||
if(flvHeader[0] != 0x46 || flvHeader[1] != 0x4C || flvHeader[2] != 0x56)
|
||||
{
|
||||
FAIL_MSG("No FLV Signature found!");
|
||||
mayExecute = false;
|
||||
return;
|
||||
bool AnalyserFLV::parsePacket(){
|
||||
if (feof(stdin)){
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
std::cin.seekg(0);
|
||||
}
|
||||
|
||||
/*
|
||||
void flvAnalyser::doValidate()
|
||||
{
|
||||
std::cout << "dfasdfsdafdsf" << std::endl;
|
||||
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl;
|
||||
}
|
||||
*/
|
||||
|
||||
bool flvAnalyser::hasInput() {
|
||||
return !feof(stdin);
|
||||
}
|
||||
|
||||
bool flvAnalyser::packetReady() {
|
||||
return flvData.FileLoader(stdin);
|
||||
}
|
||||
|
||||
int flvAnalyser::doAnalyse() {
|
||||
// std::cout<< "do analyse" << std::endl;
|
||||
|
||||
if (analyse) { // always analyse..?
|
||||
if (!filter || filter == flvData.data[0]) {
|
||||
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl;
|
||||
while (!feof(stdin)){
|
||||
if (flvData.FileLoader(stdin)){break;}
|
||||
if (feof(stdin)){
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
endTime = flvData.tagTime();
|
||||
|
||||
return endTime;
|
||||
// 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;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info "
|
||||
"about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
||||
|
||||
analysers::defaultConfig(conf);
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
flvAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/config.h>
|
||||
#include "analyser.h"
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
|
||||
class flvAnalyser : public analysers
|
||||
{
|
||||
class AnalyserFLV : public Analyser{
|
||||
public:
|
||||
AnalyserFLV(Util::Config &conf);
|
||||
bool parsePacket();
|
||||
static void init(Util::Config &conf);
|
||||
|
||||
private:
|
||||
FLV::Tag flvData;
|
||||
long long filter;
|
||||
|
||||
public:
|
||||
flvAnalyser(Util::Config config);
|
||||
bool packetReady();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
bool hasInput();
|
||||
// void doValidate();
|
||||
};
|
||||
|
||||
|
|
58
src/analysers/analyser_h264.cpp
Normal file
58
src/analysers/analyser_h264.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
/// \file analyser_h264.cpp
|
||||
/// Reads H264 data and prints it in human-readable format.
|
||||
|
||||
#include "analyser_h264.h"
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/bitstream.h>
|
||||
#include <mist/h264.h>
|
||||
|
||||
void AnalyserH264::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
JSON::Value opt;
|
||||
opt["long"] = "size-prepended";
|
||||
opt["short"] = "S";
|
||||
opt["help"] = "Parse size-prepended style instead of Annex B style";
|
||||
conf.addOption("size-prepended", opt);
|
||||
opt.null();
|
||||
}
|
||||
|
||||
AnalyserH264::AnalyserH264(Util::Config &conf) : Analyser(conf){
|
||||
curPos = prePos = 0;
|
||||
sizePrepended = conf.getBool("size-prepended");
|
||||
}
|
||||
|
||||
bool AnalyserH264::parsePacket(){
|
||||
prePos = curPos;
|
||||
// Read in smart bursts until we have enough data
|
||||
while (isOpen() && dataBuffer.size() < neededBytes()){
|
||||
uint64_t needed = neededBytes();
|
||||
dataBuffer.reserve(needed);
|
||||
for (uint64_t i = dataBuffer.size(); i < needed; ++i){
|
||||
dataBuffer += std::cin.get();
|
||||
++curPos;
|
||||
if (!std::cin.good()){dataBuffer.erase(dataBuffer.size() - 1, 1);}
|
||||
}
|
||||
}
|
||||
|
||||
size_t size = 0;
|
||||
h264::nalUnit *nalPtr =
|
||||
h264::nalFactory(dataBuffer.data(), dataBuffer.size(), size, !sizePrepended);
|
||||
if (!nalPtr){
|
||||
FAIL_MSG("Could not read a NAL unit at position %llu", prePos);
|
||||
return false;
|
||||
}
|
||||
HIGH_MSG("Read a %lu-byte NAL unit at position %llu", size, prePos);
|
||||
dataBuffer.erase(0, size); // erase the NAL unit we just read
|
||||
if (detail >= 2){nalPtr->toPrettyString(std::cout);}
|
||||
///\TODO update mediaTime with current timestamp
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t AnalyserH264::neededBytes(){
|
||||
// We buffer a megabyte if AnnexB
|
||||
if (!sizePrepended){return 1024 * 1024;}
|
||||
// otherwise, buffer the exact size needed
|
||||
if (dataBuffer.size() < 4){return 4;}
|
||||
return Bit::btohl(dataBuffer.data())+4;
|
||||
}
|
||||
|
15
src/analysers/analyser_h264.h
Normal file
15
src/analysers/analyser_h264.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include "analyser.h"
|
||||
|
||||
class AnalyserH264 : public Analyser{
|
||||
public:
|
||||
AnalyserH264(Util::Config &conf);
|
||||
static void init(Util::Config &conf);
|
||||
bool parsePacket();
|
||||
|
||||
private:
|
||||
std::string dataBuffer;
|
||||
uint64_t prePos, curPos;
|
||||
uint64_t neededBytes();
|
||||
bool sizePrepended;
|
||||
};
|
||||
|
|
@ -9,213 +9,175 @@
|
|||
#include <string.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
// http://patchy.ddvtech.com:8080/hls/bbb/index.m3u8
|
||||
//
|
||||
void AnalyserHLS::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
JSON::Value opt;
|
||||
opt["long"] = "reconstruct";
|
||||
opt["short"] = "R";
|
||||
opt["arg"] = "string";
|
||||
opt["default"] = "";
|
||||
opt["help"] = "Reconstruct TS file from HLS to the given filename";
|
||||
conf.addOption("reconstruct", opt);
|
||||
opt.null();
|
||||
}
|
||||
|
||||
std::deque<HLSPart> getParts(std::string &body, std::string &uri) {
|
||||
size_t slashPos = uri.rfind('/');
|
||||
std::string uri_prefix = uri.substr(0, slashPos + 1);
|
||||
std::deque<HLSPart> out;
|
||||
void AnalyserHLS::getParts(const std::string &body){
|
||||
std::stringstream data(body);
|
||||
std::string line;
|
||||
unsigned int start = 0;
|
||||
unsigned int durat = 0;
|
||||
do {
|
||||
line = "";
|
||||
uint64_t no = 0;
|
||||
float durat = 0;
|
||||
refreshAt = Util::bootSecs() + 10;
|
||||
while (data.good()){
|
||||
std::getline(data, line);
|
||||
if (line.size() && *line.rbegin() == '\r') { line.resize(line.size() - 1); }
|
||||
if (line != "") {
|
||||
if (line[0] != '#') {
|
||||
out.push_back(HLSPart(uri_prefix + line, start, durat));
|
||||
start += durat;
|
||||
} else {
|
||||
if (line.substr(0, 8) == "#EXTINF:") { durat = atof(line.substr(8).c_str()) * 1000; }
|
||||
}
|
||||
}
|
||||
} while (line != "");
|
||||
return out;
|
||||
}
|
||||
|
||||
hlsAnalyser::hlsAnalyser(Util::Config config) : analysers(config) {
|
||||
port = 80;
|
||||
url = conf.getString("url");
|
||||
if (url.substr(0, 7) != "http://") {
|
||||
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
|
||||
mayExecute = false;
|
||||
if (line.size() && *line.rbegin() == '\r'){line.resize(line.size() - 1);}
|
||||
if (!line.size()){continue;}
|
||||
if (line[0] != '#'){
|
||||
if (line.find("m3u") != std::string::npos){
|
||||
root = root.link(line);
|
||||
INFO_MSG("Found a sub-playlist, re-targeting %s", root.getUrl().c_str());
|
||||
refreshAt = Util::bootSecs();
|
||||
return;
|
||||
}
|
||||
url = url.substr(7);
|
||||
|
||||
//check if url ends with .m3u8?
|
||||
if((url.find('/') == std::string::npos) || (url.find(".m3u") == std::string::npos && url.find(".m3u8") == std::string::npos))
|
||||
{
|
||||
std::cout << "incorrect url"<<std::endl;
|
||||
mayExecute = false;
|
||||
return;
|
||||
if (!parsedPart || no > parsedPart){
|
||||
HTTP::URL newURL = root.link(line);
|
||||
INFO_MSG("Discovered: %s", newURL.getUrl().c_str());
|
||||
parts.push_back(HLSPart(newURL, no, durat));
|
||||
}
|
||||
++no;
|
||||
}else{
|
||||
if (line.substr(0, 8) == "#EXTINF:"){durat = atof(line.c_str() + 8) * 1000;}
|
||||
if (line.substr(0, 22) == "#EXT-X-MEDIA-SEQUENCE:"){no = atoll(line.c_str() + 22);}
|
||||
if (line.substr(0, 14) == "#EXT-X-ENDLIST"){refreshAt = 0;}
|
||||
if (line.substr(0, 22) == "#EXT-X-TARGETDURATION:" && refreshAt){
|
||||
refreshAt = Util::bootSecs() + atoll(line.c_str() + 22) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
server = url.substr(0, url.find('/'));
|
||||
url = url.substr(url.find('/'));
|
||||
|
||||
if (server.find(':') != std::string::npos) {
|
||||
port = atoi(server.substr(server.find(':') + 1).c_str());
|
||||
server = server.substr(0, server.find(':'));
|
||||
}
|
||||
|
||||
startTime = Util::bootSecs();
|
||||
abortTime = conf.getInteger("abort");
|
||||
|
||||
playlist = url;
|
||||
repeat = true; // init to true, analyse at least one time
|
||||
lastDown = "";
|
||||
pos = 0;
|
||||
output = (conf.getString("mode") == "output");
|
||||
}
|
||||
|
||||
void hlsAnalyser::doValidate() {
|
||||
long long int endTime = Util::bootSecs();
|
||||
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
|
||||
/// Returns true if we either still have parts to download, or are still refreshing the playlist.
|
||||
bool AnalyserHLS::isOpen(){
|
||||
return (*isActive) && (parts.size() || refreshAt);
|
||||
}
|
||||
|
||||
int hlsAnalyser::doAnalyse() {
|
||||
repeat = false;
|
||||
while (url.size() > 4 && (url.find(".m3u") != std::string::npos || url.find(".m3u8") != std::string::npos)) {
|
||||
playlist = url;
|
||||
DEBUG_MSG(DLVL_DEVEL, "Retrieving playlist: %s", url.c_str());
|
||||
if (!conn) { conn = Socket::Connection(server, port, false); }
|
||||
HTTP::Parser H;
|
||||
H.url = url;
|
||||
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
|
||||
void AnalyserHLS::stop(){
|
||||
parts.clear();
|
||||
refreshAt = 0;
|
||||
}
|
||||
|
||||
bool AnalyserHLS::open(const std::string &url){
|
||||
root = HTTP::URL(url);
|
||||
if (root.protocol != "http"){
|
||||
FAIL_MSG("Only http protocol is supported (%s not supported)", root.protocol.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
AnalyserHLS::AnalyserHLS(Util::Config &conf) : Analyser(conf){
|
||||
if (conf.getString("reconstruct") != ""){
|
||||
reconstruct.open(conf.getString("reconstruct").c_str());
|
||||
if (reconstruct.good()){
|
||||
WARN_MSG("Will reconstruct to %s", conf.getString("reconstruct").c_str());
|
||||
}
|
||||
}
|
||||
hlsTime = 0;
|
||||
parsedPart = 0;
|
||||
refreshAt = Util::bootSecs();
|
||||
}
|
||||
|
||||
/// Downloads the given URL into 'H', returns true on success.
|
||||
/// Makes at most 5 attempts, and will wait no longer than 5 seconds without receiving data.
|
||||
bool AnalyserHLS::download(const HTTP::URL &link){
|
||||
if (!link.host.size()){return false;}
|
||||
INFO_MSG("Retrieving %s", link.getUrl().c_str());
|
||||
unsigned int loop = 6; // max 5 attempts
|
||||
while (--loop){// loop while we are unsuccessful
|
||||
H.Clean();
|
||||
// Reconnect if needed
|
||||
if (!conn || link.host != connectedHost || link.getPort() != connectedPort){
|
||||
conn.close();
|
||||
connectedHost = link.host;
|
||||
connectedPort = link.getPort();
|
||||
conn = Socket::Connection(connectedHost, connectedPort, true);
|
||||
}
|
||||
H.url = "/" + link.path;
|
||||
if (link.port.size()){
|
||||
H.SetHeader("Host", link.host + ":" + link.port);
|
||||
}else{
|
||||
H.SetHeader("Host", link.host);
|
||||
}
|
||||
H.SendRequest(conn);
|
||||
H.Clean();
|
||||
while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {}
|
||||
parts = getParts(H.body, url);
|
||||
if (!parts.size()) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Playlist parsing error - cancelling. state: %s/%s body size: %u", conn ? "Conn" : "Disconn",
|
||||
(Util::bootSecs() < (startTime + abortTime)) ? "NoTimeout" : "TimedOut", H.body.size());
|
||||
if (conf.getString("mode") == "validate") {
|
||||
long long int endTime = Util::bootSecs();
|
||||
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
H.Clean();
|
||||
url = parts.begin()->uri;
|
||||
}
|
||||
|
||||
if (lastDown != "") {
|
||||
while (parts.size() && parts.begin()->uri != lastDown) { parts.pop_front(); }
|
||||
if (parts.size() < 2) {
|
||||
repeat = true;
|
||||
uint64_t reqTime = Util::bootSecs();
|
||||
while (conn && Util::bootSecs() < reqTime + 5){
|
||||
// No data? Wait for a second or so.
|
||||
if (!conn.spool()){
|
||||
Util::sleep(1000);
|
||||
// continue;
|
||||
return 0;
|
||||
continue;
|
||||
}
|
||||
// Data! Check if we can parse it...
|
||||
if (H.Read(conn)){
|
||||
return true; // Success!
|
||||
}
|
||||
// reset the 5 second timeout
|
||||
reqTime = Util::bootSecs();
|
||||
}
|
||||
if (conn){
|
||||
FAIL_MSG("Timeout while retrieving %s", link.getUrl().c_str());
|
||||
return false;
|
||||
}
|
||||
Util::sleep(500); // wait a bit before retrying
|
||||
}
|
||||
FAIL_MSG("Could not retrieve %s", link.getUrl().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnalyserHLS::parsePacket(){
|
||||
while (isOpen()){
|
||||
// If needed, refresh the playlist
|
||||
if (refreshAt && Util::bootSecs() >= refreshAt){
|
||||
if (download(root)){
|
||||
getParts(H.body);
|
||||
}else{
|
||||
FAIL_MSG("Could not refresh playlist!");
|
||||
return false;
|
||||
}
|
||||
parts.pop_front();
|
||||
}
|
||||
|
||||
unsigned int lastRepeat = 0;
|
||||
unsigned int numRepeat = 0;
|
||||
while (parts.size() > 0 && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) {
|
||||
// If there are parts to download, get one.
|
||||
if (parts.size()){
|
||||
HLSPart part = *parts.begin();
|
||||
parts.pop_front();
|
||||
DEBUG_MSG(DLVL_DEVEL, "Retrieving segment: %s (%u-%u)", part.uri.c_str(), part.start, part.start + part.dur);
|
||||
if (!conn) { conn = Socket::Connection(server, port, false); }
|
||||
HTTP::Parser H;
|
||||
H.url = part.uri;
|
||||
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
|
||||
H.SendRequest(conn);
|
||||
H.Clean();
|
||||
while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {}
|
||||
|
||||
if (H.GetHeader("Content-Length") != "") {
|
||||
if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Expected %s bytes of data, but only received %lu.", H.GetHeader("Content-Length").c_str(), H.body.size());
|
||||
if (lastRepeat != part.start || numRepeat < 500) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Retrying");
|
||||
if (lastRepeat != part.start) {
|
||||
numRepeat = 0;
|
||||
lastRepeat = part.start;
|
||||
} else {
|
||||
numRepeat++;
|
||||
}
|
||||
parts.push_front(part);
|
||||
Util::wait(1000);
|
||||
continue;
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_FAIL, "Aborting further downloading");
|
||||
repeat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (H.body.size() % 188) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Expected a multiple of 188 bytes, received %d bytes", H.body.size());
|
||||
if (lastRepeat != part.start || numRepeat < 500) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Retrying");
|
||||
if (lastRepeat != part.start) {
|
||||
numRepeat = 0;
|
||||
lastRepeat = part.start;
|
||||
} else {
|
||||
numRepeat++;
|
||||
}
|
||||
parts.push_front(part);
|
||||
Util::wait(1000);
|
||||
continue;
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_FAIL, "Aborting further downloading");
|
||||
repeat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = part.start + part.dur;
|
||||
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) {
|
||||
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
|
||||
}
|
||||
lastDown = part.uri;
|
||||
if (output) { std::cout << H.body; }
|
||||
if (!download(part.uri)){return false;}
|
||||
if (H.GetHeader("Content-Length") != ""){
|
||||
if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())){
|
||||
FAIL_MSG("Expected %s bytes of data, but only received %lu.",
|
||||
H.GetHeader("Content-Length").c_str(), H.body.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (H.body.size() % 188){
|
||||
FAIL_MSG("Expected a multiple of 188 bytes, received %d bytes", H.body.size());
|
||||
return false;
|
||||
}
|
||||
parsedPart = part.no;
|
||||
hlsTime += part.dur;
|
||||
mediaTime = (uint64_t)hlsTime;
|
||||
if (reconstruct.good()){reconstruct << H.body;}
|
||||
H.Clean();
|
||||
return true;
|
||||
}
|
||||
|
||||
return pos;
|
||||
// Hm. I guess we had no parts to get.
|
||||
if (refreshAt && refreshAt > Util::bootSecs()){
|
||||
// We're getting a live stream. Let's wait and check again.
|
||||
uint32_t sleepSecs = (refreshAt - Util::bootSecs());
|
||||
INFO_MSG("Sleeping for %lu seconds", sleepSecs);
|
||||
Util::sleep(sleepSecs * 1000);
|
||||
}
|
||||
//The non-live case is already handled in isOpen()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hlsAnalyser::hasInput() {
|
||||
return repeat;
|
||||
}
|
||||
|
||||
bool hlsAnalyser::packetReady() {
|
||||
return repeat;
|
||||
}
|
||||
|
||||
hlsAnalyser::~hlsAnalyser() {
|
||||
// INFO_MSG("call destructor");
|
||||
// DEBUG_MSG(DLVL_INFO, "mode: %s", conf.getString("mode").c_str());
|
||||
//
|
||||
/* call doValidate() from superclass
|
||||
|
||||
if (conf.getString("mode") == "validate") {
|
||||
long long int endTime = Util::bootSecs();
|
||||
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to "
|
||||
"do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to HLS stream index file to retrieve.\"}"));
|
||||
conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", \"arg\":\"integer\", \"default\":-1, \"help\":\"Abort after "
|
||||
"this many seconds of downloading. Negative values mean unlimited, which is the default.\"}"));
|
||||
|
||||
conf.addOption(
|
||||
"detail",
|
||||
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}"));
|
||||
|
||||
conf.parseArgs(argc, argv);
|
||||
conf.activate();
|
||||
|
||||
hlsAnalyser A(conf);
|
||||
A.Run();
|
||||
}
|
||||
|
|
|
@ -1,55 +1,37 @@
|
|||
//http://cattop:8080/hls/bunny/index.m3u8
|
||||
//
|
||||
#include <mist/config.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <iostream>
|
||||
#include "analyser.h"
|
||||
#include <fstream>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
class HLSPart {
|
||||
public:
|
||||
HLSPart(std::string u, unsigned int s, unsigned int d) {
|
||||
uri = u;
|
||||
start = s;
|
||||
dur = d;
|
||||
}
|
||||
std::string uri;
|
||||
unsigned int start;
|
||||
unsigned int dur;
|
||||
class HLSPart{
|
||||
public:
|
||||
HLSPart(const HTTP::URL &u, uint64_t n, float d) : uri(u), no(n), dur(d){}
|
||||
HTTP::URL uri;
|
||||
uint64_t no;
|
||||
float dur;
|
||||
};
|
||||
|
||||
class AnalyserHLS : public Analyser{
|
||||
|
||||
class hlsAnalyser : public analysers
|
||||
{
|
||||
|
||||
public:
|
||||
hlsAnalyser(Util::Config config);
|
||||
~hlsAnalyser();
|
||||
bool packetReady();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
void doValidate();
|
||||
bool hasInput();
|
||||
void PostProcessing();
|
||||
|
||||
private:
|
||||
|
||||
unsigned int port;
|
||||
std::string url;
|
||||
|
||||
std::string server;
|
||||
long long int startTime;
|
||||
long long int abortTime;
|
||||
public:
|
||||
AnalyserHLS(Util::Config &conf);
|
||||
bool parsePacket();
|
||||
static void init(Util::Config &conf);
|
||||
bool isOpen();
|
||||
bool open(const std::string &filename);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::deque<HLSPart> parts;
|
||||
void getParts(const std::string &body);
|
||||
HTTP::URL root;
|
||||
float hlsTime;
|
||||
uint64_t parsedPart;
|
||||
uint64_t refreshAt;
|
||||
HTTP::Parser H;
|
||||
std::string connectedHost;
|
||||
uint32_t connectedPort;
|
||||
bool download(const HTTP::URL &link);
|
||||
Socket::Connection conn;
|
||||
|
||||
std::string playlist;
|
||||
bool repeat;
|
||||
std::string lastDown;
|
||||
unsigned int pos;
|
||||
bool output;
|
||||
|
||||
std::ofstream reconstruct;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,71 +1,45 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/timing.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
#include "analyser_mp4.h"
|
||||
|
||||
mp4Analyser::mp4Analyser(Util::Config config) : analysers(config) {
|
||||
|
||||
curPos = 0;
|
||||
dataSize = 0;
|
||||
void AnalyserMP4::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
}
|
||||
|
||||
int mp4Analyser::doAnalyse() {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
|
||||
return dataSize; // endtime?
|
||||
AnalyserMP4::AnalyserMP4(Util::Config &conf) : Analyser(conf){
|
||||
curPos = prePos = 0;
|
||||
}
|
||||
|
||||
bool mp4Analyser::hasInput() {
|
||||
if (!std::cin.good()) { return false; }
|
||||
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();
|
||||
dataSize++;
|
||||
|
||||
if (!std::cin.good()) {
|
||||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
||||
dataSize--;
|
||||
++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;
|
||||
}
|
||||
|
||||
bool mp4Analyser::packetReady() {
|
||||
return mp4Data.read(mp4Buffer);
|
||||
/// 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;
|
||||
}
|
||||
|
||||
mp4Analyser::~mp4Analyser() {
|
||||
INFO_MSG("Stopped parsing at position %d", curPos);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info "
|
||||
"about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to "
|
||||
"do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.addOption("filename",
|
||||
JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}"));
|
||||
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
mp4Analyser A(conf);
|
||||
// FlvAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
#include <mist/config.h>
|
||||
#include <mist/mp4.h>
|
||||
#include "analyser.h"
|
||||
#include <mist/mp4.h>
|
||||
|
||||
class AnalyserMP4 : public Analyser{
|
||||
public:
|
||||
AnalyserMP4(Util::Config &conf);
|
||||
static void init(Util::Config &conf);
|
||||
bool parsePacket();
|
||||
|
||||
class mp4Analyser : public analysers
|
||||
{
|
||||
private:
|
||||
uint64_t neededBytes();
|
||||
std::string mp4Buffer;
|
||||
MP4::Box mp4Data;
|
||||
int dataSize;
|
||||
int curPos;
|
||||
|
||||
public:
|
||||
mp4Analyser(Util::Config config);
|
||||
~mp4Analyser();
|
||||
bool packetReady();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
// void doValidate();
|
||||
bool hasInput();
|
||||
void PostProcessing();
|
||||
uint64_t curPos;
|
||||
uint64_t prePos;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,49 +1,121 @@
|
|||
#include <string>
|
||||
#include "analyser_ogg.h"
|
||||
#include <mist/opus.h>
|
||||
|
||||
oggAnalyser::oggAnalyser(Util::Config config) : analysers(config)
|
||||
{
|
||||
std::cout << "ogg constr" << std::endl;
|
||||
|
||||
filter = conf.getInteger("filter");
|
||||
FLV::Tag flvData; // Temporary storage for incoming FLV data.
|
||||
endTime = 0;
|
||||
/// \TODO EW EW EW EW EW EW EW EW EW EW EW
|
||||
|
||||
void AnalyserOGG::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
}
|
||||
|
||||
void oggAnalyser::doValidate()
|
||||
{
|
||||
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl;
|
||||
}
|
||||
AnalyserOGG::AnalyserOGG(Util::Config &conf) : Analyser(conf){}
|
||||
|
||||
bool oggAnalyser::packetReady()
|
||||
{
|
||||
return flvData.FileLoader(stdin);
|
||||
}
|
||||
bool AnalyserOGG::parsePacket(){
|
||||
if (!oggPage.read(stdin)){return false;}
|
||||
|
||||
int oggAnalyser::doAnalyse()
|
||||
{
|
||||
if (analyse){ //always analyse..?
|
||||
if (!filter || filter == flvData.data[0]){
|
||||
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl;
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
endTime = flvData.tagTime();
|
||||
|
||||
return endTime;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
oggAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
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;
|
||||
};
|
||||
|
|
@ -1,183 +1,47 @@
|
|||
#include <string>
|
||||
/// \file analyser_rtmp.cpp
|
||||
/// Debugging tool for RTMP data.
|
||||
|
||||
#include "analyser_rtmp.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <mist/flv_tag.h>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/rtmpchunks.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/socket.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define DETAIL_RECONSTRUCT 1
|
||||
#define DETAIL_EXPLICIT 2
|
||||
#define DETAIL_VERBOSE 4
|
||||
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();
|
||||
}
|
||||
|
||||
rtmpAnalyser::rtmpAnalyser(Util::Config config) : analysers(config)
|
||||
{
|
||||
std::cout << "rtmp constr" << std::endl;
|
||||
Detail = conf.getInteger("detail");
|
||||
//Detail = 4;
|
||||
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();
|
||||
|
||||
}
|
||||
inbuffer.erase(0,3073); //strip the handshake part
|
||||
|
||||
AMF::Object amfdata("empty", AMF::AMF0_DDV_CONTAINER);
|
||||
AMF::Object3 amf3data("empty", AMF::AMF3_DDV_CONTAINER);
|
||||
|
||||
while (std::cin.good() && inbuffer.size() < 3073){inbuffer += std::cin.get();}
|
||||
RTMPStream::rec_cnt += 3073;
|
||||
|
||||
read_in = 0;
|
||||
endTime = 0;
|
||||
inbuffer.erase(0, 3073); // strip the handshake part
|
||||
MEDIUM_MSG("Handshake skipped");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rtmpAnalyser::packetReady()
|
||||
{
|
||||
return (std::cin.good() || strbuf.size());
|
||||
}
|
||||
|
||||
int rtmpAnalyser::doAnalyse()
|
||||
{
|
||||
// std::cout << "do analyse" << std::endl;
|
||||
|
||||
analyse=true;
|
||||
if (analyse){ //always analyse..?
|
||||
// std::cout << "status strbuf: " << next.Parse(strbuf) << " strbuf_size: " << strbuf.size() << std::endl;
|
||||
|
||||
if (next.Parse(strbuf)){
|
||||
if (Detail & DETAIL_VERBOSE){
|
||||
fprintf(stderr, "Chunk info: [%#2X] CS ID %u, timestamp %u, len %u, type ID %u, Stream ID %u\n", next.headertype, next.cs_id, next.timestamp,
|
||||
next.len, next.msg_type_id, next.msg_stream_id);
|
||||
}
|
||||
switch (next.msg_type_id){
|
||||
case 0: //does not exist
|
||||
fprintf(stderr, "Error chunk @ %lu - CS%i, T%i, L%i, LL%i, MID%i\n", read_in-inbuffer.size(), next.cs_id, next.timestamp, next.real_len, next.len_left, next.msg_stream_id);
|
||||
return 0;
|
||||
break; //happens when connection breaks unexpectedly
|
||||
case 1: //set chunk size
|
||||
RTMPStream::chunk_rec_max = ntohl(*(int*)next.data.c_str());
|
||||
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
||||
break;
|
||||
case 2: //abort message - we ignore this one
|
||||
fprintf(stderr, "CTRL: Abort message: %i\n", ntohl(*(int*)next.data.c_str()));
|
||||
//4 bytes of stream id to drop
|
||||
break;
|
||||
case 3: //ack
|
||||
RTMPStream::snd_window_at = ntohl(*(int*)next.data.c_str());
|
||||
fprintf(stderr, "CTRL: Acknowledgement: %i\n", RTMPStream::snd_window_at);
|
||||
break;
|
||||
case 4: {
|
||||
short int ucmtype = ntohs(*(short int*)next.data.c_str());
|
||||
switch (ucmtype){
|
||||
case 0:
|
||||
fprintf(stderr, "CTRL: User control message: stream begin %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 1:
|
||||
fprintf(stderr, "CTRL: User control message: stream EOF %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 2:
|
||||
fprintf(stderr, "CTRL: User control message: stream dry %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 3:
|
||||
fprintf(stderr, "CTRL: User control message: setbufferlen %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 4:
|
||||
fprintf(stderr, "CTRL: User control message: streamisrecorded %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 6:
|
||||
fprintf(stderr, "CTRL: User control message: pingrequest %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 7:
|
||||
fprintf(stderr, "CTRL: User control message: pingresponse %u\n", ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
case 31:
|
||||
case 32:
|
||||
//don't know, but not interesting anyway
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "CTRL: User control message: UNKNOWN %hu - %u\n", ucmtype, ntohl(*(unsigned int*)(next.data.c_str()+2)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 5: //window size of other end
|
||||
RTMPStream::rec_window_size = ntohl(*(int*)next.data.c_str());
|
||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||
fprintf(stderr, "CTRL: Window size: %i\n", RTMPStream::rec_window_size);
|
||||
break;
|
||||
case 6:
|
||||
RTMPStream::snd_window_size = ntohl(*(int*)next.data.c_str());
|
||||
//4 bytes window size, 1 byte limit type (ignored)
|
||||
fprintf(stderr, "CTRL: Set peer bandwidth: %i\n", RTMPStream::snd_window_size);
|
||||
break;
|
||||
case 8:
|
||||
case 9:
|
||||
if (Detail & (DETAIL_EXPLICIT | DETAIL_RECONSTRUCT)){
|
||||
F.ChunkLoader(next);
|
||||
if (Detail & DETAIL_EXPLICIT){
|
||||
std::cerr << "[" << F.tagTime() << "+" << F.offset() << "] " << F.tagType() << std::endl;
|
||||
}
|
||||
if (Detail & DETAIL_RECONSTRUCT){
|
||||
std::cout.write(F.data, F.len);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 15:
|
||||
fprintf(stderr, "Received AFM3 data message\n");
|
||||
break;
|
||||
case 16:
|
||||
fprintf(stderr, "Received AFM3 shared object\n");
|
||||
break;
|
||||
case 17: {
|
||||
fprintf(stderr, "Received AFM3 command message:\n");
|
||||
char soort = next.data[0];
|
||||
next.data = next.data.substr(1);
|
||||
if (soort == 0){
|
||||
amfdata = AMF::parse(next.data);
|
||||
std::cerr << amfdata.Print() << std::endl;
|
||||
}else{
|
||||
amf3data = AMF::parse3(next.data);
|
||||
amf3data.Print();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 18: {
|
||||
fprintf(stderr, "Received AFM0 data message (metadata):\n");
|
||||
amfdata = AMF::parse(next.data);
|
||||
amfdata.Print();
|
||||
if (Detail & DETAIL_RECONSTRUCT){
|
||||
F.ChunkLoader(next);
|
||||
std::cout.write(F.data, F.len);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 19:
|
||||
fprintf(stderr, "Received AFM0 shared object\n");
|
||||
break;
|
||||
case 20: { //AMF0 command message
|
||||
fprintf(stderr, "Received AFM0 command message:\n");
|
||||
amfdata = AMF::parse(next.data);
|
||||
std::cerr << amfdata.Print() << std::endl;
|
||||
}
|
||||
break;
|
||||
case 22:
|
||||
if (Detail & DETAIL_RECONSTRUCT){
|
||||
std::cout << next.data;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.\n");
|
||||
return 1;
|
||||
break;
|
||||
} //switch for type of chunk
|
||||
}else{ //if chunk parsed
|
||||
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;
|
||||
|
@ -192,22 +56,131 @@ int rtmpAnalyser::doAnalyse()
|
|||
}
|
||||
strbuf.append(tmpbuffer);
|
||||
}else{
|
||||
strbuf.get().clear();
|
||||
}
|
||||
// if we can't fill the buffer, and have no parsable packet(s), return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return endTime;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
|
||||
analysers::defaultConfig(conf);
|
||||
conf.parseArgs(argc, argv);
|
||||
rtmpAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
// 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() || validate){
|
||||
F.ChunkLoader(next);
|
||||
mediaTime = F.tagTime();
|
||||
DETAIL_VHI("[%llu+%llu] %s", F.tagTime(), F.offset(), F.tagType().c_str());
|
||||
if (reconstruct.good()){reconstruct.write(F.data, F.len);}
|
||||
}
|
||||
break;
|
||||
case 15: DETAIL_MED("Received AFM3 data message"); break;
|
||||
case 16: DETAIL_MED("Received AFM3 shared object"); break;
|
||||
case 17:{
|
||||
DETAIL_MED("Received AFM3 command message:");
|
||||
char soort = next.data[0];
|
||||
next.data = next.data.substr(1);
|
||||
if (soort == 0){
|
||||
amfdata = AMF::parse(next.data);
|
||||
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||
}else{
|
||||
amf3data = AMF::parse3(next.data);
|
||||
DETAIL_MED("%s", amf3data.Print().c_str());
|
||||
}
|
||||
}break;
|
||||
case 18:{
|
||||
DETAIL_MED("Received AFM0 data message (metadata):");
|
||||
amfdata = AMF::parse(next.data);
|
||||
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||
if (reconstruct.good()){
|
||||
F.ChunkLoader(next);
|
||||
reconstruct.write(F.data, F.len);
|
||||
}
|
||||
}break;
|
||||
case 19: DETAIL_MED("Received AFM0 shared object"); break;
|
||||
case 20:{// AMF0 command message
|
||||
DETAIL_MED("Received AFM0 command message:");
|
||||
amfdata = AMF::parse(next.data);
|
||||
DETAIL_MED("%s", amfdata.Print().c_str());
|
||||
}break;
|
||||
case 22:
|
||||
if (reconstruct.good()){reconstruct << next.data;}
|
||||
break;
|
||||
default:
|
||||
FAIL_MSG(
|
||||
"Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||
return false;
|
||||
break;
|
||||
}// switch for type of chunk
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +1,22 @@
|
|||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/config.h>
|
||||
#include "analyser.h"
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mist/amf.h>
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/rtmpchunks.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/socket.h>
|
||||
|
||||
class rtmpAnalyser : public analysers
|
||||
{
|
||||
FLV::Tag flvData;
|
||||
long long filter;
|
||||
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
|
||||
|
||||
std::string inbuffer;
|
||||
RTMPStream::Chunk next;
|
||||
FLV::Tag F; //FLV holder
|
||||
unsigned int read_in ;
|
||||
Socket::Buffer strbuf;
|
||||
AMF::Object amfdata;
|
||||
AMF::Object3 amf3data;
|
||||
|
||||
int Detail;
|
||||
|
||||
public:
|
||||
rtmpAnalyser(Util::Config config);
|
||||
bool packetReady();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
// void doValidate();
|
||||
public:
|
||||
AnalyserRTMP(Util::Config & conf);
|
||||
static void init(Util::Config & conf);
|
||||
bool parsePacket();
|
||||
virtual bool open(const std::string &filename);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,37 +1,41 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include "analyser_rtp.h"
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/rtp.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
rtpAnalyser::rtpAnalyser(Util::Config config) : analysers(config)
|
||||
{
|
||||
std::cout << "rtp constr" << std::endl;
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
|
||||
namespace Analysers {
|
||||
int analyseRTP(){
|
||||
Socket::Connection conn("localhost", 554, true);
|
||||
step = 0;
|
||||
trackIt = 0;
|
||||
|
||||
}
|
||||
|
||||
void rtpAnalyser::doValidate()
|
||||
{
|
||||
}
|
||||
|
||||
bool rtpAnalyser::packetReady()
|
||||
{
|
||||
return conn.connected();
|
||||
}
|
||||
|
||||
int rtpAnalyser::doAnalyse()
|
||||
{
|
||||
if (analyse){ //always analyse..?
|
||||
|
||||
//Socket::Connection conn("krabs", 1935, true);
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
int step = 0;
|
||||
/*1 = sent describe
|
||||
2 = recd describe
|
||||
3 = sent setup
|
||||
4 = received setup
|
||||
5 = sent play"*/
|
||||
std::vector<std::string> tracks;
|
||||
std::vector<Socket::UDPConnection> connections;
|
||||
unsigned int trackIt = 0;
|
||||
while (conn.connected()){
|
||||
// std::cerr << "loopy" << std::endl;
|
||||
if(step == 0){
|
||||
HTTP_S.protocol = "RTSP/1.0";
|
||||
HTTP_S.method = "DESCRIBE";
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
//rtsp://localhost/g1
|
||||
//HTTP_S.url = "rtsp://localhost/steers";
|
||||
HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
|
||||
HTTP_S.url = "rtsp://localhost/steers";
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
HTTP_S.SetHeader("CSeq",1);
|
||||
HTTP_S.SendRequest(conn);
|
||||
step++;
|
||||
|
@ -187,18 +191,19 @@ int rtpAnalyser::doAnalyse()
|
|||
}
|
||||
}
|
||||
}
|
||||
return 666;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return endTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
rtpAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
return Analysers::analyseRTP();
|
||||
}
|
||||
|
|
|
@ -9,172 +9,144 @@
|
|||
#include <map>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
tsAnalyser::tsAnalyser(Util::Config config) : analysers(config) {
|
||||
upTime = Util::bootSecs();
|
||||
pcr = 0;
|
||||
void AnalyserTS::init(Util::Config &conf){
|
||||
Analyser::init(conf);
|
||||
JSON::Value opt;
|
||||
opt["long"] = "detail";
|
||||
opt["short"] = "D";
|
||||
opt["arg"] = "num";
|
||||
opt["default"] = 3ll;
|
||||
opt["help"] = "Detail level of analysis bitmask (default=3). 1 = PES, 2 = TS non-stream pkts, 4 "
|
||||
"= TS stream pkts, 32 = raw PES packet bytes, 64 = raw TS packet bytes";
|
||||
conf.addOption("detail", opt);
|
||||
opt.null();
|
||||
opt["long"] = "pid";
|
||||
opt["short"] = "P";
|
||||
opt["arg"] = "num";
|
||||
opt["default"] = 0ll;
|
||||
opt["help"] = "Only use the given PID, ignore others";
|
||||
conf.addOption("pid", opt);
|
||||
opt.null();
|
||||
}
|
||||
|
||||
AnalyserTS::AnalyserTS(Util::Config &conf) : Analyser(conf){
|
||||
pidOnly = conf.getInteger("pid");
|
||||
bytes = 0;
|
||||
|
||||
detailLevel = detail;
|
||||
endTime = 0;
|
||||
incorrectPacket = 0;
|
||||
}
|
||||
|
||||
void tsAnalyser::doValidate() {
|
||||
long long int finTime = Util::bootSecs();
|
||||
fprintf(stdout, "time since boot,time at completion,real time duration of data receival,video duration\n");
|
||||
fprintf(stdout, "%lli000,%lli000,%lli000,%li \n", upTime, finTime, finTime - upTime, pcr / 27000);
|
||||
}
|
||||
|
||||
bool tsAnalyser::packetReady() {
|
||||
|
||||
if(incorrectPacket > 5)
|
||||
{
|
||||
mayExecute = false;
|
||||
//FAIL_MSG("too many incorrect ts packets!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::cin.good();
|
||||
}
|
||||
|
||||
int tsAnalyser::doAnalyse() {
|
||||
|
||||
char tsIdentifier = std::cin.peek();
|
||||
|
||||
if(tsIdentifier != 0x47)
|
||||
{
|
||||
incorrectPacket++;
|
||||
}
|
||||
|
||||
bool AnalyserTS::parsePacket(){
|
||||
static char packetPtr[188];
|
||||
std::cin.read(packetPtr, 188);
|
||||
//0x47
|
||||
if (std::cin.gcount() != 188) { return 0; }
|
||||
if (std::cin.gcount() != 188){return false;}
|
||||
DONTEVEN_MSG("Reading from position %llu", bytes);
|
||||
bytes += 188;
|
||||
if (packet.FromPointer(packetPtr)) {
|
||||
if (analyse) {
|
||||
if (packet.getUnitStart() && payloads[packet.getPID()] != "") {
|
||||
std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel);
|
||||
if (!packet.FromPointer(packetPtr)){return false;}
|
||||
if (detail){
|
||||
if (packet.getUnitStart() && payloads.count(packet.getPID()) &&
|
||||
payloads[packet.getPID()] != ""){
|
||||
if ((detail & 1) && (!pidOnly || packet.getPID() == pidOnly)){
|
||||
std::cout << printPES(payloads[packet.getPID()], packet.getPID());
|
||||
}
|
||||
payloads.erase(packet.getPID());
|
||||
}
|
||||
if (detailLevel >= 3 || !packet.getPID() || packet.isPMT()) {
|
||||
if (packet.getPID() == 0) { ((TS::ProgramAssociationTable *)&packet)->parsePIDs(); }
|
||||
std::cout << packet.toPrettyString(0, detailLevel);
|
||||
if (packet.getPID() == 0){((TS::ProgramAssociationTable *)&packet)->parsePIDs();}
|
||||
if (packet.isPMT()){((TS::ProgramMappingTable *)&packet)->parseStreams();}
|
||||
if ((((detail & 2) && !packet.isStream()) || ((detail & 4) && packet.isStream())) &&
|
||||
(!pidOnly || packet.getPID() == pidOnly)){
|
||||
std::cout << packet.toPrettyString(0, detail);
|
||||
}
|
||||
if (packet.getPID() && !packet.isPMT() && (payloads[packet.getPID()].size() || packet.getUnitStart())) {
|
||||
if (packet.getPID() >= 0x10 && !packet.isPMT() && packet.getPID() != 17 &&
|
||||
(payloads[packet.getPID()].size() || packet.getUnitStart())){
|
||||
payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength());
|
||||
}
|
||||
}
|
||||
if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()) { pcr = packet.getPCR(); }
|
||||
if (packet && packet.getAdaptationField() > 1 && packet.hasPCR()){
|
||||
mediaTime = packet.getPCR() / 27000;
|
||||
}
|
||||
if (bytes > 1024) {
|
||||
long long int tTime = Util::bootSecs();
|
||||
if (validate && tTime - upTime > 5 && tTime - upTime > pcr / 27000000) {
|
||||
std::cerr << "data received too slowly" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
return endTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
analysers::defaultConfig(conf);
|
||||
|
||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info "
|
||||
"about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
||||
|
||||
// override default detail level with specific detail level for TS Analyser.
|
||||
conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of "
|
||||
"analysis. 1 = PES only, 2 = PAT/PMT (default), 3 = all TS packets, 9 = raw PES packet bytes, 10 = "
|
||||
"raw TS packet bytes\"}"));
|
||||
|
||||
conf.parseArgs(argc, argv);
|
||||
tsAnalyser A(conf);
|
||||
|
||||
A.Run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
tsAnalyser::~tsAnalyser() {
|
||||
|
||||
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++) {
|
||||
if (!it->first || it->first == 4096) { continue; }
|
||||
std::cout << printPES(it->second, it->first, detailLevel);
|
||||
AnalyserTS::~AnalyserTS(){
|
||||
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin();
|
||||
it != payloads.end(); it++){
|
||||
if ((detail & 1) && (!pidOnly || it->first == pidOnly)){
|
||||
std::cout << printPES(it->second, it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int detailLevel) {
|
||||
std::string AnalyserTS::printPES(const std::string &d, unsigned long PID){
|
||||
unsigned int headSize = 0;
|
||||
std::stringstream res;
|
||||
bool known = false;
|
||||
res << "[PES " << PID << "]";
|
||||
if ((d[3] & 0xF0) == 0xE0) {
|
||||
if ((d[3] & 0xF0) == 0xE0){
|
||||
res << " [Video " << (int)(d[3] & 0xF) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known && (d[3] & 0xE0) == 0xC0) {
|
||||
if (!known && (d[3] & 0xE0) == 0xC0){
|
||||
res << " [Audio " << (int)(d[3] & 0x1F) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known) { res << " [Unknown stream ID: " << (int)d[3] << "]"; }
|
||||
if (d[0] != 0 || d[1] != 0 || d[2] != 1) { res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]"; }
|
||||
if (!known){res << " [Unknown stream ID: " << (int)d[3] << "]";}
|
||||
if (d[0] != 0 || d[1] != 0 || d[2] != 1){
|
||||
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2]
|
||||
<< " ]";
|
||||
}
|
||||
unsigned int padding = 0;
|
||||
if (known) {
|
||||
if ((d[6] & 0xC0) != 0x80) { res << " [!INVALID FIRST BITS!]"; }
|
||||
if (d[6] & 0x30) { res << " [SCRAMBLED]"; }
|
||||
if (d[6] & 0x08) { res << " [Priority]"; }
|
||||
if (d[6] & 0x04) { res << " [Aligned]"; }
|
||||
if (d[6] & 0x02) { res << " [Copyrighted]"; }
|
||||
if (d[6] & 0x01) {
|
||||
if (known){
|
||||
if ((d[6] & 0xC0) != 0x80){res << " [!INVALID FIRST BITS!]";}
|
||||
if (d[6] & 0x30){res << " [SCRAMBLED]";}
|
||||
if (d[6] & 0x08){res << " [Priority]";}
|
||||
if (d[6] & 0x04){res << " [Aligned]";}
|
||||
if (d[6] & 0x02){res << " [Copyrighted]";}
|
||||
if (d[6] & 0x01){
|
||||
res << " [Original]";
|
||||
} else {
|
||||
}else{
|
||||
res << " [Copy]";
|
||||
}
|
||||
|
||||
int timeFlags = ((d[7] & 0xC0) >> 6);
|
||||
if (timeFlags == 2) { headSize += 5; }
|
||||
if (timeFlags == 3) { headSize += 10; }
|
||||
if (d[7] & 0x20) {
|
||||
if (timeFlags == 2){headSize += 5;}
|
||||
if (timeFlags == 3){headSize += 10;}
|
||||
if (d[7] & 0x20){
|
||||
res << " [ESCR present, not decoded!]";
|
||||
headSize += 6;
|
||||
}
|
||||
if (d[7] & 0x10) {
|
||||
if (d[7] & 0x10){
|
||||
uint32_t es_rate = (Bit::btoh24(d.data() + 9 + headSize) & 0x7FFFFF) >> 1;
|
||||
res << " [ESR: " << (es_rate * 50) / 1024 << " KiB/s]";
|
||||
headSize += 3;
|
||||
}
|
||||
if (d[7] & 0x08) {
|
||||
if (d[7] & 0x08){
|
||||
res << " [Trick mode present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x04) {
|
||||
if (d[7] & 0x04){
|
||||
res << " [Add. copy present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x02) {
|
||||
if (d[7] & 0x02){
|
||||
res << " [CRC present, not decoded!]";
|
||||
headSize += 2;
|
||||
}
|
||||
if (d[7] & 0x01) {
|
||||
if (d[7] & 0x01){
|
||||
res << " [Extension present, not decoded!]";
|
||||
headSize += 0; /// \todo Implement this. Complicated field, bah.
|
||||
}
|
||||
if (d[8] != headSize) {
|
||||
if (d[8] != headSize){
|
||||
padding = d[8] - headSize;
|
||||
res << " [Padding: " << padding << "b]";
|
||||
}
|
||||
if (timeFlags & 0x02) {
|
||||
if (timeFlags & 0x02){
|
||||
long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1);
|
||||
time <<= 15;
|
||||
time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F);
|
||||
|
@ -182,7 +154,7 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de
|
|||
time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F);
|
||||
res << " [PTS " << ((double)time / 90000) << "s]";
|
||||
}
|
||||
if (timeFlags & 0x01) {
|
||||
if (timeFlags & 0x01){
|
||||
long long unsigned int time = ((d[14] >> 1) & 0x07);
|
||||
time <<= 15;
|
||||
time |= ((int)d[15] << 7) | (d[16] >> 1);
|
||||
|
@ -191,21 +163,26 @@ std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int de
|
|||
res << " [DTS " << ((double)time / 90000) << "s]";
|
||||
}
|
||||
}
|
||||
if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)) { res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]"; }
|
||||
if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){
|
||||
res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]";
|
||||
}else{
|
||||
res << " [Size " << (d.size() - 6) << "]";
|
||||
}
|
||||
res << std::endl;
|
||||
|
||||
if (detailLevel == 10) {
|
||||
if (detail & 32){
|
||||
unsigned int counter = 0;
|
||||
for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i) {
|
||||
if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1) {
|
||||
for (unsigned int i = 9 + headSize + padding; i < d.size(); ++i){
|
||||
if ((i < d.size() - 4) && d[i] == 0 && d[i + 1] == 0 && d[i + 2] == 0 && d[i + 3] == 1){
|
||||
res << std::endl;
|
||||
counter = 0;
|
||||
}
|
||||
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i] & 0xff) << " ";
|
||||
counter++;
|
||||
if ((counter) % 32 == 31) { res << std::endl; }
|
||||
if ((counter) % 32 == 31){res << std::endl;}
|
||||
}
|
||||
res << std::endl;
|
||||
}
|
||||
return res.str();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
#include "analyser.h"
|
||||
#include <mist/config.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include "analyser.h"
|
||||
|
||||
class tsAnalyser : public analysers
|
||||
{
|
||||
long long filter;
|
||||
|
||||
class AnalyserTS : public Analyser{
|
||||
public:
|
||||
AnalyserTS(Util::Config &conf);
|
||||
~AnalyserTS();
|
||||
bool parsePacket();
|
||||
static void init(Util::Config &conf);
|
||||
std::string printPES(const std::string &d, unsigned long PID);
|
||||
private:
|
||||
std::map<unsigned long long, std::string> payloads;
|
||||
uint32_t pidOnly;
|
||||
TS::Packet packet;
|
||||
long long int upTime;
|
||||
int64_t pcr;
|
||||
unsigned int bytes;
|
||||
char packetPtr[188];
|
||||
int detailLevel;
|
||||
int incorrectPacket;
|
||||
|
||||
public:
|
||||
tsAnalyser(Util::Config config);
|
||||
~tsAnalyser();
|
||||
bool packetReady();
|
||||
void PreProcessing();
|
||||
//int Analyse();
|
||||
int doAnalyse();
|
||||
void doValidate();
|
||||
std::string printPES(const std::string & d, unsigned long PID, int detailLevel);
|
||||
uint64_t bytes;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
/// \file dtsc_analyser.cpp
|
||||
/// Reads an DTSC file and prints all readable data about it
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <mist/dtsc.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/h264.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
///\brief Debugging tool for DTSC data.
|
||||
///
|
||||
/// Expects DTSC data in a file given on the command line, outputs human-readable information to stderr.
|
||||
///\param conf The configuration parsed from the commandline.
|
||||
///\return The return code of the analyser.
|
||||
int analyseDTSC(Util::Config conf){
|
||||
DTSC::File F(conf.getString("filename"));
|
||||
if (!F){
|
||||
std::cerr << "Not a valid DTSC file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (conf.getBool("compact")){
|
||||
bool hasH264 = false;
|
||||
bool hasAAC = false;
|
||||
JSON::Value result;
|
||||
std::stringstream issues;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = F.getMeta().tracks.begin(); it != F.getMeta().tracks.end(); it++){
|
||||
JSON::Value track;
|
||||
track["kbits"] = (long long)((double)it->second.bps * 8 / 1024);
|
||||
track["codec"] = it->second.codec;
|
||||
uint32_t shrtest_key = 0xFFFFFFFFul;
|
||||
uint32_t longest_key = 0;
|
||||
uint32_t shrtest_prt = 0xFFFFFFFFul;
|
||||
uint32_t longest_prt = 0;
|
||||
uint32_t shrtest_cnt = 0xFFFFFFFFul;
|
||||
uint32_t longest_cnt = 0;
|
||||
for (std::deque<DTSC::Key>::iterator k = it->second.keys.begin(); k != it->second.keys.end(); k++){
|
||||
if (!k->getLength()){continue;}
|
||||
if (k->getLength() > longest_key){longest_key = k->getLength();}
|
||||
if (k->getLength() < shrtest_key){shrtest_key = k->getLength();}
|
||||
if (k->getParts() > longest_cnt){longest_cnt = k->getParts();}
|
||||
if (k->getParts() < shrtest_cnt){shrtest_cnt = k->getParts();}
|
||||
if ((k->getLength()/k->getParts()) > longest_prt){longest_prt = (k->getLength()/k->getParts());}
|
||||
if ((k->getLength()/k->getParts()) < shrtest_prt){shrtest_prt = (k->getLength()/k->getParts());}
|
||||
}
|
||||
track["keys"]["ms_min"] = (long long)shrtest_key;
|
||||
track["keys"]["ms_max"] = (long long)longest_key;
|
||||
track["keys"]["frame_ms_min"] = (long long)shrtest_prt;
|
||||
track["keys"]["frame_ms_max"] = (long long)longest_prt;
|
||||
track["keys"]["frames_min"] = (long long)shrtest_cnt;
|
||||
track["keys"]["frames_max"] = (long long)longest_cnt;
|
||||
if (longest_prt > 500){issues << "unstable connection (" << longest_prt << "ms " << it->second.codec << " frame)! ";}
|
||||
if (shrtest_cnt < 6){issues << "unstable connection (" << shrtest_cnt << " " << it->second.codec << " frames in key)! ";}
|
||||
if (it->second.codec == "AAC"){hasAAC = true;}
|
||||
if (it->second.codec == "H264"){hasH264 = true;}
|
||||
if (it->second.type=="video"){
|
||||
track["width"] = (long long)it->second.width;
|
||||
track["height"] = (long long)it->second.height;
|
||||
track["fpks"] = it->second.fpks;
|
||||
if (it->second.codec == "H264"){
|
||||
h264::sequenceParameterSet sps;
|
||||
sps.fromDTSCInit(it->second.init);
|
||||
h264::SPSMeta spsData = sps.getCharacteristics();
|
||||
track["h264"]["profile"] = spsData.profile;
|
||||
track["h264"]["level"] = spsData.level;
|
||||
}
|
||||
}
|
||||
result[it->second.getWritableIdentifier()] = track;
|
||||
}
|
||||
if ((hasAAC || hasH264) && F.getMeta().tracks.size() > 1){
|
||||
if (!hasAAC){issues << "HLS no audio!";}
|
||||
if (!hasH264){issues << "HLS no video!";}
|
||||
}
|
||||
if (issues.str().size()){result["issues"] = issues.str();}
|
||||
std::cout << result.toString();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (F.getMeta().vod || F.getMeta().live){
|
||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
||||
}
|
||||
|
||||
int bPos = 0;
|
||||
F.seek_bpos(0);
|
||||
F.parseNext();
|
||||
while (F.getPacket()){
|
||||
switch (F.getPacket().getVersion()){
|
||||
case DTSC::DTSC_V1: {
|
||||
std::cout << "DTSCv1 packet: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTSC_V2: {
|
||||
std::cout << "DTSCv2 packet (Track " << F.getPacket().getTrackId() << ", time " << F.getPacket().getTime() << "): " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTSC_HEAD: {
|
||||
std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTCM: {
|
||||
std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos);
|
||||
break;
|
||||
}
|
||||
bPos = F.getBytePos();
|
||||
F.parseNext();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads an DTSC file and prints all readable data about it
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the DTSC file to analyse.\"}"));
|
||||
conf.addOption("compact", JSON::fromString("{\"short\": \"c\", \"long\": \"compact\", \"help\":\"Filename of the DTSC file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseDTSC(conf);
|
||||
} //main
|
|
@ -1,79 +0,0 @@
|
|||
/// \file flv_analyser.cpp
|
||||
/// Contains the code for the FLV Analysing tool.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <mist/flv_tag.h> //FLV support
|
||||
#include <mist/config.h>
|
||||
#include <mist/timing.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
|
||||
namespace Analysers {
|
||||
|
||||
int analyseFLV(Util::Config conf){
|
||||
bool fileinput = conf.getString("filename").length() > 0;
|
||||
bool analyse = conf.getString("mode") == "analyse";
|
||||
bool validate = conf.getString("mode") == "validate";
|
||||
|
||||
long long filter = conf.getInteger("filter");
|
||||
|
||||
if(fileinput){
|
||||
std::string filename = conf.getString("filename");
|
||||
int fp = open(filename.c_str(), O_RDONLY);
|
||||
|
||||
if(fp <= 0){
|
||||
FAIL_MSG("Cannot open file: %s",filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
dup2(fp, STDIN_FILENO);
|
||||
close(fp);
|
||||
}
|
||||
|
||||
FLV::Tag flvData; // Temporary storage for incoming FLV data.
|
||||
long long int endTime = 0;
|
||||
long long int upTime = Util::bootSecs();
|
||||
|
||||
while(!feof(stdin)){
|
||||
if (flvData.FileLoader(stdin)){
|
||||
if (analyse){
|
||||
if (!filter || filter == flvData.data[0]){
|
||||
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl;
|
||||
}
|
||||
}
|
||||
endTime = flvData.tagTime();
|
||||
}
|
||||
}
|
||||
|
||||
long long int finTime = Util::bootSecs();
|
||||
if (validate){
|
||||
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
///Debugging tool for FLV data.
|
||||
/// Expects FLV data through stdin, outputs human-readable information to stderr.
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the FLV file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
|
||||
return Analysers::analyseFLV(conf);
|
||||
}
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/// \file h264_analyser.cpp
|
||||
/// Reads an H264 file and prints all readable data about it
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/h264.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/bitstream.h>
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Full path of the file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
FILE * F = fopen(conf.getString("filename").c_str(), "r+b");
|
||||
if (!F){
|
||||
FAIL_MSG("No such file");
|
||||
}
|
||||
|
||||
h264::nalUnit * nalPtr = h264::nalFactory(F);
|
||||
while (nalPtr){
|
||||
nalPtr->toPrettyString(std::cout);
|
||||
nalPtr = h264::nalFactory(F);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
/// \file hls_analyser.cpp
|
||||
/// Contains the code for the HLS Analysing tool.
|
||||
|
||||
#include <mist/config.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <iostream>
|
||||
|
||||
class HLSPart {
|
||||
public:
|
||||
HLSPart(std::string u, unsigned int s, unsigned int d) {
|
||||
uri = u;
|
||||
start = s;
|
||||
dur = d;
|
||||
}
|
||||
std::string uri;
|
||||
unsigned int start;
|
||||
unsigned int dur;
|
||||
};
|
||||
|
||||
std::deque<HLSPart> getParts(std::string & body, std::string & uri) {
|
||||
size_t slashPos = uri.rfind('/');
|
||||
std::string uri_prefix = uri.substr(0, slashPos + 1);
|
||||
std::deque<HLSPart> out;
|
||||
std::stringstream data(body);
|
||||
std::string line;
|
||||
unsigned int start = 0;
|
||||
unsigned int durat = 0;
|
||||
do {
|
||||
line = "";
|
||||
std::getline(data, line);
|
||||
if (line.size() && *line.rbegin() == '\r'){
|
||||
line.resize(line.size() - 1);
|
||||
}
|
||||
if (line != "") {
|
||||
if (line[0] != '#') {
|
||||
out.push_back(HLSPart(uri_prefix + line, start, durat));
|
||||
start += durat;
|
||||
} else {
|
||||
if (line.substr(0, 8) == "#EXTINF:") {
|
||||
durat = atof(line.substr(8).c_str()) * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (line != "");
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
|
||||
conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to HLS stream index file to retrieve.\"}"));
|
||||
conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", \"arg\":\"integer\", \"default\":-1, \"help\":\"Abort after this many seconds of downloading. Negative values mean unlimited, which is the default.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
conf.activate();
|
||||
|
||||
unsigned int port = 80;
|
||||
std::string url = conf.getString("url");
|
||||
if (url.substr(0, 7) != "http://") {
|
||||
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
|
||||
return -1;
|
||||
}
|
||||
url = url.substr(7);
|
||||
|
||||
std::string server = url.substr(0, url.find('/'));
|
||||
url = url.substr(url.find('/'));
|
||||
|
||||
if (server.find(':') != std::string::npos) {
|
||||
port = atoi(server.substr(server.find(':') + 1).c_str());
|
||||
server = server.substr(0, server.find(':'));
|
||||
}
|
||||
|
||||
long long int startTime = Util::bootSecs();
|
||||
long long int abortTime = conf.getInteger("abort");
|
||||
|
||||
std::deque<HLSPart> parts;
|
||||
Socket::Connection conn;
|
||||
|
||||
std::string playlist = url;
|
||||
bool repeat = false;
|
||||
std::string lastDown = "";
|
||||
unsigned int pos = 0;
|
||||
bool output = (conf.getString("mode") == "output");
|
||||
|
||||
do {
|
||||
repeat = false;
|
||||
while (url.size() > 4 && (url.find(".m3u") != std::string::npos || url.find(".m3u8") != std::string::npos)) {
|
||||
playlist = url;
|
||||
DEBUG_MSG(DLVL_DEVEL, "Retrieving playlist: %s", url.c_str());
|
||||
if (!conn) {
|
||||
conn = Socket::Connection(server, port, false);
|
||||
}
|
||||
HTTP::Parser H;
|
||||
H.url = url;
|
||||
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
|
||||
H.SendRequest(conn);
|
||||
H.Clean();
|
||||
while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {}
|
||||
parts = getParts(H.body, url);
|
||||
if (!parts.size()) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Playlist parsing error - cancelling. state: %s/%s body size: %u", conn ? "Conn" : "Disconn", (Util::bootSecs() < (startTime + abortTime))?"NoTimeout":"TimedOut", H.body.size());
|
||||
if (conf.getString("mode") == "validate") {
|
||||
long long int endTime = Util::bootSecs();
|
||||
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
H.Clean();
|
||||
url = parts.begin()->uri;
|
||||
}
|
||||
|
||||
if (lastDown != "") {
|
||||
while (parts.size() && parts.begin()->uri != lastDown) {
|
||||
parts.pop_front();
|
||||
}
|
||||
if (parts.size() < 2) {
|
||||
repeat = true;
|
||||
Util::sleep(1000);
|
||||
continue;
|
||||
}
|
||||
parts.pop_front();
|
||||
}
|
||||
|
||||
unsigned int lastRepeat = 0;
|
||||
unsigned int numRepeat = 0;
|
||||
while (parts.size() > 0 && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) {
|
||||
HLSPart part = *parts.begin();
|
||||
parts.pop_front();
|
||||
DEBUG_MSG(DLVL_DEVEL, "Retrieving segment: %s (%u-%u)", part.uri.c_str(), part.start, part.start + part.dur);
|
||||
if (!conn) {
|
||||
conn = Socket::Connection(server, port, false);
|
||||
}
|
||||
HTTP::Parser H;
|
||||
H.url = part.uri;
|
||||
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
|
||||
H.SendRequest(conn);
|
||||
H.Clean();
|
||||
while (conn && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (!conn.spool() || !H.Read(conn))) {}
|
||||
|
||||
if (H.GetHeader("Content-Length") != "") {
|
||||
if (H.body.size() != atoi(H.GetHeader("Content-Length").c_str())) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Expected %s bytes of data, but only received %lu.", H.GetHeader("Content-Length").c_str(), H.body.size());
|
||||
if (lastRepeat != part.start || numRepeat < 500){
|
||||
DEBUG_MSG(DLVL_FAIL,"Retrying");
|
||||
if (lastRepeat != part.start){
|
||||
numRepeat = 0;
|
||||
lastRepeat = part.start;
|
||||
}else{
|
||||
numRepeat ++;
|
||||
}
|
||||
parts.push_front(part);
|
||||
Util::wait(1000);
|
||||
continue;
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL,"Aborting further downloading");
|
||||
repeat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (H.body.size() % 188){
|
||||
DEBUG_MSG(DLVL_FAIL, "Expected a multiple of 188 bytes, received %d bytes", H.body.size());
|
||||
if (lastRepeat != part.start || numRepeat < 500){
|
||||
DEBUG_MSG(DLVL_FAIL,"Retrying");
|
||||
if (lastRepeat != part.start){
|
||||
numRepeat = 0;
|
||||
lastRepeat = part.start;
|
||||
}else{
|
||||
numRepeat ++;
|
||||
}
|
||||
parts.push_front(part);
|
||||
Util::wait(1000);
|
||||
continue;
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL,"Aborting further downloading");
|
||||
repeat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = part.start + part.dur;
|
||||
if (conf.getString("mode") == "validate" && (Util::bootSecs()-startTime+5)*1000 < pos) {
|
||||
Util::wait(pos - (Util::bootSecs()-startTime+5)*1000);
|
||||
}
|
||||
lastDown = part.uri;
|
||||
if (output) {
|
||||
std::cout << H.body;
|
||||
}
|
||||
H.Clean();
|
||||
}
|
||||
} while (repeat);
|
||||
DEBUG_MSG(DLVL_INFO, "mode: %s", conf.getString("mode").c_str());
|
||||
if (conf.getString("mode") == "validate") {
|
||||
long long int endTime = Util::bootSecs();
|
||||
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
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,89 +0,0 @@
|
|||
/// \file mp4_analyser.cpp
|
||||
/// Debugging tool for MP4 data.
|
||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
///\brief Debugging tool for MP4 data.
|
||||
///
|
||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
||||
///\return The return code of the analyser.
|
||||
int analyseMP4(Util::Config conf){
|
||||
|
||||
std::string filename = conf.getString("filename");
|
||||
|
||||
if(filename.length() > 0){
|
||||
int fp = open(filename.c_str(), O_RDONLY);
|
||||
|
||||
if(fp <= 0){
|
||||
FAIL_MSG("Cannot open file: %s",filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
dup2(fp, STDIN_FILENO);
|
||||
close(fp);
|
||||
}
|
||||
|
||||
/*
|
||||
MP4::Box mp4Data;
|
||||
int dataSize = 0;//mp4Buffer.size();
|
||||
int curPos = 0;
|
||||
|
||||
while (!feof(stdin)){
|
||||
|
||||
if(mp4Data.read(stdin))
|
||||
{
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
//curPos += dataSize - mp4Buffer.size();
|
||||
//dataSize = mp4Buffer.size();
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
std::string mp4Buffer;
|
||||
//Read all of std::cin to mp4Buffer
|
||||
while (std::cin.good()){
|
||||
mp4Buffer += std::cin.get();
|
||||
}
|
||||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
||||
|
||||
|
||||
MP4::Box mp4Data;
|
||||
int dataSize = mp4Buffer.size();
|
||||
int curPos = 0;
|
||||
|
||||
while (mp4Data.read(mp4Buffer)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
curPos += dataSize - mp4Buffer.size();
|
||||
dataSize = mp4Buffer.size();
|
||||
}
|
||||
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Debugging tool for MP4 data.
|
||||
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("filename", JSON::fromString( "{\"arg_num\":1, \"arg\":\"string\", \"default\":\"\", \"help\":\"Filename of the MP4 file to analyse.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseMP4(conf);
|
||||
}
|
||||
|
|
@ -1,492 +0,0 @@
|
|||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
#include <mist/ogg.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/theora.h>
|
||||
#include <mist/defines.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <cmath>
|
||||
|
||||
///\todo rewrite this analyser.
|
||||
namespace Analysers{
|
||||
std::string Opus_prettyPacket(const char * part,int len){
|
||||
if (len < 1){
|
||||
return "Invalid packet (0 byte length)";
|
||||
}
|
||||
std::stringstream r;
|
||||
char config = part[0] >> 3;
|
||||
char code = part[0] & 3;
|
||||
if ((part[0] & 4) == 4){
|
||||
r << "Stereo, ";
|
||||
} else {
|
||||
r << "Mono, ";
|
||||
}
|
||||
if (config < 14){
|
||||
r << "SILK, ";
|
||||
if (config < 4){
|
||||
r << "NB, ";
|
||||
}
|
||||
if (config < 8 && config > 3){
|
||||
r << "MB, ";
|
||||
}
|
||||
if (config < 14 && config > 7){
|
||||
r << "WB, ";
|
||||
}
|
||||
if (config % 4 == 0){
|
||||
r << "10ms";
|
||||
}
|
||||
if (config % 4 == 1){
|
||||
r << "20ms";
|
||||
}
|
||||
if (config % 4 == 2){
|
||||
r << "40ms";
|
||||
}
|
||||
if (config % 4 == 3){
|
||||
r << "60ms";
|
||||
}
|
||||
}
|
||||
if (config < 16 && config > 13){
|
||||
r << "Hybrid, ";
|
||||
if (config < 14){
|
||||
r << "SWB, ";
|
||||
} else {
|
||||
r << "FB, ";
|
||||
}
|
||||
if (config % 2 == 0){
|
||||
r << "10ms";
|
||||
} else {
|
||||
r << "20ms";
|
||||
}
|
||||
}
|
||||
if (config > 15){
|
||||
r << "CELT, ";
|
||||
if (config < 20){
|
||||
r << "NB, ";
|
||||
}
|
||||
if (config < 24 && config > 19){
|
||||
r << "WB, ";
|
||||
}
|
||||
if (config < 28 && config > 23){
|
||||
r << "SWB, ";
|
||||
}
|
||||
if (config > 27){
|
||||
r << "FB, ";
|
||||
}
|
||||
if (config % 4 == 0){
|
||||
r << "2.5ms";
|
||||
}
|
||||
if (config % 4 == 1){
|
||||
r << "5ms";
|
||||
}
|
||||
if (config % 4 == 2){
|
||||
r << "10ms";
|
||||
}
|
||||
if (config % 4 == 3){
|
||||
r << "20ms";
|
||||
}
|
||||
}
|
||||
if (code == 0){
|
||||
r << ": 1 packet (" << (len - 1) << "b)";
|
||||
return r.str();
|
||||
}
|
||||
if (code == 1){
|
||||
r << ": 2 packets (" << ((len - 1) / 2) << "b / " << ((len - 1) / 2) << "b)";
|
||||
return r.str();
|
||||
}
|
||||
if (code == 2){
|
||||
if (len < 2){
|
||||
return "Invalid packet (code 2 must be > 1 byte long)";
|
||||
}
|
||||
if (part[1] < 252){
|
||||
r << ": 2 packets (" << (int)part[1] << "b / " << (int)(len - 2 - part[1]) << "b)";
|
||||
} else {
|
||||
int ilen = part[1] + part[2] * 4;
|
||||
r << ": 2 packets (" << ilen << "b / " << (int)(len - 3 - ilen) << "b)";
|
||||
}
|
||||
return r.str();
|
||||
}
|
||||
//code 3
|
||||
bool VBR = (part[1] & 128) == 128;
|
||||
bool pad = (part[1] & 64) == 64;
|
||||
bool packets = (part[1] & 63);
|
||||
r << ": " << packets << " packets (VBR = " << VBR << ", padding = " << pad << ")";
|
||||
return r.str();
|
||||
}
|
||||
|
||||
int newfunc(Util::Config & conf){
|
||||
|
||||
std::map<int,std::string> sn2Codec;
|
||||
std::string oggBuffer;
|
||||
OGG::Page oggPage;
|
||||
int kfgshift;
|
||||
|
||||
//validate variables
|
||||
bool doValidate = true;
|
||||
long long int lastTime =0;
|
||||
double mspft = 0;
|
||||
std::map<long long unsigned int,long long int> oggMap;
|
||||
theora::header * theader = 0;
|
||||
bool seenIDheader = false;
|
||||
struct sysinfo sinfo;
|
||||
sysinfo(&sinfo);
|
||||
long long int upTime = sinfo.uptime;
|
||||
|
||||
while (oggPage.read(stdin)){ //reading ogg to string
|
||||
//print the Ogg page details, if requested
|
||||
if (conf.getBool("pages")){
|
||||
printf("%s", oggPage.toPrettyString().c_str());
|
||||
}
|
||||
|
||||
//attempt to detect codec if this is the first page of a stream
|
||||
if (oggPage.getHeaderType() & OGG::BeginOfStream){
|
||||
if (memcmp("theora", oggPage.getSegment(0) + 1, 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Theora";
|
||||
|
||||
if(doValidate){
|
||||
if(!seenIDheader){
|
||||
if (theader){delete theader; theader = 0;}
|
||||
theader = new theora::header((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0));
|
||||
if(theader->getHeaderType() == 0){
|
||||
seenIDheader = true;
|
||||
}
|
||||
mspft = (double)(theader->getFRD() * 1000) / theader->getFRN();
|
||||
}
|
||||
|
||||
if(oggPage.getGranulePosition() != 0xffffffffffffffff){
|
||||
lastTime = ((oggPage.getGranulePosition()>>(int)theader->getKFGShift())*mspft);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis";
|
||||
|
||||
if(oggMap.find(oggPage.getBitstreamSerialNumber()) == oggMap.end()){
|
||||
//validate stuff
|
||||
if(doValidate){
|
||||
vorbis::header vheader((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0));
|
||||
//if (vheader){
|
||||
oggMap[oggPage.getBitstreamSerialNumber()] = ntohl(vheader.getAudioSampleRate());
|
||||
//oggPage.setInternalCodec(sn2Codec[oggPage.getBitstreamSerialNumber()]);
|
||||
//}
|
||||
lastTime = (double)oggPage.getGranulePosition()/(double)oggMap[oggPage.getBitstreamSerialNumber()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(doValidate){
|
||||
|
||||
fprintf(stdout,"printing timing....");
|
||||
if (theader){delete theader; theader = 0;}
|
||||
sysinfo(&sinfo);
|
||||
long long int finTime = sinfo.uptime;
|
||||
fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n");
|
||||
fprintf(stdout, "%lli000,%lli000,%lli000,%lli \n",upTime,finTime,finTime-upTime,lastTime);
|
||||
//print last time
|
||||
}
|
||||
|
||||
if (memcmp("OpusHead", oggPage.getSegment(0), 8) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus";
|
||||
}
|
||||
if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){
|
||||
std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " recognized as " << sn2Codec[oggPage.getBitstreamSerialNumber()] << std::endl;
|
||||
} else {
|
||||
std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " could not be recognized as any known codec" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
|
||||
std::cout << " Theora data" << std::endl;
|
||||
static unsigned int numParts = 0;
|
||||
static unsigned int keyCount = 0;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
|
||||
if (tmpHeader.isHeader()){
|
||||
if (tmpHeader.getHeaderType() == 0){
|
||||
kfgshift = tmpHeader.getKFGShift();
|
||||
}
|
||||
} else {
|
||||
if (!(oggPage.getHeaderType() == OGG::Continued) && tmpHeader.getFTYPE() == 0){ //if keyframe
|
||||
std::cout << "keyframe granule: " << (oggPage.getGranulePosition() >> kfgshift) << std::endl;
|
||||
std::cout << "keyframe " << keyCount << " has " << numParts << " parts, yo." << std::endl;
|
||||
numParts = 0;
|
||||
keyCount++;
|
||||
}
|
||||
if (oggPage.getHeaderType() != OGG::Continued || i){
|
||||
numParts++;
|
||||
}
|
||||
}
|
||||
std::cout << tmpHeader.toPrettyString(4);
|
||||
|
||||
}
|
||||
} else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){
|
||||
std::cout << " Vorbis data" << std::endl;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
int len = oggPage.getAllSegments()[i].size();
|
||||
vorbis::header tmpHeader((char*)oggPage.getSegment(i), len);
|
||||
if (tmpHeader.isHeader()){
|
||||
std::cout << tmpHeader.toPrettyString(4);
|
||||
}
|
||||
}
|
||||
} else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Opus"){
|
||||
std::cout << " Opus data" << std::endl;
|
||||
int offset = 0;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
int len = oggPage.getAllSegments()[i].size();
|
||||
const char * part = oggPage.getSegment(i);
|
||||
if (len >= 8 && memcmp(part, "Opus", 4) == 0){
|
||||
if (memcmp(part, "OpusHead", 8) == 0){
|
||||
std::cout << " Version: " << (int)(part[8]) << std::endl;
|
||||
std::cout << " Channels: " << (int)(part[9]) << std::endl;
|
||||
std::cout << " Pre-skip: " << (int)(part[10] + (part[11] << 8)) << std::endl;
|
||||
std::cout << " Orig. sample rate: " << (int)(part[12] + (part[13] << 8) + (part[14] << 16) + (part[15] << 24)) << std::endl;
|
||||
std::cout << " Gain: " << (int)(part[16] + (part[17] << 8)) << std::endl;
|
||||
std::cout << " Channel map: " << (int)(part[18]) << std::endl;
|
||||
if (part[18] > 0){
|
||||
std::cout << " Channel map family " << (int)(part[18]) << " not implemented - output incomplete" << std::endl;
|
||||
}
|
||||
}
|
||||
if (memcmp(part, "OpusTags", 8) == 0){
|
||||
unsigned int vendor_len = part[8] + (part[9] << 8) + (part[10] << 16) + (part[11] << 24);
|
||||
std::cout << " Vendor: " << std::string(part + 12, vendor_len) << std::endl;
|
||||
const char * str_data = part + 12 + vendor_len;
|
||||
unsigned int strings = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||
std::cout << " Tags: (" << strings << ")" << std::endl;
|
||||
str_data += 4;
|
||||
for (unsigned int j = 0; j < strings; j++){
|
||||
unsigned int strlen = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||
str_data += 4;
|
||||
std::cout << " [" << j << "] " << std::string((char *) str_data, strlen) << std::endl;
|
||||
str_data += strlen;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << " " << Opus_prettyPacket(part, len) << std::endl;
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int analyseOGG(Util::Config & conf){
|
||||
std::map<int,std::string> sn2Codec;
|
||||
std::string oggBuffer;
|
||||
OGG::Page oggPage;
|
||||
int kfgshift;
|
||||
//Read all of std::cin to oggBuffer
|
||||
//while OGG::page check function read
|
||||
while (oggPage.read(stdin)){ //reading ogg to string
|
||||
//print the Ogg page details, if requested
|
||||
if (conf.getBool("pages")){
|
||||
printf("%s", oggPage.toPrettyString().c_str());
|
||||
}
|
||||
|
||||
//attempt to detect codec if this is the first page of a stream
|
||||
if (oggPage.getHeaderType() & OGG::BeginOfStream){
|
||||
if (memcmp("theora", oggPage.getSegment(0) + 1, 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Theora";
|
||||
}
|
||||
if (memcmp("vorbis", oggPage.getSegment(0) + 1, 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Vorbis";
|
||||
}
|
||||
if (memcmp("OpusHead", oggPage.getSegment(0), 8) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "Opus";
|
||||
}
|
||||
if (sn2Codec[oggPage.getBitstreamSerialNumber()] != ""){
|
||||
std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " recognized as " << sn2Codec[oggPage.getBitstreamSerialNumber()] << std::endl;
|
||||
} else {
|
||||
std::cout << "Bitstream " << oggPage.getBitstreamSerialNumber() << " could not be recognized as any known codec" << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Theora"){
|
||||
std::cout << " Theora data" << std::endl;
|
||||
static unsigned int numParts = 0;
|
||||
static unsigned int keyCount = 0;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
theora::header tmpHeader((char *)oggPage.getSegment(i), oggPage.getAllSegments()[i].size());
|
||||
if (tmpHeader.isHeader()){
|
||||
if (tmpHeader.getHeaderType() == 0){
|
||||
kfgshift = tmpHeader.getKFGShift();
|
||||
}
|
||||
} else {
|
||||
if (!(oggPage.getHeaderType() == OGG::Continued) && tmpHeader.getFTYPE() == 0){ //if keyframe
|
||||
std::cout << "keyframe granule: " << (oggPage.getGranulePosition() >> kfgshift) << std::endl;
|
||||
std::cout << "keyframe " << keyCount << " has " << numParts << " parts, yo." << std::endl;
|
||||
numParts = 0;
|
||||
keyCount++;
|
||||
}
|
||||
if (oggPage.getHeaderType() != OGG::Continued || i){
|
||||
numParts++;
|
||||
}
|
||||
}
|
||||
std::cout << tmpHeader.toPrettyString(4);
|
||||
|
||||
}
|
||||
} else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Vorbis"){
|
||||
std::cout << " Vorbis data" << std::endl;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
int len = oggPage.getAllSegments()[i].size();
|
||||
vorbis::header tmpHeader((char*)oggPage.getSegment(i), len);
|
||||
if (tmpHeader.isHeader()){
|
||||
std::cout << tmpHeader.toPrettyString(4);
|
||||
}
|
||||
}
|
||||
} else if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "Opus"){
|
||||
std::cout << " Opus data" << std::endl;
|
||||
int offset = 0;
|
||||
for (unsigned int i = 0; i < oggPage.getAllSegments().size(); i++){
|
||||
int len = oggPage.getAllSegments()[i].size();
|
||||
const char * part = oggPage.getSegment(i);
|
||||
if (len >= 8 && memcmp(part, "Opus", 4) == 0){
|
||||
if (memcmp(part, "OpusHead", 8) == 0){
|
||||
std::cout << " Version: " << (int)(part[8]) << std::endl;
|
||||
std::cout << " Channels: " << (int)(part[9]) << std::endl;
|
||||
std::cout << " Pre-skip: " << (int)(part[10] + (part[11] << 8)) << std::endl;
|
||||
std::cout << " Orig. sample rate: " << (int)(part[12] + (part[13] << 8) + (part[14] << 16) + (part[15] << 24)) << std::endl;
|
||||
std::cout << " Gain: " << (int)(part[16] + (part[17] << 8)) << std::endl;
|
||||
std::cout << " Channel map: " << (int)(part[18]) << std::endl;
|
||||
if (part[18] > 0){
|
||||
std::cout << " Channel map family " << (int)(part[18]) << " not implemented - output incomplete" << std::endl;
|
||||
}
|
||||
}
|
||||
if (memcmp(part, "OpusTags", 8) == 0){
|
||||
unsigned int vendor_len = part[8] + (part[9] << 8) + (part[10] << 16) + (part[11] << 24);
|
||||
std::cout << " Vendor: " << std::string(part + 12, vendor_len) << std::endl;
|
||||
const char * str_data = part + 12 + vendor_len;
|
||||
unsigned int strings = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||
std::cout << " Tags: (" << strings << ")" << std::endl;
|
||||
str_data += 4;
|
||||
for (unsigned int j = 0; j < strings; j++){
|
||||
unsigned int strlen = str_data[0] + (str_data[1] << 8) + (str_data[2] << 16) + (str_data[3] << 24);
|
||||
str_data += 4;
|
||||
std::cout << " [" << j << "] " << std::string((char *) str_data, strlen) << std::endl;
|
||||
str_data += strlen;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::cout << " " << Opus_prettyPacket(part, len) << std::endl;
|
||||
}
|
||||
offset += len;
|
||||
}
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int validateOGG(bool analyse){
|
||||
std::map<int,std::string> sn2Codec;
|
||||
std::string oggBuffer;
|
||||
OGG::Page oggPage;
|
||||
|
||||
long long int lastTime =0;
|
||||
double mspft = 0;
|
||||
std::map<long long unsigned int,long long int> oggMap;
|
||||
theora::header * theader = 0;
|
||||
bool seenIDheader = false;
|
||||
struct sysinfo sinfo;
|
||||
sysinfo(&sinfo);
|
||||
long long int upTime = sinfo.uptime;
|
||||
while (std::cin.good()){
|
||||
oggBuffer.reserve(1024);
|
||||
|
||||
for (unsigned int i = 0; (i < 1024) && (std::cin.good()); i++){
|
||||
oggBuffer += std::cin.get();
|
||||
}
|
||||
|
||||
while (oggPage.read(oggBuffer)){//reading ogg to ogg::page
|
||||
if(oggMap.find(oggPage.getBitstreamSerialNumber()) == oggMap.end()){
|
||||
//checking header
|
||||
//check if vorbis or theora
|
||||
if (memcmp(oggPage.getSegment(0)+1, "vorbis", 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "vorbis";
|
||||
vorbis::header vheader((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0));
|
||||
//if (vheader){
|
||||
oggMap[oggPage.getBitstreamSerialNumber()] = ntohl(vheader.getAudioSampleRate());
|
||||
//oggPage.setInternalCodec(sn2Codec[oggPage.getBitstreamSerialNumber()]);
|
||||
//}
|
||||
}else if(memcmp(oggPage.getSegment(0)+1, "theora", 6) == 0){
|
||||
sn2Codec[oggPage.getBitstreamSerialNumber()] = "theora";
|
||||
if(!seenIDheader){
|
||||
if (theader){delete theader; theader = 0;}
|
||||
theader = new theora::header((char*)oggPage.getSegment(0),oggPage.getSegmentLen(0));
|
||||
if(theader->getHeaderType() == 0){
|
||||
seenIDheader = true;
|
||||
}
|
||||
mspft = (double)(theader->getFRD() * 1000) / theader->getFRN();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (sn2Codec[oggPage.getBitstreamSerialNumber()] == "vorbis"){
|
||||
// std::cout <<oggPage.toPrettyString() << std::endl<< "--------------------------------" << std::endl;
|
||||
lastTime = (double)oggPage.getGranulePosition()/(double)oggMap[oggPage.getBitstreamSerialNumber()];
|
||||
}else if(sn2Codec[oggPage.getBitstreamSerialNumber()] == "theora"){
|
||||
//theora getKFGShift()
|
||||
if(oggPage.getGranulePosition() != 0xffffffffffffffff){
|
||||
lastTime = ((oggPage.getGranulePosition()>>(int)theader->getKFGShift())*mspft);
|
||||
}
|
||||
}
|
||||
if(analyse){
|
||||
std::cout << oggPage.toPrettyString() << std::endl;
|
||||
}
|
||||
}
|
||||
//while OGG::page check function read
|
||||
//save last time0
|
||||
sysinfo(&sinfo);
|
||||
long long int tTime = sinfo.uptime;
|
||||
if((tTime-upTime) > 5 && (tTime-upTime)>(int)(lastTime) ){
|
||||
std::cerr << "data received too slowly" << std::endl;
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
if (theader){delete theader; theader = 0;}
|
||||
sysinfo(&sinfo);
|
||||
long long int finTime = sinfo.uptime;
|
||||
fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n");
|
||||
fprintf(stdout, "%lli000,%lli000,%lli000,%lli \n",upTime,finTime,finTime-upTime,lastTime);
|
||||
//print last time
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("pages", JSON::fromString("{\"long\":\"pages\", \"short\":\"p\", \"long_off\":\"nopages\", \"short_off\":\"P\", \"default\":0, \"help\":\"Enable/disable printing of Ogg pages\"}"));
|
||||
conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":0, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}"));
|
||||
conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"x\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}"));
|
||||
|
||||
conf.addOption("newfunc", JSON::fromString("{\"long\":\"newfunc\", \"short\":\"N\", \"default\":0, \"long_off\":\"notnewfunc\", \"short_off\":\"x\", \"help\":\"newfuncValidate (-N) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}"));
|
||||
|
||||
conf.parseArgs(argc, argv);
|
||||
conf.activate();
|
||||
if (conf.getBool("validate")){
|
||||
return Analysers::validateOGG(conf.getBool("analyse"));
|
||||
|
||||
//return Analysers::newfunc(conf);
|
||||
}else if(conf.getBool("analyse")){
|
||||
return Analysers::analyseOGG(conf);
|
||||
}else if(conf.getBool("newfunc")){
|
||||
fprintf(stdout, "begin newfunc\n");
|
||||
return Analysers::newfunc(conf);
|
||||
fprintf(stdout, "end newfunction\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
|
@ -1,209 +0,0 @@
|
|||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/rtp.h>
|
||||
#include <mist/http_parser.h>
|
||||
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
|
||||
namespace Analysers {
|
||||
int analyseRTP(){
|
||||
Socket::Connection conn("localhost", 554, true);
|
||||
//Socket::Connection conn("krabs", 1935, true);
|
||||
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
|
||||
int step = 0;
|
||||
/*1 = sent describe
|
||||
2 = recd describe
|
||||
3 = sent setup
|
||||
4 = received setup
|
||||
5 = sent play"*/
|
||||
std::vector<std::string> tracks;
|
||||
std::vector<Socket::UDPConnection> connections;
|
||||
unsigned int trackIt = 0;
|
||||
while (conn.connected()){
|
||||
// std::cerr << "loopy" << std::endl;
|
||||
if(step == 0){
|
||||
HTTP_S.protocol = "RTSP/1.0";
|
||||
HTTP_S.method = "DESCRIBE";
|
||||
//rtsp://krabs:1935/vod/gear1.mp4
|
||||
//rtsp://localhost/g1
|
||||
HTTP_S.url = "rtsp://localhost/steers";
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
HTTP_S.SetHeader("CSeq",1);
|
||||
HTTP_S.SendRequest(conn);
|
||||
step++;
|
||||
|
||||
}else if(step == 2){
|
||||
std::cerr <<"setup " << tracks[trackIt] << std::endl;
|
||||
HTTP_S.method = "SETUP";
|
||||
HTTP_S.url = "rtsp://localhost/steers/" + tracks[trackIt];
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4/" + tracks[trackIt];
|
||||
HTTP_S.SetHeader("CSeq",2+trackIt);
|
||||
std::stringstream ss;
|
||||
ss << "RTP/steersVP;unicast;client_port="<< 20000 + 2*trackIt<<"-"<< 20001 + 2*trackIt;
|
||||
HTTP_S.SetHeader("Transport",ss.str());//make client ports, 4200 + 2*offset
|
||||
trackIt++;
|
||||
step++;
|
||||
HTTP_S.SendRequest(conn);
|
||||
std::cerr << "step " << step << "/\\"<< ss.str()<<std::endl;
|
||||
}else if(step == 4){
|
||||
std::cerr << "Play!!!1" << std::endl;
|
||||
HTTP_S.method = "PLAY";
|
||||
HTTP_S.url = "rtsp://localhost/steers";
|
||||
//HTTP_S.url = "rtsp://krabs:1935/vod/steers.mp4";
|
||||
HTTP_S.SetHeader("Range","npt=0.000-");
|
||||
HTTP_S.SendRequest(conn);
|
||||
step++;
|
||||
std::cerr << "step for play.." << step << std::endl;
|
||||
}
|
||||
|
||||
if (conn.Received().size() || conn.spool()){
|
||||
if (HTTP_R.Read(conn)){
|
||||
if(step == 1){
|
||||
std::cerr << "recvd desc" << std::endl;
|
||||
for(size_t ml = HTTP_R.body.find("a=control:",HTTP_R.body.find("m=")); ml != std::string::npos; ml = HTTP_R.body.find("a=control:",ml+1)){
|
||||
std::cerr << "found trekk" << std::endl;
|
||||
tracks.push_back(HTTP_R.body.substr(ml+10,HTTP_R.body.find_first_of("\r\n",ml)-(ml+10)));
|
||||
connections.push_back(Socket::UDPConnection());
|
||||
}
|
||||
for(unsigned int x = 0; x < connections.size();x++){
|
||||
connections[x].SetDestination("127.0.0.1",666);
|
||||
connections[x].bind(20000+2*x);
|
||||
connections[x].setBlocking(true);
|
||||
}
|
||||
step++;
|
||||
}else if(step == 3){
|
||||
std::cerr << "recvd setup" << std::endl;
|
||||
std::cerr << "trackIt: " << trackIt << " size " << tracks.size() << std::endl;
|
||||
if(trackIt < tracks.size())
|
||||
step--;
|
||||
else
|
||||
step++;
|
||||
std::cerr << HTTP_R.GetHeader("Transport");
|
||||
}
|
||||
HTTP_R.Clean();
|
||||
}
|
||||
}//!
|
||||
if(step == 5){
|
||||
|
||||
for(unsigned int cx = 0; cx < connections.size(); cx++){
|
||||
// std::cerr <<"PLAY MF" << std::endl;
|
||||
if(connections[cx].Receive()){
|
||||
RTP::Packet* pakketje = new RTP::Packet(connections[cx].data, connections[cx].data_len);
|
||||
/*std::cout << "Version = " << pakketje->getVersion() << std::endl;
|
||||
std::cout << "Padding = " << pakketje->getPadding() << std::endl;
|
||||
std::cout << "Extension = " << pakketje->getExtension() << std::endl;
|
||||
std::cout << "Contributing sources = " << pakketje->getContribCount() << std::endl;
|
||||
std::cout << "Marker = " << pakketje->getMarker() << std::endl;
|
||||
std::cout << "Payload Type = " << pakketje->getPayloadType() << std::endl;
|
||||
std::cout << "Sequence = " << pakketje->getSequence() << std::endl;
|
||||
std::cout << "Timestamp = " << pakketje->getTimeStamp() << std::endl;
|
||||
std::cout << "SSRC = " << pakketje->getSSRC() << std::endl;
|
||||
std::cout << "datalen: " << connections[cx].data_len << std::endl;
|
||||
std::cout << "payload:" << std::endl;*/
|
||||
|
||||
if(pakketje->getPayloadType() == 97){
|
||||
int h264type = (int)(connections[cx].data[12] & 0x1f);
|
||||
std::cout << h264type << " - ";
|
||||
if(h264type == 0){
|
||||
std::cout << "unspecified - ";
|
||||
}else if(h264type == 1){
|
||||
std::cout << "Coded slice of a non-IDR picture - ";
|
||||
}else if(h264type == 2){
|
||||
std::cout << "Coded slice data partition A - ";
|
||||
}else if(h264type == 3){
|
||||
std::cout << "Coded slice data partition B - ";
|
||||
}else if(h264type == 4){
|
||||
std::cout << "Coded slice data partition C - ";
|
||||
}else if(h264type == 5){
|
||||
std::cout << "Coded slice of an IDR picture - ";
|
||||
}else if(h264type == 6){
|
||||
std::cout << "Supplemental enhancement information (SEI) - ";
|
||||
}else if(h264type == 7){
|
||||
std::cout << "Sequence parameter set - ";
|
||||
}else if(h264type == 8){
|
||||
std::cout << "Picture parameter set - ";
|
||||
}else if(h264type == 9){
|
||||
std::cout << "Access unit delimiter - ";
|
||||
}else if(h264type == 10){
|
||||
std::cout << "End of sequence - ";
|
||||
}else if(h264type == 11){
|
||||
std::cout << "End of stream - ";
|
||||
}else if(h264type == 12){
|
||||
std::cout << "Filler data - ";
|
||||
}else if(h264type == 13){
|
||||
std::cout << "Sequence parameter set extension - ";
|
||||
}else if(h264type == 14){
|
||||
std::cout << "Prefix NAL unit - ";
|
||||
}else if(h264type == 15){
|
||||
std::cout << "Subset sequence parameter set - ";
|
||||
}else if(h264type == 16){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 17){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 18){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 19){
|
||||
std::cout << "Coded slice of an auxiliary coded picture without partitioning - ";
|
||||
}else if(h264type == 20){
|
||||
std::cout << "Coded slice extension - ";
|
||||
}else if(h264type == 21){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 22){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 23){
|
||||
std::cout << "Reserved - ";
|
||||
}else if(h264type == 24){
|
||||
std::cout << "stap a - ";
|
||||
}else if(h264type == 25){
|
||||
std::cout << "stap b - ";
|
||||
}else if(h264type == 26){
|
||||
std::cout << "mtap16 - ";
|
||||
}else if(h264type == 27){
|
||||
std::cout << "mtap24 - ";
|
||||
}else if(h264type == 28){
|
||||
std::cout << "fu a - ";
|
||||
}else if(h264type == 29){
|
||||
std::cout << "fu b - ";
|
||||
}else if(h264type == 30){
|
||||
std::cout << "Unspecified - ";
|
||||
}else if(h264type == 31){
|
||||
std::cout << "Unspecified - ";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for(unsigned int i = 13 ; i < connections[cx].data_len;i++){
|
||||
std::cout << std::hex <<std::setw(2) << std::setfill('0') << (int)connections[cx].data[i]<< std::dec;
|
||||
}
|
||||
std::cout << std::endl<<std::endl;
|
||||
}
|
||||
delete pakketje;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 666;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseRTP();
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include <signal.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/defines.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/config.h>
|
||||
|
||||
|
||||
namespace Analysers {
|
||||
std::string printPES(const std::string & d, unsigned long PID, int detailLevel){
|
||||
unsigned int headSize = 0;
|
||||
std::stringstream res;
|
||||
bool known = false;
|
||||
res << "[PES " << PID << "]";
|
||||
if ((d[3] & 0xF0) == 0xE0){
|
||||
res << " [Video " << (int)(d[3] & 0xF) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known && (d[3] & 0xE0) == 0xC0){
|
||||
res << " [Audio " << (int)(d[3] & 0x1F) << "]";
|
||||
known = true;
|
||||
}
|
||||
if (!known){
|
||||
res << " [Unknown stream ID: " << (int)d[3] << "]";
|
||||
}
|
||||
if (d[0] != 0 || d[1] != 0 || d[2] != 1){
|
||||
res << " [!!! INVALID START CODE: " << (int)d[0] << " " << (int)d[1] << " " << (int)d[2] << " ]";
|
||||
}
|
||||
unsigned int padding = 0;
|
||||
if (known){
|
||||
if ((d[6] & 0xC0) != 0x80){
|
||||
res << " [!INVALID FIRST BITS!]";
|
||||
}
|
||||
if (d[6] & 0x30){
|
||||
res << " [SCRAMBLED]";
|
||||
}
|
||||
if (d[6] & 0x08){
|
||||
res << " [Priority]";
|
||||
}
|
||||
if (d[6] & 0x04){
|
||||
res << " [Aligned]";
|
||||
}
|
||||
if (d[6] & 0x02){
|
||||
res << " [Copyrighted]";
|
||||
}
|
||||
if (d[6] & 0x01){
|
||||
res << " [Original]";
|
||||
}else{
|
||||
res << " [Copy]";
|
||||
}
|
||||
|
||||
int timeFlags = ((d[7] & 0xC0) >> 6);
|
||||
if (timeFlags == 2){
|
||||
headSize += 5;
|
||||
}
|
||||
if (timeFlags == 3){
|
||||
headSize += 10;
|
||||
}
|
||||
if (d[7] & 0x20){
|
||||
res << " [ESCR present, not decoded!]";
|
||||
headSize += 6;
|
||||
}
|
||||
if (d[7] & 0x10){
|
||||
uint32_t es_rate = (Bit::btoh24(d.data()+9+headSize) & 0x7FFFFF) >> 1;
|
||||
res << " [ESR: " << (es_rate * 50) / 1024 << " KiB/s]";
|
||||
headSize += 3;
|
||||
}
|
||||
if (d[7] & 0x08){
|
||||
res << " [Trick mode present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x04){
|
||||
res << " [Add. copy present, not decoded!]";
|
||||
headSize += 1;
|
||||
}
|
||||
if (d[7] & 0x02){
|
||||
res << " [CRC present, not decoded!]";
|
||||
headSize += 2;
|
||||
}
|
||||
if (d[7] & 0x01){
|
||||
res << " [Extension present, not decoded!]";
|
||||
headSize += 0; /// \todo Implement this. Complicated field, bah.
|
||||
}
|
||||
if (d[8] != headSize){
|
||||
padding = d[8] - headSize;
|
||||
res << " [Padding: " << padding << "b]";
|
||||
}
|
||||
if (timeFlags & 0x02){
|
||||
long long unsigned int time = (((unsigned int)d[9] & 0xE) >> 1);
|
||||
time <<= 15;
|
||||
time |= ((unsigned int)d[10] << 7) | (((unsigned int)d[11] >> 1) & 0x7F);
|
||||
time <<= 15;
|
||||
time |= ((unsigned int)d[12] << 7) | (((unsigned int)d[13] >> 1) & 0x7F);
|
||||
res << " [PTS " << ((double)time / 90000) << "s]";
|
||||
}
|
||||
if (timeFlags & 0x01){
|
||||
long long unsigned int time = ((d[14] >> 1) & 0x07);
|
||||
time <<= 15;
|
||||
time |= ((int)d[15] << 7) | (d[16] >> 1);
|
||||
time <<= 15;
|
||||
time |= ((int)d[17] << 7) | (d[18] >> 1);
|
||||
res << " [DTS " << ((double)time/90000) << "s]";
|
||||
}
|
||||
}
|
||||
if ((((int)d[4]) << 8 | d[5]) != (d.size() - 6)){
|
||||
res << " [Size " << (((int)d[4]) << 8 | d[5]) << " => " << (d.size() - 6) << "]";
|
||||
}else{
|
||||
res << " [Size " << (d.size() - 6) << "]";
|
||||
}
|
||||
res << std::endl;
|
||||
|
||||
if(detailLevel&32){
|
||||
unsigned int counter = 0;
|
||||
for (unsigned int i = 9+headSize+padding; i<d.size(); ++i){
|
||||
if ((i < d.size() - 4) && d[i] == 0 && d[i+1] == 0 && d[i+2] == 0 && d[i+3] == 1){res << std::endl; counter = 0;}
|
||||
res << std::hex << std::setw(2) << std::setfill('0') << (int)(d[i]&0xff) << " ";
|
||||
counter++;
|
||||
if ((counter) % 32 == 31){res << std::endl;}
|
||||
}
|
||||
res << std::endl;
|
||||
}
|
||||
return res.str();
|
||||
}
|
||||
|
||||
/// Debugging tool for TS data.
|
||||
/// Expects TS data through stdin, outputs human-readable information to stderr.
|
||||
/// \return The return code of the analyser.
|
||||
int analyseTS(bool validate, bool analyse, int detailLevel, uint32_t pidOnly){
|
||||
std::map<unsigned long long, std::string> payloads;
|
||||
TS::Packet packet;
|
||||
long long int upTime = Util::bootSecs();
|
||||
int64_t pcr = 0;
|
||||
uint64_t bytes = 0;
|
||||
char packetPtr[188];
|
||||
while (std::cin.good()){
|
||||
std::cin.read(packetPtr,188);
|
||||
if(std::cin.gcount() != 188){break;}
|
||||
DONTEVEN_MSG("Reading from position %llu", bytes);
|
||||
bytes += 188;
|
||||
if(packet.FromPointer(packetPtr)){
|
||||
if(analyse){
|
||||
if (packet.getUnitStart() && payloads.count(packet.getPID()) && payloads[packet.getPID()] != ""){
|
||||
if ((detailLevel&1) && (!pidOnly || packet.getPID() == pidOnly)){
|
||||
std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel);
|
||||
}
|
||||
payloads.erase(packet.getPID());
|
||||
}
|
||||
if (packet.getPID() == 0){
|
||||
((TS::ProgramAssociationTable*)&packet)->parsePIDs();
|
||||
}
|
||||
if (packet.isPMT()){
|
||||
((TS::ProgramMappingTable*)&packet)->parseStreams();
|
||||
}
|
||||
if ((((detailLevel & 2) && !packet.isStream()) || ((detailLevel & 4) && packet.isStream())) && (!pidOnly || packet.getPID() == pidOnly)){
|
||||
std::cout << packet.toPrettyString(0, detailLevel);
|
||||
}
|
||||
if (packet.getPID() >= 0x10 && !packet.isPMT() && packet.getPID()!=17 && (payloads[packet.getPID()].size() || packet.getUnitStart())){
|
||||
payloads[packet.getPID()].append(packet.getPayload(), packet.getPayloadLength());
|
||||
}
|
||||
}
|
||||
if(packet && packet.getAdaptationField() > 1 && packet.hasPCR()){pcr = packet.getPCR();}
|
||||
}
|
||||
if(bytes > 1024){
|
||||
long long int tTime = Util::bootSecs();
|
||||
if(validate && tTime - upTime > 5 && tTime - upTime > pcr/27000000){
|
||||
std::cerr << "data received too slowly" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
bytes = 0;
|
||||
}
|
||||
}
|
||||
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++){
|
||||
if ((detailLevel&1) && (!pidOnly || it->first == pidOnly)){
|
||||
std::cout << printPES(it->second, it->first, detailLevel);
|
||||
}
|
||||
}
|
||||
long long int finTime = Util::bootSecs();
|
||||
if(validate){
|
||||
fprintf(stdout,"time since boot,time at completion,real time duration of data receival,video duration\n");
|
||||
fprintf(stdout, "%lli000,%lli000,%lli000,%li \n",upTime,finTime,finTime-upTime,pcr/27000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":1, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}"));
|
||||
conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"X\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}"));
|
||||
conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis bitmask (default=3). 1 = PES, 2 = TS non-stream pkts, 4 = TS stream pkts, 32 = raw PES packet bytes, 64 = raw TS packet bytes\"}"));
|
||||
conf.addOption("pid", JSON::fromString("{\"long\":\"pid\", \"short\":\"P\", \"arg\":\"num\", \"default\":0, \"help\":\"Only use at given PID, ignore others\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail"),conf.getInteger("pid"));
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <string.h>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <sstream>
|
||||
#include <signal.h>
|
||||
#include <mist/ts_packet.h>
|
||||
#include <mist/ts_stream.h>
|
||||
#include <mist/config.h>
|
||||
|
||||
|
||||
namespace Analysers {
|
||||
/// Debugging tool for TS data.
|
||||
/// Expects TS data through stdin, outputs human-readable information to stderr.
|
||||
/// \return The return code of the analyser.
|
||||
int analyseTS(bool validate, bool analyse, int detailLevel){
|
||||
TS::Stream tsStream;
|
||||
std::map<unsigned long long, std::string> payloads;
|
||||
TS::Packet packet;
|
||||
long long int upTime = Util::bootSecs();
|
||||
int64_t pcr = 0;
|
||||
unsigned int bytes = 0;
|
||||
char packetPtr[188];
|
||||
while (std::cin.good()){
|
||||
std::cin.read(packetPtr,188);
|
||||
if(std::cin.gcount() != 188){break;}
|
||||
bytes += 188;
|
||||
if(packet.FromPointer(packetPtr)){
|
||||
//std::cout << packet.toPrettyString();
|
||||
tsStream.parse(packet, bytes);
|
||||
if (tsStream.hasPacket(packet.getPID())){
|
||||
DTSC::Packet dtscPack;
|
||||
tsStream.getPacket(packet.getPID(), dtscPack);
|
||||
std::cout << dtscPack.toJSON().toPrettyString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv){
|
||||
Util::Config conf = Util::Config(argv[0]);
|
||||
conf.addOption("analyse", JSON::fromString("{\"long\":\"analyse\", \"short\":\"a\", \"default\":1, \"long_off\":\"notanalyse\", \"short_off\":\"b\", \"help\":\"Analyse a file's contents (-a), or don't (-b) returning false on error. Default is analyse.\"}"));
|
||||
conf.addOption("validate", JSON::fromString("{\"long\":\"validate\", \"short\":\"V\", \"default\":0, \"long_off\":\"notvalidate\", \"short_off\":\"X\", \"help\":\"Validate (-V) the file contents or don't validate (-X) its integrity, returning false on error. Default is don't validate.\"}"));
|
||||
conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":3, \"help\":\"Detail level of analysis.\"}"));
|
||||
conf.parseArgs(argc, argv);
|
||||
return Analysers::analyseTS(conf.getBool("validate"),conf.getBool("analyse"),conf.getInteger("detail"));
|
||||
}
|
47
src/utils/util_amf.cpp
Normal file
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