aboutsummaryrefslogtreecommitdiffstats
path: root/ui/qt/rtp_player_dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/rtp_player_dialog.cpp')
-rw-r--r--ui/qt/rtp_player_dialog.cpp656
1 files changed, 656 insertions, 0 deletions
diff --git a/ui/qt/rtp_player_dialog.cpp b/ui/qt/rtp_player_dialog.cpp
new file mode 100644
index 0000000000..b930a08d0b
--- /dev/null
+++ b/ui/qt/rtp_player_dialog.cpp
@@ -0,0 +1,656 @@
+/* rtp_player_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_player_dialog.h"
+#include "ui_rtp_player_dialog.h"
+
+#ifdef QT_MULTIMEDIA_LIB
+
+#include <epan/dissectors/packet-rtp.h>
+
+#include <ui/utf8_entities.h>
+
+#include "color_utils.h"
+#include "qcustomplot.h"
+#include "qt_ui_utils.h"
+#include "rtp_audio_stream.h"
+#include "stock_icon.h"
+#include "tango_colors.h"
+
+#include <QAudio>
+#include <QFrame>
+#include <QMenu>
+#include <QVBoxLayout>
+
+Q_DECLARE_METATYPE(RtpAudioStream *)
+Q_DECLARE_METATYPE(QCPGraph *)
+
+#endif // QT_MULTIMEDIA_LIB
+
+#include "wireshark_application.h"
+
+// To do:
+// - Fully implement shorcuts (drag, go to packet, etc.)
+// - Figure out selection and highlighting.
+// - Make streams checkable.
+// - Add silence, drop & jitter indicators to the graph.
+// - How to handle multiple channels?
+// - Threaded decoding?
+// - Play MP3s. As per Zawinski's Law we already read emails.
+// - RTP audio streams are currently keyed on src addr + src port + dst addr
+// + dst port + ssrc. This means that we can have multiple rtp_stream_info
+// structs per RtpAudioStream. Should we make them 1:1 instead?
+
+// Current RTP player bugs:
+// Bug 3368 - The timestamp line in a RTP or RTCP packet display's "Not Representable"
+// Bug 3952 - VoIP Call RTP Player: audio played is corrupted when RFC2833 packets are present
+// Bug 4960 - RTP Player: Audio and visual feedback get rapidly out of sync
+// Bug 5527 - Adding arbitrary value to x-axis RTP player
+// Bug 7935 - Wrong Timestamps in RTP Player-Decode
+// Bug 8007 - UI gets confused on playing decoded audio in rtp_player
+// Bug 9007 - Switching SSRC values in RTP stream
+// Bug 10613 - RTP audio player crashes
+// Bug 11125 - RTP Player does not show progress in selected stream in Window 7
+// Bug 11409 - Wireshark crashes when using RTP player
+
+// XXX It looks like we duplicate some functionality here and in the RTP
+// analysis code, which has its own routines for writing audio data to a
+// file.
+
+// In some places we match by conv/call number, in others we match by first frame.
+
+enum {
+ src_addr_col_,
+ src_port_col_,
+ dst_addr_col_,
+ dst_port_col_,
+ ssrc_col_,
+ first_pkt_col_,
+ num_pkts_col_,
+ time_span_col_,
+ sample_rate_col_,
+ payload_col_,
+
+ stream_data_col_ = src_addr_col_, // RtpAudioStream
+ graph_data_col_ = src_port_col_ // QCPGraph
+};
+
+static const double wf_graph_normal_width_ = 0.5;
+static const double wf_graph_selected_width_ = 2.0;
+
+RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
+ WiresharkDialog(parent, cf)
+#ifdef QT_MULTIMEDIA_LIB
+ , ui(new Ui::RtpPlayerDialog)
+ , start_rel_time_(0.0)
+#endif // QT_MULTIMEDIA_LIB
+{
+ ui->setupUi(this);
+ setWindowTitle(wsApp->windowTitleString(tr("RTP Player")));
+ resize(parent.size());
+
+#ifdef QT_MULTIMEDIA_LIB
+ ui->splitter->setStretchFactor(0, 3);
+ ui->splitter->setStretchFactor(1, 1);
+
+ ctx_menu_ = new QMenu(this);
+
+ ctx_menu_->addAction(ui->actionZoomIn);
+ ctx_menu_->addAction(ui->actionZoomOut);
+ ctx_menu_->addAction(ui->actionReset);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionMoveRight10);
+ ctx_menu_->addAction(ui->actionMoveLeft10);
+ ctx_menu_->addAction(ui->actionMoveRight1);
+ ctx_menu_->addAction(ui->actionMoveLeft1);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionGoToPacket);
+ ctx_menu_->addSeparator();
+ ctx_menu_->addAction(ui->actionDragZoom);
+ ctx_menu_->addAction(ui->actionToggleTimeOrigin);
+ ctx_menu_->addAction(ui->actionCrosshairs);
+
+ connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
+ this, SLOT(mouseMoved(QMouseEvent*)));
+ connect(ui->audioPlot, SIGNAL(mousePress(QMouseEvent*)),
+ this, SLOT(graphClicked(QMouseEvent*)));
+
+ cur_play_pos_ = new QCPItemStraightLine(ui->audioPlot);
+ ui->audioPlot->addItem(cur_play_pos_);
+ cur_play_pos_->setVisible(false);
+
+ ui->audioPlot->xAxis->setNumberFormat("gb");
+ ui->audioPlot->xAxis->setNumberPrecision(3);
+ ui->audioPlot->xAxis->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss.zzz");
+ ui->audioPlot->yAxis->setVisible(false);
+
+ ui->playButton->setIcon(StockIcon("media-playback-start"));
+ ui->stopButton->setIcon(StockIcon("media-playback-stop"));
+
+ ui->audioPlot->setMouseTracking(true);
+ ui->audioPlot->setEnabled(true);
+ ui->audioPlot->setInteractions(
+ QCP::iRangeDrag |
+ QCP::iRangeZoom
+ );
+ ui->audioPlot->setFocus();
+
+ QTimer::singleShot(0, this, SLOT(retapPackets()));
+#endif // QT_MULTIMEDIA_LIB
+}
+
+#ifdef QT_MULTIMEDIA_LIB
+RtpPlayerDialog::~RtpPlayerDialog()
+{
+ delete ui;
+}
+
+void RtpPlayerDialog::retapPackets(bool rescale_axes)
+{
+ int row_count = ui->streamTreeWidget->topLevelItemCount();
+ // Clear existing graphs and reset stream values
+ for (int row = 0; row < row_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ audio_stream->reset(start_rel_time_);
+
+ ti->setData(graph_data_col_, Qt::UserRole, QVariant());
+ }
+ ui->audioPlot->clearGraphs();
+
+ register_tap_listener("rtp", this, NULL, 0, NULL, tapPacket, NULL);
+ cap_file_.retapPackets();
+ remove_tap_listener(this);
+
+ bool show_legend = false;
+ bool relative_timestamps = !ui->todCheckBox->isChecked();
+
+ ui->audioPlot->xAxis->setTickLabelType(relative_timestamps ? QCPAxis::ltNumber : QCPAxis::ltDateTime);
+
+ for (int row = 0; row < row_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ int y_offset = row_count - row - 1;
+
+ // Waveform
+ QCPGraph *audio_graph = ui->audioPlot->addGraph();
+ QPen wf_pen(audio_stream->color());
+ wf_pen.setWidthF(wf_graph_normal_width_);
+ audio_graph->setPen(wf_pen);
+ wf_pen.setWidthF(wf_graph_selected_width_);
+ audio_graph->setSelectedPen(wf_pen);
+ audio_graph->setSelectable(false);
+ audio_graph->setData(audio_stream->visualTimestamps(relative_timestamps), audio_stream->visualSamples(y_offset));
+ audio_graph->removeFromLegend();
+ ti->setData(graph_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(audio_graph));
+ // RTP_STREAM_DEBUG("Plotting %s, %d samples", ti->text(src_addr_col_).toUtf8().constData(), audio_graph->data()->keys().length());
+
+ QString span_str = QString("%1 - %2 (%3)")
+ .arg(QString::number(audio_stream->startRelTime(), 'g', 3))
+ .arg(QString::number(audio_stream->stopRelTime(), 'g', 3))
+ .arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'g', 3));
+ ti->setText(time_span_col_, span_str);
+ ti->setText(sample_rate_col_, QString::number(audio_stream->sampleRate()));
+ ti->setText(payload_col_, audio_stream->payloadNames().join(", "));
+
+ if (audio_stream->outOfSequence() > 0) {
+ // Sequence numbers
+ QCPGraph *seq_graph = ui->audioPlot->addGraph();
+ seq_graph->setLineStyle(QCPGraph::lsNone);
+ seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssSquare, tango_aluminium_6, Qt::white, 4)); // Arbitrary
+ seq_graph->setSelectable(false);
+ seq_graph->setData(audio_stream->outOfSequenceTimestamps(relative_timestamps), audio_stream->outOfSequenceSamples(y_offset));
+ if (row < 1) {
+ seq_graph->setName(tr("Out of Sequence"));
+ show_legend = true;
+ } else {
+ seq_graph->removeFromLegend();
+ }
+ }
+ }
+ ui->audioPlot->legend->setVisible(show_legend);
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount() - 1; col++) {
+ ui->streamTreeWidget->resizeColumnToContents(col);
+ }
+
+ ui->audioPlot->replot();
+ if (rescale_axes) resetXAxis();
+
+ updateWidgets();
+}
+
+void RtpPlayerDialog::addRtpStream(struct _rtp_stream_info *rtp_stream)
+{
+ if (!rtp_stream) return;
+
+ // Find the RTP streams associated with this conversation.
+ // gtk/rtp_player.c:mark_rtp_stream_to_play does this differently.
+
+ RtpAudioStream *audio_stream = NULL;
+ int tli_count = ui->streamTreeWidget->topLevelItemCount();
+ for (int row = 0; row < tli_count; row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+ if (row_stream->isMatch(rtp_stream)) {
+ audio_stream = row_stream;
+ break;
+ }
+ }
+
+ if (!audio_stream) {
+ audio_stream = new RtpAudioStream(this, rtp_stream);
+ audio_stream->setColor(ColorUtils::graph_colors_[tli_count % ColorUtils::graph_colors_.length()]);
+
+ QTreeWidgetItem *ti = new QTreeWidgetItem(ui->streamTreeWidget);
+ ti->setText(src_addr_col_, address_to_qstring(&rtp_stream->src_addr));
+ ti->setText(src_port_col_, QString::number(rtp_stream->src_port));
+ ti->setText(dst_addr_col_, address_to_qstring(&rtp_stream->dest_addr));
+ ti->setText(dst_port_col_, QString::number(rtp_stream->dest_port));
+ ti->setText(ssrc_col_, int_to_qstring(rtp_stream->ssrc, 8, 16));
+ ti->setText(first_pkt_col_, QString::number(rtp_stream->setup_frame_number));
+ ti->setText(num_pkts_col_, QString::number(rtp_stream->packet_count));
+
+ ti->setData(stream_data_col_, Qt::UserRole, QVariant::fromValue(audio_stream));
+
+ for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
+ ti->setTextColor(col, audio_stream->color());
+ }
+
+ connect(ui->playButton, SIGNAL(clicked(bool)), audio_stream, SLOT(startPlaying()));
+ connect(ui->stopButton, SIGNAL(clicked(bool)), audio_stream, SLOT(stopPlaying()));
+
+ connect(audio_stream, SIGNAL(startedPlaying()), this, SLOT(updateWidgets()));
+ connect(audio_stream, SIGNAL(finishedPlaying()), this, SLOT(updateWidgets()));
+ connect(audio_stream, SIGNAL(processedSecs(double)), this, SLOT(setPlayPosition(double)));
+ }
+ audio_stream->addRtpStream(rtp_stream);
+ double start_rel_time = nstime_to_sec(&rtp_stream->start_rel_time);
+ if (tli_count < 2) {
+ start_rel_time_ = start_rel_time;
+ } else {
+ start_rel_time_ = qMin(start_rel_time_, start_rel_time);
+ }
+ // RTP_STREAM_DEBUG("adding stream %s to layout, %u packets, start %u", stream_key.toUtf8().constData(), rtp_stream->packet_count, rtp_stream->start_fd->num);
+}
+
+void RtpPlayerDialog::showEvent(QShowEvent *)
+{
+ QList<int> split_sizes = ui->splitter->sizes();
+ int tot_size = split_sizes[0] + split_sizes[1];
+ int plot_size = tot_size * 3 / 4;
+ split_sizes.clear();
+ split_sizes << plot_size << tot_size - plot_size;
+ ui->splitter->setSizes(split_sizes);
+}
+
+void RtpPlayerDialog::keyPressEvent(QKeyEvent *event)
+{
+ int pan_secs = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
+
+ switch(event->key()) {
+ case Qt::Key_Minus:
+ case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
+ case Qt::Key_O: // GTK+
+ case Qt::Key_R:
+ on_actionZoomOut_triggered();
+ break;
+ case Qt::Key_Plus:
+ case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
+ case Qt::Key_I: // GTK+
+ on_actionZoomIn_triggered();
+ break;
+
+ case Qt::Key_Right:
+ case Qt::Key_L:
+ panXAxis(pan_secs);
+ break;
+ case Qt::Key_Left:
+ case Qt::Key_H:
+ panXAxis(-1 * pan_secs);
+ break;
+
+ case Qt::Key_Space:
+// toggleTracerStyle();
+ break;
+
+ case Qt::Key_0:
+ case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
+ case Qt::Key_Home:
+ on_actionReset_triggered();
+ break;
+
+ case Qt::Key_G:
+ on_actionGoToPacket_triggered();
+ break;
+ case Qt::Key_T:
+// on_actionToggleTimeOrigin_triggered();
+ break;
+ case Qt::Key_Z:
+// on_actionDragZoom_triggered();
+ break;
+ }
+
+ QDialog::keyPressEvent(event);
+}
+
+void RtpPlayerDialog::updateWidgets()
+{
+ bool enable_play = true;
+ bool enable_stop = false;
+
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+
+ RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
+ if (audio_stream->outputState() != QAudio::IdleState) {
+ enable_play = false;
+ enable_stop = true;
+ }
+ }
+
+ ui->playButton->setEnabled(enable_play);
+ ui->stopButton->setEnabled(enable_stop);
+ cur_play_pos_->setVisible(enable_stop);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::graphClicked(QMouseEvent *event)
+{
+ updateWidgets();
+ if (event->button() == Qt::RightButton) {
+ ctx_menu_->exec(event->globalPos());
+ }
+ ui->audioPlot->setFocus();
+}
+
+void RtpPlayerDialog::mouseMoved(QMouseEvent *)
+{
+ int packet_num = getHoveredPacket();
+ QString hint = "<small><i>";
+
+ if (packet_num > 0) {
+ hint += tr("%1. Press \"G\" to to to packet %2")
+ .arg(getHoveredTime())
+ .arg(packet_num);
+ }
+
+ hint += "</i></small>";
+ ui->hintLabel->setText(hint);
+}
+
+void RtpPlayerDialog::resetXAxis()
+{
+ QCustomPlot *ap = ui->audioPlot;
+ QCPRange x_range = ap->xAxis->range();
+
+ double pixel_pad = 10.0; // per side
+
+ ap->rescaleAxes(true);
+
+ double axis_pixels = ap->xAxis->axisRect()->width();
+ ap->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
+
+ axis_pixels = ap->yAxis->axisRect()->height();
+ ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
+
+ ap->replot();
+}
+
+void RtpPlayerDialog::setPlayPosition(double secs)
+{
+ secs+= ui->audioPlot->xAxis->range().lower;
+ double cur_secs = cur_play_pos_->point1->key();
+ if (secs > cur_secs) {
+ cur_play_pos_->point1->setCoords(secs, 0.0);
+ cur_play_pos_->point2->setCoords(secs, 1.0);
+ ui->audioPlot->replot();
+ }
+}
+
+gboolean RtpPlayerDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
+{
+ RtpPlayerDialog *rtp_player_dialog = dynamic_cast<RtpPlayerDialog *>((RtpPlayerDialog*)tapinfo_ptr);
+ if (!rtp_player_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;
+
+ rtp_player_dialog->addPacket(pinfo, rtpinfo);
+
+ return FALSE;
+}
+
+void RtpPlayerDialog::addPacket(packet_info *pinfo, const _rtp_info *rtpinfo)
+{
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
+
+ if (row_stream->isMatch(pinfo, rtpinfo)) {
+ row_stream->addRtpPacket(pinfo, rtpinfo);
+ return;
+ }
+ }
+// qDebug() << "=ap no match!" << address_to_qstring(&pinfo->src) << address_to_qstring(&pinfo->dst);
+}
+
+void RtpPlayerDialog::zoomXAxis(bool in)
+{
+ QCustomPlot *ap = ui->audioPlot;
+ double h_factor = ap->axisRect()->rangeZoomFactor(Qt::Horizontal);
+
+ if (!in) {
+ h_factor = pow(h_factor, -1);
+ }
+
+ ap->xAxis->scaleRange(h_factor, ap->xAxis->range().center());
+ ap->replot();
+}
+
+// XXX I tried using seconds but pixels make more sense at varying zoom
+// levels.
+void RtpPlayerDialog::panXAxis(int x_pixels)
+{
+ QCustomPlot *ap = ui->audioPlot;
+ double h_pan;
+
+ h_pan = ap->xAxis->range().size() * x_pixels / ap->xAxis->axisRect()->width();
+ if (x_pixels) {
+ ap->xAxis->moveRange(h_pan);
+ ap->replot();
+ }
+}
+
+void RtpPlayerDialog::on_playButton_clicked()
+{
+ double left = ui->audioPlot->xAxis->range().lower;
+ cur_play_pos_->point1->setCoords(left, 0.0);
+ cur_play_pos_->point2->setCoords(left, 1.0);
+ cur_play_pos_->setVisible(true);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::on_stopButton_clicked()
+{
+ cur_play_pos_->setVisible(false);
+}
+
+void RtpPlayerDialog::on_actionReset_triggered()
+{
+ resetXAxis();
+}
+
+void RtpPlayerDialog::on_actionZoomIn_triggered()
+{
+ zoomXAxis(true);
+}
+
+void RtpPlayerDialog::on_actionZoomOut_triggered()
+{
+ zoomXAxis(false);
+}
+
+void RtpPlayerDialog::on_actionMoveLeft10_triggered()
+{
+ panXAxis(-10);
+}
+
+void RtpPlayerDialog::on_actionMoveRight10_triggered()
+{
+ panXAxis(10);
+}
+
+void RtpPlayerDialog::on_actionMoveLeft1_triggered()
+{
+ panXAxis(-1);
+}
+
+void RtpPlayerDialog::on_actionMoveRight1_triggered()
+{
+ panXAxis(1);
+}
+
+void RtpPlayerDialog::on_actionGoToPacket_triggered()
+{
+ int packet_num = getHoveredPacket();
+ if (packet_num > 0) emit goToPacket(packet_num);
+}
+
+// XXX Make waveform graphs selectable and update the treewidget selection accordingly.
+void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
+{
+ for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
+ QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
+ QCPGraph *audio_graph = ti->data(graph_data_col_, Qt::UserRole).value<QCPGraph*>();
+ if (audio_graph) {
+ audio_graph->setSelected(ti->isSelected());
+ }
+ }
+ ui->audioPlot->replot();
+}
+
+double RtpPlayerDialog::getLowestTimestamp()
+{
+ double lowest = QCPRange::maxRange;
+
+ for (int i = 0; i < ui->audioPlot->graphCount(); i++) {
+ QCPGraph *graph = ui->audioPlot->graph(i);
+ if (!graph->visible()) continue;
+ QCPDataMap *dm = graph->data();
+ if (dm->keys().length() < 1) continue;
+ lowest = qMin(lowest, dm->keys().first());
+ }
+ return lowest;
+}
+
+const QString RtpPlayerDialog::getHoveredTime()
+{
+ QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
+ if (!ti) return tr("Unknown");
+
+ QString time_str;
+ double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
+
+ if (ui->todCheckBox->isChecked()) {
+ QDateTime date_time = QDateTime::fromMSecsSinceEpoch(ts * 1000.0);
+ time_str = date_time.toString("yyyy-MM-dd hh:mm:ss.zzz");
+ } else {
+ time_str = QString::number(ts, 'f', 3);
+ time_str += " s";
+ }
+ return time_str;
+}
+
+int RtpPlayerDialog::getHoveredPacket()
+{
+ QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
+ if (!ti) return 0;
+
+ RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
+
+ double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
+
+ return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
+}
+
+void RtpPlayerDialog::on_todCheckBox_toggled(bool)
+{
+ QCPAxis *x_axis = ui->audioPlot->xAxis;
+ double old_lowest = getLowestTimestamp();
+
+ retapPackets(false);
+ x_axis->moveRange(getLowestTimestamp() - old_lowest);
+ ui->audioPlot->replot();
+}
+
+void RtpPlayerDialog::on_buttonBox_helpRequested()
+{
+ wsApp->helpTopicAction(HELP_TELEPHONY_RTP_PLAYER_DIALOG);
+}
+
+#if 0
+// This also serves as a title in RtpAudioFrame.
+static const QString stream_key_tmpl_ = "%1:%2 " UTF8_RIGHTWARDS_ARROW " %3:%4 0x%5";
+const QString RtpPlayerDialog::streamKey(const struct _rtp_stream_info *rtp_stream)
+{
+ const QString stream_key = QString(stream_key_tmpl_)
+ .arg(address_to_display_qstring(&rtp_stream->src_addr))
+ .arg(rtp_stream->src_port)
+ .arg(address_to_display_qstring(&rtp_stream->dest_addr))
+ .arg(rtp_stream->dest_port)
+ .arg(rtp_stream->ssrc, 0, 16);
+ return stream_key;
+}
+
+const QString RtpPlayerDialog::streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo)
+{
+ const QString stream_key = QString(stream_key_tmpl_)
+ .arg(address_to_display_qstring(&pinfo->src))
+ .arg(pinfo->srcport)
+ .arg(address_to_display_qstring(&pinfo->dst))
+ .arg(pinfo->destport)
+ .arg(rtpinfo->info_sync_src, 0, 16);
+ return stream_key;
+}
+#endif
+
+#endif // QT_MULTIMEDIA_LIB
+
+/*
+ * 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:
+ */