1 /* rtp_player_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "rtp_player_dialog.h"
11 #include <ui_rtp_player_dialog.h>
13 #ifdef QT_MULTIMEDIA_LIB
15 #include <epan/dissectors/packet-rtp.h>
17 #include <wsutil/report_message.h>
18 #include <wsutil/utf8_entities.h>
20 #include <ui/qt/utils/color_utils.h>
21 #include <ui/qt/widgets/qcustomplot.h>
22 #include <ui/qt/utils/qt_ui_utils.h>
23 #include "rtp_audio_stream.h"
24 #include <ui/qt/utils/tango_colors.h>
27 #include <QAudioDeviceInfo>
30 #include <QVBoxLayout>
32 #endif // QT_MULTIMEDIA_LIB
34 #include <QPushButton>
36 #include <ui/qt/utils/stock_icon.h>
37 #include "wireshark_application.h"
40 // - Fully implement shorcuts (drag, go to packet, etc.)
41 // - Figure out selection and highlighting.
42 // - Make streams checkable.
43 // - Add silence, drop & jitter indicators to the graph.
44 // - How to handle multiple channels?
45 // - Threaded decoding?
46 // - Play MP3s. As per Zawinski's Law we already read emails.
47 // - RTP audio streams are currently keyed on src addr + src port + dst addr
48 // + dst port + ssrc. This means that we can have multiple rtp_stream_info
49 // structs per RtpAudioStream. Should we make them 1:1 instead?
51 // Current and former RTP player bugs. Many have attachments that can be usef for testing.
52 // Bug 3368 - The timestamp line in a RTP or RTCP packet display's "Not Representable"
53 // Bug 3952 - VoIP Call RTP Player: audio played is corrupted when RFC2833 packets are present
54 // Bug 4960 - RTP Player: Audio and visual feedback get rapidly out of sync
55 // Bug 5527 - Adding arbitrary value to x-axis RTP player
56 // Bug 7935 - Wrong Timestamps in RTP Player-Decode
57 // Bug 8007 - UI gets confused on playing decoded audio in rtp_player
58 // Bug 9007 - Switching SSRC values in RTP stream
59 // Bug 10613 - RTP audio player crashes
60 // Bug 11125 - RTP Player does not show progress in selected stream in Window 7
61 // Bug 11409 - Wireshark crashes when using RTP player
62 // Bug 12166 - RTP audio player crashes
64 // XXX It looks like we duplicate some functionality here and in the RTP
65 // analysis code, which has its own routines for writing audio data to a
68 // In some places we match by conv/call number, in others we match by first frame.
82 stream_data_col_ = src_addr_col_, // RtpAudioStream
83 graph_data_col_ = src_port_col_ // QCPGraph
86 #ifdef QT_MULTIMEDIA_LIB
87 static const double wf_graph_normal_width_ = 0.5;
88 static const double wf_graph_selected_width_ = 2.0;
91 RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
92 WiresharkDialog(parent, cf)
93 #ifdef QT_MULTIMEDIA_LIB
94 , ui(new Ui::RtpPlayerDialog)
95 , start_rel_time_(0.0)
96 #endif // QT_MULTIMEDIA_LIB
99 setWindowTitle(wsApp->windowTitleString(tr("RTP Player")));
100 loadGeometry(parent.width(), parent.height());
102 #ifdef QT_MULTIMEDIA_LIB
103 ui->splitter->setStretchFactor(0, 3);
104 ui->splitter->setStretchFactor(1, 1);
106 ctx_menu_ = new QMenu(this);
108 ctx_menu_->addAction(ui->actionZoomIn);
109 ctx_menu_->addAction(ui->actionZoomOut);
110 ctx_menu_->addAction(ui->actionReset);
111 ctx_menu_->addSeparator();
112 ctx_menu_->addAction(ui->actionMoveRight10);
113 ctx_menu_->addAction(ui->actionMoveLeft10);
114 ctx_menu_->addAction(ui->actionMoveRight1);
115 ctx_menu_->addAction(ui->actionMoveLeft1);
116 ctx_menu_->addSeparator();
117 ctx_menu_->addAction(ui->actionGoToPacket);
118 ctx_menu_->addSeparator();
119 ctx_menu_->addAction(ui->actionDragZoom);
120 ctx_menu_->addAction(ui->actionToggleTimeOrigin);
121 // ctx_menu_->addAction(ui->actionCrosshairs);
123 connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
124 this, SLOT(updateHintLabel()));
125 connect(ui->audioPlot, SIGNAL(mousePress(QMouseEvent*)),
126 this, SLOT(graphClicked(QMouseEvent*)));
128 cur_play_pos_ = new QCPItemStraightLine(ui->audioPlot);
129 ui->audioPlot->addItem(cur_play_pos_);
130 cur_play_pos_->setVisible(false);
132 ui->audioPlot->xAxis->setNumberFormat("gb");
133 ui->audioPlot->xAxis->setNumberPrecision(3);
134 ui->audioPlot->xAxis->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss.zzz");
135 ui->audioPlot->yAxis->setVisible(false);
137 ui->playButton->setIcon(StockIcon("media-playback-start"));
138 ui->stopButton->setIcon(StockIcon("media-playback-stop"));
140 // Ordered, unique device names starting with the system default
141 QMap<QString, bool> out_device_map; // true == default device
142 out_device_map.insert(QAudioDeviceInfo::defaultOutputDevice().deviceName(), true);
143 foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
144 if (!out_device_map.contains(out_device.deviceName())) {
145 out_device_map.insert(out_device.deviceName(), false);
149 foreach (QString out_name, out_device_map.keys()) {
150 ui->outputDeviceComboBox->addItem(out_name);
151 if (out_device_map.value(out_name)) {
152 ui->outputDeviceComboBox->setCurrentIndex(ui->outputDeviceComboBox->count() - 1);
155 if (ui->outputDeviceComboBox->count() < 1) {
156 ui->outputDeviceComboBox->setEnabled(false);
157 ui->playButton->setEnabled(false);
158 ui->stopButton->setEnabled(false);
159 ui->outputDeviceComboBox->addItem(tr("No devices available"));
162 ui->audioPlot->setMouseTracking(true);
163 ui->audioPlot->setEnabled(true);
164 ui->audioPlot->setInteractions(
168 ui->audioPlot->setFocus();
170 QTimer::singleShot(0, this, SLOT(retapPackets()));
171 #endif // QT_MULTIMEDIA_LIB
174 QPushButton *RtpPlayerDialog::addPlayerButton(QDialogButtonBox *button_box)
176 if (!button_box) return NULL;
178 QPushButton *player_button;
179 player_button = button_box->addButton(tr("Play Streams"), QDialogButtonBox::ApplyRole);
180 player_button->setIcon(StockIcon("media-playback-start"));
181 return player_button;
184 #ifdef QT_MULTIMEDIA_LIB
185 RtpPlayerDialog::~RtpPlayerDialog()
190 void RtpPlayerDialog::accept()
192 int row_count = ui->streamTreeWidget->topLevelItemCount();
193 // Stop all streams before the dialogs are closed.
194 for (int row = 0; row < row_count; row++) {
195 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
196 RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
197 audio_stream->stopPlaying();
199 WiresharkDialog::accept();
202 void RtpPlayerDialog::reject()
204 RtpPlayerDialog::accept();
207 void RtpPlayerDialog::retapPackets()
209 GString *error_string;
211 error_string = register_tap_listener("rtp", this, NULL, 0, NULL, tapPacket, NULL, NULL);
213 report_failure("RTP Player - tap registration failed: %s", error_string->str);
214 g_string_free(error_string, TRUE);
217 cap_file_.retapPackets();
218 remove_tap_listener(this);
223 void RtpPlayerDialog::rescanPackets(bool rescale_axes)
225 int row_count = ui->streamTreeWidget->topLevelItemCount();
226 // Clear existing graphs and reset stream values
227 for (int row = 0; row < row_count; row++) {
228 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
229 RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
230 audio_stream->reset(start_rel_time_);
232 ti->setData(graph_data_col_, Qt::UserRole, QVariant());
234 ui->audioPlot->clearGraphs();
236 bool show_legend = false;
237 bool relative_timestamps = !ui->todCheckBox->isChecked();
239 ui->audioPlot->xAxis->setTickLabelType(relative_timestamps ? QCPAxis::ltNumber : QCPAxis::ltDateTime);
241 for (int row = 0; row < row_count; row++) {
242 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
243 RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
244 int y_offset = row_count - row - 1;
246 audio_stream->setJitterBufferSize((int) ui->jitterSpinBox->value());
248 RtpAudioStream::TimingMode timing_mode = RtpAudioStream::JitterBuffer;
249 switch (ui->timingComboBox->currentIndex()) {
250 case RtpAudioStream::RtpTimestamp:
251 timing_mode = RtpAudioStream::RtpTimestamp;
253 case RtpAudioStream::Uninterrupted:
254 timing_mode = RtpAudioStream::Uninterrupted;
259 audio_stream->setTimingMode(timing_mode);
261 audio_stream->decode();
264 QCPGraph *audio_graph = ui->audioPlot->addGraph();
265 QPen wf_pen(audio_stream->color());
266 wf_pen.setWidthF(wf_graph_normal_width_);
267 audio_graph->setPen(wf_pen);
268 wf_pen.setWidthF(wf_graph_selected_width_);
269 audio_graph->setSelectedPen(wf_pen);
270 audio_graph->setSelectable(false);
271 audio_graph->setData(audio_stream->visualTimestamps(relative_timestamps), audio_stream->visualSamples(y_offset));
272 audio_graph->removeFromLegend();
273 ti->setData(graph_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(audio_graph));
274 RTP_STREAM_DEBUG("Plotting %s, %d samples", ti->text(src_addr_col_).toUtf8().constData(), audio_graph->data()->keys().length());
276 QString span_str = QString("%1 - %2 (%3)")
277 .arg(QString::number(audio_stream->startRelTime(), 'g', 3))
278 .arg(QString::number(audio_stream->stopRelTime(), 'g', 3))
279 .arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'g', 3));
280 ti->setText(time_span_col_, span_str);
281 ti->setText(sample_rate_col_, QString::number(audio_stream->sampleRate()));
282 ti->setText(payload_col_, audio_stream->payloadNames().join(", "));
284 if (audio_stream->outOfSequence() > 0) {
286 QCPGraph *seq_graph = ui->audioPlot->addGraph();
287 seq_graph->setLineStyle(QCPGraph::lsNone);
288 seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssSquare, tango_aluminium_6, Qt::white, 4)); // Arbitrary
289 seq_graph->setSelectable(false);
290 seq_graph->setData(audio_stream->outOfSequenceTimestamps(relative_timestamps), audio_stream->outOfSequenceSamples(y_offset));
292 seq_graph->setName(tr("Out of Sequence"));
295 seq_graph->removeFromLegend();
299 if (audio_stream->jitterDropped() > 0) {
301 QCPGraph *seq_graph = ui->audioPlot->addGraph();
302 seq_graph->setLineStyle(QCPGraph::lsNone);
303 seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, tango_scarlet_red_5, Qt::white, 4)); // Arbitrary
304 seq_graph->setSelectable(false);
305 seq_graph->setData(audio_stream->jitterDroppedTimestamps(relative_timestamps), audio_stream->jitterDroppedSamples(y_offset));
307 seq_graph->setName(tr("Jitter Drops"));
310 seq_graph->removeFromLegend();
314 if (audio_stream->wrongTimestamps() > 0) {
316 QCPGraph *seq_graph = ui->audioPlot->addGraph();
317 seq_graph->setLineStyle(QCPGraph::lsNone);
318 seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDiamond, tango_sky_blue_5, Qt::white, 4)); // Arbitrary
319 seq_graph->setSelectable(false);
320 seq_graph->setData(audio_stream->wrongTimestampTimestamps(relative_timestamps), audio_stream->wrongTimestampSamples(y_offset));
322 seq_graph->setName(tr("Wrong Timestamps"));
325 seq_graph->removeFromLegend();
329 if (audio_stream->insertedSilences() > 0) {
331 QCPGraph *seq_graph = ui->audioPlot->addGraph();
332 seq_graph->setLineStyle(QCPGraph::lsNone);
333 seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, tango_butter_5, Qt::white, 4)); // Arbitrary
334 seq_graph->setSelectable(false);
335 seq_graph->setData(audio_stream->insertedSilenceTimestamps(relative_timestamps), audio_stream->insertedSilenceSamples(y_offset));
337 seq_graph->setName(tr("Inserted Silence"));
340 seq_graph->removeFromLegend();
344 ui->audioPlot->legend->setVisible(show_legend);
346 for (int col = 0; col < ui->streamTreeWidget->columnCount() - 1; col++) {
347 ui->streamTreeWidget->resizeColumnToContents(col);
350 ui->audioPlot->replot();
351 if (rescale_axes) resetXAxis();
356 void RtpPlayerDialog::addRtpStream(rtpstream_info_t *rtpstream)
358 if (!rtpstream) return;
360 // Find the RTP streams associated with this conversation.
361 // gtk/rtp_player.c:mark_rtp_stream_to_play does this differently.
363 RtpAudioStream *audio_stream = NULL;
364 int tli_count = ui->streamTreeWidget->topLevelItemCount();
365 for (int row = 0; row < tli_count; row++) {
366 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
367 RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
368 if (row_stream->isMatch(rtpstream)) {
369 audio_stream = row_stream;
375 audio_stream = new RtpAudioStream(this, rtpstream);
376 audio_stream->setColor(ColorUtils::graphColor(tli_count));
378 QTreeWidgetItem *ti = new QTreeWidgetItem(ui->streamTreeWidget);
379 ti->setText(src_addr_col_, address_to_qstring(&rtpstream->id.src_addr));
380 ti->setText(src_port_col_, QString::number(rtpstream->id.src_port));
381 ti->setText(dst_addr_col_, address_to_qstring(&rtpstream->id.dst_addr));
382 ti->setText(dst_port_col_, QString::number(rtpstream->id.dst_port));
383 ti->setText(ssrc_col_, int_to_qstring(rtpstream->id.ssrc, 8, 16));
384 ti->setText(first_pkt_col_, QString::number(rtpstream->setup_frame_number));
385 ti->setText(num_pkts_col_, QString::number(rtpstream->packet_count));
387 ti->setData(stream_data_col_, Qt::UserRole, QVariant::fromValue(audio_stream));
389 for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
390 ti->setTextColor(col, audio_stream->color());
393 connect(ui->playButton, SIGNAL(clicked(bool)), audio_stream, SLOT(startPlaying()));
394 connect(ui->stopButton, SIGNAL(clicked(bool)), audio_stream, SLOT(stopPlaying()));
396 connect(audio_stream, SIGNAL(startedPlaying()), this, SLOT(updateWidgets()));
397 connect(audio_stream, SIGNAL(finishedPlaying()), this, SLOT(updateWidgets()));
398 connect(audio_stream, SIGNAL(playbackError(QString)), this, SLOT(setPlaybackError(QString)));
399 connect(audio_stream, SIGNAL(processedSecs(double)), this, SLOT(setPlayPosition(double)));
401 // TODO: does not do anything
402 // audio_stream->addRtpStream(rtpstream);
403 double start_rel_time = nstime_to_sec(&rtpstream->start_rel_time);
405 start_rel_time_ = start_rel_time;
407 start_rel_time_ = qMin(start_rel_time_, start_rel_time);
409 RTP_STREAM_DEBUG("adding stream %d to layout, %u packets, start %u",
410 ui->streamTreeWidget->topLevelItemCount(),
411 rtpstream->packet_count,
412 rtpstream->start_fd ? rtpstream->start_fd->num : 0);
415 void RtpPlayerDialog::showEvent(QShowEvent *)
417 QList<int> split_sizes = ui->splitter->sizes();
418 int tot_size = split_sizes[0] + split_sizes[1];
419 int plot_size = tot_size * 3 / 4;
421 split_sizes << plot_size << tot_size - plot_size;
422 ui->splitter->setSizes(split_sizes);
425 void RtpPlayerDialog::keyPressEvent(QKeyEvent *event)
427 int pan_secs = event->modifiers() & Qt::ShiftModifier ? 1 : 10;
429 switch(event->key()) {
431 case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
432 case Qt::Key_O: // GTK+
434 on_actionZoomOut_triggered();
437 case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
438 case Qt::Key_I: // GTK+
439 on_actionZoomIn_triggered();
448 panXAxis(-1 * pan_secs);
452 // toggleTracerStyle();
456 case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
458 on_actionReset_triggered();
462 on_actionGoToPacket_triggered();
465 // on_actionToggleTimeOrigin_triggered();
468 // on_actionDragZoom_triggered();
472 QDialog::keyPressEvent(event);
475 void RtpPlayerDialog::updateWidgets()
477 bool enable_play = true;
478 bool enable_stop = false;
479 bool enable_timing = true;
481 for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
482 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
484 RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
485 if (audio_stream->outputState() != QAudio::IdleState) {
488 enable_timing = false;
492 ui->playButton->setEnabled(enable_play);
493 ui->outputDeviceComboBox->setEnabled(enable_play);
494 ui->stopButton->setEnabled(enable_stop);
495 cur_play_pos_->setVisible(enable_stop);
497 ui->jitterSpinBox->setEnabled(enable_timing);
498 ui->timingComboBox->setEnabled(enable_timing);
499 ui->todCheckBox->setEnabled(enable_timing);
502 ui->audioPlot->replot();
505 void RtpPlayerDialog::graphClicked(QMouseEvent *event)
508 if (event->button() == Qt::RightButton) {
509 ctx_menu_->exec(event->globalPos());
511 ui->audioPlot->setFocus();
514 void RtpPlayerDialog::updateHintLabel()
516 int packet_num = getHoveredPacket();
517 QString hint = "<small><i>";
519 if (packet_num > 0) {
520 hint += tr("%1. Press \"G\" to go to packet %2")
521 .arg(getHoveredTime())
523 } else if (!playback_error_.isEmpty()) {
524 hint += playback_error_;
527 hint += "</i></small>";
528 ui->hintLabel->setText(hint);
531 void RtpPlayerDialog::resetXAxis()
533 QCustomPlot *ap = ui->audioPlot;
534 QCPRange x_range = ap->xAxis->range();
536 double pixel_pad = 10.0; // per side
538 ap->rescaleAxes(true);
540 double axis_pixels = ap->xAxis->axisRect()->width();
541 ap->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, x_range.center());
543 axis_pixels = ap->yAxis->axisRect()->height();
544 ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
549 void RtpPlayerDialog::setPlayPosition(double secs)
551 secs+= start_rel_time_;
552 double cur_secs = cur_play_pos_->point1->key();
553 if (secs > cur_secs) {
554 cur_play_pos_->point1->setCoords(secs, 0.0);
555 cur_play_pos_->point2->setCoords(secs, 1.0);
556 ui->audioPlot->replot();
560 gboolean RtpPlayerDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
562 RtpPlayerDialog *rtp_player_dialog = dynamic_cast<RtpPlayerDialog *>((RtpPlayerDialog*)tapinfo_ptr);
563 if (!rtp_player_dialog) return FALSE;
565 const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
566 if (!rtpinfo) return FALSE;
568 /* we ignore packets that are not displayed */
569 if (pinfo->fd->flags.passed_dfilter == 0)
571 /* also ignore RTP Version != 2 */
572 else if (rtpinfo->info_version != 2)
575 rtp_player_dialog->addPacket(pinfo, rtpinfo);
580 void RtpPlayerDialog::addPacket(packet_info *pinfo, const _rtp_info *rtpinfo)
582 for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
583 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
584 RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
586 if (row_stream->isMatch(pinfo, rtpinfo)) {
587 row_stream->addRtpPacket(pinfo, rtpinfo);
591 // qDebug() << "=ap no match!" << address_to_qstring(&pinfo->src) << address_to_qstring(&pinfo->dst);
594 void RtpPlayerDialog::zoomXAxis(bool in)
596 QCustomPlot *ap = ui->audioPlot;
597 double h_factor = ap->axisRect()->rangeZoomFactor(Qt::Horizontal);
600 h_factor = pow(h_factor, -1);
603 ap->xAxis->scaleRange(h_factor, ap->xAxis->range().center());
607 // XXX I tried using seconds but pixels make more sense at varying zoom
609 void RtpPlayerDialog::panXAxis(int x_pixels)
611 QCustomPlot *ap = ui->audioPlot;
614 h_pan = ap->xAxis->range().size() * x_pixels / ap->xAxis->axisRect()->width();
616 ap->xAxis->moveRange(h_pan);
621 void RtpPlayerDialog::on_playButton_clicked()
623 double left = start_rel_time_;
624 cur_play_pos_->point1->setCoords(left, 0.0);
625 cur_play_pos_->point2->setCoords(left, 1.0);
626 cur_play_pos_->setVisible(true);
627 playback_error_.clear();
628 ui->audioPlot->replot();
631 void RtpPlayerDialog::on_stopButton_clicked()
633 cur_play_pos_->setVisible(false);
636 void RtpPlayerDialog::on_actionReset_triggered()
641 void RtpPlayerDialog::on_actionZoomIn_triggered()
646 void RtpPlayerDialog::on_actionZoomOut_triggered()
651 void RtpPlayerDialog::on_actionMoveLeft10_triggered()
656 void RtpPlayerDialog::on_actionMoveRight10_triggered()
661 void RtpPlayerDialog::on_actionMoveLeft1_triggered()
666 void RtpPlayerDialog::on_actionMoveRight1_triggered()
671 void RtpPlayerDialog::on_actionGoToPacket_triggered()
673 int packet_num = getHoveredPacket();
674 if (packet_num > 0) emit goToPacket(packet_num);
677 // XXX Make waveform graphs selectable and update the treewidget selection accordingly.
678 void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
680 for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
681 QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
682 QCPGraph *audio_graph = ti->data(graph_data_col_, Qt::UserRole).value<QCPGraph*>();
684 audio_graph->setSelected(ti->isSelected());
687 ui->audioPlot->replot();
690 double RtpPlayerDialog::getLowestTimestamp()
692 double lowest = QCPRange::maxRange;
694 for (int i = 0; i < ui->audioPlot->graphCount(); i++) {
695 QCPGraph *graph = ui->audioPlot->graph(i);
696 if (!graph->visible()) continue;
697 QCPDataMap *dm = graph->data();
698 if (dm->keys().length() < 1) continue;
699 lowest = qMin(lowest, dm->keys().first());
704 const QString RtpPlayerDialog::getHoveredTime()
706 QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
707 if (!ti) return tr("Unknown");
710 double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
712 if (ui->todCheckBox->isChecked()) {
713 QDateTime date_time = QDateTime::fromMSecsSinceEpoch(ts * 1000.0);
714 time_str = date_time.toString("yyyy-MM-dd hh:mm:ss.zzz");
716 time_str = QString::number(ts, 'f', 3);
722 int RtpPlayerDialog::getHoveredPacket()
724 QTreeWidgetItem *ti = ui->streamTreeWidget->currentItem();
727 RtpAudioStream *audio_stream = ti->data(src_addr_col_, Qt::UserRole).value<RtpAudioStream*>();
729 double ts = ui->audioPlot->xAxis->pixelToCoord(ui->audioPlot->mapFromGlobal(QCursor::pos()).x());
731 return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
734 // Used by RtpAudioStreams to initialize QAudioOutput. We could alternatively
735 // pass the corresponding QAudioDeviceInfo directly.
736 QString RtpPlayerDialog::currentOutputDeviceName()
738 return ui->outputDeviceComboBox->currentText();
741 void RtpPlayerDialog::on_outputDeviceComboBox_currentIndexChanged(const QString &)
746 void RtpPlayerDialog::on_jitterSpinBox_valueChanged(double)
751 void RtpPlayerDialog::on_timingComboBox_currentIndexChanged(int)
756 void RtpPlayerDialog::on_todCheckBox_toggled(bool)
758 QCPAxis *x_axis = ui->audioPlot->xAxis;
759 double old_lowest = getLowestTimestamp();
762 x_axis->moveRange(getLowestTimestamp() - old_lowest);
763 ui->audioPlot->replot();
766 void RtpPlayerDialog::on_buttonBox_helpRequested()
768 wsApp->helpTopicAction(HELP_TELEPHONY_RTP_PLAYER_DIALOG);
772 // This also serves as a title in RtpAudioFrame.
773 static const QString stream_key_tmpl_ = "%1:%2 " UTF8_RIGHTWARDS_ARROW " %3:%4 0x%5";
774 const QString RtpPlayerDialog::streamKey(const rtpstream_info_t *rtpstream)
776 const QString stream_key = QString(stream_key_tmpl_)
777 .arg(address_to_display_qstring(&rtpstream->src_addr))
778 .arg(rtpstream->src_port)
779 .arg(address_to_display_qstring(&rtpstream->dst_addr))
780 .arg(rtpstream->dst_port)
781 .arg(rtpstream->ssrc, 0, 16);
785 const QString RtpPlayerDialog::streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo)
787 const QString stream_key = QString(stream_key_tmpl_)
788 .arg(address_to_display_qstring(&pinfo->src))
790 .arg(address_to_display_qstring(&pinfo->dst))
791 .arg(pinfo->destport)
792 .arg(rtpinfo->info_sync_src, 0, 16);
797 #endif // QT_MULTIMEDIA_LIB
805 * indent-tabs-mode: nil
808 * ex: set shiftwidth=4 tabstop=8 expandtab:
809 * :indentSize=4:tabSize=8:noTabs=true: