Qt: Make the RTP player output device selectable.

Add a combobox for selecting the output device and populate it with our
available devices. Let the user know if our output format isn't
supported.

Ping-Bug: 13105
Change-Id: I299c7d0f191bb66d93896338036000e2c377781f
Reviewed-on: https://code.wireshark.org/review/19046
Petri-Dish: Gerald Combs <gerald@wireshark.org>
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org>
Reviewed-by: Gerald Combs <gerald@wireshark.org>
This commit is contained in:
Gerald Combs 2016-12-02 15:52:02 -08:00
parent 263fea9723
commit d59653f8d5
6 changed files with 128 additions and 11 deletions

View File

@ -39,6 +39,7 @@ since version 2.2.0:
* Wireshark can now go fullscreen to have more room for packets.
* TShark can now export objects like the other GUI interfaces.
* Support for G.722 and G.726 codecs in the RTP Player (via the Spandsp library).
* You can now choose the output device when playing RTP streams.
//=== Removed Dissectors

View File

@ -42,6 +42,7 @@
#include <QAudioOutput>
#include <QDir>
#include <QTemporaryFile>
#include <QVariant>
// To do:
// - Only allow one rtp_stream_info_t per RtpAudioStream?
@ -521,10 +522,41 @@ QAudio::State RtpAudioStream::outputState() const
return audio_output_->state();
}
const QString RtpAudioStream::formatDescription(const QAudioFormat &format)
{
QString fmt_descr = QString("%1 Hz, ").arg(format.sampleRate());
switch (format.sampleType()) {
case QAudioFormat::SignedInt:
fmt_descr += "Int";
break;
case QAudioFormat::UnSignedInt:
fmt_descr += "UInt";
break;
case QAudioFormat::Float:
fmt_descr += "Float";
break;
default:
fmt_descr += "Unknown";
break;
}
fmt_descr += QString::number(format.sampleSize());
fmt_descr += format.byteOrder() == QAudioFormat::BigEndian ? "BE" : "LE";
return fmt_descr;
}
void RtpAudioStream::startPlaying()
{
if (audio_output_) return;
QAudioDeviceInfo cur_out_device = QAudioDeviceInfo::defaultOutputDevice();
QString cur_out_name = parent()->property("currentOutputDeviceName").toString();
foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
if (cur_out_name == out_device.deviceName()) {
cur_out_device = out_device;
}
}
QAudioFormat format;
format.setSampleRate(audio_out_rate_);
format.setSampleSize(sample_bytes_ * 8); // bits
@ -536,7 +568,15 @@ void RtpAudioStream::startPlaying()
// tempfile_->fileName().toUtf8().constData(),
// (int) tempfile_->size(), audio_out_rate_);
audio_output_ = new QAudioOutput(format, this);
if (!cur_out_device.isFormatSupported(format)) {
QString playback_error = tr("%1 does not support PCM at %2. Preferred format is %3")
.arg(cur_out_device.deviceName())
.arg(formatDescription(format))
.arg(formatDescription(cur_out_device.nearestFormat(format)));
emit playbackError(playback_error);
}
audio_output_ = new QAudioOutput(cur_out_device, format, this);
audio_output_->setNotifyInterval(65); // ~15 fps
connect(audio_output_, SIGNAL(stateChanged(QAudio::State)), this, SLOT(outputStateChanged(QAudio::State)));
connect(audio_output_, SIGNAL(notify()), this, SLOT(outputNotify()));

View File

@ -37,6 +37,7 @@
#include <QSet>
#include <QVector>
class QAudioFormat;
class QAudioOutput;
class QTemporaryFile;
@ -142,6 +143,7 @@ public:
signals:
void startedPlaying();
void processedSecs(double secs);
void playbackError(const QString error_msg);
void finishedPlaying();
public slots:
@ -183,6 +185,7 @@ private:
TimingMode timing_mode_;
void writeSilence(int samples);
const QString formatDescription(const QAudioFormat & format);
private slots:
void outputStateChanged(QAudio::State new_state);

View File

@ -35,6 +35,7 @@
#include "tango_colors.h"
#include <QAudio>
#include <QAudioDeviceInfo>
#include <QFrame>
#include <QMenu>
#include <QVBoxLayout>
@ -134,7 +135,7 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
ctx_menu_->addAction(ui->actionCrosshairs);
connect(ui->audioPlot, SIGNAL(mouseMove(QMouseEvent*)),
this, SLOT(mouseMoved(QMouseEvent*)));
this, SLOT(updateHintLabel()));
connect(ui->audioPlot, SIGNAL(mousePress(QMouseEvent*)),
this, SLOT(graphClicked(QMouseEvent*)));
@ -150,6 +151,21 @@ RtpPlayerDialog::RtpPlayerDialog(QWidget &parent, CaptureFile &cf) :
ui->playButton->setIcon(StockIcon("media-playback-start"));
ui->stopButton->setIcon(StockIcon("media-playback-stop"));
QString default_out_name = QAudioDeviceInfo::defaultOutputDevice().deviceName();
foreach (QAudioDeviceInfo out_device, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
QString out_name = out_device.deviceName();
ui->outputDeviceComboBox->addItem(out_name);
if (out_name == default_out_name) {
ui->outputDeviceComboBox->setCurrentText(out_name);
}
}
if (ui->outputDeviceComboBox->count() < 1) {
ui->outputDeviceComboBox->setEnabled(false);
ui->playButton->setEnabled(false);
ui->stopButton->setEnabled(false);
ui->outputDeviceComboBox->addItem(tr("No devices available"));
}
ui->audioPlot->setMouseTracking(true);
ui->audioPlot->setEnabled(true);
ui->audioPlot->setInteractions(
@ -379,6 +395,7 @@ void RtpPlayerDialog::addRtpStream(struct _rtp_stream_info *rtp_stream)
connect(audio_stream, SIGNAL(startedPlaying()), this, SLOT(updateWidgets()));
connect(audio_stream, SIGNAL(finishedPlaying()), this, SLOT(updateWidgets()));
connect(audio_stream, SIGNAL(playbackError(QString)), this, SLOT(setPlaybackError(QString)));
connect(audio_stream, SIGNAL(processedSecs(double)), this, SLOT(setPlayPosition(double)));
}
audio_stream->addRtpStream(rtp_stream);
@ -473,6 +490,7 @@ void RtpPlayerDialog::updateWidgets()
}
ui->playButton->setEnabled(enable_play);
ui->outputDeviceComboBox->setEnabled(enable_play);
ui->stopButton->setEnabled(enable_stop);
cur_play_pos_->setVisible(enable_stop);
@ -480,6 +498,7 @@ void RtpPlayerDialog::updateWidgets()
ui->timingComboBox->setEnabled(enable_timing);
ui->todCheckBox->setEnabled(enable_timing);
updateHintLabel();
ui->audioPlot->replot();
}
@ -492,7 +511,7 @@ void RtpPlayerDialog::graphClicked(QMouseEvent *event)
ui->audioPlot->setFocus();
}
void RtpPlayerDialog::mouseMoved(QMouseEvent *)
void RtpPlayerDialog::updateHintLabel()
{
int packet_num = getHoveredPacket();
QString hint = "<small><i>";
@ -501,6 +520,8 @@ void RtpPlayerDialog::mouseMoved(QMouseEvent *)
hint += tr("%1. Press \"G\" to go to packet %2")
.arg(getHoveredTime())
.arg(packet_num);
} else if (!playback_error_.isEmpty()) {
hint += playback_error_;
}
hint += "</i></small>";
@ -603,6 +624,7 @@ void RtpPlayerDialog::on_playButton_clicked()
cur_play_pos_->point1->setCoords(left, 0.0);
cur_play_pos_->point2->setCoords(left, 1.0);
cur_play_pos_->setVisible(true);
playback_error_.clear();
ui->audioPlot->replot();
}
@ -709,6 +731,13 @@ int RtpPlayerDialog::getHoveredPacket()
return audio_stream->nearestPacket(ts, !ui->todCheckBox->isChecked());
}
// Used by RtpAudioStreams to initialize QAudioOutput. We could alternatively
// pass the corresponding QAudioDeviceInfo directly.
const QString RtpPlayerDialog::currentOutputDeviceName()
{
return ui->outputDeviceComboBox->currentText();
}
void RtpPlayerDialog::on_jitterSpinBox_valueChanged(double)
{
rescanPackets();

View File

@ -46,6 +46,7 @@ class RtpAudioStream;
class RtpPlayerDialog : public WiresharkDialog
{
Q_OBJECT
Q_PROPERTY(QString currentOutputDeviceName READ currentOutputDeviceName CONSTANT)
public:
explicit RtpPlayerDialog(QWidget &parent, CaptureFile &cf);
@ -92,10 +93,11 @@ private slots:
void rescanPackets(bool rescale_axes = false);
void updateWidgets();
void graphClicked(QMouseEvent *event);
void mouseMoved(QMouseEvent *);
void updateHintLabel();
void resetXAxis();
void setPlayPosition(double secs);
void setPlaybackError(const QString playback_error) { playback_error_ = playback_error; }
void on_playButton_clicked();
void on_stopButton_clicked();
void on_actionReset_triggered();
@ -117,6 +119,7 @@ private:
QMenu *ctx_menu_;
double start_rel_time_;
QCPItemStraightLine *cur_play_pos_;
QString playback_error_;
// const QString streamKey(const struct _rtp_stream_info *rtp_stream);
// const QString streamKey(const packet_info *pinfo, const struct _rtp_info *rtpinfo);
@ -132,6 +135,7 @@ private:
double getLowestTimestamp();
const QString getHoveredTime();
int getHoveredPacket();
const QString currentOutputDeviceName();
#else // QT_MULTIMEDIA_LIB
private:

View File

@ -6,14 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>708</width>
<height>400</height>
<width>750</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>RTP Player</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
@ -118,7 +118,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0,0,0,1,0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,1">
<item>
<widget class="QToolButton" name="playButton">
<property name="text">
@ -153,12 +153,39 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Output Device:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="outputDeviceComboBox"/>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,0,0,0,0,0,1">
<item>
<widget class="QLabel" name="label">
<property name="toolTip">
@ -241,8 +268,8 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
@ -257,6 +284,19 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>48</width>
<height>24</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>