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