Analysers unification

This commit is contained in:
Ramoe 2016-07-18 15:42:32 +02:00 committed by Thulinma
parent 19198e3abd
commit 051a8c826b
24 changed files with 2588 additions and 35 deletions

View file

@ -266,11 +266,74 @@ macro(makeAnalyser analyserName format)
)
endmacro()
makeAnalyser(RTMP rtmp)
makeAnalyser(FLV flv)
makeAnalyser(DTSC dtsc)
makeAnalyser(AMF amf)
makeAnalyser(MP4 mp4)
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(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

View file

@ -8,6 +8,7 @@
#include <string>
#include <mist/amf.h>
#include <mist/config.h>
#include <mist/defines.h>
///\brief Holds everything unique to the analysers.
namespace Analysers {
@ -15,7 +16,22 @@ namespace Analysers {
///
/// Expects AMF data through stdin, outputs human-readable information to stderr.
///\return The return code of the analyser.
int analyseAMF(){
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()){
@ -33,8 +49,9 @@ namespace Analysers {
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();
return Analysers::analyseAMF(conf);
}

104
src/analysers/analyser.cpp Normal file
View file

@ -0,0 +1,104 @@
#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();
}
int analysers::doAnalyse() {
return 0;
}
analysers::~analysers() {
}
void analysers::doValidate() {
std::cout << upTime << ", " << finTime << ", " << (finTime - upTime) << ", " << endTime << std::endl;
}
bool analysers::packetReady() {
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);
}
}
bool analysers::hasInput() {
// std::cout << std::cin.good() << std::endl;
return std::cin.good();
// return !feof(stdin);
}
int analysers::Run() {
if(mayExecute)
{
std::cout << "start analyser with detailLevel: " << detail << std::endl;
endTime = 0;
upTime = Util::bootSecs();
while (hasInput() && mayExecute) {
while (packetReady()) {
// std::cout << "in loop..." << std::endl;
endTime = doAnalyse();
}
}
finTime = Util::bootSecs();
if (validate) {
// std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << endTime << std::endl;
doValidate();
}
}
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.\"}"));
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(
"detail",
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}"));
}

36
src/analysers/analyser.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <mist/config.h>
#include <string>
class analysers
{
protected:
bool fileinput;
std::string filename;
bool analyse;
bool validate;
bool mayExecute = true;
int detail;
long long int endTime;
long long int upTime;
long long int finTime;
bool useBuffer;
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();
};

View file

@ -0,0 +1,645 @@
#include "analyser_dash.h"
#include <fstream>
#include <iostream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/mp4.h>
#include <mist/mp4_generic.h>
#include <mist/timing.h>
#include <set>
#define OTHER 0x00
#define VIDEO 0x01
#define AUDIO 0x02
// http://cattop:8080/dash/bunny/index.mpd
// http://balderlaptop:8080/dash/f+short.mp4/index.mpd
//
///\brief simple struct for storage of stream-specific data
StreamData tempSD; // temp global
bool getDelimBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim) {
size_t offset = data.find(name);
if (offset == std::string::npos) {
return false; // name string not found.
}
// expected: delim character BEFORE blockstart.
offset--;
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
offset = blockStart + 1; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos) {
return false; // no start/end quotes found
}
blockEnd++; // include delim
// DEBUG_MSG(DLVL_INFO, "getDelimPos: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getValueBlock(std::string &data, std::string name, size_t &blockStart, size_t &blockEnd, std::string delim) {
size_t offset = data.find(name);
if (offset == std::string::npos) {
return false; // name string not found.
}
blockStart = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockStart: %i ", offset, blockStart);
blockStart++; // clip off quote characters
offset = blockStart; // skip single character!
blockEnd = data.find(delim, offset);
// DEBUG_MSG(DLVL_INFO, "offset: %i blockEnd: %i ", offset, blockEnd);
if (blockStart == std::string::npos || blockEnd == std::string::npos) {
return false; // no start/end quotes found
}
// DEBUG_MSG(DLVL_INFO, "getValueBlock: data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) );
return true;
}
bool getString(std::string &data, std::string name, std::string &output) {
size_t blockStart = 0;
size_t blockEnd = 0;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")) {
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "data.size() %i start %i end %i num %i", data.size(), blockStart,blockEnd,(blockEnd-blockStart) )
output = data.substr(blockStart, (blockEnd - blockStart));
// looks like this function is working as expected
// DEBUG_MSG(DLVL_INFO, "data in getstring %s", (data.substr(blockStart,(blockEnd-blockStart))).c_str());
return true;
}
bool getLong(std::string &data, std::string name, long &output) {
size_t blockStart, blockEnd;
if (!getValueBlock(data, name, blockStart, blockEnd, "\"")) {
// DEBUG_MSG(DLVL_FAIL, "could not find \"%s\" in data block", name.c_str());
return false; // could not find value in this data block.
}
// DEBUG_MSG(DLVL_INFO, "name: %s data in atol %s",name.c_str(), (data.substr(blockStart,(blockEnd-blockStart))).c_str());
output = atol((data.substr(blockStart, (blockEnd - blockStart))).c_str());
return true;
}
// block expecting separate name and /name occurence, or name and /> before another occurence of <.
bool getBlock(std::string &data, std::string name, int offset, size_t &blockStart, size_t &blockEnd) {
blockStart = data.find("<" + name + ">", offset);
if (blockStart == std::string::npos) {
blockStart = data.find("<" + name + " ", offset); // this considers both valid situations <name> and <name bla="bla"/>
}
if (blockStart == std::string::npos) {
DEBUG_MSG(DLVL_INFO, "no block start found for name: %s at offset: %i", name.c_str(), offset);
return false;
}
blockEnd = data.find("/" + name + ">", blockStart);
if (blockEnd == std::string::npos) {
blockEnd = data.find("/>", blockStart);
if (blockEnd == std::string::npos) {
DEBUG_MSG(DLVL_INFO, "no block end found.");
return false;
}
size_t temp = data.find("<", blockStart + 1, (blockEnd - blockStart - 1)); // the +1 is to avoid re-interpreting the starting < //TODO!!
if (temp != std::string::npos) { // all info is epxected between <name ... />
DEBUG_MSG(DLVL_FAIL, "block start found before block end. offset: %lu block: %s", temp, data.c_str());
return false;
}
// DEBUG_MSG(DLVL_FAIL, "special block end found");
blockEnd += 2; // position after />
} else {
blockEnd += name.size() + 2; // position after /name>
}
// DEBUG_MSG(DLVL_INFO, "getBlock: start: %i end: %i",blockStart,blockEnd);
return true;
}
bool parseAdaptationSet(std::string &data, std::set<seekPos> &currentPos) {
// DEBUG_MSG(DLVL_INFO, "Parsing adaptationSet: %s", data.c_str());
size_t offset = 0;
size_t blockStart, blockEnd;
tempSD.trackType = OTHER;
// get value: mimetype //todo: handle this!
std::string mimeType;
if (!getString(data, "mimeType", mimeType)) { // get first occurence of mimeType. --> this will break if multiple mimetypes should be read from
// this block because no offset is provided. solution: use this on a substring containing the
// desired information.
DEBUG_MSG(DLVL_FAIL, "mimeType not found");
return false;
}
DEBUG_MSG(DLVL_INFO, "mimeType: %s", mimeType.c_str()); // checked, OK
if (mimeType.find("video") != std::string::npos) { tempSD.trackType = VIDEO; }
if (mimeType.find("audio") != std::string::npos) { tempSD.trackType = AUDIO; }
if (tempSD.trackType == OTHER) {
DEBUG_MSG(DLVL_FAIL, "no audio or video type found. giving up.");
return false;
}
// find an ID within this adaptationSet block.
if (!getBlock(data, (std::string) "Representation", offset, blockStart, blockEnd)) {
DEBUG_MSG(DLVL_FAIL, "Representation not found");
return false;
}
// representation string
std::string block = data.substr(blockStart, (blockEnd - blockStart));
DEBUG_MSG(DLVL_INFO, "Representation block: %s", block.c_str());
// check if block is not junk?
if (!getLong(block, "id", tempSD.trackID)) {
DEBUG_MSG(DLVL_FAIL, "Representation id not found in block %s", block.c_str());
return false;
}
DEBUG_MSG(DLVL_INFO, "Representation/id: %li", tempSD.trackID); // checked, OK
offset = 0;
// get values from SegmentTemplate
if (!getBlock(data, (std::string) "SegmentTemplate", offset, blockStart, blockEnd)) {
DEBUG_MSG(DLVL_FAIL, "SegmentTemplate not found");
return false;
}
block = data.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "SegmentTemplate block: %s",block.c_str()); //OK
getLong(block, "timescale", tempSD.timeScale);
getString(block, "media", tempSD.media);
getString(block, "initialization", tempSD.initialization);
size_t tmpBlockStart = 0;
size_t tmpBlockEnd = 0;
if (!getDelimBlock(tempSD.media, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")) {
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.media, "Time", tmpBlockStart, tmpBlockEnd, "$")) {
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $Time$ in %s", tempSD.media.c_str());
return false;
}
tempSD.media.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
if (!getDelimBlock(tempSD.initialization, "RepresentationID", tmpBlockStart, tmpBlockEnd, "$")) {
DEBUG_MSG(DLVL_FAIL, "Failed to find and replace $RepresentationID$ in %s", tempSD.initialization.c_str());
return false;
}
tempSD.initialization.replace(tmpBlockStart, (tmpBlockEnd - tmpBlockStart), "%d");
// get segment timeline block from within segment template:
size_t blockOffset = 0; // offset should be 0 because this is a new block
if (!getBlock(block, "SegmentTimeline", blockOffset, blockStart, blockEnd)) {
DEBUG_MSG(DLVL_FAIL, "SegmentTimeline block not found");
return false;
}
std::string block2 = block.substr(blockStart, (blockEnd - blockStart)); // overwrites previous block (takes just the segmentTimeline part
// DEBUG_MSG(DLVL_INFO, "SegmentTimeline block: %s",block2.c_str()); //OK
int numS = 0;
offset = 0;
long long unsigned int totalDuration = 0;
long timeValue;
while (1) {
if (!getBlock(block2, "S", offset, blockStart, blockEnd)) {
if (numS == 0) {
DEBUG_MSG(DLVL_FAIL, "no S found within SegmentTimeline");
return false;
} else {
DEBUG_MSG(DLVL_INFO, "all S found within SegmentTimeline %i", numS);
return true; // break; //escape from while loop (to return true)
}
}
numS++;
// stuff S data into: currentPos
// searching for t(start position)
std::string sBlock = block2.substr(blockStart, (blockEnd - blockStart));
// DEBUG_MSG(DLVL_INFO, "S found. offset: %i blockStart: %i blockend: %i block: %s",offset,blockStart, blockEnd, sBlock.c_str()); //OK!
if (getLong(sBlock, "t", timeValue)) {
totalDuration = timeValue; // reset totalDuration to value of t
}
if (!getLong(sBlock, "d", timeValue)) { // expected duration in every S.
DEBUG_MSG(DLVL_FAIL, "no d found within S");
return false;
}
// stuff data with old value (start of block)
// DEBUG_MSG(DLVL_INFO, "stuffing info from S into set");
seekPos thisPos;
thisPos.trackType = tempSD.trackType;
thisPos.trackID = tempSD.trackID;
thisPos.adaptationSet = tempSD.adaptationSet;
// thisPos.trackID=id;
thisPos.seekTime = totalDuration; // previous total duration is start time of this S.
thisPos.duration = timeValue;
thisPos.timeScale = tempSD.timeScale;
static char charBuf[512];
snprintf(charBuf, 512, tempSD.media.c_str(), tempSD.trackID, totalDuration);
thisPos.url.assign(charBuf);
// DEBUG_MSG(DLVL_INFO, "media url (from rep.ID %d, startTime %d): %s", tempSD.trackID, totalDuration,thisPos.url.c_str());
currentPos.insert(thisPos); // assumes insert copies all data in seekPos struct.
totalDuration += timeValue; // update totalDuration
offset = blockEnd; // blockEnd and blockStart are absolute values within string, offset is not relevant.
}
return true;
}
bool parseXML(std::string &body, std::set<seekPos> &currentPos, std::vector<StreamData> &streamData) {
// for all adaptation sets
// representation ID
int numAdaptationSet = 0;
size_t currentOffset = 0;
size_t adaptationSetStart;
size_t adaptationSetEnd;
// DEBUG_MSG(DLVL_INFO, "body received: %s", body.c_str());
while (getBlock(body, "AdaptationSet", currentOffset, adaptationSetStart, adaptationSetEnd)) {
tempSD.adaptationSet = numAdaptationSet;
numAdaptationSet++;
DEBUG_MSG(DLVL_INFO, "adaptationSet found. start: %lu end: %lu num: %lu ", adaptationSetStart, adaptationSetEnd,
(adaptationSetEnd - adaptationSetStart));
// get substring: from <adaptationSet... to /adaptationSet>
std::string adaptationSet = body.substr(adaptationSetStart, (adaptationSetEnd - adaptationSetStart));
// function was verified: output as expected.
if (!parseAdaptationSet(adaptationSet, currentPos)) {
DEBUG_MSG(DLVL_FAIL, "parseAdaptationSet returned false."); // this also happens in the case of OTHER mimetype. in that case it might be
// desirable to continue searching for valid data instead of quitting.
return false;
}
streamData.push_back(tempSD); // put temp values into adaptation set vector
currentOffset = adaptationSetEnd; // the getblock function should make sure End is at the correct offset.
}
if (numAdaptationSet == 0) {
DEBUG_MSG(DLVL_FAIL, "no adaptationSet found.");
return false;
}
DEBUG_MSG(DLVL_INFO, "all adaptation sets found. total: %i", numAdaptationSet);
return true;
}
dashAnalyser::dashAnalyser(Util::Config conf) : analysers(conf) {
port = 80;
url = conf.getString("url");
if (url.substr(0, 7) != "http://") {
DEBUG_MSG(DLVL_FAIL, "The URL must start with http://");
// return -1;
exit(1);
}
url = url.substr(7); // found problem if url is to short!! it gives out of range when entering http://meh.meh
if((url.find('/') == std::string::npos) || (url.find(".mpd") == std::string::npos))
{
std::cout << "incorrect url"<<std::endl;
mayExecute = false;
return;
}
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");
conn = Socket::Connection(server, port, false);
if(!conn.connected())
{
mayExecute = false;
return;
}
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
if (!conn) {
conn = Socket::Connection(server, port, false);
}
pos = 0;
HTTP::Parser H;
H.url = url;
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SendRequest(conn);
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))) {}
H.BuildResponse();
currentPos;
streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)) {
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate") {
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
// return -1;
exit(1);
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++) {
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++) { // get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet, streamData[i].trackID,
streamData[i].initURL.c_str());
}
}
bool dashAnalyser::hasInput() {
return currentPos.size();
}
bool dashAnalyser::packetReady() {
return (abortTime <= 0 || Util::bootSecs() < startTime + abortTime) && (currentPos.size() > 0);
}
dashAnalyser::~dashAnalyser() {
INFO_MSG("stopped");
}
int dashAnalyser::doAnalyse() {
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
// match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++) {
if (streamData[i].trackID == currentPos.begin()->trackID && streamData[i].adaptationSet == currentPos.begin()->adaptationSet) tempID = i;
}
if (!conn) { conn = Socket::Connection(server, port, false); }
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))) {} // ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()) {
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
// break;
return 0;
}
size_t beforeParse = H.body.size();
MP4::Box mp4Data;
bool mdatSeen = false;
while (mp4Data.read(H.body)) {
if (mp4Data.isType("mdat")) { mdatSeen = true; }
}
if (!mdatSeen) {
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
// break;
return 0;
}
if (H.body.size()) {
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()) {
// break;
return 0;
}
}
H.Clean();
pos = 1000 * (currentPos.begin()->seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale;
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) {
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
}
currentPos.erase(currentPos.begin());
endTime = pos;
return pos;
}
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();
dashAnalyser A(conf);
A.Run();
return 0;
}
int main2(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); // found problem if url is to short!! it gives out of range when entering http://meh.meh
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");
Socket::Connection conn(server, port, false);
// url:
DEBUG_MSG(DLVL_INFO, "url %s server: %s port: %d", url.c_str(), server.c_str(), port);
std::string urlPrependStuff = url.substr(0, url.rfind("/") + 1);
DEBUG_MSG(DLVL_INFO, "prepend stuff: %s", urlPrependStuff.c_str());
if (!conn) { conn = Socket::Connection(server, port, false); }
unsigned int pos = 0;
HTTP::Parser H;
H.url = url;
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString());
H.SendRequest(conn);
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))) {}
H.BuildResponse();
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
// DEBUG_MSG(DLVL_INFO, "body received: %s", H.body.c_str()); //keeps giving empty stuff :(
// DEBUG_MSG(DLVL_INFO, "url %s ", url.c_str());
// std::ifstream in(url.c_str());
// std::string s((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!parseXML(H.body, currentPos, streamData)) {
DEBUG_MSG(DLVL_FAIL, "Manifest parsing failed. body: \n %s", H.body.c_str());
if (conf.getString("mode") == "validate") {
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return -1;
}
H.Clean();
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "*SUMMARY*");
DEBUG_MSG(DLVL_INFO, "*********");
DEBUG_MSG(DLVL_INFO, "num streams: %lu", streamData.size());
for (unsigned int i = 0; i < streamData.size(); i++) {
DEBUG_MSG(DLVL_INFO, "");
DEBUG_MSG(DLVL_INFO, "ID in vector %d", i);
DEBUG_MSG(DLVL_INFO, "trackID %ld", streamData[i].trackID);
DEBUG_MSG(DLVL_INFO, "adaptationSet %d", streamData[i].adaptationSet);
DEBUG_MSG(DLVL_INFO, "trackType (audio 0x02, video 0x01) %d", streamData[i].trackType);
DEBUG_MSG(DLVL_INFO, "TimeScale %ld", streamData[i].timeScale);
DEBUG_MSG(DLVL_INFO, "Media string %s", streamData[i].media.c_str());
DEBUG_MSG(DLVL_INFO, "Init string %s", streamData[i].initialization.c_str());
}
DEBUG_MSG(DLVL_INFO, "");
for (unsigned int i = 0; i < streamData.size(); i++) { // get init url
static char charBuf[512];
snprintf(charBuf, 512, streamData[i].initialization.c_str(), streamData[i].trackID);
streamData[i].initURL.assign(charBuf);
DEBUG_MSG(DLVL_INFO, "init url for adaptationSet %d trackID %ld: %s ", streamData[i].adaptationSet, streamData[i].trackID,
streamData[i].initURL.c_str());
}
while (currentPos.size() && (abortTime <= 0 || Util::bootSecs() < startTime + abortTime)) {
// DEBUG_MSG(DLVL_INFO, "next url: %s", currentPos.begin()->url.c_str());
std::cout << "blaa" << std::endl;
// match adaptation set and track id?
int tempID = 0;
for (unsigned int i = 0; i < streamData.size(); i++) {
if (streamData[i].trackID == currentPos.begin()->trackID && streamData[i].adaptationSet == currentPos.begin()->adaptationSet) tempID = i;
}
if (!conn) { conn = Socket::Connection(server, port, false); }
HTTP::Parser H;
H.url = urlPrependStuff;
H.url.append(currentPos.begin()->url);
DEBUG_MSG(DLVL_INFO, "Retrieving segment: %s (%llu-%llu)", H.url.c_str(), currentPos.begin()->seekTime,
currentPos.begin()->seekTime + currentPos.begin()->duration);
H.SetHeader("Host", server + ":" + JSON::Value((long long)port).toString()); // wut?
H.SendRequest(conn);
// TODO: get response?
H.Clean();
while (conn && (!conn.spool() || !H.Read(conn))) {} // ehm...
// std::cout << "leh vomi: "<<H.body <<std::endl;
// DEBUG_MSG(DLVL_INFO, "zut: %s", H.body.c_str());
// strBuf[tempID].append(H.body);
if (!H.body.size()) {
DEBUG_MSG(DLVL_FAIL, "No data downloaded from %s", H.url.c_str());
break;
}
size_t beforeParse = H.body.size();
MP4::Box mp4Data;
bool mdatSeen = false;
while (mp4Data.read(H.body)) {
if (mp4Data.isType("mdat")) { mdatSeen = true; }
}
if (!mdatSeen) {
DEBUG_MSG(DLVL_FAIL, "No mdat present. Sadface. :-(");
break;
}
if (H.body.size()) {
DEBUG_MSG(DLVL_FAIL, "%lu bytes left in body. Assuming horrible things...", H.body.size()); //,H.body.c_str());
std::cerr << H.body << std::endl;
if (beforeParse == H.body.size()) { break; }
}
H.Clean();
pos = 1000 * (currentPos.begin()->seekTime + currentPos.begin()->duration) / streamData[tempID].timeScale;
if (conf.getString("mode") == "validate" && (Util::bootSecs() - startTime + 5) * 1000 < pos) {
Util::wait(pos - (Util::bootSecs() - startTime + 5) * 1000);
}
currentPos.erase(currentPos.begin());
}
if (conf.getString("mode") == "validate") {
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
return 0;
}

View file

@ -0,0 +1,78 @@
#include <mist/config.h>
#include "analyser.h"
#include <set>
struct StreamData{
long timeScale;
std::string media;
std::string initialization;
std::string initURL;
long trackID;
unsigned int adaptationSet;
unsigned char trackType;
};
struct seekPos {
///\brief Less-than comparison for seekPos structures.
///\param rhs The seekPos to compare with.
///\return Whether this object is smaller than rhs.
bool operator < (const seekPos & rhs) const {
if ((seekTime*rhs.timeScale) < (rhs.seekTime*timeScale)) {
return true;
} else {
if ( (seekTime*rhs.timeScale) == (rhs.seekTime*timeScale)){
if (adaptationSet < rhs.adaptationSet){
return true;
} else if (adaptationSet == rhs.adaptationSet){
if (trackID < rhs.trackID) {
return true;
}
}
}
}
return false;
}
long timeScale;
long long unsigned int bytePos; /// ?
long long unsigned int seekTime; ///start
long long unsigned int duration; ///duration
unsigned int trackID; ///stores representation ID
unsigned int adaptationSet; ///stores type
unsigned char trackType; ///stores type
std::string url;
};
class dashAnalyser : public analysers
{
public:
dashAnalyser(Util::Config config);
~dashAnalyser();
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;
Socket::Connection conn;
std::string urlPrependStuff;
unsigned int pos;
std::set<seekPos> currentPos;
std::vector<StreamData> streamData;
};

View file

@ -0,0 +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;
F.reInit(conn);
totalBytes = 0;
// F = DTSC::Packet(config.getString("filename"));
if (!F) {
std::cerr << "Not a valid DTSC file" << std::endl;
mayExecute = false;
return;
}
if (F.getVersion() == DTSC::DTSC_HEAD) // for meta
{
DTSC::Meta m(F);
if (detail > 0) {
JSON::Value result;
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["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") {
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;
}
}
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 (m.vod || m.live) { m.toPrettyString(std::cout, 0, 0x03); }
}
}
bool dtscAnalyser::packetReady() {
return (F.getDataLen() > 0);
}
bool dtscAnalyser::hasInput() {
return F;
}
int dtscAnalyser::doAnalyse() {
if (analyse) { // always analyse..?
switch (F.getVersion()) {
case DTSC::DTSC_V1: {
std::cout << "DTSCv1 packet: " << F.getScan().toPrettyString() << std::endl;
break;
}
case DTSC::DTSC_V2: {
std::cout << "DTSCv2 packet (Track " << F.getTrackId() << ", time " << F.getTime() << "): " << F.getScan().toPrettyString() << std::endl;
break;
}
case DTSC::DTSC_HEAD: {
std::cout << "DTSC header: " << F.getScan().toPrettyString() << std::endl;
break;
}
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;
}
}
totalBytes += F.getDataLen();
F.reInit(conn);
return totalBytes;
}
int main(int argc, char **argv) {
Util::Config conf = Util::Config(argv[0]);
analysers::defaultConfig(conf);
conf.parseArgs(argc, argv);
dtscAnalyser A(conf);
A.Run();
return 0;
}

View file

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

View file

@ -0,0 +1,73 @@
#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;
}
filter = conf.getInteger("filter");
FLV::Tag flvData; // Temporary storage for incoming FLV data.
//check for flv data
char flvHeader[3];
std::cin.read(flvHeader,3);
if(flvHeader[0] != 0x46 || flvHeader[1] != 0x4C || flvHeader[2] != 0x56)
{
FAIL_MSG("No FLV Signature found!");
mayExecute = false;
return;
}
std::cin.seekg(0);
}
/*
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;
}
}
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)\"}"));
analysers::defaultConfig(conf);
conf.parseArgs(argc, argv);
flvAnalyser A(conf);
A.Run();
return 0;
}

View file

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

View file

@ -0,0 +1,221 @@
#include "analyser_hls.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <mist/config.h>
#include <mist/defines.h>
#include <mist/http_parser.h>
#include <mist/timing.h>
#include <string.h>
#include <sys/sysinfo.h>
// http://patchy.ddvtech.com:8080/hls/bbb/index.m3u8
//
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;
}
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;
return;
}
url = url.substr(7);
//check if url ends with .m3u8?
if((url.find('/') == std::string::npos) || (url.find(".m3u") == std::string::npos && url.find(".m3u8") == std::string::npos))
{
std::cout << "incorrect url"<<std::endl;
mayExecute = false;
return;
}
server = url.substr(0, url.find('/'));
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;
}
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());
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;
return 0;
}
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();
}
return pos;
}
bool hlsAnalyser::hasInput() {
return repeat;
}
bool hlsAnalyser::packetReady() {
return repeat;
}
hlsAnalyser::~hlsAnalyser() {
// INFO_MSG("call destructor");
// DEBUG_MSG(DLVL_INFO, "mode: %s", conf.getString("mode").c_str());
//
/* call doValidate() from superclass
if (conf.getString("mode") == "validate") {
long long int endTime = Util::bootSecs();
std::cout << startTime << ", " << endTime << ", " << (endTime - startTime) << ", " << pos << std::endl;
}
*/
}
int main(int argc, char **argv) {
Util::Config conf = Util::Config(argv[0]);
conf.addOption("mode", JSON::fromString("{\"long\":\"mode\", \"arg\":\"string\", \"short\":\"m\", \"default\":\"analyse\", \"help\":\"What to "
"do with the stream. Valid modes are 'analyse', 'validate', 'output'.\"}"));
conf.addOption("url", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"URL to HLS stream index file to retrieve.\"}"));
conf.addOption("abort", JSON::fromString("{\"long\":\"abort\", \"short\":\"a\", \"arg\":\"integer\", \"default\":-1, \"help\":\"Abort after "
"this many seconds of downloading. Negative values mean unlimited, which is the default.\"}"));
conf.addOption(
"detail",
JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of analysis. \"}"));
conf.parseArgs(argc, argv);
conf.activate();
hlsAnalyser A(conf);
A.Run();
}

View file

@ -0,0 +1,55 @@
//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"
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 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;
std::deque<HLSPart> parts;
Socket::Connection conn;
std::string playlist;
bool repeat;
std::string lastDown;
unsigned int pos;
bool output;
};

View file

@ -0,0 +1,71 @@
#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;
}
int mp4Analyser::doAnalyse() {
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
std::cerr << mp4Data.toPrettyString(0) << std::endl;
return dataSize; // endtime?
}
bool mp4Analyser::hasInput() {
if (!std::cin.good()) { return false; }
mp4Buffer += std::cin.get();
dataSize++;
if (!std::cin.good()) {
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
dataSize--;
}
return true;
}
bool mp4Analyser::packetReady() {
return mp4Data.read(mp4Buffer);
}
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;
}

View file

@ -0,0 +1,23 @@
#include <mist/config.h>
#include <mist/mp4.h>
#include "analyser.h"
class mp4Analyser : public analysers
{
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();
};

View file

@ -0,0 +1,49 @@
#include <string>
#include "analyser_ogg.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;
}
void oggAnalyser::doValidate()
{
std::cout << upTime << ", " << finTime << ", " << (finTime-upTime) << ", " << flvData.tagTime() << std::endl;
}
bool oggAnalyser::packetReady()
{
return flvData.FileLoader(stdin);
}
int oggAnalyser::doAnalyse()
{
if (analyse){ //always analyse..?
if (!filter || filter == flvData.data[0]){
std::cout << "[" << flvData.tagTime() << "+" << flvData.offset() << "] " << flvData.tagType() << std::endl;
}
}
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;
}

View file

@ -0,0 +1,213 @@
#include <string>
#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
rtmpAnalyser::rtmpAnalyser(Util::Config config) : analysers(config)
{
std::cout << "rtmp constr" << std::endl;
Detail = conf.getInteger("detail");
//Detail = 4;
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);
RTMPStream::rec_cnt += 3073;
read_in = 0;
endTime = 0;
}
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
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();
}
}
}
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();
return 0;
}

View file

@ -0,0 +1,35 @@
#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/rtmpchunks.h>
#include <mist/config.h>
#include <mist/socket.h>
class rtmpAnalyser : public analysers
{
FLV::Tag flvData;
long long filter;
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();
};

View file

@ -0,0 +1,204 @@
#include <string>
#include "analyser_rtp.h"
rtpAnalyser::rtpAnalyser(Util::Config config) : analysers(config)
{
std::cout << "rtp constr" << std::endl;
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..?
// 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 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;
}

View file

@ -0,0 +1,27 @@
#include <mist/config.h>
#include "analyser.h"
#include <cstdlib>
#include <iomanip>
#include <vector>
#include <sstream>
#include <mist/socket.h>
#include <mist/rtp.h>
#include <mist/http_parser.h>
class rtpAnalyser : public analysers
{
Socket::Connection conn;
HTTP::Parser HTTP_R, HTTP_S;//HTTP Receiver en HTTP Sender.
int step;
std::vector<std::string> tracks;
std::vector<Socket::UDPConnection> connections;
unsigned int trackIt;
public:
rtpAnalyser(Util::Config config);
bool packetReady();
void PreProcessing();
//int Analyse();
int doAnalyse();
void doValidate();
};

View file

@ -0,0 +1,211 @@
#include "analyser_ts.h"
#include "analyser.h"
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <mist/bitfields.h>
#include <mist/config.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;
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++;
}
std::cin.read(packetPtr, 188);
//0x47
if (std::cin.gcount() != 188) { return 0; }
bytes += 188;
if (packet.FromPointer(packetPtr)) {
if (analyse) {
if (packet.getUnitStart() && payloads[packet.getPID()] != "") {
std::cout << printPES(payloads[packet.getPID()], packet.getPID(), detailLevel);
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() && !packet.isPMT() && (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;
}
return endTime;
}
int main(int argc, char **argv) {
Util::Config conf = Util::Config(argv[0]);
analysers::defaultConfig(conf);
conf.addOption("filter", JSON::fromString("{\"arg\":\"num\", \"short\":\"f\", \"long\":\"filter\", \"default\":0, \"help\":\"Only print info "
"about this tag type (8 = audio, 9 = video, 0 = all)\"}"));
// override default detail level with specific detail level for TS Analyser.
conf.addOption("detail", JSON::fromString("{\"long\":\"detail\", \"short\":\"D\", \"arg\":\"num\", \"default\":2, \"help\":\"Detail level of "
"analysis. 1 = PES only, 2 = PAT/PMT (default), 3 = all TS packets, 9 = raw PES packet bytes, 10 = "
"raw TS packet bytes\"}"));
conf.parseArgs(argc, argv);
tsAnalyser A(conf);
A.Run();
return 0;
}
tsAnalyser::~tsAnalyser() {
for (std::map<unsigned long long, std::string>::iterator it = payloads.begin(); it != payloads.end(); it++) {
if (!it->first || it->first == 4096) { continue; }
std::cout << printPES(it->second, it->first, detailLevel);
}
}
std::string tsAnalyser::printPES(const std::string &d, unsigned long PID, int detailLevel) {
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) << "]"; }
res << std::endl;
if (detailLevel == 10) {
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();
}

View file

@ -0,0 +1,27 @@
#include <mist/config.h>
#include <mist/ts_packet.h>
#include "analyser.h"
class tsAnalyser : public analysers
{
long long filter;
std::map<unsigned long long, std::string> payloads;
TS::Packet packet;
long long int upTime;
int64_t pcr;
unsigned int bytes;
char packetPtr[188];
int detailLevel;
int incorrectPacket;
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);
};

View file

@ -15,6 +15,54 @@
#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.
@ -22,28 +70,10 @@ 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);
bool analyse = conf.getString("mode") == "analyse";
bool validate = conf.getString("mode") == "validate";
long long filter = conf.getInteger("filter");
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;
return Analysers::analyseFLV(conf);
}

View file

@ -17,7 +17,42 @@ namespace Analysers {
///
/// Expects MP4 data through stdin, outputs human-readable information to stderr.
///\return The return code of the analyser.
int analyseMP4(){
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()){
@ -25,15 +60,19 @@ namespace Analysers {
}
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;
}
@ -43,7 +82,8 @@ namespace Analysers {
/// 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();
return Analysers::analyseMP4(conf);
}

View file

@ -118,6 +118,167 @@ namespace Analysers{
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;
@ -227,7 +388,7 @@ namespace Analysers{
return 0;
}
int validateOGG(bool analyse){
std::map<int,std::string> sn2Codec;
std::string oggBuffer;
@ -242,7 +403,8 @@ namespace Analysers{
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();
}
@ -286,7 +448,7 @@ namespace Analysers{
}
}
//while OGG::page check function read
//save last time
//save last time0
sysinfo(&sinfo);
long long int tTime = sinfo.uptime;
if((tTime-upTime) > 5 && (tTime-upTime)>(int)(lastTime) ){
@ -307,15 +469,23 @@ namespace Analysers{
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\":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("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");
}
}