2014-12-13 00:51:40 +00:00
|
|
|
/* rtp_player_dialog.cpp
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
2018-04-30 07:47:58 +00:00
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-11-12 17:23:04 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
#include <ui/rtp_media.h>
|
2021-04-13 14:38:13 +00:00
|
|
|
#include <ui/tap-rtp-common.h>
|
2014-12-13 00:51:40 +00:00
|
|
|
#include "rtp_player_dialog.h"
|
2016-06-12 21:00:21 +00:00
|
|
|
#include <ui_rtp_player_dialog.h>
|
2021-04-14 13:47:07 +00:00
|
|
|
#include "epan/epan_dissect.h"
|
|
|
|
|
|
|
|
#include "file.h"
|
|
|
|
#include "frame_tvbuff.h"
|
|
|
|
|
|
|
|
#include "rtp_analysis_dialog.h"
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
|
|
|
|
|
|
|
#include <epan/dissectors/packet-rtp.h>
|
2021-04-07 05:40:09 +00:00
|
|
|
#include <epan/to_str.h>
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2018-03-25 21:24:59 +00:00
|
|
|
#include <wsutil/report_message.h>
|
2015-10-04 17:10:29 +00:00
|
|
|
#include <wsutil/utf8_entities.h>
|
2021-03-31 14:52:06 +00:00
|
|
|
#include <wsutil/pint.h>
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2017-07-25 14:15:18 +00:00
|
|
|
#include <ui/qt/utils/color_utils.h>
|
|
|
|
#include <ui/qt/widgets/qcustomplot.h>
|
|
|
|
#include <ui/qt/utils/qt_ui_utils.h>
|
2014-12-13 00:51:40 +00:00
|
|
|
#include "rtp_audio_stream.h"
|
2017-07-25 14:15:18 +00:00
|
|
|
#include <ui/qt/utils/tango_colors.h>
|
2021-03-09 00:58:15 +00:00
|
|
|
#include <widgets/rtp_audio_graph.h>
|
2022-02-01 03:30:09 +00:00
|
|
|
#include "main_application.h"
|
2021-03-31 14:52:06 +00:00
|
|
|
#include "ui/qt/widgets/wireshark_file_dialog.h"
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
#include <QAudio>
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
2022-07-08 13:10:21 +00:00
|
|
|
#include <algorithm>
|
2022-05-24 01:59:35 +00:00
|
|
|
#include <QAudioDevice>
|
|
|
|
#include <QAudioSink>
|
|
|
|
#include <QMediaDevices>
|
|
|
|
#else
|
2016-12-02 23:52:02 +00:00
|
|
|
#include <QAudioDeviceInfo>
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2014-12-13 00:51:40 +00:00
|
|
|
#include <QFrame>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QVBoxLayout>
|
2021-04-08 09:15:08 +00:00
|
|
|
#include <QTimer>
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
#include <QAudioFormat>
|
|
|
|
#include <QAudioOutput>
|
|
|
|
#include <ui/qt/utils/rtp_audio_silence_generator.h>
|
|
|
|
|
2015-10-12 17:15:09 +00:00
|
|
|
#endif // QT_MULTIMEDIA_LIB
|
2015-10-08 21:56:06 +00:00
|
|
|
|
|
|
|
#include <QPushButton>
|
2021-05-01 22:33:54 +00:00
|
|
|
#include <QToolButton>
|
2015-10-08 21:56:06 +00:00
|
|
|
|
2017-07-25 14:15:18 +00:00
|
|
|
#include <ui/qt/utils/stock_icon.h>
|
2022-02-01 03:30:09 +00:00
|
|
|
#include "main_application.h"
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
// To do:
|
|
|
|
// - Threaded decoding?
|
|
|
|
|
2016-06-06 21:55:10 +00:00
|
|
|
// Current and former RTP player bugs. Many have attachments that can be usef for testing.
|
2014-12-13 00:51:40 +00:00
|
|
|
// Bug 3368 - The timestamp line in a RTP or RTCP packet display's "Not Representable"
|
|
|
|
// Bug 3952 - VoIP Call RTP Player: audio played is corrupted when RFC2833 packets are present
|
|
|
|
// Bug 4960 - RTP Player: Audio and visual feedback get rapidly out of sync
|
|
|
|
// Bug 5527 - Adding arbitrary value to x-axis RTP player
|
|
|
|
// Bug 7935 - Wrong Timestamps in RTP Player-Decode
|
|
|
|
// Bug 8007 - UI gets confused on playing decoded audio in rtp_player
|
|
|
|
// Bug 9007 - Switching SSRC values in RTP stream
|
|
|
|
// Bug 10613 - RTP audio player crashes
|
|
|
|
// Bug 11125 - RTP Player does not show progress in selected stream in Window 7
|
|
|
|
// Bug 11409 - Wireshark crashes when using RTP player
|
2016-06-06 21:55:10 +00:00
|
|
|
// Bug 12166 - RTP audio player crashes
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
// In some places we match by conv/call number, in others we match by first frame.
|
|
|
|
|
|
|
|
enum {
|
2020-01-07 21:16:42 +00:00
|
|
|
channel_col_,
|
2014-12-13 00:51:40 +00:00
|
|
|
src_addr_col_,
|
|
|
|
src_port_col_,
|
|
|
|
dst_addr_col_,
|
|
|
|
dst_port_col_,
|
|
|
|
ssrc_col_,
|
|
|
|
first_pkt_col_,
|
|
|
|
num_pkts_col_,
|
|
|
|
time_span_col_,
|
|
|
|
sample_rate_col_,
|
2021-03-29 06:49:51 +00:00
|
|
|
play_rate_col_,
|
2014-12-13 00:51:40 +00:00
|
|
|
payload_col_,
|
|
|
|
|
|
|
|
stream_data_col_ = src_addr_col_, // RtpAudioStream
|
2021-02-05 09:19:48 +00:00
|
|
|
graph_audio_data_col_ = src_port_col_, // QCPGraph (wave)
|
|
|
|
graph_sequence_data_col_ = dst_addr_col_, // QCPGraph (sequence)
|
|
|
|
graph_jitter_data_col_ = dst_port_col_, // QCPGraph (jitter)
|
|
|
|
graph_timestamp_data_col_ = ssrc_col_, // QCPGraph (timestamp)
|
2021-03-09 13:47:07 +00:00
|
|
|
// first_pkt_col_ is skipped, it is used for real data
|
|
|
|
graph_silence_data_col_ = num_pkts_col_, // QCPGraph (silence)
|
2014-12-13 00:51:40 +00:00
|
|
|
};
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
class RtpPlayerTreeWidgetItem : public QTreeWidgetItem
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
RtpPlayerTreeWidgetItem(QTreeWidget *tree) :
|
|
|
|
QTreeWidgetItem(tree)
|
|
|
|
{
|
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
bool operator< (const QTreeWidgetItem &other) const
|
|
|
|
{
|
|
|
|
// Handle numeric sorting
|
|
|
|
switch (treeWidget()->sortColumn()) {
|
|
|
|
case src_port_col_:
|
|
|
|
case dst_port_col_:
|
|
|
|
case num_pkts_col_:
|
|
|
|
case sample_rate_col_:
|
2021-03-09 13:47:07 +00:00
|
|
|
return text(treeWidget()->sortColumn()).toInt() < other.text(treeWidget()->sortColumn()).toInt();
|
2021-03-29 06:49:51 +00:00
|
|
|
case play_rate_col_:
|
|
|
|
return text(treeWidget()->sortColumn()).toInt() < other.text(treeWidget()->sortColumn()).toInt();
|
2021-03-09 13:47:07 +00:00
|
|
|
case first_pkt_col_:
|
|
|
|
int v1;
|
|
|
|
int v2;
|
|
|
|
|
|
|
|
v1 = data(first_pkt_col_, Qt::UserRole).toInt();
|
|
|
|
v2 = other.data(first_pkt_col_, Qt::UserRole).toInt();
|
|
|
|
|
|
|
|
return v1 < v2;
|
2021-03-09 00:58:15 +00:00
|
|
|
default:
|
|
|
|
// Fall back to string comparison
|
|
|
|
return QTreeWidgetItem::operator <(other);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-05-09 14:29:11 +00:00
|
|
|
RtpPlayerDialog *RtpPlayerDialog::pinstance_{nullptr};
|
2022-04-04 19:21:26 +00:00
|
|
|
std::mutex RtpPlayerDialog::init_mutex_;
|
|
|
|
std::mutex RtpPlayerDialog::run_mutex_;
|
2021-05-09 14:29:11 +00:00
|
|
|
|
|
|
|
RtpPlayerDialog *RtpPlayerDialog::openRtpPlayerDialog(QWidget &parent, CaptureFile &cf, QObject *packet_list, bool capture_running)
|
|
|
|
{
|
2022-04-04 19:21:26 +00:00
|
|
|
std::lock_guard<std::mutex> lock(init_mutex_);
|
2021-05-09 14:29:11 +00:00
|
|
|
if (pinstance_ == nullptr)
|
|
|
|
{
|
|
|
|
pinstance_ = new RtpPlayerDialog(parent, cf, capture_running);
|
|
|
|
connect(pinstance_, SIGNAL(goToPacket(int)),
|
|
|
|
packet_list, SLOT(goToPacket(int)));
|
|
|
|
}
|
|
|
|
return pinstance_;
|
|
|
|
}
|
|
|
|
|
2022-03-23 13:42:35 +00:00
|
|
|
RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf, bool capture_running _U_) :
|
2014-12-13 00:51:40 +00:00
|
|
|
WiresharkDialog(parent, cf)
|
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
|
|
|
, ui(new Ui::RtpPlayerDialog)
|
2020-01-01 23:38:35 +00:00
|
|
|
, first_stream_rel_start_time_(0.0)
|
|
|
|
, first_stream_abs_start_time_(0.0)
|
|
|
|
, first_stream_rel_stop_time_(0.0)
|
|
|
|
, streams_length_(0.0)
|
|
|
|
, start_marker_time_(0.0)
|
2016-09-25 09:57:55 +00:00
|
|
|
, number_ticker_(new QCPAxisTicker)
|
|
|
|
, datetime_ticker_(new QCPAxisTickerDateTime)
|
2020-01-07 21:16:42 +00:00
|
|
|
, stereo_available_(false)
|
2021-02-08 21:35:12 +00:00
|
|
|
, marker_stream_(0)
|
2021-04-08 09:15:08 +00:00
|
|
|
, marker_stream_requested_out_rate_(0)
|
2021-03-09 00:58:15 +00:00
|
|
|
, last_ti_(0)
|
2021-03-27 20:01:43 +00:00
|
|
|
, listener_removed_(true)
|
2021-04-13 14:38:13 +00:00
|
|
|
, block_redraw_(false)
|
|
|
|
, lock_ui_(0)
|
2021-04-22 19:33:11 +00:00
|
|
|
, read_capture_enabled_(capture_running)
|
2021-05-01 11:51:31 +00:00
|
|
|
, silence_skipped_time_(0.0)
|
2021-11-21 21:05:36 +00:00
|
|
|
#endif // QT_MULTIMEDIA_LIB
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
2016-02-28 18:23:20 +00:00
|
|
|
loadGeometry(parent.width(), parent.height());
|
2022-02-01 03:30:09 +00:00
|
|
|
setWindowTitle(mainApp->windowTitleString(tr("RTP Player")));
|
2021-03-09 00:58:15 +00:00
|
|
|
ui->streamTreeWidget->installEventFilter(this);
|
|
|
|
ui->audioPlot->installEventFilter(this);
|
2021-04-27 16:08:28 +00:00
|
|
|
installEventFilter(this);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
|
|
|
ui->splitter->setStretchFactor(0, 3);
|
|
|
|
ui->splitter->setStretchFactor(1, 1);
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
ui->streamTreeWidget->sortByColumn(first_pkt_col_, Qt::AscendingOrder);
|
|
|
|
|
2021-02-05 09:19:48 +00:00
|
|
|
graph_ctx_menu_ = new QMenu(this);
|
|
|
|
|
|
|
|
graph_ctx_menu_->addAction(ui->actionZoomIn);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionZoomOut);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionReset);
|
|
|
|
graph_ctx_menu_->addSeparator();
|
|
|
|
graph_ctx_menu_->addAction(ui->actionMoveRight10);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionMoveLeft10);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionMoveRight1);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionMoveLeft1);
|
|
|
|
graph_ctx_menu_->addSeparator();
|
|
|
|
graph_ctx_menu_->addAction(ui->actionGoToPacket);
|
2021-03-09 13:47:07 +00:00
|
|
|
graph_ctx_menu_->addAction(ui->actionGoToSetupPacketPlot);
|
2021-02-05 09:19:48 +00:00
|
|
|
set_action_shortcuts_visible_in_context_menu(graph_ctx_menu_->actions());
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
ui->streamTreeWidget->setMouseTracking(true);
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(ui->streamTreeWidget, &QTreeWidget::itemEntered, this, &RtpPlayerDialog::itemEntered);
|
|
|
|
|
|
|
|
connect(ui->audioPlot, &QCustomPlot::mouseMove, this, &RtpPlayerDialog::mouseMovePlot);
|
|
|
|
connect(ui->audioPlot, &QCustomPlot::mousePress, this, &RtpPlayerDialog::graphClicked);
|
|
|
|
connect(ui->audioPlot, &QCustomPlot::mouseDoubleClick, this, &RtpPlayerDialog::graphDoubleClicked);
|
|
|
|
connect(ui->audioPlot, &QCustomPlot::plottableClick, this, &RtpPlayerDialog::plotClicked);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
cur_play_pos_ = new QCPItemStraightLine(ui->audioPlot);
|
|
|
|
cur_play_pos_->setVisible(false);
|
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
start_marker_pos_ = new QCPItemStraightLine(ui->audioPlot);
|
|
|
|
start_marker_pos_->setPen(QPen(Qt::green,4));
|
|
|
|
setStartPlayMarker(0);
|
|
|
|
drawStartPlayMarker();
|
|
|
|
start_marker_pos_->setVisible(true);
|
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
notify_timer_.setInterval(100); // ~15 fps
|
|
|
|
connect(¬ify_timer_, &QTimer::timeout, this, &RtpPlayerDialog::outputNotify);
|
|
|
|
#endif
|
|
|
|
|
2016-09-25 09:57:55 +00:00
|
|
|
datetime_ticker_->setDateTimeFormat("yyyy-MM-dd\nhh:mm:ss.zzz");
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->audioPlot->xAxis->setNumberFormat("gb");
|
|
|
|
ui->audioPlot->xAxis->setNumberPrecision(3);
|
2016-09-25 09:57:55 +00:00
|
|
|
ui->audioPlot->xAxis->setTicker(datetime_ticker_);
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->audioPlot->yAxis->setVisible(false);
|
|
|
|
|
|
|
|
ui->playButton->setIcon(StockIcon("media-playback-start"));
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->playButton->setEnabled(false);
|
2020-12-29 13:41:14 +00:00
|
|
|
ui->pauseButton->setIcon(StockIcon("media-playback-pause"));
|
|
|
|
ui->pauseButton->setCheckable(true);
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->pauseButton->setVisible(false);
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->stopButton->setIcon(StockIcon("media-playback-stop"));
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->stopButton->setEnabled(false);
|
2021-05-01 11:51:31 +00:00
|
|
|
ui->skipSilenceButton->setIcon(StockIcon("media-seek-forward"));
|
|
|
|
ui->skipSilenceButton->setCheckable(true);
|
|
|
|
ui->skipSilenceButton->setEnabled(false);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
read_btn_ = ui->buttonBox->addButton(ui->actionReadCapture->text(), QDialogButtonBox::ActionRole);
|
|
|
|
read_btn_->setToolTip(ui->actionReadCapture->toolTip());
|
|
|
|
read_btn_->setEnabled(false);
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(read_btn_, &QPushButton::pressed, this, &RtpPlayerDialog::on_actionReadCapture_triggered);
|
2021-04-22 19:33:11 +00:00
|
|
|
|
2021-05-01 22:33:54 +00:00
|
|
|
inaudible_btn_ = new QToolButton();
|
|
|
|
ui->buttonBox->addButton(inaudible_btn_, QDialogButtonBox::ActionRole);
|
|
|
|
inaudible_btn_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
|
|
inaudible_btn_->setPopupMode(QToolButton::MenuButtonPopup);
|
|
|
|
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(ui->actionInaudibleButton, &QAction::triggered, this, &RtpPlayerDialog::on_actionSelectInaudible_triggered);
|
2021-05-01 22:33:54 +00:00
|
|
|
inaudible_btn_->setDefaultAction(ui->actionInaudibleButton);
|
|
|
|
// Overrides text striping of shortcut undercode in QAction
|
|
|
|
inaudible_btn_->setText(ui->actionInaudibleButton->text());
|
2021-04-14 13:47:07 +00:00
|
|
|
inaudible_btn_->setEnabled(false);
|
|
|
|
inaudible_btn_->setMenu(ui->menuInaudible);
|
|
|
|
|
|
|
|
analyze_btn_ = RtpAnalysisDialog::addAnalyzeButton(ui->buttonBox, this);
|
|
|
|
|
|
|
|
prepare_btn_ = ui->buttonBox->addButton(ui->actionPrepareFilter->text(), QDialogButtonBox::ActionRole);
|
|
|
|
prepare_btn_->setToolTip(ui->actionPrepareFilter->toolTip());
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(prepare_btn_, &QPushButton::pressed, this, &RtpPlayerDialog::on_actionPrepareFilter_triggered);
|
2021-05-01 22:33:54 +00:00
|
|
|
|
2021-04-06 11:23:00 +00:00
|
|
|
export_btn_ = ui->buttonBox->addButton(ui->actionExportButton->text(), QDialogButtonBox::ActionRole);
|
|
|
|
export_btn_->setToolTip(ui->actionExportButton->toolTip());
|
|
|
|
export_btn_->setEnabled(false);
|
2021-04-14 13:47:07 +00:00
|
|
|
export_btn_->setMenu(ui->menuExport);
|
2021-04-06 11:23:00 +00:00
|
|
|
|
2017-07-21 18:03:04 +00:00
|
|
|
// Ordered, unique device names starting with the system default
|
|
|
|
QMap<QString, bool> out_device_map; // true == default device
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
out_device_map.insert(QMediaDevices::defaultAudioOutput().description(), true);
|
|
|
|
foreach (QAudioDevice out_device, QMediaDevices::audioOutputs()) {
|
|
|
|
if (!out_device_map.contains(out_device.description())) {
|
|
|
|
out_device_map.insert(out_device.description(), false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2017-07-21 18:03:04 +00:00
|
|
|
out_device_map.insert(QAudioDeviceInfo::defaultOutputDevice().deviceName(), true);
|
2016-12-02 23:52:02 +00:00
|
|
|
foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
|
2017-07-21 18:03:04 +00:00
|
|
|
if (!out_device_map.contains(out_device.deviceName())) {
|
|
|
|
out_device_map.insert(out_device.deviceName(), false);
|
|
|
|
}
|
|
|
|
}
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2017-07-21 18:03:04 +00:00
|
|
|
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->outputDeviceComboBox->blockSignals(true);
|
2017-07-21 18:03:04 +00:00
|
|
|
foreach (QString out_name, out_device_map.keys()) {
|
2016-12-02 23:52:02 +00:00
|
|
|
ui->outputDeviceComboBox->addItem(out_name);
|
2017-07-21 18:03:04 +00:00
|
|
|
if (out_device_map.value(out_name)) {
|
|
|
|
ui->outputDeviceComboBox->setCurrentIndex(ui->outputDeviceComboBox->count() - 1);
|
2016-12-02 23:52:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ui->outputDeviceComboBox->count() < 1) {
|
|
|
|
ui->outputDeviceComboBox->setEnabled(false);
|
|
|
|
ui->playButton->setEnabled(false);
|
2020-12-29 13:41:14 +00:00
|
|
|
ui->pauseButton->setEnabled(false);
|
2016-12-02 23:52:02 +00:00
|
|
|
ui->stopButton->setEnabled(false);
|
2021-05-01 11:51:31 +00:00
|
|
|
ui->skipSilenceButton->setEnabled(false);
|
|
|
|
ui->minSilenceSpinBox->setEnabled(false);
|
2016-12-02 23:52:02 +00:00
|
|
|
ui->outputDeviceComboBox->addItem(tr("No devices available"));
|
2021-03-29 09:37:21 +00:00
|
|
|
ui->outputAudioRate->setEnabled(false);
|
2021-02-08 21:35:12 +00:00
|
|
|
} else {
|
|
|
|
stereo_available_ = isStereoAvailable();
|
2021-03-29 09:37:21 +00:00
|
|
|
fillAudioRateMenu();
|
2016-12-02 23:52:02 +00:00
|
|
|
}
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->outputDeviceComboBox->blockSignals(false);
|
2016-12-02 23:52:02 +00:00
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->audioPlot->setMouseTracking(true);
|
|
|
|
ui->audioPlot->setEnabled(true);
|
|
|
|
ui->audioPlot->setInteractions(
|
|
|
|
QCP::iRangeDrag |
|
|
|
|
QCP::iRangeZoom
|
|
|
|
);
|
|
|
|
|
2021-02-05 09:19:48 +00:00
|
|
|
graph_ctx_menu_->addSeparator();
|
|
|
|
list_ctx_menu_ = new QMenu(this);
|
2021-03-11 08:22:15 +00:00
|
|
|
list_ctx_menu_->addAction(ui->actionPlay);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionPlay);
|
|
|
|
list_ctx_menu_->addAction(ui->actionStop);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionStop);
|
2021-04-14 13:47:07 +00:00
|
|
|
list_ctx_menu_->addMenu(ui->menuSelect);
|
|
|
|
graph_ctx_menu_->addMenu(ui->menuSelect);
|
|
|
|
list_ctx_menu_->addMenu(ui->menuAudioRouting);
|
|
|
|
graph_ctx_menu_->addMenu(ui->menuAudioRouting);
|
2021-02-04 14:03:45 +00:00
|
|
|
list_ctx_menu_->addAction(ui->actionRemoveStream);
|
|
|
|
graph_ctx_menu_->addAction(ui->actionRemoveStream);
|
2021-03-09 13:47:07 +00:00
|
|
|
list_ctx_menu_->addAction(ui->actionGoToSetupPacketTree);
|
2021-02-05 09:19:48 +00:00
|
|
|
set_action_shortcuts_visible_in_context_menu(list_ctx_menu_->actions());
|
2021-04-22 19:33:11 +00:00
|
|
|
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(&cap_file_, &CaptureFile::captureEvent, this, &RtpPlayerDialog::captureEvent);
|
2021-05-09 14:29:11 +00:00
|
|
|
connect(this, SIGNAL(updateFilter(QString, bool)),
|
|
|
|
&parent, SLOT(filterPackets(QString, bool)));
|
|
|
|
connect(this, SIGNAL(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)),
|
|
|
|
&parent, SLOT(rtpAnalysisDialogReplaceRtpStreams(QVector<rtpstream_id_t *>)));
|
|
|
|
connect(this, SIGNAL(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)),
|
|
|
|
&parent, SLOT(rtpAnalysisDialogAddRtpStreams(QVector<rtpstream_id_t *>)));
|
|
|
|
connect(this, SIGNAL(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)),
|
|
|
|
&parent, SLOT(rtpAnalysisDialogRemoveRtpStreams(QVector<rtpstream_id_t *>)));
|
2014-12-13 00:51:40 +00:00
|
|
|
#endif // QT_MULTIMEDIA_LIB
|
|
|
|
}
|
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
// _U_ is used when no QT_MULTIMEDIA_LIB is available
|
2021-05-01 22:33:54 +00:00
|
|
|
QToolButton *RtpPlayerDialog::addPlayerButton(QDialogButtonBox *button_box, QDialog *dialog _U_)
|
2015-10-08 21:56:06 +00:00
|
|
|
{
|
|
|
|
if (!button_box) return NULL;
|
|
|
|
|
2021-05-01 22:33:54 +00:00
|
|
|
QAction *ca;
|
|
|
|
QToolButton *player_button = new QToolButton();
|
|
|
|
button_box->addButton(player_button, QDialogButtonBox::ActionRole);
|
|
|
|
player_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
|
|
player_button->setPopupMode(QToolButton::MenuButtonPopup);
|
|
|
|
|
2023-02-26 12:49:56 +00:00
|
|
|
ca = new QAction(tr("&Play Streams"), player_button);
|
2021-05-01 22:33:54 +00:00
|
|
|
ca->setToolTip(tr("Open RTP player dialog"));
|
|
|
|
ca->setIcon(StockIcon("media-playback-start"));
|
|
|
|
connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerReplace()));
|
|
|
|
player_button->setDefaultAction(ca);
|
|
|
|
// Overrides text striping of shortcut undercode in QAction
|
|
|
|
player_button->setText(ca->text());
|
2021-03-27 20:01:43 +00:00
|
|
|
|
|
|
|
#if defined(QT_MULTIMEDIA_LIB)
|
|
|
|
QMenu *button_menu = new QMenu(player_button);
|
|
|
|
button_menu->setToolTipsVisible(true);
|
2021-04-13 11:42:57 +00:00
|
|
|
ca = button_menu->addAction(tr("&Set playlist"));
|
2021-04-06 11:23:00 +00:00
|
|
|
ca->setToolTip(tr("Replace existing playlist in RTP Player with new one"));
|
2021-03-27 20:01:43 +00:00
|
|
|
connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerReplace()));
|
2021-04-13 11:42:57 +00:00
|
|
|
ca = button_menu->addAction(tr("&Add to playlist"));
|
2021-04-06 11:23:00 +00:00
|
|
|
ca->setToolTip(tr("Add new set to existing playlist in RTP Player"));
|
2021-03-27 20:01:43 +00:00
|
|
|
connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerAdd()));
|
2021-04-13 11:42:57 +00:00
|
|
|
ca = button_menu->addAction(tr("&Remove from playlist"));
|
2021-04-06 11:23:00 +00:00
|
|
|
ca->setToolTip(tr("Remove selected streams from playlist in RTP Player"));
|
2021-03-27 20:01:43 +00:00
|
|
|
connect(ca, SIGNAL(triggered()), dialog, SLOT(rtpPlayerRemove()));
|
|
|
|
player_button->setMenu(button_menu);
|
|
|
|
#else
|
|
|
|
player_button->setEnabled(false);
|
|
|
|
player_button->setText(tr("No Audio"));
|
|
|
|
#endif
|
|
|
|
|
2015-10-08 21:56:06 +00:00
|
|
|
return player_button;
|
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
|
|
|
RtpPlayerDialog::~RtpPlayerDialog()
|
|
|
|
{
|
2022-04-04 19:21:26 +00:00
|
|
|
std::lock_guard<std::mutex> lock(init_mutex_);
|
|
|
|
if (pinstance_ != nullptr) {
|
|
|
|
cleanupMarkerStream();
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream)
|
|
|
|
delete audio_stream;
|
|
|
|
}
|
|
|
|
delete ui;
|
|
|
|
pinstance_ = nullptr;
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2015-11-17 17:54:41 +00:00
|
|
|
void RtpPlayerDialog::accept()
|
|
|
|
{
|
2021-01-02 00:58:21 +00:00
|
|
|
if (!listener_removed_) {
|
|
|
|
remove_tap_listener(this);
|
|
|
|
listener_removed_ = true;
|
|
|
|
}
|
|
|
|
|
2015-11-17 17:54:41 +00:00
|
|
|
int row_count = ui->streamTreeWidget->topLevelItemCount();
|
|
|
|
// Stop all streams before the dialogs are closed.
|
|
|
|
for (int row = 0; row < row_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
audio_stream->stopPlaying();
|
|
|
|
}
|
|
|
|
WiresharkDialog::accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::reject()
|
|
|
|
{
|
|
|
|
RtpPlayerDialog::accept();
|
|
|
|
}
|
|
|
|
|
2015-10-20 15:31:52 +00:00
|
|
|
void RtpPlayerDialog::retapPackets()
|
|
|
|
{
|
2021-03-27 20:01:43 +00:00
|
|
|
if (!listener_removed_) {
|
|
|
|
// Retap is running, nothing better we can do
|
|
|
|
return;
|
|
|
|
}
|
2021-04-13 11:42:57 +00:00
|
|
|
lockUI();
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->hintLabel->setText("<i><small>" + tr("Decoding streams...") + "</i></small>");
|
2022-02-01 03:30:09 +00:00
|
|
|
mainApp->processEvents();
|
2021-02-08 21:35:12 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
// Clear packets from existing streams before retap
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
|
|
|
|
row_stream->clearPackets();
|
|
|
|
}
|
|
|
|
|
2021-01-02 00:58:21 +00:00
|
|
|
// destroyCheck is protection againts destroying dialog during recap.
|
|
|
|
// It stores dialog pointer in data() and if dialog destroyed, it
|
|
|
|
// returns null
|
|
|
|
QPointer<RtpPlayerDialog> destroyCheck=this;
|
2018-03-25 21:24:59 +00:00
|
|
|
GString *error_string;
|
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
listener_removed_ = false;
|
2018-07-21 00:07:19 +00:00
|
|
|
error_string = register_tap_listener("rtp", this, NULL, 0, NULL, tapPacket, NULL, NULL);
|
2018-03-25 21:24:59 +00:00
|
|
|
if (error_string) {
|
|
|
|
report_failure("RTP Player - tap registration failed: %s", error_string->str);
|
|
|
|
g_string_free(error_string, TRUE);
|
2021-04-13 11:42:57 +00:00
|
|
|
unlockUI();
|
2018-03-25 21:24:59 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-10-20 15:31:52 +00:00
|
|
|
cap_file_.retapPackets();
|
|
|
|
|
2021-01-02 00:58:21 +00:00
|
|
|
// Check if dialog exists still
|
|
|
|
if (destroyCheck.data()) {
|
|
|
|
if (!listener_removed_) {
|
|
|
|
remove_tap_listener(this);
|
|
|
|
listener_removed_ = true;
|
|
|
|
}
|
2021-04-22 19:33:11 +00:00
|
|
|
fillTappedColumns();
|
2021-01-02 00:58:21 +00:00
|
|
|
rescanPackets(true);
|
|
|
|
}
|
2021-04-13 11:42:57 +00:00
|
|
|
unlockUI();
|
2015-10-20 15:31:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::rescanPackets(bool rescale_axes)
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
lockUI();
|
2021-02-04 12:51:44 +00:00
|
|
|
// Show information for a user - it can last long time...
|
2021-04-14 13:47:07 +00:00
|
|
|
playback_error_.clear();
|
2021-02-04 12:51:44 +00:00
|
|
|
ui->hintLabel->setText("<i><small>" + tr("Decoding streams...") + "</i></small>");
|
2022-02-01 03:30:09 +00:00
|
|
|
mainApp->processEvents();
|
2021-02-04 12:51:44 +00:00
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
QAudioDevice cur_out_device = getCurrentDeviceInfo();
|
|
|
|
#else
|
2021-02-08 21:35:12 +00:00
|
|
|
QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2014-12-13 00:51:40 +00:00
|
|
|
int row_count = ui->streamTreeWidget->topLevelItemCount();
|
2020-01-07 21:16:42 +00:00
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Reset stream values
|
|
|
|
for (int row = 0; row < row_count; row++) {
|
2014-12-13 00:51:40 +00:00
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
2021-02-08 21:35:12 +00:00
|
|
|
audio_stream->setStereoRequired(stereo_available_);
|
|
|
|
audio_stream->reset(first_stream_rel_start_time_);
|
|
|
|
|
|
|
|
audio_stream->setJitterBufferSize((int) ui->jitterSpinBox->value());
|
|
|
|
|
|
|
|
RtpAudioStream::TimingMode timing_mode = RtpAudioStream::JitterBuffer;
|
|
|
|
switch (ui->timingComboBox->currentIndex()) {
|
|
|
|
case RtpAudioStream::RtpTimestamp:
|
|
|
|
timing_mode = RtpAudioStream::RtpTimestamp;
|
|
|
|
break;
|
|
|
|
case RtpAudioStream::Uninterrupted:
|
|
|
|
timing_mode = RtpAudioStream::Uninterrupted;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-01-07 21:16:42 +00:00
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
audio_stream->setTimingMode(timing_mode);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2022-07-10 20:48:47 +00:00
|
|
|
//if (!cur_out_device.isNull()) {
|
|
|
|
audio_stream->decode(cur_out_device);
|
|
|
|
//}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
for (int col = 0; col < ui->streamTreeWidget->columnCount() - 1; col++) {
|
|
|
|
ui->streamTreeWidget->resizeColumnToContents(col);
|
|
|
|
}
|
|
|
|
|
|
|
|
createPlot(rescale_axes);
|
|
|
|
|
|
|
|
updateWidgets();
|
2021-04-13 14:38:13 +00:00
|
|
|
unlockUI();
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::createPlot(bool rescale_axes)
|
|
|
|
{
|
2021-03-22 10:25:41 +00:00
|
|
|
bool legend_out_of_sequence = false;
|
|
|
|
bool legend_jitter_dropped = false;
|
|
|
|
bool legend_wrong_timestamps = false;
|
|
|
|
bool legend_inserted_silences = false;
|
2014-12-13 00:51:40 +00:00
|
|
|
bool relative_timestamps = !ui->todCheckBox->isChecked();
|
2021-02-08 21:35:12 +00:00
|
|
|
int row_count = ui->streamTreeWidget->topLevelItemCount();
|
2021-03-23 08:48:20 +00:00
|
|
|
gint16 total_max_sample_value = 1;
|
2021-02-08 21:35:12 +00:00
|
|
|
|
|
|
|
ui->audioPlot->clearGraphs();
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2016-09-25 09:57:55 +00:00
|
|
|
if (relative_timestamps) {
|
|
|
|
ui->audioPlot->xAxis->setTicker(number_ticker_);
|
|
|
|
} else {
|
|
|
|
ui->audioPlot->xAxis->setTicker(datetime_ticker_);
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-03-23 08:48:20 +00:00
|
|
|
// Calculate common Y scale for graphs
|
|
|
|
for (int row = 0; row < row_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
gint16 max_sample_value = audio_stream->getMaxSampleValue();
|
|
|
|
|
|
|
|
if (max_sample_value > total_max_sample_value) {
|
|
|
|
total_max_sample_value = max_sample_value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Clear existing graphs
|
2014-12-13 00:51:40 +00:00
|
|
|
for (int row = 0; row < row_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
int y_offset = row_count - row - 1;
|
2021-02-08 21:35:12 +00:00
|
|
|
AudioRouting audio_routing = audio_stream->getAudioRouting();
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant());
|
2015-10-20 15:31:52 +00:00
|
|
|
|
2021-03-23 08:48:20 +00:00
|
|
|
// Set common scale
|
|
|
|
audio_stream->setMaxSampleValue(total_max_sample_value);
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
// Waveform
|
2021-03-09 00:58:15 +00:00
|
|
|
RtpAudioGraph *audio_graph = new RtpAudioGraph(ui->audioPlot, audio_stream->color());
|
|
|
|
audio_graph->setMuted(audio_routing.isMuted());
|
2014-12-13 00:51:40 +00:00
|
|
|
audio_graph->setData(audio_stream->visualTimestamps(relative_timestamps), audio_stream->visualSamples(y_offset));
|
2021-03-09 00:58:15 +00:00
|
|
|
ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant::fromValue<RtpAudioGraph *>(audio_graph));
|
|
|
|
//RTP_STREAM_DEBUG("Plotting %s, %d samples", ti->text(src_addr_col_).toUtf8().constData(), audio_graph->wave->data()->size());
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-01-01 18:22:07 +00:00
|
|
|
QString span_str;
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
QDateTime date_time1 = QDateTime::fromMSecsSinceEpoch((audio_stream->startRelTime() + first_stream_abs_start_time_ - audio_stream->startRelTime()) * 1000.0);
|
|
|
|
QDateTime date_time2 = QDateTime::fromMSecsSinceEpoch((audio_stream->stopRelTime() + first_stream_abs_start_time_ - audio_stream->startRelTime()) * 1000.0);
|
|
|
|
QString time_str1 = date_time1.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
|
|
QString time_str2 = date_time2.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
|
|
span_str = QString("%1 - %2 (%3)")
|
|
|
|
.arg(time_str1)
|
|
|
|
.arg(time_str2)
|
|
|
|
.arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1));
|
|
|
|
} else {
|
|
|
|
span_str = QString("%1 - %2 (%3)")
|
2020-12-30 12:28:31 +00:00
|
|
|
.arg(QString::number(audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1))
|
|
|
|
.arg(QString::number(audio_stream->stopRelTime(), 'f', prefs.gui_decimal_places1))
|
|
|
|
.arg(QString::number(audio_stream->stopRelTime() - audio_stream->startRelTime(), 'f', prefs.gui_decimal_places1));
|
2021-01-01 18:22:07 +00:00
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
ti->setText(time_span_col_, span_str);
|
|
|
|
ti->setText(sample_rate_col_, QString::number(audio_stream->sampleRate()));
|
2021-03-29 06:49:51 +00:00
|
|
|
ti->setText(play_rate_col_, QString::number(audio_stream->playRate()));
|
2014-12-13 00:51:40 +00:00
|
|
|
ti->setText(payload_col_, audio_stream->payloadNames().join(", "));
|
|
|
|
|
|
|
|
if (audio_stream->outOfSequence() > 0) {
|
|
|
|
// Sequence numbers
|
|
|
|
QCPGraph *seq_graph = ui->audioPlot->addGraph();
|
|
|
|
seq_graph->setLineStyle(QCPGraph::lsNone);
|
2022-02-01 03:30:09 +00:00
|
|
|
seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssSquare, tango_aluminium_6, Qt::white, mainApp->font().pointSize())); // Arbitrary
|
2016-09-25 09:57:55 +00:00
|
|
|
seq_graph->setSelectable(QCP::stNone);
|
2014-12-13 00:51:40 +00:00
|
|
|
seq_graph->setData(audio_stream->outOfSequenceTimestamps(relative_timestamps), audio_stream->outOfSequenceSamples(y_offset));
|
2021-02-05 09:19:48 +00:00
|
|
|
ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
|
2021-03-22 10:25:41 +00:00
|
|
|
if (legend_out_of_sequence) {
|
2014-12-13 00:51:40 +00:00
|
|
|
seq_graph->removeFromLegend();
|
2021-03-22 10:25:41 +00:00
|
|
|
} else {
|
|
|
|
seq_graph->setName(tr("Out of Sequence"));
|
|
|
|
legend_out_of_sequence = true;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
2015-10-26 15:34:58 +00:00
|
|
|
|
|
|
|
if (audio_stream->jitterDropped() > 0) {
|
|
|
|
// Jitter drops
|
|
|
|
QCPGraph *seq_graph = ui->audioPlot->addGraph();
|
|
|
|
seq_graph->setLineStyle(QCPGraph::lsNone);
|
2022-02-01 03:30:09 +00:00
|
|
|
seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, tango_scarlet_red_5, Qt::white, mainApp->font().pointSize())); // Arbitrary
|
2016-09-25 09:57:55 +00:00
|
|
|
seq_graph->setSelectable(QCP::stNone);
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->setData(audio_stream->jitterDroppedTimestamps(relative_timestamps), audio_stream->jitterDroppedSamples(y_offset));
|
2021-02-05 09:19:48 +00:00
|
|
|
ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
|
2021-03-22 10:25:41 +00:00
|
|
|
if (legend_jitter_dropped) {
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->removeFromLegend();
|
2021-03-22 10:25:41 +00:00
|
|
|
} else {
|
|
|
|
seq_graph->setName(tr("Jitter Drops"));
|
|
|
|
legend_jitter_dropped = true;
|
2015-10-26 15:34:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (audio_stream->wrongTimestamps() > 0) {
|
|
|
|
// Wrong timestamps
|
|
|
|
QCPGraph *seq_graph = ui->audioPlot->addGraph();
|
|
|
|
seq_graph->setLineStyle(QCPGraph::lsNone);
|
2022-02-01 03:30:09 +00:00
|
|
|
seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDiamond, tango_sky_blue_5, Qt::white, mainApp->font().pointSize())); // Arbitrary
|
2016-09-25 09:57:55 +00:00
|
|
|
seq_graph->setSelectable(QCP::stNone);
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->setData(audio_stream->wrongTimestampTimestamps(relative_timestamps), audio_stream->wrongTimestampSamples(y_offset));
|
2021-02-05 09:19:48 +00:00
|
|
|
ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
|
2021-03-22 10:25:41 +00:00
|
|
|
if (legend_wrong_timestamps) {
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->removeFromLegend();
|
2021-03-22 10:25:41 +00:00
|
|
|
} else {
|
|
|
|
seq_graph->setName(tr("Wrong Timestamps"));
|
|
|
|
legend_wrong_timestamps = true;
|
2015-10-26 15:34:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (audio_stream->insertedSilences() > 0) {
|
|
|
|
// Inserted silence
|
|
|
|
QCPGraph *seq_graph = ui->audioPlot->addGraph();
|
|
|
|
seq_graph->setLineStyle(QCPGraph::lsNone);
|
2022-02-01 03:30:09 +00:00
|
|
|
seq_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, tango_butter_5, Qt::white, mainApp->font().pointSize())); // Arbitrary
|
2016-09-25 09:57:55 +00:00
|
|
|
seq_graph->setSelectable(QCP::stNone);
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->setData(audio_stream->insertedSilenceTimestamps(relative_timestamps), audio_stream->insertedSilenceSamples(y_offset));
|
2021-02-05 09:19:48 +00:00
|
|
|
ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant::fromValue<QCPGraph *>(seq_graph));
|
2021-03-22 10:25:41 +00:00
|
|
|
if (legend_inserted_silences) {
|
2015-10-26 15:34:58 +00:00
|
|
|
seq_graph->removeFromLegend();
|
2021-03-22 10:25:41 +00:00
|
|
|
} else {
|
|
|
|
seq_graph->setName(tr("Inserted Silence"));
|
|
|
|
legend_inserted_silences = true;
|
2015-10-26 15:34:58 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
2021-03-22 10:25:41 +00:00
|
|
|
ui->audioPlot->legend->setVisible(legend_out_of_sequence || legend_jitter_dropped || legend_wrong_timestamps || legend_inserted_silences);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
if (rescale_axes) resetXAxis();
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::fillTappedColumns()
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2021-04-22 19:33:11 +00:00
|
|
|
// true just for first stream
|
|
|
|
bool is_first = true;
|
2021-04-13 14:38:13 +00:00
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
// Get all rows, immutable list. Later changes in rows migth reorder them
|
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->findItems(
|
|
|
|
QString("*"), Qt::MatchWrap | Qt::MatchWildcard | Qt::MatchRecursive);
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
// Update rows by calculated values, it might reorder them in view...
|
|
|
|
foreach(QTreeWidgetItem *ti, items) {
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream) {
|
|
|
|
rtpstream_info_t *rtpstream = audio_stream->getStreamInfo();
|
2021-04-13 11:42:57 +00:00
|
|
|
|
|
|
|
// 0xFFFFFFFF mean no setup frame
|
|
|
|
// first_packet_num == setup_frame_number happens, when
|
|
|
|
// rtp_udp is active or Decode as was used
|
|
|
|
if ((rtpstream->setup_frame_number == 0xFFFFFFFF) ||
|
|
|
|
(rtpstream->rtp_stats.first_packet_num == rtpstream->setup_frame_number)
|
|
|
|
) {
|
|
|
|
int packet = rtpstream->rtp_stats.first_packet_num;
|
|
|
|
ti->setText(first_pkt_col_, QString("RTP %1").arg(packet));
|
|
|
|
ti->setData(first_pkt_col_, Qt::UserRole, QVariant(packet));
|
|
|
|
} else {
|
|
|
|
int packet = rtpstream->setup_frame_number;
|
|
|
|
ti->setText(first_pkt_col_, QString("SETUP %1").arg(rtpstream->setup_frame_number));
|
|
|
|
ti->setData(first_pkt_col_, Qt::UserRole, QVariant(packet));
|
|
|
|
}
|
|
|
|
ti->setText(num_pkts_col_, QString::number(rtpstream->packet_count));
|
2021-04-22 19:33:11 +00:00
|
|
|
updateStartStopTime(rtpstream, is_first);
|
|
|
|
is_first = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setMarkers();
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::addSingleRtpStream(rtpstream_id_t *id)
|
|
|
|
{
|
|
|
|
bool found = false;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
AudioRouting audio_routing = AudioRouting(AUDIO_UNMUTED, channel_mono);
|
|
|
|
|
|
|
|
if (!id) return;
|
|
|
|
|
|
|
|
// Find the RTP streams associated with this conversation.
|
|
|
|
// gtk/rtp_player.c:mark_rtp_stream_to_play does this differently.
|
|
|
|
|
|
|
|
QList<RtpAudioStream *> streams = stream_hash_.values(rtpstream_id_to_hash(id));
|
|
|
|
for (int i = 0; i < streams.size(); i++) {
|
|
|
|
RtpAudioStream *row_stream = streams.at(i);
|
|
|
|
if (row_stream->isMatch(id)) {
|
|
|
|
found = true;
|
|
|
|
break;
|
2021-04-13 11:42:57 +00:00
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
if (found) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
int tli_count = ui->streamTreeWidget->topLevelItemCount();
|
|
|
|
|
|
|
|
RtpAudioStream *audio_stream = new RtpAudioStream(this, id, stereo_available_);
|
|
|
|
audio_stream->setColor(ColorUtils::graphColor(tli_count));
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = new RtpPlayerTreeWidgetItem(ui->streamTreeWidget);
|
|
|
|
stream_hash_.insert(rtpstream_id_to_hash(id), audio_stream);
|
|
|
|
ti->setText(src_addr_col_, address_to_qstring(&(id->src_addr)));
|
|
|
|
ti->setText(src_port_col_, QString::number(id->src_port));
|
|
|
|
ti->setText(dst_addr_col_, address_to_qstring(&(id->dst_addr)));
|
|
|
|
ti->setText(dst_port_col_, QString::number(id->dst_port));
|
|
|
|
ti->setText(ssrc_col_, int_to_qstring(id->ssrc, 8, 16));
|
|
|
|
|
|
|
|
// Calculated items are updated after every retapPackets()
|
|
|
|
|
|
|
|
ti->setData(stream_data_col_, Qt::UserRole, QVariant::fromValue(audio_stream));
|
|
|
|
if (stereo_available_) {
|
|
|
|
if (tli_count%2) {
|
|
|
|
audio_routing.setChannel(channel_stereo_right);
|
|
|
|
} else {
|
|
|
|
audio_routing.setChannel(channel_stereo_left);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
audio_routing.setChannel(channel_mono);
|
|
|
|
}
|
|
|
|
ti->setToolTip(channel_col_, QString(tr("Double click on cell to change audio routing")));
|
|
|
|
formatAudioRouting(ti, audio_routing);
|
|
|
|
audio_stream->setAudioRouting(audio_routing);
|
|
|
|
|
|
|
|
for (int col = 0; col < ui->streamTreeWidget->columnCount(); col++) {
|
|
|
|
QBrush fgBrush = ti->foreground(col);
|
|
|
|
fgBrush.setColor(audio_stream->color());
|
2022-09-27 09:48:40 +00:00
|
|
|
fgBrush.setStyle(Qt::SolidPattern);
|
2021-04-22 19:33:11 +00:00
|
|
|
ti->setForeground(col, fgBrush);
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-06-21 19:17:56 +00:00
|
|
|
connect(audio_stream, &RtpAudioStream::finishedPlaying, this, &RtpPlayerDialog::playFinished);
|
|
|
|
connect(audio_stream, &RtpAudioStream::playbackError, this, &RtpPlayerDialog::setPlaybackError);
|
2021-04-22 19:33:11 +00:00
|
|
|
} catch (...) {
|
|
|
|
qWarning() << "Stream ignored, try to add fewer streams to playlist";
|
|
|
|
}
|
|
|
|
|
|
|
|
RTP_STREAM_DEBUG("adding stream %d to layout",
|
|
|
|
ui->streamTreeWidget->topLevelItemCount());
|
2021-03-27 20:01:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 11:42:57 +00:00
|
|
|
void RtpPlayerDialog::lockUI()
|
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
if (0 == lock_ui_++) {
|
|
|
|
if (playing_streams_.count() > 0) {
|
|
|
|
on_stopButton_clicked();
|
|
|
|
}
|
|
|
|
setEnabled(false);
|
2021-04-13 11:42:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::unlockUI()
|
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
if (--lock_ui_ == 0) {
|
|
|
|
setEnabled(true);
|
|
|
|
}
|
2021-04-13 11:42:57 +00:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::replaceRtpStreams(QVector<rtpstream_id_t *> stream_ids)
|
2021-03-27 20:01:43 +00:00
|
|
|
{
|
2022-04-04 19:21:26 +00:00
|
|
|
std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
|
|
|
|
if (lock.owns_lock()) {
|
|
|
|
lockUI();
|
|
|
|
|
|
|
|
// Delete all existing rows
|
|
|
|
if (last_ti_) {
|
|
|
|
highlightItem(last_ti_, false);
|
|
|
|
last_ti_ = NULL;
|
|
|
|
}
|
2021-04-13 11:42:57 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
for (int row = ui->streamTreeWidget->topLevelItemCount() - 1; row >= 0; row--) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
removeRow(ti);
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
// Add all new streams
|
|
|
|
for (int i=0; i < stream_ids.size(); i++) {
|
|
|
|
addSingleRtpStream(stream_ids[i]);
|
|
|
|
}
|
|
|
|
setMarkers();
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
unlockUI();
|
2021-04-13 11:42:57 +00:00
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
2022-04-04 19:21:26 +00:00
|
|
|
QTimer::singleShot(0, this, SLOT(retapPackets()));
|
2021-04-13 11:42:57 +00:00
|
|
|
#endif
|
2022-04-04 19:21:26 +00:00
|
|
|
} else {
|
|
|
|
ws_warning("replaceRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::addRtpStreams(QVector<rtpstream_id_t *> stream_ids)
|
2021-03-27 20:01:43 +00:00
|
|
|
{
|
2022-04-04 19:21:26 +00:00
|
|
|
std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
|
|
|
|
if (lock.owns_lock()) {
|
|
|
|
lockUI();
|
2021-04-13 11:42:57 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
int tli_count = ui->streamTreeWidget->topLevelItemCount();
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
// Add new streams
|
|
|
|
for (int i=0; i < stream_ids.size(); i++) {
|
|
|
|
addSingleRtpStream(stream_ids[i]);
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
if (tli_count == 0) {
|
|
|
|
setMarkers();
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
unlockUI();
|
2021-04-13 11:42:57 +00:00
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
2022-04-04 19:21:26 +00:00
|
|
|
QTimer::singleShot(0, this, SLOT(retapPackets()));
|
2021-04-13 11:42:57 +00:00
|
|
|
#endif
|
2022-04-04 19:21:26 +00:00
|
|
|
} else {
|
|
|
|
ws_warning("addRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::removeRtpStreams(QVector<rtpstream_id_t *> stream_ids)
|
2021-03-27 20:01:43 +00:00
|
|
|
{
|
2022-04-04 19:21:26 +00:00
|
|
|
std::unique_lock<std::mutex> lock(run_mutex_, std::try_to_lock);
|
|
|
|
if (lock.owns_lock()) {
|
|
|
|
lockUI();
|
|
|
|
int tli_count = ui->streamTreeWidget->topLevelItemCount();
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
for (int i=0; i < stream_ids.size(); i++) {
|
|
|
|
for (int row = 0; row < tli_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *row_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (row_stream->isMatch(stream_ids[i])) {
|
|
|
|
removeRow(ti);
|
|
|
|
tli_count--;
|
|
|
|
break;
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-04 19:21:26 +00:00
|
|
|
updateGraphs();
|
2021-03-27 20:01:43 +00:00
|
|
|
|
2022-04-04 19:21:26 +00:00
|
|
|
updateWidgets();
|
|
|
|
unlockUI();
|
|
|
|
} else {
|
|
|
|
ws_warning("removeRtpStreams was called while other thread locked it. Current call is ignored, try it later.");
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
void RtpPlayerDialog::setMarkers()
|
|
|
|
{
|
|
|
|
setStartPlayMarker(0);
|
|
|
|
drawStartPlayMarker();
|
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::showEvent(QShowEvent *)
|
|
|
|
{
|
|
|
|
QList<int> split_sizes = ui->splitter->sizes();
|
|
|
|
int tot_size = split_sizes[0] + split_sizes[1];
|
|
|
|
int plot_size = tot_size * 3 / 4;
|
|
|
|
split_sizes.clear();
|
|
|
|
split_sizes << plot_size << tot_size - plot_size;
|
|
|
|
ui->splitter->setSizes(split_sizes);
|
|
|
|
}
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
bool RtpPlayerDialog::eventFilter(QObject *, QEvent *event)
|
|
|
|
{
|
|
|
|
if (event->type() == QEvent::KeyPress) {
|
|
|
|
QKeyEvent &keyEvent = static_cast<QKeyEvent&>(*event);
|
|
|
|
int pan_secs = keyEvent.modifiers() & Qt::ShiftModifier ? 1 : 10;
|
|
|
|
|
|
|
|
switch(keyEvent.key()) {
|
|
|
|
case Qt::Key_Minus:
|
|
|
|
case Qt::Key_Underscore: // Shifted minus on U.S. keyboards
|
|
|
|
case Qt::Key_O: // GTK+
|
|
|
|
case Qt::Key_R:
|
|
|
|
on_actionZoomOut_triggered();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Plus:
|
|
|
|
case Qt::Key_Equal: // Unshifted plus on U.S. keyboards
|
|
|
|
case Qt::Key_I: // GTK+
|
|
|
|
if (keyEvent.modifiers() == Qt::ControlModifier) {
|
2021-03-09 13:47:07 +00:00
|
|
|
// Ctrl+I
|
2021-03-13 20:28:06 +00:00
|
|
|
on_actionSelectInvert_triggered();
|
2021-03-09 00:58:15 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
2021-03-09 13:47:07 +00:00
|
|
|
// I
|
2021-03-09 00:58:15 +00:00
|
|
|
on_actionZoomIn_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_Right:
|
|
|
|
case Qt::Key_L:
|
|
|
|
panXAxis(pan_secs);
|
|
|
|
return true;
|
|
|
|
case Qt::Key_Left:
|
|
|
|
case Qt::Key_H:
|
|
|
|
panXAxis(-1 * pan_secs);
|
|
|
|
return true;
|
|
|
|
case Qt::Key_0:
|
|
|
|
case Qt::Key_ParenRight: // Shifted 0 on U.S. keyboards
|
|
|
|
on_actionReset_triggered();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_G:
|
2021-03-09 13:47:07 +00:00
|
|
|
if (keyEvent.modifiers() == Qt::ShiftModifier) {
|
|
|
|
// Goto SETUP frame, use correct call based on caller
|
|
|
|
QPoint pos1 = ui->audioPlot->mapFromGlobal(QCursor::pos());
|
|
|
|
QPoint pos2 = ui->streamTreeWidget->mapFromGlobal(QCursor::pos());
|
|
|
|
if (ui->audioPlot->rect().contains(pos1)) {
|
|
|
|
// audio plot, by mouse coords
|
|
|
|
on_actionGoToSetupPacketPlot_triggered();
|
|
|
|
} else if (ui->streamTreeWidget->rect().contains(pos2)) {
|
|
|
|
// packet tree, by cursor
|
|
|
|
on_actionGoToSetupPacketTree_triggered();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
on_actionGoToPacket_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
2021-03-09 00:58:15 +00:00
|
|
|
case Qt::Key_A:
|
|
|
|
if (keyEvent.modifiers() == Qt::ControlModifier) {
|
2021-03-09 13:47:07 +00:00
|
|
|
// Ctrl+A
|
2021-03-13 20:28:06 +00:00
|
|
|
on_actionSelectAll_triggered();
|
2021-03-09 00:58:15 +00:00
|
|
|
return true;
|
|
|
|
} else if (keyEvent.modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) {
|
2021-03-09 13:47:07 +00:00
|
|
|
// Ctrl+Shift+A
|
2021-03-13 20:28:06 +00:00
|
|
|
on_actionSelectNone_triggered();
|
2021-03-09 00:58:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_M:
|
2021-03-11 08:22:15 +00:00
|
|
|
if (keyEvent.modifiers() == Qt::ShiftModifier) {
|
|
|
|
on_actionAudioRoutingUnmute_triggered();
|
|
|
|
return true;
|
|
|
|
} else if (keyEvent.modifiers() == Qt::ControlModifier) {
|
|
|
|
on_actionAudioRoutingMuteInvert_triggered();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
on_actionAudioRoutingMute_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
2021-03-09 00:58:15 +00:00
|
|
|
case Qt::Key_Delete:
|
|
|
|
on_actionRemoveStream_triggered();
|
|
|
|
return true;
|
|
|
|
case Qt::Key_X:
|
|
|
|
if (keyEvent.modifiers() == Qt::ControlModifier) {
|
2021-03-09 13:47:07 +00:00
|
|
|
// Ctrl+X
|
2021-03-09 00:58:15 +00:00
|
|
|
on_actionRemoveStream_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Qt::Key_Down:
|
|
|
|
case Qt::Key_Up:
|
|
|
|
case Qt::Key_PageUp:
|
|
|
|
case Qt::Key_PageDown:
|
|
|
|
case Qt::Key_Home:
|
|
|
|
case Qt::Key_End:
|
2021-03-09 13:47:07 +00:00
|
|
|
// Route keys to QTreeWidget
|
2021-03-09 00:58:15 +00:00
|
|
|
ui->streamTreeWidget->setFocus();
|
|
|
|
break;
|
2021-03-11 08:22:15 +00:00
|
|
|
case Qt::Key_P:
|
2021-05-01 22:33:54 +00:00
|
|
|
if (keyEvent.modifiers() == Qt::NoModifier) {
|
|
|
|
on_actionPlay_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
2021-03-11 08:22:15 +00:00
|
|
|
case Qt::Key_S:
|
|
|
|
on_actionStop_triggered();
|
|
|
|
return true;
|
2021-04-14 13:47:07 +00:00
|
|
|
case Qt::Key_N:
|
|
|
|
if (keyEvent.modifiers() == Qt::ShiftModifier) {
|
|
|
|
// Shift+N
|
|
|
|
on_actionDeselectInaudible_triggered();
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
on_actionSelectInaudible_triggered();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
return false;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-05 09:19:48 +00:00
|
|
|
void RtpPlayerDialog::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
{
|
2022-07-03 11:35:48 +00:00
|
|
|
list_ctx_menu_->popup(event->globalPos());
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::updateWidgets()
|
|
|
|
{
|
|
|
|
bool enable_play = true;
|
2020-12-29 13:41:14 +00:00
|
|
|
bool enable_pause = false;
|
2014-12-13 00:51:40 +00:00
|
|
|
bool enable_stop = false;
|
2015-10-26 15:34:58 +00:00
|
|
|
bool enable_timing = true;
|
2021-04-14 13:47:07 +00:00
|
|
|
int count = ui->streamTreeWidget->topLevelItemCount();
|
2022-05-24 01:59:35 +00:00
|
|
|
qsizetype selected = ui->streamTreeWidget->selectedItems().count();
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-05-01 11:51:31 +00:00
|
|
|
if (count < 1) {
|
2021-02-04 14:03:45 +00:00
|
|
|
enable_play = false;
|
2021-05-01 11:51:31 +00:00
|
|
|
ui->skipSilenceButton->setEnabled(false);
|
|
|
|
ui->minSilenceSpinBox->setEnabled(false);
|
|
|
|
} else {
|
|
|
|
ui->skipSilenceButton->setEnabled(true);
|
|
|
|
ui->minSilenceSpinBox->setEnabled(true);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
|
2021-02-05 09:19:48 +00:00
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
2014-12-13 00:51:40 +00:00
|
|
|
if (audio_stream->outputState() != QAudio::IdleState) {
|
|
|
|
enable_play = false;
|
2020-12-29 13:41:14 +00:00
|
|
|
enable_pause = true;
|
2014-12-13 00:51:40 +00:00
|
|
|
enable_stop = true;
|
2015-10-26 15:34:58 +00:00
|
|
|
enable_timing = false;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->actionAudioRoutingP->setVisible(!stereo_available_);
|
|
|
|
ui->actionAudioRoutingL->setVisible(stereo_available_);
|
|
|
|
ui->actionAudioRoutingLR->setVisible(stereo_available_);
|
|
|
|
ui->actionAudioRoutingR->setVisible(stereo_available_);
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->playButton->setEnabled(enable_play);
|
2020-12-29 13:41:14 +00:00
|
|
|
if (enable_play) {
|
|
|
|
ui->playButton->setVisible(true);
|
|
|
|
ui->pauseButton->setVisible(false);
|
|
|
|
} else if (enable_pause) {
|
|
|
|
ui->playButton->setVisible(false);
|
|
|
|
ui->pauseButton->setVisible(true);
|
|
|
|
}
|
2016-12-02 23:52:02 +00:00
|
|
|
ui->outputDeviceComboBox->setEnabled(enable_play);
|
2021-03-29 09:37:21 +00:00
|
|
|
ui->outputAudioRate->setEnabled(enable_play);
|
2020-12-29 13:41:14 +00:00
|
|
|
ui->pauseButton->setEnabled(enable_pause);
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->stopButton->setEnabled(enable_stop);
|
2021-03-11 08:22:15 +00:00
|
|
|
ui->actionStop->setEnabled(enable_stop);
|
2014-12-13 00:51:40 +00:00
|
|
|
cur_play_pos_->setVisible(enable_stop);
|
2015-10-26 15:34:58 +00:00
|
|
|
|
|
|
|
ui->jitterSpinBox->setEnabled(enable_timing);
|
|
|
|
ui->timingComboBox->setEnabled(enable_timing);
|
|
|
|
ui->todCheckBox->setEnabled(enable_timing);
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
read_btn_->setEnabled(read_capture_enabled_);
|
2021-04-14 13:47:07 +00:00
|
|
|
inaudible_btn_->setEnabled(count > 0);
|
|
|
|
analyze_btn_->setEnabled(selected > 0);
|
|
|
|
prepare_btn_->setEnabled(selected > 0);
|
|
|
|
|
2016-12-02 23:52:02 +00:00
|
|
|
updateHintLabel();
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->audioPlot->replot();
|
|
|
|
}
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
void RtpPlayerDialog::handleItemHighlight(QTreeWidgetItem *ti, bool scroll)
|
|
|
|
{
|
|
|
|
if (ti) {
|
|
|
|
if (ti != last_ti_) {
|
|
|
|
if (last_ti_) {
|
|
|
|
highlightItem(last_ti_, false);
|
|
|
|
}
|
|
|
|
highlightItem(ti, true);
|
|
|
|
|
|
|
|
if (scroll)
|
|
|
|
ui->streamTreeWidget->scrollToItem(ti, QAbstractItemView::EnsureVisible);
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
last_ti_ = ti;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (last_ti_) {
|
|
|
|
highlightItem(last_ti_, false);
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
last_ti_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::highlightItem(QTreeWidgetItem *ti, bool highlight)
|
|
|
|
{
|
|
|
|
QFont font;
|
|
|
|
RtpAudioGraph *audio_graph;
|
|
|
|
|
|
|
|
font.setBold(highlight);
|
|
|
|
for(int i=0; i<ui->streamTreeWidget->columnCount(); i++) {
|
|
|
|
ti->setFont(i, font);
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
|
|
|
|
if (audio_graph) {
|
|
|
|
audio_graph->setHighlight(highlight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::itemEntered(QTreeWidgetItem *item, int column _U_)
|
|
|
|
{
|
|
|
|
handleItemHighlight(item, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::mouseMovePlot(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
updateHintLabel();
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = findItemByCoords(event->pos());
|
|
|
|
handleItemHighlight(ti, true);
|
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::graphClicked(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
updateWidgets();
|
|
|
|
if (event->button() == Qt::RightButton) {
|
2022-03-22 21:47:18 +00:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0 ,0)
|
2022-07-03 11:35:48 +00:00
|
|
|
graph_ctx_menu_->popup(event->globalPosition().toPoint());
|
2022-03-22 21:47:18 +00:00
|
|
|
#else
|
2022-07-03 11:35:48 +00:00
|
|
|
graph_ctx_menu_->popup(event->globalPos());
|
2022-03-22 21:47:18 +00:00
|
|
|
#endif
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
void RtpPlayerDialog::graphDoubleClicked(QMouseEvent *event)
|
|
|
|
{
|
|
|
|
updateWidgets();
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
|
|
|
// Move start play line
|
2021-03-09 00:58:15 +00:00
|
|
|
double ts = ui->audioPlot->xAxis->pixelToCoord(event->pos().x());
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
setStartPlayMarker(ts);
|
|
|
|
drawStartPlayMarker();
|
|
|
|
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
void RtpPlayerDialog::plotClicked(QCPAbstractPlottable *plottable _U_, int dataIndex _U_, QMouseEvent *event)
|
|
|
|
{
|
|
|
|
// Delivered plottable very often points to different element than a mouse
|
|
|
|
// so we find right one by mouse coordinates
|
|
|
|
QTreeWidgetItem *ti = findItemByCoords(event->pos());
|
|
|
|
if (ti) {
|
|
|
|
if (event->modifiers() == Qt::NoModifier) {
|
|
|
|
ti->setSelected(true);
|
|
|
|
} else if (event->modifiers() == Qt::ControlModifier) {
|
|
|
|
ti->setSelected(!ti->isSelected());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QTreeWidgetItem *RtpPlayerDialog::findItemByCoords(QPoint point)
|
|
|
|
{
|
|
|
|
QCPAbstractPlottable *plottable=ui->audioPlot->plottableAt(point);
|
|
|
|
if (plottable) {
|
|
|
|
return findItem(plottable);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTreeWidgetItem *RtpPlayerDialog::findItem(QCPAbstractPlottable *plottable)
|
2021-02-04 13:23:43 +00:00
|
|
|
{
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
2021-03-09 00:58:15 +00:00
|
|
|
RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
|
2021-04-22 19:33:11 +00:00
|
|
|
if (audio_graph && audio_graph->isMyPlottable(plottable)) {
|
2021-03-09 00:58:15 +00:00
|
|
|
return ti;
|
2021-02-04 13:23:43 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-09 00:58:15 +00:00
|
|
|
|
|
|
|
return NULL;
|
2021-02-04 13:23:43 +00:00
|
|
|
}
|
|
|
|
|
2016-12-02 23:52:02 +00:00
|
|
|
void RtpPlayerDialog::updateHintLabel()
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
|
|
|
int packet_num = getHoveredPacket();
|
|
|
|
QString hint = "<small><i>";
|
2020-01-01 23:38:35 +00:00
|
|
|
double start_pos = getStartPlayMarker();
|
2021-04-13 11:42:57 +00:00
|
|
|
int row_count = ui->streamTreeWidget->topLevelItemCount();
|
2022-05-24 01:59:35 +00:00
|
|
|
qsizetype selected = ui->streamTreeWidget->selectedItems().count();
|
2021-04-13 11:42:57 +00:00
|
|
|
int not_muted = 0;
|
|
|
|
|
|
|
|
hint += tr("%1 streams").arg(row_count);
|
|
|
|
|
|
|
|
if (row_count > 0) {
|
|
|
|
if (selected > 0) {
|
|
|
|
hint += tr(", %1 selected").arg(selected);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int row = 0; row < row_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream && (!audio_stream->getAudioRouting().isMuted())) {
|
|
|
|
not_muted++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hint += tr(", %1 not muted").arg(not_muted);
|
|
|
|
}
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
if (packet_num == 0) {
|
2021-04-13 11:42:57 +00:00
|
|
|
hint += tr(", start: %1. Double click on graph to set start of playback.")
|
2020-01-01 23:38:35 +00:00
|
|
|
.arg(getFormatedTime(start_pos));
|
|
|
|
} else if (packet_num > 0) {
|
2021-04-13 11:42:57 +00:00
|
|
|
hint += tr(", start: %1, cursor: %2. Press \"G\" to go to packet %3. Double click on graph to set start of playback.")
|
2020-01-01 23:38:35 +00:00
|
|
|
.arg(getFormatedTime(start_pos))
|
|
|
|
.arg(getFormatedHoveredTime())
|
2014-12-13 00:51:40 +00:00
|
|
|
.arg(packet_num);
|
2021-04-13 11:42:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!playback_error_.isEmpty()) {
|
|
|
|
hint += " <font color=\"red\">";
|
2016-12-02 23:52:02 +00:00
|
|
|
hint += playback_error_;
|
2021-04-13 11:42:57 +00:00
|
|
|
hint += " </font>";
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hint += "</i></small>";
|
|
|
|
ui->hintLabel->setText(hint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::resetXAxis()
|
|
|
|
{
|
|
|
|
QCustomPlot *ap = ui->audioPlot;
|
|
|
|
|
|
|
|
double pixel_pad = 10.0; // per side
|
|
|
|
|
|
|
|
ap->rescaleAxes(true);
|
|
|
|
|
|
|
|
double axis_pixels = ap->xAxis->axisRect()->width();
|
2021-01-02 12:45:43 +00:00
|
|
|
ap->xAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->xAxis->range().center());
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
axis_pixels = ap->yAxis->axisRect()->height();
|
|
|
|
ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
|
|
|
|
|
|
|
|
ap->replot();
|
|
|
|
}
|
|
|
|
|
2021-03-23 08:48:20 +00:00
|
|
|
void RtpPlayerDialog::updateGraphs()
|
|
|
|
{
|
|
|
|
QCustomPlot *ap = ui->audioPlot;
|
|
|
|
|
|
|
|
// Create new plots, just existing ones
|
|
|
|
createPlot(false);
|
|
|
|
|
|
|
|
// Rescale Y axis
|
|
|
|
double pixel_pad = 10.0; // per side
|
|
|
|
double axis_pixels = ap->yAxis->axisRect()->height();
|
|
|
|
ap->yAxis->rescale(true);
|
|
|
|
ap->yAxis->scaleRange((axis_pixels + (pixel_pad * 2)) / axis_pixels, ap->yAxis->range().center());
|
|
|
|
|
|
|
|
ap->replot();
|
|
|
|
}
|
|
|
|
|
2021-04-13 11:42:57 +00:00
|
|
|
void RtpPlayerDialog::playFinished(RtpAudioStream *stream, QAudio::Error error)
|
2021-02-08 21:35:12 +00:00
|
|
|
{
|
2021-04-13 11:42:57 +00:00
|
|
|
if ((error != QAudio::NoError) && (error != QAudio::UnderrunError)) {
|
|
|
|
setPlaybackError(tr("Playback of stream %1 failed!")
|
|
|
|
.arg(stream->getIDAsQString())
|
|
|
|
);
|
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
playing_streams_.removeOne(stream);
|
|
|
|
if (playing_streams_.isEmpty()) {
|
2021-04-08 09:15:08 +00:00
|
|
|
marker_stream_->stop();
|
2021-02-08 21:35:12 +00:00
|
|
|
updateWidgets();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::setPlayPosition(double secs)
|
|
|
|
{
|
|
|
|
double cur_secs = cur_play_pos_->point1->key();
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
secs += first_stream_abs_start_time_;
|
|
|
|
} else {
|
|
|
|
secs += first_stream_rel_start_time_;
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
if (secs > cur_secs) {
|
|
|
|
cur_play_pos_->point1->setCoords(secs, 0.0);
|
|
|
|
cur_play_pos_->point2->setCoords(secs, 1.0);
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
void RtpPlayerDialog::setPlaybackError(const QString playback_error)
|
|
|
|
{
|
|
|
|
playback_error_ = playback_error;
|
|
|
|
updateHintLabel();
|
|
|
|
}
|
|
|
|
|
2022-06-09 15:47:35 +00:00
|
|
|
tap_packet_status RtpPlayerDialog::tapPacket(void *tapinfo_ptr, packet_info *pinfo, epan_dissect_t *, const void *rtpinfo_ptr, tap_flags_t)
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
|
|
|
RtpPlayerDialog *rtp_player_dialog = dynamic_cast<RtpPlayerDialog *>((RtpPlayerDialog*)tapinfo_ptr);
|
2019-01-01 03:36:12 +00:00
|
|
|
if (!rtp_player_dialog) return TAP_PACKET_DONT_REDRAW;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
const struct _rtp_info *rtpinfo = (const struct _rtp_info *)rtpinfo_ptr;
|
2019-01-01 03:36:12 +00:00
|
|
|
if (!rtpinfo) return TAP_PACKET_DONT_REDRAW;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-01-01 16:12:11 +00:00
|
|
|
/* ignore RTP Version != 2 */
|
|
|
|
if (rtpinfo->info_version != 2)
|
2019-01-01 03:36:12 +00:00
|
|
|
return TAP_PACKET_DONT_REDRAW;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
rtp_player_dialog->addPacket(pinfo, rtpinfo);
|
|
|
|
|
2019-01-01 03:36:12 +00:00
|
|
|
return TAP_PACKET_DONT_REDRAW;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::addPacket(packet_info *pinfo, const _rtp_info *rtpinfo)
|
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
// Search stream in hash key, if there are multiple streams with same hash
|
|
|
|
QList<RtpAudioStream *> streams = stream_hash_.values(pinfo_rtp_info_to_hash(pinfo, rtpinfo));
|
|
|
|
for (int i = 0; i < streams.size(); i++) {
|
|
|
|
RtpAudioStream *row_stream = streams.at(i);
|
2014-12-13 00:51:40 +00:00
|
|
|
if (row_stream->isMatch(pinfo, rtpinfo)) {
|
|
|
|
row_stream->addRtpPacket(pinfo, rtpinfo);
|
2021-04-13 14:38:13 +00:00
|
|
|
break;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-13 14:38:13 +00:00
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
// qDebug() << "=ap no match!" << address_to_qstring(&pinfo->src) << address_to_qstring(&pinfo->dst);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::zoomXAxis(bool in)
|
|
|
|
{
|
|
|
|
QCustomPlot *ap = ui->audioPlot;
|
|
|
|
double h_factor = ap->axisRect()->rangeZoomFactor(Qt::Horizontal);
|
|
|
|
|
|
|
|
if (!in) {
|
|
|
|
h_factor = pow(h_factor, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
ap->xAxis->scaleRange(h_factor, ap->xAxis->range().center());
|
|
|
|
ap->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX I tried using seconds but pixels make more sense at varying zoom
|
|
|
|
// levels.
|
|
|
|
void RtpPlayerDialog::panXAxis(int x_pixels)
|
|
|
|
{
|
|
|
|
QCustomPlot *ap = ui->audioPlot;
|
|
|
|
double h_pan;
|
|
|
|
|
|
|
|
h_pan = ap->xAxis->range().size() * x_pixels / ap->xAxis->axisRect()->width();
|
|
|
|
if (x_pixels) {
|
|
|
|
ap->xAxis->moveRange(h_pan);
|
|
|
|
ap->replot();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_playButton_clicked()
|
|
|
|
{
|
2020-01-01 23:38:35 +00:00
|
|
|
double start_time;
|
2022-10-08 09:30:52 +00:00
|
|
|
QList<RtpAudioStream *> streams_to_start;
|
2020-01-01 23:38:35 +00:00
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
ui->hintLabel->setText("<i><small>" + tr("Preparing to play...") + "</i></small>");
|
2022-02-01 03:30:09 +00:00
|
|
|
mainApp->processEvents();
|
2020-12-29 13:41:14 +00:00
|
|
|
ui->pauseButton->setChecked(false);
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Protect start time against move of marker during the play
|
|
|
|
start_marker_time_play_ = start_marker_time_;
|
2021-05-01 11:51:31 +00:00
|
|
|
silence_skipped_time_ = 0.0;
|
2021-02-08 21:35:12 +00:00
|
|
|
cur_play_pos_->point1->setCoords(start_marker_time_play_, 0.0);
|
|
|
|
cur_play_pos_->point2->setCoords(start_marker_time_play_, 1.0);
|
2014-12-13 00:51:40 +00:00
|
|
|
cur_play_pos_->setVisible(true);
|
2016-12-02 23:52:02 +00:00
|
|
|
playback_error_.clear();
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
2021-02-08 21:35:12 +00:00
|
|
|
start_time = start_marker_time_play_;
|
2020-01-01 23:38:35 +00:00
|
|
|
} else {
|
2021-02-08 21:35:12 +00:00
|
|
|
start_time = start_marker_time_play_ - first_stream_rel_start_time_;
|
2020-01-01 23:38:35 +00:00
|
|
|
}
|
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
QAudioDevice cur_out_device = getCurrentDeviceInfo();
|
|
|
|
#else
|
2021-02-08 21:35:12 +00:00
|
|
|
QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2021-02-08 21:35:12 +00:00
|
|
|
playing_streams_.clear();
|
2020-01-01 23:38:35 +00:00
|
|
|
int row_count = ui->streamTreeWidget->topLevelItemCount();
|
|
|
|
for (int row = 0; row < row_count; row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
// All streams starts at first_stream_rel_start_time_
|
|
|
|
audio_stream->setStartPlayTime(start_time);
|
2021-02-08 21:35:12 +00:00
|
|
|
if (audio_stream->prepareForPlay(cur_out_device)) {
|
|
|
|
playing_streams_ << audio_stream;
|
|
|
|
}
|
2020-01-01 23:38:35 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Prepare silent stream for progress marker
|
|
|
|
if (!marker_stream_) {
|
|
|
|
marker_stream_ = getSilenceAudioOutput();
|
|
|
|
} else {
|
|
|
|
marker_stream_->stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start progress marker and then audio streams
|
2022-11-24 15:32:36 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
notify_timer_start_diff_ = -1;
|
|
|
|
#endif
|
2023-05-15 11:07:05 +00:00
|
|
|
marker_stream_->start(new AudioSilenceGenerator(marker_stream_));
|
2022-10-08 09:30:52 +00:00
|
|
|
// It may happen that stream play is finished before all others are started
|
|
|
|
// therefore we do not use playing_streams_ there, but separate temporarly
|
|
|
|
// list. It avoids access element/remove element race condition.
|
|
|
|
streams_to_start = playing_streams_;
|
|
|
|
for( int i = 0; i<streams_to_start.count(); ++i ) {
|
|
|
|
streams_to_start[i]->startPlaying();
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateWidgets();
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
QAudioDevice RtpPlayerDialog::getCurrentDeviceInfo()
|
|
|
|
{
|
|
|
|
QAudioDevice cur_out_device = QMediaDevices::defaultAudioOutput();
|
|
|
|
QString cur_out_name = currentOutputDeviceName();
|
|
|
|
foreach (QAudioDevice out_device, QMediaDevices::audioOutputs()) {
|
|
|
|
if (cur_out_name == out_device.description()) {
|
|
|
|
cur_out_device = out_device;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cur_out_device;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::sinkStateChanged()
|
|
|
|
{
|
|
|
|
if (marker_stream_->state() == QAudio::ActiveState) {
|
|
|
|
notify_timer_.start();
|
|
|
|
} else {
|
|
|
|
notify_timer_.stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2021-02-08 21:35:12 +00:00
|
|
|
QAudioDeviceInfo RtpPlayerDialog::getCurrentDeviceInfo()
|
|
|
|
{
|
|
|
|
QAudioDeviceInfo cur_out_device = QAudioDeviceInfo::defaultOutputDevice();
|
|
|
|
QString cur_out_name = currentOutputDeviceName();
|
|
|
|
foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
|
|
|
|
if (cur_out_name == out_device.deviceName()) {
|
|
|
|
cur_out_device = out_device;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cur_out_device;
|
|
|
|
}
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
QAudioSink *RtpPlayerDialog::getSilenceAudioOutput()
|
|
|
|
{
|
|
|
|
QAudioDevice cur_out_device = getCurrentDeviceInfo();
|
|
|
|
|
|
|
|
QAudioFormat format;
|
|
|
|
if (marker_stream_requested_out_rate_ > 0) {
|
|
|
|
format.setSampleRate(marker_stream_requested_out_rate_);
|
|
|
|
} else {
|
|
|
|
format.setSampleRate(8000);
|
|
|
|
}
|
|
|
|
// Must match rtp_media.h.
|
|
|
|
format.setSampleFormat(QAudioFormat::Int16);
|
|
|
|
format.setChannelCount(1);
|
|
|
|
if (!cur_out_device.isFormatSupported(format)) {
|
|
|
|
format = cur_out_device.preferredFormat();
|
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
QAudioSink *sink = new QAudioSink(cur_out_device, format, this);
|
|
|
|
connect(sink, &QAudioSink::stateChanged, this, &RtpPlayerDialog::sinkStateChanged);
|
|
|
|
return sink;
|
|
|
|
}
|
|
|
|
#else
|
2021-02-08 21:35:12 +00:00
|
|
|
QAudioOutput *RtpPlayerDialog::getSilenceAudioOutput()
|
|
|
|
{
|
|
|
|
QAudioOutput *o;
|
|
|
|
QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
|
|
|
|
|
|
|
|
QAudioFormat format;
|
2021-04-08 09:15:08 +00:00
|
|
|
if (marker_stream_requested_out_rate_ > 0) {
|
|
|
|
format.setSampleRate(marker_stream_requested_out_rate_);
|
2021-02-08 21:35:12 +00:00
|
|
|
} else {
|
2021-04-08 09:15:08 +00:00
|
|
|
format.setSampleRate(8000);
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
2021-04-08 09:15:08 +00:00
|
|
|
format.setSampleSize(SAMPLE_BYTES * 8); // bits
|
|
|
|
format.setSampleType(QAudioFormat::SignedInt);
|
|
|
|
format.setChannelCount(1);
|
2021-02-08 21:35:12 +00:00
|
|
|
format.setCodec("audio/pcm");
|
2021-04-08 09:15:08 +00:00
|
|
|
if (!cur_out_device.isFormatSupported(format)) {
|
|
|
|
format = cur_out_device.nearestFormat(format);
|
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
|
|
|
|
o = new QAudioOutput(cur_out_device, format, this);
|
|
|
|
o->setNotifyInterval(100); // ~15 fps
|
2022-05-24 01:59:35 +00:00
|
|
|
connect(o, &QAudioOutput::notify, this, &RtpPlayerDialog::outputNotify);
|
2021-02-08 21:35:12 +00:00
|
|
|
|
|
|
|
return o;
|
|
|
|
}
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2021-02-08 21:35:12 +00:00
|
|
|
|
|
|
|
void RtpPlayerDialog::outputNotify()
|
|
|
|
{
|
2021-05-01 11:51:31 +00:00
|
|
|
double new_current_pos = 0.0;
|
|
|
|
double current_pos = 0.0;
|
2022-07-08 13:10:21 +00:00
|
|
|
qint64 usecs = marker_stream_->processedUSecs();
|
2021-05-01 11:51:31 +00:00
|
|
|
|
2022-07-08 13:10:21 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
// First notify can show end of buffer, not play point so we have
|
|
|
|
// remember the shift
|
2023-04-06 05:59:52 +00:00
|
|
|
if ( -1 == notify_timer_start_diff_ || 0 == notify_timer_start_diff_) {
|
2022-07-08 13:10:21 +00:00
|
|
|
notify_timer_start_diff_ = usecs;
|
|
|
|
}
|
|
|
|
usecs -= notify_timer_start_diff_;
|
|
|
|
#endif
|
|
|
|
double secs = usecs / 1000000.0;
|
2021-05-01 11:51:31 +00:00
|
|
|
|
|
|
|
if (ui->skipSilenceButton->isChecked()) {
|
|
|
|
// We should check whether we can skip some silence
|
|
|
|
// We must calculate in time domain as every stream can use different
|
|
|
|
// play rate
|
|
|
|
double min_silence = playing_streams_[0]->getEndOfSilenceTime();
|
|
|
|
for( int i = 1; i<playing_streams_.count(); ++i ) {
|
|
|
|
qint64 cur_silence = playing_streams_[i]->getEndOfSilenceTime();
|
|
|
|
if (cur_silence < min_silence) {
|
|
|
|
min_silence = cur_silence;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (min_silence > 0.0) {
|
|
|
|
double silence_duration;
|
|
|
|
|
|
|
|
// Calculate silence duration we can skip
|
|
|
|
new_current_pos = first_stream_rel_start_time_ + min_silence;
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
current_pos = secs + start_marker_time_play_ + first_stream_rel_start_time_;
|
|
|
|
} else {
|
|
|
|
current_pos = secs + start_marker_time_play_;
|
|
|
|
}
|
|
|
|
silence_duration = new_current_pos - current_pos;
|
|
|
|
|
|
|
|
if (silence_duration >= ui->minSilenceSpinBox->value()) {
|
|
|
|
// Skip silence gap and update cursor difference
|
|
|
|
for( int i = 0; i<playing_streams_.count(); ++i ) {
|
|
|
|
// Convert silence from time domain to samples
|
|
|
|
qint64 skip_samples = playing_streams_[i]->convertTimeToSamples(min_silence);
|
|
|
|
playing_streams_[i]->seekPlaying(skip_samples);
|
|
|
|
}
|
|
|
|
silence_skipped_time_ = silence_duration;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate new cursor position
|
2021-02-08 21:35:12 +00:00
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
secs += start_marker_time_play_;
|
2021-05-01 11:51:31 +00:00
|
|
|
secs += silence_skipped_time_;
|
2021-02-08 21:35:12 +00:00
|
|
|
} else {
|
|
|
|
secs += start_marker_time_play_;
|
|
|
|
secs -= first_stream_rel_start_time_;
|
2021-05-01 11:51:31 +00:00
|
|
|
secs += silence_skipped_time_;
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
|
|
|
setPlayPosition(secs);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_pauseButton_clicked()
|
|
|
|
{
|
|
|
|
for( int i = 0; i<playing_streams_.count(); ++i ) {
|
|
|
|
playing_streams_[i]->pausePlaying();
|
|
|
|
}
|
|
|
|
if (ui->pauseButton->isChecked()) {
|
|
|
|
marker_stream_->suspend();
|
|
|
|
} else {
|
|
|
|
marker_stream_->resume();
|
|
|
|
}
|
|
|
|
updateWidgets();
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_stopButton_clicked()
|
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
// We need copy of list because items will be removed during stopPlaying()
|
|
|
|
QList<RtpAudioStream *> ps=QList<RtpAudioStream *>(playing_streams_);
|
|
|
|
for( int i = 0; i<ps.count(); ++i ) {
|
|
|
|
ps[i]->stopPlaying();
|
|
|
|
}
|
|
|
|
marker_stream_->stop();
|
2014-12-13 00:51:40 +00:00
|
|
|
cur_play_pos_->setVisible(false);
|
2021-02-08 21:35:12 +00:00
|
|
|
updateWidgets();
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionReset_triggered()
|
|
|
|
{
|
|
|
|
resetXAxis();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionZoomIn_triggered()
|
|
|
|
{
|
|
|
|
zoomXAxis(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionZoomOut_triggered()
|
|
|
|
{
|
|
|
|
zoomXAxis(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionMoveLeft10_triggered()
|
|
|
|
{
|
|
|
|
panXAxis(-10);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionMoveRight10_triggered()
|
|
|
|
{
|
|
|
|
panXAxis(10);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionMoveLeft1_triggered()
|
|
|
|
{
|
|
|
|
panXAxis(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionMoveRight1_triggered()
|
|
|
|
{
|
|
|
|
panXAxis(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionGoToPacket_triggered()
|
|
|
|
{
|
|
|
|
int packet_num = getHoveredPacket();
|
|
|
|
if (packet_num > 0) emit goToPacket(packet_num);
|
|
|
|
}
|
|
|
|
|
2021-03-09 13:47:07 +00:00
|
|
|
void RtpPlayerDialog::handleGoToSetupPacket(QTreeWidgetItem *ti)
|
|
|
|
{
|
|
|
|
if (ti) {
|
|
|
|
bool ok;
|
|
|
|
|
|
|
|
int packet_num = ti->data(first_pkt_col_, Qt::UserRole).toInt(&ok);
|
|
|
|
if (ok) {
|
|
|
|
emit goToPacket(packet_num);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionGoToSetupPacketPlot_triggered()
|
|
|
|
{
|
|
|
|
QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
|
|
|
|
handleGoToSetupPacket(findItemByCoords(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionGoToSetupPacketTree_triggered()
|
|
|
|
{
|
|
|
|
handleGoToSetupPacket(last_ti_);
|
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Make waveform graphs selectable and update the treewidget selection accordingly.
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::on_streamTreeWidget_itemSelectionChanged()
|
|
|
|
{
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
2021-03-09 00:58:15 +00:00
|
|
|
RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
|
2014-12-13 00:51:40 +00:00
|
|
|
if (audio_graph) {
|
2021-03-09 00:58:15 +00:00
|
|
|
audio_graph->setSelected(ti->isSelected());
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-06 11:23:00 +00:00
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
qsizetype selected = ui->streamTreeWidget->selectedItems().count();
|
2021-04-06 11:23:00 +00:00
|
|
|
if (selected == 0) {
|
2021-04-14 13:47:07 +00:00
|
|
|
analyze_btn_->setEnabled(false);
|
|
|
|
prepare_btn_->setEnabled(false);
|
2021-04-06 11:23:00 +00:00
|
|
|
export_btn_->setEnabled(false);
|
|
|
|
} else if (selected == 1) {
|
2021-04-14 13:47:07 +00:00
|
|
|
analyze_btn_->setEnabled(true);
|
|
|
|
prepare_btn_->setEnabled(true);
|
2021-04-06 11:23:00 +00:00
|
|
|
export_btn_->setEnabled(true);
|
|
|
|
ui->actionSavePayload->setEnabled(true);
|
|
|
|
} else {
|
2021-04-14 13:47:07 +00:00
|
|
|
analyze_btn_->setEnabled(true);
|
|
|
|
prepare_btn_->setEnabled(true);
|
2021-04-06 11:23:00 +00:00
|
|
|
export_btn_->setEnabled(true);
|
|
|
|
ui->actionSavePayload->setEnabled(false);
|
|
|
|
}
|
|
|
|
|
2021-04-13 14:38:13 +00:00
|
|
|
if (!block_redraw_) {
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
updateHintLabel();
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Change channel audio routing if double clicked channel column
|
2020-01-07 21:16:42 +00:00
|
|
|
void RtpPlayerDialog::on_streamTreeWidget_itemDoubleClicked(QTreeWidgetItem *item, const int column)
|
|
|
|
{
|
|
|
|
if (column == channel_col_) {
|
2021-02-08 21:35:12 +00:00
|
|
|
RtpAudioStream *audio_stream = item->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (!audio_stream)
|
|
|
|
return;
|
|
|
|
|
|
|
|
AudioRouting audio_routing = audio_stream->getAudioRouting();
|
|
|
|
audio_routing = audio_routing.getNextChannel(stereo_available_);
|
|
|
|
changeAudioRoutingOnItem(item, audio_routing);
|
2020-01-07 21:16:42 +00:00
|
|
|
}
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2020-01-07 21:16:42 +00:00
|
|
|
}
|
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
void RtpPlayerDialog::removeRow(QTreeWidgetItem *ti)
|
2021-02-04 14:03:45 +00:00
|
|
|
{
|
2021-04-22 19:33:11 +00:00
|
|
|
if (last_ti_ && (last_ti_ == ti)) {
|
|
|
|
highlightItem(last_ti_, false);
|
|
|
|
last_ti_ = NULL;
|
|
|
|
}
|
2021-03-27 20:01:43 +00:00
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream) {
|
2021-04-13 14:38:13 +00:00
|
|
|
stream_hash_.remove(audio_stream->getHash(), audio_stream);
|
2021-03-27 20:01:43 +00:00
|
|
|
ti->setData(stream_data_col_, Qt::UserRole, QVariant());
|
|
|
|
delete audio_stream;
|
2021-02-04 14:03:45 +00:00
|
|
|
}
|
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
|
|
|
|
if (audio_graph) {
|
|
|
|
ti->setData(graph_audio_data_col_, Qt::UserRole, QVariant());
|
|
|
|
audio_graph->remove(ui->audioPlot);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
QCPGraph *graph;
|
|
|
|
graph = ti->data(graph_sequence_data_col_, Qt::UserRole).value<QCPGraph*>();
|
|
|
|
if (graph) {
|
|
|
|
ti->setData(graph_sequence_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ui->audioPlot->removeGraph(graph);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
graph = ti->data(graph_jitter_data_col_, Qt::UserRole).value<QCPGraph*>();
|
|
|
|
if (graph) {
|
|
|
|
ti->setData(graph_jitter_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ui->audioPlot->removeGraph(graph);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
graph = ti->data(graph_timestamp_data_col_, Qt::UserRole).value<QCPGraph*>();
|
|
|
|
if (graph) {
|
|
|
|
ti->setData(graph_timestamp_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ui->audioPlot->removeGraph(graph);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
graph = ti->data(graph_silence_data_col_, Qt::UserRole).value<QCPGraph*>();
|
|
|
|
if (graph) {
|
|
|
|
ti->setData(graph_silence_data_col_, Qt::UserRole, QVariant());
|
|
|
|
ui->audioPlot->removeGraph(graph);
|
|
|
|
}
|
2021-02-04 14:03:45 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
delete ti;
|
|
|
|
}
|
2021-03-09 00:58:15 +00:00
|
|
|
|
2021-03-27 20:01:43 +00:00
|
|
|
void RtpPlayerDialog::on_actionRemoveStream_triggered()
|
|
|
|
{
|
2021-04-15 21:54:06 +00:00
|
|
|
lockUI();
|
2021-03-27 20:01:43 +00:00
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
|
2021-03-09 00:58:15 +00:00
|
|
|
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = true;
|
2022-05-24 01:59:35 +00:00
|
|
|
for(int i = static_cast<int>(items.count()) - 1; i>=0; i-- ) {
|
2021-03-27 20:01:43 +00:00
|
|
|
removeRow(items[i]);
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = false;
|
2021-03-23 08:48:20 +00:00
|
|
|
// TODO: Recalculate legend
|
|
|
|
// - Graphs used for legend could be removed above and we must add new
|
|
|
|
// - If no legend is required, it should be removed
|
|
|
|
|
|
|
|
// Redraw existing waveforms and rescale Y axis
|
|
|
|
updateGraphs();
|
2021-02-04 14:03:45 +00:00
|
|
|
|
|
|
|
updateWidgets();
|
2021-04-15 21:54:06 +00:00
|
|
|
unlockUI();
|
2021-02-04 14:03:45 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// If called with channel_any, just muted flag should be changed
|
|
|
|
void RtpPlayerDialog::changeAudioRoutingOnItem(QTreeWidgetItem *ti, AudioRouting new_audio_routing)
|
|
|
|
{
|
|
|
|
if (!ti)
|
|
|
|
return;
|
|
|
|
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (!audio_stream)
|
|
|
|
return;
|
|
|
|
|
|
|
|
AudioRouting audio_routing = audio_stream->getAudioRouting();
|
|
|
|
audio_routing.mergeAudioRouting(new_audio_routing);
|
|
|
|
formatAudioRouting(ti, audio_routing);
|
|
|
|
|
|
|
|
audio_stream->setAudioRouting(audio_routing);
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
RtpAudioGraph *audio_graph = ti->data(graph_audio_data_col_, Qt::UserRole).value<RtpAudioGraph*>();
|
2021-02-08 21:35:12 +00:00
|
|
|
if (audio_graph) {
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
audio_graph->setSelected(ti->isSelected());
|
|
|
|
audio_graph->setMuted(audio_routing.isMuted());
|
2021-04-13 14:38:13 +00:00
|
|
|
if (!block_redraw_) {
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
}
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find current item and apply change on it
|
|
|
|
void RtpPlayerDialog::changeAudioRouting(AudioRouting new_audio_routing)
|
2021-02-05 09:19:48 +00:00
|
|
|
{
|
2021-04-15 21:54:06 +00:00
|
|
|
lockUI();
|
2021-03-09 00:58:15 +00:00
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
|
|
|
|
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = true;
|
2021-03-09 00:58:15 +00:00
|
|
|
for(int i = 0; i<items.count(); i++ ) {
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = items[i];
|
|
|
|
changeAudioRoutingOnItem(ti, new_audio_routing);
|
|
|
|
}
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = false;
|
|
|
|
ui->audioPlot->replot();
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2021-04-15 21:54:06 +00:00
|
|
|
unlockUI();
|
2021-02-08 21:35:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Invert mute/unmute on item
|
|
|
|
void RtpPlayerDialog::invertAudioMutingOnItem(QTreeWidgetItem *ti)
|
|
|
|
{
|
2021-02-05 09:19:48 +00:00
|
|
|
if (!ti)
|
|
|
|
return;
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (!audio_stream)
|
|
|
|
return;
|
|
|
|
|
|
|
|
AudioRouting audio_routing = audio_stream->getAudioRouting();
|
|
|
|
// Invert muting
|
|
|
|
if (audio_routing.isMuted()) {
|
|
|
|
changeAudioRoutingOnItem(ti, AudioRouting(AUDIO_UNMUTED, channel_any));
|
2021-02-05 09:19:48 +00:00
|
|
|
} else {
|
2021-02-08 21:35:12 +00:00
|
|
|
changeAudioRoutingOnItem(ti, AudioRouting(AUDIO_MUTED, channel_any));
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingP_triggered()
|
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_mono));
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingL_triggered()
|
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_left));
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingLR_triggered()
|
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_both));
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingR_triggered()
|
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_stereo_right));
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 08:22:15 +00:00
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingMute_triggered()
|
|
|
|
{
|
|
|
|
changeAudioRouting(AudioRouting(AUDIO_MUTED, channel_any));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingUnmute_triggered()
|
|
|
|
{
|
|
|
|
changeAudioRouting(AudioRouting(AUDIO_UNMUTED, channel_any));
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionAudioRoutingMuteInvert_triggered()
|
2021-02-05 09:19:48 +00:00
|
|
|
{
|
2021-04-15 21:54:06 +00:00
|
|
|
lockUI();
|
2021-03-09 00:58:15 +00:00
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
|
|
|
|
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = true;
|
2021-03-09 00:58:15 +00:00
|
|
|
for(int i = 0; i<items.count(); i++ ) {
|
|
|
|
|
|
|
|
QTreeWidgetItem *ti = items[i];
|
2021-02-08 21:35:12 +00:00
|
|
|
invertAudioMutingOnItem(ti);
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = false;
|
|
|
|
ui->audioPlot->replot();
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2021-04-15 21:54:06 +00:00
|
|
|
unlockUI();
|
2021-02-05 09:19:48 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 21:16:42 +00:00
|
|
|
const QString RtpPlayerDialog::getFormatedTime(double f_time)
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2020-01-01 23:38:35 +00:00
|
|
|
QString time_str;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
if (ui->todCheckBox->isChecked()) {
|
2020-01-07 21:16:42 +00:00
|
|
|
QDateTime date_time = QDateTime::fromMSecsSinceEpoch(f_time * 1000.0);
|
2020-01-01 23:38:35 +00:00
|
|
|
time_str = date_time.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
|
|
} else {
|
2020-12-30 12:28:31 +00:00
|
|
|
time_str = QString::number(f_time, 'f', 6);
|
2020-01-01 23:38:35 +00:00
|
|
|
time_str += " s";
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
2020-01-01 23:38:35 +00:00
|
|
|
|
|
|
|
return time_str;
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
const QString RtpPlayerDialog::getFormatedHoveredTime()
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2021-03-09 00:58:15 +00:00
|
|
|
QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
|
|
|
|
QTreeWidgetItem *ti = findItemByCoords(pos);
|
2014-12-13 00:51:40 +00:00
|
|
|
if (!ti) return tr("Unknown");
|
|
|
|
|
2021-03-27 08:41:58 +00:00
|
|
|
double ts = ui->audioPlot->xAxis->pixelToCoord(pos.x());
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
return getFormatedTime(ts);
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int RtpPlayerDialog::getHoveredPacket()
|
|
|
|
{
|
2021-03-09 00:58:15 +00:00
|
|
|
QPoint pos = ui->audioPlot->mapFromGlobal(QCursor::pos());
|
|
|
|
QTreeWidgetItem *ti = findItemByCoords(pos);
|
2014-12-13 00:51:40 +00:00
|
|
|
if (!ti) return 0;
|
|
|
|
|
2021-02-04 13:12:44 +00:00
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-03-27 08:41:58 +00:00
|
|
|
double ts = ui->audioPlot->xAxis->pixelToCoord(pos.x());
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
|
|
|
|
}
|
|
|
|
|
2016-12-02 23:52:02 +00:00
|
|
|
// Used by RtpAudioStreams to initialize QAudioOutput. We could alternatively
|
|
|
|
// pass the corresponding QAudioDeviceInfo directly.
|
2016-12-07 17:58:28 +00:00
|
|
|
QString RtpPlayerDialog::currentOutputDeviceName()
|
2016-12-02 23:52:02 +00:00
|
|
|
{
|
|
|
|
return ui->outputDeviceComboBox->currentText();
|
|
|
|
}
|
|
|
|
|
2021-03-29 09:37:21 +00:00
|
|
|
void RtpPlayerDialog::fillAudioRateMenu()
|
|
|
|
{
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->outputAudioRate->blockSignals(true);
|
2021-03-29 09:37:21 +00:00
|
|
|
ui->outputAudioRate->clear();
|
|
|
|
ui->outputAudioRate->addItem(tr("Automatic"));
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
// XXX QAudioDevice doesn't provide supportedSampleRates(). Fake it with
|
|
|
|
// what's available.
|
|
|
|
QAudioDevice cur_out_device = getCurrentDeviceInfo();
|
|
|
|
QSet<int>sample_rates;
|
2022-07-10 20:48:47 +00:00
|
|
|
if (!cur_out_device.isNull()) {
|
|
|
|
sample_rates.insert(cur_out_device.preferredFormat().sampleRate());
|
|
|
|
// Add 8000 if supported
|
|
|
|
if ((cur_out_device.minimumSampleRate() <= 8000) &&
|
|
|
|
(8000 <= cur_out_device.maximumSampleRate())
|
|
|
|
) {
|
|
|
|
sample_rates.insert(8000);
|
|
|
|
}
|
|
|
|
// Add 16000 if supported
|
|
|
|
if ((cur_out_device.minimumSampleRate() <= 16000) &&
|
|
|
|
(16000 <= cur_out_device.maximumSampleRate())
|
|
|
|
) {
|
|
|
|
sample_rates.insert(16000);
|
|
|
|
}
|
|
|
|
// Add 44100 if supported
|
|
|
|
if ((cur_out_device.minimumSampleRate() <= 44100) &&
|
|
|
|
(44100 <= cur_out_device.maximumSampleRate())
|
|
|
|
) {
|
|
|
|
sample_rates.insert(44100);
|
|
|
|
}
|
2022-07-08 13:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort values
|
|
|
|
QList<int> sorter = sample_rates.values();
|
|
|
|
std::sort(sorter.begin(), sorter.end());
|
|
|
|
|
|
|
|
// Insert rates to the list
|
|
|
|
for (auto rate : sorter) {
|
2022-05-24 01:59:35 +00:00
|
|
|
ui->outputAudioRate->addItem(QString::number(rate));
|
|
|
|
}
|
|
|
|
#else
|
2022-07-10 20:48:47 +00:00
|
|
|
QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
|
|
|
|
|
|
|
|
if (!cur_out_device.isNull()) {
|
|
|
|
foreach (int rate, cur_out_device.supportedSampleRates()) {
|
|
|
|
ui->outputAudioRate->addItem(QString::number(rate));
|
|
|
|
}
|
2021-03-29 09:37:21 +00:00
|
|
|
}
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->outputAudioRate->blockSignals(false);
|
2021-03-29 09:37:21 +00:00
|
|
|
}
|
|
|
|
|
2021-04-08 09:15:08 +00:00
|
|
|
void RtpPlayerDialog::cleanupMarkerStream()
|
|
|
|
{
|
|
|
|
if (marker_stream_) {
|
|
|
|
marker_stream_->stop();
|
|
|
|
delete marker_stream_;
|
|
|
|
marker_stream_ = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-21 19:17:56 +00:00
|
|
|
void RtpPlayerDialog::on_outputDeviceComboBox_currentTextChanged(const QString &)
|
2017-07-21 18:03:04 +00:00
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
lockUI();
|
2021-02-08 21:35:12 +00:00
|
|
|
stereo_available_ = isStereoAvailable();
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (!audio_stream)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
changeAudioRoutingOnItem(ti, audio_stream->getAudioRouting().convert(stereo_available_));
|
|
|
|
}
|
2021-03-29 09:37:21 +00:00
|
|
|
|
2021-04-08 09:15:08 +00:00
|
|
|
marker_stream_requested_out_rate_ = 0;
|
|
|
|
cleanupMarkerStream();
|
2021-03-29 09:37:21 +00:00
|
|
|
fillAudioRateMenu();
|
|
|
|
rescanPackets();
|
2021-04-13 14:38:13 +00:00
|
|
|
unlockUI();
|
2021-03-29 09:37:21 +00:00
|
|
|
}
|
|
|
|
|
2022-06-21 19:17:56 +00:00
|
|
|
void RtpPlayerDialog::on_outputAudioRate_currentTextChanged(const QString & rate_string)
|
2021-03-29 09:37:21 +00:00
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
lockUI();
|
2021-03-29 09:37:21 +00:00
|
|
|
// Any unconvertable string is converted to 0 => used as Automatic rate
|
|
|
|
unsigned selected_rate = rate_string.toInt();
|
|
|
|
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (!audio_stream)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
audio_stream->setRequestedPlayRate(selected_rate);
|
|
|
|
}
|
2021-04-08 09:15:08 +00:00
|
|
|
marker_stream_requested_out_rate_ = selected_rate;
|
|
|
|
cleanupMarkerStream();
|
2017-07-21 18:03:04 +00:00
|
|
|
rescanPackets();
|
2021-04-13 14:38:13 +00:00
|
|
|
unlockUI();
|
2017-07-21 18:03:04 +00:00
|
|
|
}
|
|
|
|
|
2015-10-26 15:34:58 +00:00
|
|
|
void RtpPlayerDialog::on_jitterSpinBox_valueChanged(double)
|
|
|
|
{
|
|
|
|
rescanPackets();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_timingComboBox_currentIndexChanged(int)
|
|
|
|
{
|
|
|
|
rescanPackets();
|
|
|
|
}
|
|
|
|
|
2014-12-13 00:51:40 +00:00
|
|
|
void RtpPlayerDialog::on_todCheckBox_toggled(bool)
|
|
|
|
{
|
|
|
|
QCPAxis *x_axis = ui->audioPlot->xAxis;
|
2020-01-01 23:38:35 +00:00
|
|
|
double move;
|
2014-12-13 00:51:40 +00:00
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
// Create plot with new tod settings
|
|
|
|
createPlot();
|
|
|
|
|
|
|
|
// Move view to same place as was shown before the change
|
2020-01-01 23:38:35 +00:00
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
// rel -> abs
|
|
|
|
// based on abs time of first sample
|
|
|
|
setStartPlayMarker(first_stream_abs_start_time_ + start_marker_time_ - first_stream_rel_start_time_);
|
|
|
|
move = first_stream_abs_start_time_ - first_stream_rel_start_time_;
|
|
|
|
} else {
|
|
|
|
// abs -> rel
|
|
|
|
// based on 0s
|
|
|
|
setStartPlayMarker(first_stream_rel_start_time_ + start_marker_time_);
|
|
|
|
move = - first_stream_abs_start_time_ + first_stream_rel_start_time_;
|
|
|
|
}
|
|
|
|
x_axis->moveRange(move);
|
|
|
|
drawStartPlayMarker();
|
2014-12-13 00:51:40 +00:00
|
|
|
ui->audioPlot->replot();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_buttonBox_helpRequested()
|
|
|
|
{
|
2022-02-01 03:30:09 +00:00
|
|
|
mainApp->helpTopicAction(HELP_TELEPHONY_RTP_PLAYER_DIALOG);
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2020-01-01 23:38:35 +00:00
|
|
|
double RtpPlayerDialog::getStartPlayMarker()
|
|
|
|
{
|
|
|
|
double start_pos;
|
|
|
|
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
start_pos = start_marker_time_ + first_stream_abs_start_time_;
|
|
|
|
} else {
|
|
|
|
start_pos = start_marker_time_;
|
|
|
|
}
|
|
|
|
|
|
|
|
return start_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::drawStartPlayMarker()
|
|
|
|
{
|
|
|
|
double pos = getStartPlayMarker();
|
|
|
|
|
|
|
|
start_marker_pos_->point1->setCoords(pos, 0.0);
|
|
|
|
start_marker_pos_->point2->setCoords(pos, 1.0);
|
|
|
|
|
|
|
|
updateHintLabel();
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:16:42 +00:00
|
|
|
void RtpPlayerDialog::setStartPlayMarker(double new_time)
|
2020-01-01 23:38:35 +00:00
|
|
|
{
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
2020-01-07 21:16:42 +00:00
|
|
|
new_time = qBound(first_stream_abs_start_time_, new_time, first_stream_abs_start_time_ + streams_length_);
|
2020-01-01 23:38:35 +00:00
|
|
|
// start_play_time is relative, we must calculate it
|
2020-01-07 21:16:42 +00:00
|
|
|
start_marker_time_ = new_time - first_stream_abs_start_time_;
|
2020-01-01 23:38:35 +00:00
|
|
|
} else {
|
2020-01-07 21:16:42 +00:00
|
|
|
new_time = qBound(first_stream_rel_start_time_, new_time, first_stream_rel_start_time_ + streams_length_);
|
|
|
|
start_marker_time_ = new_time;
|
2020-01-01 23:38:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::updateStartStopTime(rtpstream_info_t *rtpstream, bool is_first)
|
2020-01-01 23:38:35 +00:00
|
|
|
{
|
2021-04-22 19:33:11 +00:00
|
|
|
// Calculate start time of first last packet of last stream
|
2020-01-01 23:38:35 +00:00
|
|
|
double stream_rel_start_time = nstime_to_sec(&rtpstream->start_rel_time);
|
|
|
|
double stream_abs_start_time = nstime_to_sec(&rtpstream->start_abs_time);
|
|
|
|
double stream_rel_stop_time = nstime_to_sec(&rtpstream->stop_rel_time);
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
if (is_first) {
|
2020-01-01 23:38:35 +00:00
|
|
|
// Take start/stop time for first stream
|
|
|
|
first_stream_rel_start_time_ = stream_rel_start_time;
|
|
|
|
first_stream_abs_start_time_ = stream_abs_start_time;
|
|
|
|
first_stream_rel_stop_time_ = stream_rel_stop_time;
|
|
|
|
} else {
|
|
|
|
// Calculate min/max for start/stop time for other streams
|
|
|
|
first_stream_rel_start_time_ = qMin(first_stream_rel_start_time_, stream_rel_start_time);
|
|
|
|
first_stream_abs_start_time_ = qMin(first_stream_abs_start_time_, stream_abs_start_time);
|
|
|
|
first_stream_rel_stop_time_ = qMax(first_stream_rel_stop_time_, stream_rel_stop_time);
|
|
|
|
}
|
|
|
|
streams_length_ = first_stream_rel_stop_time_ - first_stream_rel_start_time_;
|
|
|
|
}
|
|
|
|
|
2021-02-08 21:35:12 +00:00
|
|
|
void RtpPlayerDialog::formatAudioRouting(QTreeWidgetItem *ti, AudioRouting audio_routing)
|
2020-01-07 21:16:42 +00:00
|
|
|
{
|
2021-02-08 21:35:12 +00:00
|
|
|
ti->setText(channel_col_, tr(audio_routing.formatAudioRoutingToString()));
|
2020-01-07 21:16:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool RtpPlayerDialog::isStereoAvailable()
|
|
|
|
{
|
2022-05-24 01:59:35 +00:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
|
|
|
QAudioDevice cur_out_device = getCurrentDeviceInfo();
|
|
|
|
if (cur_out_device.maximumChannelCount() > 1) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
#else
|
2021-02-08 21:35:12 +00:00
|
|
|
QAudioDeviceInfo cur_out_device = getCurrentDeviceInfo();
|
2020-01-07 21:16:42 +00:00
|
|
|
foreach(int count, cur_out_device.supportedChannelCounts()) {
|
2022-05-24 01:59:35 +00:00
|
|
|
if (count > 1) {
|
2020-01-07 21:16:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2022-05-24 01:59:35 +00:00
|
|
|
#endif
|
2020-01-07 21:16:42 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-09 00:58:15 +00:00
|
|
|
void RtpPlayerDialog::invertSelection()
|
|
|
|
{
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = true;
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->streamTreeWidget->blockSignals(true);
|
2021-03-09 00:58:15 +00:00
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
ti->setSelected(!ti->isSelected());
|
|
|
|
}
|
2021-04-14 13:47:07 +00:00
|
|
|
ui->streamTreeWidget->blockSignals(false);
|
2021-04-13 14:38:13 +00:00
|
|
|
block_redraw_ = false;
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
updateHintLabel();
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionSelectAll_triggered()
|
|
|
|
{
|
|
|
|
ui->streamTreeWidget->selectAll();
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionSelectInvert_triggered()
|
|
|
|
{
|
|
|
|
invertSelection();
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionSelectNone_triggered()
|
|
|
|
{
|
|
|
|
ui->streamTreeWidget->clearSelection();
|
2021-04-13 11:42:57 +00:00
|
|
|
updateHintLabel();
|
2021-03-09 00:58:15 +00:00
|
|
|
}
|
|
|
|
|
2021-03-11 08:22:15 +00:00
|
|
|
void RtpPlayerDialog::on_actionPlay_triggered()
|
|
|
|
{
|
|
|
|
if (ui->playButton->isEnabled()) {
|
|
|
|
ui->playButton->animateClick();
|
|
|
|
} else if (ui->pauseButton->isEnabled()) {
|
|
|
|
ui->pauseButton->animateClick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionStop_triggered()
|
|
|
|
{
|
|
|
|
if (ui->stopButton->isEnabled()) {
|
|
|
|
ui->stopButton->animateClick();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
qint64 RtpPlayerDialog::saveAudioHeaderAU(QFile *save_file, quint32 channels, unsigned audio_rate)
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
uint8_t pd[4];
|
|
|
|
int64_t nchars;
|
|
|
|
|
|
|
|
/* https://pubs.opengroup.org/external/auformat.html */
|
|
|
|
/* First we write the .au header. All values in the header are
|
|
|
|
* 4-byte big-endian values, so we use pntoh32() to copy them
|
|
|
|
* to a 4-byte buffer, in big-endian order, and then write out
|
|
|
|
* the buffer. */
|
|
|
|
|
|
|
|
/* the magic word 0x2e736e64 == .snd */
|
|
|
|
phton32(pd, 0x2e736e64);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* header offset == 24 bytes */
|
|
|
|
phton32(pd, 24);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* total length; it is permitted to set this to 0xffffffff */
|
|
|
|
phton32(pd, 0xffffffff);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* encoding format == 16-bit linear PCM */
|
|
|
|
phton32(pd, 3);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sample rate [Hz] */
|
|
|
|
phton32(pd, audio_rate);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* channels */
|
|
|
|
phton32(pd, channels);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return save_file->pos();
|
|
|
|
}
|
|
|
|
|
2022-05-24 01:59:35 +00:00
|
|
|
qint64 RtpPlayerDialog::saveAudioHeaderWAV(QFile *save_file, quint32 channels, unsigned audio_rate, qint64 samples)
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
uint8_t pd[4];
|
|
|
|
int64_t nchars;
|
|
|
|
gint32 subchunk2Size;
|
|
|
|
gint32 data32;
|
|
|
|
gint16 data16;
|
|
|
|
|
|
|
|
subchunk2Size = sizeof(SAMPLE) * channels * (gint32)samples;
|
|
|
|
|
|
|
|
/* http://soundfile.sapp.org/doc/WaveFormat/ */
|
|
|
|
|
|
|
|
/* RIFF header, ChunkID 0x52494646 == RIFF */
|
|
|
|
phton32(pd, 0x52494646);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* RIFF header, ChunkSize */
|
|
|
|
data32 = 36 + subchunk2Size;
|
|
|
|
nchars = save_file->write((const char *)&data32, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* RIFF header, Format 0x57415645 == WAVE */
|
|
|
|
phton32(pd, 0x57415645);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, Subchunk1ID 0x666d7420 == 'fmt ' */
|
|
|
|
phton32(pd, 0x666d7420);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, Subchunk1Size */
|
|
|
|
data32 = 16;
|
|
|
|
nchars = save_file->write((const char *)&data32, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, AudioFormat 1 == PCM */
|
|
|
|
data16 = 1;
|
|
|
|
nchars = save_file->write((const char *)&data16, 2);
|
|
|
|
if (nchars != 2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, NumChannels */
|
|
|
|
data16 = channels;
|
|
|
|
nchars = save_file->write((const char *)&data16, 2);
|
|
|
|
if (nchars != 2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, SampleRate */
|
|
|
|
data32 = audio_rate;
|
|
|
|
nchars = save_file->write((const char *)&data32, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, ByteRate */
|
|
|
|
data32 = audio_rate * channels * sizeof(SAMPLE);
|
|
|
|
nchars = save_file->write((const char *)&data32, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, BlockAlign */
|
|
|
|
data16 = channels * (gint16)sizeof(SAMPLE);
|
|
|
|
nchars = save_file->write((const char *)&data16, 2);
|
|
|
|
if (nchars != 2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE fmt header, BitsPerSample */
|
|
|
|
data16 = (gint16)sizeof(SAMPLE) * 8;
|
|
|
|
nchars = save_file->write((const char *)&data16, 2);
|
|
|
|
if (nchars != 2) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE data header, Subchunk2ID 0x64617461 == 'data' */
|
|
|
|
phton32(pd, 0x64617461);
|
|
|
|
nchars = save_file->write((const char *)pd, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* WAVE data header, Subchunk2Size */
|
|
|
|
data32 = subchunk2Size;
|
|
|
|
nchars = save_file->write((const char *)&data32, 4);
|
|
|
|
if (nchars != 4) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now we are ready for saving data */
|
|
|
|
|
|
|
|
return save_file->pos();
|
|
|
|
}
|
|
|
|
|
2021-04-24 16:08:21 +00:00
|
|
|
bool RtpPlayerDialog::writeAudioSilenceSamples(QFile *out_file, qint64 samples, int stream_count)
|
|
|
|
{
|
|
|
|
uint8_t pd[2];
|
|
|
|
|
|
|
|
phton16(pd, 0x0000);
|
|
|
|
for(int s=0; s < stream_count; s++) {
|
|
|
|
for(qint64 i=0; i < samples; i++) {
|
|
|
|
if (sizeof(SAMPLE) != out_file->write((char *)&pd, sizeof(SAMPLE))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
bool RtpPlayerDialog::writeAudioStreamsSamples(QFile *out_file, QVector<RtpAudioStream *> streams, bool swap_bytes)
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
SAMPLE sample;
|
|
|
|
uint8_t pd[2];
|
|
|
|
|
|
|
|
// Did we read something in last cycle?
|
|
|
|
bool read = true;
|
|
|
|
|
|
|
|
while (read) {
|
|
|
|
read = false;
|
2021-04-01 08:07:22 +00:00
|
|
|
// Loop over all streams, read one sample from each, write to output
|
2021-03-31 14:52:06 +00:00
|
|
|
foreach(RtpAudioStream *audio_stream, streams) {
|
2021-04-24 16:08:21 +00:00
|
|
|
if (sizeof(sample) == audio_stream->readSample(&sample)) {
|
2021-03-31 14:52:06 +00:00
|
|
|
if (swap_bytes) {
|
2021-04-01 08:07:22 +00:00
|
|
|
// same as phton16(), but more clear in compare
|
|
|
|
// to else branch
|
|
|
|
pd[0] = (guint8)(sample >> 8);
|
|
|
|
pd[1] = (guint8)(sample >> 0);
|
2021-03-31 14:52:06 +00:00
|
|
|
} else {
|
2021-04-01 08:07:22 +00:00
|
|
|
// just copy
|
2021-03-31 14:52:06 +00:00
|
|
|
pd[1] = (guint8)(sample >> 8);
|
|
|
|
pd[0] = (guint8)(sample >> 0);
|
|
|
|
}
|
|
|
|
read = true;
|
|
|
|
} else {
|
2021-04-01 08:07:22 +00:00
|
|
|
// for 0x0000 doesn't matter on order
|
2021-03-31 14:52:06 +00:00
|
|
|
phton16(pd, 0x0000);
|
|
|
|
}
|
2021-04-01 08:07:22 +00:00
|
|
|
if (sizeof(sample) != out_file->write((char *)&pd, sizeof(sample))) {
|
|
|
|
return false;
|
|
|
|
}
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-01 08:07:22 +00:00
|
|
|
|
|
|
|
return true;
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
save_audio_t RtpPlayerDialog::selectFileAudioFormatAndName(QString *file_path)
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
QString ext_filter = "";
|
|
|
|
QString ext_filter_wav = tr("WAV (*.wav)");
|
|
|
|
QString ext_filter_au = tr("Sun Audio (*.au)");
|
|
|
|
ext_filter.append(ext_filter_wav);
|
|
|
|
ext_filter.append(";;");
|
|
|
|
ext_filter.append(ext_filter_au);
|
|
|
|
|
|
|
|
QString sel_filter;
|
|
|
|
*file_path = WiresharkFileDialog::getSaveFileName(
|
Work around macOS running applications in /.
If an application is launched from the Finder, it appears to get / as
its current directory. This causes Wireshark open/save dialogs to open
up / if the user hasn't already opened a file in another directory, so
that there's no "last open directory" in the recent file.
Have get_persdatafile_dir(), on UN*X, cache the personal data directory
just as it does on Windows and, if nothing's been cached, have it fetch
the current directory and, if that succeeds *and* it's not the root
directory, use that. Otherwise, use the user's home directory.
Fixes #9862.
In addition, separate the notion of "last open directory" and "open
dialog initial directory", where the latter is the last open directory
*if* a file has been opened in this session or the recent file has the
last open directory from a previous session, otherwise it's the user's
personal data directory.
Use the latter notion in file open/save dialogs; use the former notion
when reading from and writing to the recent file.
This means we don't need to set the "last open directory" at startup
time. That way, running Wireshark without opening a file won't cause
the "last open directory" to be set, so that if a user runs it from a
directory, the "open dialog initial directory" won't be the last
directory from which Wireshark was run.
2023-11-10 08:08:48 +00:00
|
|
|
this, tr("Save audio"), mainApp->openDialogInitialDir().absoluteFilePath(""),
|
2021-03-31 14:52:06 +00:00
|
|
|
ext_filter, &sel_filter);
|
|
|
|
|
|
|
|
if (file_path->isEmpty()) return save_audio_none;
|
|
|
|
|
|
|
|
save_audio_t save_format = save_audio_none;
|
|
|
|
if (0 == QString::compare(sel_filter, ext_filter_au)) {
|
|
|
|
save_format = save_audio_au;
|
|
|
|
} else if (0 == QString::compare(sel_filter, ext_filter_wav)) {
|
|
|
|
save_format = save_audio_wav;
|
|
|
|
}
|
|
|
|
|
|
|
|
return save_format;
|
|
|
|
}
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
save_payload_t RtpPlayerDialog::selectFilePayloadFormatAndName(QString *file_path)
|
|
|
|
{
|
|
|
|
QString ext_filter = "";
|
|
|
|
QString ext_filter_raw = tr("Raw (*.raw)");
|
|
|
|
ext_filter.append(ext_filter_raw);
|
|
|
|
|
|
|
|
QString sel_filter;
|
|
|
|
*file_path = WiresharkFileDialog::getSaveFileName(
|
Work around macOS running applications in /.
If an application is launched from the Finder, it appears to get / as
its current directory. This causes Wireshark open/save dialogs to open
up / if the user hasn't already opened a file in another directory, so
that there's no "last open directory" in the recent file.
Have get_persdatafile_dir(), on UN*X, cache the personal data directory
just as it does on Windows and, if nothing's been cached, have it fetch
the current directory and, if that succeeds *and* it's not the root
directory, use that. Otherwise, use the user's home directory.
Fixes #9862.
In addition, separate the notion of "last open directory" and "open
dialog initial directory", where the latter is the last open directory
*if* a file has been opened in this session or the recent file has the
last open directory from a previous session, otherwise it's the user's
personal data directory.
Use the latter notion in file open/save dialogs; use the former notion
when reading from and writing to the recent file.
This means we don't need to set the "last open directory" at startup
time. That way, running Wireshark without opening a file won't cause
the "last open directory" to be set, so that if a user runs it from a
directory, the "open dialog initial directory" won't be the last
directory from which Wireshark was run.
2023-11-10 08:08:48 +00:00
|
|
|
this, tr("Save payload"), mainApp->openDialogInitialDir().absoluteFilePath(""),
|
2021-04-01 08:07:22 +00:00
|
|
|
ext_filter, &sel_filter);
|
|
|
|
|
|
|
|
if (file_path->isEmpty()) return save_payload_none;
|
|
|
|
|
|
|
|
save_payload_t save_format = save_payload_none;
|
|
|
|
if (0 == QString::compare(sel_filter, ext_filter_raw)) {
|
|
|
|
save_format = save_payload_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return save_format;
|
|
|
|
}
|
|
|
|
|
2021-04-14 13:47:07 +00:00
|
|
|
QVector<rtpstream_id_t *>RtpPlayerDialog::getSelectedRtpStreamIDs()
|
|
|
|
{
|
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
|
|
|
|
QVector<rtpstream_id_t *> ids;
|
|
|
|
|
|
|
|
if (items.count() > 0) {
|
|
|
|
foreach(QTreeWidgetItem *ti, items) {
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream) {
|
|
|
|
ids << audio_stream->getID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVector<RtpAudioStream *>RtpPlayerDialog::getSelectedAudibleNonmutedAudioStreams()
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
QList<QTreeWidgetItem *> items = ui->streamTreeWidget->selectedItems();
|
|
|
|
QVector<RtpAudioStream *> streams;
|
|
|
|
|
|
|
|
if (items.count() > 0) {
|
|
|
|
foreach(QTreeWidgetItem *ti, items) {
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
// Ignore muted streams and streams with no audio
|
|
|
|
if (audio_stream &&
|
|
|
|
!audio_stream->getAudioRouting().isMuted() &&
|
|
|
|
(audio_stream->sampleRate()>0)
|
|
|
|
) {
|
|
|
|
streams << audio_stream;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return streams;
|
|
|
|
}
|
|
|
|
|
2021-04-27 20:01:16 +00:00
|
|
|
void RtpPlayerDialog::saveAudio(save_mode_t save_mode)
|
2021-03-31 14:52:06 +00:00
|
|
|
{
|
|
|
|
qint64 minSilenceSamples;
|
|
|
|
qint64 startSample;
|
2021-04-24 16:08:21 +00:00
|
|
|
qint64 lead_silence_samples;
|
2021-03-31 14:52:06 +00:00
|
|
|
qint64 maxSample;
|
|
|
|
QString path;
|
|
|
|
QVector<RtpAudioStream *>streams;
|
|
|
|
|
2021-04-14 13:47:07 +00:00
|
|
|
streams = getSelectedAudibleNonmutedAudioStreams();
|
2021-03-31 14:52:06 +00:00
|
|
|
if (streams.count() < 1) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("No stream selected or none of selected streams provide audio"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned save_audio_rate = streams[0]->playRate();
|
|
|
|
// Check whether all streams use same audio rate
|
|
|
|
foreach(RtpAudioStream *audio_stream, streams) {
|
|
|
|
if (save_audio_rate != audio_stream->playRate()) {
|
|
|
|
QMessageBox::warning(this, tr("Error"), tr("All selected streams must use same play rate. Manual set of Output Audio Rate might help."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
save_audio_t format = selectFileAudioFormatAndName(&path);
|
2021-03-31 14:52:06 +00:00
|
|
|
if (format == save_audio_none) return;
|
|
|
|
|
|
|
|
// Use start silence and length of first stream
|
|
|
|
minSilenceSamples = streams[0]->getLeadSilenceSamples();
|
|
|
|
maxSample = streams[0]->getTotalSamples();
|
|
|
|
// Find shortest start silence and longest stream
|
|
|
|
foreach(RtpAudioStream *audio_stream, streams) {
|
|
|
|
if (minSilenceSamples > audio_stream->getLeadSilenceSamples()) {
|
|
|
|
minSilenceSamples = audio_stream->getLeadSilenceSamples();
|
|
|
|
}
|
|
|
|
if (maxSample < audio_stream->getTotalSamples()) {
|
|
|
|
maxSample = audio_stream->getTotalSamples();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 20:01:16 +00:00
|
|
|
switch (save_mode) {
|
|
|
|
case save_mode_from_cursor:
|
|
|
|
if (ui->todCheckBox->isChecked()) {
|
|
|
|
startSample = start_marker_time_ * save_audio_rate;
|
|
|
|
} else {
|
|
|
|
startSample = (start_marker_time_ - first_stream_rel_start_time_) * save_audio_rate;
|
|
|
|
}
|
|
|
|
lead_silence_samples = 0;
|
|
|
|
break;
|
|
|
|
case save_mode_sync_stream:
|
|
|
|
// Skip start of first stream, no lead silence
|
|
|
|
startSample = minSilenceSamples;
|
|
|
|
lead_silence_samples = 0;
|
|
|
|
break;
|
|
|
|
case save_mode_sync_file:
|
|
|
|
default:
|
|
|
|
// Full first stream, lead silence
|
|
|
|
startSample = 0;
|
|
|
|
lead_silence_samples = first_stream_rel_start_time_ * save_audio_rate;
|
|
|
|
break;
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 20:01:16 +00:00
|
|
|
QVector<RtpAudioStream *>temp = QVector<RtpAudioStream *>(streams);
|
|
|
|
|
|
|
|
// Remove streams shorter than startSample and
|
|
|
|
// seek to correct start for longer ones
|
|
|
|
foreach(RtpAudioStream *audio_stream, temp) {
|
|
|
|
if (startSample > audio_stream->getTotalSamples()) {
|
|
|
|
streams.removeAll(audio_stream);
|
|
|
|
} else {
|
|
|
|
audio_stream->seekSample(startSample);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (streams.count() < 1) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("No streams are suitable for save"));
|
|
|
|
return;
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QFile file(path);
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
if (!file.isOpen() || (file.error() != QFile::NoError)) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
} else {
|
|
|
|
switch (format) {
|
|
|
|
case save_audio_au:
|
2022-05-24 01:59:35 +00:00
|
|
|
if (-1 == saveAudioHeaderAU(&file, static_cast<quint32>(streams.count()), save_audio_rate)) {
|
2021-04-01 08:07:22 +00:00
|
|
|
QMessageBox::warning(this, tr("Error"), tr("Can't write header of AU file"));
|
|
|
|
return;
|
|
|
|
}
|
2021-04-24 16:08:21 +00:00
|
|
|
if (lead_silence_samples > 0) {
|
2022-05-24 01:59:35 +00:00
|
|
|
if (!writeAudioSilenceSamples(&file, lead_silence_samples, static_cast<int>(streams.count()))) {
|
2021-04-24 16:08:21 +00:00
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
}
|
|
|
|
}
|
2021-04-01 08:07:22 +00:00
|
|
|
if (!writeAudioStreamsSamples(&file, streams, true)) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case save_audio_wav:
|
2022-05-24 01:59:35 +00:00
|
|
|
if (-1 == saveAudioHeaderWAV(&file, static_cast<quint32>(streams.count()), save_audio_rate, (maxSample - startSample) + lead_silence_samples)) {
|
2021-04-01 08:07:22 +00:00
|
|
|
QMessageBox::warning(this, tr("Error"), tr("Can't write header of WAV file"));
|
|
|
|
return;
|
|
|
|
}
|
2021-04-24 16:08:21 +00:00
|
|
|
if (lead_silence_samples > 0) {
|
2022-05-24 01:59:35 +00:00
|
|
|
if (!writeAudioSilenceSamples(&file, lead_silence_samples, static_cast<int>(streams.count()))) {
|
2021-04-24 16:08:21 +00:00
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
}
|
|
|
|
}
|
2021-04-01 08:07:22 +00:00
|
|
|
if (!writeAudioStreamsSamples(&file, streams, false)) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case save_audio_none:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::savePayload()
|
|
|
|
{
|
|
|
|
QString path;
|
|
|
|
QList<QTreeWidgetItem *> items;
|
|
|
|
RtpAudioStream *audio_stream = NULL;
|
|
|
|
|
|
|
|
items = ui->streamTreeWidget->selectedItems();
|
|
|
|
foreach(QTreeWidgetItem *ti, items) {
|
|
|
|
audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
if (audio_stream)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (items.count() != 1 || !audio_stream) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Payload save works with just one audio stream."));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
save_payload_t format = selectFilePayloadFormatAndName(&path);
|
|
|
|
if (format == save_payload_none) return;
|
|
|
|
|
|
|
|
QFile file(path);
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
|
|
|
|
if (!file.isOpen() || (file.error() != QFile::NoError)) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
|
|
|
} else if (!audio_stream->savePayload(&file)) {
|
|
|
|
QMessageBox::warning(this, tr("Warning"), tr("Save failed!"));
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
2021-04-27 20:01:16 +00:00
|
|
|
void RtpPlayerDialog::on_actionSaveAudioFromCursor_triggered()
|
|
|
|
{
|
|
|
|
saveAudio(save_mode_from_cursor);
|
|
|
|
}
|
|
|
|
|
2021-03-31 14:52:06 +00:00
|
|
|
void RtpPlayerDialog::on_actionSaveAudioSyncStream_triggered()
|
|
|
|
{
|
2021-04-27 20:01:16 +00:00
|
|
|
saveAudio(save_mode_sync_stream);
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionSaveAudioSyncFile_triggered()
|
|
|
|
{
|
2021-04-27 20:01:16 +00:00
|
|
|
saveAudio(save_mode_sync_file);
|
2021-03-31 14:52:06 +00:00
|
|
|
}
|
|
|
|
|
2021-04-01 08:07:22 +00:00
|
|
|
void RtpPlayerDialog::on_actionSavePayload_triggered()
|
|
|
|
{
|
|
|
|
savePayload();
|
|
|
|
}
|
|
|
|
|
2021-04-14 13:47:07 +00:00
|
|
|
void RtpPlayerDialog::selectInaudible(bool select)
|
|
|
|
{
|
|
|
|
block_redraw_ = true;
|
|
|
|
ui->streamTreeWidget->blockSignals(true);
|
|
|
|
for (int row = 0; row < ui->streamTreeWidget->topLevelItemCount(); row++) {
|
|
|
|
QTreeWidgetItem *ti = ui->streamTreeWidget->topLevelItem(row);
|
|
|
|
RtpAudioStream *audio_stream = ti->data(stream_data_col_, Qt::UserRole).value<RtpAudioStream*>();
|
|
|
|
// Streams with no audio
|
|
|
|
if (audio_stream && (audio_stream->sampleRate()==0)) {
|
|
|
|
ti->setSelected(select);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ui->streamTreeWidget->blockSignals(false);
|
|
|
|
block_redraw_ = false;
|
|
|
|
ui->audioPlot->replot();
|
|
|
|
updateHintLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionSelectInaudible_triggered()
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2021-04-14 13:47:07 +00:00
|
|
|
selectInaudible(true);
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
|
|
|
|
2021-04-14 13:47:07 +00:00
|
|
|
void RtpPlayerDialog::on_actionDeselectInaudible_triggered()
|
2014-12-13 00:51:40 +00:00
|
|
|
{
|
2021-04-14 13:47:07 +00:00
|
|
|
selectInaudible(false);
|
2014-12-13 00:51:40 +00:00
|
|
|
}
|
2021-04-14 13:47:07 +00:00
|
|
|
|
|
|
|
void RtpPlayerDialog::on_actionPrepareFilter_triggered()
|
|
|
|
{
|
|
|
|
QVector<rtpstream_id_t *> ids = getSelectedRtpStreamIDs();
|
|
|
|
QString filter = make_filter_based_on_rtpstream_id(ids);
|
|
|
|
if (filter.length() > 0) {
|
|
|
|
emit updateFilter(filter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::rtpAnalysisReplace()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
|
|
|
|
emit rtpAnalysisDialogReplaceRtpStreams(getSelectedRtpStreamIDs());
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::rtpAnalysisAdd()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
|
|
|
|
emit rtpAnalysisDialogAddRtpStreams(getSelectedRtpStreamIDs());
|
|
|
|
}
|
|
|
|
|
|
|
|
void RtpPlayerDialog::rtpAnalysisRemove()
|
|
|
|
{
|
|
|
|
if (ui->streamTreeWidget->selectedItems().count() < 1) return;
|
|
|
|
|
|
|
|
emit rtpAnalysisDialogRemoveRtpStreams(getSelectedRtpStreamIDs());
|
|
|
|
}
|
|
|
|
|
2021-04-22 19:33:11 +00:00
|
|
|
void RtpPlayerDialog::on_actionReadCapture_triggered()
|
|
|
|
{
|
|
|
|
#ifdef QT_MULTIMEDIA_LIB
|
|
|
|
QTimer::singleShot(0, this, SLOT(retapPackets()));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// _U_ is used for case w have no LIBPCAP
|
|
|
|
void RtpPlayerDialog::captureEvent(CaptureEvent e _U_)
|
|
|
|
{
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
bool new_read_capture_enabled = false;
|
|
|
|
bool found = false;
|
|
|
|
|
|
|
|
if ((e.captureContext() & CaptureEvent::Capture) &&
|
|
|
|
(e.eventType() == CaptureEvent::Prepared)
|
|
|
|
) {
|
|
|
|
new_read_capture_enabled = true;
|
|
|
|
found = true;
|
|
|
|
} else if ((e.captureContext() & CaptureEvent::Capture) &&
|
|
|
|
(e.eventType() == CaptureEvent::Finished)
|
|
|
|
) {
|
|
|
|
new_read_capture_enabled = false;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
bool retap = false;
|
|
|
|
if (read_capture_enabled_ && !new_read_capture_enabled) {
|
|
|
|
// Capturing ended, automatically refresh data
|
|
|
|
retap = true;
|
|
|
|
}
|
|
|
|
read_capture_enabled_ = new_read_capture_enabled;
|
|
|
|
updateWidgets();
|
|
|
|
if (retap) {
|
|
|
|
QTimer::singleShot(0, this, SLOT(retapPackets()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
2014-12-13 00:51:40 +00:00
|
|
|
|
|
|
|
#endif // QT_MULTIMEDIA_LIB
|