diff --git a/IPTVUtils.pro b/IPTVUtils.pro index 04b7706..60a75f4 100644 --- a/IPTVUtils.pro +++ b/IPTVUtils.pro @@ -5,6 +5,7 @@ #------------------------------------------------- QT += core gui network +QT += core gui charts greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -61,7 +62,10 @@ SOURCES += \ Analyzer/tsanalyzer.cpp \ playbackwidget.cpp \ recordwidget.cpp \ - convertwidget.cpp + convertwidget.cpp \ + Middleware/networkjitter.cpp \ + recordwidgetgraph.cpp \ + recordtxtprinter.cpp HEADERS += \ mainwindow.h \ @@ -110,7 +114,10 @@ HEADERS += \ recordwidget.h \ convertwidget.h \ Status/streamid.h \ - Status/workerstatus.h + Status/workerstatus.h \ + Middleware/networkjitter.h \ + recordwidgetgraph.h \ + recordtxtprinter.h FORMS += \ mainwindow.ui \ diff --git a/Middleware/analyzerpcapmiddleware.cpp b/Middleware/analyzerpcapmiddleware.cpp index da17dec..d5fe357 100644 --- a/Middleware/analyzerpcapmiddleware.cpp +++ b/Middleware/analyzerpcapmiddleware.cpp @@ -14,6 +14,8 @@ void AnalyzerPcapMiddleware::init() { this->moveToThread(&runnerThread); connect(&runnerThread, &QThread::started, this, &AnalyzerPcapMiddleware::run); connect(this, &AnalyzerPcapMiddleware::finished, &runnerThread, &QThread::quit); + + } void AnalyzerPcapMiddleware::start() { @@ -34,9 +36,10 @@ void AnalyzerPcapMiddleware::run() { this->thread()->quit(); } + // Buffers packets void AnalyzerPcapMiddleware::bufferProducts() { - QElapsedTimer statusTimer; + QTime statusTimer; statusTimer.start(); packetNumber = 0; @@ -56,7 +59,10 @@ void AnalyzerPcapMiddleware::bufferProducts() { PcapProduct input; + while (!stopping) { + + input = prevProvider->getProduct(); if (input.type == PcapProduct::NORMAL && !hasLooped) { PacketParser parser((pcap_pkthdr*)input.header.data(), (const u_char*)input.data.data()); @@ -65,6 +71,7 @@ void AnalyzerPcapMiddleware::bufferProducts() { if (startTime == -1) { startTime = ((pcap_pkthdr*)input.header.data())->ts.tv_sec * 1000; // convert to ms startTime += ((pcap_pkthdr*)input.header.data())->ts.tv_usec/1000; // convert to ms + lastDuration = 0; } else { endTime = ((pcap_pkthdr*)input.header.data())->ts.tv_sec*1000; @@ -72,21 +79,18 @@ void AnalyzerPcapMiddleware::bufferProducts() { duration = endTime - startTime; if (lastDuration == 0) lastDuration = duration; - } - - // 1 second of trafic, calculate bitrate for that second - if (duration - lastDuration >= 1000) { - bitrate = (bytes - lastSecondBytes)*8*1000/(duration - lastDuration); - lastSecondBytes = bytes; - lastDuration = duration; + // 1 second of trafic, calculate bitrate for that second + if (duration - lastDuration >= emitFrequency) { + bitrate = (bytes - lastSecondBytes)*8*1000/(duration - lastDuration); + lastSecondBytes = bytes; + } } // sanity check, prevents analyzation of packets that do not contain ts-packets if (parser.data_len % 188 == 0 && parser.ih->proto == 17 && parser.data_len != 0) { - tsPerIp = parser.data_len/188; // For now, assume the only existing protocols @@ -112,13 +116,14 @@ void AnalyzerPcapMiddleware::bufferProducts() { else stream.bitrateMode = StreamInfo::BitrateMode::VBR; - stream.id = StreamId(streamId); - stream.bytes += parser.data_len; - stream.tsPerIp = tsPerIp; - stream.currentTime = duration; - - tsAnalyzer.setStream(&stream.tsErrors, &stream.pidMap); + //if (duration - stream.lastDuration >= 200) + stream.id = StreamId(streamId); + stream.bytes += parser.data_len; + stream.tsPerIp = tsPerIp; + stream.currentTime = duration; + tsAnalyzer.setStream(&stream.tsErrors, &stream.pidMap); + // } // Analyze every TsPacket for (quint8 i = 0; i < tsPerIp; i++) { tsParser.parse((quint8*)(parser.data+i*188)); @@ -135,15 +140,12 @@ void AnalyzerPcapMiddleware::bufferProducts() { } else { - } } // case 1: sanity check true buffer.push(input); packetNumber++; - - } else { @@ -153,7 +155,7 @@ void AnalyzerPcapMiddleware::bufferProducts() { } } - else if (input.type == PcapProduct::LOOP) { + else if (input.type == PcapProduct::LOOP || input.type == PcapProduct::END) { hasLooped = true; buffer.push(input); @@ -162,10 +164,36 @@ void AnalyzerPcapMiddleware::bufferProducts() { } - if (statusTimer.elapsed() >= 200) { + + + + + + if (lastDuration > 1000000000){ + lastDuration = 0; + } + + + if (duration - lastDuration >= emitFrequency) { + + lastDuration = duration; + + for (auto &stream : streams) { + + stream.avgBitrate = ((double)stream.bytes*8*1000.0/stream.currentTime) /1000000.0; + stream.currentBitrate = (double)(stream.bytes - stream.lastSecondBytes)*8*1000/(duration - stream.lastDuration) / 1000000; + stream.lastSecondBytes = stream.bytes; + stream.lastDuration = duration; + } + emit status(AnalyzerStatus(Status::STATUS_PERIODIC, bytes, duration, bitrate, duration, pidMap, tsErrors, proto, tsPerIp)); - emit workerStatus(WorkerStatus(WorkerStatus::STATUS_PERIODIC, streams)); + if(bitrate / 1000000 < 100){ + emit bitrateStatus((double) bitrate /1000000, duration ); + + } + statusTimer.restart(); + emit workerStatus(WorkerStatus(WorkerStatus::STATUS_PERIODIC, streams), false); } if (input.type == PcapProduct::END || input.type == PcapProduct::STOP) { @@ -175,12 +203,12 @@ void AnalyzerPcapMiddleware::bufferProducts() { qInfo("Analyzer done, processed %lli packets", packetNumber-1); emit status(AnalyzerStatus(Status::STATUS_FINISHED, bytes, duration, bitrate, duration, pidMap, tsErrors, proto, tsPerIp)); - emit workerStatus(WorkerStatus(WorkerStatus::STATUS_FINISHED, streams)); + emit workerStatus(WorkerStatus(WorkerStatus::STATUS_FINISHED, streams), false); if (input.type == PcapProduct::END && (config.getWorkerMode() == WorkerConfiguration::ANALYSIS_MODE_LIVE || config.getWorkerMode() == WorkerConfiguration::ANALYSIS_MODE_OFFLINE)) { - emit workerStatus(WorkerStatus(WorkerStatus::STATUS_ANALYZED_ENTIRE, streams)); + emit workerStatus(WorkerStatus(WorkerStatus::STATUS_ANALYZED_ENTIRE, streams), false); } } diff --git a/Middleware/analyzerpcapmiddleware.h b/Middleware/analyzerpcapmiddleware.h index 32bc8db..fbb32f5 100644 --- a/Middleware/analyzerpcapmiddleware.h +++ b/Middleware/analyzerpcapmiddleware.h @@ -16,6 +16,8 @@ class AnalyzerPcapMiddleware : public PcapMiddleware { Q_OBJECT private: + double currentIatDev; + qint64 currentIatDevTimestamp; ConcurrentQueue buffer; TsParser tsParser; TsAnalyzer tsAnalyzer; @@ -41,6 +43,7 @@ class AnalyzerPcapMiddleware : public PcapMiddleware signals: void status(AnalyzerStatus status); + void bitrateStatus(double bitrate, qint64 duration); protected slots: void run(); diff --git a/Middleware/networkjitter.cpp b/Middleware/networkjitter.cpp new file mode 100644 index 0000000..18f1541 --- /dev/null +++ b/Middleware/networkjitter.cpp @@ -0,0 +1,160 @@ +#include "networkjitter.h" +#include + +#include +#include "Status/streamid.h" +#include "Status/streaminfo.h" +#include "packetparser.h" +#include "tsparser.h" +#include +#include + +//This class is for measuring network jitter + +void NetworkJitter::init() { + this->moveToThread(&runnerThread); + connect(&runnerThread, &QThread::started, this, &NetworkJitter::run); + connect(this, &NetworkJitter::finished, &runnerThread, &QThread::quit); +} + + +void NetworkJitter::run(){ + + emit started(); + QElapsedTimer statusTimer; + statusTimer.start(); + + packetNumber = 0; + + + qint64 inputTs = 0; + qint64 previousInputTs = 0; + qint64 difference = 0; + qint64 differencePerSec = 0; + qint64 packetCounter = 0; + qint16 diffratePerSec = 0; + + qint64 diffSum = 0; + qint64 finalSum = 0; + qint64 startTime = -1; + qint64 endTime = -1; + + + QList distanceList; + qint64 bytes = 0; + PcapProduct input; + + PcapProduct previousInput = prevProvider->getProduct(); + + buffer.push(previousInput); + + while (!stopping) { + PacketParser parser((pcap_pkthdr*)input.header.data(), (const u_char*)input.data.data()); + bytes += parser.data_len; + input = prevProvider->getProduct(); + + { + + PacketParser parser((pcap_pkthdr*)input.header.data(), (const u_char*)input.data.data()); + bytes += parser.data_len; + + // Takes packet Timestamp and compares it to the previous one to calculate difference. + inputTs = ((pcap_pkthdr*)input.header.data())->ts.tv_sec * 1000000; + inputTs += ((pcap_pkthdr*)input.header.data())->ts.tv_usec; + + previousInputTs = ((pcap_pkthdr*)previousInput.header.data())->ts.tv_sec * 1000000; + previousInputTs += ((pcap_pkthdr*)previousInput.header.data())->ts.tv_usec; + + + + if (startTime == -1) { + startTime = ((pcap_pkthdr*)input.header.data())->ts.tv_sec * 1000; // convert to ms + startTime += ((pcap_pkthdr*)input.header.data())->ts.tv_usec/1000; // convert to ms + } + else { + endTime = ((pcap_pkthdr*)input.header.data())->ts.tv_sec*1000; + endTime += ((pcap_pkthdr*)input.header.data())->ts.tv_usec/1000; // convert to ms + duration = endTime - startTime; + + } + + + + + + difference = inputTs - previousInputTs; + differencePerSec += difference; + distanceList.append(difference); + + //Calculate average diff per second and IAT standard deviation per millisecond + + if(statusTimer.elapsed() >= emitFrequency){ + // If no packages recieved, wait(?) + if(packetCounter != 0){ + diffratePerSec = differencePerSec/packetCounter; + } + + for( int a = 0; a < distanceList.length(); a = a + 1 ) { + + diffSum += ((distanceList[a] - diffratePerSec) * (distanceList[a] - diffratePerSec)); + } + + // sqrt of finalSum is equal to the IAT dev + + finalSum = diffSum/ distanceList.length(); + + + + quint64 streamId = StreamId::calcId(parser.ih->daddr, parser.dport); + StreamInfo &stream = streams[streamId]; + + + stream.iatDeviation = sqrt(finalSum); + + emit iatStatus(sqrt(finalSum), duration); + + emit workerStatus(WorkerStatus(WorkerStatus::STATUS_PERIODIC, streams), true); + + + + packetCounter = 0; + diffSum = 0; + finalSum = 0; + distanceList.clear(); + + + differencePerSec = 0; + statusTimer.restart(); + + } + + } + previousInput = input; + buffer.push(input); + packetNumber++; + packetCounter ++; + + if (input.type == PcapProduct::END || input.type == PcapProduct::STOP) { + break; + } + + } + emit finished(); +} + + + +void NetworkJitter::start() { + runnerThread.start(); +} + + +PcapProduct NetworkJitter::getProduct() { + return buffer.pop(); +} + + +void NetworkJitter::stop() { + stopping = true; + buffer.stop(); +} diff --git a/Middleware/networkjitter.h b/Middleware/networkjitter.h new file mode 100644 index 0000000..f74c58a --- /dev/null +++ b/Middleware/networkjitter.h @@ -0,0 +1,49 @@ +#ifndef NETWORKJITTER_H +#define NETWORKJITTER_H + + +#include "pcapmiddleware.h" +#include "concurrentqueue.h" +#include "Status/analyzerstatus.h" +#include "pidinfo.h" +#include "Analyzer/tsanalyzer.h" +#include "Analyzer/tserrors.h" +#include "Status/streaminfo.h" + +#include +#include + +class NetworkJitter : public PcapMiddleware +{ + Q_OBJECT +private: + ConcurrentQueue buffer; + qint64 duration; + qint64 packetNumber; + QHash streams; + bool signalType; + +public: + NetworkJitter(WorkerConfiguration config) : + PcapMiddleware(config), + buffer(MIDDLEWARE_BUFFER_SIZE) + { + init(); + } + void init(); + PcapProduct getProduct(); + +signals: + void status(AnalyzerStatus status); + void iatStatus(double iatDev, qint64 duration); + + +protected slots: + void run(); + +public slots: + void start(); + void stop(); +}; + +#endif // NETWORKJITTER_H diff --git a/Middleware/pcapmiddleware.h b/Middleware/pcapmiddleware.h index 343edd4..0ad88c7 100644 --- a/Middleware/pcapmiddleware.h +++ b/Middleware/pcapmiddleware.h @@ -20,17 +20,13 @@ class PcapMiddleware : public QObject, public PcapProductProvider PcapMiddleware(WorkerConfiguration config) : QObject(), config(config) {} + + quint16 emitFrequency = 100; + signals: void started(); void finished(); - void workerStatus(WorkerStatus status); - -protected slots: - virtual void run() = 0; - -public slots: - virtual void start() = 0; - virtual void stop() { stopping = true; } + void workerStatus(WorkerStatus status, bool signalType); }; #endif // PCAPMIDDLEWARE_H diff --git a/PacketConsumer/pcapfileconsumer.cpp b/PacketConsumer/pcapfileconsumer.cpp index 84f71ae..44b8510 100644 --- a/PacketConsumer/pcapfileconsumer.cpp +++ b/PacketConsumer/pcapfileconsumer.cpp @@ -62,6 +62,7 @@ void PcapFileConsumer::saveToFile() { PacketParser parser; packet = prevProvider->getProduct(); + const u_char *pkt_data; struct pcap_pkthdr *header; diff --git a/PacketConsumer/tsfileconsumer.cpp b/PacketConsumer/tsfileconsumer.cpp index e16e395..5271ba2 100644 --- a/PacketConsumer/tsfileconsumer.cpp +++ b/PacketConsumer/tsfileconsumer.cpp @@ -14,7 +14,6 @@ void TsFileConsumer::start(QThread *thread) { connect(this, &TsFileConsumer::finished, thread, &QThread::quit); //connect(this, &TsFileConsumer::finished, &QThread::deleteLater); //connect(thread, &QThread::finished, thread, &QThread::deleteLater); - thread->start(); } diff --git a/PacketProducer/pcapbufferedproducer.cpp b/PacketProducer/pcapbufferedproducer.cpp index 243835b..45318ef 100644 --- a/PacketProducer/pcapbufferedproducer.cpp +++ b/PacketProducer/pcapbufferedproducer.cpp @@ -291,6 +291,8 @@ int PcapBufferedProducer::bufferFromNetworkSetup() { statusLastTime = 0; elapsedTimer.start(); + qInfo() << "TIME JITTER " << QTime::currentTime(); + #ifndef Q_OS_WIN // Setup no data timer diff --git a/Recorder/networkpcapfilerecorder.cpp b/Recorder/networkpcapfilerecorder.cpp index 5473d40..d081e97 100644 --- a/Recorder/networkpcapfilerecorder.cpp +++ b/Recorder/networkpcapfilerecorder.cpp @@ -1,8 +1,11 @@ #include "networkpcapfilerecorder.h" +#include +#include + void NetworkPcapFileRecorder::start() { finalStatus.setAnalysisMode(config.getWorkerMode()); - producer.addNext(&analyzerMiddleware)->addNext(&consumer); + producer.addNext(&networkJitter)->addNext(&analyzerMiddleware)->addNext(&consumer); producer.init(&producerThread); connect(&consumer, &PcapFileConsumer::finished, &producer, &PcapBufferedProducer::stop); @@ -11,14 +14,27 @@ void NetworkPcapFileRecorder::start() { connect(&producerThread, &QThread::finished, this, &NetworkPcapFileRecorder::moduleFinished); connect(analyzerMiddleware.thread(), &QThread::finished, this, &NetworkPcapFileRecorder::moduleFinished); connect(&consumerThread, &QThread::finished, this, &NetworkPcapFileRecorder::moduleFinished); + connect(networkJitter.thread(), &QThread::finished, this, &NetworkPcapFileRecorder::moduleFinished); + //These take care of the bottom info panel connect(&producer, &PcapBufferedProducer::status, this, &NetworkPcapFileRecorder::gotProducerStatus); connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::status, this, &NetworkPcapFileRecorder::gotAnalyzerStatus); + connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::bitrateStatus, this, &NetworkPcapFileRecorder::gotBitrate); + connect(&networkJitter, &NetworkJitter::workerStatus, this, &NetworkPcapFileRecorder::gotIatDev); + + + // connect(&networkJitter, &NetworkJitter::status, this, &NetworkPcapFileRecorder::gotNetworkStatus); + + + //Worker-status is connected to the right side stream info panel connect(&consumer, &PcapFileConsumer::status, this, &NetworkPcapFileRecorder::gotConsumerStatus); - connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::workerStatus, this, &NetworkPcapFileRecorder::workerStatus); + connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::workerStatus, this, &NetworkPcapFileRecorder::joinStreamInfo); + // connect(&networkJitter, &NetworkJitter::workerStatus, this, &NetworkPcapFileRecorder::joinStreamInfo); + producerThread.start(); analyzerMiddleware.start(); + networkJitter.start(); consumer.start(&consumerThread); emit started(); @@ -37,6 +53,51 @@ void NetworkPcapFileRecorder::gotProducerStatus(Status pStatus) { } } + + +void NetworkPcapFileRecorder::joinStreamInfo(WorkerStatus status) { + qint8 counter = 0; + + for(auto iter = status.getStreams().begin(); iter != status.getStreams().end(); ++iter) { + qint64 streamID = iter.key(); + const StreamInfo &streamInfo = iter.value(); + + // if(!isDeviationSignal){ + if (previousAnalyzerStream.streams.count(streamID) > 0) { + previousAnalyzerStream.streams[streamID] = streamInfo; + } + // Make IAT work for any numer of streams + if(iatVector.size() <= counter){ + previousAnalyzerStream.streams[streamID].iatDeviation = 0; + + } else { + previousAnalyzerStream.streams[streamID].iatDeviation = (iatVector[counter].last() ); + } + + WorkerStatus completeSignal; + completeSignal.setStreams(previousAnalyzerStream.streams); + emit workerStatus(completeSignal); + counter++; + } +} + +void NetworkPcapFileRecorder::gotIatDev(WorkerStatus status){ + + this->iatVector.resize(status.streams.count()); + + for(int i = 0; i < status.streams.count(); i++) { + quint64 hashKey = status.streams.keys().at(i); + + iatVector[i].append(status.streams[hashKey].iatDeviation); + } +} + + + + + + + void NetworkPcapFileRecorder::gotAnalyzerStatus(AnalyzerStatus aStatus) { if (aStatus.getType() == Status::STATUS_ERROR) { finalStatus.setError(aStatus.getError()); @@ -50,6 +111,15 @@ void NetworkPcapFileRecorder::gotAnalyzerStatus(AnalyzerStatus aStatus) { } } +void NetworkPcapFileRecorder::gotBitrate(double bitrate, qint64 duration){ + + emit bitrateStatus(bitrate, duration); +} + + + + + void NetworkPcapFileRecorder::gotConsumerStatus(Status cStatus) { if (cStatus.getType() == Status::STATUS_ERROR) { finalStatus.setError(cStatus.getError()); diff --git a/Recorder/networkpcapfilerecorder.h b/Recorder/networkpcapfilerecorder.h index aca0007..a074357 100644 --- a/Recorder/networkpcapfilerecorder.h +++ b/Recorder/networkpcapfilerecorder.h @@ -5,21 +5,34 @@ #include "PacketProducer/pcapbufferedproducer.h" #include "PacketConsumer/pcapfileconsumer.h" #include "Middleware/analyzerpcapmiddleware.h" +#include "Middleware/networkjitter.h" class NetworkPcapFileRecorder : public Recorder { Q_OBJECT private: + QVector > iatVector; PcapBufferedProducer producer; PcapFileConsumer consumer; + NetworkJitter networkJitter; + WorkerStatus previousAnalyzerStream; + AnalyzerPcapMiddleware analyzerMiddleware; public: + + + NetworkPcapFileRecorder(WorkerConfiguration config, QObject *parent = 0) : Recorder(config, parent), producer(config), consumer(config), - analyzerMiddleware(config) {} + networkJitter(config), + analyzerMiddleware(config) { + + + } + ~NetworkPcapFileRecorder() { printf("DESTRUCT\n"); } void start(); void stop(); @@ -28,7 +41,11 @@ class NetworkPcapFileRecorder : public Recorder private slots: void gotProducerStatus(Status pStatus); void gotAnalyzerStatus(AnalyzerStatus aStatus); + void joinStreamInfo(WorkerStatus dStatus); void gotConsumerStatus(Status cStatus); + void gotBitrate(double bitrate, qint64 duration); + void gotIatDev(WorkerStatus dStatus); + void moduleFinished(); }; diff --git a/Recorder/recorder.h b/Recorder/recorder.h index 0ea60ed..d743aec 100644 --- a/Recorder/recorder.h +++ b/Recorder/recorder.h @@ -25,6 +25,8 @@ class Recorder : public QObject void started(); void finished(); void status(FinalStatus status); + void bitrateStatus(double bitrate, qint64 duration); + void iatStatus(double iatDev, qint64 duration); void workerStatus(WorkerStatus status); public slots: diff --git a/Recorder/tsnetworkfilerecorder.cpp b/Recorder/tsnetworkfilerecorder.cpp index bb7c3fa..9b6775a 100644 --- a/Recorder/tsnetworkfilerecorder.cpp +++ b/Recorder/tsnetworkfilerecorder.cpp @@ -2,7 +2,8 @@ void TsNetworkFileRecorder::start() { finalStatus.setAnalysisMode(config.getWorkerMode()); - producer.addToEnd(&analyzerMiddleware)->addToEnd(&consumer); + producer.addNext(&networkJitter)->addNext(&analyzerMiddleware)->addNext(&consumer); + producer.init(&producerThread); connect(&consumer, &TsFileConsumer::finished, &producer, &PcapBufferedProducer::stop); @@ -10,15 +11,25 @@ void TsNetworkFileRecorder::start() { // Performs waiting for all modules to finish before emitting finished connect(&producerThread, &QThread::finished, this, &TsNetworkFileRecorder::moduleFinished); connect(analyzerMiddleware.thread(), &QThread::finished, this, &TsNetworkFileRecorder::moduleFinished); + connect(networkJitter.thread(), &QThread::finished, this, &TsNetworkFileRecorder::moduleFinished); + connect(&consumerThread, &QThread::finished, this, &TsNetworkFileRecorder::moduleFinished); connect(&producer, &PcapBufferedProducer::status, this, &TsNetworkFileRecorder::gotProducerStatus); connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::status, this, &TsNetworkFileRecorder::gotAnalyzerStatus); connect(&consumer, &TsFileConsumer::status, this, &TsNetworkFileRecorder::gotConsumerStatus); - connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::workerStatus, this, &TsNetworkFileRecorder::workerStatus); + connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::workerStatus, this, &TsNetworkFileRecorder::joinStreamInfo); + connect(&analyzerMiddleware, &AnalyzerPcapMiddleware::bitrateStatus, this, &TsNetworkFileRecorder::gotBitrate); + connect(&networkJitter, &NetworkJitter::workerStatus, this, &TsNetworkFileRecorder::gotIatDev); + + + + + producerThread.start(); analyzerMiddleware.start(); + networkJitter.start(); consumer.start(&consumerThread); emit started(); @@ -50,6 +61,31 @@ void TsNetworkFileRecorder::gotAnalyzerStatus(AnalyzerStatus aStatus) { } } +void TsNetworkFileRecorder::joinStreamInfo(WorkerStatus status, bool isDeviationSignal) { + qint8 counter = 0; + + for(auto iter = status.getStreams().begin(); iter != status.getStreams().end(); ++iter) { + qint64 streamID = iter.key(); + const StreamInfo &streamInfo= iter.value(); + + // if(!isDeviationSignal){ + previousAnalyzerStream.streams[streamID] = streamInfo; + + // Make IAT work for any numer of streams + if(iatVector.size() <= counter){ + previousAnalyzerStream.streams[streamID].iatDeviation = 0; + + } else { + previousAnalyzerStream.streams[streamID].iatDeviation = (iatVector[counter].last() ); + } + WorkerStatus completeSignal; + + completeSignal.setStreams(previousAnalyzerStream.streams); + emit workerStatus(completeSignal); + counter++; + } +} + void TsNetworkFileRecorder::gotConsumerStatus(Status cStatus) { if (cStatus.getType() == Status::STATUS_ERROR) { finalStatus.setError(cStatus.getError()); @@ -59,6 +95,27 @@ void TsNetworkFileRecorder::gotConsumerStatus(Status cStatus) { } } +void TsNetworkFileRecorder::gotBitrate(double bitrate, qint64 duration){ + + emit bitrateStatus(bitrate, duration); +} + + + +void TsNetworkFileRecorder::gotIatDev(WorkerStatus status, bool isDeviationSignal){ + + this->iatVector.resize(status.streams.count()); + + for(int i = 0; i < status.streams.count(); i++) { + quint64 hashKey = status.streams.keys().at(i); + + iatVector[i].append(status.streams[hashKey].iatDeviation); + } +} + + + + // Only emit finished when all modules are finished void TsNetworkFileRecorder::moduleFinished() { if (!producerThread.isFinished()) diff --git a/Recorder/tsnetworkfilerecorder.h b/Recorder/tsnetworkfilerecorder.h index f1245f1..8f0f97e 100644 --- a/Recorder/tsnetworkfilerecorder.h +++ b/Recorder/tsnetworkfilerecorder.h @@ -5,22 +5,29 @@ #include "PacketProducer/pcapbufferedproducer.h" #include "PacketConsumer/tsfileconsumer.h" #include "Middleware/analyzerpcapmiddleware.h" +#include "Middleware/networkjitter.h" class TsNetworkFileRecorder : public Recorder { private: + QVector > iatVector; PcapBufferedProducer producer; TsFileConsumer consumer; AnalyzerPcapMiddleware analyzerMiddleware; + NetworkJitter networkJitter; + WorkerStatus previousAnalyzerStream; + public: TsNetworkFileRecorder(WorkerConfiguration config, QObject *parent = 0) : Recorder(config, parent), producer(config), consumer(config), - analyzerMiddleware(config) {} + analyzerMiddleware(config), + networkJitter(config){} void stopAndWait(); + public slots: void start(); void stop(); @@ -30,6 +37,11 @@ private slots: void gotAnalyzerStatus(AnalyzerStatus aStatus); void gotConsumerStatus(Status cStatus); void moduleFinished(); + void joinStreamInfo(WorkerStatus dStatus, bool signalType); + void gotBitrate(double bitrate, qint64 duration); + void gotIatDev(WorkerStatus status, bool isDeviationSignal); + + }; #endif // TSNETWORKFILERECORDER_H diff --git a/Status/analyzerstatus.h b/Status/analyzerstatus.h index 15c9c63..6780035 100644 --- a/Status/analyzerstatus.h +++ b/Status/analyzerstatus.h @@ -67,11 +67,13 @@ class AnalyzerStatus : public Status pidMap(other.pidMap), tsErrors(other.tsErrors), proto(other.proto), - tsPerIp(other.tsPerIp) {} + tsPerIp(other.tsPerIp) + {} void setTsPerIp(quint8 tsPerIp) { this->tsPerIp = tsPerIp; } void setProtocol(Protocol proto) { this->proto = proto; } + qint64 getDuration() const { return duration; } QMap getPidMap() { diff --git a/Status/status.h b/Status/status.h index e6da1d0..46c0eec 100644 --- a/Status/status.h +++ b/Status/status.h @@ -47,8 +47,16 @@ class Status : public QObject { qint64 getElapsed() { return elapsed; } qint64 getBitrate() { return bitrate; } QString getError() { return error; } + void setType(const StatusType &value); + void setBytes(const qint64 &value); + void setElapsed(const qint64 &value); + void setBitrate(const qint64 &value); }; + + + + Q_DECLARE_METATYPE(Status) #endif // STATUS_H diff --git a/Status/streaminfo.h b/Status/streaminfo.h index fae0d31..f1d613b 100644 --- a/Status/streaminfo.h +++ b/Status/streaminfo.h @@ -26,9 +26,14 @@ class StreamInfo { quint64 bytes; quint64 currentTime; quint64 bitrate; + double avgBitrate; + double currentBitrate; NetworkProtocol protocol; BitrateMode bitrateMode; quint8 tsPerIp; + quint32 iatDeviation; + quint64 lastSecondBytes; + quint64 lastDuration; TsErrors tsErrors; QMap pidMap; QString protocolName() const { @@ -62,18 +67,23 @@ class StreamInfo { protocol(NetworkProtocol::UNKNOWN), bitrateMode(BitrateMode::UNKNOWN), tsPerIp(0), + iatDeviation(0), + lastSecondBytes(0), + lastDuration(0), tsErrors() {} StreamInfo(StreamId id, quint64 bytes, quint64 bitrate, NetworkProtocol protocol, quint8 tsPerIp, + quint8 networkJitters, TsErrors tsErrors) : id(id), bytes(bytes), bitrate(bitrate), protocol(protocol), tsPerIp(tsPerIp), + iatDeviation(networkJitters), tsErrors(tsErrors) {} }; diff --git a/Status/workerstatus.h b/Status/workerstatus.h index 215f555..a5081e5 100644 --- a/Status/workerstatus.h +++ b/Status/workerstatus.h @@ -20,17 +20,25 @@ class WorkerStatus : public QObject { STATUS_ANALYZED_ENTIRE }; -protected: StatusType type = STATUS_ERROR; - QString error = ""; QHash streams; +protected: + QString error = ""; + QTreeWidgetItem* makeItem(QString text, bool parent = false) { QTreeWidgetItem *item = new QTreeWidgetItem((QTreeWidget*)0, QStringList(text)); item->setDisabled(!parent); return item; } + QTreeWidgetItem* makeItems(QString text, QVariant value, bool parent = false) { + QTreeWidgetItem *item = new QTreeWidgetItem((QTreeWidget*)0, QStringList(text)); + item->setData(0, Qt::UserRole, value); + item->setDisabled(!parent); + return item; + } + void makeTsError(QTreeWidgetItem *parent, quint64 count, TsErrors::ErrorType error) { QTreeWidgetItem *item = new QTreeWidgetItem( (QTreeWidget*)0, @@ -41,7 +49,12 @@ class WorkerStatus : public QObject { item->setDisabled(true); } + + + public: + double value; + QTreeWidget *treeWidget; WorkerStatus() : QObject() {} WorkerStatus(StatusType type) : QObject(), type(type) {} WorkerStatus(QString error) : QObject(), type(STATUS_ERROR), error(error) {} @@ -57,23 +70,66 @@ class WorkerStatus : public QObject { StatusType getType() { return type; } QString getError() { return error; } - QHash getStreams() { return streams; } + const QHash& getStreams() const { return streams; } + + + + + void setStreams(const QHash &value) + { + streams = value; + } + + // New method that is meant to update the values in the tree rather than recreate the tree every iteration. + void updateTree(QTreeWidget *tree){ + + quint8 y = 0; + double value = 0; + + for (int i = 0; i < streams.count(); i++){ + quint64 key = streams.keys().at(i); + const StreamInfo &info = streams.value(key); + + tree->topLevelItem(i)->child(y++)->setText(0, QString(tr("size %1 MB")).arg(QString::number(info.bytes/1000000.0, 'f', 2))); + tree->topLevelItem(i)->child(y++)->setText(0, QString(tr("duration %1")).arg(QDateTime::fromTime_t(info.currentTime/1000).toUTC().toString("HH:mm:ss"))); + value = (info.bytes*8*1000.0/info.currentTime)/1000000.0; + tree->topLevelItem(i)->child(y++)->setText(0,QString((tr("avg bitrate %1 Mbit/s")).arg(QString::number(value, 'f', 2)))); + tree->topLevelItem(i)->child(y++)->setText(0,QString(tr("protocol %1")).arg(info.protocolName())); + tree->topLevelItem(i)->child(y++)->setText(0, (QString(tr("bitrate mode %1")).arg(info.bitrateModeName()))); + tree->topLevelItem(i)->child(y++)->setText(0, QString(tr("%1 TS/IP")).arg(info.tsPerIp)); + tree->topLevelItem(i)->child(y++)->setText(0, QString(tr("%1 µs IAT deviation ")).arg(info.iatDeviation)); + tree->topLevelItem(i)->child(y++)->setText(0, QString(tr("%1 PIDs")).arg(info.pidMap.size())); + y= 0; + } + } + + + + + + // Keeping this method as the initial tree creator void insertIntoTree(QTreeWidget *tree) { tree->clear(); tree->setSelectionMode(QAbstractItemView::SingleSelection); + qInfo() << "ADDDING " << streams.count() << " NUMBER OF STREAMS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; for (int i = 0; i < streams.count(); i++) { quint64 key = streams.keys().at(i); const StreamInfo &info = streams.value(key); QTreeWidgetItem* parent = makeItem(StreamId::calcName(key), true); tree->addTopLevelItem(parent); + parent->addChild(makeItem(QString(tr("size %1 MB")).arg(QString::number(info.bytes/1000000.0, 'f', 2)))); parent->addChild(makeItem(QString(tr("duration %1")).arg(QDateTime::fromTime_t(info.currentTime/1000).toUTC().toString("HH:mm:ss")))); - parent->addChild(makeItem(QString(tr("avg bitrate %1 Mbit/s")).arg(QString::number((info.bytes*8*1000.0/info.currentTime)/1000000.0, 'f', 2)))); + auto value = (info.bytes*8*1000.0/info.currentTime)/1000000.0; + parent->addChild(makeItems(QString(tr("avg bitrate %1 Mbit/s")).arg(QString::number(value, 'f', 2)), value)); //Allows avg bitrate to be accessed in other places such as recordwidget parent->addChild(makeItem(QString(tr("protocol %1")).arg(info.protocolName()))); parent->addChild(makeItem(QString(tr("bitrate mode %1")).arg(info.bitrateModeName()))); parent->addChild(makeItem(QString(tr("%1 TS/IP")).arg(info.tsPerIp))); + // if(info.networkJitters != 0){ + parent->addChild(makeItem(QString(tr("%1 µs IAT deviation ")).arg(info.iatDeviation))); + // } parent->addChild(makeItem(QString(tr("%1 PIDs")).arg(info.pidMap.size()))); quint64 tsErrCount = info.tsErrors.totalErrors(); @@ -87,14 +143,20 @@ class WorkerStatus : public QObject { } parent->setExpanded(true); - - tree->clearSelection(); - parent->setSelected(true); + // this->treeWidget = tree; + if (i==0){ + tree->clearSelection(); + parent->setSelected(true); + } } } + }; + + + Q_DECLARE_METATYPE(WorkerStatus) diff --git a/graphchart.cpp b/graphchart.cpp new file mode 100644 index 0000000..6f2227b --- /dev/null +++ b/graphchart.cpp @@ -0,0 +1,54 @@ +#include "graphchart.h" +#include +#include +#include +//#include +#include + +GraphChart::GraphChart(QGraphicsItem *parent, Qt::WindowFlags wFlags): + QChart(QChart::ChartTypeCartesian, parent, wFlags), + m_series(0), + m_axisX(new QValueAxis()), + m_axisY(new QValueAxis()), + m_step(0), + m_x(5), + m_y(1) +{ + QObject::connect(&m_timer, &QTimer::timeout, this, &GraphChart::handleTimeout); + m_timer.setInterval(1000); + + m_series = new QSplineSeries(this); + QPen green(Qt::red); + green.setWidth(3); + m_series->setPen(green); + m_series->append(m_x, m_y); + + addSeries(m_series); + + addAxis(m_axisX,Qt::AlignBottom); + addAxis(m_axisY,Qt::AlignLeft); + m_series->attachAxis(m_axisX); + m_series->attachAxis(m_axisY); + m_axisX->setTickCount(5); + m_axisX->setRange(0, 10); + m_axisY->setRange(-5, 10); + + m_timer.start(); +} + +GraphChart::~GraphChart() +{ + +} + +void GraphChart::handleTimeout() +{ + qreal x = plotArea().width() / m_axisX->tickCount(); + qreal y = (m_axisX->max() - m_axisX->min()) / m_axisX->tickCount(); + m_x += y; + m_y = 2.5; + m_series->append(m_x, m_y); + scroll(x, 0); + if (m_x == 100) + m_timer.stop(); +} diff --git a/main.cpp b/main.cpp index 03fe616..43111af 100644 --- a/main.cpp +++ b/main.cpp @@ -73,6 +73,9 @@ int main(int argc, char *argv[]) #endif // QT_DEBUG MainWindow w; + // w.setCentralWidget(ui); + w.grabGesture(Qt::PanGesture); + w.grabGesture(Qt::PinchGesture); w.show(); qInfo() << QThread::currentThread(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 1739192..4134428 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,6 +2,7 @@ #include "ui_mainwindow.h" #include "interface.h" #include "igmp.h" +#include #include "Player/pcapfilenetworkplayer.h" #include "Recorder/networkpcapfilerecorder.h" #include "Recorder/tsnetworkfilerecorder.h" @@ -9,7 +10,6 @@ #include #include - QList MainWindow::interfaces = QList(); MainWindow::MainWindow(QWidget *parent) : @@ -19,12 +19,16 @@ MainWindow::MainWindow(QWidget *parent) : Interface::getInterfaces(&interfaces); ui->setupUi(this); + // Setup strings setWindowTitle(QCoreApplication::applicationName()); ui->actionAbout->setText(tr("About %1...").arg(QCoreApplication::applicationName())); IGMP::init(); loadSettings(); + this->setMouseTracking(true); + // this->setRubberBand(QChartView::RectangleRubberBand); + } MainWindow::~MainWindow() @@ -37,6 +41,9 @@ void MainWindow::loadSettings() { ui->playbackWidget->loadSettings(); ui->recordWidget->loadSettings(); ui->convertWidget->loadSettings(); + //ui->recordWidget->graph.setFocus(); + + } @@ -69,11 +76,39 @@ void MainWindow::closeEvent(QCloseEvent *event) event->accept(); } +void MainWindow::keyPressEvent(QKeyEvent *event) +{ + + ui->recordWidget->graph.keyPressEvent(event); + QMainWindow::keyPressEvent(event); + +} + + +void MainWindow::mousePressEvent(QMouseEvent *event) +{ + // ui->recordWidget->graph.mousePressEvent(event); + + + // QMainWindow::mousePressEvent(event); +} + +void MainWindow::mouseMoveEvent(QMouseEvent *event) +{ + ui->recordWidget->graph.mouseMoveEvent(event); + + QMainWindow::mouseMoveEvent(event); +} + + void MainWindow::on_actionExit_triggered() { close(); + QApplication::quit(); } + + void MainWindow::on_actionAbout_triggered() { QMessageBox::about(this, tr("About %1").arg(QCoreApplication::applicationName()), diff --git a/mainwindow.h b/mainwindow.h index 3249990..8626886 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,31 +3,42 @@ #include #include +#include #include "Status/finalstatus.h" #include "Configuration/workerconfiguration.h" + namespace Ui { + + class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT - public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); static QList interfaces; +protected : + void keyPressEvent(QKeyEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + + public slots: void closeEvent(QCloseEvent *event); void loadSettings(); void saveSettings(); + private slots: void on_actionExit_triggered(); + void on_actionAbout_triggered(); private: diff --git a/mainwindow.ui b/mainwindow.ui index 0188807..959a9b5 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -18,6 +18,12 @@ + + true + + + Qt::NoFocus + true @@ -73,7 +79,7 @@ 0 0 800 - 19 + 22 @@ -112,6 +118,16 @@ &About... + + + + :/icons/pngicon + + + + How to navigate graph + + diff --git a/playbackwidget.cpp b/playbackwidget.cpp index c372eef..bbe6324 100644 --- a/playbackwidget.cpp +++ b/playbackwidget.cpp @@ -31,9 +31,11 @@ PlaybackWidget::PlaybackWidget(QWidget *parent) : connect(ui->playbackHost, &QLineEdit::textChanged, this, &PlaybackWidget::playbackInputChanged); connect(ui->playbackPort, &QLineEdit::textChanged, this, &PlaybackWidget::playbackInputChanged); + for (int i = 0; i < MainWindow::interfaces.length(); i++) { ui->playbackInterfaceSelect->addItem(MainWindow::interfaces.at(i).getName()); ui->playbackInterfaceSelect->setItemData(i, MainWindow::interfaces.at(i).getName(), Qt::ToolTipRole); + } @@ -104,7 +106,8 @@ void PlaybackWidget::playbackStarted() { } -void PlaybackWidget::playbackStatusChanged(FinalStatus status) { +void PlaybackWidget::playbackStatusChanged(FinalStatus status) { //Kolla in det här, troligen anledningen till att det ej markeras som finished längre. + ui->playbackStatus->setText(status.toUiString()); } diff --git a/playbackwidget.h b/playbackwidget.h index ee5c546..36913e9 100644 --- a/playbackwidget.h +++ b/playbackwidget.h @@ -4,6 +4,7 @@ #include "Configuration/workerconfiguration.h" #include "Player/pcapfilenetworkplayer.h" #include +// #include namespace Ui { class PlaybackWidget; diff --git a/playbackwidget.ui b/playbackwidget.ui index c07ad4c..f50c30c 100644 --- a/playbackwidget.ui +++ b/playbackwidget.ui @@ -7,7 +7,7 @@ 0 0 730 - 389 + 396 @@ -176,7 +176,7 @@ - + @@ -257,7 +257,4 @@ QTreeView::branch:open:has-children:has-siblings { - - - diff --git a/recordtxtprinter.cpp b/recordtxtprinter.cpp new file mode 100644 index 0000000..8986c74 --- /dev/null +++ b/recordtxtprinter.cpp @@ -0,0 +1,50 @@ +#include "recordtxtprinter.h" +#include +#include + +RecordTxtPrinter::RecordTxtPrinter() +{ + +} + + + +void RecordTxtPrinter::printToFile(QFile *file, QString text, QString currentFileName, QString streamIpAdress, quint8 currentIterationIndex){ + + + if(!containedIndexes.contains(currentIterationIndex)){ + + QString filename = currentFileName; + + streamIpAdress.replace('.', '_'); + filename.remove(".pcap"); + filename.remove(".ts"); + filename.append("_" +streamIpAdress); + filename.append(".csv"); + + file->setFileName(filename); + + if ( file->open(QIODevice::Truncate | QIODevice::Text | QIODevice::WriteOnly) ) + { + QTextStream stream( file ); + + QString s1 = "Timestamp,"; + QString s2 = "Bitrate," ; + QString s3 = "IAT deviation"; + + QString string = ( s1+ s2 + s3 ); + stream << string << endl; + + containedIndexes.append(currentIterationIndex); + } + + + + } + +// if ( file.open(QIODevice::Append) ) + { + QTextStream stream( file ); + stream << text << endl; + } +} diff --git a/recordtxtprinter.h b/recordtxtprinter.h new file mode 100644 index 0000000..eff63ab --- /dev/null +++ b/recordtxtprinter.h @@ -0,0 +1,19 @@ +#ifndef RECORDTXTPRINTER_H +#define RECORDTXTPRINTER_H +#include + +#include +#include + + +class RecordTxtPrinter +{ +public: + RecordTxtPrinter(); + void printToFile(QFile *file, QString text, QString currentFilename, QString streamIpAdress,quint8 currentIterationIndex); + QList containedIndexes; + +protected: +}; + +#endif // RECORDTXTPRINTER_H diff --git a/recordwidget.cpp b/recordwidget.cpp index 6023ece..a4c1c77 100644 --- a/recordwidget.cpp +++ b/recordwidget.cpp @@ -3,11 +3,8 @@ #include "mainwindow.h" #include "validator.h" #include "pcapfilter.h" - -#include -#include -#include -#include +#include "recordwidgetgraph.cpp" +#include #include #include @@ -16,23 +13,32 @@ TsNetworkFileRecorder* RecordWidget::tsNetworkFileRecorder; RecordWidget::RecordWidget(QWidget *parent) : QWidget(parent), + graph(this), ui(new Ui::RecordWidget), - started(false) + started(false), + isBitrateSignal(true), + didRun(false) { ui->setupUi(this); + this->setupGraph(); + this->setMouseTracking(true); + treeWidgetCounter = 0; + selectedStreamIndex = 0; + + + // Advanced PCAP filter ui->recordPcapFilterContainer->hide(); connect(ui->recordExpandPCAPFilterButton, SIGNAL(toggled(bool)), this, SLOT(on_recordExpandPCAPFilterButton_toggled(bool))); - // File format - // connect(ui->recordFileFormatPCAP, SIGNAL(toggled(bool)), this, SLOT(on_recordFileFormatPCAP_toggled(bool))); - // connect(ui-> recordFileFormatTS, SIGNAL(toggled(bool)), this, SLOT(on_recordFileFormatTS_toggled(bool))); - connect(ui->recordHost, &QLineEdit::textChanged, this, &RecordWidget::recordFilterShouldUpdate); connect(ui->recordPort, &QLineEdit::textChanged, this, &RecordWidget::recordFilterShouldUpdate); connect(ui->recordRtpFecCheckBox, &QCheckBox::stateChanged, this, &RecordWidget::recordFilterShouldUpdate); connect(ui->recordUnicastCheckBox, &QCheckBox::stateChanged, this, &RecordWidget::recordFilterShouldUpdate); + connect(ui->treeWidget, &QTreeWidget::itemClicked, this, &RecordWidget::changeStream); + + connect(ui->graphDataBox, &QComboBox::currentTextChanged, this, &RecordWidget::changeStream); for (int i = 0; i < MainWindow::interfaces.length(); i++) { ui->recordInterfaceSelect->addItem(MainWindow::interfaces.at(i).getName()); @@ -95,21 +101,88 @@ void RecordWidget::recordingStarted() { ui->recordExpandPCAPFilterButton->setEnabled(false); ui->recordPcapFilterContainer->setEnabled(false); ui->recordInterfaceSelect->setEnabled(false); + ui->graphDataBox->setEnabled(false); + this->setupGraph(); + graph.setCurrentFileName(currentFilename); + didRun = true; - - + if(ui->graphDataBox->currentText()== "Bitrate"){ + this->graph.setYAxisTitle("Bitrate mbps"); + } else{ + this->graph.setYAxisTitle("Std IAT dev µs"); + } } void RecordWidget::recordStatusChanged(FinalStatus status) { ui->recordStatus->setText(status.toUiString()); } +void RecordWidget::recordWorkerGraphInfo(WorkerStatus status){ + quint64 hashKey = status.streams.keys().at(selectedStreamIndex); + if(isBitrateSignal){ + graph.setBitrate(status.streams[hashKey].currentBitrate, status.streams[hashKey].currentTime, isBitrateSignal); + } else { + + graph.setBitrate(status.streams[hashKey].iatDeviation, status.streams[hashKey].currentTime, isBitrateSignal); + + } + if(ui->treeWidget->topLevelItemCount() > 0){ + graph.recordMultipleStreams(status); + + if(selectedStreamIndex != graph.selectedStreamIndex){ + graph.changeStream(selectedStreamIndex, isBitrateSignal); + } + } +} + void RecordWidget::recordWorkerStatusChanged(WorkerStatus status) { - status.insertIntoTree(ui->treeWidget); + + if(ui->treeWidget->topLevelItemCount() != status.streams.count() && started){ // started prevents crash when pressing stop button + + status.insertIntoTree(ui->treeWidget); + + } else { + updateStreamIndex(); + quint64 key = status.streams.keys().at(selectedStreamIndex); + if(isBitrateSignal){ + this->graph.setAvgBitrate(status.streams[key].avgBitrate); + } + recordWorkerGraphInfo(status); + status.updateTree(ui->treeWidget); + } +} + +void RecordWidget::changeStream(){ + + if(didRun){ + updateStreamIndex(); + + if (ui->graphDataBox->currentText().contains("IAT")){ + isBitrateSignal = false; + } else { + isBitrateSignal = true; + } + + graph.changeStream(selectedStreamIndex, isBitrateSignal); + ui->graphView->setChart(graph.chart()); + + } +} + +void RecordWidget::updateStreamIndex(){ + QList itemList; + + itemList = this->ui->treeWidget->selectedItems(); + + foreach(QTreeWidgetItem *item, itemList) // Maybe pass data to graph here if there are multiple streams? + { + selectedStreamIndex = this->ui->treeWidget->indexOfTopLevelItem(item); + } } void RecordWidget::recordingFinished() { + started = false; ui->recordStartStopBtn->setText(tr("Start recording")); ui->recordStartStopBtn->setEnabled(true); ui->recordOpenFileDialog->setEnabled(true); @@ -120,12 +193,12 @@ void RecordWidget::recordingFinished() { ui->recordExpandPCAPFilterButton->setEnabled(true); ui->recordPcapFilterContainer->setEnabled(true); ui->recordInterfaceSelect->setEnabled(true); - + ui->graphDataBox->setEnabled(true); + this->treeWidgetCounter = 0; networkPcapFileRecorder = NULL; tsNetworkFileRecorder = NULL; - started = false; } @@ -144,7 +217,7 @@ bool RecordWidget::validatePortInputs() { bool RecordWidget::validateAdressInputs() { bool valid = true; - // Performs validation on Address + // Performs validation on adress valid = Validator::validateIp(ui->recordHost) && valid; return valid; @@ -188,7 +261,6 @@ void RecordWidget::recordFilterShouldUpdate() { } - bool RecordWidget::startPcapRecord(WorkerConfiguration::WorkerMode mode) { if (networkPcapFileRecorder != NULL) { qWarning("Atempt to start PcapRecord while pointer not released"); @@ -205,7 +277,14 @@ bool RecordWidget::startPcapRecord(WorkerConfiguration::WorkerMode mode) { connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::started, this, &RecordWidget::recordingStarted); connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::finished, this, &RecordWidget::recordingFinished); connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::status, this, &RecordWidget::recordStatusChanged); - connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + + if(ui->graphDataBox->currentText()!= "Bitrate"){ + isBitrateSignal = false; + connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + } else { + isBitrateSignal = true; + connect(networkPcapFileRecorder, &NetworkPcapFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + } ui->recordStartStopBtn->setEnabled(false); @@ -231,7 +310,15 @@ bool RecordWidget::startTsRecord(WorkerConfiguration::WorkerMode mode) { connect(tsNetworkFileRecorder, &TsNetworkFileRecorder::started, this, &RecordWidget::recordingStarted); connect(tsNetworkFileRecorder, &TsNetworkFileRecorder::finished, this, &RecordWidget::recordingFinished); connect(tsNetworkFileRecorder, &TsNetworkFileRecorder::status, this, &RecordWidget::recordStatusChanged); - connect(tsNetworkFileRecorder, &TsNetworkFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + + if(ui->graphDataBox->currentText()!= "Bitrate"){ + isBitrateSignal = false; + connect(tsNetworkFileRecorder, &NetworkPcapFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + } else { + isBitrateSignal = true; + connect(tsNetworkFileRecorder, &NetworkPcapFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); + } + // connect(tsNetworkFileRecorder, &TsNetworkFileRecorder::workerStatus, this, &RecordWidget::recordWorkerStatusChanged); ui->recordStartStopBtn->setEnabled(false); tsNetworkFileRecorder->start(); @@ -250,6 +337,12 @@ void RecordWidget::on_recordExpandPCAPFilterButton_toggled(bool checked) } } +void RecordWidget::setupGraph(){ + graph.setupGraph(); + ui->graphView->setChart(graph.chart()); + ui->graphView->setRenderHint(QPainter::Antialiasing); +} + void RecordWidget::on_recordFileFormatPCAP_toggled(bool checked) { QString newExtension = checked ? "pcap" : "ts"; @@ -344,6 +437,7 @@ void RecordWidget::on_recordStartStopBtn_clicked() qInfo("atempting to stop pcapRecorder"); ui->recordStartStopBtn->setEnabled(false); networkPcapFileRecorder->stop(); + recordingFinished(); } if (tsNetworkFileRecorder != NULL) { qInfo("atempting to stop tsRecorder"); @@ -353,6 +447,11 @@ void RecordWidget::on_recordStartStopBtn_clicked() } } + + + + + void RecordWidget::on_treeWidget_activated(const QModelIndex &index) { diff --git a/recordwidget.h b/recordwidget.h index 42e8b09..b24f04b 100644 --- a/recordwidget.h +++ b/recordwidget.h @@ -5,6 +5,11 @@ #include "Recorder/networkpcapfilerecorder.h" #include "Recorder/tsnetworkfilerecorder.h" #include +#include "Status/streaminfo.h" +#include "recordwidgetgraph.h" +#include "recordtxtprinter.h" + + namespace Ui { class RecordWidget; @@ -17,6 +22,8 @@ class RecordWidget : public QWidget public: explicit RecordWidget(QWidget *parent = 0); ~RecordWidget(); + RecordWidgetGraph graph; + void loadSettings(); void saveSettings(); @@ -24,17 +31,35 @@ class RecordWidget : public QWidget static NetworkPcapFileRecorder *networkPcapFileRecorder; static TsNetworkFileRecorder *tsNetworkFileRecorder; + private: + + void updateStreamIndex(); + Ui::RecordWidget *ui; bool started; + bool isBitrateSignal; + bool didRun; + QString currentTreeStream; QString currentDirectory; QString currentFilename; + quint8 treeWidgetCounter; + RecordTxtPrinter printer; + QTreeWidgetItem *currentStreamAdress; + quint8 selectedStreamIndex; + + // QString getTreeStream(QTreeWidget widget); + // void getTreeData(QString string, QTreeWidget treeWidget); + private slots: void recordingStarted(); void recordingFinished(); bool validateAdressInputs(); bool validatePortInputs(); + void changeStream(); + + void setupGraph(); @@ -55,6 +80,7 @@ private slots: public slots: void recordStatusChanged(FinalStatus status); void recordWorkerStatusChanged(WorkerStatus status); + void recordWorkerGraphInfo (WorkerStatus status); }; diff --git a/recordwidget.ui b/recordwidget.ui index c18b5cf..5124579 100644 --- a/recordwidget.ui +++ b/recordwidget.ui @@ -7,7 +7,7 @@ 0 0 644 - 435 + 556 @@ -22,17 +22,33 @@ 0 + + Qt::StrongFocus + Record - + Settings - - + + + + + Qt::Vertical + + + + 20 + 40 + + + + + 0 @@ -49,7 +65,65 @@ - + + + + 0 + + + + + + + + + + Qt::NoFocus + + + Start recording + + + + + + + 0 + + + + + Save to file (.ts or .pcap) + + + + + + + + + Qt::NoFocus + + + true + + + + + + + Qt::ClickFocus + + + Browse... + + + + + + + + @@ -64,7 +138,11 @@ - + + + Qt::ClickFocus + + @@ -81,12 +159,19 @@ - + + + Qt::ClickFocus + + + + Qt::ClickFocus + Unicast @@ -94,6 +179,9 @@ + + Qt::ClickFocus + RTP-FEC @@ -101,26 +189,7 @@ - - - - border: none - - - Advanced - - - true - - - Qt::ToolButtonTextBesideIcon - - - Qt::RightArrow - - - - + @@ -134,70 +203,90 @@ - + + + + 3 + 0 + + + + + 16777215 + 16777215 + + + + Qt::ClickFocus + + - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - 0 - + + - + + + + 120 + 50 + + + + border: none + + + Advanced + + + true + + + Qt::ToolButtonTextBesideIcon + + + Qt::RightArrow + + - - - - - - 0 - - - + + - Save to file (.ts or .pcap) + Graph data - + - - - true - - + + Bitrate + - - - Browse... - - + + IAT deviation + - + - - - - Start recording + + + + + 0 + 200 + + + + true + + + Qt::NoFocus @@ -208,12 +297,21 @@ + + + 340 + 16777215 + + Stream info + + Qt::ClickFocus + QTreeView::branch:has-siblings:!adjoins-item { border-image: url(:/img/res/stylesheet-vline.png) 0; @@ -272,6 +370,13 @@ QTreeView::branch:open:has-children:has-siblings { + + + QChartView + QGraphicsView +
QtCharts
+
+
diff --git a/recordwidgetgraph.cpp b/recordwidgetgraph.cpp new file mode 100644 index 0000000..ea36e97 --- /dev/null +++ b/recordwidgetgraph.cpp @@ -0,0 +1,349 @@ +#include "recordwidgetgraph.h" + +#include +#include +#include +#include + + + +RecordWidgetGraph::RecordWidgetGraph( QWidget *parent): + QChartView(parent), + m_isTouching(false) +{ + setDragMode(QGraphicsView::NoDrag); + this->setMouseTracking(true); + + this->setRubberBand(QChartView::RectangleRubberBand); + dataRefreshCounter = 0; + printer = new RecordTxtPrinter(); + bitrateSelected = true; + +} + +RecordWidgetGraph::~RecordWidgetGraph() +{ + for (auto ls : this->streamList) { + delete ls; + } + + delete printer; +} + + +QChart* RecordWidgetGraph::setupGraph(){ + + fileList.clear(); + streamList.clear(); + avgStreamList.clear(); + iatDevList.clear(); + maxBitrateList.clear(); + minBitrateList.clear(); + minIatList.clear(); + maxIatList.clear(); + + + lineSeries = new QLineSeries(); + avgSeries = new QLineSeries(); + + + + avgSeries->setName("Avg bitrate"); + lineSeries->setName("Bitrate"); + + // Create chart and add axis + QChart * chart = new QChart(); + + + + + chart->addSeries(lineSeries); + chart->addSeries(avgSeries); + chart->createDefaultAxes(); + chart->axisY()->setTitleText("Bitrate Mbps"); + + // Change the line color and weight + QPen pen(QRgb(0x000000)); + pen.setWidth(1); + lineSeries->setPen(pen); + + this->setChart(chart); + + this->chartCounter = 0; + durations = 0; + dataRefreshCounter = 0; + this->maxBitrate = 0; + this->minBitrate = 5000; + this->zoomInt = 0; + this->avgBitrate = 0; + + QDateTimeAxis *axisX = new QDateTimeAxis; + axisX->setFormat("m:ss"); + axisX->setTickCount(10); + axisX->setTitleText("Time m:s"); + this->chart()->setAxisX(axisX, lineSeries); + avgSeries->attachAxis(axisX); + + return(chart); +} + + + +void RecordWidgetGraph::setYAxisTitle(QString title){ + chart()->axisY()->setTitleText(title); + + if(title == "Std IAT dev µs"){ + lineSeries->setName("IAT dev"); + chart()->removeSeries(avgSeries); + bitrateSelected = false; + } +} + +void RecordWidgetGraph::setAvgBitrate(double avgBitrate){ + this->avgBitrate = avgBitrate; +} + + +void RecordWidgetGraph::setBitrate (double bitrate, qint64 duration, bool isBitrateSignal){ + + // Appends new values and updates graph + if(bitrate > (double) 0.1 && bitrate < 1000){ + this->durations = duration; + + lineSeries->append(duration, bitrate); + + + if(avgBitrate != 0){ + avgSeries->append(duration, avgBitrate); + } + this->chart()->axisX()->setRange(QDateTime::fromMSecsSinceEpoch(duration - 20000), QDateTime::fromMSecsSinceEpoch(duration + 2000)); + + + if(bitrate < this->minBitrate){ + minBitrate = bitrate; + this->chart()->axisY()->setRange(minBitrate - 0.5, this->maxBitrate + 0.5); + } + + if (this->maxBitrate < bitrate) { + this->maxBitrate = bitrate; + this->chart()->axisY()->setRange(minBitrate - 0.5, this->maxBitrate + 0.5); + } + + chartCounter++; + } +} + + + + +void RecordWidgetGraph::changeStream(int selectedStream, bool isBitrateSignal){ + + chart()->removeSeries(lineSeries); + chart()->removeSeries(avgSeries); + + if(isBitrateSignal){ + lineSeries = streamList[selectedStream]; + avgSeries = avgStreamList[selectedStream]; + + + chart()->addSeries(lineSeries); + chart()->addSeries(avgSeries); + + avgSeries->setName("Avg bitrate"); + lineSeries->setName("Bitrate"); + } else { + lineSeries = iatDevList[selectedStream]; + chart()->addSeries(lineSeries); + lineSeries->setName("IAT dev"); + } + + QPen pen(QRgb(0x000000)); + pen.setWidth(1); + lineSeries->setPen(pen); + + + chart()->createDefaultAxes(); + + QDateTimeAxis *axisX = new QDateTimeAxis; + axisX->setFormat("m:ss"); + axisX->setTickCount(10); + axisX->setTitleText("Time m:ss"); + + this->chart()->setAxisX(axisX, lineSeries); + + if(isBitrateSignal){ + avgSeries->attachAxis(chart()->axisX()); + chart()->axisY()->setTitleText("Bitrate mbps"); + this->chart()->axisY()->setRange(minBitrateList[selectedStream] - 0.5 , maxBitrateList[selectedStream] + 0.5); + bitrateSelected = true; + } else { + bitrateSelected = false; + chart()->axisY()->setTitleText("Std IAT dev µs"); + this->chart()->axisY()->setRange(minIatList[selectedStream] - 10, maxIatList[selectedStream] + 10); + } + + this->chart()->axisX()->setRange(QDateTime::fromMSecsSinceEpoch(durations - 20000), QDateTime::fromMSecsSinceEpoch(durations + 2000)); + selectedStreamIndex = selectedStream; +} + +void RecordWidgetGraph::setNoOfStreams(quint8 noOfStreams){ + this->noOfStreams = noOfStreams; +} + +void RecordWidgetGraph::recordMultipleStreams(WorkerStatus status){ // This could probably be made more generic, it's not optimal. Records all streams coming in and prints them to CSV file. + + if (streamList.count() < status.streams.count()){ + + for(int i = streamList.count(); i < status.streams.count(); i++){ + + this->streamList.append(new QLineSeries()); + this->avgStreamList.append(new QLineSeries()); + this->iatDevList.append(new QLineSeries()); + this->fileList.append(new QFile()); + + this->maxBitrateList.append(0); + this->minBitrateList.append(2000); + + this->minIatList.append(3000); + this->maxIatList.append(0); + + } + } + + for(int i = 0; i < status.streams.count(); i++){ + + quint64 hashKey = status.streams.keys().at(i); + + if(status.streams[hashKey].currentBitrate > 0.1 && status.streams[hashKey].avgBitrate != 0 && status.streams[hashKey].iatDeviation){ + + double bitrate = status.streams[hashKey].currentBitrate; + quint16 iatDev = status.streams[hashKey].iatDeviation; + + streamList[i]->append( status.streams[hashKey].currentTime, status.streams[hashKey].currentBitrate); + avgStreamList[i]->append(status.streams[hashKey].currentTime, status.streams[hashKey].avgBitrate); + iatDevList[i]->append(status.streams[hashKey].currentTime, status.streams[hashKey].iatDeviation); + + QString streamIpAdress = StreamId::calcName(hashKey); + + printer->printToFile(fileList[i], ((QString::number(status.streams[hashKey].currentTime))) + "," + (QString::number(status.streams[hashKey].currentBitrate)) + "," + (QString::number(status.streams[hashKey].iatDeviation)), currentFileName, streamIpAdress, i); + + if( bitrate < minBitrateList[i]){ + minBitrateList[i] = bitrate; + + } + if (maxBitrateList[i] < bitrate) { + maxBitrateList[i] = bitrate; + } + + + if(iatDev < minIatList[i]){ + minIatList[i] = iatDev; + } + + if(maxIatList[i] < iatDev){ + + maxIatList[i] = iatDev; + } + } + } + + if(durations > 300000){ + for(int i = 0; i < status.streams.count(); i++){ + + streamList[i]->remove(0); + avgStreamList[i]->remove(0); + iatDevList[i]->remove(0); + } + lineSeries->remove(0); + avgSeries->remove(0); + } + } + + + +void RecordWidgetGraph::refreshData(WorkerStatus status){ + + for(int i = 0; i < status.streams.count(); i++){ + + streamList[i]->remove(dataRefreshCounter); + avgStreamList[i]->remove(dataRefreshCounter); + iatDevList[i]->remove(dataRefreshCounter); + + } + avgSeries->remove(dataRefreshCounter); +} + + +void RecordWidgetGraph::setCurrentFileName(QString string){ + currentFileName = string; +} + + +void RecordWidgetGraph::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + QApplication::setOverrideCursor(QCursor(Qt::SizeAllCursor)); + m_lastMousePos = event->pos(); + event->accept(); + } + + QChartView::mousePressEvent(event); +} + + +void RecordWidgetGraph::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Plus: + chart()->zoomIn(); + break; + case Qt::Key_Minus: + this->zoomInt += 10000; + this->chart()->axisX()->setRange(QDateTime::fromMSecsSinceEpoch((durations - 20000) - zoomInt), QDateTime::fromMSecsSinceEpoch(durations + 2000)); + if(bitrateSelected){ + this->chart()->axisY()->setRange(minBitrateList[selectedStreamIndex] - 0.5, maxBitrateList[selectedStreamIndex] + 0.5); + } else { + this->chart()->axisY()->setRange(minIatList[selectedStreamIndex] - 10, maxIatList[selectedStreamIndex] + 10); + } + break; + + case Qt::Key_Left: + chart()->scroll(-10, 0); + + break; + case Qt::Key_Right: + chart()->scroll(10, 0); + + break; + case Qt::Key_Up: + chart()->scroll(0, 10); + break; + case Qt::Key_Down: + chart()->scroll(0, -10); + break; + default: + QGraphicsView::keyPressEvent(event); + break; + } +} + +// The code below is not used, may be nice to implement mouse scroll in future. +void RecordWidgetGraph::mouseMoveEvent(QMouseEvent *event) +{ + if (m_isTouching) + return; + QChartView::mouseMoveEvent(event); +} + +void RecordWidgetGraph::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_isTouching) + m_isTouching = false; + + // Because we disabled animations when touch event was detected + // we must put them back on. + chart()->setAnimationOptions(QChart::SeriesAnimations); + + QChartView::mouseReleaseEvent(event); +} diff --git a/recordwidgetgraph.h b/recordwidgetgraph.h new file mode 100644 index 0000000..be9c164 --- /dev/null +++ b/recordwidgetgraph.h @@ -0,0 +1,99 @@ +#ifndef RECORDWIDGETGRAPH_H +#define RECORDWIDGETGRAPH_H + +#include + +#include "mainwindow.h" +#include "recordwidget.h" +#include "recordtxtprinter.h" + + +#include +#include +#include +#include +#include + + +QT_CHARTS_USE_NAMESPACE + +class RecordWidgetGraph : public QChartView +{ + +public: + + quint8 selectedStreamIndex; + + RecordWidgetGraph(QWidget *parent); + ~RecordWidgetGraph(); + RecordTxtPrinter *printer; + QChart GraphChart; + qint16 chartCounter; + quint8 noOfStreams; + double maxBitrate; + double minBitrate; + QChart* setupGraph(); + + void setAvgBitrate(double avgBitrate); + void setCurrentFileName(QString string); + void changeStream(int i, bool isBitrateSignal); + void recordMultipleStreams(WorkerStatus status); + void setNoOfStreams(quint8 streams); + void keyPressEvent(QKeyEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + + void mousePressEvent(QMouseEvent *event) override; + + + +protected: + QList streamList; + QList avgStreamList; + QList iatDevList; + QList fileList; + + + QList maxBitrateList; + QList minBitrateList; + + QList maxIatList; + QList minIatList; + + + + + + +public slots: + void setBitrate (double bitrate, qint64 duration, bool isBitrateSignal); + void setYAxisTitle(QString title); + + + + +private: + + QString currentFileName; + QDateTimeAxis *axisX; + QString filename="/home/marko.marinkovic/Documents/Data.txt"; + QFile txtFile; + quint32 zoomInt; + double avgBitrate; + qint64 durations; + qint64 currentBitrate; + qint64 bitrateTimestamp; + quint64 dataRefreshCounter; + QLineSeries *lineSeries; + QLineSeries *avgSeries; + QPointF m_lastMousePos; + bool m_isTouching; + bool isScrolling = false; + bool bitrateSelected; + + void refreshData(WorkerStatus status); + void setAxisRange(bool isBitrateSignal, double bitrate); + + +}; +#endif // RECORDWIDGETGRAPH_H