1 /* rtp_analysis_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "rtp_analysis_dialog.h"
11 #include <ui_rtp_analysis_dialog.h>
14 #include "frame_tvbuff.h"
16 #include "epan/epan_dissect.h"
17 #include "epan/rtp_pt.h"
19 #include "epan/dfilter/dfilter.h"
21 #include "epan/dissectors/packet-rtp.h"
23 #include "ui/help_url.h"
24 #include <wsutil/utf8_entities.h>
26 #include <wsutil/g711.h>
27 #include <wsutil/pint.h>
29 #include <QMessageBox>
30 #include <QPushButton>
31 #include <QTemporaryFile>
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"
41 * @file RTP stream analysis dialog
43 * Displays forward and reverse RTP streams and graphs each stream
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?
63 static const QRgb color_cn_ = 0xbfbfff;
64 static const QRgb color_rtp_warn_ = 0xffdbbf;
65 static const QRgb color_pt_event_ = 0xefffff;
67 enum { rtp_analysis_type_ = 1000 };
68 class RtpAnalysisTreeWidgetItem : public QTreeWidgetItem
71 RtpAnalysisTreeWidgetItem(QTreeWidget *tree, tap_rtp_stat_t *statinfo, packet_info *pinfo, const struct _rtp_info *rtpinfo) :
72 QTreeWidgetItem(tree, rtp_analysis_type_)
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) {
83 delta_ = statinfo->delta;
84 jitter_ = statinfo->jitter;
85 skew_ = statinfo->skew;
87 bandwidth_ = statinfo->bandwidth;
88 marker_ = rtpinfo->info_marker_set ? true : false;
91 QColor bg_color = QColor();
94 if (statinfo->pt == PT_CN) {
95 status = "Comfort noise (PT=13, RFC 3389)";
97 } else if (statinfo->pt == PT_CN_OLD) {
98 status = "Comfort noise (PT=19, reserved)";
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");
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);
126 bg_color = color_pt_event_;
128 if (statinfo->flags & STAT_FLAG_MARKER) {
129 bg_color = color_rtp_warn_;
133 if (status.isEmpty()) {
135 status = UTF8_CHECK_MARK;
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));
145 setText(marker_col_, UTF8_BULLET);
147 setText(status_col_, status);
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);
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);
165 guint32 frameNum() { return frame_num_; }
166 bool frameStatus() { return ok_; }
168 QList<QVariant> rowData() {
170 QString status_str = ok_ ? "OK" : text(status_col_);
172 if (marker_) marker_str = "SET";
174 return QList<QVariant>()
175 << frame_num_ << sequence_num_ << delta_ << jitter_ << skew_ << bandwidth_
176 << marker_str << status_str;
179 bool operator< (const QTreeWidgetItem &other) const
181 if (other.type() != rtp_analysis_type_) return QTreeWidgetItem::operator< (other);
182 const RtpAnalysisTreeWidgetItem *other_row = static_cast<const RtpAnalysisTreeWidgetItem *>(&other);
184 switch (treeWidget()->sortColumn()) {
186 return frame_num_ < other_row->frame_num_;
188 case (sequence_col_):
189 return sequence_num_ < other_row->sequence_num_;
192 return delta_ < other_row->delta_;
195 return jitter_ < other_row->jitter_;
198 return skew_ < other_row->skew_;
200 case (bandwidth_col_):
201 return bandwidth_ < other_row->bandwidth_;
207 // Fall back to string comparison
208 return QTreeWidgetItem::operator <(other);
212 guint32 sequence_num_;
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),
237 save_payload_error_(TAP_RTP_NO_ERROR)
240 loadGeometry(parent.width() * 4 / 5, parent.height() * 4 / 5);
241 setWindowSubtitle(tr("RTP Stream Analysis"));
243 ui->progressFrame->hide();
245 player_button_ = RtpPlayerDialog::addPlayerButton(ui->buttonBox);
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*)));
280 graph_ctx_menu_.addAction(ui->actionSaveGraph);
282 QStringList header_labels;
283 for (int i = 0; i < ui->forwardTreeWidget->columnCount(); i++) {
284 header_labels << ui->forwardTreeWidget->headerItem()->text(i);
286 ui->reverseTreeWidget->setHeaderLabels(header_labels);
288 memset(&fwd_statinfo_, 0, sizeof(fwd_statinfo_));
289 memset(&rev_statinfo_, 0, sizeof(rev_statinfo_));
291 QList<QCheckBox *> graph_cbs = QList<QCheckBox *>()
292 << ui->fJitterCheckBox << ui->fDiffCheckBox << ui->fDeltaCheckBox
293 << ui->rJitterCheckBox << ui->rDiffCheckBox << ui->rDeltaCheckBox;
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());
300 graph_cbs[i]->setChecked(true);
301 graph_cbs[i]->setIcon(StockIcon::colorIcon(ColorUtils::graphColor(i), QPalette::Text));
303 ui->streamGraph->xAxis->setLabel("Arrival Time");
304 ui->streamGraph->yAxis->setLabel("Value (ms)");
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();
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);
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);
348 if (stream_fwd) { // XXX What if stream_fwd == 0 && stream_rev != 0?
349 rtpstream_info_copy_deep(&fwd_statinfo_, stream_fwd);
352 rtpstream_info_copy_deep(&rev_statinfo_, stream_rev);
359 if (err_str_.isEmpty() && num_streams_ < 1) {
360 err_str_ = tr("No streams found.");
363 registerTapListener("rtp", this, NULL, 0, tapReset, tapPacket, tapDraw);
364 cap_file_.retapPackets();
365 removeTapListeners();
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()));
380 RtpAnalysisDialog::~RtpAnalysisDialog()
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_;
390 void RtpAnalysisDialog::captureFileClosing()
393 WiresharkDialog::captureFileClosing();
396 void RtpAnalysisDialog::updateWidgets()
398 bool enable_tab = false;
399 QString hint = err_str_;
401 if (hint.isEmpty()) {
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
410 bool enable_nav = false;
412 && ((ui->tabWidget->currentWidget() == ui->forwardTreeWidget
413 && ui->forwardTreeWidget->selectedItems().length() > 0)
414 || (ui->tabWidget->currentWidget() == ui->reverseTreeWidget
415 && ui->reverseTreeWidget->selectedItems().length() > 0))) {
418 ui->actionGoToPacket->setEnabled(enable_nav);
419 ui->actionNextProblem->setEnabled(enable_nav);
422 hint.append(tr(" G: Go to packet, N: Next problem packet"));
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);
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);
443 #if defined(QT_MULTIMEDIA_LIB)
444 player_button_->setEnabled(num_streams_ > 0);
446 player_button_->setEnabled(false);
447 player_button_->setText(tr("No Audio"));
450 ui->tabWidget->setEnabled(enable_tab);
451 hint.prepend("<small><i>");
452 hint.append("</i></small>");
453 ui->hintLabel->setText(hint);
455 WiresharkDialog::updateWidgets();
458 void RtpAnalysisDialog::on_actionGoToPacket_triggered()
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;
464 QTreeWidgetItem *ti = cur_tree->selectedItems()[0];
465 if (ti->type() != rtp_analysis_type_) return;
467 RtpAnalysisTreeWidgetItem *ra_ti = dynamic_cast<RtpAnalysisTreeWidgetItem *>((RtpAnalysisTreeWidgetItem *)ti);
468 emit goToPacket(ra_ti->frameNum());
471 void RtpAnalysisDialog::on_actionNextProblem_triggered()
473 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
474 if (!cur_tree || cur_tree->topLevelItemCount() < 2) return;
476 // Choose convenience over correctness.
477 if (cur_tree->selectedItems().length() < 1) {
478 cur_tree->setCurrentItem(cur_tree->topLevelItem(0));
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);
492 test_ti = cur_tree->itemBelow(test_ti);
493 if (!test_ti) test_ti = cur_tree->topLevelItem(0);
497 void RtpAnalysisDialog::on_fJitterCheckBox_toggled(bool checked)
499 ui->streamGraph->graph(fwd_jitter_graph_)->setVisible(checked);
503 void RtpAnalysisDialog::on_fDiffCheckBox_toggled(bool checked)
505 ui->streamGraph->graph(fwd_diff_graph_)->setVisible(checked);
509 void RtpAnalysisDialog::on_fDeltaCheckBox_toggled(bool checked)
511 ui->streamGraph->graph(fwd_delta_graph_)->setVisible(checked);
515 void RtpAnalysisDialog::on_rJitterCheckBox_toggled(bool checked)
517 ui->streamGraph->graph(rev_jitter_graph_)->setVisible(checked);
521 void RtpAnalysisDialog::on_rDiffCheckBox_toggled(bool checked)
523 ui->streamGraph->graph(rev_diff_graph_)->setVisible(checked);
527 void RtpAnalysisDialog::on_rDeltaCheckBox_toggled(bool checked)
529 ui->streamGraph->graph(rev_delta_graph_)->setVisible(checked);
533 void RtpAnalysisDialog::on_actionSaveAudioUnsync_triggered()
535 saveAudio(dir_both_, sync_unsync_);
538 void RtpAnalysisDialog::on_actionSaveForwardAudioUnsync_triggered()
540 saveAudio(dir_forward_, sync_unsync_);
543 void RtpAnalysisDialog::on_actionSaveReverseAudioUnsync_triggered()
545 saveAudio(dir_reverse_, sync_unsync_);
548 void RtpAnalysisDialog::on_actionSaveAudioSyncStream_triggered()
550 saveAudio(dir_both_, sync_sync_stream_);
553 void RtpAnalysisDialog::on_actionSaveForwardAudioSyncStream_triggered()
555 saveAudio(dir_forward_, sync_sync_stream_);
558 void RtpAnalysisDialog::on_actionSaveReverseAudioSyncStream_triggered()
560 saveAudio(dir_reverse_, sync_sync_stream_);
563 void RtpAnalysisDialog::on_actionSaveAudioSyncFile_triggered()
565 saveAudio(dir_both_, sync_sync_file_);
568 void RtpAnalysisDialog::on_actionSaveForwardAudioSyncFile_triggered()
570 saveAudio(dir_forward_, sync_sync_file_);
573 void RtpAnalysisDialog::on_actionSaveReverseAudioSyncFile_triggered()
575 saveAudio(dir_reverse_, sync_sync_file_);
578 void RtpAnalysisDialog::on_actionSaveCsv_triggered()
583 void RtpAnalysisDialog::on_actionSaveForwardCsv_triggered()
585 saveCsv(dir_forward_);
588 void RtpAnalysisDialog::on_actionSaveReverseCsv_triggered()
590 saveCsv(dir_reverse_);
593 void RtpAnalysisDialog::on_actionSaveGraph_triggered()
595 ui->tabWidget->setCurrentWidget(ui->graphTab);
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")
610 QString save_file = path.canonicalPath();
612 save_file += QString("/%1").arg(cap_file_.fileBaseName());
614 file_name = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(tr("Save Graph As" UTF8_HORIZONTAL_ELLIPSIS)),
615 save_file, filter, &extension);
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);
630 // ui->streamGraph->legend->setVisible(false);
631 // else error dialog?
633 path = QDir(file_name);
634 wsApp->setLastOpenDir(path.canonicalPath().toUtf8().constData());
639 void RtpAnalysisDialog::on_buttonBox_clicked(QAbstractButton *button)
641 if (button == player_button_) {
646 void RtpAnalysisDialog::on_buttonBox_helpRequested()
648 wsApp->helpTopicAction(HELP_RTP_ANALYSIS_DIALOG);
651 void RtpAnalysisDialog::tapReset(void *tapinfo_ptr)
653 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
654 if (!rtp_analysis_dialog) return;
656 rtp_analysis_dialog->resetStatistics();
659 gboolean RtpAnalysisDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr)
661 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
662 if (!rtp_analysis_dialog) return FALSE;
664 const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
665 if (!rtpinfo) return FALSE;
667 /* we ignore packets that are not displayed */
668 if (pinfo->fd->flags.passed_dfilter == 0)
670 /* also ignore RTP Version != 2 */
671 else if (rtpinfo->info_version != 2)
673 /* is it the forward direction? */
674 else if (rtpstream_id_equal_pinfo_rtp_info(&(rtp_analysis_dialog->fwd_statinfo_.id),pinfo,rtpinfo)) {
676 rtp_analysis_dialog->addPacket(true, pinfo, rtpinfo);
678 /* is it the reversed direction? */
679 else if (rtpstream_id_equal_pinfo_rtp_info(&(rtp_analysis_dialog->rev_statinfo_.id),pinfo,rtpinfo)) {
681 rtp_analysis_dialog->addPacket(false, pinfo, rtpinfo);
686 void RtpAnalysisDialog::tapDraw(void *tapinfo_ptr)
688 RtpAnalysisDialog *rtp_analysis_dialog = dynamic_cast<RtpAnalysisDialog *>((RtpAnalysisDialog*)tapinfo_ptr);
689 if (!rtp_analysis_dialog) return;
690 rtp_analysis_dialog->updateStatistics();
693 void RtpAnalysisDialog::resetStatistics()
695 memset(&fwd_statinfo_.rtp_stats, 0, sizeof(fwd_statinfo_.rtp_stats));
696 memset(&rev_statinfo_.rtp_stats, 0, sizeof(rev_statinfo_.rtp_stats));
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;
703 ui->forwardTreeWidget->clear();
704 ui->reverseTreeWidget->clear();
706 for (int i = 0; i < ui->streamGraph->graphCount(); i++) {
707 ui->streamGraph->graph(i)->clearData();
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();
719 fwd_tempfile_->resize(0);
720 rev_tempfile_->resize(0);
723 void RtpAnalysisDialog::addPacket(bool forward, packet_info *pinfo, const _rtp_info *rtpinfo)
725 /* add this RTP for future listening using the RTP Player*/
726 // add_rtp_packet(rtpinfo, pinfo);
729 rtppacket_analyse(&fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
730 new RtpAnalysisTreeWidgetItem(ui->forwardTreeWidget, &fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
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);
737 savePayload(fwd_tempfile_, &fwd_statinfo_.rtp_stats, pinfo, rtpinfo);
739 rtppacket_analyse(&rev_statinfo_.rtp_stats, pinfo, rtpinfo);
740 new RtpAnalysisTreeWidgetItem(ui->reverseTreeWidget, &rev_statinfo_.rtp_stats, pinfo, rtpinfo);
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);
747 savePayload(rev_tempfile_, &rev_statinfo_.rtp_stats, pinfo, rtpinfo);
752 void RtpAnalysisDialog::savePayload(QTemporaryFile *tmpfile, tap_rtp_stat_t *statinfo, packet_info *pinfo, const _rtp_info *rtpinfo)
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;
760 // saveinfo->saved = TRUE;
764 /* Save the voice information */
766 /* If there was already an error, we quit */
767 if (!tmpfile->isOpen() || tmpfile->error() != QFile::NoError)
770 /* Quit if the captured length and packet length aren't equal or
771 * if the RTP dissector thinks there is some information missing
773 if ((pinfo->fd->pkt_len != pinfo->fd->cap_len) &&
774 (!rtpinfo->info_all_data_present))
777 err_str_ = tr("Can't save in a file: Wrong length of captured packets.");
778 save_payload_error_ = TAP_RTP_WRONG_LENGTH;
782 /* If padding bit is set but the padding count is bigger
783 * then the whole RTP data - error with padding count
785 if ((rtpinfo->info_padding_set != FALSE) &&
786 (rtpinfo->info_padding_count > rtpinfo->info_payload_len))
789 err_str_ = tr("Can't save in a file: RTP data with padding.");
790 save_payload_error_ = TAP_RTP_PADDING_ERROR;
794 if ((rtpinfo->info_payload_type == PT_CN) ||
795 (rtpinfo->info_payload_type == PT_CN_OLD)) {
796 } else { /* All other payloads */
799 tap_rtp_save_data_t save_data;
801 if (!rtpinfo->info_all_data_present) {
802 /* Not all the data was captured. */
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;
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
813 data = (const char *) rtpinfo->info_data + rtpinfo->info_payload_offset;
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;
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;
842 void RtpAnalysisDialog::updateStatistics()
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;
868 if (f_clock_rate == 0) {
872 if (r_clock_rate == 0) {
877 f_perc = (double)(f_lost*100)/(double)f_expected;
882 r_perc = (double)(r_lost*100)/(double)r_expected;
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);
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);
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>")
916 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</td></tr>")
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";
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>")
948 stats_tables += QString("<tr><th align=\"left\">Expected</th><td>%1</td></tr>")
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);
969 stats_tables += "</body></html>\n";
971 ui->statisticsLabel->setText(stats_tables);
973 for (int col = 0; col < ui->forwardTreeWidget->columnCount() - 1; col++) {
974 ui->forwardTreeWidget->resizeColumnToContents(col);
975 ui->reverseTreeWidget->resizeColumnToContents(col);
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_);
990 void RtpAnalysisDialog::updateGraph()
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);
997 ui->streamGraph->replot();
1000 void RtpAnalysisDialog::showPlayer()
1002 #ifdef QT_MULTIMEDIA_LIB
1003 if (num_streams_ < 1) return;
1005 RtpPlayerDialog rtp_player_dialog(*this, cap_file_);
1006 rtpstream_info_t stream_info;
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);
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);
1026 connect(&rtp_player_dialog, SIGNAL(goToPacket(int)), this, SIGNAL(goToPacket(int)));
1028 rtp_player_dialog.exec();
1029 #endif // QT_MULTIMEDIA_LIB
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)
1036 size_t sample_count;
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];
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];
1062 /* Read payload, but ignore it */
1064 for(size_t i = 0; i < payload_len; i++) {
1065 tempfile->read((char *)&f_rawvalue, sizeof(f_rawvalue));
1069 return sample_count;
1072 gboolean RtpAnalysisDialog::saveAudioAUSilence(size_t total_len, QFile *save_file, gboolean *stop_flag)
1075 guint8 pd_out[2*4000];
1080 phton16(pd, silence);
1083 /* Fill whole file with silence */
1084 for(size_t i=0; i<total_len; i++) {
1088 nchars = save_file->write((const char *)pd_out, 2);
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)
1100 guint8 pd_out[2*4000];
1102 tap_rtp_save_data_t save_data;
1104 while (sizeof(save_data) == tempfile->read((char *)&save_data,sizeof(save_data))) {
1105 size_t sample_count;
1110 ui->progressFrame->setValue(int(tempfile->pos() * 100 / tempfile->size()));
1112 sample_count=convert_payload_to_samples(save_data.payload_type, tempfile ,pd_out, save_data.payload_len);
1114 if (sample_count > 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 ];
1121 save_file->seek(header_end+(prefix_silence + (guint32_wraparound_diff(save_data.timestamp, statinfo.first_timestamp) + i)) * 4);
1123 save_file->seek(header_end+(prefix_silence + (guint32_wraparound_diff(save_data.timestamp, statinfo.first_timestamp) + i)) * 2);
1125 nchars += save_file->write((const char *)pd, 2);
1127 if (nchars < sample_count*2) {
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)
1138 if (! saveAudioAUUnidir(fwd_statinfo, fwd_tempfile, save_file, header_end, stop_flag, TRUE, prefix_silence_fwd))
1142 if (! saveAudioAUUnidir(rev_statinfo, rev_tempfile, save_file, header_end+2, stop_flag, TRUE, prefix_silence_rev))
1150 gboolean RtpAnalysisDialog::saveAudioAU(StreamDirection direction, QFile *save_file, gboolean *stop_flag, RtpAnalysisDialog::SyncType sync)
1155 size_t fwd_total_len;
1156 size_t rev_total_len;
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
1165 /* the magic word 0x2e736e64 == .snd */
1166 phton32(pd, 0x2e736e64);
1167 nchars = save_file->write((const char *)pd, 4);
1170 /* header offset == 24 bytes */
1172 nchars = save_file->write((const char *)pd, 4);
1175 /* total length; it is permitted to set this to 0xffffffff */
1176 phton32(pd, 0xffffffff);
1177 nchars = save_file->write((const char *)pd, 4);
1180 /* encoding format == 16-bit linear PCM */
1182 nchars = save_file->write((const char *)pd, 4);
1185 /* sample rate == 8000 Hz */
1187 nchars = save_file->write((const char *)pd, 4);
1190 /* channels == 1 or == 2 */
1191 switch (direction) {
1192 case dir_forward_: {
1196 case dir_reverse_: {
1205 nchars = save_file->write((const char *)pd, 4);
1209 header_end=save_file->pos();
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;
1220 case sync_unsync_: {
1221 fwd_samples_diff = 0;
1222 rev_samples_diff = 0;
1223 bidir_samples_diff = 0;
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"));
1234 fwd_samples_diff = t_fwd_diff*8000/1000;
1235 rev_samples_diff = t_rev_diff*8000/1000;
1236 bidir_samples_diff = 0;
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;
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;
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))
1264 if (! saveAudioAUUnidir(fwd_statinfo_.rtp_stats, fwd_tempfile_, save_file, header_end, stop_flag, FALSE, fwd_samples_diff + bidir_samples_diff))
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))
1277 if (! saveAudioAUUnidir(rev_statinfo_.rtp_stats, rev_tempfile_, save_file, header_end, stop_flag, FALSE, rev_samples_diff + bidir_samples_diff))
1283 /* Both directions */
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))
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))
1302 gboolean RtpAnalysisDialog::saveAudioRAW(StreamDirection direction, QFile *save_file, gboolean *stop_flag)
1305 tap_rtp_save_data_t save_data;
1307 switch (direction) {
1308 /* Only forward direction */
1309 case dir_forward_: {
1310 tempfile = fwd_tempfile_;
1313 /* Only reversed direction */
1314 case dir_reverse_: {
1315 tempfile = rev_tempfile_;
1323 /* Copy just payload */
1324 while (sizeof(save_data) == tempfile->read((char *)&save_data,sizeof(save_data))) {
1331 ui->progressFrame->setValue(int(tempfile->pos() * 100 / fwd_tempfile_->size()));
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))) {
1338 if (sizeof(f_rawvalue) != save_file->write((char *)&f_rawvalue, sizeof(f_rawvalue))) {
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)
1352 if (!fwd_tempfile_->isOpen() || !rev_tempfile_->isOpen()) return;
1356 switch (direction) {
1358 caption = tr("Save forward stream audio");
1361 caption = tr("Save reverse stream audio");
1365 caption = tr("Save forward and reverse stream audio");
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);
1378 QString file_path = WiresharkFileDialog::getSaveFileName(
1379 this, caption, wsApp->lastOpenDir().absoluteFilePath("Saved RTP Audio.au"),
1380 ext_filter, &sel_filter);
1382 if (file_path.isEmpty()) return;
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_;
1391 if (save_format == save_audio_none_) {
1392 QMessageBox::warning(this, tr("Warning"), tr("Unable to save in that format"));
1396 QFile save_file(file_path);
1397 gboolean stop_flag = FALSE;
1399 save_file.open(QIODevice::WriteOnly);
1400 fwd_tempfile_->seek(0);
1401 rev_tempfile_->seek(0);
1403 if (save_file.error() != QFile::NoError) {
1404 QMessageBox::warning(this, tr("Warning"), tr("Unable to save %1").arg(save_file.fileName()));
1408 ui->hintLabel->setText(tr("Saving %1" UTF8_HORIZONTAL_ELLIPSIS).arg(save_file.fileName()));
1409 ui->progressFrame->showProgress(true, true, &stop_flag);
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))
1415 QMessageBox::warning(this, tr("Warning"), tr("Can save audio with 8000 Hz clock rate only"));
1417 if (! saveAudioAU(direction, &save_file, &stop_flag, sync)) {
1421 } else if (save_format == save_audio_raw_) { /* raw format */
1422 if (! saveAudioRAW(direction, &save_file, &stop_flag)) {
1428 ui->progressFrame->hide();
1433 // XXX The GTK+ UI saves the length and timestamp.
1434 void RtpAnalysisDialog::saveCsv(RtpAnalysisDialog::StreamDirection direction)
1438 switch (direction) {
1440 caption = tr("Save forward stream CSV");
1443 caption = tr("Save reverse stream CSV");
1447 caption = tr("Save CSV");
1451 QString file_path = WiresharkFileDialog::getSaveFileName(
1452 this, caption, wsApp->lastOpenDir().absoluteFilePath("RTP Packet Data.csv"),
1453 tr("Comma-separated values (*.csv)"));
1455 if (file_path.isEmpty()) return;
1457 QFile save_file(file_path);
1458 save_file.open(QFile::WriteOnly);
1460 if (direction == dir_forward_ || direction == dir_both_) {
1461 save_file.write("Forward\n");
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);
1468 foreach (QVariant v, ra_ti->rowData()) {
1471 } else if ((int) v.type() == (int) QMetaType::QString) {
1472 values << QString("\"%1\"").arg(v.toString());
1474 values << v.toString();
1477 save_file.write(values.join(",").toUtf8());
1478 save_file.write("\n");
1481 if (direction == dir_both_) {
1482 save_file.write("\n");
1484 if (direction == dir_reverse_ || direction == dir_both_) {
1485 save_file.write("Reverse\n");
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);
1492 foreach (QVariant v, ra_ti->rowData()) {
1495 } else if (v.type() == QVariant::String) {
1496 values << QString("\"%1\"").arg(v.toString());
1498 values << v.toString();
1501 save_file.write(values.join(",").toUtf8());
1502 save_file.write("\n");
1507 bool RtpAnalysisDialog::eventFilter(QObject *, QEvent *event)
1509 if (event->type() != QEvent::KeyPress) return false;
1511 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
1513 switch(kevt->key()) {
1515 on_actionGoToPacket_triggered();
1518 on_actionNextProblem_triggered();
1526 void RtpAnalysisDialog::graphClicked(QMouseEvent *event)
1529 if (event->button() == Qt::RightButton) {
1530 graph_ctx_menu_.exec(event->globalPos());
1534 void RtpAnalysisDialog::findStreams()
1536 const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
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.");
1548 /* Try to compile the filter. */
1549 if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
1550 err_str_ = QString(err_msg);
1556 if (!cap_file_.capFile() || !cap_file_.capFile()->current_frame) close();
1558 frame_data *fdata = cap_file_.capFile()->current_frame;
1560 if (!cf_read_record(cap_file_.capFile(), fdata)) close();
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),
1572 * Packet must be an RTPv2 packet with an SSRC; we use the filter to
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");
1583 dfilter_free(sfcode);
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);
1588 /* assume the inverse ip/port combination for the reverse direction */
1589 rtpstream_id_copy_pinfo(&(edt.pi),&(rev_statinfo_.id),TRUE);
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.");
1600 fwd_statinfo_.id.ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
1602 epan_dissect_cleanup(&edt);
1604 /* Register the tap listener */
1605 memset(&tapinfo_, 0, sizeof(rtpstream_tapinfo_t));
1606 tapinfo_.tap_data = this;
1607 tapinfo_.mode = TAP_ANALYSE;
1609 // register_tap_listener_rtpstream(&tapinfo_, NULL);
1610 /* Scan for RTP streams (redissect all packets) */
1611 rtpstream_scan(&tapinfo_, cap_file_.capFile(), NULL);
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))
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);
1623 if (rtpstream_id_equal(&(strinfo->id), &(rev_statinfo_.id),RTPSTREAM_ID_EQUAL_NONE))
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);
1629 if (rev_statinfo_.id.ssrc == 0) {
1630 rev_statinfo_.id.ssrc = strinfo->id.ssrc;
1636 void RtpAnalysisDialog::showStreamMenu(QPoint pos)
1638 QTreeWidget *cur_tree = qobject_cast<QTreeWidget *>(ui->tabWidget->currentWidget());
1639 if (!cur_tree) return;
1642 stream_ctx_menu_.popup(cur_tree->viewport()->mapToGlobal(pos));
1651 * indent-tabs-mode: nil
1654 * ex: set shiftwidth=4 tabstop=8 expandtab:
1655 * :indentSize=4:tabSize=8:noTabs=true: