Qt: Make the RTP player output device selectable.
authorGerald Combs <gerald@wireshark.org>
Fri, 2 Dec 2016 23:52:02 +0000 (15:52 -0800)
committerGerald Combs <gerald@wireshark.org>
Tue, 6 Dec 2016 22:36:55 +0000 (22:36 +0000)
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>
docbook/release-notes.asciidoc
ui/qt/rtp_audio_stream.cpp
ui/qt/rtp_audio_stream.h
ui/qt/rtp_player_dialog.cpp
ui/qt/rtp_player_dialog.h
ui/qt/rtp_player_dialog.ui

index 83745f4bf1f78decc863f740c5ed97cefde7db4c..1507cf20522f420412f2e11b05f78d2b396bcc05 100644 (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
 
index 798d73fcbea6f41edfe7a4d96eccbac21e0a420d..fee1ebc3910fdcc181ea945adcd65d5be04c3182 100644 (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()));
index e60e5e7ebeeba5261271f98771b2f2bf9137b893..d4cfa2229063b9ef7eaca3b43b201bb8ac8592f8 100644 (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);
index 743685c30777dcb41dc3eaca43f25e019cce4a56..35274cb3f0900cca716b81cee89c8417229ef1aa 100644 (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();
index e6e1a428be04e06e4467d60b5f0cd396ad39aa44..0067c6f3e92b3d62c38b7565d371646e7b4f2181 100644 (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:
index fd5d2420c2a5311c03201f03ef89c5f02a31924d..d0a5005336c66d0255a3b4fd42a3b5746db3dcfd 100644 (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">
     </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">
        </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">
        </property>
        <property name="sizeHint" stdset="0">
         <size>
-         <width>40</width>
-         <height>20</height>
+         <width>20</width>
+         <height>10</height>
         </size>
        </property>
       </spacer>
        </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>