1 /* rtp_stream_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_stream_dialog.h"
11 #include <ui_rtp_stream_dialog.h>
15 #include "epan/addr_resolv.h"
16 #include <epan/rtp_pt.h>
18 #include <wsutil/utf8_entities.h>
20 #include <ui/qt/utils/qt_ui_utils.h>
21 #include "rtp_analysis_dialog.h"
22 #include "progress_frame.h"
23 #include "main_application.h"
24 #include "ui/qt/widgets/wireshark_file_dialog.h"
29 #include <QPushButton>
30 #include <QTextStream>
31 #include <QTreeWidgetItem>
32 #include <QTreeWidgetItemIterator>
35 #include <ui/qt/utils/color_utils.h>
38 * @file RTP stream dialog
40 * Displays a list of RTP streams with the following information:
44 * - Stats: Packets, lost, max delta, max jitter, mean jitter
47 * Finds reverse streams
50 * Go to the setup frame
52 * Copy As CSV and YAML
57 // - Add more statistics to the hint text (e.g. lost packets).
58 // - Add more statistics to the main list (e.g. stream duration)
60 const int src_addr_col_
= 0;
61 const int src_port_col_
= 1;
62 const int dst_addr_col_
= 2;
63 const int dst_port_col_
= 3;
64 const int ssrc_col_
= 4;
65 const int start_time_col_
= 5;
66 const int duration_col_
= 6;
67 const int payload_col_
= 7;
68 const int packets_col_
= 8;
69 const int lost_col_
= 9;
70 const int min_delta_col_
= 10;
71 const int mean_delta_col_
= 11;
72 const int max_delta_col_
= 12;
73 const int min_jitter_col_
= 13;
74 const int mean_jitter_col_
= 14;
75 const int max_jitter_col_
= 15;
76 const int status_col_
= 16;
77 const int ssrc_fmt_col_
= 17;
78 const int lost_perc_col_
= 18;
80 enum { rtp_stream_type_
= 1000 };
82 bool operator==(rtpstream_id_t
const& a
, rtpstream_id_t
const& b
);
84 class RtpStreamTreeWidgetItem
: public QTreeWidgetItem
87 RtpStreamTreeWidgetItem(QTreeWidget
*tree
, rtpstream_info_t
*stream_info
) :
88 QTreeWidgetItem(tree
, rtp_stream_type_
),
89 stream_info_(stream_info
),
95 rtpstream_info_t
*streamInfo() const { return stream_info_
; }
98 rtpstream_info_calc_t calc
;
103 rtpstream_info_calculate(stream_info_
, &calc
);
105 setText(src_addr_col_
, calc
.src_addr_str
);
106 setText(src_port_col_
, QString::number(calc
.src_port
));
107 setText(dst_addr_col_
, calc
.dst_addr_str
);
108 setText(dst_port_col_
, QString::number(calc
.dst_port
));
109 setText(ssrc_col_
, QStringLiteral("0x%1").arg(calc
.ssrc
, 0, 16));
111 QDateTime abs_dt
= QDateTime::fromMSecsSinceEpoch(nstime_to_msec(&stream_info_
->start_fd
->abs_ts
));
112 setText(start_time_col_
, QStringLiteral("%1")
113 .arg(abs_dt
.toString("yyyy-MM-dd hh:mm:ss.zzz")));
115 setText(start_time_col_
, QString::number(calc
.start_time_ms
, 'f', 6));
117 setText(duration_col_
, QString::number(calc
.duration_ms
, 'f', prefs
.gui_decimal_places1
));
118 setText(payload_col_
, calc
.all_payload_type_names
);
119 setText(packets_col_
, QString::number(calc
.packet_count
));
120 setText(lost_col_
, QObject::tr("%1 (%L2%)").arg(calc
.lost_num
).arg(QString::number(calc
.lost_perc
, 'f', 1)));
121 setText(min_delta_col_
, QString::number(calc
.min_delta
, 'f', prefs
.gui_decimal_places3
)); // This is RTP. Do we need nanoseconds?
122 setText(mean_delta_col_
, QString::number(calc
.mean_delta
, 'f', prefs
.gui_decimal_places3
)); // This is RTP. Do we need nanoseconds?
123 setText(max_delta_col_
, QString::number(calc
.max_delta
, 'f', prefs
.gui_decimal_places3
)); // This is RTP. Do we need nanoseconds?
124 setText(min_jitter_col_
, QString::number(calc
.min_jitter
, 'f', prefs
.gui_decimal_places3
));
125 setText(mean_jitter_col_
, QString::number(calc
.mean_jitter
, 'f', prefs
.gui_decimal_places3
));
126 setText(max_jitter_col_
, QString::number(calc
.max_jitter
, 'f', prefs
.gui_decimal_places3
));
129 setText(status_col_
, UTF8_BULLET
);
130 setTextAlignment(status_col_
, Qt::AlignCenter
);
131 QColor
bgColor(ColorUtils::warningBackground());
132 QColor
textColor(QApplication::palette().text().color());
133 for (int i
= 0; i
< columnCount(); i
++) {
134 QBrush bgBrush
= background(i
);
135 bgBrush
.setColor(bgColor
);
136 bgBrush
.setStyle(Qt::SolidPattern
);
137 setBackground(i
, bgBrush
);
138 QBrush fgBrush
= foreground(i
);
139 fgBrush
.setColor(textColor
);
140 fgBrush
.setStyle(Qt::SolidPattern
);
141 setForeground(i
, fgBrush
);
145 rtpstream_info_calc_free(&calc
);
147 // Return a QString, int, double, or invalid QVariant representing the raw column data.
148 QVariant
colData(int col
) const {
149 rtpstream_info_calc_t calc
;
155 rtpstream_info_calculate(stream_info_
, &calc
);
159 ret
= QVariant(text(col
));
173 case start_time_col_
:
174 ret
= calc
.start_time_ms
;
177 ret
= calc
.duration_ms
;
183 ret
= calc
.packet_count
;
189 ret
= calc
.min_delta
;
191 case mean_delta_col_
:
192 ret
= calc
.mean_delta
;
195 ret
= calc
.max_delta
;
197 case min_jitter_col_
:
198 ret
= calc
.min_jitter
;
200 case mean_jitter_col_
:
201 ret
= calc
.mean_jitter
;
203 case max_jitter_col_
:
204 ret
= calc
.max_jitter
;
207 ret
= calc
.problem
? "Problem" : "";
210 ret
= QStringLiteral("0x%1").arg(calc
.ssrc
, 0, 16);
213 ret
= QString::number(calc
.lost_perc
, 'f', prefs
.gui_decimal_places1
);
219 rtpstream_info_calc_free(&calc
);
223 bool operator< (const QTreeWidgetItem
&other
) const
225 rtpstream_info_calc_t calc1
;
226 rtpstream_info_calc_t calc2
;
229 if (other
.type() != rtp_stream_type_
) return QTreeWidgetItem::operator <(other
);
230 const RtpStreamTreeWidgetItem
&other_rstwi
= dynamic_cast<const RtpStreamTreeWidgetItem
&>(other
);
232 switch (treeWidget()->sortColumn()) {
234 return cmp_address(&(stream_info_
->id
.src_addr
), &(other_rstwi
.stream_info_
->id
.src_addr
)) < 0;
236 return stream_info_
->id
.src_port
< other_rstwi
.stream_info_
->id
.src_port
;
238 return cmp_address(&(stream_info_
->id
.dst_addr
), &(other_rstwi
.stream_info_
->id
.dst_addr
)) < 0;
240 return stream_info_
->id
.dst_port
< other_rstwi
.stream_info_
->id
.dst_port
;
242 return stream_info_
->id
.ssrc
< other_rstwi
.stream_info_
->id
.ssrc
;
243 case start_time_col_
:
244 rtpstream_info_calculate(stream_info_
, &calc1
);
245 rtpstream_info_calculate(other_rstwi
.stream_info_
, &calc2
);
246 ret
= calc1
.start_time_ms
< calc2
.start_time_ms
;
247 rtpstream_info_calc_free(&calc1
);
248 rtpstream_info_calc_free(&calc2
);
251 rtpstream_info_calculate(stream_info_
, &calc1
);
252 rtpstream_info_calculate(other_rstwi
.stream_info_
, &calc2
);
253 ret
= calc1
.duration_ms
< calc2
.duration_ms
;
254 rtpstream_info_calc_free(&calc1
);
255 rtpstream_info_calc_free(&calc2
);
258 return g_strcmp0(stream_info_
->all_payload_type_names
, other_rstwi
.stream_info_
->all_payload_type_names
);
260 return stream_info_
->packet_count
< other_rstwi
.stream_info_
->packet_count
;
262 rtpstream_info_calculate(stream_info_
, &calc1
);
263 rtpstream_info_calculate(other_rstwi
.stream_info_
, &calc2
);
264 /* XXX: Should this sort on the total number or the percentage?
265 * lost_num is displayed first and lost_perc in parenthesis,
266 * so let's use the total number.
268 ret
= calc1
.lost_num
< calc2
.lost_num
;
269 rtpstream_info_calc_free(&calc1
);
270 rtpstream_info_calc_free(&calc2
);
273 return stream_info_
->rtp_stats
.min_delta
< other_rstwi
.stream_info_
->rtp_stats
.min_delta
;
274 case mean_delta_col_
:
275 return stream_info_
->rtp_stats
.mean_delta
< other_rstwi
.stream_info_
->rtp_stats
.mean_delta
;
277 return stream_info_
->rtp_stats
.max_delta
< other_rstwi
.stream_info_
->rtp_stats
.max_delta
;
278 case min_jitter_col_
:
279 return stream_info_
->rtp_stats
.min_jitter
< other_rstwi
.stream_info_
->rtp_stats
.min_jitter
;
280 case mean_jitter_col_
:
281 return stream_info_
->rtp_stats
.mean_jitter
< other_rstwi
.stream_info_
->rtp_stats
.mean_jitter
;
282 case max_jitter_col_
:
283 return stream_info_
->rtp_stats
.max_jitter
< other_rstwi
.stream_info_
->rtp_stats
.max_jitter
;
288 // Fall back to string comparison
289 return QTreeWidgetItem::operator <(other
);
292 void setTOD(bool tod
)
298 rtpstream_info_t
*stream_info_
;
303 RtpStreamDialog
*RtpStreamDialog::pinstance_
{nullptr};
304 std::mutex
RtpStreamDialog::mutex_
;
306 RtpStreamDialog
*RtpStreamDialog::openRtpStreamDialog(QWidget
&parent
, CaptureFile
&cf
, QObject
*packet_list
)
308 std::lock_guard
<std::mutex
> lock(mutex_
);
309 if (pinstance_
== nullptr)
311 pinstance_
= new RtpStreamDialog(parent
, cf
);
312 connect(pinstance_
, SIGNAL(packetsMarked()),
313 packet_list
, SLOT(redrawVisiblePackets()));
314 connect(pinstance_
, SIGNAL(goToPacket(int)),
315 packet_list
, SLOT(goToPacket(int)));
320 RtpStreamDialog::RtpStreamDialog(QWidget
&parent
, CaptureFile
&cf
) :
321 WiresharkDialog(parent
, cf
),
322 ui(new Ui::RtpStreamDialog
),
326 loadGeometry(parent
.width() * 4 / 5, parent
.height() * 2 / 3);
327 setWindowSubtitle(tr("RTP Streams"));
328 ui
->streamTreeWidget
->installEventFilter(this);
330 ctx_menu_
.addMenu(ui
->menuSelect
);
331 ctx_menu_
.addMenu(ui
->menuFindReverse
);
332 ctx_menu_
.addAction(ui
->actionGoToSetup
);
333 ctx_menu_
.addAction(ui
->actionMarkPackets
);
334 ctx_menu_
.addAction(ui
->actionPrepareFilter
);
335 ctx_menu_
.addAction(ui
->actionExportAsRtpDump
);
336 ctx_menu_
.addAction(ui
->actionCopyAsCsv
);
337 ctx_menu_
.addAction(ui
->actionCopyAsYaml
);
338 ctx_menu_
.addAction(ui
->actionAnalyze
);
339 set_action_shortcuts_visible_in_context_menu(ctx_menu_
.actions());
341 ui
->streamTreeWidget
->setContextMenuPolicy(Qt::CustomContextMenu
);
342 ui
->streamTreeWidget
->header()->setSortIndicator(0, Qt::AscendingOrder
);
343 connect(ui
->streamTreeWidget
, SIGNAL(customContextMenuRequested(QPoint
)),
344 SLOT(showStreamMenu(QPoint
)));
346 find_reverse_button_
= new QToolButton();
347 ui
->buttonBox
->addButton(find_reverse_button_
, QDialogButtonBox::ActionRole
);
348 find_reverse_button_
->setToolButtonStyle(Qt::ToolButtonTextBesideIcon
);
349 find_reverse_button_
->setPopupMode(QToolButton::MenuButtonPopup
);
351 connect(ui
->actionFindReverse
, &QAction::triggered
, this, &RtpStreamDialog::on_actionFindReverseNormal_triggered
);
352 find_reverse_button_
->setDefaultAction(ui
->actionFindReverse
);
353 // Overrides text striping of shortcut undercode in QAction
354 find_reverse_button_
->setText(ui
->actionFindReverseNormal
->text());
355 find_reverse_button_
->setMenu(ui
->menuFindReverse
);
357 analyze_button_
= RtpAnalysisDialog::addAnalyzeButton(ui
->buttonBox
, this);
358 prepare_button_
= ui
->buttonBox
->addButton(ui
->actionPrepareFilter
->text(), QDialogButtonBox::ActionRole
);
359 prepare_button_
->setToolTip(ui
->actionPrepareFilter
->toolTip());
360 connect(prepare_button_
, &QPushButton::pressed
, this, &RtpStreamDialog::on_actionPrepareFilter_triggered
);
361 player_button_
= RtpPlayerDialog::addPlayerButton(ui
->buttonBox
, this);
362 copy_button_
= ui
->buttonBox
->addButton(ui
->actionCopyButton
->text(), QDialogButtonBox::ActionRole
);
363 copy_button_
->setToolTip(ui
->actionCopyButton
->toolTip());
364 export_button_
= ui
->buttonBox
->addButton(ui
->actionExportAsRtpDump
->text(), QDialogButtonBox::ActionRole
);
365 export_button_
->setToolTip(ui
->actionExportAsRtpDump
->toolTip());
366 connect(export_button_
, &QPushButton::pressed
, this, &RtpStreamDialog::on_actionExportAsRtpDump_triggered
);
368 QMenu
*copy_menu
= new QMenu(copy_button_
);
370 ca
= copy_menu
->addAction(tr("as CSV"));
371 ca
->setToolTip(ui
->actionCopyAsCsv
->toolTip());
372 connect(ca
, &QAction::triggered
, this, &RtpStreamDialog::on_actionCopyAsCsv_triggered
);
373 ca
= copy_menu
->addAction(tr("as YAML"));
374 ca
->setToolTip(ui
->actionCopyAsYaml
->toolTip());
375 connect(ca
, &QAction::triggered
, this, &RtpStreamDialog::on_actionCopyAsYaml_triggered
);
376 copy_button_
->setMenu(copy_menu
);
377 connect(&cap_file_
, SIGNAL(captureEvent(CaptureEvent
)),
378 this, SLOT(captureEvent(CaptureEvent
)));
380 /* Register the tap listener */
381 memset(&tapinfo_
, 0, sizeof(rtpstream_tapinfo_t
));
382 tapinfo_
.tap_reset
= tapReset
;
383 tapinfo_
.tap_draw
= tapDraw
;
384 tapinfo_
.tap_mark_packet
= tapMarkPacket
;
385 tapinfo_
.tap_data
= this;
386 tapinfo_
.mode
= TAP_ANALYSE
;
388 register_tap_listener_rtpstream(&tapinfo_
, NULL
, show_tap_registration_error
);
389 if (cap_file_
.isValid() && cap_file_
.capFile()->dfilter
) {
390 // Activate display filter checking
391 tapinfo_
.apply_display_filter
= true;
392 ui
->displayFilterCheckBox
->setChecked(true);
395 connect(ui
->displayFilterCheckBox
, &QCheckBox::toggled
,
396 this, &RtpStreamDialog::displayFilterCheckBoxToggled
);
397 connect(this, SIGNAL(updateFilter(QString
, bool)),
398 &parent
, SLOT(filterPackets(QString
, bool)));
399 connect(&parent
, SIGNAL(displayFilterSuccess(bool)),
400 this, SLOT(displayFilterSuccess(bool)));
401 connect(this, SIGNAL(rtpPlayerDialogReplaceRtpStreams(QVector
<rtpstream_id_t
*>)),
402 &parent
, SLOT(rtpPlayerDialogReplaceRtpStreams(QVector
<rtpstream_id_t
*>)));
403 connect(this, SIGNAL(rtpPlayerDialogAddRtpStreams(QVector
<rtpstream_id_t
*>)),
404 &parent
, SLOT(rtpPlayerDialogAddRtpStreams(QVector
<rtpstream_id_t
*>)));
405 connect(this, SIGNAL(rtpPlayerDialogRemoveRtpStreams(QVector
<rtpstream_id_t
*>)),
406 &parent
, SLOT(rtpPlayerDialogRemoveRtpStreams(QVector
<rtpstream_id_t
*>)));
407 connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector
<rtpstream_id_t
*>)),
408 &parent
, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector
<rtpstream_id_t
*>)));
409 connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector
<rtpstream_id_t
*>)),
410 &parent
, SLOT(rtpAnalysisDialogAddRtpStreams(QVector
<rtpstream_id_t
*>)));
411 connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector
<rtpstream_id_t
*>)),
412 &parent
, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector
<rtpstream_id_t
*>)));
414 ProgressFrame::addToButtonBox(ui
->buttonBox
, &parent
);
418 if (cap_file_
.isValid()) {
419 cap_file_
.delayedRetapPackets();
423 RtpStreamDialog::~RtpStreamDialog()
425 std::lock_guard
<std::mutex
> lock(mutex_
);
428 rtpstream_reset(&tapinfo_
);
429 remove_tap_listener_rtpstream(&tapinfo_
);
430 pinstance_
= nullptr;
433 void RtpStreamDialog::setRtpStreamSelection(rtpstream_id_t
*id
, bool state
)
435 QTreeWidgetItemIterator
iter(ui
->streamTreeWidget
);
437 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(*iter
);
438 rtpstream_info_t
*stream_info
= rsti
->streamInfo();
440 if (rtpstream_id_equal(id
,&stream_info
->id
,RTPSTREAM_ID_EQUAL_SSRC
)) {
441 (*iter
)->setSelected(state
);
448 void RtpStreamDialog::selectRtpStream(QVector
<rtpstream_id_t
*> stream_ids
)
450 std::lock_guard
<std::mutex
> lock(mutex_
);
451 foreach(rtpstream_id_t
*id
, stream_ids
) {
452 setRtpStreamSelection(id
, true);
456 void RtpStreamDialog::deselectRtpStream(QVector
<rtpstream_id_t
*> stream_ids
)
458 std::lock_guard
<std::mutex
> lock(mutex_
);
459 foreach(rtpstream_id_t
*id
, stream_ids
) {
460 setRtpStreamSelection(id
, false);
464 bool RtpStreamDialog::eventFilter(QObject
*, QEvent
*event
)
466 if (ui
->streamTreeWidget
->hasFocus() && event
->type() == QEvent::KeyPress
) {
467 QKeyEvent
&keyEvent
= static_cast<QKeyEvent
&>(*event
);
468 switch(keyEvent
.key()) {
470 on_actionGoToSetup_triggered();
473 on_actionMarkPackets_triggered();
476 on_actionPrepareFilter_triggered();
479 if (keyEvent
.modifiers() == Qt::ShiftModifier
) {
480 on_actionFindReversePair_triggered();
481 } else if (keyEvent
.modifiers() == Qt::ControlModifier
) {
482 on_actionFindReverseSingle_triggered();
484 on_actionFindReverseNormal_triggered();
488 if (keyEvent
.modifiers() == Qt::ControlModifier
) {
490 on_actionSelectInvert_triggered();
495 if (keyEvent
.modifiers() == Qt::ControlModifier
) {
497 on_actionSelectAll_triggered();
499 } else if (keyEvent
.modifiers() == (Qt::ShiftModifier
| Qt::ControlModifier
)) {
501 on_actionSelectNone_triggered();
503 } else if (keyEvent
.modifiers() == Qt::NoModifier
) {
504 on_actionAnalyze_triggered();
514 void RtpStreamDialog::captureEvent(CaptureEvent e
)
516 if (e
.captureContext() == CaptureEvent::Retap
)
518 switch (e
.eventType())
520 case CaptureEvent::Started
:
521 ui
->displayFilterCheckBox
->setEnabled(false);
523 case CaptureEvent::Finished
:
524 ui
->displayFilterCheckBox
->setEnabled(true);
533 void RtpStreamDialog::tapReset(rtpstream_tapinfo_t
*tapinfo
)
535 RtpStreamDialog
*rtp_stream_dialog
= dynamic_cast<RtpStreamDialog
*>((RtpStreamDialog
*)tapinfo
->tap_data
);
536 if (rtp_stream_dialog
) {
537 rtp_stream_dialog
->freeLastSelected();
538 /* Copy currently selected rtpstream_ids */
539 QTreeWidgetItemIterator
iter(rtp_stream_dialog
->ui
->streamTreeWidget
);
540 rtpstream_id_t selected_id
;
542 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(*iter
);
543 rtpstream_info_t
*stream_info
= rsti
->streamInfo();
544 if ((*iter
)->isSelected()) {
545 /* QList.append() does a member by member copy, so allocate new
546 * addresses. rtpstream_id_copy() overwrites all struct members.
548 rtpstream_id_copy(&stream_info
->id
, &selected_id
);
549 rtp_stream_dialog
->last_selected_
.append(selected_id
);
553 /* invalidate items which refer to old strinfo_list items. */
554 rtp_stream_dialog
->ui
->streamTreeWidget
->clear();
558 void RtpStreamDialog::tapDraw(rtpstream_tapinfo_t
*tapinfo
)
560 RtpStreamDialog
*rtp_stream_dialog
= dynamic_cast<RtpStreamDialog
*>((RtpStreamDialog
*)tapinfo
->tap_data
);
561 if (rtp_stream_dialog
) {
562 rtp_stream_dialog
->updateStreams();
566 void RtpStreamDialog::tapMarkPacket(rtpstream_tapinfo_t
*tapinfo
, frame_data
*fd
)
568 if (!tapinfo
) return;
570 RtpStreamDialog
*rtp_stream_dialog
= dynamic_cast<RtpStreamDialog
*>((RtpStreamDialog
*)tapinfo
->tap_data
);
571 if (rtp_stream_dialog
) {
572 cf_mark_frame(rtp_stream_dialog
->cap_file_
.capFile(), fd
);
573 rtp_stream_dialog
->need_redraw_
= true;
577 /* Operator == for rtpstream_id_t */
578 bool operator==(rtpstream_id_t
const& a
, rtpstream_id_t
const& b
)
580 return rtpstream_id_equal(&a
, &b
, RTPSTREAM_ID_EQUAL_SSRC
);
583 void RtpStreamDialog::updateStreams()
585 // string_list is reverse ordered, so we must add
586 // just first "to_insert_count" of streams
587 GList
*cur_stream
= g_list_first(tapinfo_
.strinfo_list
);
588 unsigned tap_len
= g_list_length(tapinfo_
.strinfo_list
);
589 unsigned tree_len
= static_cast<unsigned>(ui
->streamTreeWidget
->topLevelItemCount());
590 unsigned to_insert_count
= tap_len
- tree_len
;
592 // Add any missing items
593 while (cur_stream
&& cur_stream
->data
&& to_insert_count
) {
594 rtpstream_info_t
*stream_info
= gxx_list_data(rtpstream_info_t
*, cur_stream
);
595 RtpStreamTreeWidgetItem
*rsti
= new RtpStreamTreeWidgetItem(ui
->streamTreeWidget
, stream_info
);
596 cur_stream
= gxx_list_next(cur_stream
);
599 // Check if item was selected last time. If so, select it
600 if (-1 != last_selected_
.indexOf(stream_info
->id
)) {
601 rsti
->setSelected(true);
605 // Recalculate values
606 QTreeWidgetItemIterator
iter(ui
->streamTreeWidget
);
608 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(*iter
);
614 for (int i
= 0; i
< ui
->streamTreeWidget
->columnCount(); i
++) {
615 ui
->streamTreeWidget
->resizeColumnToContents(i
);
618 ui
->streamTreeWidget
->setSortingEnabled(true);
623 emit
packetsMarked();
624 need_redraw_
= false;
628 void RtpStreamDialog::updateWidgets()
630 bool selected
= ui
->streamTreeWidget
->selectedItems().count() > 0;
632 QString hint
= "<small><i>";
633 hint
+= tr("%1 streams").arg(ui
->streamTreeWidget
->topLevelItemCount());
637 foreach(QTreeWidgetItem
*ti
, ui
->streamTreeWidget
->selectedItems()) {
638 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
639 if (rsti
->streamInfo()) {
640 tot_packets
+= rsti
->streamInfo()->packet_count
;
643 hint
+= tr(", %1 selected, %2 total packets")
644 .arg(ui
->streamTreeWidget
->selectedItems().count())
648 hint
+= ". Right-click for more options.";
649 hint
+= "</i></small>";
650 ui
->hintLabel
->setText(hint
);
652 bool enable
= selected
&& !file_closed_
;
653 bool has_data
= ui
->streamTreeWidget
->topLevelItemCount() > 0;
655 find_reverse_button_
->setEnabled(has_data
);
656 prepare_button_
->setEnabled(enable
);
657 export_button_
->setEnabled(enable
);
658 copy_button_
->setEnabled(has_data
);
659 analyze_button_
->setEnabled(enable
);
661 ui
->actionFindReverseNormal
->setEnabled(enable
);
662 ui
->actionFindReversePair
->setEnabled(has_data
);
663 ui
->actionFindReverseSingle
->setEnabled(has_data
);
664 ui
->actionGoToSetup
->setEnabled(enable
);
665 ui
->actionMarkPackets
->setEnabled(enable
);
666 ui
->actionPrepareFilter
->setEnabled(enable
);
667 ui
->actionExportAsRtpDump
->setEnabled(enable
);
668 ui
->actionCopyAsCsv
->setEnabled(has_data
);
669 ui
->actionCopyAsYaml
->setEnabled(has_data
);
670 ui
->actionAnalyze
->setEnabled(enable
);
672 #if defined(QT_MULTIMEDIA_LIB)
673 player_button_
->setEnabled(enable
);
676 WiresharkDialog::updateWidgets();
679 QList
<QVariant
> RtpStreamDialog::streamRowData(int row
) const
681 QList
<QVariant
> row_data
;
683 if (row
>= ui
->streamTreeWidget
->topLevelItemCount()) {
687 for (int col
= 0; col
< ui
->streamTreeWidget
->columnCount(); col
++) {
689 row_data
<< ui
->streamTreeWidget
->headerItem()->text(col
);
691 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(row
));
693 row_data
<< rsti
->colData(col
);
698 // Add additional columns to export
700 row_data
<< QStringLiteral("SSRC formatted");
701 row_data
<< QStringLiteral("Lost percentage");
703 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(row
));
705 row_data
<< rsti
->colData(ssrc_fmt_col_
);
706 row_data
<< rsti
->colData(lost_perc_col_
);
712 void RtpStreamDialog::freeLastSelected()
715 for(int i
=0; i
<last_selected_
.length(); i
++) {
716 rtpstream_id_t id
= last_selected_
.at(i
);
717 rtpstream_id_free(&id
);
719 /* Clear list and reuse it */
720 last_selected_
.clear();
723 void RtpStreamDialog::captureFileClosing()
725 remove_tap_listener_rtpstream(&tapinfo_
);
727 WiresharkDialog::captureFileClosing();
730 void RtpStreamDialog::captureFileClosed()
732 ui
->todCheckBox
->setEnabled(false);
733 ui
->displayFilterCheckBox
->setEnabled(false);
735 WiresharkDialog::captureFileClosed();
738 void RtpStreamDialog::showStreamMenu(QPoint pos
)
740 ui
->actionGoToSetup
->setEnabled(!file_closed_
);
741 ui
->actionMarkPackets
->setEnabled(!file_closed_
);
742 ui
->actionPrepareFilter
->setEnabled(!file_closed_
);
743 ui
->actionExportAsRtpDump
->setEnabled(!file_closed_
);
744 ui
->actionAnalyze
->setEnabled(!file_closed_
);
745 ctx_menu_
.popup(ui
->streamTreeWidget
->viewport()->mapToGlobal(pos
));
748 void RtpStreamDialog::on_actionCopyAsCsv_triggered()
751 QTextStream
stream(&csv
, QIODevice::Text
);
752 for (int row
= -1; row
< ui
->streamTreeWidget
->topLevelItemCount(); row
++) {
754 foreach (QVariant v
, streamRowData(row
)) {
757 } else if (v
.userType() == QMetaType::QString
) {
758 rdsl
<< QStringLiteral("\"%1\"").arg(v
.toString());
760 rdsl
<< v
.toString();
763 stream
<< rdsl
.join(",") << '\n';
765 mainApp
->clipboard()->setText(stream
.readAll());
768 void RtpStreamDialog::on_actionCopyAsYaml_triggered()
771 QTextStream
stream(&yaml
, QIODevice::Text
);
772 stream
<< "---" << '\n';
773 for (int row
= -1; row
< ui
->streamTreeWidget
->topLevelItemCount(); row
++) {
774 stream
<< "-" << '\n';
775 foreach (QVariant v
, streamRowData(row
)) {
776 stream
<< " - " << v
.toString() << '\n';
779 mainApp
->clipboard()->setText(stream
.readAll());
782 void RtpStreamDialog::on_actionExportAsRtpDump_triggered()
784 if (file_closed_
|| ui
->streamTreeWidget
->selectedItems().count() < 1) return;
786 // XXX If the user selected multiple frames is this the one we actually want?
787 QTreeWidgetItem
*ti
= ui
->streamTreeWidget
->selectedItems()[0];
788 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
789 rtpstream_info_t
*stream_info
= rsti
->streamInfo();
792 QDir
path(mainApp
->openDialogInitialDir());
793 QString save_file
= path
.canonicalPath() + "/" + cap_file_
.fileBaseName();
795 file_name
= WiresharkFileDialog::getSaveFileName(this, mainApp
->windowTitleString(tr("Save RTPDump As…")),
796 save_file
, "RTPDump Format (*.rtp)", &extension
);
798 if (file_name
.length() > 0) {
799 char *dest_file
= qstring_strdup(file_name
);
800 bool save_ok
= rtpstream_save(&tapinfo_
, cap_file_
.capFile(), stream_info
, dest_file
);
802 // else error dialog?
804 mainApp
->setLastOpenDirFromFilename(file_name
);
811 // Search for reverse stream of every selected stream
812 void RtpStreamDialog::on_actionFindReverseNormal_triggered()
814 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
816 ui
->streamTreeWidget
->blockSignals(true);
818 // Traverse all items and if stream is selected, search reverse from
819 // current position till last item (NxN/2)
820 for (int fwd_row
= 0; fwd_row
< ui
->streamTreeWidget
->topLevelItemCount(); fwd_row
++) {
821 RtpStreamTreeWidgetItem
*fwd_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(fwd_row
));
822 rtpstream_info_t
*fwd_stream
= fwd_rsti
->streamInfo();
823 if (fwd_stream
&& fwd_rsti
->isSelected()) {
824 for (int rev_row
= fwd_row
+ 1; rev_row
< ui
->streamTreeWidget
->topLevelItemCount(); rev_row
++) {
825 RtpStreamTreeWidgetItem
*rev_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(rev_row
));
826 rtpstream_info_t
*rev_stream
= rev_rsti
->streamInfo();
827 if (rev_stream
&& rtpstream_info_is_reverse(fwd_stream
, rev_stream
)) {
828 rev_rsti
->setSelected(true);
834 ui
->streamTreeWidget
->blockSignals(false);
838 // Select all pairs of forward/reverse streams
839 void RtpStreamDialog::on_actionFindReversePair_triggered()
841 ui
->streamTreeWidget
->blockSignals(true);
842 ui
->streamTreeWidget
->clearSelection();
844 // Traverse all items and search reverse from current position till last
846 for (int fwd_row
= 0; fwd_row
< ui
->streamTreeWidget
->topLevelItemCount(); fwd_row
++) {
847 RtpStreamTreeWidgetItem
*fwd_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(fwd_row
));
848 rtpstream_info_t
*fwd_stream
= fwd_rsti
->streamInfo();
850 for (int rev_row
= fwd_row
+ 1; rev_row
< ui
->streamTreeWidget
->topLevelItemCount(); rev_row
++) {
851 RtpStreamTreeWidgetItem
*rev_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(rev_row
));
852 rtpstream_info_t
*rev_stream
= rev_rsti
->streamInfo();
853 if (rev_stream
&& rtpstream_info_is_reverse(fwd_stream
, rev_stream
)) {
854 fwd_rsti
->setSelected(true);
855 rev_rsti
->setSelected(true);
861 ui
->streamTreeWidget
->blockSignals(false);
865 // Select all streams which don't have reverse stream
866 void RtpStreamDialog::on_actionFindReverseSingle_triggered()
868 ui
->streamTreeWidget
->blockSignals(true);
869 ui
->streamTreeWidget
->selectAll();
871 // Traverse all items and search reverse from current position till last
873 for (int fwd_row
= 0; fwd_row
< ui
->streamTreeWidget
->topLevelItemCount(); fwd_row
++) {
874 RtpStreamTreeWidgetItem
*fwd_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(fwd_row
));
875 rtpstream_info_t
*fwd_stream
= fwd_rsti
->streamInfo();
877 for (int rev_row
= fwd_row
+ 1; rev_row
< ui
->streamTreeWidget
->topLevelItemCount(); rev_row
++) {
878 RtpStreamTreeWidgetItem
*rev_rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ui
->streamTreeWidget
->topLevelItem(rev_row
));
879 rtpstream_info_t
*rev_stream
= rev_rsti
->streamInfo();
880 if (rev_stream
&& rtpstream_info_is_reverse(fwd_stream
, rev_stream
)) {
881 fwd_rsti
->setSelected(false);
882 rev_rsti
->setSelected(false);
888 ui
->streamTreeWidget
->blockSignals(false);
892 void RtpStreamDialog::on_actionGoToSetup_triggered()
894 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
895 // XXX If the user selected multiple frames is this the one we actually want?
896 QTreeWidgetItem
*ti
= ui
->streamTreeWidget
->selectedItems()[0];
897 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
898 rtpstream_info_t
*stream_info
= rsti
->streamInfo();
900 emit
goToPacket(stream_info
->setup_frame_number
);
904 void RtpStreamDialog::on_actionMarkPackets_triggered()
906 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
907 rtpstream_info_t
*stream_a
, *stream_b
= NULL
;
909 QTreeWidgetItem
*ti
= ui
->streamTreeWidget
->selectedItems()[0];
910 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
911 stream_a
= rsti
->streamInfo();
912 if (ui
->streamTreeWidget
->selectedItems().count() > 1) {
913 ti
= ui
->streamTreeWidget
->selectedItems()[1];
914 rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
915 stream_b
= rsti
->streamInfo();
918 if (stream_a
== NULL
&& stream_b
== NULL
) return;
920 // XXX Mark the setup frame as well?
921 need_redraw_
= false;
922 rtpstream_mark(&tapinfo_
, cap_file_
.capFile(), stream_a
, stream_b
);
926 void RtpStreamDialog::on_actionPrepareFilter_triggered()
928 QVector
<rtpstream_id_t
*> ids
= getSelectedRtpIds();
929 QString filter
= make_filter_based_on_rtpstream_id(ids
);
930 if (filter
.length() > 0) {
931 remove_tap_listener_rtpstream(&tapinfo_
);
932 emit
updateFilter(filter
);
936 void RtpStreamDialog::on_streamTreeWidget_itemSelectionChanged()
941 void RtpStreamDialog::on_buttonBox_helpRequested()
943 mainApp
->helpTopicAction(HELP_TELEPHONY_RTP_STREAMS_DIALOG
);
946 void RtpStreamDialog::displayFilterCheckBoxToggled(bool checked
)
948 if (!cap_file_
.isValid()) {
952 tapinfo_
.apply_display_filter
= checked
;
954 cap_file_
.retapPackets();
957 void RtpStreamDialog::on_todCheckBox_toggled(bool checked
)
959 QTreeWidgetItemIterator
iter(ui
->streamTreeWidget
);
961 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(*iter
);
962 rsti
->setTOD(checked
);
966 ui
->streamTreeWidget
->resizeColumnToContents(start_time_col_
);
969 void RtpStreamDialog::on_actionSelectAll_triggered()
971 ui
->streamTreeWidget
->selectAll();
974 void RtpStreamDialog::on_actionSelectInvert_triggered()
979 void RtpStreamDialog::on_actionSelectNone_triggered()
981 ui
->streamTreeWidget
->clearSelection();
984 QVector
<rtpstream_id_t
*>RtpStreamDialog::getSelectedRtpIds()
986 // Gather up our selected streams...
987 QVector
<rtpstream_id_t
*> stream_ids
;
988 foreach(QTreeWidgetItem
*ti
, ui
->streamTreeWidget
->selectedItems()) {
989 RtpStreamTreeWidgetItem
*rsti
= static_cast<RtpStreamTreeWidgetItem
*>(ti
);
990 rtpstream_info_t
*selected_stream
= rsti
->streamInfo();
991 if (selected_stream
) {
992 stream_ids
<< &(selected_stream
->id
);
999 void RtpStreamDialog::rtpPlayerReplace()
1001 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1003 emit
rtpPlayerDialogReplaceRtpStreams(getSelectedRtpIds());
1006 void RtpStreamDialog::rtpPlayerAdd()
1008 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1010 emit
rtpPlayerDialogAddRtpStreams(getSelectedRtpIds());
1013 void RtpStreamDialog::rtpPlayerRemove()
1015 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1017 emit
rtpPlayerDialogRemoveRtpStreams(getSelectedRtpIds());
1020 void RtpStreamDialog::rtpAnalysisReplace()
1022 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1024 emit
rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpIds());
1027 void RtpStreamDialog::rtpAnalysisAdd()
1029 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1031 emit
rtpAnalysisDialogAddRtpStreams(getSelectedRtpIds());
1034 void RtpStreamDialog::rtpAnalysisRemove()
1036 if (ui
->streamTreeWidget
->selectedItems().count() < 1) return;
1038 emit
rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpIds());
1041 void RtpStreamDialog::displayFilterSuccess(bool success
)
1043 if (success
&& ui
->displayFilterCheckBox
->isChecked()) {
1044 cap_file_
.retapPackets();
1048 void RtpStreamDialog::invertSelection()
1050 ui
->streamTreeWidget
->blockSignals(true);
1051 for (int row
= 0; row
< ui
->streamTreeWidget
->topLevelItemCount(); row
++) {
1052 QTreeWidgetItem
*ti
= ui
->streamTreeWidget
->topLevelItem(row
);
1053 ti
->setSelected(!ti
->isSelected());
1055 ui
->streamTreeWidget
->blockSignals(false);
1059 void RtpStreamDialog::on_actionAnalyze_triggered()
1061 RtpStreamDialog::rtpAnalysisAdd();