aboutsummaryrefslogtreecommitdiffstats
path: root/ui/qt/rtp_analysis_dialog.cpp
diff options
context:
space:
mode:
authorGerald Combs <gerald@wireshark.org>2015-02-02 09:01:29 -0800
committerGerald Combs <gerald@wireshark.org>2015-07-16 00:30:14 +0000
commitd2acb04b4cadd90bf09bdc4959e6bffb25dcfd38 (patch)
tree44079a225b9df1ca5dbb72d58b55ff2112c15cc4 /ui/qt/rtp_analysis_dialog.cpp
parent2e215db532f408697ba16f2d83524a64cc9e6424 (diff)
Add the RTP Stream Analysis dialog.
Combine the GTK+ RTP Stream Analysis and RTP Graph Analysis dialogs into one. Yell at the user less. Disable the Analyze RTP Stream menu item if we don't have an RTP stream selected. There are a *lot* of moving parts in this dialog. I've tested with the few RTP captures I have but it's by no means complete. "To do" items are listed at the top of rtp_analysis.cpp. Change-Id: Id503977f069bebc46cc68bc749f0c9cbf4d37bf6 Reviewed-on: https://code.wireshark.org/review/9650 Petri-Dish: Gerald Combs <gerald@wireshark.org> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'ui/qt/rtp_analysis_dialog.cpp')
-rw-r--r--ui/qt/rtp_analysis_dialog.cpp1538
1 files changed, 1538 insertions, 0 deletions
diff --git a/ui/qt/rtp_analysis_dialog.cpp b/ui/qt/rtp_analysis_dialog.cpp
new file mode 100644
index 0000000000..e1246cd97e
--- /dev/null
+++ b/ui/qt/rtp_analysis_dialog.cpp
@@ -0,0 +1,1538 @@
+/* rtp_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 "rtp_analysis_dialog.h"
+#include "ui_rtp_analysis_dialog.h"
+
+#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-rtp.h"
+
+#include "ui/help_url.h"
+#include "ui/utf8_entities.h"
+
+#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_,
+ sequence_col_,
+ delta_col_,
+ jitter_col_,
+ skew_col_,
+ bandwidth_col_,
+ marker_col_,
+ status_col_
+};
+
+const QRgb color_cn_ = 0xbfbfff;
+const QRgb color_rtp_warn_ = 0xffdbbf;
+const QRgb color_pt_event_ = 0xefffff;
+
+enum { rtp_analysis_type_ = 1000 };
+class RtpAnalysisTreeWidgetItem : public QTreeWidgetItem
+{
+public:
+ RtpAnalysisTreeWidgetItem(QTreeWidget *tree, tap_rtp_stat_t *statinfo, packet_info *pinfo, const struct _rtp_info *rtpinfo) :
+ QTreeWidgetItem(tree, rtp_analysis_type_)
+ {
+ frame_num_ = pinfo->fd->num;
+ sequence_num_ = rtpinfo->info_seq_num;
+ pkt_len_ = pinfo->fd->pkt_len;
+ flags_ = statinfo->flags;
+ if (flags_ & STAT_FLAG_FIRST) {
+ delta_ = 0.0;
+ jitter_ = 0.0;
+ skew_ = 0.0;
+ } else {
+ delta_ = statinfo->delta;
+ jitter_ = statinfo->jitter;
+ skew_ = statinfo->skew;
+ }
+ bandwidth_ = statinfo->bandwidth;
+ marker_ = rtpinfo->info_marker_set ? true : false;
+ ok_ = false;
+
+ QColor bg_color = QColor();
+ QString status;
+
+ if (statinfo->pt == PT_CN) {
+ status = "Comfort noise (PT=13, RFC 3389)";
+ bg_color = color_cn_;
+ } else if (statinfo->pt == PT_CN_OLD) {
+ status = "Comfort noise (PT=19, reserved)";
+ bg_color = color_cn_;
+ } else if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
+ status = "Wrong sequence number";
+ bg_color = ColorUtils::expert_color_error;
+ } else if (statinfo->flags & STAT_FLAG_DUP_PKT) {
+ status = "Suspected duplicate (MAC address) only delta time calculated";
+ bg_color = color_rtp_warn_;
+ } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
+ status = QString("Payload changed to PT=%u").arg(statinfo->pt);
+ if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
+ status.append(" telephone/event");
+ }
+ bg_color = color_rtp_warn_;
+ } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
+ status = "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 = "Marker missing?";
+ bg_color = color_rtp_warn_;
+ } else if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
+ status = QString("PT=%u telephone/event").arg(statinfo->pt);
+ /* XXX add color? */
+ bg_color = color_pt_event_;
+ } 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(sequence_col_, QString::number(sequence_num_));
+ setText(delta_col_, QString::number(delta_, 'f', 2));
+ setText(jitter_col_, QString::number(jitter_, 'f', 2));
+ setText(skew_col_, QString::number(skew_, 'f', 2));
+ setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
+ if (marker_) {
+ setText(marker_col_, UTF8_BULLET);
+ }
+ setText(status_col_, status);
+
+ setTextAlignment(packet_col_, Qt::AlignRight);
+ setTextAlignment(sequence_col_, Qt::AlignRight);
+ setTextAlignment(delta_col_, Qt::AlignRight);
+ setTextAlignment(jitter_col_, Qt::AlignRight);
+ setTextAlignment(skew_col_, Qt::AlignRight);
+ setTextAlignment(bandwidth_col_, Qt::AlignRight);
+ setTextAlignment(marker_col_, Qt::AlignCenter);
+
+ 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 marker_str;
+ QString status_str = ok_ ? "OK" : text(status_col_);
+
+ if (marker_) marker_str = "SET";
+
+ return QList<QVariant>()
+ << frame_num_ << sequence_num_ << delta_ << jitter_ << skew_ << bandwidth_
+ << marker_str << status_str;
+ }
+
+ bool operator< (const QTreeWidgetItem &other) const
+ {
+ if (other.type() != rtp_analysis_type_) return QTreeWidgetItem::operator< (other);
+ const RtpAnalysisTreeWidgetItem *other_row = static_cast<const RtpAnalysisTreeWidgetItem *>(&other);
+
+ switch (treeWidget()->sortColumn()) {
+ case (packet_col_):
+ return frame_num_ < other_row->frame_num_;
+ break;
+ case (sequence_col_):
+ return sequence_num_ < other_row->sequence_num_;
+ break;
+ case (delta_col_):
+ return delta_ < other_row->delta_;
+ break;
+ case (jitter_col_):
+ return jitter_ < other_row->jitter_;
+ break;
+ case (skew_col_):
+ return skew_ < other_row->skew_;
+ break;
+ case (bandwidth_col_):
+ return bandwidth_ < other_row->bandwidth_;
+ break;
+ default:
+ break;
+ }
+
+ // Fall back to string comparison
+ return QTreeWidgetItem::operator <(other);
+ }
+private:
+ guint32 frame_num_;
+ guint32 sequence_num_;
+ guint32 pkt_len_;
+ guint32 flags_;
+ double delta_;
+ double jitter_;
+ double skew_;
+ double bandwidth_;
+ bool marker_;
+ bool ok_;
+};
+
+enum {
+ fwd_jitter_graph_,
+ fwd_diff_graph_,
+ fwd_delta_graph_,
+ rev_jitter_graph_,
+ rev_diff_graph_,
+ rev_delta_graph_,
+ num_graphs_
+};
+
+RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf),
+ ui(new Ui::RtpAnalysisDialog),
+ port_src_fwd_(0),
+ port_dst_fwd_(0),
+ ssrc_fwd_(0),
+ port_src_rev_(0),
+ port_dst_rev_(0),
+ ssrc_rev_(0)
+{
+ ui->setupUi(this);
+ setWindowSubtitle(tr("RTP Stream Analysis"));
+
+ // XXX Use recent settings instead
+ resize(parent.width() * 4 / 5, parent.height() * 4 / 5);
+
+ 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->fDeltaCheckBox
+ << ui->rJitterCheckBox << ui->rDiffCheckBox << ui->rDeltaCheckBox;
+
+ for (int i = 0; i < num_graphs_; i++) {
+ QCPGraph *graph = ui->streamGraph->addGraph();
+ graph->setPen(QPen(ColorUtils::graph_colors_[i]));
+ graph->setName(graph_cbs[i]->text());
+ graphs_ << graph;
+ graph_cbs[i]->setChecked(true);
+ graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graph_colors_[i], QPalette::Text));
+ }
+ 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_rtp_f").arg(QDir::tempPath());
+ fwd_tempfile_ = new QTemporaryFile(tempname, this);
+ fwd_tempfile_->open();
+ tempname = QString("%1/wireshark_rtp_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);
+
+ const gchar *filter_text = "rtp && rtp.version && rtp.ssrc && (ip || ipv6)";
+ dfilter_t *sfcode;
+ gchar *err_msg;
+
+ if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
+ QMessageBox::warning(this, tr("No RTP packets found"), QString("%1").arg(err_msg));
+ g_free(err_msg);
+ close();
+ }
+
+ if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close();
+
+ 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);
+ err_str_ = tr("Please select an RTP packet");
+ updateWidgets();
+ return;
+ }
+
+ dfilter_free(sfcode);
+
+ /* OK, it is an RTP frame. Let's get the IP and port values */
+ COPY_ADDRESS(&(src_fwd_), &(edt.pi.src));
+ COPY_ADDRESS(&(dst_fwd_), &(edt.pi.dst));
+ port_src_fwd_ = edt.pi.srcport;
+ port_dst_fwd_ = edt.pi.destport;
+
+ /* assume the inverse ip/port combination for the reverse direction */
+ COPY_ADDRESS(&(src_rev_), &(edt.pi.dst));
+ COPY_ADDRESS(&(dst_rev_), &(edt.pi.src));
+ port_src_rev_ = edt.pi.destport;
+ port_dst_rev_ = edt.pi.srcport;
+
+ /* Check if it is RTP Version 2 */
+ unsigned int version_fwd;
+ bool ok;
+ version_fwd = getIntFromProtoTree(edt.tree, "rtp", "rtp.version", &ok);
+ if (!ok || version_fwd != 2) {
+ err_str_ = tr("RTP version %1 found. Only version 2 is supported.").arg(version_fwd);
+ updateWidgets();
+ return;
+ }
+
+ /* now we need the SSRC value of the current frame */
+ ssrc_fwd_ = getIntFromProtoTree(edt.tree, "rtp", "rtp.ssrc", &ok);
+ if (!ok) {
+ err_str_ = tr("SSRC value not found.");
+ updateWidgets();
+ return;
+ }
+
+ /* Register the tap listener */
+ memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
+ 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);
+
+ 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);
+ if (ADDRESSES_EQUAL(&(strinfo->src_addr), &(src_fwd_))
+ && (strinfo->src_port == port_src_fwd_)
+ && (ADDRESSES_EQUAL(&(strinfo->dest_addr), &(dst_fwd_)))
+ && (strinfo->dest_port == port_dst_fwd_))
+ {
+ ++num_streams_;
+ filtered_list = g_list_prepend(filtered_list, strinfo);
+ }
+
+ if (ADDRESSES_EQUAL(&(strinfo->src_addr), &(src_rev_))
+ && (strinfo->src_port == port_src_rev_)
+ && (ADDRESSES_EQUAL(&(strinfo->dest_addr), &(dst_rev_)))
+ && (strinfo->dest_port == port_dst_rev_))
+ {
+ ++num_streams_;
+ filtered_list = g_list_append(filtered_list, strinfo);
+ if (ssrc_rev_ == 0)
+ ssrc_rev_ = strinfo->ssrc;
+ }
+ }
+
+ if (num_streams_ < 1) {
+ err_str_ = tr("No streams found.");
+ }
+
+ 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();
+
+ register_tap_listener("rtp", this, NULL, 0, tapReset, tapPacket, tapDraw);
+
+ cap_file_.retapPackets();
+ remove_tap_listener(this);
+
+ updateStatistics();
+}
+
+RtpAnalysisDialog::~RtpAnalysisDialog()
+{
+ delete ui;
+// remove_tap_listener_rtp_stream(&tapinfo_);
+ delete fwd_tempfile_;
+ delete rev_tempfile_;
+}
+
+// XXX Should we do this in WiresharkDialog?
+void RtpAnalysisDialog::reject()
+{
+ // We need to make sure our temporary files are closed.
+ deleteLater();
+ WiresharkDialog::reject();
+}
+
+void RtpAnalysisDialog::updateWidgets()
+{
+ bool enable_tab = false;
+ QString hint = err_str_;
+
+ if (hint.isEmpty()) {
+ enable_tab = true;
+ hint = tr("%1 streams found.").arg(num_streams_);
+ }
+
+ 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"));
+ }
+
+ bool enable_save_fwd_audio = fwd_tempfile_->isOpen();
+ bool enable_save_rev_audio = rev_tempfile_->isOpen();
+ 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);
+}
+
+void RtpAnalysisDialog::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() != rtp_analysis_type_) return;
+
+ RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
+ emit goToPacket(ra_ti->frameNum());
+}
+
+void RtpAnalysisDialog::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() != rtp_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);
+ RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)test_ti);
+ if (!ra_ti->frameStatus()) {
+ cur_tree->setCurrentItem(ra_ti);
+ break;
+ }
+
+ test_ti = cur_tree->itemBelow(test_ti);
+ }
+}
+
+void RtpAnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_fDeltaCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(fwd_delta_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_rDeltaCheckBox_toggled(bool checked)
+{
+ ui->streamGraph->graph(rev_delta_graph_)->setVisible(checked);
+ updateGraph();
+}
+
+void RtpAnalysisDialog::on_actionSaveAudio_triggered()
+{
+ saveAudio(dir_both_);
+}
+
+void RtpAnalysisDialog::on_actionSaveForwardAudio_triggered()
+{
+ saveAudio(dir_forward_);
+}
+
+void RtpAnalysisDialog::on_actionSaveReverseAudio_triggered()
+{
+ saveAudio(dir_reverse_);
+}
+
+void RtpAnalysisDialog::on_actionSaveCsv_triggered()
+{
+ saveCsv(dir_both_);
+}
+
+void RtpAnalysisDialog::on_actionSaveForwardCsv_triggered()
+{
+ saveCsv(dir_forward_);
+}
+
+void RtpAnalysisDialog::on_actionSaveReverseCsv_triggered()
+{
+ saveCsv(dir_reverse_);
+}
+
+void RtpAnalysisDialog::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 RtpAnalysisDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG);
+}
+
+void RtpAnalysisDialog::tapReset(void *tapinfo_ptr)
+{
+ RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
+ if (!rtp_analysis_dialog) return;
+
+ rtp_analysis_dialog->resetStatistics();
+}
+
+gboolean RtpAnalysisDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
+{
+ RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
+ if (!rtp_analysis_dialog) return FALSE;
+
+ const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
+ if (!rtpinfo) return FALSE;
+
+ /* we ignore packets that are not displayed */
+ if (pinfo->fd->flags.passed_dfilter == 0)
+ return FALSE;
+ /* also ignore RTP Version != 2 */
+ else if (rtpinfo->info_version != 2)
+ return FALSE;
+ /* is it the forward direction? */
+ else if (rtp_analysis_dialog->ssrc_fwd_ == rtpinfo->info_sync_src
+ && (CMP_ADDRESS(&(rtp_analysis_dialog->src_fwd_), &(pinfo->src)) == 0)
+ && (rtp_analysis_dialog->port_src_fwd_ == pinfo->srcport)
+ && (CMP_ADDRESS(&(rtp_analysis_dialog->dst_fwd_), &(pinfo->dst)) == 0)
+ && (rtp_analysis_dialog->port_dst_fwd_ == pinfo->destport)) {
+
+ rtp_analysis_dialog->addPacket(true, pinfo, rtpinfo);
+ }
+ /* is it the reversed direction? */
+ else if (rtp_analysis_dialog->ssrc_rev_ == rtpinfo->info_sync_src
+ && (CMP_ADDRESS(&(rtp_analysis_dialog->src_rev_), &(pinfo->src)) == 0)
+ && (rtp_analysis_dialog->port_src_rev_ == pinfo->srcport)
+ && (CMP_ADDRESS(&(rtp_analysis_dialog->dst_rev_), &(pinfo->dst)) == 0)
+ && (rtp_analysis_dialog->port_dst_rev_ == pinfo->destport)) {
+
+ rtp_analysis_dialog->addPacket(false, pinfo, rtpinfo);
+ }
+ return FALSE;
+}
+
+void RtpAnalysisDialog::tapDraw(void *tapinfo_ptr)
+{
+ RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
+ if (!rtp_analysis_dialog) return;
+ rtp_analysis_dialog->updateStatistics();
+}
+
+void RtpAnalysisDialog::resetStatistics()
+{
+ memset(&fwd_statinfo_, 0, sizeof(tap_rtp_stat_t));
+ memset(&rev_statinfo_, 0, sizeof(tap_rtp_stat_t));
+
+ 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();
+ fwd_delta_vals_.clear();
+ rev_time_vals_.clear();
+ rev_jitter_vals_.clear();
+ rev_diff_vals_.clear();
+ rev_delta_vals_.clear();
+
+ fwd_tempfile_->resize(0);
+ rev_tempfile_->resize(0);
+}
+
+void RtpAnalysisDialog::addPacket(bool forward, packet_info *pinfo, const _rtp_info *rtpinfo)
+{
+ /* add this RTP for future listening using the RTP Player*/
+// add_rtp_packet(rtpinfo, pinfo);
+
+ if (forward) {
+ rtp_packet_analyse(&fwd_statinfo_, pinfo, rtpinfo);
+ new RtpAnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_, pinfo, rtpinfo);
+
+ fwd_time_vals_.append((fwd_statinfo_.time - fwd_statinfo_.start_time) / 1000);
+ fwd_jitter_vals_.append(fwd_statinfo_.jitter * 1000);
+ fwd_diff_vals_.append(fwd_statinfo_.diff * 1000);
+ fwd_delta_vals_.append(fwd_statinfo_.delta * 1000);
+
+ savePayload(fwd_tempfile_, &fwd_statinfo_, pinfo, rtpinfo);
+ } else {
+ rtp_packet_analyse(&rev_statinfo_, pinfo, rtpinfo);
+ new RtpAnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_, pinfo, rtpinfo);
+
+ rev_time_vals_.append((rev_statinfo_.time - rev_statinfo_.start_time) / 1000);
+ rev_jitter_vals_.append(rev_statinfo_.jitter * 1000);
+ rev_diff_vals_.append(rev_statinfo_.diff * 1000);
+ rev_delta_vals_.append(rev_statinfo_.delta * 1000);
+
+ savePayload(rev_tempfile_, &rev_statinfo_, pinfo, rtpinfo);
+ }
+
+}
+
+// rtp_analysis.c:rtp_packet_save_payload
+const unsigned int max_silence_ticks_ = 1000000;
+const guint8 silence_pcmu_ = 0xff;
+const guint8 silence_pcma_ = 0x55;
+void RtpAnalysisDialog::savePayload(QTemporaryFile *tmpfile, tap_rtp_stat_t *statinfo, packet_info *pinfo, const _rtp_info *rtpinfo)
+{
+ /* 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 or
+ * if the RTP dissector thinks there is some information missing
+ */
+ if ((pinfo->fd->pkt_len != pinfo->fd->cap_len)
+ && (!rtpinfo->info_all_data_present)) {
+ tmpfile->close();
+ err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
+ return;
+ }
+
+ /* If padding bit is set but the padding count is bigger
+ * then the whole RTP data - error with padding count
+ */
+ if ( (rtpinfo->info_padding_set != FALSE)
+ && (rtpinfo->info_padding_count > rtpinfo->info_payload_len) ) {
+ tmpfile->close();
+ err_str_ = tr("Can't save in a file: RTP data with padding.");
+ return;
+ }
+
+ /* Do we need to insert some silence? */
+ if ((rtpinfo->info_marker_set)
+ && ! (statinfo->flags & STAT_FLAG_FIRST)
+ && ! (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP)
+ && (statinfo->delta_timestamp > (rtpinfo->info_payload_len - rtpinfo->info_padding_count)) ) {
+ /* the amount of silence should be the difference between
+ * the last timestamp and the current one minus x
+ * x should equal the amount of information in the last frame
+ * XXX not done yet */
+ for (unsigned int i = 0;
+ (i < (statinfo->delta_timestamp - rtpinfo->info_payload_len - rtpinfo->info_padding_count))
+ && (i < max_silence_ticks_);
+ i++) {
+ guint8 tmp;
+ size_t nchars;
+
+ switch (statinfo->reg_pt) {
+ case PT_PCMU:
+ tmp = silence_pcmu_;
+ break;
+ case PT_PCMA:
+ tmp = silence_pcma_;
+ break;
+ default:
+ tmp = 0;
+ break;
+ }
+ nchars = tmpfile->write((char *)&tmp, 1);
+ if (nchars != 1) {
+ /* Write error or short write */
+ tmpfile->close();
+ err_str_ = tr("Can't save in a file: File I/O problem.");
+ return;
+ }
+ }
+ }
+
+
+ if ((rtpinfo->info_payload_type == PT_CN)
+ || (rtpinfo->info_payload_type == PT_CN_OLD)) {
+ } else { /* All other payloads */
+ const char *data;
+ size_t nchars;
+
+ if (!rtpinfo->info_all_data_present) {
+ /* Not all the data was captured. */
+ tmpfile->close();
+ err_str_ = tr("Can't save in a file: Not all data in all packets was captured.");
+ return;
+ }
+
+ /* We put the pointer at the beginning of the RTP
+ * payload, that is, at the beginning of the RTP data
+ * plus the offset of the payload from the beginning
+ * of the RTP data */
+ data = (const char *) rtpinfo->info_data + rtpinfo->info_payload_offset;
+ nchars = tmpfile->write(data, rtpinfo->info_payload_len - rtpinfo->info_padding_count);
+ if (nchars != (rtpinfo->info_payload_len - rtpinfo->info_padding_count)) {
+ /* Write error or short write */
+ err_str_ = tr("Can't save in a file: File I/O problem.");
+ tmpfile->close();
+ return;
+ }
+ return;
+ }
+ return;
+}
+
+void RtpAnalysisDialog::updateStatistics()
+{
+ unsigned int f_clock_rate = fwd_statinfo_.clock_rate;
+ unsigned int r_clock_rate = rev_statinfo_.clock_rate;
+ unsigned int f_expected = (fwd_statinfo_.stop_seq_nr + fwd_statinfo_.cycles*65536)
+ - fwd_statinfo_.start_seq_nr + 1;
+ unsigned int r_expected = (rev_statinfo_.stop_seq_nr + rev_statinfo_.cycles*65536)
+ - rev_statinfo_.start_seq_nr + 1;
+ unsigned int f_total_nr = fwd_statinfo_.total_nr;
+ unsigned int r_total_nr = rev_statinfo_.total_nr;
+ int f_lost = f_expected - f_total_nr;
+ int r_lost = r_expected - r_total_nr;
+ double f_sumt = fwd_statinfo_.sumt;
+ double f_sumTS = fwd_statinfo_.sumTS;
+ double f_sumt2 = fwd_statinfo_.sumt2;
+ double f_sumtTS = fwd_statinfo_.sumtTS;
+ double r_sumt = rev_statinfo_.sumt;
+ double r_sumTS = rev_statinfo_.sumTS;
+ double r_sumt2 = rev_statinfo_.sumt2;
+ double r_sumtTS = rev_statinfo_.sumtTS;
+ double f_perc, r_perc;
+ double f_clock_drift = 1.0;
+ double r_clock_drift = 1.0;
+ double f_duration = fwd_statinfo_.time - fwd_statinfo_.start_time;
+ double r_duration = rev_statinfo_.time - rev_statinfo_.start_time;
+
+ if (f_clock_rate == 0) {
+ f_clock_rate = 1;
+ }
+
+ if (r_clock_rate == 0) {
+ r_clock_rate = 1;
+ }
+
+ 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;
+ }
+
+ if ((f_total_nr >0) && (f_sumt2 > 0)) {
+ f_clock_drift = (f_total_nr * f_sumtTS - f_sumt * f_sumTS) / (f_total_nr * f_sumt2 - f_sumt * f_sumt);
+ }
+ if ((r_total_nr >0) && (r_sumt2 > 0)) {
+ r_clock_drift = (r_total_nr * r_sumtTS - r_sumt * r_sumTS) / (r_total_nr * r_sumt2 - r_sumt * r_sumt);
+ }
+
+ 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\">SSRC</th><td>0x%1</tr>")
+ .arg(ssrc_fwd_, 8, 16, QChar('0'));
+ 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\">Max Skew</th><td>%1 ms</tr>")
+ .arg(fwd_statinfo_.max_skew, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">RTP Packets</th><td>%1</tr>")
+ .arg(f_total_nr);
+ 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);
+ stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
+ .arg(f_duration / 1000.0, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Clock Drift</th><td>%1 ms</tr>")
+ .arg(f_duration * (f_clock_drift - 1.0), 0, 'f', 0);
+ stats_tables += QString("<tr><th align=\"left\">Freq Drift</th><td>%1 Hz (%2 %)</tr>") // XXX Terminology?
+ .arg(f_clock_drift * f_clock_rate, 0, 'f', 0).arg(100.0 * (f_clock_drift - 1.0), 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\">SSRC</th><td>0x%1</tr>")
+ .arg(ssrc_fwd_, 8, 16, QChar('0'));
+ 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\">Max Skew</th><td>%1 ms</tr>")
+ .arg(rev_statinfo_.max_skew, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">RTP Packets</th><td>%1</tr>")
+ .arg(r_total_nr);
+ 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);
+ stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</tr>")
+ .arg(r_duration / 1000.0, 0, 'f', 2);
+ stats_tables += QString("<tr><th align=\"left\">Clock Drift</th><td>%1 ms</tr>")
+ .arg(r_duration * (r_clock_drift - 1.0), 0, 'f', 0);
+ stats_tables += QString("<tr><th align=\"left\">Freq Drift</th><td>%1 Hz (%2 %)</tr>") // XXX Terminology?
+ .arg(r_clock_drift * r_clock_rate, 0, 'f', 0).arg(100.0 * (r_clock_drift - 1.0), 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_[fwd_delta_graph_]->setData(fwd_time_vals_, fwd_delta_vals_);
+ graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
+ graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
+ graphs_[rev_delta_graph_]->setData(rev_time_vals_, rev_delta_vals_);
+
+ updateGraph();
+
+ updateWidgets();
+}
+
+void RtpAnalysisDialog::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();
+}
+
+// rtp_analysis.c:copy_file
+enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
+void RtpAnalysisDialog::saveAudio(RtpAnalysisDialog::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];
+// progdlg_t *progbar;
+// guint32 progbar_count, progbar_quantum;
+// guint32 progbar_nextstep = 0;
+ guint32 count = 0;
+ 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;
+ }
+
+// progbar = create_progress_dlg(top_level, "Saving voice in a file", dest, 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;
+// progbar_count = user_data->forward.saveinfo.count;
+// progbar_quantum = user_data->forward.saveinfo.count/100;
+ while (fwd_tempfile_->getChar(&f_rawvalue)) {
+ if (stop_flag)
+ break;
+// if ((count > progbar_nextstep) && (count <= progbar_count)) {
+// update_progress_dlg(progbar,
+// (gfloat) count/progbar_count, "Saving");
+// progbar_nextstep = progbar_nextstep + progbar_quantum;
+// }
+ count++;
+
+ 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;
+// progbar_count = user_data->reversed.saveinfo.count;
+// progbar_quantum = user_data->reversed.saveinfo.count/100;
+ while (rev_tempfile_->getChar(&r_rawvalue)) {
+ if (stop_flag)
+ break;
+// if ((count > progbar_nextstep) && (count <= progbar_count)) {
+// update_progress_dlg(progbar,
+// (gfloat) count/progbar_count, "Saving");
+// progbar_nextstep = progbar_nextstep + progbar_quantum;
+// }
+ count++;
+
+ 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;
+// if (user_data->forward.saveinfo.count > user_data->reversed.saveinfo.count) {
+// progbar_count = user_data->forward.saveinfo.count;
+// } else {
+// progbar_count = user_data->reversed.saveinfo.count;
+// }
+// progbar_quantum = progbar_count/100;
+ /* 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;
+// if ((count > progbar_nextstep) && (count <= progbar_count)) {
+// update_progress_dlg(progbar,
+// (gfloat) count/progbar_count, "Saving");
+// progbar_nextstep = progbar_nextstep + progbar_quantum;
+// }
+ count++;
+ 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;
+ switch (direction) {
+ /* Only forward direction */
+ case dir_forward_: {
+// progbar_count = user_data->forward.saveinfo.count;
+// progbar_quantum = user_data->forward.saveinfo.count/100;
+ tempfile = fwd_tempfile_;
+ break;
+ }
+ /* only reversed direction */
+ case dir_reverse_: {
+// progbar_count = user_data->reversed.saveinfo.count;
+// progbar_quantum = user_data->reversed.saveinfo.count/100;
+ 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);
+// if ((count > progbar_nextstep) && (count <= progbar_count)) {
+// update_progress_dlg(progbar,
+// (gfloat) count/progbar_count, "Saving");
+// progbar_nextstep = progbar_nextstep + progbar_quantum;
+// }
+// count++;
+
+ if (!save_file.write(bytes)) {
+ goto copy_file_err;
+ }
+ chunk_size = bytes.length();
+ }
+ }
+
+// ret_val = TRUE;
+// goto copy_file_xit;
+
+copy_file_err:
+ return;
+// ret_val = FALSE;
+// goto copy_file_xit;
+
+//copy_file_xit:
+// destroy_progress_dlg(progbar);
+// fclose(forw_stream);
+// fclose(rev_stream);
+// fclose(to_stream);
+ // return ret_val;
+}
+
+// XXX The GTK+ UI saves the length and timestamp.
+void RtpAnalysisDialog::saveCsv(RtpAnalysisDialog::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() != rtp_analysis_type_) continue;
+ RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)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() != rtp_analysis_type_) continue;
+ RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)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");
+ }
+ }
+}
+
+// Adapted from rtp_analysis.c:process_node
+guint32 RtpAnalysisDialog::processNode(proto_node *ptree_node, header_field_info *hfinformation, const gchar *proto_field, bool *ok)
+{
+ field_info *finfo;
+ proto_node *proto_sibling_node;
+ header_field_info *hfssrc;
+ ipv4_addr *ipv4;
+
+ finfo = PNODE_FINFO(ptree_node);
+
+ /* Caller passed top of the protocol tree. Expected child node */
+ g_assert(finfo);
+
+ if (hfinformation == (finfo->hfinfo)) {
+ hfssrc = proto_registrar_get_byname(proto_field);
+ if (hfssrc == NULL) {
+ return 0;
+ }
+ for (ptree_node = ptree_node->first_child;
+ ptree_node != NULL;
+ ptree_node = ptree_node->next) {
+ finfo = PNODE_FINFO(ptree_node);
+ if (hfssrc == finfo->hfinfo) {
+ guint32 result;
+ if (hfinformation->type == FT_IPv4) {
+ ipv4 = (ipv4_addr *)fvalue_get(&finfo->value);
+ result = ipv4_get_net_order_addr(ipv4);
+ } else {
+ result = fvalue_get_uinteger(&finfo->value);
+ }
+ if (ok) *ok = true;
+ return result;
+ }
+ }
+ if (!ptree_node) {
+ return 0;
+ }
+ }
+
+ proto_sibling_node = ptree_node->next;
+
+ if (proto_sibling_node) {
+ return processNode(proto_sibling_node, hfinformation, proto_field, ok);
+ } else {
+ return 0;
+ }
+}
+
+// Adapted from rtp_analysis.c:get_int_value_from_proto_tree
+guint32 RtpAnalysisDialog::getIntFromProtoTree(proto_tree *protocol_tree, const gchar *proto_name, const gchar *proto_field, bool *ok)
+{
+ proto_node *ptree_node;
+ header_field_info *hfinformation;
+
+ if (ok) *ok = false;
+
+ hfinformation = proto_registrar_get_byname(proto_name);
+ if (hfinformation == NULL) {
+ return 0;
+ }
+
+ ptree_node = ((proto_node *)protocol_tree)->first_child;
+ if (!ptree_node) {
+ return 0;
+ }
+
+ return processNode(ptree_node, hfinformation, proto_field, ok);
+}
+
+bool RtpAnalysisDialog::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 RtpAnalysisDialog::graphClicked(QMouseEvent *event)
+{
+ updateWidgets();
+ if (event->button() == Qt::RightButton) {
+ graph_ctx_menu_.exec(event->globalPos());
+ }
+}
+
+void RtpAnalysisDialog::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:
+ */