58050211b48865cbd2959c2663188333f9873118
[gd/wireshark/.git] / ui / qt / rtp_analysis_dialog.cpp
1 /* rtp_analysis_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 "rtp_analysis_dialog.h"
11 #include <ui_rtp_analysis_dialog.h>
12
13 #include "file.h"
14 #include "frame_tvbuff.h"
15
16 #include "epan/epan_dissect.h"
17 #include "epan/rtp_pt.h"
18
19 #include "epan/dfilter/dfilter.h"
20
21 #include "epan/dissectors/packet-rtp.h"
22
23 #include "ui/help_url.h"
24 #include <wsutil/utf8_entities.h>
25
26 #include <wsutil/g711.h>
27 #include <wsutil/pint.h>
28
29 #include <QMessageBox>
30 #include <QPushButton>
31 #include <QTemporaryFile>
32
33 #include <ui/qt/utils/color_utils.h>
34 #include <ui/qt/utils/qt_ui_utils.h>
35 #include "rtp_player_dialog.h"
36 #include <ui/qt/utils/stock_icon.h>
37 #include "wireshark_application.h"
38 #include "ui/qt/widgets/wireshark_file_dialog.h"
39
40 /*
41  * @file RTP stream analysis dialog
42  *
43  * Displays forward and reverse RTP streams and graphs each stream
44  */
45
46 // To do:
47 // - Progress bar for tapping and saving.
48 // - Add a refresh button and/or action.
49 // - Fixup output file names.
50 // - Add a graph title and legend when saving?
51
52 enum {
53     packet_col_,
54     sequence_col_,
55     delta_col_,
56     jitter_col_,
57     skew_col_,
58     bandwidth_col_,
59     marker_col_,
60     status_col_
61 };
62
63 static const QRgb color_cn_ = 0xbfbfff;
64 static const QRgb color_rtp_warn_ = 0xffdbbf;
65 static const QRgb color_pt_event_ = 0xefffff;
66
67 enum { rtp_analysis_type_ = 1000 };
68 class RtpAnalysisTreeWidgetItem : public QTreeWidgetItem
69 {
70 public:
71     RtpAnalysisTreeWidgetItem(QTreeWidget *tree, tap_rtp_stat_t *statinfo, packet_info *pinfo, const struct _rtp_info *rtpinfo) :
72         QTreeWidgetItem(tree, rtp_analysis_type_)
73     {
74         frame_num_ = pinfo->num;
75         sequence_num_ = rtpinfo->info_seq_num;
76         pkt_len_ = pinfo->fd->pkt_len;
77         flags_ = statinfo->flags;
78         if (flags_ & STAT_FLAG_FIRST) {
79             delta_ = 0.0;
80             jitter_ = 0.0;
81             skew_ = 0.0;
82         } else {
83             delta_ = statinfo->delta;
84             jitter_ = statinfo->jitter;
85             skew_ = statinfo->skew;
86         }
87         bandwidth_ = statinfo->bandwidth;
88         marker_ = rtpinfo->info_marker_set ? true : false;
89         ok_ = false;
90
91         QColor bg_color = QColor();
92         QString status;
93
94         if (statinfo->pt == PT_CN) {
95             status = "Comfort noise (PT=13, RFC 3389)";
96             bg_color = color_cn_;
97         } else if (statinfo->pt == PT_CN_OLD) {
98             status = "Comfort noise (PT=19, reserved)";
99             bg_color = color_cn_;
100         } else if (statinfo->flags & STAT_FLAG_WRONG_SEQ) {
101             status = "Wrong sequence number";
102             bg_color = ColorUtils::expert_color_error;
103         } else if (statinfo->flags & STAT_FLAG_DUP_PKT) {
104             status = "Suspected duplicate (MAC address) only delta time calculated";
105             bg_color = color_rtp_warn_;
106         } else if (statinfo->flags & STAT_FLAG_REG_PT_CHANGE) {
107             status = QString("Payload changed to PT=%1").arg(statinfo->pt);
108             if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
109                 status.append(" telephone/event");
110             }
111             bg_color = color_rtp_warn_;
112         } else if (statinfo->flags & STAT_FLAG_WRONG_TIMESTAMP) {
113             status = "Incorrect timestamp";
114             /* color = COLOR_WARNING; */
115             bg_color = color_rtp_warn_;
116         } else if ((statinfo->flags & STAT_FLAG_PT_CHANGE)
117             &&  !(statinfo->flags & STAT_FLAG_FIRST)
118             &&  !(statinfo->flags & STAT_FLAG_PT_CN)
119             &&  (statinfo->flags & STAT_FLAG_FOLLOW_PT_CN)
120             &&  !(statinfo->flags & STAT_FLAG_MARKER)) {
121             status = "Marker missing?";
122             bg_color = color_rtp_warn_;
123         } else if (statinfo->flags & STAT_FLAG_PT_T_EVENT) {
124             status = QString("PT=%1 telephone/event").arg(statinfo->pt);
125             /* XXX add color? */
126             bg_color = color_pt_event_;
127         } else {
128             if (statinfo->flags & STAT_FLAG_MARKER) {
129                 bg_color = color_rtp_warn_;
130             }
131         }
132
133         if (status.isEmpty()) {
134             ok_ = true;
135             status = UTF8_CHECK_MARK;
136         }
137
138         setText(packet_col_, QString::number(frame_num_));
139         setText(sequence_col_, QString::number(sequence_num_));
140         setText(delta_col_, QString::number(delta_, 'f', 2));
141         setText(jitter_col_, QString::number(jitter_, 'f', 2));
142         setText(skew_col_, QString::number(skew_, 'f', 2));
143         setText(bandwidth_col_, QString::number(bandwidth_, 'f', 2));
144         if (marker_) {
145             setText(marker_col_, UTF8_BULLET);
146         }
147         setText(status_col_, status);
148
149         setTextAlignment(packet_col_, Qt::AlignRight);
150         setTextAlignment(sequence_col_, Qt::AlignRight);
151         setTextAlignment(delta_col_, Qt::AlignRight);
152         setTextAlignment(jitter_col_, Qt::AlignRight);
153         setTextAlignment(skew_col_, Qt::AlignRight);
154         setTextAlignment(bandwidth_col_, Qt::AlignRight);
155         setTextAlignment(marker_col_, Qt::AlignCenter);
156
157         if (bg_color.isValid()) {
158             for (int col = 0; col < columnCount(); col++) {
159                 setBackground(col, bg_color);
160                 setForeground(col, ColorUtils::expert_color_foreground);
161             }
162         }
163     }
164
165     guint32 frameNum() { return frame_num_; }
166     bool frameStatus() { return ok_; }
167
168     QList<QVariant> rowData() {
169         QString marker_str;
170         QString status_str = ok_ ? "OK" : text(status_col_);
171
172         if (marker_) marker_str = "SET";
173
174         return QList<QVariant>()
175                 << frame_num_ << sequence_num_ << delta_ << jitter_ << skew_ << bandwidth_
176                 << marker_str << status_str;
177     }
178
179     bool operator< (const QTreeWidgetItem &other) const
180     {
181         if (other.type() != rtp_analysis_type_) return QTreeWidgetItem::operator< (other);
182         const RtpAnalysisTreeWidgetItem *other_row = static_cast<const RtpAnalysisTreeWidgetItem *>(&other);
183
184         switch (treeWidget()->sortColumn()) {
185         case (packet_col_):
186             return frame_num_ < other_row->frame_num_;
187             break;
188         case (sequence_col_):
189             return sequence_num_ < other_row->sequence_num_;
190             break;
191         case (delta_col_):
192             return delta_ < other_row->delta_;
193             break;
194         case (jitter_col_):
195             return jitter_ < other_row->jitter_;
196             break;
197         case (skew_col_):
198             return skew_ < other_row->skew_;
199             break;
200         case (bandwidth_col_):
201             return bandwidth_ < other_row->bandwidth_;
202             break;
203         default:
204             break;
205         }
206
207         // Fall back to string comparison
208         return QTreeWidgetItem::operator <(other);
209     }
210 private:
211     guint32 frame_num_;
212     guint32 sequence_num_;
213     guint32 pkt_len_;
214     guint32 flags_;
215     double delta_;
216     double jitter_;
217     double skew_;
218     double bandwidth_;
219     bool marker_;
220     bool ok_;
221 };
222
223 enum {
224     fwd_jitter_graph_,
225     fwd_diff_graph_,
226     fwd_delta_graph_,
227     rev_jitter_graph_,
228     rev_diff_graph_,
229     rev_delta_graph_,
230     num_graphs_
231 };
232
233 RtpAnalysisDialog::RtpAnalysisDialog(QWidget &parent, CaptureFile &cf, rtpstream_info_t *stream_fwd, rtpstream_info_t *stream_rev) :
234     WiresharkDialog(parent, cf),
235     ui(new Ui::RtpAnalysisDialog),
236     num_streams_(0),
237     save_payload_error_(TAP_RTP_NO_ERROR)
238 {
239     ui->setupUi(this);
240     loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
241     setWindowSubtitle(tr("RTP Stream Analysis"));
242
243     ui->progressFrame->hide();
244
245     player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox);
246
247     stream_ctx_menu_.addAction(ui->actionGoToPacket);
248     stream_ctx_menu_.addAction(ui->actionNextProblem);
249     stream_ctx_menu_.addSeparator();
250     stream_ctx_menu_.addAction(ui->actionSaveAudioUnsync);
251     stream_ctx_menu_.addAction(ui->actionSaveForwardAudioUnsync);
252     stream_ctx_menu_.addAction(ui->actionSaveReverseAudioUnsync);
253     stream_ctx_menu_.addSeparator();
254     stream_ctx_menu_.addAction(ui->actionSaveAudioSyncStream);
255     stream_ctx_menu_.addAction(ui->actionSaveForwardAudioSyncStream);
256     stream_ctx_menu_.addAction(ui->actionSaveReverseAudioSyncStream);
257     stream_ctx_menu_.addSeparator();
258     stream_ctx_menu_.addAction(ui->actionSaveAudioSyncFile);
259     stream_ctx_menu_.addAction(ui->actionSaveForwardAudioSyncFile);
260     stream_ctx_menu_.addAction(ui->actionSaveReverseAudioSyncFile);
261     stream_ctx_menu_.addSeparator();
262     stream_ctx_menu_.addAction(ui->actionSaveCsv);
263     stream_ctx_menu_.addAction(ui->actionSaveForwardCsv);
264     stream_ctx_menu_.addAction(ui->actionSaveReverseCsv);
265     stream_ctx_menu_.addSeparator();
266     stream_ctx_menu_.addAction(ui->actionSaveGraph);
267     ui->forwardTreeWidget->installEventFilter(this);
268     ui->forwardTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
269     ui->forwardTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
270     connect(ui->forwardTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
271                 SLOT(showStreamMenu(QPoint)));
272     ui->reverseTreeWidget->installEventFilter(this);
273     ui->reverseTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
274     ui->reverseTreeWidget->header()->setSortIndicator(0, Qt::AscendingOrder);
275     connect(ui->reverseTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
276                 SLOT(showStreamMenu(QPoint)));
277     connect(ui->streamGraph, SIGNAL(mousePress(QMouseEvent*)),
278             this, SLOT(graphClicked(QMouseEvent*)));
279
280     graph_ctx_menu_.addAction(ui->actionSaveGraph);
281
282     QStringList header_labels;
283     for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
284         header_labels << ui->forwardTreeWidget->headerItem()->text(i);
285     }
286     ui->reverseTreeWidget->setHeaderLabels(header_labels);
287
288     memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
289     memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
290
291     QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
292             << ui->fJitterCheckBox << ui->fDiffCheckBox << ui->fDeltaCheckBox
293             << ui->rJitterCheckBox << ui->rDiffCheckBox << ui->rDeltaCheckBox;
294
295     for (int i = 0; i < num_graphs_; i++) {
296         QCPGraph *graph = ui->streamGraph->addGraph();
297         graph->setPen(QPen(ColorUtils::graphColor(i)));
298         graph->setName(graph_cbs[i]->text());
299         graphs_ << graph;
300         graph_cbs[i]->setChecked(true);
301         graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
302     }
303     ui->streamGraph->xAxis->setLabel("Arrival Time");
304     ui->streamGraph->yAxis->setLabel("Value (ms)");
305
306     // We keep our temp files open for the lifetime of the dialog. The GTK+
307     // UI opens and closes at various points.
308     QString tempname = QString("%1/wireshark_rtp_f").arg(QDir::tempPath());
309     fwd_tempfile_ = new QTemporaryFile(tempname, this);
310     fwd_tempfile_->open();
311     tempname = QString("%1/wireshark_rtp_r").arg(QDir::tempPath());
312     rev_tempfile_ = new QTemporaryFile(tempname, this);
313     rev_tempfile_->open();
314
315     if (fwd_tempfile_->error() != QFile::NoError || rev_tempfile_->error() != QFile::NoError) {
316         err_str_ = tr("Unable to save RTP data.");
317         ui->actionSaveAudioUnsync->setEnabled(false);
318         ui->actionSaveForwardAudioUnsync->setEnabled(false);
319         ui->actionSaveReverseAudioUnsync->setEnabled(false);
320         ui->actionSaveAudioSyncStream->setEnabled(false);
321         ui->actionSaveForwardAudioSyncStream->setEnabled(false);
322         ui->actionSaveReverseAudioSyncStream->setEnabled(false);
323         ui->actionSaveAudioSyncFile->setEnabled(false);
324         ui->actionSaveForwardAudioSyncFile->setEnabled(false);
325         ui->actionSaveReverseAudioSyncFile->setEnabled(false);
326     }
327
328     QMenu *save_menu = new QMenu();
329     save_menu->addAction(ui->actionSaveAudioUnsync);
330     save_menu->addAction(ui->actionSaveForwardAudioUnsync);
331     save_menu->addAction(ui->actionSaveReverseAudioUnsync);
332     save_menu->addSeparator();
333     save_menu->addAction(ui->actionSaveAudioSyncStream);
334     save_menu->addAction(ui->actionSaveForwardAudioSyncStream);
335     save_menu->addAction(ui->actionSaveReverseAudioSyncStream);
336     save_menu->addSeparator();
337     save_menu->addAction(ui->actionSaveAudioSyncFile);
338     save_menu->addAction(ui->actionSaveForwardAudioSyncFile);
339     save_menu->addAction(ui->actionSaveReverseAudioSyncFile);
340     save_menu->addSeparator();
341     save_menu->addAction(ui->actionSaveCsv);
342     save_menu->addAction(ui->actionSaveForwardCsv);
343     save_menu->addAction(ui->actionSaveReverseCsv);
344     save_menu->addSeparator();
345     save_menu->addAction(ui->actionSaveGraph);
346     ui->buttonBox->button(QDialogButtonBox::Save)->setMenu(save_menu);
347
348     if (stream_fwd) { // XXX What if stream_fwd == 0 && stream_rev != 0?
349         rtpstream_info_copy_deep(&fwd_statinfo_, stream_fwd);
350         num_streams_=1;
351         if (stream_rev) {
352             rtpstream_info_copy_deep(&rev_statinfo_, stream_rev);
353             num_streams_=2;
354         }
355     } else {
356         findStreams();
357     }
358
359     if (err_str_.isEmpty() && num_streams_ < 1) {
360         err_str_ = tr("No streams found.");
361     }
362
363     registerTapListener("rtp", this, NULL, 0, tapReset, tapPacket, tapDraw);
364     cap_file_.retapPackets();
365     removeTapListeners();
366
367     connect(ui->tabWidget, SIGNAL(currentChanged(int)),
368             this, SLOT(updateWidgets()));
369     connect(ui->forwardTreeWidget, SIGNAL(itemSelectionChanged()),
370             this, SLOT(updateWidgets()));
371     connect(ui->reverseTreeWidget, SIGNAL(itemSelectionChanged()),
372             this, SLOT(updateWidgets()));
373     connect(&cap_file_, SIGNAL(captureFileClosing()),
374             this, SLOT(updateWidgets()));
375     updateWidgets();
376
377     updateStatistics();
378 }
379
380 RtpAnalysisDialog::~RtpAnalysisDialog()
381 {
382     delete ui;
383 //    remove_tap_listener_rtpstream(&tapinfo_);
384     rtpstream_info_free_data(&fwd_statinfo_);
385     rtpstream_info_free_data(&rev_statinfo_);
386     delete fwd_tempfile_;
387     delete rev_tempfile_;
388 }
389
390 void RtpAnalysisDialog::captureFileClosing()
391 {
392     updateWidgets();
393     WiresharkDialog::captureFileClosing();
394 }
395
396 void RtpAnalysisDialog::updateWidgets()
397 {
398     bool enable_tab = false;
399     QString hint = err_str_;
400
401     if (hint.isEmpty()) {
402         enable_tab = true;
403         hint = tr("%1 streams found.").arg(num_streams_);
404     } else if (save_payload_error_ != TAP_RTP_NO_ERROR) {
405         /* We cannot save the payload but can still display the widget
406            or save CSV data */
407         enable_tab = true;
408     }
409
410     bool enable_nav = false;
411     if (!file_closed_
412             && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
413                  && ui->forwardTreeWidget->selectedItems().length() > 0)
414                 || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
415                     && ui->reverseTreeWidget->selectedItems().length() > 0))) {
416         enable_nav = true;
417     }
418     ui->actionGoToPacket->setEnabled(enable_nav);
419     ui->actionNextProblem->setEnabled(enable_nav);
420
421     if (enable_nav) {
422         hint.append(tr(" G: Go to packet, N: Next problem packet"));
423     }
424
425     bool enable_save_fwd_audio = fwd_statinfo_.rtp_stats.total_nr && (save_payload_error_ == TAP_RTP_NO_ERROR);
426     bool enable_save_rev_audio = rev_statinfo_.rtp_stats.total_nr && (save_payload_error_ == TAP_RTP_NO_ERROR);
427     ui->actionSaveAudioUnsync->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
428     ui->actionSaveForwardAudioUnsync->setEnabled(enable_save_fwd_audio);
429     ui->actionSaveReverseAudioUnsync->setEnabled(enable_save_rev_audio);
430     ui->actionSaveAudioSyncStream->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
431     ui->actionSaveForwardAudioSyncStream->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
432     ui->actionSaveReverseAudioSyncStream->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
433     ui->actionSaveAudioSyncFile->setEnabled(enable_save_fwd_audio && enable_save_rev_audio);
434     ui->actionSaveForwardAudioSyncFile->setEnabled(enable_save_fwd_audio);
435     ui->actionSaveReverseAudioSyncFile->setEnabled(enable_save_rev_audio);
436
437     bool enable_save_fwd_csv = ui->forwardTreeWidget->topLevelItemCount() > 0;
438     bool enable_save_rev_csv = ui->reverseTreeWidget->topLevelItemCount() > 0;
439     ui->actionSaveCsv->setEnabled(enable_save_fwd_csv && enable_save_rev_csv);
440     ui->actionSaveForwardCsv->setEnabled(enable_save_fwd_csv);
441     ui->actionSaveReverseCsv->setEnabled(enable_save_rev_csv);
442
443 #if defined(QT_MULTIMEDIA_LIB)
444     player_button_->setEnabled(num_streams_ > 0);
445 #else
446     player_button_->setEnabled(false);
447     player_button_->setText(tr("No Audio"));
448 #endif
449
450     ui->tabWidget->setEnabled(enable_tab);
451     hint.prepend("<small><i>");
452     hint.append("</i></small>");
453     ui->hintLabel->setText(hint);
454
455     WiresharkDialog::updateWidgets();
456 }
457
458 void RtpAnalysisDialog::on_actionGoToPacket_triggered()
459 {
460     if (file_closed_) return;
461     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
462     if (!cur_tree || cur_tree->selectedItems().length() < 1) return;
463
464     QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
465     if (ti->type() != rtp_analysis_type_) return;
466
467     RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
468     emit goToPacket(ra_ti->frameNum());
469 }
470
471 void RtpAnalysisDialog::on_actionNextProblem_triggered()
472 {
473     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
474     if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
475
476     // Choose convenience over correctness.
477     if (cur_tree->selectedItems().length() < 1) {
478         cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
479     }
480
481     QTreeWidgetItem *sel_ti = cur_tree->selectedItems()[0];
482     if (sel_ti->type() != rtp_analysis_type_) return;
483     QTreeWidgetItem *test_ti = cur_tree->itemBelow(sel_ti);
484     if (!test_ti) test_ti = cur_tree->topLevelItem(0);
485     while (test_ti != sel_ti) {
486         RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)test_ti);
487         if (!ra_ti->frameStatus()) {
488             cur_tree->setCurrentItem(ra_ti);
489             break;
490         }
491
492         test_ti = cur_tree->itemBelow(test_ti);
493         if (!test_ti) test_ti = cur_tree->topLevelItem(0);
494     }
495 }
496
497 void RtpAnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
498 {
499     ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
500     updateGraph();
501 }
502
503 void RtpAnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
504 {
505     ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
506     updateGraph();
507 }
508
509 void RtpAnalysisDialog::on_fDeltaCheckBox_toggled(bool checked)
510 {
511     ui->streamGraph->graph(fwd_delta_graph_)->setVisible(checked);
512     updateGraph();
513 }
514
515 void RtpAnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
516 {
517     ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
518     updateGraph();
519 }
520
521 void RtpAnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
522 {
523     ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
524     updateGraph();
525 }
526
527 void RtpAnalysisDialog::on_rDeltaCheckBox_toggled(bool checked)
528 {
529     ui->streamGraph->graph(rev_delta_graph_)->setVisible(checked);
530     updateGraph();
531 }
532
533 void RtpAnalysisDialog::on_actionSaveAudioUnsync_triggered()
534 {
535     saveAudio(dir_both_, sync_unsync_);
536 }
537
538 void RtpAnalysisDialog::on_actionSaveForwardAudioUnsync_triggered()
539 {
540     saveAudio(dir_forward_, sync_unsync_);
541 }
542
543 void RtpAnalysisDialog::on_actionSaveReverseAudioUnsync_triggered()
544 {
545     saveAudio(dir_reverse_, sync_unsync_);
546 }
547
548 void RtpAnalysisDialog::on_actionSaveAudioSyncStream_triggered()
549 {
550     saveAudio(dir_both_, sync_sync_stream_);
551 }
552
553 void RtpAnalysisDialog::on_actionSaveForwardAudioSyncStream_triggered()
554 {
555     saveAudio(dir_forward_, sync_sync_stream_);
556 }
557
558 void RtpAnalysisDialog::on_actionSaveReverseAudioSyncStream_triggered()
559 {
560     saveAudio(dir_reverse_, sync_sync_stream_);
561 }
562
563 void RtpAnalysisDialog::on_actionSaveAudioSyncFile_triggered()
564 {
565     saveAudio(dir_both_, sync_sync_file_);
566 }
567
568 void RtpAnalysisDialog::on_actionSaveForwardAudioSyncFile_triggered()
569 {
570     saveAudio(dir_forward_, sync_sync_file_);
571 }
572
573 void RtpAnalysisDialog::on_actionSaveReverseAudioSyncFile_triggered()
574 {
575     saveAudio(dir_reverse_, sync_sync_file_);
576 }
577
578 void RtpAnalysisDialog::on_actionSaveCsv_triggered()
579 {
580     saveCsv(dir_both_);
581 }
582
583 void RtpAnalysisDialog::on_actionSaveForwardCsv_triggered()
584 {
585     saveCsv(dir_forward_);
586 }
587
588 void RtpAnalysisDialog::on_actionSaveReverseCsv_triggered()
589 {
590     saveCsv(dir_reverse_);
591 }
592
593 void RtpAnalysisDialog::on_actionSaveGraph_triggered()
594 {
595     ui->tabWidget->setCurrentWidget(ui->graphTab);
596
597     QString file_name, extension;
598     QDir path(wsApp->lastOpenDir());
599     QString pdf_filter = tr("Portable Document Format (*.pdf)");
600     QString png_filter = tr("Portable Network Graphics (*.png)");
601     QString bmp_filter = tr("Windows Bitmap (*.bmp)");
602     // Gaze upon my beautiful graph with lossy artifacts!
603     QString jpeg_filter = tr("JPEG File Interchange Format (*.jpeg *.jpg)");
604     QString filter = QString("%1;;%2;;%3;;%4")
605             .arg(pdf_filter)
606             .arg(png_filter)
607             .arg(bmp_filter)
608             .arg(jpeg_filter);
609
610     QString save_file = path.canonicalPath();
611     if (!file_closed_) {
612         save_file += QString("/%1").arg(cap_file_.fileBaseName());
613     }
614     file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
615                                              save_file, filter, &extension);
616
617     if (!file_name.isEmpty()) {
618         bool save_ok = false;
619         // http://www.qcustomplot.com/index.php/support/forum/63
620 //        ui->streamGraph->legend->setVisible(true);
621         if (extension.compare(pdf_filter) == 0) {
622             save_ok = ui->streamGraph->savePdf(file_name);
623         } else if (extension.compare(png_filter) == 0) {
624             save_ok = ui->streamGraph->savePng(file_name);
625         } else if (extension.compare(bmp_filter) == 0) {
626             save_ok = ui->streamGraph->saveBmp(file_name);
627         } else if (extension.compare(jpeg_filter) == 0) {
628             save_ok = ui->streamGraph->saveJpg(file_name);
629         }
630 //        ui->streamGraph->legend->setVisible(false);
631         // else error dialog?
632         if (save_ok) {
633             path = QDir(file_name);
634             wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
635         }
636     }
637 }
638
639 void RtpAnalysisDialog::on_buttonBox_clicked(QAbstractButton *button)
640 {
641     if (button == player_button_) {
642         showPlayer();
643     }
644 }
645
646 void RtpAnalysisDialog::on_buttonBox_helpRequested()
647 {
648     wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG);
649 }
650
651 void RtpAnalysisDialog::tapReset(void *tapinfo_ptr)
652 {
653     RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
654     if (!rtp_analysis_dialog) return;
655
656     rtp_analysis_dialog->resetStatistics();
657 }
658
659 gboolean RtpAnalysisDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
660 {
661     RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
662     if (!rtp_analysis_dialog) return FALSE;
663
664     const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
665     if (!rtpinfo) return FALSE;
666
667     /* we ignore packets that are not displayed */
668     if (pinfo->fd->flags.passed_dfilter == 0)
669         return FALSE;
670     /* also ignore RTP Version != 2 */
671     else if (rtpinfo->info_version != 2)
672         return FALSE;
673     /* is it the forward direction?  */
674     else if (rtpstream_id_equal_pinfo_rtp_info(&(rtp_analysis_dialog->fwd_statinfo_.id),pinfo,rtpinfo))  {
675
676         rtp_analysis_dialog->addPacket(true, pinfo, rtpinfo);
677     }
678     /* is it the reversed direction? */
679     else if (rtpstream_id_equal_pinfo_rtp_info(&(rtp_analysis_dialog->rev_statinfo_.id),pinfo,rtpinfo))  {
680
681         rtp_analysis_dialog->addPacket(false, pinfo, rtpinfo);
682     }
683     return FALSE;
684 }
685
686 void RtpAnalysisDialog::tapDraw(void *tapinfo_ptr)
687 {
688     RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
689     if (!rtp_analysis_dialog) return;
690     rtp_analysis_dialog->updateStatistics();
691 }
692
693 void RtpAnalysisDialog::resetStatistics()
694 {
695     memset(&fwd_statinfo_.rtp_stats, 0, sizeof(fwd_statinfo_.rtp_stats));
696     memset(&rev_statinfo_.rtp_stats, 0, sizeof(rev_statinfo_.rtp_stats));
697
698     fwd_statinfo_.rtp_stats.first_packet = TRUE;
699     rev_statinfo_.rtp_stats.first_packet = TRUE;
700     fwd_statinfo_.rtp_stats.reg_pt = PT_UNDEFINED;
701     rev_statinfo_.rtp_stats.reg_pt = PT_UNDEFINED;
702
703     ui->forwardTreeWidget->clear();
704     ui->reverseTreeWidget->clear();
705
706     for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
707         ui->streamGraph->graph(i)->clearData();
708     }
709
710     fwd_time_vals_.clear();
711     fwd_jitter_vals_.clear();
712     fwd_diff_vals_.clear();
713     fwd_delta_vals_.clear();
714     rev_time_vals_.clear();
715     rev_jitter_vals_.clear();
716     rev_diff_vals_.clear();
717     rev_delta_vals_.clear();
718
719     fwd_tempfile_->resize(0);
720     rev_tempfile_->resize(0);
721 }
722
723 void RtpAnalysisDialog::addPacket(bool forward, packet_info *pinfo, const _rtp_info *rtpinfo)
724 {
725     /* add this RTP for future listening using the RTP Player*/
726 //    add_rtp_packet(rtpinfo, pinfo);
727
728     if (forward) {
729         rtppacket_analyse(&fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
730         new RtpAnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
731
732         fwd_time_vals_.append(fwd_statinfo_.rtp_stats.time / 1000);
733         fwd_jitter_vals_.append(fwd_statinfo_.rtp_stats.jitter);
734         fwd_diff_vals_.append(fwd_statinfo_.rtp_stats.diff);
735         fwd_delta_vals_.append(fwd_statinfo_.rtp_stats.delta);
736
737         savePayload(fwd_tempfile_, &fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
738     } else {
739         rtppacket_analyse(&rev_statinfo_.rtp_stats, pinfo, rtpinfo);
740         new RtpAnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_.rtp_stats, pinfo, rtpinfo);
741
742         rev_time_vals_.append(rev_statinfo_.rtp_stats.time / 1000);
743         rev_jitter_vals_.append(rev_statinfo_.rtp_stats.jitter);
744         rev_diff_vals_.append(rev_statinfo_.rtp_stats.diff);
745         rev_delta_vals_.append(rev_statinfo_.rtp_stats.delta);
746
747         savePayload(rev_tempfile_, &rev_statinfo_.rtp_stats, pinfo, rtpinfo);
748     }
749
750 }
751
752 void RtpAnalysisDialog::savePayload(QTemporaryFile *tmpfile, tap_rtp_stat_t *statinfo, packet_info *pinfo, const _rtp_info *rtpinfo)
753 {
754     /* Is this the first packet we got in this direction? */
755 //    if (statinfo->flags & STAT_FLAG_FIRST) {
756 //        if (saveinfo->fp == NULL) {
757 //            saveinfo->saved = FALSE;
758 //            saveinfo->error_type = TAP_RTP_FILE_OPEN_ERROR;
759 //        } else {
760 //            saveinfo->saved = TRUE;
761 //        }
762 //    }
763
764     /* Save the voice information */
765
766     /* If there was already an error, we quit */
767     if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError)
768         return;
769
770     /* Quit if the captured length and packet length aren't equal or
771      * if the RTP dissector thinks there is some information missing
772      */
773     if ((pinfo->fd->pkt_len != pinfo->fd->cap_len) &&
774         (!rtpinfo->info_all_data_present))
775     {
776         tmpfile->close();
777         err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
778         save_payload_error_ = TAP_RTP_WRONG_LENGTH;
779         return;
780     }
781
782     /* If padding bit is set but the padding count is bigger
783      * then the whole RTP data - error with padding count
784      */
785     if ((rtpinfo->info_padding_set != FALSE) &&
786         (rtpinfo->info_padding_count > rtpinfo->info_payload_len))
787     {
788         tmpfile->close();
789         err_str_ = tr("Can't save in a file: RTP data with padding.");
790         save_payload_error_ = TAP_RTP_PADDING_ERROR;
791         return;
792     }
793
794     if ((rtpinfo->info_payload_type == PT_CN) ||
795         (rtpinfo->info_payload_type == PT_CN_OLD)) {
796     } else { /* All other payloads */
797         const char *data;
798         size_t nchars;
799         tap_rtp_save_data_t save_data;
800
801         if (!rtpinfo->info_all_data_present) {
802             /* Not all the data was captured. */
803             tmpfile->close();
804             err_str_ = tr("Can't save in a file: Not all data in all packets was captured.");
805             save_payload_error_ = TAP_RTP_WRONG_LENGTH;
806             return;
807         }
808
809         /* We put the pointer at the beginning of the RTP
810          * payload, that is, at the beginning of the RTP data
811          * plus the offset of the payload from the beginning
812          * of the RTP data */
813         data = (const char *) rtpinfo->info_data + rtpinfo->info_payload_offset;
814
815         /* Store information about timestamp, payload_type and payload in file */
816         save_data.timestamp = statinfo->timestamp;
817         save_data.payload_type = rtpinfo->info_payload_type;
818         save_data.payload_len = rtpinfo->info_payload_len - rtpinfo->info_padding_count;
819         nchars = tmpfile->write((char *)&save_data, sizeof(save_data));
820         if (nchars != sizeof(save_data)) {
821                 /* Write error or short write */
822                 err_str_ = tr("Can't save in a file: File I/O problem.");
823                 save_payload_error_ = TAP_RTP_FILE_WRITE_ERROR;
824                 tmpfile->close();
825                 return;
826         }
827         if (save_data.payload_len > 0) {
828             nchars = tmpfile->write(data, save_data.payload_len);
829             if (nchars != save_data.payload_len) {
830                 /* Write error or short write */
831                 err_str_ = tr("Can't save in a file: File I/O problem.");
832                 save_payload_error_ = TAP_RTP_FILE_WRITE_ERROR;
833                 tmpfile->close();
834                 return;
835             }
836         }
837         return;
838     }
839     return;
840 }
841
842 void RtpAnalysisDialog::updateStatistics()
843 {
844     unsigned int f_clock_rate = fwd_statinfo_.rtp_stats.clock_rate;
845     unsigned int r_clock_rate = rev_statinfo_.rtp_stats.clock_rate;
846     unsigned int f_expected = (fwd_statinfo_.rtp_stats.stop_seq_nr + fwd_statinfo_.rtp_stats.cycles*65536)
847             - fwd_statinfo_.rtp_stats.start_seq_nr + 1;
848     unsigned int r_expected = (rev_statinfo_.rtp_stats.stop_seq_nr + rev_statinfo_.rtp_stats.cycles*65536)
849             - rev_statinfo_.rtp_stats.start_seq_nr + 1;
850     unsigned int f_total_nr = fwd_statinfo_.rtp_stats.total_nr;
851     unsigned int r_total_nr = rev_statinfo_.rtp_stats.total_nr;
852     int f_lost = f_expected - f_total_nr;
853     int r_lost = r_expected - r_total_nr;
854     double f_sumt = fwd_statinfo_.rtp_stats.sumt;
855     double f_sumTS = fwd_statinfo_.rtp_stats.sumTS;
856     double f_sumt2 = fwd_statinfo_.rtp_stats.sumt2;
857     double f_sumtTS = fwd_statinfo_.rtp_stats.sumtTS;
858     double r_sumt = rev_statinfo_.rtp_stats.sumt;
859     double r_sumTS = rev_statinfo_.rtp_stats.sumTS;
860     double r_sumt2 = rev_statinfo_.rtp_stats.sumt2;
861     double r_sumtTS = rev_statinfo_.rtp_stats.sumtTS;
862     double f_perc, r_perc;
863     double f_clock_drift = 1.0;
864     double r_clock_drift = 1.0;
865     double f_duration = fwd_statinfo_.rtp_stats.time - fwd_statinfo_.rtp_stats.start_time;
866     double r_duration = rev_statinfo_.rtp_stats.time - rev_statinfo_.rtp_stats.start_time;
867
868     if (f_clock_rate == 0) {
869         f_clock_rate = 1;
870     }
871
872     if (r_clock_rate == 0) {
873         r_clock_rate = 1;
874     }
875
876     if (f_expected) {
877         f_perc = (double)(f_lost*100)/(double)f_expected;
878     } else {
879         f_perc = 0;
880     }
881     if (r_expected) {
882         r_perc = (double)(r_lost*100)/(double)r_expected;
883     } else {
884         r_perc = 0;
885     }
886
887     if ((f_total_nr >0) && (f_sumt2 > 0)) {
888         f_clock_drift = (f_total_nr * f_sumtTS - f_sumt * f_sumTS) / (f_total_nr * f_sumt2 - f_sumt * f_sumt);
889     }
890     if ((r_total_nr >0) && (r_sumt2 > 0)) {
891         r_clock_drift = (r_total_nr * r_sumtTS - r_sumt * r_sumTS) / (r_total_nr * r_sumt2 - r_sumt * r_sumt);
892     }
893
894     QString stats_tables = "<html><head><style>td{vertical-align:bottom;}</style></head><body>\n";
895     stats_tables += QString("<p>%1:%2 " UTF8_LEFT_RIGHT_ARROW)
896             .arg(address_to_qstring(&fwd_statinfo_.id.src_addr, true))
897             .arg(fwd_statinfo_.id.src_port);
898     stats_tables += QString("<br>%1:%2</p>\n")
899             .arg(address_to_qstring(&fwd_statinfo_.id.dst_addr, true))
900             .arg(fwd_statinfo_.id.dst_port);
901     stats_tables += "<h4>Forward</h4>\n";
902     stats_tables += "<p><table>\n";
903     stats_tables += QString("<tr><th align=\"left\">SSRC</th><td>%1</td></tr>")
904             .arg(int_to_qstring(fwd_statinfo_.id.ssrc, 8, 16));
905     stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
906             .arg(fwd_statinfo_.rtp_stats.max_delta, 0, 'f', 2)
907             .arg(fwd_statinfo_.rtp_stats.max_nr);
908     stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</td></tr>")
909             .arg(fwd_statinfo_.rtp_stats.max_jitter, 0, 'f', 2);
910     stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</td></tr>")
911             .arg(fwd_statinfo_.rtp_stats.mean_jitter, 0, 'f', 2);
912     stats_tables += QString("<tr><th align=\"left\">Max Skew</th><td>%1 ms</td></tr>")
913             .arg(fwd_statinfo_.rtp_stats.max_skew, 0, 'f', 2);
914     stats_tables += QString("<tr><th align=\"left\">RTP Packets</th><td>%1</td></tr>")
915             .arg(f_total_nr);
916     stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</td></tr>")
917             .arg(f_expected);
918     stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</td></tr>")
919             .arg(f_lost).arg(f_perc, 0, 'f', 2);
920     stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</td></tr>")
921             .arg(fwd_statinfo_.rtp_stats.sequence);
922     stats_tables += QString("<tr><th align=\"left\">Start at</th><td>%1 s @ %2</td></tr>")
923             .arg(fwd_statinfo_.rtp_stats.start_time / 1000.0, 0, 'f', 6)
924             .arg(fwd_statinfo_.rtp_stats.first_packet_num);
925     stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</td></tr>")
926             .arg(f_duration / 1000.0, 0, 'f', 2);
927     stats_tables += QString("<tr><th align=\"left\">Clock Drift</th><td>%1 ms</td></tr>")
928             .arg(f_duration * (f_clock_drift - 1.0), 0, 'f', 0);
929     stats_tables += QString("<tr><th align=\"left\">Freq Drift</th><td>%1 Hz (%2 %)</td></tr>") // XXX Terminology?
930             .arg(f_clock_drift * f_clock_rate, 0, 'f', 0).arg(100.0 * (f_clock_drift - 1.0), 0, 'f', 2);
931     stats_tables += "</table></p>\n";
932
933     stats_tables += "<h4>Reverse</h4>\n";
934     stats_tables += "<p><table>\n";
935     stats_tables += QString("<tr><th align=\"left\">SSRC</th><td>%1</td></tr>")
936             .arg(int_to_qstring(rev_statinfo_.id.ssrc, 8, 16));
937     stats_tables += QString("<tr><th align=\"left\">Max Delta</th><td>%1 ms @ %2</td></tr>")
938             .arg(rev_statinfo_.rtp_stats.max_delta, 0, 'f', 2)
939             .arg(rev_statinfo_.rtp_stats.max_nr);
940     stats_tables += QString("<tr><th align=\"left\">Max Jitter</th><td>%1 ms</td></tr>")
941             .arg(rev_statinfo_.rtp_stats.max_jitter, 0, 'f', 2);
942     stats_tables += QString("<tr><th align=\"left\">Mean Jitter</th><td>%1 ms</td></tr>")
943             .arg(rev_statinfo_.rtp_stats.mean_jitter, 0, 'f', 2);
944     stats_tables += QString("<tr><th align=\"left\">Max Skew</th><td>%1 ms</td></tr>")
945             .arg(rev_statinfo_.rtp_stats.max_skew, 0, 'f', 2);
946     stats_tables += QString("<tr><th align=\"left\">RTP Packets</th><td>%1</td></tr>")
947             .arg(r_total_nr);
948     stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</td></tr>")
949             .arg(r_expected);
950     stats_tables += QString("<tr><th align=\"left\">Lost</th><td>%1 (%2 %)</td></tr>")
951             .arg(r_lost).arg(r_perc, 0, 'f', 2);
952     stats_tables += QString("<tr><th align=\"left\">Seq Errs</th><td>%1</td></tr>")
953             .arg(rev_statinfo_.rtp_stats.sequence);
954     stats_tables += QString("<tr><th align=\"left\">Start at</th><td>%1 s @ %2</td></tr>")
955             .arg(rev_statinfo_.rtp_stats.start_time / 1000.0, 0, 'f', 6)
956             .arg(rev_statinfo_.rtp_stats.first_packet_num);
957     stats_tables += QString("<tr><th align=\"left\">Duration</th><td>%1 s</td></tr>")
958             .arg(r_duration / 1000.0, 0, 'f', 2);
959     stats_tables += QString("<tr><th align=\"left\">Clock Drift</th><td>%1 ms</td></tr>")
960             .arg(r_duration * (r_clock_drift - 1.0), 0, 'f', 0);
961     stats_tables += QString("<tr><th align=\"left\">Freq Drift</th><td>%1 Hz (%2 %)</td></tr>") // XXX Terminology?
962             .arg(r_clock_drift * r_clock_rate, 0, 'f', 0).arg(100.0 * (r_clock_drift - 1.0), 0, 'f', 2);
963     stats_tables += "</table></p>";
964     if (rev_statinfo_.rtp_stats.total_nr) {
965         stats_tables += QString("<h4>Forward to reverse<br/>start diff %1 s @ %2</h4>")
966             .arg((rev_statinfo_.rtp_stats.start_time - fwd_statinfo_.rtp_stats.start_time) / 1000.0, 0, 'f', 6)
967             .arg((gint64)rev_statinfo_.rtp_stats.first_packet_num - (gint64)fwd_statinfo_.rtp_stats.first_packet_num);
968     }
969     stats_tables += "</body></html>\n";
970
971     ui->statisticsLabel->setText(stats_tables);
972
973     for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
974         ui->forwardTreeWidget->resizeColumnToContents(col);
975         ui->reverseTreeWidget->resizeColumnToContents(col);
976     }
977
978     graphs_[fwd_jitter_graph_]->setData(fwd_time_vals_, fwd_jitter_vals_);
979     graphs_[fwd_diff_graph_]->setData(fwd_time_vals_, fwd_diff_vals_);
980     graphs_[fwd_delta_graph_]->setData(fwd_time_vals_, fwd_delta_vals_);
981     graphs_[rev_jitter_graph_]->setData(rev_time_vals_, rev_jitter_vals_);
982     graphs_[rev_diff_graph_]->setData(rev_time_vals_, rev_diff_vals_);
983     graphs_[rev_delta_graph_]->setData(rev_time_vals_, rev_delta_vals_);
984
985     updateGraph();
986
987     updateWidgets();
988 }
989
990 void RtpAnalysisDialog::updateGraph()
991 {
992     for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
993         if (ui->streamGraph->graph(i)->visible()) {
994             ui->streamGraph->graph(i)->rescaleAxes(i > 0);
995         }
996     }
997     ui->streamGraph->replot();
998 }
999
1000 void RtpAnalysisDialog::showPlayer()
1001 {
1002 #ifdef QT_MULTIMEDIA_LIB
1003     if (num_streams_ < 1) return;
1004
1005     RtpPlayerDialog rtp_player_dialog(*this, cap_file_);
1006     rtpstream_info_t stream_info;
1007
1008     // XXX We might want to create an "rtp_stream_id_t" struct with only
1009     // addresses, ports & SSRC.
1010     rtpstream_info_init(&stream_info);
1011     rtpstream_id_copy(&fwd_statinfo_.id, &stream_info.id);
1012     stream_info.packet_count = fwd_statinfo_.packet_count;
1013     stream_info.setup_frame_number = fwd_statinfo_.setup_frame_number;
1014     nstime_copy(&stream_info.start_rel_time, &fwd_statinfo_.start_rel_time);
1015     rtp_player_dialog.addRtpStream(&stream_info);
1016
1017     if (num_streams_ > 1) {
1018         rtpstream_info_init(&stream_info);
1019         rtpstream_id_copy(&rev_statinfo_.id, &stream_info.id);
1020         stream_info.packet_count = rev_statinfo_.packet_count;
1021         stream_info.setup_frame_number = rev_statinfo_.setup_frame_number;
1022         nstime_copy(&stream_info.start_rel_time, &rev_statinfo_.start_rel_time);
1023         rtp_player_dialog.addRtpStream(&stream_info);
1024     }
1025
1026     connect(&rtp_player_dialog, SIGNAL(goToPacket(int)), this, SIGNAL(goToPacket(int)));
1027
1028     rtp_player_dialog.exec();
1029 #endif // QT_MULTIMEDIA_LIB
1030 }
1031
1032 /* Convert one packet payload to samples in row */
1033 /* It supports G.711 now, but can be extended to any other codecs */
1034 size_t RtpAnalysisDialog::convert_payload_to_samples(unsigned int payload_type, QTemporaryFile *tempfile, guint8 *pd_out, size_t payload_len)
1035 {
1036     size_t sample_count;
1037     char f_rawvalue;
1038     gint16 sample;
1039     guint8 pd[4];
1040
1041     if (payload_type == PT_PCMU) {
1042         /* Output sample count is same as input sample count for G.711 */
1043         sample_count = payload_len;
1044         for(size_t i = 0; i < payload_len; i++) {
1045             tempfile->read((char *)&f_rawvalue, sizeof(f_rawvalue));
1046             sample = ulaw2linear((unsigned char)f_rawvalue);
1047             phton16(pd, sample);
1048             pd_out[2*i] = pd[0];
1049             pd_out[2*i+1] = pd[1];
1050         }
1051     } else if (payload_type == PT_PCMA) {
1052         /* Output sample count is same as input sample count for G.711 */
1053         sample_count = payload_len;
1054         for(size_t i = 0; i < payload_len; i++) {
1055             tempfile->read((char *)&f_rawvalue, sizeof(f_rawvalue));
1056             sample = alaw2linear((unsigned char)f_rawvalue);
1057             phton16(pd, sample);
1058             pd_out[2*i] = pd[0];
1059             pd_out[2*i+1] = pd[1];
1060         }
1061     } else {
1062         /* Read payload, but ignore it */
1063         sample_count = 0;
1064         for(size_t i = 0; i < payload_len; i++) {
1065             tempfile->read((char *)&f_rawvalue, sizeof(f_rawvalue));
1066         }
1067     }
1068
1069     return sample_count;
1070 }
1071
1072 gboolean RtpAnalysisDialog::saveAudioAUSilence(size_t total_len, QFile *save_file, gboolean *stop_flag)
1073 {
1074     size_t nchars;
1075     guint8 pd_out[2*4000];
1076     gint16 silence;
1077     guint8 pd[4];
1078
1079     silence = 0x0000;
1080     phton16(pd, silence);
1081     pd_out[0] = pd[0];
1082     pd_out[1] = pd[1];
1083     /* Fill whole file with silence */
1084     for(size_t i=0; i<total_len; i++) {
1085         if (*stop_flag) {
1086             return FALSE;
1087         }
1088         nchars = save_file->write((const char *)pd_out, 2);
1089         if (nchars < 2) {
1090             return FALSE;
1091         }
1092     }
1093
1094     return TRUE;
1095 }
1096
1097 gboolean RtpAnalysisDialog::saveAudioAUUnidir(tap_rtp_stat_t &statinfo, QTemporaryFile *tempfile, QFile *save_file, size_t header_end, gboolean *stop_flag, gboolean interleave, size_t prefix_silence)
1098 {
1099     size_t nchars;
1100     guint8 pd_out[2*4000];
1101     guint8 pd[4];
1102     tap_rtp_save_data_t save_data;
1103
1104     while (sizeof(save_data) == tempfile->read((char *)&save_data,sizeof(save_data))) {
1105         size_t sample_count;
1106
1107         if (*stop_flag) {
1108             return FALSE;
1109         }
1110         ui->progressFrame->setValue(int(tempfile->pos() * 100 / tempfile->size()));
1111
1112         sample_count=convert_payload_to_samples(save_data.payload_type, tempfile ,pd_out, save_data.payload_len);
1113
1114         if (sample_count > 0 ) {
1115             nchars = 0;
1116             /* Save payload samples with optional interleaving */
1117             for (size_t i = 0; i < sample_count; i++) {
1118                 pd[0] = pd_out[ 2 * i ];
1119                 pd[1] = pd_out[ 2 * i + 1 ];
1120                 if (interleave) {
1121                     save_file->seek(header_end+(prefix_silence + (guint32_wraparound_diff(save_data.timestamp, statinfo.first_timestamp) + i)) * 4);
1122                 } else {
1123                     save_file->seek(header_end+(prefix_silence + (guint32_wraparound_diff(save_data.timestamp, statinfo.first_timestamp) + i)) * 2);
1124                 }
1125                 nchars += save_file->write((const char *)pd, 2);
1126             }
1127             if (nchars < sample_count*2) {
1128                 return FALSE;
1129             }
1130         }
1131     }
1132
1133     return TRUE;
1134 }
1135
1136 gboolean RtpAnalysisDialog::saveAudioAUBidir(tap_rtp_stat_t &fwd_statinfo, tap_rtp_stat_t &rev_statinfo, QTemporaryFile *fwd_tempfile, QTemporaryFile *rev_tempfile, QFile *save_file, size_t header_end, gboolean *stop_flag, size_t prefix_silence_fwd, size_t prefix_silence_rev)
1137 {
1138     if (! saveAudioAUUnidir(fwd_statinfo, fwd_tempfile, save_file, header_end, stop_flag, TRUE, prefix_silence_fwd))
1139     {
1140         return FALSE;
1141     }
1142     if (! saveAudioAUUnidir(rev_statinfo, rev_tempfile, save_file, header_end+2, stop_flag, TRUE, prefix_silence_rev))
1143     {
1144         return FALSE;
1145     }
1146
1147     return TRUE;
1148 }
1149
1150 gboolean RtpAnalysisDialog::saveAudioAU(StreamDirection direction, QFile *save_file, gboolean *stop_flag, RtpAnalysisDialog::SyncType sync)
1151 {
1152     guint8 pd[4];
1153     size_t nchars;
1154     size_t header_end;
1155     size_t fwd_total_len;
1156     size_t rev_total_len;
1157     size_t total_len;
1158
1159     /* http://pubs.opengroup.org/external/auformat.html */
1160     /* First we write the .au header.  All values in the header are
1161      * 4-byte big-endian values, so we use pntoh32() to copy them
1162      * to a 4-byte buffer, in big-endian order, and then write out
1163      * the buffer. */
1164
1165     /* the magic word 0x2e736e64 == .snd */
1166     phton32(pd, 0x2e736e64);
1167     nchars = save_file->write((const char *)pd, 4);
1168     if (nchars != 4)
1169         return FALSE;
1170     /* header offset == 24 bytes */
1171     phton32(pd, 24);
1172     nchars = save_file->write((const char *)pd, 4);
1173     if (nchars != 4)
1174         return FALSE;
1175     /* total length; it is permitted to set this to 0xffffffff */
1176     phton32(pd, 0xffffffff);
1177     nchars = save_file->write((const char *)pd, 4);
1178     if (nchars != 4)
1179         return FALSE;
1180     /* encoding format == 16-bit linear PCM */
1181     phton32(pd, 3);
1182     nchars = save_file->write((const char *)pd, 4);
1183     if (nchars != 4)
1184         return FALSE;
1185     /* sample rate == 8000 Hz */
1186     phton32(pd, 8000);
1187     nchars = save_file->write((const char *)pd, 4);
1188     if (nchars != 4)
1189         return FALSE;
1190     /* channels == 1 or == 2 */
1191     switch (direction) {
1192         case dir_forward_: {
1193             phton32(pd, 1);
1194             break;
1195         }
1196         case dir_reverse_: {
1197             phton32(pd, 1);
1198             break;
1199         }
1200         case dir_both_: {
1201             phton32(pd, 2);
1202             break;
1203         }
1204     }
1205     nchars = save_file->write((const char *)pd, 4);
1206     if (nchars != 4)
1207         return FALSE;
1208
1209     header_end=save_file->pos();
1210
1211     bool two_channels = rev_statinfo_.rtp_stats.total_nr && (save_payload_error_ == TAP_RTP_NO_ERROR);
1212     double t_min = MIN(fwd_statinfo_.rtp_stats.start_time, rev_statinfo_.rtp_stats.start_time);
1213     double t_fwd_diff = fwd_statinfo_.rtp_stats.start_time - t_min;
1214     double t_rev_diff = rev_statinfo_.rtp_stats.start_time - t_min;
1215     size_t fwd_samples_diff = 0;
1216     size_t rev_samples_diff = 0;
1217     size_t bidir_samples_diff = 0;
1218
1219     switch (sync) {
1220         case sync_unsync_: {
1221             fwd_samples_diff = 0;
1222             rev_samples_diff = 0;
1223             bidir_samples_diff = 0;
1224             break;
1225         }
1226         case sync_sync_stream_: {
1227             if (! two_channels) {
1228                 /* Only forward channel */
1229                 /* This branch should not be reached ever */
1230                 QMessageBox::warning(this, tr("Warning"), tr("Can't synchronize when only one channel is selected"));
1231                 return FALSE;
1232             } else {
1233                 /* Two channels */
1234                 fwd_samples_diff = t_fwd_diff*8000/1000;
1235                 rev_samples_diff = t_rev_diff*8000/1000;
1236                 bidir_samples_diff = 0;
1237             }
1238             break;
1239         }
1240         case sync_sync_file_: {
1241             if (! two_channels) {
1242                 /* Only forward channel */
1243                 fwd_samples_diff = t_fwd_diff*8000/1000;
1244                 rev_samples_diff = 0;
1245                 bidir_samples_diff = fwd_samples_diff;
1246             } else {
1247                 /* Two channels */
1248                 fwd_samples_diff = t_fwd_diff*8000/1000;
1249                 rev_samples_diff = t_rev_diff*8000/1000;
1250                 bidir_samples_diff = t_min*8000/1000;
1251             }
1252             break;
1253         }
1254     }
1255
1256     switch (direction) {
1257         /* Only forward direction */
1258         case dir_forward_: {
1259             fwd_total_len = guint32_wraparound_diff(fwd_statinfo_.rtp_stats.timestamp, fwd_statinfo_.rtp_stats.first_timestamp) + fwd_statinfo_.rtp_stats.last_payload_len;
1260             if (! saveAudioAUSilence(fwd_total_len + fwd_samples_diff + bidir_samples_diff, save_file, stop_flag))
1261             {
1262                 return FALSE;
1263             }
1264             if (! saveAudioAUUnidir(fwd_statinfo_.rtp_stats, fwd_tempfile_, save_file, header_end, stop_flag, FALSE, fwd_samples_diff + bidir_samples_diff))
1265             {
1266                 return FALSE;
1267             }
1268             break;
1269         }
1270         /* Only reverse direction */
1271         case dir_reverse_: {
1272             rev_total_len = guint32_wraparound_diff(rev_statinfo_.rtp_stats.timestamp, rev_statinfo_.rtp_stats.first_timestamp) + rev_statinfo_.rtp_stats.last_payload_len;
1273             if (! saveAudioAUSilence(rev_total_len + rev_samples_diff + bidir_samples_diff, save_file, stop_flag))
1274             {
1275                 return FALSE;
1276             }
1277             if (! saveAudioAUUnidir(rev_statinfo_.rtp_stats, rev_tempfile_, save_file, header_end, stop_flag, FALSE, rev_samples_diff + bidir_samples_diff))
1278             {
1279                 return FALSE;
1280             }
1281             break;
1282         }
1283         /* Both directions */
1284         case dir_both_: {
1285             fwd_total_len = guint32_wraparound_diff(fwd_statinfo_.rtp_stats.timestamp, fwd_statinfo_.rtp_stats.first_timestamp) + fwd_statinfo_.rtp_stats.last_payload_len;
1286             rev_total_len = guint32_wraparound_diff(rev_statinfo_.rtp_stats.timestamp, rev_statinfo_.rtp_stats.first_timestamp) + rev_statinfo_.rtp_stats.last_payload_len;
1287             total_len = MAX(fwd_total_len + fwd_samples_diff, rev_total_len + rev_samples_diff);
1288             if (! saveAudioAUSilence((total_len + bidir_samples_diff) * 2, save_file, stop_flag))
1289             {
1290                 return FALSE;
1291             }
1292             if (! saveAudioAUBidir(fwd_statinfo_.rtp_stats, rev_statinfo_.rtp_stats, fwd_tempfile_, rev_tempfile_, save_file, header_end, stop_flag, fwd_samples_diff + bidir_samples_diff, rev_samples_diff + bidir_samples_diff))
1293             {
1294                 return FALSE;
1295             }
1296         }
1297     }
1298
1299     return TRUE;
1300 }
1301
1302 gboolean RtpAnalysisDialog::saveAudioRAW(StreamDirection direction, QFile *save_file, gboolean *stop_flag)
1303 {
1304     QFile *tempfile;
1305     tap_rtp_save_data_t save_data;
1306
1307     switch (direction) {
1308         /* Only forward direction */
1309         case dir_forward_: {
1310             tempfile = fwd_tempfile_;
1311             break;
1312         }
1313         /* Only reversed direction */
1314         case dir_reverse_: {
1315             tempfile = rev_tempfile_;
1316             break;
1317         }
1318         default: {
1319             return FALSE;
1320         }
1321     }
1322
1323     /* Copy just payload */
1324     while (sizeof(save_data) == tempfile->read((char *)&save_data,sizeof(save_data))) {
1325         char f_rawvalue;
1326
1327         if (*stop_flag) {
1328             return FALSE;
1329         }
1330
1331         ui->progressFrame->setValue(int(tempfile->pos() * 100 / fwd_tempfile_->size()));
1332
1333         if (save_data.payload_len > 0) {
1334             for(size_t i = 0; i < save_data.payload_len; i++) {
1335                 if (sizeof(f_rawvalue) != tempfile->read((char *)&f_rawvalue, sizeof(f_rawvalue))) {
1336                     return FALSE;
1337                 }
1338                 if (sizeof(f_rawvalue) != save_file->write((char *)&f_rawvalue, sizeof(f_rawvalue))) {
1339                     return FALSE;
1340                 }
1341             }
1342         }
1343     }
1344
1345     return TRUE;
1346 }
1347
1348 // rtp_analysis.c:copy_file
1349 enum { save_audio_none_, save_audio_au_, save_audio_raw_ };
1350 void RtpAnalysisDialog::saveAudio(RtpAnalysisDialog::StreamDirection direction, RtpAnalysisDialog::SyncType sync)
1351 {
1352     if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
1353
1354     QString caption;
1355
1356     switch (direction) {
1357     case dir_forward_:
1358         caption = tr("Save forward stream audio");
1359         break;
1360     case dir_reverse_:
1361         caption = tr("Save reverse stream audio");
1362         break;
1363     case dir_both_:
1364     default:
1365         caption = tr("Save forward and reverse stream audio");
1366         break;
1367     }
1368
1369     QString ext_filter = "";
1370     QString ext_filter_au = tr("Sun Audio (*.au)");
1371     QString ext_filter_raw = tr("Raw (*.raw)");
1372     ext_filter.append(ext_filter_au);
1373     if (direction != dir_both_) {
1374         ext_filter.append(";;");
1375         ext_filter.append(ext_filter_raw);
1376     }
1377     QString sel_filter;
1378     QString file_path = WiresharkFileDialog::getSaveFileName(
1379                 this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
1380                 ext_filter, &sel_filter);
1381
1382     if (file_path.isEmpty()) return;
1383
1384     int save_format = save_audio_none_;
1385     if (0 == QString::compare(sel_filter, ext_filter_au)) {
1386         save_format = save_audio_au_;
1387     } else if (0 == QString::compare(sel_filter, ext_filter_raw)) {
1388         save_format = save_audio_raw_;
1389     }
1390
1391     if (save_format == save_audio_none_) {
1392         QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
1393         return;
1394     }
1395
1396     QFile      save_file(file_path);
1397     gboolean   stop_flag = FALSE;
1398
1399     save_file.open(QIODevice::WriteOnly);
1400     fwd_tempfile_->seek(0);
1401     rev_tempfile_->seek(0);
1402
1403     if (save_file.error() != QFile::NoError) {
1404         QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
1405         return;
1406     }
1407
1408     ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
1409     ui->progressFrame->showProgress(true, true, &stop_flag);
1410
1411     if (save_format == save_audio_au_) { /* au format */
1412         if ((fwd_statinfo_.rtp_stats.clock_rate != 8000) ||
1413             ((rev_statinfo_.rtp_stats.clock_rate != 0) && (rev_statinfo_.rtp_stats.clock_rate != 8000))
1414            ) {
1415             QMessageBox::warning(this, tr("Warning"), tr("Can save audio with 8000 Hz clock rate only"));
1416         } else {
1417             if (! saveAudioAU(direction, &save_file, &stop_flag, sync)) {
1418                 goto copy_file_err;
1419             }
1420         }
1421     } else if (save_format == save_audio_raw_) { /* raw format */
1422         if (! saveAudioRAW(direction, &save_file, &stop_flag)) {
1423             goto copy_file_err;
1424         }
1425     }
1426
1427 copy_file_err:
1428     ui->progressFrame->hide();
1429     updateWidgets();
1430     return;
1431 }
1432
1433 // XXX The GTK+ UI saves the length and timestamp.
1434 void RtpAnalysisDialog::saveCsv(RtpAnalysisDialog::StreamDirection direction)
1435 {
1436     QString caption;
1437
1438     switch (direction) {
1439     case dir_forward_:
1440         caption = tr("Save forward stream CSV");
1441         break;
1442     case dir_reverse_:
1443         caption = tr("Save reverse stream CSV");
1444         break;
1445     case dir_both_:
1446     default:
1447         caption = tr("Save CSV");
1448         break;
1449     }
1450
1451     QString file_path = WiresharkFileDialog::getSaveFileName(
1452                 this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
1453                 tr("Comma-separated values (*.csv)"));
1454
1455     if (file_path.isEmpty()) return;
1456
1457     QFile save_file(file_path);
1458     save_file.open(QFile::WriteOnly);
1459
1460     if (direction == dir_forward_ || direction == dir_both_) {
1461         save_file.write("Forward\n");
1462
1463         for (int row = 0; row < ui->forwardTreeWidget->topLevelItemCount(); row++) {
1464             QTreeWidgetItem *ti = ui->forwardTreeWidget->topLevelItem(row);
1465             if (ti->type() != rtp_analysis_type_) continue;
1466             RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
1467             QStringList values;
1468             foreach (QVariant v, ra_ti->rowData()) {
1469                 if (!v.isValid()) {
1470                     values << "\"\"";
1471                 } else if ((int) v.type() == (int) QMetaType::QString) {
1472                     values << QString("\"%1\"").arg(v.toString());
1473                 } else {
1474                     values << v.toString();
1475                 }
1476             }
1477             save_file.write(values.join(",").toUtf8());
1478             save_file.write("\n");
1479         }
1480     }
1481     if (direction == dir_both_) {
1482         save_file.write("\n");
1483     }
1484     if (direction == dir_reverse_ || direction == dir_both_) {
1485         save_file.write("Reverse\n");
1486
1487         for (int row = 0; row < ui->reverseTreeWidget->topLevelItemCount(); row++) {
1488             QTreeWidgetItem *ti = ui->reverseTreeWidget->topLevelItem(row);
1489             if (ti->type() != rtp_analysis_type_) continue;
1490             RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
1491             QStringList values;
1492             foreach (QVariant v, ra_ti->rowData()) {
1493                 if (!v.isValid()) {
1494                     values << "\"\"";
1495                 } else if (v.type() == QVariant::String) {
1496                     values << QString("\"%1\"").arg(v.toString());
1497                 } else {
1498                     values << v.toString();
1499                 }
1500             }
1501             save_file.write(values.join(",").toUtf8());
1502             save_file.write("\n");
1503         }
1504     }
1505 }
1506
1507 bool RtpAnalysisDialog::eventFilter(QObject *, QEvent *event)
1508 {
1509     if (event->type() != QEvent::KeyPress) return false;
1510
1511     QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
1512
1513     switch(kevt->key()) {
1514     case Qt::Key_G:
1515         on_actionGoToPacket_triggered();
1516         return true;
1517     case Qt::Key_N:
1518         on_actionNextProblem_triggered();
1519         return true;
1520     default:
1521         break;
1522     }
1523     return false;
1524 }
1525
1526 void RtpAnalysisDialog::graphClicked(QMouseEvent *event)
1527 {
1528     updateWidgets();
1529     if (event->button() == Qt::RightButton) {
1530         graph_ctx_menu_.exec(event->globalPos());
1531     }
1532 }
1533
1534 void RtpAnalysisDialog::findStreams()
1535 {
1536     const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
1537     dfilter_t *sfcode;
1538     gchar *err_msg;
1539
1540     /* Try to get the hfid for "rtp.ssrc". */
1541     int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
1542     if (hfid_rtp_ssrc == -1) {
1543         err_str_ = tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
1544         updateWidgets();
1545         return;
1546     }
1547
1548     /* Try to compile the filter. */
1549     if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
1550         err_str_ = QString(err_msg);
1551         g_free(err_msg);
1552         updateWidgets();
1553         return;
1554     }
1555
1556     if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close();
1557
1558     frame_data *fdata = cap_file_.capFile()->current_frame;
1559
1560     if (!cf_read_record(cap_file_.capFile(), fdata)) close();
1561
1562     epan_dissect_t edt;
1563
1564     epan_dissect_init(&edt, cap_file_.capFile()->epan, TRUE, FALSE);
1565     epan_dissect_prime_with_dfilter(&edt, sfcode);
1566     epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
1567     epan_dissect_run(&edt, cap_file_.capFile()->cd_t, &cap_file_.capFile()->rec,
1568                      frame_tvbuff_new_buffer(&cap_file_.capFile()->provider, fdata, &cap_file_.capFile()->buf),
1569                      fdata, NULL);
1570
1571     /*
1572      * Packet must be an RTPv2 packet with an SSRC; we use the filter to
1573      * check.
1574      */
1575     if (!dfilter_apply_edt(sfcode, &edt)) {
1576         epan_dissect_cleanup(&edt);
1577         dfilter_free(sfcode);
1578         err_str_ = tr("Please select an RTPv2 packet with an SSRC value");
1579         updateWidgets();
1580         return;
1581     }
1582
1583     dfilter_free(sfcode);
1584
1585     /* OK, it is an RTP frame. Let's get the IP and port values */
1586     rtpstream_id_copy_pinfo(&(edt.pi),&(fwd_statinfo_.id),FALSE);
1587
1588     /* assume the inverse ip/port combination for the reverse direction */
1589     rtpstream_id_copy_pinfo(&(edt.pi),&(rev_statinfo_.id),TRUE);
1590
1591     /* now we need the SSRC value of the current frame */
1592     GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
1593     if (gp == NULL || gp->len == 0) {
1594         /* XXX - should not happen, as the filter includes rtp.ssrc */
1595         epan_dissect_cleanup(&edt);
1596         err_str_ = tr("SSRC value not found.");
1597         updateWidgets();
1598         return;
1599     }
1600     fwd_statinfo_.id.ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
1601
1602     epan_dissect_cleanup(&edt);
1603
1604     /* Register the tap listener */
1605     memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
1606     tapinfo_.tap_data = this;
1607     tapinfo_.mode = TAP_ANALYSE;
1608
1609 //    register_tap_listener_rtpstream(&tapinfo_, NULL);
1610     /* Scan for RTP streams (redissect all packets) */
1611     rtpstream_scan(&tapinfo_, cap_file_.capFile(), NULL);
1612
1613     for (GList *strinfo_list = g_list_first(tapinfo_.strinfo_list); strinfo_list; strinfo_list = g_list_next(strinfo_list)) {
1614         rtpstream_info_t * strinfo = (rtpstream_info_t*)(strinfo_list->data);
1615         if (rtpstream_id_equal(&(strinfo->id), &(fwd_statinfo_.id),RTPSTREAM_ID_EQUAL_NONE))
1616         {
1617             fwd_statinfo_.packet_count = strinfo->packet_count;
1618             fwd_statinfo_.setup_frame_number = strinfo->setup_frame_number;
1619             nstime_copy(&fwd_statinfo_.start_rel_time, &strinfo->start_rel_time);
1620             num_streams_++;
1621         }
1622
1623         if (rtpstream_id_equal(&(strinfo->id), &(rev_statinfo_.id),RTPSTREAM_ID_EQUAL_NONE))
1624         {
1625             rev_statinfo_.packet_count = strinfo->packet_count;
1626             rev_statinfo_.setup_frame_number = strinfo->setup_frame_number;
1627             nstime_copy(&rev_statinfo_.start_rel_time, &strinfo->start_rel_time);
1628             num_streams_++;
1629             if (rev_statinfo_.id.ssrc == 0) {
1630                 rev_statinfo_.id.ssrc = strinfo->id.ssrc;
1631             }
1632         }
1633     }
1634 }
1635
1636 void RtpAnalysisDialog::showStreamMenu(QPoint pos)
1637 {
1638     QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
1639     if (!cur_tree) return;
1640
1641     updateWidgets();
1642     stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1643 }
1644
1645 /*
1646  * Editor modelines
1647  *
1648  * Local Variables:
1649  * c-basic-offset: 4
1650  * tab-width: 8
1651  * indent-tabs-mode: nil
1652  * End:
1653  *
1654  * ex: set shiftwidth=4 tabstop=8 expandtab:
1655  * :indentSize=4:tabSize=8:noTabs=true:
1656  */