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