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