1 /* rtp_player_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include "config.h"
11 
12 #include <ui/rtp_media.h>
13 #include <ui/tap-rtp-common.h>
14 #include "rtp_player_dialog.h"
15 #include <ui_rtp_player_dialog.h>
16 #include "epan/epan_dissect.h"
17 
18 #include "file.h"
19 #include "frame_tvbuff.h"
20 
21 #include "rtp_analysis_dialog.h"
22 
23 #ifdef QT_MULTIMEDIA_LIB
24 
25 #include <epan/dissectors/packet-rtp.h>
26 #include <epan/to_str.h>
27 
28 #include <wsutil/report_message.h>
29 #include <wsutil/utf8_entities.h>
30 #include <wsutil/pint.h>
31 
32 #include <ui/qt/utils/color_utils.h>
33 #include <ui/qt/widgets/qcustomplot.h>
34 #include <ui/qt/utils/qt_ui_utils.h>
35 #include "rtp_audio_stream.h"
36 #include <ui/qt/utils/tango_colors.h>
37 #include <widgets/rtp_audio_graph.h>
38 #include "wireshark_application.h"
39 #include "ui/qt/widgets/wireshark_file_dialog.h"
40 
41 #include <QAudio>
42 #include <QAudioDeviceInfo>
43 #include <QFrame>
44 #include <QMenu>
45 #include <QVBoxLayout>
46 #include <QTimer>
47 
48 #include <QAudioFormat>
49 #include <QAudioOutput>
50 #include <ui/qt/utils/rtp_audio_silence_generator.h>
51 
52 #endif // QT_MULTIMEDIA_LIB
53 
54 #include <QPushButton>
55 #include <QToolButton>
56 
57 #include <ui/qt/utils/stock_icon.h>
58 #include "wireshark_application.h"
59 
60 // To do:
61 // - Threaded decoding?
62 
63 // Current and former RTP player bugs. Many have attachments that can be usef for testing.
64 // Bug 3368 - The timestamp line in a RTP or RTCP packet display's "Not Representable"
65 // Bug 3952 - VoIP Call RTP Player: audio played is corrupted when RFC2833 packets are present
66 // Bug 4960 - RTP Player: Audio and visual feedback get rapidly out of sync
67 // Bug 5527 - Adding arbitrary value to x-axis RTP player
68 // Bug 7935 - Wrong Timestamps in RTP Player-Decode
69 // Bug 8007 - UI gets confused on playing decoded audio in rtp_player
70 // Bug 9007 - Switching SSRC values in RTP stream
71 // Bug 10613 - RTP audio player crashes
72 // Bug 11125 - RTP Player does not show progress in selected stream in Window 7
73 // Bug 11409 - Wireshark crashes when using RTP player
74 // Bug 12166 - RTP audio player crashes
75 
76 // In some places we match by conv/call number, in others we match by first frame.
77 
78 enum {
79     channel_col_,
80     src_addr_col_,
81     src_port_col_,
82     dst_addr_col_,
83     dst_port_col_,
84     ssrc_col_,
85     first_pkt_col_,
86     num_pkts_col_,
87     time_span_col_,
88     sample_rate_col_,
89     play_rate_col_,
90     payload_col_,
91 
92     stream_data_col_ = src_addr_col_, // RtpAudioStream
93     graph_audio_data_col_ = src_port_col_, // QCPGraph (wave)
94     graph_sequence_data_col_ = dst_addr_col_, // QCPGraph (sequence)
95     graph_jitter_data_col_ = dst_port_col_, // QCPGraph (jitter)
96     graph_timestamp_data_col_ = ssrc_col_, // QCPGraph (timestamp)
97     // first_pkt_col_ is skipped, it is used for real data
98     graph_silence_data_col_ = num_pkts_col_, // QCPGraph (silence)
99 };
100 
101 class RtpPlayerTreeWidgetItem : public QTreeWidgetItem
102 {
103 public:
RtpPlayerTreeWidgetItem(QTreeWidget * tree)104     RtpPlayerTreeWidgetItem(QTreeWidget *tree) :
105         QTreeWidgetItem(tree)
106     {
107     }
108 
operator <(const QTreeWidgetItem & other) const109     bool operator< (const QTreeWidgetItem &other) const
110     {
111         // Handle numeric sorting
112         switch (treeWidget()->sortColumn()) {
113             case src_port_col_:
114             case dst_port_col_:
115             case num_pkts_col_:
116             case sample_rate_col_:
117                 return text(treeWidget()->sortColumn()).toInt() < other.text(treeWidget()->sortColumn()).toInt();
118             case play_rate_col_:
119                 return text(treeWidget()->sortColumn()).toInt() < other.text(treeWidget()->sortColumn()).toInt();
120             case first_pkt_col_:
121                 int v1;
122                 int v2;
123 
124                 v1 = data(first_pkt_col_, Qt::UserRole).toInt();
125                 v2 = other.data(first_pkt_col_, Qt::UserRole).toInt();
126 
127                 return v1 < v2;
128             default:
129                 // Fall back to string comparison
130                 return QTreeWidgetItem::operator <(other);
131                 break;
132         }
133     }
134 };
135 
136 
137 RtpPlayerDialog *RtpPlayerDialog::pinstance_{nullptr};
138 std::mutex RtpPlayerDialog::mutex_;
139 
openRtpPlayerDialog(QWidget & parent,CaptureFile & cf,QObject * packet_list,bool capture_running)140 RtpPlayerDialog *RtpPlayerDialog::openRtpPlayerDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list, bool capture_running)
141 {
142     std::lock_guard<std::mutex> lock(mutex_);
143     if (pinstance_ == nullptr)
144     {
145         pinstance_ = new RtpPlayerDialog(parent, cf, capture_running);
146         connect(pinstance_, SIGNAL(goToPacket(int)),
147                 packet_list, SLOT(goToPacket(int)));
148     }
149     return pinstance_;
150 }
151 
RtpPlayerDialog(QWidget & parent,CaptureFile & cf,bool capture_running)152 RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf, bool capture_running) :
153     WiresharkDialog(parent, cf)
154 #ifdef QT_MULTIMEDIA_LIB
155     , ui(new Ui::RtpPlayerDialog)
156     , first_stream_rel_start_time_(0.0)
157     , first_stream_abs_start_time_(0.0)
158     , first_stream_rel_stop_time_(0.0)
159     , streams_length_(0.0)
160     , start_marker_time_(0.0)
161 #endif // QT_MULTIMEDIA_LIB
162     , number_ticker_(new QCPAxisTicker)
163     , datetime_ticker_(new QCPAxisTickerDateTime)
164     , stereo_available_(false)
165     , marker_stream_(0)
166     , marker_stream_requested_out_rate_(0)
167     , last_ti_(0)
168     , listener_removed_(true)
169     , block_redraw_(false)
170     , lock_ui_(0)
171     , read_capture_enabled_(capture_running)
172     , silence_skipped_time_(0.0)
173 {
174     ui->setupUi(this);
175     loadGeometry(parent.width(), parent.height());
176     setWindowTitle(wsApp->windowTitleString(tr("RTP Player")));
177     ui->streamTreeWidget->installEventFilter(this);
178     ui->audioPlot->installEventFilter(this);
179     installEventFilter(this);
180 
181 #ifdef QT_MULTIMEDIA_LIB
182     ui->splitter->setStretchFactor(0, 3);
183     ui->splitter->setStretchFactor(1, 1);
184 
185     ui->streamTreeWidget->sortByColumn(first_pkt_col_, Qt::AscendingOrder);
186 
187     graph_ctx_menu_ = new QMenu(this);
188 
189     graph_ctx_menu_->addAction(ui->actionZoomIn);
190     graph_ctx_menu_->addAction(ui->actionZoomOut);
191     graph_ctx_menu_->addAction(ui->actionReset);
192     graph_ctx_menu_->addSeparator();
193     graph_ctx_menu_->addAction(ui->actionMoveRight10);
194     graph_ctx_menu_->addAction(ui->actionMoveLeft10);
195     graph_ctx_menu_->addAction(ui->actionMoveRight1);
196     graph_ctx_menu_->addAction(ui->actionMoveLeft1);
197     graph_ctx_menu_->addSeparator();
198     graph_ctx_menu_->addAction(ui->actionGoToPacket);
199     graph_ctx_menu_->addAction(ui->actionGoToSetupPacketPlot);
200     set_action_shortcuts_visible_in_context_menu(graph_ctx_menu_->actions());
201 
202     ui->streamTreeWidget->setMouseTracking(true);
203     connect(ui->streamTreeWidget, SIGNAL(itemEntered(QTreeWidgetItem *, int)),
204             this, SLOT(itemEntered(QTreeWidgetItem *, int)));
205 
206     connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
207             this, SLOT(mouseMovePlot(QMouseEvent*)));
208     connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
209             this, SLOT(mouseMovePlot(QMouseEvent*)));
210     connect(ui->audioPlot, SIGNAL(mousePress(QMouseEvent*)),
211             this, SLOT(graphClicked(QMouseEvent*)));
212     connect(ui->audioPlot, SIGNAL(mouseDoubleClick(QMouseEvent*)),
213             this, SLOT(graphDoubleClicked(QMouseEvent*)));
214     connect(ui->audioPlot, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)),
215             this, SLOT(plotClicked(QCPAbstractPlottable*,int,QMouseEvent*)));
216 
217     cur_play_pos_ = new QCPItemStraightLine(ui->audioPlot);
218     cur_play_pos_->setVisible(false);
219 
220     start_marker_pos_ = new QCPItemStraightLine(ui->audioPlot);
221     start_marker_pos_->setPen(QPen(Qt::green,4));
222     setStartPlayMarker(0);
223     drawStartPlayMarker();
224     start_marker_pos_->setVisible(true);
225 
226     datetime_ticker_->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss.zzz");
227 
228     ui->audioPlot->xAxis->setNumberFormat("gb");
229     ui->audioPlot->xAxis->setNumberPrecision(3);
230     ui->audioPlot->xAxis->setTicker(datetime_ticker_);
231     ui->audioPlot->yAxis->setVisible(false);
232 
233     ui->playButton->setIcon(StockIcon("media-playback-start"));
234     ui->playButton->setEnabled(false);
235     ui->pauseButton->setIcon(StockIcon("media-playback-pause"));
236     ui->pauseButton->setCheckable(true);
237     ui->pauseButton->setVisible(false);
238     ui->stopButton->setIcon(StockIcon("media-playback-stop"));
239     ui->stopButton->setEnabled(false);
240     ui->skipSilenceButton->setIcon(StockIcon("media-seek-forward"));
241     ui->skipSilenceButton->setCheckable(true);
242     ui->skipSilenceButton->setEnabled(false);
243 
244     read_btn_ = ui->buttonBox->addButton(ui->actionReadCapture->text(), QDialogButtonBox::ActionRole);
245     read_btn_->setToolTip(ui->actionReadCapture->toolTip());
246     read_btn_->setEnabled(false);
247     connect(read_btn_, SIGNAL(pressed()), this, SLOT(on_actionReadCapture_triggered()));
248 
249     inaudible_btn_ = new QToolButton();
250     ui->buttonBox->addButton(inaudible_btn_, QDialogButtonBox::ActionRole);
251     inaudible_btn_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
252     inaudible_btn_->setPopupMode(QToolButton::MenuButtonPopup);
253 
254     connect(ui->actionInaudibleButton, SIGNAL(triggered()), this, SLOT(on_actionSelectInaudible_triggered()));
255     inaudible_btn_->setDefaultAction(ui->actionInaudibleButton);
256     // Overrides text striping of shortcut undercode in QAction
257     inaudible_btn_->setText(ui->actionInaudibleButton->text());
258     inaudible_btn_->setEnabled(false);
259     inaudible_btn_->setMenu(ui->menuInaudible);
260 
261     analyze_btn_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
262 
263     prepare_btn_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
264     prepare_btn_->setToolTip(ui->actionPrepareFilter->toolTip());
265     connect(prepare_btn_, SIGNAL(pressed()), this, SLOT(on_actionPrepareFilter_triggered()));
266 
267     export_btn_ = ui->buttonBox->addButton(ui->actionExportButton->text(), QDialogButtonBox::ActionRole);
268     export_btn_->setToolTip(ui->actionExportButton->toolTip());
269     export_btn_->setEnabled(false);
270     export_btn_->setMenu(ui->menuExport);
271 
272     // Ordered, unique device names starting with the system default
273     QMap<QString, bool> out_device_map; // true == default device
274     out_device_map.insert(QAudioDeviceInfo::defaultOutputDevice().deviceName(), true);
275     foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
276         if (!out_device_map.contains(out_device.deviceName())) {
277             out_device_map.insert(out_device.deviceName(), false);
278         }
279     }
280 
281     ui->outputDeviceComboBox->blockSignals(true);
282     foreach (QString out_name, out_device_map.keys()) {
283         ui->outputDeviceComboBox->addItem(out_name);
284         if (out_device_map.value(out_name)) {
285             ui->outputDeviceComboBox->setCurrentIndex(ui->outputDeviceComboBox->count() - 1);
286         }
287     }
288     if (ui->outputDeviceComboBox->count() < 1) {
289         ui->outputDeviceComboBox->setEnabled(false);
290         ui->playButton->setEnabled(false);
291         ui->pauseButton->setEnabled(false);
292         ui->stopButton->setEnabled(false);
293         ui->skipSilenceButton->setEnabled(false);
294         ui->minSilenceSpinBox->setEnabled(false);
295         ui->outputDeviceComboBox->addItem(tr("No devices available"));
296         ui->outputAudioRate->setEnabled(false);
297     } else {
298         stereo_available_ = isStereoAvailable();
299         fillAudioRateMenu();
300     }
301     ui->outputDeviceComboBox->blockSignals(false);
302 
303     ui->audioPlot->setMouseTracking(true);
304     ui->audioPlot->setEnabled(true);
305     ui->audioPlot->setInteractions(
306                 QCP::iRangeDrag |
307                 QCP::iRangeZoom
308                 );
309 
310     graph_ctx_menu_->addSeparator();
311     list_ctx_menu_ = new QMenu(this);
312     list_ctx_menu_->addAction(ui->actionPlay);
313     graph_ctx_menu_->addAction(ui->actionPlay);
314     list_ctx_menu_->addAction(ui->actionStop);
315     graph_ctx_menu_->addAction(ui->actionStop);
316     list_ctx_menu_->addMenu(ui->menuSelect);
317     graph_ctx_menu_->addMenu(ui->menuSelect);
318     list_ctx_menu_->addMenu(ui->menuAudioRouting);
319     graph_ctx_menu_->addMenu(ui->menuAudioRouting);
320     list_ctx_menu_->addAction(ui->actionRemoveStream);
321     graph_ctx_menu_->addAction(ui->actionRemoveStream);
322     list_ctx_menu_->addAction(ui->actionGoToSetupPacketTree);
323     set_action_shortcuts_visible_in_context_menu(list_ctx_menu_->actions());
324 
325     connect(&cap_file_, SIGNAL(captureEvent(CaptureEvent)),
326             this, SLOT(captureEvent(CaptureEvent)));
327     connect(this, SIGNAL(updateFilter(QString, bool)),
328             &parent, SLOT(filterPackets(QString, bool)));
329     connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
330             &parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
331     connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
332             &parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
333     connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
334             &parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
335 #endif // QT_MULTIMEDIA_LIB
336 }
337 
338 // _U_ is used when no QT_MULTIMEDIA_LIB is available
addPlayerButton(QDialogButtonBox * button_box,QDialog * dialog _U_)339 QToolButton *RtpPlayerDialog::addPlayerButton(QDialogButtonBox *button_box, QDialog *dialog _U_)
340 {
341     if (!button_box) return NULL;
342 
343     QAction *ca;
344     QToolButton *player_button = new QToolButton();
345     button_box->addButton(player_button, QDialogButtonBox::ActionRole);
346     player_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
347     player_button->setPopupMode(QToolButton::MenuButtonPopup);
348 
349     ca = new QAction(tr("&Play Streams"));
350     ca->setToolTip(tr("Open RTP player dialog"));
351     ca->setIcon(StockIcon("media-playback-start"));
352     connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerReplace()));
353     player_button->setDefaultAction(ca);
354     // Overrides text striping of shortcut undercode in QAction
355     player_button->setText(ca->text());
356 
357 #if defined(QT_MULTIMEDIA_LIB)
358     QMenu *button_menu = new QMenu(player_button);
359     button_menu->setToolTipsVisible(true);
360     ca = button_menu->addAction(tr("&Set playlist"));
361     ca->setToolTip(tr("Replace existing playlist in RTP Player with new one"));
362     connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerReplace()));
363     ca = button_menu->addAction(tr("&Add to playlist"));
364     ca->setToolTip(tr("Add new set to existing playlist in RTP Player"));
365     connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerAdd()));
366     ca = button_menu->addAction(tr("&Remove from playlist"));
367     ca->setToolTip(tr("Remove selected streams from playlist in RTP Player"));
368     connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerRemove()));
369     player_button->setMenu(button_menu);
370 #else
371     player_button->setEnabled(false);
372     player_button->setText(tr("No Audio"));
373 #endif
374 
375     return player_button;
376 }
377 
378 #ifdef QT_MULTIMEDIA_LIB
~RtpPlayerDialog()379 RtpPlayerDialog::~RtpPlayerDialog()
380 {
381     std::lock_guard<std::mutex> lock(mutex_);
382     cleanupMarkerStream();
383     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
384         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
385         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
386         if (audio_stream)
387             delete audio_stream;
388     }
389     delete ui;
390     pinstance_ = nullptr;
391 }
392 
accept()393 void RtpPlayerDialog::accept()
394 {
395     if (!listener_removed_) {
396         remove_tap_listener(this);
397         listener_removed_ = true;
398     }
399 
400     int row_count = ui->streamTreeWidget->topLevelItemCount();
401     // Stop all streams before the dialogs are closed.
402     for (int row = 0; row < row_count; row++) {
403         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
404         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
405         audio_stream->stopPlaying();
406     }
407     WiresharkDialog::accept();
408 }
409 
reject()410 void RtpPlayerDialog::reject()
411 {
412     RtpPlayerDialog::accept();
413 }
414 
retapPackets()415 void RtpPlayerDialog::retapPackets()
416 {
417     if (!listener_removed_) {
418         // Retap is running, nothing better we can do
419         return;
420     }
421     lockUI();
422     ui->hintLabel->setText("<i><small>" + tr("Decoding streams...") + "</i></small>");
423     wsApp->processEvents();
424 
425     // Clear packets from existing streams before retap
426     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
427         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
428         RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
429 
430         row_stream->clearPackets();
431     }
432 
433     // destroyCheck is protection againts destroying dialog during recap.
434     // It stores dialog pointer in data() and if dialog destroyed, it
435     // returns null
436     QPointer<RtpPlayerDialog> destroyCheck=this;
437     GString *error_string;
438 
439     listener_removed_ = false;
440     error_string = register_tap_listener("rtp", this, NULL, 0, NULL, tapPacket, NULL, NULL);
441     if (error_string) {
442         report_failure("RTP Player - tap registration failed: %s", error_string->str);
443         g_string_free(error_string, TRUE);
444         unlockUI();
445         return;
446     }
447     cap_file_.retapPackets();
448 
449     // Check if dialog exists still
450     if (destroyCheck.data()) {
451         if (!listener_removed_) {
452             remove_tap_listener(this);
453             listener_removed_ = true;
454         }
455         fillTappedColumns();
456         rescanPackets(true);
457     }
458     unlockUI();
459 }
460 
rescanPackets(bool rescale_axes)461 void RtpPlayerDialog::rescanPackets(bool rescale_axes)
462 {
463     lockUI();
464     // Show information for a user - it can last long time...
465     playback_error_.clear();
466     ui->hintLabel->setText("<i><small>" + tr("Decoding streams...") + "</i></small>");
467     wsApp->processEvents();
468 
469     QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
470     int row_count = ui->streamTreeWidget->topLevelItemCount();
471 
472     // Reset stream values
473     for (int row = 0; row < row_count; row++) {
474         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
475         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
476         audio_stream->setStereoRequired(stereo_available_);
477         audio_stream->reset(first_stream_rel_start_time_);
478 
479         audio_stream->setJitterBufferSize((int) ui->jitterSpinBox->value());
480 
481         RtpAudioStream::TimingMode timing_mode = RtpAudioStream::JitterBuffer;
482         switch (ui->timingComboBox->currentIndex()) {
483         case RtpAudioStream::RtpTimestamp:
484             timing_mode = RtpAudioStream::RtpTimestamp;
485             break;
486         case RtpAudioStream::Uninterrupted:
487             timing_mode = RtpAudioStream::Uninterrupted;
488             break;
489         default:
490             break;
491         }
492         audio_stream->setTimingMode(timing_mode);
493 
494         audio_stream->decode(cur_out_device);
495     }
496 
497     for (int col = 0; col < ui->streamTreeWidget->columnCount() - 1; col++) {
498         ui->streamTreeWidget->resizeColumnToContents(col);
499     }
500 
501     createPlot(rescale_axes);
502 
503     updateWidgets();
504     unlockUI();
505 }
506 
createPlot(bool rescale_axes)507 void RtpPlayerDialog::createPlot(bool rescale_axes)
508 {
509     bool legend_out_of_sequence = false;
510     bool legend_jitter_dropped = false;
511     bool legend_wrong_timestamps = false;
512     bool legend_inserted_silences = false;
513     bool relative_timestamps = !ui->todCheckBox->isChecked();
514     int row_count = ui->streamTreeWidget->topLevelItemCount();
515     gint16 total_max_sample_value = 1;
516 
517     ui->audioPlot->clearGraphs();
518 
519     if (relative_timestamps) {
520         ui->audioPlot->xAxis->setTicker(number_ticker_);
521     } else {
522         ui->audioPlot->xAxis->setTicker(datetime_ticker_);
523     }
524 
525     // Calculate common Y scale for graphs
526     for (int row = 0; row < row_count; row++) {
527         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
528         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
529         gint16 max_sample_value = audio_stream->getMaxSampleValue();
530 
531         if (max_sample_value > total_max_sample_value) {
532             total_max_sample_value = max_sample_value;
533         }
534     }
535 
536     // Clear existing graphs
537     for (int row = 0; row < row_count; row++) {
538         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
539         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
540         int y_offset = row_count - row - 1;
541         AudioRouting audio_routing = audio_stream->getAudioRouting();
542 
543         ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant());
544         ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant());
545         ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant());
546         ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant());
547         ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant());
548 
549         // Set common scale
550         audio_stream->setMaxSampleValue(total_max_sample_value);
551 
552         // Waveform
553         RtpAudioGraph *audio_graph = new RtpAudioGraph(ui->audioPlot, audio_stream->color());
554         audio_graph->setMuted(audio_routing.isMuted());
555         audio_graph->setData(audio_stream->visualTimestamps(relative_timestamps), audio_stream->visualSamples(y_offset));
556         ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant::fromValue<RtpAudioGraph *>(audio_graph));
557         //RTP_STREAM_DEBUG("Plotting %s, %d samples", ti->text(src_addr_col_).toUtf8().constData(), audio_graph->wave->data()->size());
558 
559         QString span_str;
560         if (ui->todCheckBox->isChecked()) {
561             QDateTime date_time1 = QDateTime::fromMSecsSinceEpoch((audio_stream->startRelTime() + first_stream_abs_start_time_ - audio_stream->startRelTime()) * 1000.0);
562             QDateTime date_time2 = QDateTime::fromMSecsSinceEpoch((audio_stream->stopRelTime() + first_stream_abs_start_time_ - audio_stream->startRelTime()) * 1000.0);
563             QString time_str1 = date_time1.toString("yyyy-MM-dd hh:mm:ss.zzz");
564             QString time_str2 = date_time2.toString("yyyy-MM-dd hh:mm:ss.zzz");
565             span_str = QString("%1 - %2 (%3)")
566                 .arg(time_str1)
567                 .arg(time_str2)
568                 .arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1));
569         } else {
570             span_str = QString("%1 - %2 (%3)")
571                 .arg(QString::number(audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1))
572                 .arg(QString::number(audio_stream->stopRelTime(), 'f', prefs.gui_decimal_places1))
573                 .arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1));
574         }
575         ti->setText(time_span_col_, span_str);
576         ti->setText(sample_rate_col_, QString::number(audio_stream->sampleRate()));
577         ti->setText(play_rate_col_, QString::number(audio_stream->playRate()));
578         ti->setText(payload_col_, audio_stream->payloadNames().join(", "));
579 
580         if (audio_stream->outOfSequence() > 0) {
581             // Sequence numbers
582             QCPGraph *seq_graph = ui->audioPlot->addGraph();
583             seq_graph->setLineStyle(QCPGraph::lsNone);
584             seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssSquare, tango_aluminium_6, Qt::white, wsApp->font().pointSize())); // Arbitrary
585             seq_graph->setSelectable(QCP::stNone);
586             seq_graph->setData(audio_stream->outOfSequenceTimestamps(relative_timestamps), audio_stream->outOfSequenceSamples(y_offset));
587             ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
588             if (legend_out_of_sequence) {
589                 seq_graph->removeFromLegend();
590             } else {
591                 seq_graph->setName(tr("Out of Sequence"));
592                 legend_out_of_sequence = true;
593             }
594         }
595 
596         if (audio_stream->jitterDropped() > 0) {
597             // Jitter drops
598             QCPGraph *seq_graph = ui->audioPlot->addGraph();
599             seq_graph->setLineStyle(QCPGraph::lsNone);
600             seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, tango_scarlet_red_5, Qt::white, wsApp->font().pointSize())); // Arbitrary
601             seq_graph->setSelectable(QCP::stNone);
602             seq_graph->setData(audio_stream->jitterDroppedTimestamps(relative_timestamps), audio_stream->jitterDroppedSamples(y_offset));
603             ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
604             if (legend_jitter_dropped) {
605                 seq_graph->removeFromLegend();
606             } else {
607                 seq_graph->setName(tr("Jitter Drops"));
608                 legend_jitter_dropped = true;
609             }
610         }
611 
612         if (audio_stream->wrongTimestamps() > 0) {
613             // Wrong timestamps
614             QCPGraph *seq_graph = ui->audioPlot->addGraph();
615             seq_graph->setLineStyle(QCPGraph::lsNone);
616             seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDiamond, tango_sky_blue_5, Qt::white, wsApp->font().pointSize())); // Arbitrary
617             seq_graph->setSelectable(QCP::stNone);
618             seq_graph->setData(audio_stream->wrongTimestampTimestamps(relative_timestamps), audio_stream->wrongTimestampSamples(y_offset));
619             ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
620             if (legend_wrong_timestamps) {
621                 seq_graph->removeFromLegend();
622             } else {
623                 seq_graph->setName(tr("Wrong Timestamps"));
624                 legend_wrong_timestamps = true;
625             }
626         }
627 
628         if (audio_stream->insertedSilences() > 0) {
629             // Inserted silence
630             QCPGraph *seq_graph = ui->audioPlot->addGraph();
631             seq_graph->setLineStyle(QCPGraph::lsNone);
632             seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, tango_butter_5, Qt::white, wsApp->font().pointSize())); // Arbitrary
633             seq_graph->setSelectable(QCP::stNone);
634             seq_graph->setData(audio_stream->insertedSilenceTimestamps(relative_timestamps), audio_stream->insertedSilenceSamples(y_offset));
635             ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
636             if (legend_inserted_silences) {
637                 seq_graph->removeFromLegend();
638             } else {
639                 seq_graph->setName(tr("Inserted Silence"));
640                 legend_inserted_silences = true;
641             }
642         }
643     }
644     ui->audioPlot->legend->setVisible(legend_out_of_sequence || legend_jitter_dropped || legend_wrong_timestamps || legend_inserted_silences);
645 
646     ui->audioPlot->replot();
647     if (rescale_axes) resetXAxis();
648 }
649 
fillTappedColumns()650 void RtpPlayerDialog::fillTappedColumns()
651 {
652     // true just for first stream
653     bool is_first = true;
654 
655     // Get all rows, immutable list. Later changes in rows migth reorder them
656     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->findItems(
657         QString("*"), Qt::MatchWrap | Qt::MatchWildcard | Qt::MatchRecursive);
658 
659     // Update rows by calculated values, it might reorder them in view...
660     foreach(QTreeWidgetItem *ti, items) {
661         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
662         if (audio_stream) {
663             rtpstream_info_t *rtpstream = audio_stream->getStreamInfo();
664 
665             // 0xFFFFFFFF mean no setup frame
666             // first_packet_num == setup_frame_number happens, when
667             // rtp_udp is active or Decode as was used
668             if ((rtpstream->setup_frame_number == 0xFFFFFFFF) ||
669                 (rtpstream->rtp_stats.first_packet_num == rtpstream->setup_frame_number)
670                ) {
671                 int packet = rtpstream->rtp_stats.first_packet_num;
672                 ti->setText(first_pkt_col_, QString("RTP %1").arg(packet));
673                 ti->setData(first_pkt_col_, Qt::UserRole, QVariant(packet));
674             } else {
675                 int packet = rtpstream->setup_frame_number;
676                 ti->setText(first_pkt_col_, QString("SETUP %1").arg(rtpstream->setup_frame_number));
677                 ti->setData(first_pkt_col_, Qt::UserRole, QVariant(packet));
678             }
679             ti->setText(num_pkts_col_, QString::number(rtpstream->packet_count));
680             updateStartStopTime(rtpstream, is_first);
681             is_first = false;
682         }
683     }
684     setMarkers();
685 }
686 
addSingleRtpStream(rtpstream_id_t * id)687 void RtpPlayerDialog::addSingleRtpStream(rtpstream_id_t *id)
688 {
689     bool found = false;
690 
691     AudioRouting audio_routing = AudioRouting(AUDIO_UNMUTED, channel_mono);
692 
693     if (!id) return;
694 
695     // Find the RTP streams associated with this conversation.
696     // gtk/rtp_player.c:mark_rtp_stream_to_play does this differently.
697 
698     QList<RtpAudioStream *> streams = stream_hash_.values(rtpstream_id_to_hash(id));
699     for (int i = 0; i < streams.size(); i++) {
700         RtpAudioStream *row_stream = streams.at(i);
701         if (row_stream->isMatch(id)) {
702             found = true;
703             break;
704         }
705     }
706 
707 
708     if (found) {
709        return;
710     }
711 
712     try {
713         int tli_count = ui->streamTreeWidget->topLevelItemCount();
714 
715         RtpAudioStream *audio_stream = new RtpAudioStream(this, id, stereo_available_);
716         audio_stream->setColor(ColorUtils::graphColor(tli_count));
717 
718         QTreeWidgetItem *ti = new RtpPlayerTreeWidgetItem(ui->streamTreeWidget);
719         stream_hash_.insert(rtpstream_id_to_hash(id), audio_stream);
720         ti->setText(src_addr_col_, address_to_qstring(&(id->src_addr)));
721         ti->setText(src_port_col_, QString::number(id->src_port));
722         ti->setText(dst_addr_col_, address_to_qstring(&(id->dst_addr)));
723         ti->setText(dst_port_col_, QString::number(id->dst_port));
724         ti->setText(ssrc_col_, int_to_qstring(id->ssrc, 8, 16));
725 
726         // Calculated items are updated after every retapPackets()
727 
728         ti->setData(stream_data_col_, Qt::UserRole, QVariant::fromValue(audio_stream));
729         if (stereo_available_) {
730             if (tli_count%2) {
731                 audio_routing.setChannel(channel_stereo_right);
732             } else {
733                 audio_routing.setChannel(channel_stereo_left);
734             }
735         } else {
736             audio_routing.setChannel(channel_mono);
737         }
738         ti->setToolTip(channel_col_, QString(tr("Double click on cell to change audio routing")));
739         formatAudioRouting(ti, audio_routing);
740         audio_stream->setAudioRouting(audio_routing);
741 
742         for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
743             QBrush fgBrush = ti->foreground(col);
744             fgBrush.setColor(audio_stream->color());
745             ti->setForeground(col, fgBrush);
746         }
747 
748         connect(audio_stream, SIGNAL(finishedPlaying(RtpAudioStream *, QAudio::Error)), this, SLOT(playFinished(RtpAudioStream *, QAudio::Error)));
749         connect(audio_stream, SIGNAL(playbackError(QString)), this, SLOT(setPlaybackError(QString)));
750     } catch (...) {
751         qWarning() << "Stream ignored, try to add fewer streams to playlist";
752     }
753 
754     RTP_STREAM_DEBUG("adding stream %d to layout",
755                      ui->streamTreeWidget->topLevelItemCount());
756 }
757 
lockUI()758 void RtpPlayerDialog::lockUI()
759 {
760     if (0 == lock_ui_++) {
761         if (playing_streams_.count() > 0) {
762             on_stopButton_clicked();
763         }
764         setEnabled(false);
765     }
766 }
767 
unlockUI()768 void RtpPlayerDialog::unlockUI()
769 {
770     if (--lock_ui_ == 0) {
771         setEnabled(true);
772     }
773 }
774 
replaceRtpStreams(QVector<rtpstream_id_t * > stream_ids)775 void RtpPlayerDialog::replaceRtpStreams(QVector<rtpstream_id_t *> stream_ids)
776 {
777     std::lock_guard<std::mutex> lock(mutex_);
778     lockUI();
779 
780     // Delete all existing rows
781     if (last_ti_) {
782         highlightItem(last_ti_, false);
783         last_ti_ = NULL;
784     }
785 
786     for (int row = ui->streamTreeWidget->topLevelItemCount() - 1; row >= 0; row--) {
787         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
788         removeRow(ti);
789     }
790 
791     // Add all new streams
792     for (int i=0; i < stream_ids.size(); i++) {
793         addSingleRtpStream(stream_ids[i]);
794     }
795     setMarkers();
796 
797     unlockUI();
798 #ifdef QT_MULTIMEDIA_LIB
799     QTimer::singleShot(0, this, SLOT(retapPackets()));
800 #endif
801 }
802 
addRtpStreams(QVector<rtpstream_id_t * > stream_ids)803 void RtpPlayerDialog::addRtpStreams(QVector<rtpstream_id_t *> stream_ids)
804 {
805     std::lock_guard<std::mutex> lock(mutex_);
806     lockUI();
807 
808     int tli_count = ui->streamTreeWidget->topLevelItemCount();
809 
810     // Add new streams
811     for (int i=0; i < stream_ids.size(); i++) {
812         addSingleRtpStream(stream_ids[i]);
813     }
814 
815     if (tli_count == 0) {
816         setMarkers();
817     }
818 
819     unlockUI();
820 #ifdef QT_MULTIMEDIA_LIB
821     QTimer::singleShot(0, this, SLOT(retapPackets()));
822 #endif
823 }
824 
removeRtpStreams(QVector<rtpstream_id_t * > stream_ids)825 void RtpPlayerDialog::removeRtpStreams(QVector<rtpstream_id_t *> stream_ids)
826 {
827     std::lock_guard<std::mutex> lock(mutex_);
828     lockUI();
829     int tli_count = ui->streamTreeWidget->topLevelItemCount();
830 
831     for (int i=0; i < stream_ids.size(); i++) {
832         for (int row = 0; row < tli_count; row++) {
833             QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
834             RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
835             if (row_stream->isMatch(stream_ids[i])) {
836                 removeRow(ti);
837                 tli_count--;
838                 break;
839             }
840         }
841     }
842     updateGraphs();
843 
844     updateWidgets();
845     unlockUI();
846 }
847 
setMarkers()848 void RtpPlayerDialog::setMarkers()
849 {
850     setStartPlayMarker(0);
851     drawStartPlayMarker();
852 }
853 
showEvent(QShowEvent *)854 void RtpPlayerDialog::showEvent(QShowEvent *)
855 {
856     QList<int> split_sizes = ui->splitter->sizes();
857     int tot_size = split_sizes[0] + split_sizes[1];
858     int plot_size = tot_size * 3 / 4;
859     split_sizes.clear();
860     split_sizes << plot_size << tot_size - plot_size;
861     ui->splitter->setSizes(split_sizes);
862 }
863 
eventFilter(QObject *,QEvent * event)864 bool RtpPlayerDialog::eventFilter(QObject *, QEvent *event)
865 {
866     if (event->type() == QEvent::KeyPress) {
867         QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
868         int pan_secs = keyEvent.modifiers() & Qt::ShiftModifier ? 1 : 10;
869 
870         switch(keyEvent.key()) {
871             case Qt::Key_Minus:
872             case Qt::Key_Underscore:    // Shifted minus on U.S. keyboards
873             case Qt::Key_O:             // GTK+
874             case Qt::Key_R:
875                 on_actionZoomOut_triggered();
876                 return true;
877             case Qt::Key_Plus:
878             case Qt::Key_Equal:         // Unshifted plus on U.S. keyboards
879             case Qt::Key_I:             // GTK+
880                 if (keyEvent.modifiers() == Qt::ControlModifier) {
881                     // Ctrl+I
882                     on_actionSelectInvert_triggered();
883                     return true;
884                 } else {
885                     // I
886                     on_actionZoomIn_triggered();
887                     return true;
888                 }
889                 break;
890             case Qt::Key_Right:
891             case Qt::Key_L:
892                 panXAxis(pan_secs);
893                 return true;
894             case Qt::Key_Left:
895             case Qt::Key_H:
896                 panXAxis(-1 * pan_secs);
897                 return true;
898             case Qt::Key_0:
899             case Qt::Key_ParenRight:    // Shifted 0 on U.S. keyboards
900                 on_actionReset_triggered();
901                 return true;
902             case Qt::Key_G:
903                 if (keyEvent.modifiers() == Qt::ShiftModifier) {
904                     // Goto SETUP frame, use correct call based on caller
905                     QPoint pos1 = ui->audioPlot->mapFromGlobal(QCursor::pos());
906                     QPoint pos2 = ui->streamTreeWidget->mapFromGlobal(QCursor::pos());
907                     if (ui->audioPlot->rect().contains(pos1)) {
908                         // audio plot, by mouse coords
909                         on_actionGoToSetupPacketPlot_triggered();
910                     } else if (ui->streamTreeWidget->rect().contains(pos2)) {
911                         // packet tree, by cursor
912                         on_actionGoToSetupPacketTree_triggered();
913                     }
914                     return true;
915                 } else {
916                     on_actionGoToPacket_triggered();
917                     return true;
918                 }
919             case Qt::Key_A:
920                 if (keyEvent.modifiers() == Qt::ControlModifier) {
921                     // Ctrl+A
922                     on_actionSelectAll_triggered();
923                     return true;
924                 } else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
925                     // Ctrl+Shift+A
926                     on_actionSelectNone_triggered();
927                     return true;
928                 }
929                 break;
930             case Qt::Key_M:
931                 if (keyEvent.modifiers() == Qt::ShiftModifier) {
932                     on_actionAudioRoutingUnmute_triggered();
933                     return true;
934                 } else if (keyEvent.modifiers() == Qt::ControlModifier) {
935                     on_actionAudioRoutingMuteInvert_triggered();
936                     return true;
937                 } else  {
938                     on_actionAudioRoutingMute_triggered();
939                     return true;
940                 }
941             case Qt::Key_Delete:
942                 on_actionRemoveStream_triggered();
943                 return true;
944             case Qt::Key_X:
945                 if (keyEvent.modifiers() == Qt::ControlModifier) {
946                     // Ctrl+X
947                     on_actionRemoveStream_triggered();
948                     return true;
949                 }
950                 break;
951             case Qt::Key_Down:
952             case Qt::Key_Up:
953             case Qt::Key_PageUp:
954             case Qt::Key_PageDown:
955             case Qt::Key_Home:
956             case Qt::Key_End:
957                 // Route keys to QTreeWidget
958                 ui->streamTreeWidget->setFocus();
959                 break;
960             case Qt::Key_P:
961                 if (keyEvent.modifiers() == Qt::NoModifier) {
962                     on_actionPlay_triggered();
963                     return true;
964                 }
965                 break;
966             case Qt::Key_S:
967                 on_actionStop_triggered();
968                 return true;
969             case Qt::Key_N:
970                 if (keyEvent.modifiers() == Qt::ShiftModifier) {
971                     // Shift+N
972                     on_actionDeselectInaudible_triggered();
973                     return true;
974                 } else {
975                     on_actionSelectInaudible_triggered();
976                     return true;
977                 }
978                 break;
979         }
980     }
981 
982     return false;
983 }
984 
contextMenuEvent(QContextMenuEvent * event)985 void RtpPlayerDialog::contextMenuEvent(QContextMenuEvent *event)
986 {
987     list_ctx_menu_->exec(event->globalPos());
988 }
989 
updateWidgets()990 void RtpPlayerDialog::updateWidgets()
991 {
992     bool enable_play = true;
993     bool enable_pause = false;
994     bool enable_stop = false;
995     bool enable_timing = true;
996     int count = ui->streamTreeWidget->topLevelItemCount();
997     int selected = ui->streamTreeWidget->selectedItems().count();
998 
999     if (count < 1) {
1000         enable_play = false;
1001         ui->skipSilenceButton->setEnabled(false);
1002         ui->minSilenceSpinBox->setEnabled(false);
1003     } else {
1004         ui->skipSilenceButton->setEnabled(true);
1005         ui->minSilenceSpinBox->setEnabled(true);
1006     }
1007 
1008     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1009         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1010 
1011         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1012         if (audio_stream->outputState() != QAudio::IdleState) {
1013             enable_play = false;
1014             enable_pause = true;
1015             enable_stop = true;
1016             enable_timing = false;
1017         }
1018     }
1019 
1020     ui->actionAudioRoutingP->setVisible(!stereo_available_);
1021     ui->actionAudioRoutingL->setVisible(stereo_available_);
1022     ui->actionAudioRoutingLR->setVisible(stereo_available_);
1023     ui->actionAudioRoutingR->setVisible(stereo_available_);
1024 
1025     ui->playButton->setEnabled(enable_play);
1026     if (enable_play) {
1027         ui->playButton->setVisible(true);
1028         ui->pauseButton->setVisible(false);
1029     } else if (enable_pause) {
1030         ui->playButton->setVisible(false);
1031         ui->pauseButton->setVisible(true);
1032     }
1033     ui->outputDeviceComboBox->setEnabled(enable_play);
1034     ui->outputAudioRate->setEnabled(enable_play);
1035     ui->pauseButton->setEnabled(enable_pause);
1036     ui->stopButton->setEnabled(enable_stop);
1037     ui->actionStop->setEnabled(enable_stop);
1038     cur_play_pos_->setVisible(enable_stop);
1039 
1040     ui->jitterSpinBox->setEnabled(enable_timing);
1041     ui->timingComboBox->setEnabled(enable_timing);
1042     ui->todCheckBox->setEnabled(enable_timing);
1043 
1044     read_btn_->setEnabled(read_capture_enabled_);
1045     inaudible_btn_->setEnabled(count > 0);
1046     analyze_btn_->setEnabled(selected > 0);
1047     prepare_btn_->setEnabled(selected > 0);
1048 
1049     updateHintLabel();
1050     ui->audioPlot->replot();
1051 }
1052 
handleItemHighlight(QTreeWidgetItem * ti,bool scroll)1053 void RtpPlayerDialog::handleItemHighlight(QTreeWidgetItem *ti, bool scroll)
1054 {
1055     if (ti) {
1056         if (ti != last_ti_) {
1057             if (last_ti_) {
1058                 highlightItem(last_ti_, false);
1059             }
1060             highlightItem(ti, true);
1061 
1062             if (scroll)
1063                 ui->streamTreeWidget->scrollToItem(ti, QAbstractItemView::EnsureVisible);
1064             ui->audioPlot->replot();
1065             last_ti_ = ti;
1066         }
1067     } else {
1068         if (last_ti_) {
1069             highlightItem(last_ti_, false);
1070             ui->audioPlot->replot();
1071             last_ti_ = NULL;
1072         }
1073     }
1074 }
1075 
highlightItem(QTreeWidgetItem * ti,bool highlight)1076 void RtpPlayerDialog::highlightItem(QTreeWidgetItem *ti, bool highlight)
1077 {
1078     QFont font;
1079     RtpAudioGraph *audio_graph;
1080 
1081     font.setBold(highlight);
1082     for(int i=0; i<ui->streamTreeWidget->columnCount(); i++) {
1083         ti->setFont(i, font);
1084     }
1085 
1086     audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
1087     if (audio_graph) {
1088         audio_graph->setHighlight(highlight);
1089     }
1090 }
1091 
itemEntered(QTreeWidgetItem * item,int column _U_)1092 void RtpPlayerDialog::itemEntered(QTreeWidgetItem *item, int column _U_)
1093 {
1094     handleItemHighlight(item, false);
1095 }
1096 
mouseMovePlot(QMouseEvent * event)1097 void RtpPlayerDialog::mouseMovePlot(QMouseEvent *event)
1098 {
1099     updateHintLabel();
1100 
1101     QTreeWidgetItem *ti = findItemByCoords(event->pos());
1102     handleItemHighlight(ti, true);
1103 }
1104 
graphClicked(QMouseEvent * event)1105 void RtpPlayerDialog::graphClicked(QMouseEvent *event)
1106 {
1107     updateWidgets();
1108     if (event->button() == Qt::RightButton) {
1109         graph_ctx_menu_->exec(event->globalPos());
1110     }
1111 }
1112 
graphDoubleClicked(QMouseEvent * event)1113 void RtpPlayerDialog::graphDoubleClicked(QMouseEvent *event)
1114 {
1115     updateWidgets();
1116     if (event->button() == Qt::LeftButton) {
1117         // Move start play line
1118         double ts = ui->audioPlot->xAxis->pixelToCoord(event->pos().x());
1119 
1120         setStartPlayMarker(ts);
1121         drawStartPlayMarker();
1122 
1123         ui->audioPlot->replot();
1124     }
1125 }
1126 
plotClicked(QCPAbstractPlottable * plottable _U_,int dataIndex _U_,QMouseEvent * event)1127 void RtpPlayerDialog::plotClicked(QCPAbstractPlottable *plottable _U_, int dataIndex _U_, QMouseEvent *event)
1128 {
1129     // Delivered plottable very often points to different element than a mouse
1130     // so we find right one by mouse coordinates
1131     QTreeWidgetItem *ti = findItemByCoords(event->pos());
1132     if (ti) {
1133         if (event->modifiers() == Qt::NoModifier) {
1134             ti->setSelected(true);
1135         } else if (event->modifiers() == Qt::ControlModifier) {
1136             ti->setSelected(!ti->isSelected());
1137         }
1138     }
1139 }
1140 
findItemByCoords(QPoint point)1141 QTreeWidgetItem *RtpPlayerDialog::findItemByCoords(QPoint point)
1142 {
1143     QCPAbstractPlottable *plottable=ui->audioPlot->plottableAt(point);
1144     if (plottable) {
1145         return findItem(plottable);
1146     }
1147 
1148     return NULL;
1149 }
1150 
findItem(QCPAbstractPlottable * plottable)1151 QTreeWidgetItem *RtpPlayerDialog::findItem(QCPAbstractPlottable *plottable)
1152 {
1153     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1154         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1155         RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
1156         if (audio_graph && audio_graph->isMyPlottable(plottable)) {
1157             return ti;
1158         }
1159     }
1160 
1161     return NULL;
1162 }
1163 
updateHintLabel()1164 void RtpPlayerDialog::updateHintLabel()
1165 {
1166     int packet_num = getHoveredPacket();
1167     QString hint = "<small><i>";
1168     double start_pos = getStartPlayMarker();
1169     int row_count = ui->streamTreeWidget->topLevelItemCount();
1170     int selected = ui->streamTreeWidget->selectedItems().count();
1171     int not_muted = 0;
1172 
1173     hint += tr("%1 streams").arg(row_count);
1174 
1175     if (row_count > 0) {
1176         if (selected > 0) {
1177             hint += tr(", %1 selected").arg(selected);
1178         }
1179 
1180         for (int row = 0; row < row_count; row++) {
1181             QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1182             RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1183             if (audio_stream && (!audio_stream->getAudioRouting().isMuted())) {
1184                 not_muted++;
1185             }
1186         }
1187 
1188         hint += tr(", %1 not muted").arg(not_muted);
1189     }
1190 
1191     if (packet_num == 0) {
1192         hint += tr(", start: %1. Double click on graph to set start of playback.")
1193                 .arg(getFormatedTime(start_pos));
1194     } else if (packet_num > 0) {
1195         hint += tr(", start: %1, cursor: %2. Press \"G\" to go to packet %3. Double click on graph to set start of playback.")
1196                 .arg(getFormatedTime(start_pos))
1197                 .arg(getFormatedHoveredTime())
1198                 .arg(packet_num);
1199     }
1200 
1201     if (!playback_error_.isEmpty()) {
1202         hint += " <font color=\"red\">";
1203         hint += playback_error_;
1204         hint += " </font>";
1205     }
1206 
1207     hint += "</i></small>";
1208     ui->hintLabel->setText(hint);
1209 }
1210 
resetXAxis()1211 void RtpPlayerDialog::resetXAxis()
1212 {
1213     QCustomPlot *ap = ui->audioPlot;
1214 
1215     double pixel_pad = 10.0; // per side
1216 
1217     ap->rescaleAxes(true);
1218 
1219     double axis_pixels = ap->xAxis->axisRect()->width();
1220     ap->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->xAxis->range().center());
1221 
1222     axis_pixels = ap->yAxis->axisRect()->height();
1223     ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
1224 
1225     ap->replot();
1226 }
1227 
updateGraphs()1228 void RtpPlayerDialog::updateGraphs()
1229 {
1230     QCustomPlot *ap = ui->audioPlot;
1231 
1232     // Create new plots, just existing ones
1233     createPlot(false);
1234 
1235     // Rescale Y axis
1236     double pixel_pad = 10.0; // per side
1237     double axis_pixels = ap->yAxis->axisRect()->height();
1238     ap->yAxis->rescale(true);
1239     ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
1240 
1241     ap->replot();
1242 }
1243 
playFinished(RtpAudioStream * stream,QAudio::Error error)1244 void RtpPlayerDialog::playFinished(RtpAudioStream *stream, QAudio::Error error)
1245 {
1246     if ((error != QAudio::NoError) && (error != QAudio::UnderrunError)) {
1247         setPlaybackError(tr("Playback of stream %1 failed!")
1248             .arg(stream->getIDAsQString())
1249         );
1250     }
1251     playing_streams_.removeOne(stream);
1252     if (playing_streams_.isEmpty()) {
1253         marker_stream_->stop();
1254         updateWidgets();
1255     }
1256 }
1257 
setPlayPosition(double secs)1258 void RtpPlayerDialog::setPlayPosition(double secs)
1259 {
1260     double cur_secs = cur_play_pos_->point1->key();
1261 
1262     if (ui->todCheckBox->isChecked()) {
1263         secs += first_stream_abs_start_time_;
1264     } else {
1265         secs += first_stream_rel_start_time_;
1266     }
1267     if (secs > cur_secs) {
1268         cur_play_pos_->point1->setCoords(secs, 0.0);
1269         cur_play_pos_->point2->setCoords(secs, 1.0);
1270         ui->audioPlot->replot();
1271     }
1272 }
1273 
setPlaybackError(const QString playback_error)1274 void RtpPlayerDialog::setPlaybackError(const QString playback_error)
1275 {
1276     playback_error_ = playback_error;
1277     updateHintLabel();
1278 }
1279 
tapPacket(void * tapinfo_ptr,packet_info * pinfo,epan_dissect_t *,const void * rtpinfo_ptr)1280 tap_packet_status RtpPlayerDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
1281 {
1282     RtpPlayerDialog *rtp_player_dialog = dynamic_cast<RtpPlayerDialog *>((RtpPlayerDialog*)tapinfo_ptr);
1283     if (!rtp_player_dialog) return TAP_PACKET_DONT_REDRAW;
1284 
1285     const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
1286     if (!rtpinfo) return TAP_PACKET_DONT_REDRAW;
1287 
1288     /* ignore RTP Version != 2 */
1289     if (rtpinfo->info_version != 2)
1290         return TAP_PACKET_DONT_REDRAW;
1291 
1292     rtp_player_dialog->addPacket(pinfo, rtpinfo);
1293 
1294     return TAP_PACKET_DONT_REDRAW;
1295 }
1296 
addPacket(packet_info * pinfo,const _rtp_info * rtpinfo)1297 void RtpPlayerDialog::addPacket(packet_info *pinfo, const _rtp_info *rtpinfo)
1298 {
1299     // Search stream in hash key, if there are multiple streams with same hash
1300     QList<RtpAudioStream *> streams = stream_hash_.values(pinfo_rtp_info_to_hash(pinfo, rtpinfo));
1301     for (int i = 0; i < streams.size(); i++) {
1302         RtpAudioStream *row_stream = streams.at(i);
1303         if (row_stream->isMatch(pinfo, rtpinfo)) {
1304             row_stream->addRtpPacket(pinfo, rtpinfo);
1305             break;
1306         }
1307     }
1308 
1309 //    qDebug() << "=ap no match!" << address_to_qstring(&pinfo->src) << address_to_qstring(&pinfo->dst);
1310 }
1311 
zoomXAxis(bool in)1312 void RtpPlayerDialog::zoomXAxis(bool in)
1313 {
1314     QCustomPlot *ap = ui->audioPlot;
1315     double h_factor = ap->axisRect()->rangeZoomFactor(Qt::Horizontal);
1316 
1317     if (!in) {
1318         h_factor = pow(h_factor, -1);
1319     }
1320 
1321     ap->xAxis->scaleRange(h_factor, ap->xAxis->range().center());
1322     ap->replot();
1323 }
1324 
1325 // XXX I tried using seconds but pixels make more sense at varying zoom
1326 // levels.
panXAxis(int x_pixels)1327 void RtpPlayerDialog::panXAxis(int x_pixels)
1328 {
1329     QCustomPlot *ap = ui->audioPlot;
1330     double h_pan;
1331 
1332     h_pan = ap->xAxis->range().size() * x_pixels / ap->xAxis->axisRect()->width();
1333     if (x_pixels) {
1334         ap->xAxis->moveRange(h_pan);
1335         ap->replot();
1336     }
1337 }
1338 
on_playButton_clicked()1339 void RtpPlayerDialog::on_playButton_clicked()
1340 {
1341     double start_time;
1342 
1343     ui->hintLabel->setText("<i><small>" + tr("Preparing to play...") + "</i></small>");
1344     wsApp->processEvents();
1345     ui->pauseButton->setChecked(false);
1346 
1347     // Protect start time against move of marker during the play
1348     start_marker_time_play_ = start_marker_time_;
1349     silence_skipped_time_ = 0.0;
1350     cur_play_pos_->point1->setCoords(start_marker_time_play_, 0.0);
1351     cur_play_pos_->point2->setCoords(start_marker_time_play_, 1.0);
1352     cur_play_pos_->setVisible(true);
1353     playback_error_.clear();
1354 
1355     if (ui->todCheckBox->isChecked()) {
1356         start_time = start_marker_time_play_;
1357     } else {
1358         start_time = start_marker_time_play_ - first_stream_rel_start_time_;
1359     }
1360 
1361     QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
1362     playing_streams_.clear();
1363     int row_count = ui->streamTreeWidget->topLevelItemCount();
1364     for (int row = 0; row < row_count; row++) {
1365         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1366         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1367         // All streams starts at first_stream_rel_start_time_
1368         audio_stream->setStartPlayTime(start_time);
1369         if (audio_stream->prepareForPlay(cur_out_device)) {
1370             playing_streams_ << audio_stream;
1371         }
1372     }
1373 
1374     // Prepare silent stream for progress marker
1375     if (!marker_stream_) {
1376         marker_stream_ = getSilenceAudioOutput();
1377     } else {
1378         marker_stream_->stop();
1379     }
1380 
1381     // Start progress marker and then audio streams
1382     marker_stream_->start(new AudioSilenceGenerator());
1383     for( int i = 0; i<playing_streams_.count(); ++i ) {
1384         playing_streams_[i]->startPlaying();
1385     }
1386 
1387     updateWidgets();
1388 }
1389 
getCurrentDeviceInfo()1390 QAudioDeviceInfo RtpPlayerDialog::getCurrentDeviceInfo()
1391 {
1392     QAudioDeviceInfo cur_out_device = QAudioDeviceInfo::defaultOutputDevice();
1393     QString cur_out_name = currentOutputDeviceName();
1394     foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
1395         if (cur_out_name == out_device.deviceName()) {
1396             cur_out_device = out_device;
1397         }
1398     }
1399 
1400     return cur_out_device;
1401 }
1402 
getSilenceAudioOutput()1403 QAudioOutput *RtpPlayerDialog::getSilenceAudioOutput()
1404 {
1405     QAudioOutput *o;
1406     QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
1407 
1408     QAudioFormat format;
1409     if (marker_stream_requested_out_rate_ > 0) {
1410         format.setSampleRate(marker_stream_requested_out_rate_);
1411     } else {
1412         format.setSampleRate(8000);
1413     }
1414     format.setSampleSize(SAMPLE_BYTES * 8); // bits
1415     format.setSampleType(QAudioFormat::SignedInt);
1416     format.setChannelCount(1);
1417     format.setCodec("audio/pcm");
1418     if (!cur_out_device.isFormatSupported(format)) {
1419         format = cur_out_device.nearestFormat(format);
1420     }
1421 
1422     o = new QAudioOutput(cur_out_device, format, this);
1423     o->setNotifyInterval(100); // ~15 fps
1424     connect(o, SIGNAL(notify()), this, SLOT(outputNotify()));
1425 
1426     return o;
1427 }
1428 
outputNotify()1429 void RtpPlayerDialog::outputNotify()
1430 {
1431     double new_current_pos = 0.0;
1432     double current_pos = 0.0;
1433 
1434     double secs = marker_stream_->processedUSecs() / 1000000.0;
1435 
1436     if (ui->skipSilenceButton->isChecked()) {
1437         // We should check whether we can skip some silence
1438         // We must calculate in time domain as every stream can use different
1439         // play rate
1440         double min_silence = playing_streams_[0]->getEndOfSilenceTime();
1441         for( int i = 1; i<playing_streams_.count(); ++i ) {
1442             qint64 cur_silence = playing_streams_[i]->getEndOfSilenceTime();
1443             if (cur_silence < min_silence) {
1444                 min_silence = cur_silence;
1445             }
1446         }
1447 
1448         if (min_silence > 0.0) {
1449             double silence_duration;
1450 
1451             // Calculate silence duration we can skip
1452             new_current_pos = first_stream_rel_start_time_ + min_silence;
1453             if (ui->todCheckBox->isChecked()) {
1454                 current_pos = secs + start_marker_time_play_ + first_stream_rel_start_time_;
1455             } else {
1456                 current_pos = secs + start_marker_time_play_;
1457             }
1458             silence_duration = new_current_pos - current_pos;
1459 
1460             if (silence_duration >= ui->minSilenceSpinBox->value()) {
1461                 // Skip silence gap and update cursor difference
1462                 for( int i = 0; i<playing_streams_.count(); ++i ) {
1463                     // Convert silence from time domain to samples
1464                     qint64 skip_samples = playing_streams_[i]->convertTimeToSamples(min_silence);
1465                     playing_streams_[i]->seekPlaying(skip_samples);
1466                 }
1467                 silence_skipped_time_ = silence_duration;
1468             }
1469         }
1470     }
1471 
1472     // Calculate new cursor position
1473     if (ui->todCheckBox->isChecked()) {
1474         secs += start_marker_time_play_;
1475         secs += silence_skipped_time_;
1476     } else {
1477         secs += start_marker_time_play_;
1478         secs -= first_stream_rel_start_time_;
1479         secs += silence_skipped_time_;
1480     }
1481     setPlayPosition(secs);
1482 }
1483 
on_pauseButton_clicked()1484 void RtpPlayerDialog::on_pauseButton_clicked()
1485 {
1486     for( int i = 0; i<playing_streams_.count(); ++i ) {
1487         playing_streams_[i]->pausePlaying();
1488     }
1489     if (ui->pauseButton->isChecked()) {
1490         marker_stream_->suspend();
1491     } else {
1492         marker_stream_->resume();
1493     }
1494     updateWidgets();
1495 }
1496 
on_stopButton_clicked()1497 void RtpPlayerDialog::on_stopButton_clicked()
1498 {
1499     // We need copy of list because items will be removed during stopPlaying()
1500     QList<RtpAudioStream *> ps=QList<RtpAudioStream *>(playing_streams_);
1501     for( int i = 0; i<ps.count(); ++i ) {
1502         ps[i]->stopPlaying();
1503     }
1504     marker_stream_->stop();
1505     cur_play_pos_->setVisible(false);
1506     updateWidgets();
1507 }
1508 
on_actionReset_triggered()1509 void RtpPlayerDialog::on_actionReset_triggered()
1510 {
1511     resetXAxis();
1512 }
1513 
on_actionZoomIn_triggered()1514 void RtpPlayerDialog::on_actionZoomIn_triggered()
1515 {
1516     zoomXAxis(true);
1517 }
1518 
on_actionZoomOut_triggered()1519 void RtpPlayerDialog::on_actionZoomOut_triggered()
1520 {
1521     zoomXAxis(false);
1522 }
1523 
on_actionMoveLeft10_triggered()1524 void RtpPlayerDialog::on_actionMoveLeft10_triggered()
1525 {
1526     panXAxis(-10);
1527 }
1528 
on_actionMoveRight10_triggered()1529 void RtpPlayerDialog::on_actionMoveRight10_triggered()
1530 {
1531     panXAxis(10);
1532 }
1533 
on_actionMoveLeft1_triggered()1534 void RtpPlayerDialog::on_actionMoveLeft1_triggered()
1535 {
1536     panXAxis(-1);
1537 }
1538 
on_actionMoveRight1_triggered()1539 void RtpPlayerDialog::on_actionMoveRight1_triggered()
1540 {
1541     panXAxis(1);
1542 }
1543 
on_actionGoToPacket_triggered()1544 void RtpPlayerDialog::on_actionGoToPacket_triggered()
1545 {
1546     int packet_num = getHoveredPacket();
1547     if (packet_num > 0) emit goToPacket(packet_num);
1548 }
1549 
handleGoToSetupPacket(QTreeWidgetItem * ti)1550 void RtpPlayerDialog::handleGoToSetupPacket(QTreeWidgetItem *ti)
1551 {
1552     if (ti) {
1553         bool ok;
1554 
1555         int packet_num = ti->data(first_pkt_col_, Qt::UserRole).toInt(&ok);
1556         if (ok) {
1557             emit goToPacket(packet_num);
1558         }
1559     }
1560 }
1561 
on_actionGoToSetupPacketPlot_triggered()1562 void RtpPlayerDialog::on_actionGoToSetupPacketPlot_triggered()
1563 {
1564     QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
1565     handleGoToSetupPacket(findItemByCoords(pos));
1566 }
1567 
on_actionGoToSetupPacketTree_triggered()1568 void RtpPlayerDialog::on_actionGoToSetupPacketTree_triggered()
1569 {
1570     handleGoToSetupPacket(last_ti_);
1571 }
1572 
1573 // Make waveform graphs selectable and update the treewidget selection accordingly.
on_streamTreeWidget_itemSelectionChanged()1574 void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
1575 {
1576     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1577         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1578         RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
1579         if (audio_graph) {
1580             audio_graph->setSelected(ti->isSelected());
1581         }
1582     }
1583 
1584     int selected = ui->streamTreeWidget->selectedItems().count();
1585     if (selected == 0) {
1586         analyze_btn_->setEnabled(false);
1587         prepare_btn_->setEnabled(false);
1588         export_btn_->setEnabled(false);
1589     } else if (selected == 1) {
1590         analyze_btn_->setEnabled(true);
1591         prepare_btn_->setEnabled(true);
1592         export_btn_->setEnabled(true);
1593         ui->actionSavePayload->setEnabled(true);
1594     } else {
1595         analyze_btn_->setEnabled(true);
1596         prepare_btn_->setEnabled(true);
1597         export_btn_->setEnabled(true);
1598         ui->actionSavePayload->setEnabled(false);
1599     }
1600 
1601     if (!block_redraw_) {
1602         ui->audioPlot->replot();
1603         updateHintLabel();
1604     }
1605 }
1606 
1607 // Change channel audio routing if double clicked channel column
on_streamTreeWidget_itemDoubleClicked(QTreeWidgetItem * item,const int column)1608 void RtpPlayerDialog::on_streamTreeWidget_itemDoubleClicked(QTreeWidgetItem *item, const int column)
1609 {
1610     if (column == channel_col_) {
1611         RtpAudioStream *audio_stream = item->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1612         if (!audio_stream)
1613             return;
1614 
1615         AudioRouting audio_routing = audio_stream->getAudioRouting();
1616         audio_routing = audio_routing.getNextChannel(stereo_available_);
1617         changeAudioRoutingOnItem(item, audio_routing);
1618     }
1619     updateHintLabel();
1620 }
1621 
removeRow(QTreeWidgetItem * ti)1622 void RtpPlayerDialog::removeRow(QTreeWidgetItem *ti)
1623 {
1624     if (last_ti_ && (last_ti_ == ti)) {
1625         highlightItem(last_ti_, false);
1626         last_ti_ = NULL;
1627     }
1628     RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1629     if (audio_stream) {
1630         stream_hash_.remove(audio_stream->getHash(), audio_stream);
1631         ti->setData(stream_data_col_, Qt::UserRole, QVariant());
1632         delete audio_stream;
1633     }
1634 
1635     RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
1636     if (audio_graph) {
1637         ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant());
1638         audio_graph->remove(ui->audioPlot);
1639     }
1640 
1641     QCPGraph *graph;
1642     graph = ti->data(graph_sequence_data_col_, Qt::UserRole).value<QCPGraph*>();
1643     if (graph) {
1644         ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant());
1645         ui->audioPlot->removeGraph(graph);
1646     }
1647 
1648     graph = ti->data(graph_jitter_data_col_, Qt::UserRole).value<QCPGraph*>();
1649     if (graph) {
1650         ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant());
1651         ui->audioPlot->removeGraph(graph);
1652     }
1653 
1654     graph = ti->data(graph_timestamp_data_col_, Qt::UserRole).value<QCPGraph*>();
1655     if (graph) {
1656         ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant());
1657         ui->audioPlot->removeGraph(graph);
1658     }
1659 
1660     graph = ti->data(graph_silence_data_col_, Qt::UserRole).value<QCPGraph*>();
1661     if (graph) {
1662         ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant());
1663         ui->audioPlot->removeGraph(graph);
1664     }
1665 
1666     delete ti;
1667 }
1668 
on_actionRemoveStream_triggered()1669 void RtpPlayerDialog::on_actionRemoveStream_triggered()
1670 {
1671     lockUI();
1672     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
1673 
1674     block_redraw_ = true;
1675     for(int i = items.count() - 1; i>=0; i-- ) {
1676         removeRow(items[i]);
1677     }
1678     block_redraw_ = false;
1679     // TODO: Recalculate legend
1680     // - Graphs used for legend could be removed above and we must add new
1681     // - If no legend is required, it should be removed
1682 
1683     // Redraw existing waveforms and rescale Y axis
1684     updateGraphs();
1685 
1686     updateWidgets();
1687     unlockUI();
1688 }
1689 
1690 // If called with channel_any, just muted flag should be changed
changeAudioRoutingOnItem(QTreeWidgetItem * ti,AudioRouting new_audio_routing)1691 void RtpPlayerDialog::changeAudioRoutingOnItem(QTreeWidgetItem *ti, AudioRouting new_audio_routing)
1692 {
1693     if (!ti)
1694         return;
1695 
1696     RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1697     if (!audio_stream)
1698         return;
1699 
1700     AudioRouting audio_routing = audio_stream->getAudioRouting();
1701     audio_routing.mergeAudioRouting(new_audio_routing);
1702     formatAudioRouting(ti, audio_routing);
1703 
1704     audio_stream->setAudioRouting(audio_routing);
1705 
1706     RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
1707     if (audio_graph) {
1708 
1709         audio_graph->setSelected(ti->isSelected());
1710         audio_graph->setMuted(audio_routing.isMuted());
1711         if (!block_redraw_) {
1712             ui->audioPlot->replot();
1713         }
1714     }
1715 }
1716 
1717 // Find current item and apply change on it
changeAudioRouting(AudioRouting new_audio_routing)1718 void RtpPlayerDialog::changeAudioRouting(AudioRouting new_audio_routing)
1719 {
1720     lockUI();
1721     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
1722 
1723     block_redraw_ = true;
1724     for(int i = 0; i<items.count(); i++ ) {
1725 
1726         QTreeWidgetItem *ti = items[i];
1727         changeAudioRoutingOnItem(ti, new_audio_routing);
1728     }
1729     block_redraw_ = false;
1730     ui->audioPlot->replot();
1731     updateHintLabel();
1732     unlockUI();
1733 }
1734 
1735 // Invert mute/unmute on item
invertAudioMutingOnItem(QTreeWidgetItem * ti)1736 void RtpPlayerDialog::invertAudioMutingOnItem(QTreeWidgetItem *ti)
1737 {
1738     if (!ti)
1739         return;
1740 
1741     RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1742     if (!audio_stream)
1743         return;
1744 
1745     AudioRouting audio_routing = audio_stream->getAudioRouting();
1746     // Invert muting
1747     if (audio_routing.isMuted()) {
1748         changeAudioRoutingOnItem(ti, AudioRouting(AUDIO_UNMUTED, channel_any));
1749     } else {
1750         changeAudioRoutingOnItem(ti, AudioRouting(AUDIO_MUTED, channel_any));
1751     }
1752 }
1753 
on_actionAudioRoutingP_triggered()1754 void RtpPlayerDialog::on_actionAudioRoutingP_triggered()
1755 {
1756     changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_mono));
1757 }
1758 
on_actionAudioRoutingL_triggered()1759 void RtpPlayerDialog::on_actionAudioRoutingL_triggered()
1760 {
1761     changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_left));
1762 }
1763 
on_actionAudioRoutingLR_triggered()1764 void RtpPlayerDialog::on_actionAudioRoutingLR_triggered()
1765 {
1766     changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_both));
1767 }
1768 
on_actionAudioRoutingR_triggered()1769 void RtpPlayerDialog::on_actionAudioRoutingR_triggered()
1770 {
1771     changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_right));
1772 }
1773 
on_actionAudioRoutingMute_triggered()1774 void RtpPlayerDialog::on_actionAudioRoutingMute_triggered()
1775 {
1776     changeAudioRouting(AudioRouting(AUDIO_MUTED, channel_any));
1777 }
1778 
on_actionAudioRoutingUnmute_triggered()1779 void RtpPlayerDialog::on_actionAudioRoutingUnmute_triggered()
1780 {
1781     changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_any));
1782 }
1783 
on_actionAudioRoutingMuteInvert_triggered()1784 void RtpPlayerDialog::on_actionAudioRoutingMuteInvert_triggered()
1785 {
1786     lockUI();
1787     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
1788 
1789     block_redraw_ = true;
1790     for(int i = 0; i<items.count(); i++ ) {
1791 
1792         QTreeWidgetItem *ti = items[i];
1793         invertAudioMutingOnItem(ti);
1794     }
1795     block_redraw_ = false;
1796     ui->audioPlot->replot();
1797     updateHintLabel();
1798     unlockUI();
1799 }
1800 
getFormatedTime(double f_time)1801 const QString RtpPlayerDialog::getFormatedTime(double f_time)
1802 {
1803     QString time_str;
1804 
1805     if (ui->todCheckBox->isChecked()) {
1806         QDateTime date_time = QDateTime::fromMSecsSinceEpoch(f_time * 1000.0);
1807         time_str = date_time.toString("yyyy-MM-dd hh:mm:ss.zzz");
1808     } else {
1809         time_str = QString::number(f_time, 'f', 6);
1810         time_str += " s";
1811     }
1812 
1813     return time_str;
1814 }
1815 
getFormatedHoveredTime()1816 const QString RtpPlayerDialog::getFormatedHoveredTime()
1817 {
1818     QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
1819     QTreeWidgetItem *ti = findItemByCoords(pos);
1820     if (!ti) return tr("Unknown");
1821 
1822     double ts = ui->audioPlot->xAxis->pixelToCoord(pos.x());
1823 
1824     return getFormatedTime(ts);
1825 }
1826 
getHoveredPacket()1827 int RtpPlayerDialog::getHoveredPacket()
1828 {
1829     QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
1830     QTreeWidgetItem *ti = findItemByCoords(pos);
1831     if (!ti) return 0;
1832 
1833     RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1834 
1835     double ts = ui->audioPlot->xAxis->pixelToCoord(pos.x());
1836 
1837     return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
1838 }
1839 
1840 // Used by RtpAudioStreams to initialize QAudioOutput. We could alternatively
1841 // pass the corresponding QAudioDeviceInfo directly.
currentOutputDeviceName()1842 QString RtpPlayerDialog::currentOutputDeviceName()
1843 {
1844     return ui->outputDeviceComboBox->currentText();
1845 }
1846 
fillAudioRateMenu()1847 void RtpPlayerDialog::fillAudioRateMenu()
1848 {
1849     ui->outputAudioRate->blockSignals(true);
1850     ui->outputAudioRate->clear();
1851     ui->outputAudioRate->addItem(tr("Automatic"));
1852     foreach (int rate, getCurrentDeviceInfo().supportedSampleRates()) {
1853         ui->outputAudioRate->addItem(QString::number(rate));
1854     }
1855     ui->outputAudioRate->blockSignals(false);
1856 }
1857 
cleanupMarkerStream()1858 void RtpPlayerDialog::cleanupMarkerStream()
1859 {
1860     if (marker_stream_) {
1861         marker_stream_->stop();
1862         delete marker_stream_;
1863         marker_stream_ = NULL;
1864     }
1865 }
1866 
on_outputDeviceComboBox_currentIndexChanged(const QString &)1867 void RtpPlayerDialog::on_outputDeviceComboBox_currentIndexChanged(const QString &)
1868 {
1869     lockUI();
1870     stereo_available_ = isStereoAvailable();
1871     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1872         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1873         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1874         if (!audio_stream)
1875             continue;
1876 
1877         changeAudioRoutingOnItem(ti, audio_stream->getAudioRouting().convert(stereo_available_));
1878     }
1879 
1880     marker_stream_requested_out_rate_ = 0;
1881     cleanupMarkerStream();
1882     fillAudioRateMenu();
1883     rescanPackets();
1884     unlockUI();
1885 }
1886 
on_outputAudioRate_currentIndexChanged(const QString & rate_string)1887 void RtpPlayerDialog::on_outputAudioRate_currentIndexChanged(const QString & rate_string)
1888 {
1889     lockUI();
1890     // Any unconvertable string is converted to 0 => used as Automatic rate
1891     unsigned selected_rate = rate_string.toInt();
1892 
1893     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
1894         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
1895         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
1896         if (!audio_stream)
1897             continue;
1898 
1899         audio_stream->setRequestedPlayRate(selected_rate);
1900     }
1901     marker_stream_requested_out_rate_ = selected_rate;
1902     cleanupMarkerStream();
1903     rescanPackets();
1904     unlockUI();
1905 }
1906 
on_jitterSpinBox_valueChanged(double)1907 void RtpPlayerDialog::on_jitterSpinBox_valueChanged(double)
1908 {
1909     rescanPackets();
1910 }
1911 
on_timingComboBox_currentIndexChanged(int)1912 void RtpPlayerDialog::on_timingComboBox_currentIndexChanged(int)
1913 {
1914     rescanPackets();
1915 }
1916 
on_todCheckBox_toggled(bool)1917 void RtpPlayerDialog::on_todCheckBox_toggled(bool)
1918 {
1919     QCPAxis *x_axis = ui->audioPlot->xAxis;
1920     double move;
1921 
1922     // Create plot with new tod settings
1923     createPlot();
1924 
1925     // Move view to same place as was shown before the change
1926     if (ui->todCheckBox->isChecked()) {
1927        // rel -> abs
1928        // based on abs time of first sample
1929        setStartPlayMarker(first_stream_abs_start_time_ + start_marker_time_ - first_stream_rel_start_time_);
1930        move = first_stream_abs_start_time_ - first_stream_rel_start_time_;
1931     } else {
1932        // abs -> rel
1933        // based on 0s
1934        setStartPlayMarker(first_stream_rel_start_time_ + start_marker_time_);
1935        move = - first_stream_abs_start_time_ + first_stream_rel_start_time_;
1936     }
1937     x_axis->moveRange(move);
1938     drawStartPlayMarker();
1939     ui->audioPlot->replot();
1940 }
1941 
on_buttonBox_helpRequested()1942 void RtpPlayerDialog::on_buttonBox_helpRequested()
1943 {
1944     wsApp->helpTopicAction(HELP_TELEPHONY_RTP_PLAYER_DIALOG);
1945 }
1946 
getStartPlayMarker()1947 double RtpPlayerDialog::getStartPlayMarker()
1948 {
1949     double start_pos;
1950 
1951     if (ui->todCheckBox->isChecked()) {
1952         start_pos = start_marker_time_ + first_stream_abs_start_time_;
1953     } else {
1954         start_pos = start_marker_time_;
1955     }
1956 
1957     return start_pos;
1958 }
1959 
drawStartPlayMarker()1960 void RtpPlayerDialog::drawStartPlayMarker()
1961 {
1962     double pos = getStartPlayMarker();
1963 
1964     start_marker_pos_->point1->setCoords(pos, 0.0);
1965     start_marker_pos_->point2->setCoords(pos, 1.0);
1966 
1967     updateHintLabel();
1968 }
1969 
setStartPlayMarker(double new_time)1970 void RtpPlayerDialog::setStartPlayMarker(double new_time)
1971 {
1972     if (ui->todCheckBox->isChecked()) {
1973         new_time = qBound(first_stream_abs_start_time_, new_time, first_stream_abs_start_time_ + streams_length_);
1974         // start_play_time is relative, we must calculate it
1975         start_marker_time_ = new_time - first_stream_abs_start_time_;
1976     } else {
1977         new_time = qBound(first_stream_rel_start_time_, new_time, first_stream_rel_start_time_ + streams_length_);
1978         start_marker_time_ = new_time;
1979     }
1980 }
1981 
updateStartStopTime(rtpstream_info_t * rtpstream,bool is_first)1982 void RtpPlayerDialog::updateStartStopTime(rtpstream_info_t *rtpstream, bool is_first)
1983 {
1984     // Calculate start time of first last packet of last stream
1985     double stream_rel_start_time = nstime_to_sec(&rtpstream->start_rel_time);
1986     double stream_abs_start_time = nstime_to_sec(&rtpstream->start_abs_time);
1987     double stream_rel_stop_time = nstime_to_sec(&rtpstream->stop_rel_time);
1988 
1989     if (is_first) {
1990         // Take start/stop time for first stream
1991         first_stream_rel_start_time_ = stream_rel_start_time;
1992         first_stream_abs_start_time_ = stream_abs_start_time;
1993         first_stream_rel_stop_time_ = stream_rel_stop_time;
1994     } else {
1995         // Calculate min/max for start/stop time for other streams
1996         first_stream_rel_start_time_ = qMin(first_stream_rel_start_time_, stream_rel_start_time);
1997         first_stream_abs_start_time_ = qMin(first_stream_abs_start_time_, stream_abs_start_time);
1998         first_stream_rel_stop_time_ = qMax(first_stream_rel_stop_time_, stream_rel_stop_time);
1999     }
2000     streams_length_ = first_stream_rel_stop_time_ - first_stream_rel_start_time_;
2001 }
2002 
formatAudioRouting(QTreeWidgetItem * ti,AudioRouting audio_routing)2003 void RtpPlayerDialog::formatAudioRouting(QTreeWidgetItem *ti, AudioRouting audio_routing)
2004 {
2005     ti->setText(channel_col_, tr(audio_routing.formatAudioRoutingToString()));
2006 }
2007 
isStereoAvailable()2008 bool RtpPlayerDialog::isStereoAvailable()
2009 {
2010     QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
2011     foreach(int count, cur_out_device.supportedChannelCounts()) {
2012         if (count>1) {
2013             return true;
2014         }
2015     }
2016 
2017     return false;
2018 }
2019 
invertSelection()2020 void RtpPlayerDialog::invertSelection()
2021 {
2022     block_redraw_ = true;
2023     ui->streamTreeWidget->blockSignals(true);
2024     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
2025         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
2026         ti->setSelected(!ti->isSelected());
2027     }
2028     ui->streamTreeWidget->blockSignals(false);
2029     block_redraw_ = false;
2030     ui->audioPlot->replot();
2031     updateHintLabel();
2032 }
2033 
on_actionSelectAll_triggered()2034 void RtpPlayerDialog::on_actionSelectAll_triggered()
2035 {
2036     ui->streamTreeWidget->selectAll();
2037     updateHintLabel();
2038 }
2039 
on_actionSelectInvert_triggered()2040 void RtpPlayerDialog::on_actionSelectInvert_triggered()
2041 {
2042     invertSelection();
2043     updateHintLabel();
2044 }
2045 
on_actionSelectNone_triggered()2046 void RtpPlayerDialog::on_actionSelectNone_triggered()
2047 {
2048     ui->streamTreeWidget->clearSelection();
2049     updateHintLabel();
2050 }
2051 
on_actionPlay_triggered()2052 void RtpPlayerDialog::on_actionPlay_triggered()
2053 {
2054     if (ui->playButton->isEnabled()) {
2055         ui->playButton->animateClick();
2056     } else if (ui->pauseButton->isEnabled()) {
2057         ui->pauseButton->animateClick();
2058     }
2059 }
2060 
on_actionStop_triggered()2061 void RtpPlayerDialog::on_actionStop_triggered()
2062 {
2063     if (ui->stopButton->isEnabled()) {
2064         ui->stopButton->animateClick();
2065     }
2066 }
2067 
saveAudioHeaderAU(QFile * save_file,int channels,unsigned audio_rate)2068 qint64 RtpPlayerDialog::saveAudioHeaderAU(QFile *save_file, int channels, unsigned audio_rate)
2069 {
2070     uint8_t pd[4];
2071     int64_t nchars;
2072 
2073     /* https://pubs.opengroup.org/external/auformat.html */
2074     /* First we write the .au header.  All values in the header are
2075      * 4-byte big-endian values, so we use pntoh32() to copy them
2076      * to a 4-byte buffer, in big-endian order, and then write out
2077      * the buffer. */
2078 
2079     /* the magic word 0x2e736e64 == .snd */
2080     phton32(pd, 0x2e736e64);
2081     nchars = save_file->write((const char *)pd, 4);
2082     if (nchars != 4) {
2083         return -1;
2084     }
2085 
2086     /* header offset == 24 bytes */
2087     phton32(pd, 24);
2088     nchars = save_file->write((const char *)pd, 4);
2089     if (nchars != 4) {
2090         return -1;
2091     }
2092 
2093     /* total length; it is permitted to set this to 0xffffffff */
2094     phton32(pd, 0xffffffff);
2095     nchars = save_file->write((const char *)pd, 4);
2096     if (nchars != 4) {
2097         return -1;
2098     }
2099 
2100     /* encoding format == 16-bit linear PCM */
2101     phton32(pd, 3);
2102     nchars = save_file->write((const char *)pd, 4);
2103     if (nchars != 4) {
2104         return -1;
2105     }
2106 
2107     /* sample rate [Hz] */
2108     phton32(pd, audio_rate);
2109     nchars = save_file->write((const char *)pd, 4);
2110     if (nchars != 4) {
2111         return -1;
2112     }
2113 
2114     /* channels */
2115     phton32(pd, channels);
2116     nchars = save_file->write((const char *)pd, 4);
2117     if (nchars != 4) {
2118         return -1;
2119     }
2120 
2121     return save_file->pos();
2122 }
2123 
saveAudioHeaderWAV(QFile * save_file,int channels,unsigned audio_rate,qint64 samples)2124 qint64 RtpPlayerDialog::saveAudioHeaderWAV(QFile *save_file, int channels, unsigned audio_rate, qint64 samples)
2125 {
2126     uint8_t pd[4];
2127     int64_t nchars;
2128     gint32  subchunk2Size;
2129     gint32  data32;
2130     gint16  data16;
2131 
2132     subchunk2Size = sizeof(SAMPLE) * channels * (gint32)samples;
2133 
2134     /* http://soundfile.sapp.org/doc/WaveFormat/ */
2135 
2136     /* RIFF header, ChunkID 0x52494646 == RIFF */
2137     phton32(pd, 0x52494646);
2138     nchars = save_file->write((const char *)pd, 4);
2139     if (nchars != 4) {
2140         return -1;
2141     }
2142 
2143     /* RIFF header, ChunkSize */
2144     data32 = 36 + subchunk2Size;
2145     nchars = save_file->write((const char *)&data32, 4);
2146     if (nchars != 4) {
2147         return -1;
2148     }
2149 
2150     /* RIFF header, Format 0x57415645 == WAVE */
2151     phton32(pd, 0x57415645);
2152     nchars = save_file->write((const char *)pd, 4);
2153     if (nchars != 4) {
2154         return -1;
2155     }
2156 
2157     /* WAVE fmt header, Subchunk1ID 0x666d7420 == 'fmt ' */
2158     phton32(pd, 0x666d7420);
2159     nchars = save_file->write((const char *)pd, 4);
2160     if (nchars != 4) {
2161         return -1;
2162     }
2163 
2164     /* WAVE fmt header, Subchunk1Size */
2165     data32 = 16;
2166     nchars = save_file->write((const char *)&data32, 4);
2167     if (nchars != 4) {
2168         return -1;
2169     }
2170 
2171     /* WAVE fmt header, AudioFormat 1 == PCM */
2172     data16 = 1;
2173     nchars = save_file->write((const char *)&data16, 2);
2174     if (nchars != 2) {
2175         return -1;
2176     }
2177 
2178     /* WAVE fmt header, NumChannels */
2179     data16 = channels;
2180     nchars = save_file->write((const char *)&data16, 2);
2181     if (nchars != 2) {
2182         return -1;
2183     }
2184 
2185     /* WAVE fmt header, SampleRate */
2186     data32 = audio_rate;
2187     nchars = save_file->write((const char *)&data32, 4);
2188     if (nchars != 4) {
2189         return -1;
2190     }
2191 
2192     /* WAVE fmt header, ByteRate */
2193     data32 = audio_rate * channels * sizeof(SAMPLE);
2194     nchars = save_file->write((const char *)&data32, 4);
2195     if (nchars != 4) {
2196         return -1;
2197     }
2198 
2199     /* WAVE fmt header, BlockAlign */
2200     data16 = channels * (gint16)sizeof(SAMPLE);
2201     nchars = save_file->write((const char *)&data16, 2);
2202     if (nchars != 2) {
2203         return -1;
2204     }
2205 
2206     /* WAVE fmt header, BitsPerSample */
2207     data16 = (gint16)sizeof(SAMPLE) * 8;
2208     nchars = save_file->write((const char *)&data16, 2);
2209     if (nchars != 2) {
2210         return -1;
2211     }
2212 
2213     /* WAVE data header, Subchunk2ID 0x64617461 == 'data' */
2214     phton32(pd, 0x64617461);
2215     nchars = save_file->write((const char *)pd, 4);
2216     if (nchars != 4) {
2217         return -1;
2218     }
2219 
2220     /* WAVE data header, Subchunk2Size */
2221     data32 = subchunk2Size;
2222     nchars = save_file->write((const char *)&data32, 4);
2223     if (nchars != 4) {
2224         return -1;
2225     }
2226 
2227     /* Now we are ready for saving data */
2228 
2229     return save_file->pos();
2230 }
2231 
writeAudioSilenceSamples(QFile * out_file,qint64 samples,int stream_count)2232 bool RtpPlayerDialog::writeAudioSilenceSamples(QFile *out_file, qint64 samples, int stream_count)
2233 {
2234     uint8_t pd[2];
2235 
2236     phton16(pd, 0x0000);
2237     for(int s=0; s < stream_count; s++) {
2238         for(qint64 i=0; i < samples; i++) {
2239             if (sizeof(SAMPLE) != out_file->write((char *)&pd, sizeof(SAMPLE))) {
2240                 return false;
2241             }
2242         }
2243     }
2244 
2245     return true;
2246 }
2247 
writeAudioStreamsSamples(QFile * out_file,QVector<RtpAudioStream * > streams,bool swap_bytes)2248 bool RtpPlayerDialog::writeAudioStreamsSamples(QFile *out_file, QVector<RtpAudioStream *> streams, bool swap_bytes)
2249 {
2250     SAMPLE sample;
2251     uint8_t pd[2];
2252 
2253     // Did we read something in last cycle?
2254     bool read = true;
2255 
2256     while (read) {
2257         read = false;
2258         // Loop over all streams, read one sample from each, write to output
2259         foreach(RtpAudioStream *audio_stream, streams) {
2260             if (sizeof(sample) == audio_stream->readSample(&sample)) {
2261                 if (swap_bytes) {
2262                     // same as phton16(), but more clear in compare
2263                     // to else branch
2264                     pd[0] = (guint8)(sample >> 8);
2265                     pd[1] = (guint8)(sample >> 0);
2266                 } else {
2267                     // just copy
2268                     pd[1] = (guint8)(sample >> 8);
2269                     pd[0] = (guint8)(sample >> 0);
2270                 }
2271                 read = true;
2272             } else {
2273                 // for 0x0000 doesn't matter on order
2274                 phton16(pd, 0x0000);
2275             }
2276             if (sizeof(sample) != out_file->write((char *)&pd, sizeof(sample))) {
2277                 return false;
2278             }
2279         }
2280     }
2281 
2282     return true;
2283 }
2284 
selectFileAudioFormatAndName(QString * file_path)2285 save_audio_t RtpPlayerDialog::selectFileAudioFormatAndName(QString *file_path)
2286 {
2287     QString ext_filter = "";
2288     QString ext_filter_wav = tr("WAV (*.wav)");
2289     QString ext_filter_au = tr("Sun Audio (*.au)");
2290     ext_filter.append(ext_filter_wav);
2291     ext_filter.append(";;");
2292     ext_filter.append(ext_filter_au);
2293 
2294     QString sel_filter;
2295     *file_path = WiresharkFileDialog::getSaveFileName(
2296                 this, tr("Save audio"), wsApp->lastOpenDir().absoluteFilePath(""),
2297                 ext_filter, &sel_filter);
2298 
2299     if (file_path->isEmpty()) return save_audio_none;
2300 
2301     save_audio_t save_format = save_audio_none;
2302     if (0 == QString::compare(sel_filter, ext_filter_au)) {
2303         save_format = save_audio_au;
2304     } else if (0 == QString::compare(sel_filter, ext_filter_wav)) {
2305         save_format = save_audio_wav;
2306     }
2307 
2308     return save_format;
2309 }
2310 
selectFilePayloadFormatAndName(QString * file_path)2311 save_payload_t RtpPlayerDialog::selectFilePayloadFormatAndName(QString *file_path)
2312 {
2313     QString ext_filter = "";
2314     QString ext_filter_raw = tr("Raw (*.raw)");
2315     ext_filter.append(ext_filter_raw);
2316 
2317     QString sel_filter;
2318     *file_path = WiresharkFileDialog::getSaveFileName(
2319                 this, tr("Save payload"), wsApp->lastOpenDir().absoluteFilePath(""),
2320                 ext_filter, &sel_filter);
2321 
2322     if (file_path->isEmpty()) return save_payload_none;
2323 
2324     save_payload_t save_format = save_payload_none;
2325     if (0 == QString::compare(sel_filter, ext_filter_raw)) {
2326         save_format = save_payload_data;
2327     }
2328 
2329     return save_format;
2330 }
2331 
getSelectedRtpStreamIDs()2332 QVector<rtpstream_id_t *>RtpPlayerDialog::getSelectedRtpStreamIDs()
2333 {
2334     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
2335     QVector<rtpstream_id_t *> ids;
2336 
2337     if (items.count() > 0) {
2338         foreach(QTreeWidgetItem *ti, items) {
2339             RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
2340             if (audio_stream) {
2341                 ids << audio_stream->getID();
2342             }
2343         }
2344     }
2345 
2346     return ids;
2347 }
2348 
getSelectedAudibleNonmutedAudioStreams()2349 QVector<RtpAudioStream *>RtpPlayerDialog::getSelectedAudibleNonmutedAudioStreams()
2350 {
2351     QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
2352     QVector<RtpAudioStream *> streams;
2353 
2354     if (items.count() > 0) {
2355         foreach(QTreeWidgetItem *ti, items) {
2356             RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
2357             // Ignore muted streams and streams with no audio
2358             if (audio_stream &&
2359                 !audio_stream->getAudioRouting().isMuted() &&
2360                 (audio_stream->sampleRate()>0)
2361                ) {
2362                 streams << audio_stream;
2363             }
2364         }
2365     }
2366 
2367     return streams;
2368 }
2369 
saveAudio(save_mode_t save_mode)2370 void RtpPlayerDialog::saveAudio(save_mode_t save_mode)
2371 {
2372     qint64 minSilenceSamples;
2373     qint64 startSample;
2374     qint64 lead_silence_samples;
2375     qint64 maxSample;
2376     QString path;
2377     QVector<RtpAudioStream *>streams;
2378 
2379     streams = getSelectedAudibleNonmutedAudioStreams();
2380     if (streams.count() < 1) {
2381         QMessageBox::warning(this, tr("Warning"), tr("No stream selected or none of selected streams provide audio"));
2382         return;
2383     }
2384 
2385     unsigned save_audio_rate = streams[0]->playRate();
2386     // Check whether all streams use same audio rate
2387     foreach(RtpAudioStream *audio_stream, streams) {
2388         if (save_audio_rate != audio_stream->playRate()) {
2389             QMessageBox::warning(this, tr("Error"), tr("All selected streams must use same play rate. Manual set of Output Audio Rate might help."));
2390             return;
2391         }
2392     }
2393 
2394     save_audio_t format = selectFileAudioFormatAndName(&path);
2395     if (format == save_audio_none) return;
2396 
2397     // Use start silence and length of first stream
2398     minSilenceSamples = streams[0]->getLeadSilenceSamples();
2399     maxSample = streams[0]->getTotalSamples();
2400     // Find shortest start silence and longest stream
2401     foreach(RtpAudioStream *audio_stream, streams) {
2402         if (minSilenceSamples > audio_stream->getLeadSilenceSamples()) {
2403             minSilenceSamples = audio_stream->getLeadSilenceSamples();
2404         }
2405         if (maxSample < audio_stream->getTotalSamples()) {
2406             maxSample = audio_stream->getTotalSamples();
2407         }
2408     }
2409 
2410     switch (save_mode) {
2411         case save_mode_from_cursor:
2412             if (ui->todCheckBox->isChecked()) {
2413                 startSample = start_marker_time_ * save_audio_rate;
2414             } else {
2415                 startSample = (start_marker_time_ - first_stream_rel_start_time_) * save_audio_rate;
2416             }
2417             lead_silence_samples = 0;
2418             break;
2419         case save_mode_sync_stream:
2420             // Skip start of first stream, no lead silence
2421             startSample = minSilenceSamples;
2422             lead_silence_samples = 0;
2423             break;
2424         case save_mode_sync_file:
2425         default:
2426             // Full first stream, lead silence
2427             startSample = 0;
2428             lead_silence_samples = first_stream_rel_start_time_ * save_audio_rate;
2429             break;
2430     }
2431 
2432     QVector<RtpAudioStream *>temp = QVector<RtpAudioStream *>(streams);
2433 
2434     // Remove streams shorter than startSample and
2435     // seek to correct start for longer ones
2436     foreach(RtpAudioStream *audio_stream, temp) {
2437         if (startSample > audio_stream->getTotalSamples()) {
2438             streams.removeAll(audio_stream);
2439         } else {
2440             audio_stream->seekSample(startSample);
2441         }
2442     }
2443 
2444     if (streams.count() < 1) {
2445         QMessageBox::warning(this, tr("Warning"), tr("No streams are suitable for save"));
2446         return;
2447     }
2448 
2449     QFile file(path);
2450     file.open(QIODevice::WriteOnly);
2451 
2452     if (!file.isOpen() || (file.error() != QFile::NoError)) {
2453         QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2454     } else {
2455         switch (format) {
2456             case save_audio_au:
2457                 if (-1 == saveAudioHeaderAU(&file, streams.count(), save_audio_rate)) {
2458                    QMessageBox::warning(this, tr("Error"), tr("Can't write header of AU file"));
2459                    return;
2460                 }
2461                 if (lead_silence_samples > 0) {
2462                     if (!writeAudioSilenceSamples(&file, lead_silence_samples, streams.count())) {
2463                         QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2464                     }
2465                 }
2466                 if (!writeAudioStreamsSamples(&file, streams, true)) {
2467                     QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2468                 }
2469                 break;
2470             case save_audio_wav:
2471                 if (-1 == saveAudioHeaderWAV(&file, streams.count(), save_audio_rate, (maxSample - startSample) + lead_silence_samples)) {
2472                    QMessageBox::warning(this, tr("Error"), tr("Can't write header of WAV file"));
2473                    return;
2474                 }
2475                 if (lead_silence_samples > 0) {
2476                     if (!writeAudioSilenceSamples(&file, lead_silence_samples, streams.count())) {
2477                         QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2478                     }
2479                 }
2480                 if (!writeAudioStreamsSamples(&file, streams, false)) {
2481                     QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2482                 }
2483                 break;
2484             case save_audio_none:
2485                 break;
2486         }
2487     }
2488 
2489     file.close();
2490 }
2491 
savePayload()2492 void RtpPlayerDialog::savePayload()
2493 {
2494     QString path;
2495     QList<QTreeWidgetItem *> items;
2496     RtpAudioStream *audio_stream = NULL;
2497 
2498     items = ui->streamTreeWidget->selectedItems();
2499     foreach(QTreeWidgetItem *ti, items) {
2500         audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
2501         if (audio_stream)
2502            break;
2503     }
2504     if (items.count() != 1 || !audio_stream) {
2505         QMessageBox::warning(this, tr("Warning"), tr("Payload save works with just one audio stream."));
2506         return;
2507     }
2508 
2509     save_payload_t format = selectFilePayloadFormatAndName(&path);
2510     if (format == save_payload_none) return;
2511 
2512     QFile file(path);
2513     file.open(QIODevice::WriteOnly);
2514 
2515     if (!file.isOpen() || (file.error() != QFile::NoError)) {
2516         QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2517     } else if (!audio_stream->savePayload(&file)) {
2518         QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
2519     }
2520 
2521     file.close();
2522 }
2523 
on_actionSaveAudioFromCursor_triggered()2524 void RtpPlayerDialog::on_actionSaveAudioFromCursor_triggered()
2525 {
2526     saveAudio(save_mode_from_cursor);
2527 }
2528 
on_actionSaveAudioSyncStream_triggered()2529 void RtpPlayerDialog::on_actionSaveAudioSyncStream_triggered()
2530 {
2531     saveAudio(save_mode_sync_stream);
2532 }
2533 
on_actionSaveAudioSyncFile_triggered()2534 void RtpPlayerDialog::on_actionSaveAudioSyncFile_triggered()
2535 {
2536     saveAudio(save_mode_sync_file);
2537 }
2538 
on_actionSavePayload_triggered()2539 void RtpPlayerDialog::on_actionSavePayload_triggered()
2540 {
2541     savePayload();
2542 }
2543 
selectInaudible(bool select)2544 void RtpPlayerDialog::selectInaudible(bool select)
2545 {
2546     block_redraw_ = true;
2547     ui->streamTreeWidget->blockSignals(true);
2548     for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
2549         QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
2550         RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
2551         // Streams with no audio
2552         if (audio_stream && (audio_stream->sampleRate()==0)) {
2553             ti->setSelected(select);
2554         }
2555     }
2556     ui->streamTreeWidget->blockSignals(false);
2557     block_redraw_ = false;
2558     ui->audioPlot->replot();
2559     updateHintLabel();
2560 }
2561 
on_actionSelectInaudible_triggered()2562 void RtpPlayerDialog::on_actionSelectInaudible_triggered()
2563 {
2564     selectInaudible(true);
2565 }
2566 
on_actionDeselectInaudible_triggered()2567 void RtpPlayerDialog::on_actionDeselectInaudible_triggered()
2568 {
2569     selectInaudible(false);
2570 }
2571 
on_actionPrepareFilter_triggered()2572 void RtpPlayerDialog::on_actionPrepareFilter_triggered()
2573 {
2574     QVector<rtpstream_id_t *> ids = getSelectedRtpStreamIDs();
2575     QString filter = make_filter_based_on_rtpstream_id(ids);
2576     if (filter.length() > 0) {
2577         emit updateFilter(filter);
2578     }
2579 }
2580 
rtpAnalysisReplace()2581 void RtpPlayerDialog::rtpAnalysisReplace()
2582 {
2583     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
2584 
2585     emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpStreamIDs());
2586 }
2587 
rtpAnalysisAdd()2588 void RtpPlayerDialog::rtpAnalysisAdd()
2589 {
2590     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
2591 
2592     emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpStreamIDs());
2593 }
2594 
rtpAnalysisRemove()2595 void RtpPlayerDialog::rtpAnalysisRemove()
2596 {
2597     if (ui->streamTreeWidget->selectedItems().count() < 1) return;
2598 
2599     emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpStreamIDs());
2600 }
2601 
on_actionReadCapture_triggered()2602 void RtpPlayerDialog::on_actionReadCapture_triggered()
2603 {
2604 #ifdef QT_MULTIMEDIA_LIB
2605     QTimer::singleShot(0, this, SLOT(retapPackets()));
2606 #endif
2607 }
2608 
2609 // _U_ is used for case w have no LIBPCAP
captureEvent(CaptureEvent e _U_)2610 void RtpPlayerDialog::captureEvent(CaptureEvent e _U_)
2611 {
2612 #ifdef HAVE_LIBPCAP
2613     bool new_read_capture_enabled = false;
2614     bool found = false;
2615 
2616     if ((e.captureContext() & CaptureEvent::Capture) &&
2617         (e.eventType() == CaptureEvent::Prepared)
2618        ) {
2619         new_read_capture_enabled = true;
2620         found = true;
2621     } else if ((e.captureContext() & CaptureEvent::Capture) &&
2622                (e.eventType() == CaptureEvent::Finished)
2623               ) {
2624         new_read_capture_enabled = false;
2625         found = true;
2626     }
2627 
2628     if (found) {
2629         bool retap = false;
2630         if (read_capture_enabled_ && !new_read_capture_enabled) {
2631             // Capturing ended, automatically refresh data
2632             retap = true;
2633         }
2634         read_capture_enabled_ = new_read_capture_enabled;
2635         updateWidgets();
2636         if (retap) {
2637             QTimer::singleShot(0, this, SLOT(retapPackets()));
2638         }
2639     }
2640 #endif
2641 }
2642 
2643 #endif // QT_MULTIMEDIA_LIB
2644