File: | usr/include/x86_64-linux-gnu/qt6/QtCore/qsharedpointer_impl.h |
Warning: | line 130, column 50 Use of memory after it is freed |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* rtp_player_dialog.cpp | |||
2 | * | |||
3 | * Wireshark - Network traffic analyzer | |||
4 | * By Gerald Combs <[email protected]> | |||
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_) { | |||
| ||||
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++) { | |||
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 against 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) { | |||
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; | |||
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 = QStringLiteral("%1 - %2 (%3)")(QString(QtPrivate::qMakeStringPrivate(u"" "%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 = QStringLiteral("%1 - %2 (%3)")(QString(QtPrivate::qMakeStringPrivate(u"" "%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 | QStringLiteral("*")(QString(QtPrivate::qMakeStringPrivate(u"" "*"))), 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_, QStringLiteral("RTP %1")(QString(QtPrivate::qMakeStringPrivate(u"" "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_, QStringLiteral("SETUP %1")(QString(QtPrivate::qMakeStringPrivate(u"" "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_, 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() && !playing_streams_.isEmpty()) { | |||
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) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QPOINTER_H |
5 | #define QPOINTER_H |
6 | |
7 | #include <QtCore/qsharedpointer.h> |
8 | #include <QtCore/qtypeinfo.h> |
9 | |
10 | #ifndef QT_NO_QOBJECT |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | class QVariant; |
15 | |
16 | template <class T> |
17 | class QPointer |
18 | { |
19 | static_assert(!std::is_pointer<T>::value, "QPointer's template type must not be a pointer type"); |
20 | |
21 | using QObjectType = |
22 | typename std::conditional<std::is_const<T>::value, const QObject, QObject>::type; |
23 | QWeakPointer<QObjectType> wp; |
24 | public: |
25 | QPointer() = default; |
26 | inline QPointer(T *p) : wp(p, true) { } |
27 | // compiler-generated copy/move ctor/assignment operators are fine! |
28 | // compiler-generated dtor is fine! |
29 | |
30 | #ifdef Q_QDOC |
31 | // Stop qdoc from complaining about missing function |
32 | ~QPointer(); |
33 | #endif |
34 | |
35 | inline void swap(QPointer &other) noexcept { wp.swap(other.wp); } |
36 | |
37 | inline QPointer<T> &operator=(T* p) |
38 | { wp.assign(static_cast<QObjectType*>(p)); return *this; } |
39 | |
40 | inline T* data() const |
41 | { return static_cast<T*>(wp.internalData()); } |
42 | inline T* get() const |
43 | { return data(); } |
44 | inline T* operator->() const |
45 | { return data(); } |
46 | inline T& operator*() const |
47 | { return *data(); } |
48 | inline operator T*() const |
49 | { return data(); } |
50 | |
51 | inline bool isNull() const |
52 | { return wp.isNull(); } |
53 | |
54 | inline void clear() |
55 | { wp.clear(); } |
56 | |
57 | #define DECLARE_COMPARE_SET(T1, A1, T2, A2) \ |
58 | friend bool operator==(T1, T2) \ |
59 | { return A1 == A2; } \ |
60 | friend bool operator!=(T1, T2) \ |
61 | { return A1 != A2; } |
62 | |
63 | #define DECLARE_TEMPLATE_COMPARE_SET(T1, A1, T2, A2) \ |
64 | template <typename X> \ |
65 | friend bool operator==(T1, T2) noexcept \ |
66 | { return A1 == A2; } \ |
67 | template <typename X> \ |
68 | friend bool operator!=(T1, T2) noexcept \ |
69 | { return A1 != A2; } |
70 | |
71 | DECLARE_TEMPLATE_COMPARE_SET(const QPointer &p1, p1.data(), const QPointer<X> &p2, p2.data()) |
72 | DECLARE_TEMPLATE_COMPARE_SET(const QPointer &p1, p1.data(), X *ptr, ptr) |
73 | DECLARE_TEMPLATE_COMPARE_SET(X *ptr, ptr, const QPointer &p2, p2.data()) |
74 | DECLARE_COMPARE_SET(const QPointer &p1, p1.data(), std::nullptr_t, nullptr) |
75 | DECLARE_COMPARE_SET(std::nullptr_t, nullptr, const QPointer &p2, p2.data()) |
76 | #undef DECLARE_COMPARE_SET |
77 | #undef DECLARE_TEMPLATE_COMPARE_SET |
78 | }; |
79 | template <class T> Q_DECLARE_TYPEINFO_BODY(QPointer<T>, Q_RELOCATABLE_TYPE)class QTypeInfo<QPointer<T> > { public: enum { isComplex = (((Q_RELOCATABLE_TYPE) & Q_PRIMITIVE_TYPE) == 0) && !std::is_trivial_v<QPointer<T> >, isRelocatable = !isComplex || ((Q_RELOCATABLE_TYPE) & Q_RELOCATABLE_TYPE ) || qIsRelocatable<QPointer<T> >, isPointer = false , isIntegral = std::is_integral< QPointer<T> >::value , }; }; |
80 | |
81 | template<typename T> |
82 | QPointer<T> |
83 | qPointerFromVariant(const QVariant &variant) |
84 | { |
85 | const auto wp = QtSharedPointer::weakPointerFromVariant_internal(variant); |
86 | return QPointer<T>{qobject_cast<T*>(QtPrivate::EnableInternalData::internalData(wp))}; |
87 | } |
88 | |
89 | template <class T> |
90 | inline void swap(QPointer<T> &p1, QPointer<T> &p2) noexcept |
91 | { p1.swap(p2); } |
92 | |
93 | QT_END_NAMESPACE |
94 | |
95 | #endif // QT_NO_QOBJECT |
96 | |
97 | #endif // QPOINTER_H |
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); } | |||
| ||||
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; } | |||
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 |