2015-09-03 18:18:13 +00:00
|
|
|
/* iax2_analysis_dialog.cpp
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "iax2_analysis_dialog.h"
|
2016-06-12 21:00:21 +00:00
|
|
|
#include <ui_iax2_analysis_dialog.h>
|
2015-09-03 18:18:13 +00:00
|
|
|
|
|
|
|
#include "file.h"
|
|
|
|
#include "frame_tvbuff.h"
|
|
|
|
|
|
|
|
#include <epan/epan_dissect.h>
|
|
|
|
#include <epan/rtp_pt.h>
|
|
|
|
|
|
|
|
#include <epan/dfilter/dfilter.h>
|
|
|
|
|
|
|
|
#include <epan/dissectors/packet-iax2.h>
|
|
|
|
|
|
|
|
#include "ui/help_url.h"
|
|
|
|
#ifdef IAX2_RTP_STREAM_CHECK
|
|
|
|
#include "ui/rtp_stream.h"
|
|
|
|
#endif
|
2015-10-04 17:10:29 +00:00
|
|
|
#include <wsutil/utf8_entities.h>
|
2015-09-03 18:18:13 +00:00
|
|
|
|
|
|
|
#include <wsutil/g711.h>
|
|
|
|
#include <wsutil/pint.h>
|
|
|
|
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QTemporaryFile>
|
|
|
|
|
|
|
|
#include "color_utils.h"
|
|
|
|
#include "qt_ui_utils.h"
|
|
|
|
#include "stock_icon.h"
|
|
|
|
#include "wireshark_application.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @file RTP stream analysis dialog
|
|
|
|
*
|
|
|
|
* Displays forward and reverse RTP streams and graphs each stream
|
|
|
|
*/
|
|
|
|
|
|
|
|
// To do:
|
|
|
|
// - Progress bar for tapping and saving.
|
|
|
|
// - Add a refresh button and/or action.
|
|
|
|
// - Fixup output file names.
|
|
|
|
// - Add a graph title and legend when saving?
|
|
|
|
|
|
|
|
enum {
|
|
|
|
packet_col_,
|
|
|
|
delta_col_,
|
|
|
|
jitter_col_,
|
|
|
|
bandwidth_col_,
|
|
|
|
status_col_,
|
|
|
|
length_col_
|
|
|
|
};
|
|
|
|
|
|
|
|
static const QRgb color_rtp_warn_ = 0xffdbbf;
|
|
|
|
|
|
|
|
enum { iax2_analysis_type_ = 1000 };
|
|
|
|
class Iax2AnalysisTreeWidgetItem : public QTreeWidgetItem
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Iax2AnalysisTreeWidgetItem(QTreeWidget *tree, tap_iax2_stat_t *statinfo, packet_info *pinfo) :
|
|
|
|
QTreeWidgetItem(tree, iax2_analysis_type_)
|
|
|
|
{
|
2016-01-24 03:40:51 +00:00
|
|
|
frame_num_ = pinfo->num;
|
2015-09-03 18:18:13 +00:00
|
|
|
pkt_len_ = pinfo->fd->pkt_len;
|
|
|
|
flags_ = statinfo->flags;
|
|
|
|
if (flags_ & STAT_FLAG_FIRST) {
|
|
|
|
delta_ = 0.0;
|
|
|
|
jitter_ = 0.0;
|
|
|
|
} else {
|
|
|
|
delta_ = statinfo->delta;
|
|
|
|
jitter_ = statinfo->jitter;
|
|
|
|
}
|
|
|
|
bandwidth_ = statinfo->bandwidth;
|
|
|
|
ok_ = false;
|
|
|
|
|
|
|
|
QColor bg_color = QColor();
|
|
|
|
QString status;
|
|
|
|
|
|
|
|
if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
|
|
|
|
status = QObject::tr("Wrong sequence number");
|
|
|
|
bg_color = ColorUtils::expert_color_error;
|
|
|
|
} else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
|
|
|
|
status = QObject::tr("Payload changed to PT=%1").arg(statinfo->pt);
|
|
|
|
bg_color = color_rtp_warn_;
|
|
|
|
} else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
|
|
|
|
status = QObject::tr("Incorrect timestamp");
|
|
|
|
/* color = COLOR_WARNING; */
|
|
|
|
bg_color = color_rtp_warn_;
|
|
|
|
} else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
|
|
|
|
&& !(statinfo->flags & STAT_FLAG_FIRST)
|
|
|
|
&& !(statinfo->flags & STAT_FLAG_PT_CN)
|
|
|
|
&& (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
|
|
|
|
&& !(statinfo->flags & STAT_FLAG_MARKER)) {
|
|
|
|
status = QObject::tr("Marker missing?");
|
|
|
|
bg_color = color_rtp_warn_;
|
|
|
|
} else {
|
|
|
|
if (statinfo->flags & STAT_FLAG_MARKER) {
|
|
|
|
bg_color = color_rtp_warn_;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status.isEmpty()) {
|
|
|
|
ok_ = true;
|
|
|
|
status = UTF8_CHECK_MARK;
|
|
|
|
}
|
|
|
|
|
|
|
|
setText(packet_col_, QString::number(frame_num_));
|
|
|
|
setText(delta_col_, QString::number(delta_, 'f', 2));
|
|
|
|
setText(jitter_col_, QString::number(jitter_, 'f', 2));
|
|
|
|
setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
|
|
|
|
setText(status_col_, status);
|
|
|
|
setText(length_col_, QString::number(pkt_len_));
|
|
|
|
|
|
|
|
setTextAlignment(packet_col_, Qt::AlignRight);
|
|
|
|
setTextAlignment(delta_col_, Qt::AlignRight);
|
|
|
|
setTextAlignment(jitter_col_, Qt::AlignRight);
|
|
|
|
setTextAlignment(bandwidth_col_, Qt::AlignRight);
|
|
|
|
setTextAlignment(length_col_, Qt::AlignRight);
|
|
|
|
|
|
|
|
if (bg_color.isValid()) {
|
|
|
|
for (int col = 0; col < columnCount(); col++) {
|
|
|
|
setBackground(col, bg_color);
|
|
|
|
setForeground(col, ColorUtils::expert_color_foreground);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
guint32 frameNum() { return frame_num_; }
|
|
|
|
bool frameStatus() { return ok_; }
|
|
|
|
|
|
|
|
QList<QVariant> rowData() {
|
|
|
|
QString status_str = ok_ ? "OK" : text(status_col_);
|
|
|
|
|
|
|
|
return QList<QVariant>()
|
|
|
|
<< frame_num_ << delta_ << jitter_ << bandwidth_
|
|
|
|
<< status_str << pkt_len_;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator< (const QTreeWidgetItem &other) const
|
|
|
|
{
|
|
|
|
if (other.type() != iax2_analysis_type_) return QTreeWidgetItem::operator< (other);
|
|
|
|
const Iax2AnalysisTreeWidgetItem *other_row = static_cast<const Iax2AnalysisTreeWidgetItem *>(&other);
|
|
|
|
|
|
|
|
switch (treeWidget()->sortColumn()) {
|
|
|
|
case (packet_col_):
|
|
|
|
return frame_num_ < other_row->frame_num_;
|
|
|
|
break;
|
|
|
|
case (delta_col_):
|
|
|
|
return delta_ < other_row->delta_;
|
|
|
|
break;
|
|
|
|
case (jitter_col_):
|
|
|
|
return jitter_ < other_row->jitter_;
|
|
|
|
break;
|
|
|
|
case (bandwidth_col_):
|
|
|
|
return bandwidth_ < other_row->bandwidth_;
|
|
|
|
break;
|
|
|
|
case (length_col_):
|
|
|
|
return pkt_len_ < other_row->pkt_len_;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back to string comparison
|
|
|
|
return QTreeWidgetItem::operator <(other);
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
guint32 frame_num_;
|
|
|
|
guint32 pkt_len_;
|
|
|
|
guint32 flags_;
|
|
|
|
double delta_;
|
|
|
|
double jitter_;
|
|
|
|
double bandwidth_;
|
|
|
|
bool ok_;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
fwd_jitter_graph_,
|
|
|
|
fwd_diff_graph_,
|
|
|
|
rev_jitter_graph_,
|
|
|
|
rev_diff_graph_,
|
|
|
|
num_graphs_
|
|
|
|
};
|
|
|
|
|
|
|
|
Iax2AnalysisDialog::Iax2AnalysisDialog(QWidget &parent, CaptureFile &cf) :
|
|
|
|
WiresharkDialog(parent, cf),
|
|
|
|
ui(new Ui::Iax2AnalysisDialog),
|
|
|
|
port_src_fwd_(0),
|
|
|
|
port_dst_fwd_(0),
|
|
|
|
port_src_rev_(0),
|
2016-05-09 21:21:28 +00:00
|
|
|
port_dst_rev_(0),
|
|
|
|
save_payload_error_(TAP_IAX2_NO_ERROR)
|
2015-09-03 18:18:13 +00:00
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
2016-02-28 18:23:20 +00:00
|
|
|
loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
|
2015-09-03 18:18:13 +00:00
|
|
|
setWindowSubtitle(tr("IAX2 Stream Analysis"));
|
|
|
|
|
|
|
|
ui->progressFrame->hide();
|
|
|
|
|
|
|
|
stream_ctx_menu_.addAction(ui->actionGoToPacket);
|
|
|
|
stream_ctx_menu_.addAction(ui->actionNextProblem);
|
|
|
|
stream_ctx_menu_.addSeparator();
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveAudio);
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveForwardAudio);
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveReverseAudio);
|
|
|
|
stream_ctx_menu_.addSeparator();
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveCsv);
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
|
|
|
|
stream_ctx_menu_.addSeparator();
|
|
|
|
stream_ctx_menu_.addAction(ui->actionSaveGraph);
|
|
|
|
ui->forwardTreeWidget->installEventFilter(this);
|
|
|
|
ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
|
|
|
|
SLOT(showStreamMenu(QPoint)));
|
|
|
|
ui->reverseTreeWidget->installEventFilter(this);
|
|
|
|
ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
|
|
|
|
SLOT(showStreamMenu(QPoint)));
|
|
|
|
connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
|
|
|
|
this, SLOT(graphClicked(QMouseEvent*)));
|
|
|
|
|
|
|
|
graph_ctx_menu_.addAction(ui->actionSaveGraph);
|
|
|
|
|
|
|
|
QStringList header_labels;
|
|
|
|
for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
|
|
|
|
header_labels << ui->forwardTreeWidget->headerItem()->text(i);
|
|
|
|
}
|
|
|
|
ui->reverseTreeWidget->setHeaderLabels(header_labels);
|
|
|
|
|
|
|
|
memset(&src_fwd_, 0, sizeof(address));
|
|
|
|
memset(&dst_fwd_, 0, sizeof(address));
|
|
|
|
memset(&src_rev_, 0, sizeof(address));
|
|
|
|
memset(&dst_rev_, 0, sizeof(address));
|
|
|
|
|
|
|
|
QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
|
|
|
|
<< ui->fJitterCheckBox << ui->fDiffCheckBox
|
|
|
|
<< ui->rJitterCheckBox << ui->rDiffCheckBox;
|
|
|
|
|
|
|
|
for (int i = 0; i < num_graphs_; i++) {
|
|
|
|
QCPGraph *graph = ui->streamGraph->addGraph();
|
2015-11-30 22:42:42 +00:00
|
|
|
graph->setPen(QPen(ColorUtils::graphColor(i)));
|
2015-09-03 18:18:13 +00:00
|
|
|
graph->setName(graph_cbs[i]->text());
|
|
|
|
graphs_ << graph;
|
|
|
|
graph_cbs[i]->setChecked(true);
|
2015-11-30 22:42:42 +00:00
|
|
|
graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
|
2015-09-03 18:18:13 +00:00
|
|
|
}
|
|
|
|
ui->streamGraph->xAxis->setLabel("Arrival Time");
|
|
|
|
ui->streamGraph->yAxis->setLabel("Value (ms)");
|
|
|
|
|
|
|
|
// We keep our temp files open for the lifetime of the dialog. The GTK+
|
|
|
|
// UI opens and closes at various points.
|
|
|
|
QString tempname = QString("%1/wireshark_iax2_f").arg(QDir::tempPath());
|
|
|
|
fwd_tempfile_ = new QTemporaryFile(tempname, this);
|
|
|
|
fwd_tempfile_->open();
|
|
|
|
tempname = QString("%1/wireshark_iax2_r").arg(QDir::tempPath());
|
|
|
|
rev_tempfile_ = new QTemporaryFile(tempname, this);
|
|
|
|
rev_tempfile_->open();
|
|
|
|
|
|
|
|
if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
|
|
|
|
err_str_ = tr("Unable to save RTP data.");
|
|
|
|
ui->actionSaveAudio->setEnabled(false);
|
|
|
|
ui->actionSaveForwardAudio->setEnabled(false);
|
|
|
|
ui->actionSaveReverseAudio->setEnabled(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
QMenu *save_menu = new QMenu();
|
|
|
|
save_menu->addAction(ui->actionSaveAudio);
|
|
|
|
save_menu->addAction(ui->actionSaveForwardAudio);
|
|
|
|
save_menu->addAction(ui->actionSaveReverseAudio);
|
|
|
|
save_menu->addSeparator();
|
|
|
|
save_menu->addAction(ui->actionSaveCsv);
|
|
|
|
save_menu->addAction(ui->actionSaveForwardCsv);
|
|
|
|
save_menu->addAction(ui->actionSaveReverseCsv);
|
|
|
|
save_menu->addSeparator();
|
|
|
|
save_menu->addAction(ui->actionSaveGraph);
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu);
|
|
|
|
|
2016-09-15 07:08:18 +00:00
|
|
|
ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
|
|
|
|
|
|
|
|
resetStatistics();
|
|
|
|
updateStatistics(); // Initialize stats if an error occurs
|
|
|
|
|
2016-01-04 21:29:45 +00:00
|
|
|
#if 0
|
|
|
|
/* Only accept Voice or MiniPacket packets */
|
|
|
|
const gchar filter_text[] = "iax2.call && (ip || ipv6)";
|
|
|
|
#else
|
|
|
|
const gchar filter_text[] = "iax2 && (ip || ipv6)";
|
|
|
|
#endif
|
2015-09-03 18:18:13 +00:00
|
|
|
dfilter_t *sfcode;
|
|
|
|
gchar *err_msg;
|
|
|
|
|
2016-01-04 21:29:45 +00:00
|
|
|
/* Try to compile the filter. */
|
2015-09-03 18:18:13 +00:00
|
|
|
if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
|
2016-01-04 21:29:45 +00:00
|
|
|
err_str_ = QString(err_msg);
|
2015-09-03 18:18:13 +00:00
|
|
|
g_free(err_msg);
|
2016-01-04 21:29:45 +00:00
|
|
|
updateWidgets();
|
|
|
|
return;
|
2015-09-03 18:18:13 +00:00
|
|
|
}
|
|
|
|
|
2016-09-15 07:08:18 +00:00
|
|
|
if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) {
|
|
|
|
err_str_ = tr("Please select an IAX2 packet.");
|
|
|
|
save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
|
|
|
|
updateWidgets();
|
|
|
|
return;
|
|
|
|
}
|
2015-09-03 18:18:13 +00:00
|
|
|
|
|
|
|
frame_data *fdata = cap_file_.capFile()->current_frame;
|
|
|
|
|
|
|
|
if (!cf_read_record(cap_file_.capFile(), fdata)) close();
|
|
|
|
|
|
|
|
epan_dissect_t edt;
|
|
|
|
|
|
|
|
epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
|
|
|
|
epan_dissect_prime_dfilter(&edt, sfcode);
|
|
|
|
epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->phdr,
|
|
|
|
frame_tvbuff_new_buffer(fdata, &cap_file_.capFile()->buf), fdata, NULL);
|
|
|
|
|
|
|
|
// This shouldn't happen (the menu item should be disabled) but check anyway
|
|
|
|
if (!dfilter_apply_edt(sfcode, &edt)) {
|
|
|
|
epan_dissect_cleanup(&edt);
|
|
|
|
dfilter_free(sfcode);
|
2016-09-15 07:08:18 +00:00
|
|
|
err_str_ = tr("Please select an IAX2 packet.");
|
|
|
|
save_payload_error_ = TAP_IAX2_NO_PACKET_SELECTED;
|
2015-09-03 18:18:13 +00:00
|
|
|
updateWidgets();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dfilter_free(sfcode);
|
|
|
|
|
|
|
|
/* ok, it is a IAX2 frame, so let's get the ip and port values */
|
2015-10-21 18:46:49 +00:00
|
|
|
copy_address(&(src_fwd_), &(edt.pi.src));
|
|
|
|
copy_address(&(dst_fwd_), &(edt.pi.dst));
|
2015-09-03 18:18:13 +00:00
|
|
|
port_src_fwd_ = edt.pi.srcport;
|
|
|
|
port_dst_fwd_ = edt.pi.destport;
|
|
|
|
|
|
|
|
/* assume the inverse ip/port combination for the reverse direction */
|
2015-10-21 18:46:49 +00:00
|
|
|
copy_address(&(src_rev_), &(edt.pi.dst));
|
|
|
|
copy_address(&(dst_rev_), &(edt.pi.src));
|
2015-09-03 18:18:13 +00:00
|
|
|
port_src_rev_ = edt.pi.destport;
|
|
|
|
port_dst_rev_ = edt.pi.srcport;
|
|
|
|
|
|
|
|
#ifdef IAX2_RTP_STREAM_CHECK
|
|
|
|
rtpstream_tapinfot tapinfo;
|
|
|
|
|
|
|
|
/* Register the tap listener */
|
|
|
|
memset(&tapinfo, 0, sizeof(rtpstream_tapinfot));
|
|
|
|
tapinfo.tap_data = this;
|
|
|
|
tapinfo.mode = TAP_ANALYSE;
|
|
|
|
|
|
|
|
// register_tap_listener_rtp_stream(&tapinfo, NULL);
|
|
|
|
/* Scan for RTP streams (redissect all packets) */
|
|
|
|
rtpstream_scan(&tapinfo, cap_file_.capFile(), NULL);
|
|
|
|
|
|
|
|
int num_streams = 0;
|
|
|
|
GList *filtered_list = NULL;
|
|
|
|
for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) {
|
|
|
|
rtp_stream_info_t * strinfo = (rtp_stream_info_t*)(strinfo_list->data);
|
|
|
|
<< address_to_qstring(&strinfo->dest_addr) << address_to_qstring(&src_rev_) << address_to_qstring(&dst_rev_);
|
2015-10-21 18:46:49 +00:00
|
|
|
if (addresses_equal(&(strinfo->src_addr), &(src_fwd_))
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (strinfo->src_port == port_src_fwd_)
|
2015-10-21 18:46:49 +00:00
|
|
|
&& (addresses_equal(&(strinfo->dest_addr), &(dst_fwd_)))
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (strinfo->dest_port == port_dst_fwd_))
|
|
|
|
{
|
|
|
|
++num_streams;
|
|
|
|
filtered_list = g_list_prepend(filtered_list, strinfo);
|
|
|
|
}
|
|
|
|
|
2015-10-21 18:46:49 +00:00
|
|
|
if (addresses_equal(&(strinfo->src_addr), &(src_rev_))
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (strinfo->src_port == port_src_rev_)
|
2015-10-21 18:46:49 +00:00
|
|
|
&& (addresses_equal(&(strinfo->dest_addr), &(dst_rev_)))
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (strinfo->dest_port == port_dst_rev_))
|
|
|
|
{
|
|
|
|
++num_streams;
|
|
|
|
filtered_list = g_list_append(filtered_list, strinfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_streams > 1) {
|
|
|
|
// Open the RTP streams dialog.
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
connect(ui->tabWidget, SIGNAL(currentChanged(int)),
|
|
|
|
this, SLOT(updateWidgets()));
|
|
|
|
connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
|
|
|
|
this, SLOT(updateWidgets()));
|
|
|
|
connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
|
|
|
|
this, SLOT(updateWidgets()));
|
|
|
|
connect(&cap_file_, SIGNAL(captureFileClosing()),
|
|
|
|
this, SLOT(updateWidgets()));
|
|
|
|
updateWidgets();
|
|
|
|
|
|
|
|
registerTapListener("IAX2", this, NULL, 0, tapReset, tapPacket, tapDraw);
|
|
|
|
cap_file_.retapPackets();
|
|
|
|
removeTapListeners();
|
|
|
|
|
|
|
|
updateStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
Iax2AnalysisDialog::~Iax2AnalysisDialog()
|
|
|
|
{
|
|
|
|
delete ui;
|
|
|
|
// remove_tap_listener_rtp_stream(&tapinfo);
|
|
|
|
delete fwd_tempfile_;
|
|
|
|
delete rev_tempfile_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::updateWidgets()
|
|
|
|
{
|
|
|
|
bool enable_tab = false;
|
|
|
|
QString hint = err_str_;
|
|
|
|
|
2016-05-09 21:21:28 +00:00
|
|
|
if (hint.isEmpty() || save_payload_error_ != TAP_IAX2_NO_ERROR) {
|
|
|
|
/* We cannot save the payload but can still display the widget
|
|
|
|
or save CSV data */
|
2015-09-03 18:18:13 +00:00
|
|
|
enable_tab = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool enable_nav = false;
|
|
|
|
if (!file_closed_
|
|
|
|
&& ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
|
|
|
|
&& ui->forwardTreeWidget->selectedItems().length() > 0)
|
|
|
|
|| (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
|
|
|
|
&& ui->reverseTreeWidget->selectedItems().length() > 0))) {
|
|
|
|
enable_nav = true;
|
|
|
|
}
|
|
|
|
ui->actionGoToPacket->setEnabled(enable_nav);
|
|
|
|
ui->actionNextProblem->setEnabled(enable_nav);
|
|
|
|
|
|
|
|
if (enable_nav) {
|
|
|
|
hint.append(tr(" G: Go to packet, N: Next problem packet"));
|
|
|
|
}
|
|
|
|
|
2016-05-09 21:21:28 +00:00
|
|
|
bool enable_save_fwd_audio = fwd_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
|
|
|
|
bool enable_save_rev_audio = rev_tempfile_->isOpen() && (save_payload_error_ == TAP_IAX2_NO_ERROR);
|
2015-09-03 18:18:13 +00:00
|
|
|
ui->actionSaveAudio->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
|
|
|
|
ui->actionSaveForwardAudio->setEnabled(enable_save_fwd_audio);
|
|
|
|
ui->actionSaveReverseAudio->setEnabled(enable_save_rev_audio);
|
|
|
|
|
|
|
|
bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
|
|
|
|
bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
|
|
|
|
ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
|
|
|
|
ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
|
|
|
|
ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
|
|
|
|
|
|
|
|
ui->tabWidget->setEnabled(enable_tab);
|
|
|
|
hint.prepend("<small><i>");
|
|
|
|
hint.append("</i></small>");
|
|
|
|
ui->hintLabel->setText(hint);
|
2015-09-25 09:05:45 +00:00
|
|
|
|
|
|
|
WiresharkDialog::updateWidgets();
|
2015-09-03 18:18:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionGoToPacket_triggered()
|
|
|
|
{
|
|
|
|
if (file_closed_) return;
|
|
|
|
QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
|
|
|
|
if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
|
|
|
|
if (ti->type() != iax2_analysis_type_) return;
|
|
|
|
|
|
|
|
Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
|
|
|
|
emit goToPacket(ra_ti->frameNum());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionNextProblem_triggered()
|
|
|
|
{
|
|
|
|
QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
|
|
|
|
if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
|
|
|
|
|
|
|
|
// Choose convenience over correctness.
|
|
|
|
if (cur_tree->selectedItems().length() < 1) {
|
|
|
|
cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
|
|
|
|
if (sel_ti->type() != iax2_analysis_type_) return;
|
|
|
|
QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
|
|
|
|
while (test_ti != sel_ti) {
|
|
|
|
if (!test_ti) test_ti = cur_tree->topLevelItem(0);
|
|
|
|
Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)test_ti);
|
|
|
|
if (!ra_ti->frameStatus()) {
|
|
|
|
cur_tree->setCurrentItem(ra_ti);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
test_ti = cur_tree->itemBelow(test_ti);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
|
|
|
|
updateGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
|
|
|
|
updateGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
|
|
|
|
updateGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
|
|
|
|
{
|
|
|
|
ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
|
|
|
|
updateGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveAudio_triggered()
|
|
|
|
{
|
|
|
|
saveAudio(dir_both_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveForwardAudio_triggered()
|
|
|
|
{
|
|
|
|
saveAudio(dir_forward_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveReverseAudio_triggered()
|
|
|
|
{
|
|
|
|
saveAudio(dir_reverse_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveCsv_triggered()
|
|
|
|
{
|
|
|
|
saveCsv(dir_both_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveForwardCsv_triggered()
|
|
|
|
{
|
|
|
|
saveCsv(dir_forward_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveReverseCsv_triggered()
|
|
|
|
{
|
|
|
|
saveCsv(dir_reverse_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_actionSaveGraph_triggered()
|
|
|
|
{
|
|
|
|
ui->tabWidget->setCurrentWidget(ui->graphTab);
|
|
|
|
|
|
|
|
QString file_name, extension;
|
|
|
|
QDir path(wsApp->lastOpenDir());
|
|
|
|
QString pdf_filter = tr("Portable Document Format (*.pdf)");
|
|
|
|
QString png_filter = tr("Portable Network Graphics (*.png)");
|
|
|
|
QString bmp_filter = tr("Windows Bitmap (*.bmp)");
|
|
|
|
// Gaze upon my beautiful graph with lossy artifacts!
|
|
|
|
QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
|
|
|
|
QString filter = QString("%1;;%2;;%3;;%4")
|
|
|
|
.arg(pdf_filter)
|
|
|
|
.arg(png_filter)
|
|
|
|
.arg(bmp_filter)
|
|
|
|
.arg(jpeg_filter);
|
|
|
|
|
|
|
|
QString save_file = path.canonicalPath();
|
|
|
|
if (!file_closed_) {
|
|
|
|
save_file += QString("/%1").arg(cap_file_.fileTitle());
|
|
|
|
}
|
|
|
|
file_name = QFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
|
|
|
|
save_file, filter, &extension);
|
|
|
|
|
|
|
|
if (!file_name.isEmpty()) {
|
|
|
|
bool save_ok = false;
|
|
|
|
// http://www.qcustomplot.com/index.php/support/forum/63
|
|
|
|
// ui->streamGraph->legend->setVisible(true);
|
|
|
|
if (extension.compare(pdf_filter) == 0) {
|
|
|
|
save_ok = ui->streamGraph->savePdf(file_name);
|
|
|
|
} else if (extension.compare(png_filter) == 0) {
|
|
|
|
save_ok = ui->streamGraph->savePng(file_name);
|
|
|
|
} else if (extension.compare(bmp_filter) == 0) {
|
|
|
|
save_ok = ui->streamGraph->saveBmp(file_name);
|
|
|
|
} else if (extension.compare(jpeg_filter) == 0) {
|
|
|
|
save_ok = ui->streamGraph->saveJpg(file_name);
|
|
|
|
}
|
|
|
|
// ui->streamGraph->legend->setVisible(false);
|
|
|
|
// else error dialog?
|
|
|
|
if (save_ok) {
|
|
|
|
path = QDir(file_name);
|
|
|
|
wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::on_buttonBox_helpRequested()
|
|
|
|
{
|
|
|
|
wsApp->helpTopicAction(HELP_IAX2_ANALYSIS_DIALOG);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::tapReset(void *tapinfoptr)
|
|
|
|
{
|
|
|
|
Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
|
|
|
|
if (!iax2_analysis_dialog) return;
|
|
|
|
|
|
|
|
iax2_analysis_dialog->resetStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean Iax2AnalysisDialog::tapPacket(void *tapinfoptr, packet_info *pinfo, struct epan_dissect *, const void *iax2info_ptr)
|
|
|
|
{
|
|
|
|
Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
|
|
|
|
if (!iax2_analysis_dialog) return FALSE;
|
|
|
|
|
|
|
|
const iax2_info_t *iax2info = (const iax2_info_t *)iax2info_ptr;
|
|
|
|
if (!iax2info) return FALSE;
|
|
|
|
|
|
|
|
/* we ignore packets that are not displayed */
|
|
|
|
if (pinfo->fd->flags.passed_dfilter == 0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* we ignore packets that carry no data */
|
|
|
|
if (iax2info->payload_len < 1)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* is it the forward direction? */
|
2015-10-21 18:46:49 +00:00
|
|
|
else if ((cmp_address(&(iax2_analysis_dialog->src_fwd_), &(pinfo->src)) == 0)
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (iax2_analysis_dialog->port_src_fwd_ == pinfo->srcport)
|
2015-10-21 18:46:49 +00:00
|
|
|
&& (cmp_address(&(iax2_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0)
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (iax2_analysis_dialog->port_dst_fwd_ == pinfo->destport)) {
|
|
|
|
|
|
|
|
iax2_analysis_dialog->addPacket(true, pinfo, iax2info);
|
|
|
|
}
|
|
|
|
/* is it the reversed direction? */
|
2015-10-21 18:46:49 +00:00
|
|
|
else if ((cmp_address(&(iax2_analysis_dialog->src_rev_), &(pinfo->src)) == 0)
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (iax2_analysis_dialog->port_src_rev_ == pinfo->srcport)
|
2015-10-21 18:46:49 +00:00
|
|
|
&& (cmp_address(&(iax2_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0)
|
2015-09-03 18:18:13 +00:00
|
|
|
&& (iax2_analysis_dialog->port_dst_rev_ == pinfo->destport)) {
|
|
|
|
|
|
|
|
iax2_analysis_dialog->addPacket(false, pinfo, iax2info);
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::tapDraw(void *tapinfoptr)
|
|
|
|
{
|
|
|
|
Iax2AnalysisDialog *iax2_analysis_dialog = dynamic_cast<Iax2AnalysisDialog *>((Iax2AnalysisDialog*)tapinfoptr);
|
|
|
|
if (!iax2_analysis_dialog) return;
|
|
|
|
iax2_analysis_dialog->updateStatistics();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::resetStatistics()
|
|
|
|
{
|
|
|
|
memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
|
|
|
|
memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
|
|
|
|
|
|
|
|
fwd_statinfo_.first_packet = TRUE;
|
|
|
|
rev_statinfo_.first_packet = TRUE;
|
|
|
|
fwd_statinfo_.reg_pt = PT_UNDEFINED;
|
|
|
|
rev_statinfo_.reg_pt = PT_UNDEFINED;
|
|
|
|
|
|
|
|
ui->forwardTreeWidget->clear();
|
|
|
|
ui->reverseTreeWidget->clear();
|
|
|
|
|
|
|
|
for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
|
|
|
|
ui->streamGraph->graph(i)->clearData();
|
|
|
|
}
|
|
|
|
|
|
|
|
fwd_time_vals_.clear();
|
|
|
|
fwd_jitter_vals_.clear();
|
|
|
|
fwd_diff_vals_.clear();
|
|
|
|
rev_time_vals_.clear();
|
|
|
|
rev_jitter_vals_.clear();
|
|
|
|
rev_diff_vals_.clear();
|
|
|
|
|
|
|
|
fwd_tempfile_->resize(0);
|
|
|
|
rev_tempfile_->resize(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::addPacket(bool forward, packet_info *pinfo, const struct _iax2_info_t *iax2info)
|
|
|
|
{
|
|
|
|
/* add this RTP for future listening using the RTP Player*/
|
|
|
|
// add_rtp_packet(rtpinfo, pinfo);
|
|
|
|
|
|
|
|
if (forward) {
|
|
|
|
iax2_packet_analyse(&fwd_statinfo_, pinfo, iax2info);
|
|
|
|
new Iax2AnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo);
|
|
|
|
|
2015-11-26 18:08:47 +00:00
|
|
|
fwd_time_vals_.append((fwd_statinfo_.time));
|
2015-09-03 18:18:13 +00:00
|
|
|
fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
|
|
|
|
fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
|
|
|
|
|
|
|
|
savePayload(fwd_tempfile_, pinfo, iax2info);
|
|
|
|
} else {
|
|
|
|
iax2_packet_analyse(&rev_statinfo_, pinfo, iax2info);
|
|
|
|
new Iax2AnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo);
|
|
|
|
|
2015-11-26 18:08:47 +00:00
|
|
|
rev_time_vals_.append((rev_statinfo_.time));
|
2015-09-03 18:18:13 +00:00
|
|
|
rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
|
|
|
|
rev_diff_vals_.append(rev_statinfo_.diff * 1000);
|
|
|
|
|
|
|
|
savePayload(rev_tempfile_, pinfo, iax2info);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// iax2_analysis.c:rtp_packet_save_payload
|
|
|
|
const guint8 silence_pcmu_ = 0xff;
|
|
|
|
const guint8 silence_pcma_ = 0x55;
|
|
|
|
void Iax2AnalysisDialog::savePayload(QTemporaryFile *tmpfile, packet_info *pinfo, const struct _iax2_info_t *iax2info)
|
|
|
|
{
|
|
|
|
/* Is this the first packet we got in this direction? */
|
|
|
|
// if (statinfo->flags & STAT_FLAG_FIRST) {
|
|
|
|
// if (saveinfo->fp == NULL) {
|
|
|
|
// saveinfo->saved = FALSE;
|
|
|
|
// saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
|
|
|
|
// } else {
|
|
|
|
// saveinfo->saved = TRUE;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
/* Save the voice information */
|
|
|
|
|
|
|
|
/* If there was already an error, we quit */
|
|
|
|
if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError) return;
|
|
|
|
|
|
|
|
/* Quit if the captured length and packet length aren't equal.
|
|
|
|
*/
|
|
|
|
if (pinfo->fd->pkt_len != pinfo->fd->cap_len) {
|
|
|
|
tmpfile->close();
|
|
|
|
err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
|
2016-05-09 21:21:28 +00:00
|
|
|
save_payload_error_ = TAP_IAX2_WRONG_LENGTH;
|
2015-09-03 18:18:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iax2info->payload_len > 0) {
|
|
|
|
const char *data = (const char *) iax2info->payload_data;
|
|
|
|
size_t nchars;
|
|
|
|
|
|
|
|
nchars = tmpfile->write(data, iax2info->payload_len);
|
|
|
|
if (nchars != (iax2info->payload_len)) {
|
|
|
|
/* Write error or short write */
|
|
|
|
err_str_ = tr("Can't save in a file: File I/O problem.");
|
2016-05-09 21:21:28 +00:00
|
|
|
save_payload_error_ = TAP_IAX2_FILE_IO_ERROR;
|
2015-09-03 18:18:13 +00:00
|
|
|
tmpfile->close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::updateStatistics()
|
|
|
|
{
|
|
|
|
double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time; // s
|
|
|
|
double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
|
|
|
|
#if 0 // Marked as "TODO" in tap-iax2-analysis.c:128
|
|
|
|
unsigned int f_expected = fwd_statinfo_.stop_seq_nr - fwd_statinfo_.start_seq_nr + 1;
|
|
|
|
unsigned int r_expected = rev_statinfo_.stop_seq_nr - rev_statinfo_.start_seq_nr + 1;
|
|
|
|
int f_lost = f_expected - fwd_statinfo_.total_nr;
|
|
|
|
int r_lost = r_expected - rev_statinfo_.total_nr;
|
|
|
|
double f_perc, r_perc;
|
|
|
|
|
|
|
|
if (f_expected) {
|
|
|
|
f_perc = (double)(f_lost*100)/(double)f_expected;
|
|
|
|
} else {
|
|
|
|
f_perc = 0;
|
|
|
|
}
|
|
|
|
if (r_expected) {
|
|
|
|
r_perc = (double)(r_lost*100)/(double)r_expected;
|
|
|
|
} else {
|
|
|
|
r_perc = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
QString stats_tables = "<html><head></head><body>\n";
|
|
|
|
stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
|
|
|
|
.arg(address_to_qstring(&src_fwd_, true))
|
|
|
|
.arg(port_src_fwd_);
|
|
|
|
stats_tables += QString("<br>%1:%2</p>\n")
|
|
|
|
.arg(address_to_qstring(&dst_fwd_, true))
|
|
|
|
.arg(port_dst_fwd_);
|
|
|
|
stats_tables += "<h4>Forward</h4>\n";
|
|
|
|
stats_tables += "<p><table>\n";
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
|
|
|
|
.arg(fwd_statinfo_.max_delta, 0, 'f', 2)
|
|
|
|
.arg(fwd_statinfo_.max_nr);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
|
|
|
|
.arg(fwd_statinfo_.max_jitter, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
|
|
|
|
.arg(fwd_statinfo_.mean_jitter, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
|
|
|
|
.arg(fwd_statinfo_.total_nr);
|
|
|
|
#if 0
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
|
|
|
|
.arg(f_expected);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
|
|
|
|
.arg(f_lost).arg(f_perc, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
|
|
|
|
.arg(fwd_statinfo_.sequence);
|
|
|
|
#endif
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
|
|
|
|
.arg(f_duration, 0, 'f', 2);
|
|
|
|
stats_tables += "</table></p>\n";
|
|
|
|
|
|
|
|
stats_tables += "<h4>Reverse</h4>\n";
|
|
|
|
stats_tables += "<p><table>\n";
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
|
|
|
|
.arg(rev_statinfo_.max_delta, 0, 'f', 2)
|
|
|
|
.arg(rev_statinfo_.max_nr);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</tr>")
|
|
|
|
.arg(rev_statinfo_.max_jitter, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</tr>")
|
|
|
|
.arg(rev_statinfo_.mean_jitter, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">IAX2 Packets</th><td>%1</tr>")
|
|
|
|
.arg(rev_statinfo_.total_nr);
|
|
|
|
#if 0
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</tr>")
|
|
|
|
.arg(r_expected);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</tr>")
|
|
|
|
.arg(r_lost).arg(r_perc, 0, 'f', 2);
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</tr>")
|
|
|
|
.arg(rev_statinfo_.sequence);
|
|
|
|
#endif
|
|
|
|
stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
|
|
|
|
.arg(r_duration, 0, 'f', 2);
|
|
|
|
stats_tables += "</table></p></body>\n";
|
|
|
|
|
|
|
|
ui->statisticsLabel->setText(stats_tables);
|
|
|
|
|
|
|
|
for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
|
|
|
|
ui->forwardTreeWidget->resizeColumnToContents(col);
|
|
|
|
ui->reverseTreeWidget->resizeColumnToContents(col);
|
|
|
|
}
|
|
|
|
|
|
|
|
graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
|
|
|
|
graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
|
|
|
|
graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
|
|
|
|
graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
|
|
|
|
|
|
|
|
updateGraph();
|
|
|
|
|
|
|
|
updateWidgets();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::updateGraph()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
|
|
|
|
if (ui->streamGraph->graph(i)->visible()) {
|
|
|
|
ui->streamGraph->graph(i)->rescaleAxes(i > 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ui->streamGraph->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
// iax2_analysis.c:copy_file
|
|
|
|
enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
|
|
|
|
void Iax2AnalysisDialog::saveAudio(Iax2AnalysisDialog::StreamDirection direction)
|
|
|
|
{
|
|
|
|
if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
|
|
|
|
|
|
|
|
QString caption;
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
case dir_forward_:
|
|
|
|
caption = tr("Save forward stream audio");
|
|
|
|
break;
|
|
|
|
case dir_reverse_:
|
|
|
|
caption = tr("Save reverse stream audio");
|
|
|
|
break;
|
|
|
|
case dir_both_:
|
|
|
|
default:
|
|
|
|
caption = tr("Save audio");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ext_filter = tr("Sun Audio (*.au)");
|
|
|
|
if (direction != dir_both_) {
|
|
|
|
ext_filter.append(tr(";;Raw (*.raw)"));
|
|
|
|
}
|
|
|
|
QString sel_filter;
|
|
|
|
QString file_path = QFileDialog::getSaveFileName(
|
|
|
|
this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
|
|
|
|
ext_filter, &sel_filter);
|
|
|
|
|
|
|
|
if (file_path.isEmpty()) return;
|
|
|
|
|
|
|
|
int save_format = save_audio_none_;
|
|
|
|
if (file_path.endsWith(".au")) {
|
|
|
|
save_format = save_audio_au_;
|
|
|
|
} else if (file_path.endsWith(".raw")) {
|
|
|
|
save_format = save_audio_raw_;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (save_format == save_audio_none_) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile save_file(file_path);
|
|
|
|
gint16 sample;
|
|
|
|
gchar pd[4];
|
|
|
|
gboolean stop_flag = FALSE;
|
|
|
|
size_t nchars;
|
|
|
|
|
|
|
|
save_file.open(QIODevice::WriteOnly);
|
|
|
|
fwd_tempfile_->seek(0);
|
|
|
|
rev_tempfile_->seek(0);
|
|
|
|
|
|
|
|
if (save_file.error() != QFile::NoError) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
|
|
|
|
ui->progressFrame->showProgress(true, true, &stop_flag);
|
|
|
|
|
|
|
|
if (save_format == save_audio_au_) { /* au format */
|
|
|
|
/* First we write the .au header. XXX Hope this is endian independent */
|
|
|
|
/* the magic word 0x2e736e64 == .snd */
|
|
|
|
phton32(pd, 0x2e736e64);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
/* header offset == 24 bytes */
|
|
|
|
phton32(pd, 24);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
/* total length; it is permitted to set this to 0xffffffff */
|
|
|
|
phton32(pd, 0xffffffff);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
/* encoding format == 16-bit linear PCM */
|
|
|
|
phton32(pd, 3);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
/* sample rate == 8000 Hz */
|
|
|
|
phton32(pd, 8000);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
/* channels == 1 */
|
|
|
|
phton32(pd, 1);
|
|
|
|
nchars = save_file.write((const char *)pd, 4);
|
|
|
|
if (nchars != 4)
|
|
|
|
goto copy_file_err;
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
/* Only forward direction */
|
|
|
|
case dir_forward_:
|
|
|
|
{
|
|
|
|
char f_rawvalue;
|
|
|
|
while (fwd_tempfile_->getChar(&f_rawvalue)) {
|
|
|
|
if (stop_flag) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ui->progressFrame->setValue(fwd_tempfile_->pos() * 100 / fwd_tempfile_->size());
|
|
|
|
|
|
|
|
if (fwd_statinfo_.pt == PT_PCMU) {
|
|
|
|
sample = ulaw2linear((unsigned char)f_rawvalue);
|
|
|
|
phton16(pd, sample);
|
|
|
|
} else if (fwd_statinfo_.pt == PT_PCMA) {
|
|
|
|
sample = alaw2linear((unsigned char)f_rawvalue);
|
|
|
|
phton16(pd, sample);
|
|
|
|
} else {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
nchars = save_file.write((const char *)pd, 2);
|
|
|
|
if (nchars < 2) {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Only reverse direction */
|
|
|
|
case dir_reverse_:
|
|
|
|
{
|
|
|
|
char r_rawvalue;
|
|
|
|
while (rev_tempfile_->getChar(&r_rawvalue)) {
|
|
|
|
if (stop_flag) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ui->progressFrame->setValue(rev_tempfile_->pos() * 100 / rev_tempfile_->size());
|
|
|
|
|
|
|
|
if (rev_statinfo_.pt == PT_PCMU) {
|
|
|
|
sample = ulaw2linear((unsigned char)r_rawvalue);
|
|
|
|
phton16(pd, sample);
|
|
|
|
} else if (rev_statinfo_.pt == PT_PCMA) {
|
|
|
|
sample = alaw2linear((unsigned char)r_rawvalue);
|
|
|
|
phton16(pd, sample);
|
|
|
|
} else {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
nchars = save_file.write((const char *)pd, 2);
|
|
|
|
if (nchars < 2) {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Both directions */
|
|
|
|
case dir_both_:
|
|
|
|
{
|
|
|
|
char f_rawvalue, r_rawvalue;
|
|
|
|
guint32 f_write_silence = 0;
|
|
|
|
guint32 r_write_silence = 0;
|
|
|
|
/* since conversation in one way can start later than in the other one,
|
|
|
|
* we have to write some silence information for one channel */
|
|
|
|
if (fwd_statinfo_.start_time > rev_statinfo_.start_time) {
|
|
|
|
f_write_silence = (guint32)
|
|
|
|
((fwd_statinfo_.start_time - rev_statinfo_.start_time)
|
|
|
|
* (8000/1000));
|
|
|
|
} else if (fwd_statinfo_.start_time < rev_statinfo_.start_time) {
|
|
|
|
r_write_silence = (guint32)
|
|
|
|
((rev_statinfo_.start_time - fwd_statinfo_.start_time)
|
|
|
|
* (8000/1000));
|
|
|
|
}
|
|
|
|
for (;;) {
|
|
|
|
if (stop_flag) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
int fwd_pct = fwd_tempfile_->pos() * 100 / fwd_tempfile_->size();
|
|
|
|
int rev_pct = rev_tempfile_->pos() * 100 / rev_tempfile_->size();
|
|
|
|
ui->progressFrame->setValue(qMin(fwd_pct, rev_pct));
|
|
|
|
|
|
|
|
if (f_write_silence > 0) {
|
|
|
|
rev_tempfile_->getChar(&r_rawvalue);
|
|
|
|
switch (fwd_statinfo_.reg_pt) {
|
|
|
|
case PT_PCMU:
|
|
|
|
f_rawvalue = silence_pcmu_;
|
|
|
|
break;
|
|
|
|
case PT_PCMA:
|
|
|
|
f_rawvalue = silence_pcma_;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
f_rawvalue = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
f_write_silence--;
|
|
|
|
} else if (r_write_silence > 0) {
|
|
|
|
fwd_tempfile_->getChar(&f_rawvalue);
|
|
|
|
switch (rev_statinfo_.reg_pt) {
|
|
|
|
case PT_PCMU:
|
|
|
|
r_rawvalue = silence_pcmu_;
|
|
|
|
break;
|
|
|
|
case PT_PCMA:
|
|
|
|
r_rawvalue = silence_pcma_;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
r_rawvalue = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
r_write_silence--;
|
|
|
|
} else {
|
|
|
|
fwd_tempfile_->getChar(&f_rawvalue);
|
|
|
|
rev_tempfile_->getChar(&r_rawvalue);
|
|
|
|
}
|
|
|
|
if (fwd_tempfile_->atEnd() && rev_tempfile_->atEnd())
|
|
|
|
break;
|
|
|
|
if ((fwd_statinfo_.pt == PT_PCMU)
|
|
|
|
&& (rev_statinfo_.pt == PT_PCMU)) {
|
|
|
|
sample = (ulaw2linear((unsigned char)r_rawvalue)
|
|
|
|
+ ulaw2linear((unsigned char)f_rawvalue)) / 2;
|
|
|
|
phton16(pd, sample);
|
|
|
|
}
|
|
|
|
else if ((fwd_statinfo_.pt == PT_PCMA)
|
|
|
|
&& (rev_statinfo_.pt == PT_PCMA)) {
|
|
|
|
sample = (alaw2linear((unsigned char)r_rawvalue)
|
|
|
|
+ alaw2linear((unsigned char)f_rawvalue)) / 2;
|
|
|
|
phton16(pd, sample);
|
|
|
|
} else {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
nchars = save_file.write((const char *)pd, 2);
|
|
|
|
if (nchars < 2) {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (save_format == save_audio_raw_) { /* raw format */
|
|
|
|
QFile *tempfile;
|
|
|
|
int progress_pct;
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
/* Only forward direction */
|
|
|
|
case dir_forward_: {
|
|
|
|
progress_pct = fwd_tempfile_->pos() * 100 / fwd_tempfile_->size();
|
|
|
|
tempfile = fwd_tempfile_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* only reversed direction */
|
|
|
|
case dir_reverse_: {
|
|
|
|
progress_pct = rev_tempfile_->pos() * 100 / rev_tempfile_->size();
|
|
|
|
tempfile = rev_tempfile_;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int chunk_size = 65536;
|
|
|
|
/* XXX how do you just copy the file? */
|
|
|
|
while (chunk_size > 0) {
|
|
|
|
if (stop_flag)
|
|
|
|
break;
|
|
|
|
QByteArray bytes = tempfile->read(chunk_size);
|
|
|
|
ui->progressFrame->setValue(progress_pct);
|
|
|
|
|
|
|
|
if (!save_file.write(bytes)) {
|
|
|
|
goto copy_file_err;
|
|
|
|
}
|
|
|
|
chunk_size = bytes.length();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
copy_file_err:
|
|
|
|
ui->progressFrame->hide();
|
|
|
|
updateWidgets();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX The GTK+ UI saves the length and timestamp.
|
|
|
|
void Iax2AnalysisDialog::saveCsv(Iax2AnalysisDialog::StreamDirection direction)
|
|
|
|
{
|
|
|
|
QString caption;
|
|
|
|
|
|
|
|
switch (direction) {
|
|
|
|
case dir_forward_:
|
|
|
|
caption = tr("Save forward stream CSV");
|
|
|
|
break;
|
|
|
|
case dir_reverse_:
|
|
|
|
caption = tr("Save reverse stream CSV");
|
|
|
|
break;
|
|
|
|
case dir_both_:
|
|
|
|
default:
|
|
|
|
caption = tr("Save CSV");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString file_path = QFileDialog::getSaveFileName(
|
|
|
|
this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
|
|
|
|
tr("Comma-separated values (*.csv)"));
|
|
|
|
|
|
|
|
if (file_path.isEmpty()) return;
|
|
|
|
|
|
|
|
QFile save_file(file_path);
|
|
|
|
save_file.open(QFile::WriteOnly);
|
|
|
|
|
|
|
|
if (direction == dir_forward_ || direction == dir_both_) {
|
|
|
|
save_file.write("Forward\n");
|
|
|
|
|
|
|
|
for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
|
|
|
|
if (ti->type() != iax2_analysis_type_) continue;
|
|
|
|
Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
|
|
|
|
QStringList values;
|
|
|
|
foreach (QVariant v, ra_ti->rowData()) {
|
|
|
|
if (!v.isValid()) {
|
|
|
|
values << "\"\"";
|
|
|
|
} else if ((int) v.type() == (int) QMetaType::QString) {
|
|
|
|
values << QString("\"%1\"").arg(v.toString());
|
|
|
|
} else {
|
|
|
|
values << v.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
save_file.write(values.join(",").toUtf8());
|
|
|
|
save_file.write("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (direction == dir_both_) {
|
|
|
|
save_file.write("\n");
|
|
|
|
}
|
|
|
|
if (direction == dir_reverse_ || direction == dir_both_) {
|
|
|
|
save_file.write("Reverse\n");
|
|
|
|
|
|
|
|
for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
|
|
|
|
if (ti->type() != iax2_analysis_type_) continue;
|
|
|
|
Iax2AnalysisTreeWidgetItem *ra_ti = dynamic_cast<Iax2AnalysisTreeWidgetItem *>((Iax2AnalysisTreeWidgetItem *)ti);
|
|
|
|
QStringList values;
|
|
|
|
foreach (QVariant v, ra_ti->rowData()) {
|
|
|
|
if (!v.isValid()) {
|
|
|
|
values << "\"\"";
|
|
|
|
} else if ((int) v.type() == (int) QMetaType::QString) {
|
|
|
|
values << QString("\"%1\"").arg(v.toString());
|
|
|
|
} else {
|
|
|
|
values << v.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
save_file.write(values.join(",").toUtf8());
|
|
|
|
save_file.write("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Iax2AnalysisDialog::eventFilter(QObject *, QEvent *event)
|
|
|
|
{
|
|
|
|
if (event->type() != QEvent::KeyPress) return false;
|
|
|
|
|
|
|
|
QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
|
|
|
|
|
|
|
|
switch(kevt->key()) {
|
|
|
|
case Qt::Key_G:
|
|
|
|
on_actionGoToPacket_triggered();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_N:
|
|
|
|
on_actionNextProblem_triggered();
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::graphClicked(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
updateWidgets();
|
|
|
|
if (event->button() == Qt::RightButton) {
|
|
|
|
graph_ctx_menu_.exec(event->globalPos());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Iax2AnalysisDialog::showStreamMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
|
|
|
|
if (!cur_tree) return;
|
|
|
|
|
|
|
|
updateWidgets();
|
|
|
|
stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Editor modelines
|
|
|
|
*
|
|
|
|
* Local Variables:
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* tab-width: 8
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
* End:
|
|
|
|
*
|
|
|
|
* ex: set shiftwidth=4 tabstop=8 expandtab:
|
|
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
|
|
*/
|