Fix a few audiodecoder things.
authorMichael Goddard <michael.goddard@nokia.com>
Fri, 17 Feb 2012 09:10:35 +0000 (19:10 +1000)
committerQt by Nokia <qt-info@nokia.com>
Tue, 21 Feb 2012 06:35:35 +0000 (07:35 +0100)
Beef up the autotest a little, and check the conversion.

Change-Id: Ifffca118e092eb6c388db50a6eb12810a87aa32a
Reviewed-by: Lev Zelenskiy <lev.zelenskiy@nokia.com>
Reviewed-by: Michael Goddard <michael.goddard@nokia.com>
src/gsttools/qgstreamermessage.cpp
src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp
src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h
src/plugins/gstreamer/qgstappsrc.cpp
tests/auto/integration/multimedia.pro
tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp

index 3161856..02b03a3 100644 (file)
@@ -84,11 +84,15 @@ GstMessage* QGstreamerMessage::rawMessage() const
 
 QGstreamerMessage& QGstreamerMessage::operator=(QGstreamerMessage const& rhs)
 {
-    if (m_message != 0)
-        gst_message_unref(m_message);
+    if (rhs.m_message != m_message) {
+        if (rhs.m_message != 0)
+            gst_message_ref(rhs.m_message);
+
+        if (m_message != 0)
+            gst_message_unref(m_message);
 
-    if ((m_message = rhs.m_message) != 0)
-        gst_message_ref(m_message);
+        m_message = rhs.m_message;
+    }
 
     return *this;
 }
index 88f3a0e..518bf77 100644 (file)
@@ -38,6 +38,7 @@
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/
+//#define DEBUG_DECODER
 
 #include "qgstreameraudiodecodersession.h"
 #include <private/qgstreamerbushelper_p.h>
@@ -56,7 +57,7 @@
 #include <QtCore/qstandardpaths.h>
 #include <QtCore/qurl.h>
 
-#define MAX_BUFFERS_IN_QUEUE 5
+#define MAX_BUFFERS_IN_QUEUE 4
 
 QT_BEGIN_NAMESPACE
 
@@ -79,6 +80,8 @@ QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent)
      m_busHelper(0),
      m_bus(0),
      m_playbin(0),
+     m_outputBin(0),
+     m_audioConvert(0),
      m_appSink(0),
 #if defined(HAVE_GST_APPSRC)
      m_appSrc(0),
@@ -86,55 +89,31 @@ QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent)
      mDevice(0),
      m_buffersAvailable(0)
 {
-    // Default format
-    mFormat.setChannels(2);
-    mFormat.setSampleSize(16);
-    mFormat.setFrequency(48000);
-    mFormat.setCodec("audio/pcm");
-    mFormat.setSampleType(QAudioFormat::UnSignedInt);
-
-
     // Create pipeline here
     m_playbin = gst_element_factory_make("playbin2", NULL);
 
     if (m_playbin != 0) {
+        // Sort out messages
+        m_bus = gst_element_get_bus(m_playbin);
+        m_busHelper = new QGstreamerBusHelper(m_bus, this);
+        m_busHelper->installMessageFilter(this);
 
-        int flags = 0;
-        g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL);
-        // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO, it prevents audio format conversion
-        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO);
-        flags |= GST_PLAY_FLAG_AUDIO;
-        g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL);
-
-        m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL);
-        gst_object_ref(GST_OBJECT(m_appSink));
-
-        GstAppSinkCallbacks callbacks;
-        memset(&callbacks, 0, sizeof(callbacks));
-        callbacks.new_buffer = &new_buffer;
-        gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL);
-        gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE);
-        gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE);
+        // Set the rest of the pipeline up
+        setAudioFlags(true);
 
-        GstElement *audioConvert = gst_element_factory_make("audioconvert", NULL);
+        m_audioConvert = gst_element_factory_make("audioconvert", NULL);
 
-        GstElement *bin = gst_bin_new("audio-output-bin");
-        gst_bin_add(GST_BIN(bin), audioConvert);
-        gst_bin_add(GST_BIN(bin), GST_ELEMENT(m_appSink));
-        gst_element_link(audioConvert, GST_ELEMENT(m_appSink));
+        m_outputBin = gst_bin_new("audio-output-bin");
+        gst_bin_add(GST_BIN(m_outputBin), m_audioConvert);
 
         // add ghostpad
-        GstPad *pad = gst_element_get_static_pad(audioConvert, "sink");
+        GstPad *pad = gst_element_get_static_pad(m_audioConvert, "sink");
         Q_ASSERT(pad);
-        gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("sink", pad));
+        gst_element_add_pad(GST_ELEMENT(m_outputBin), gst_ghost_pad_new("sink", pad));
         gst_object_unref(GST_OBJECT(pad));
 
-        // Sort out messages
-        m_bus = gst_element_get_bus(m_playbin);
-        m_busHelper = new QGstreamerBusHelper(m_bus, this);
-        m_busHelper->installMessageFilter(this);
-
-        g_object_set(G_OBJECT(m_playbin), "audio-sink", bin, NULL);
+        g_object_set(G_OBJECT(m_playbin), "audio-sink", m_outputBin, NULL);
+        g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderSession::configureAppSrcElement, (gpointer)this);
 
         // Set volume to 100%
         gdouble volume = 1.0;
@@ -148,9 +127,11 @@ QGstreamerAudioDecoderSession::~QGstreamerAudioDecoderSession()
         stop();
 
         delete m_busHelper;
+#if defined(HAVE_GST_APPSRC)
+        delete m_appSrc;
+#endif
         gst_object_unref(GST_OBJECT(m_bus));
         gst_object_unref(GST_OBJECT(m_playbin));
-        gst_object_unref(GST_OBJECT(m_appSink));
     }
 }
 
@@ -160,6 +141,10 @@ void QGstreamerAudioDecoderSession::configureAppSrcElement(GObject* object, GObj
     Q_UNUSED(object);
     Q_UNUSED(pspec);
 
+    // In case we switch from appsrc to not
+    if (!self->appsrc())
+        return;
+
     if (self->appsrc()->isReady())
         return;
 
@@ -185,14 +170,14 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
 
                     gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
 
-#ifdef DEBUG_PLAYBIN
+#ifdef DEBUG_DECODER
                     QStringList states;
                     states << "GST_STATE_VOID_PENDING" <<  "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING";
 
                     qDebug() << QString("state changed: old: %1  new: %2  pending: %3") \
                             .arg(states[oldState]) \
                             .arg(states[newState]) \
-                            .arg(states[pending]);
+                                .arg(states[pending]) << "internal" << m_state;
 #endif
 
                     switch (newState) {
@@ -208,7 +193,6 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
                     case GST_STATE_PLAYING:
                         if (m_state != QAudioDecoder::DecodingState)
                             emit stateChanged(m_state = QAudioDecoder::DecodingState);
-
                         break;
                     case GST_STATE_PAUSED:
                         if (m_state != QAudioDecoder::WaitingState)
@@ -222,10 +206,6 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
                 emit stateChanged(m_state = QAudioDecoder::StoppedState);
                 break;
 
-            case GST_MESSAGE_TAG:
-            case GST_MESSAGE_STREAM_STATUS:
-            case GST_MESSAGE_UNKNOWN:
-                break;
             case GST_MESSAGE_ERROR: {
                     GError *err;
                     gchar *debug;
@@ -249,8 +229,8 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
                     g_free (debug);
                 }
                 break;
+#ifdef DEBUG_DECODER
             case GST_MESSAGE_INFO:
-#ifdef DEBUG_PLAYBIN
                 {
                     GError *err;
                     gchar *debug;
@@ -259,31 +239,8 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
                     g_error_free (err);
                     g_free (debug);
                 }
-#endif
-                break;
-            case GST_MESSAGE_BUFFERING:
-            case GST_MESSAGE_STATE_DIRTY:
-            case GST_MESSAGE_STEP_DONE:
-            case GST_MESSAGE_CLOCK_PROVIDE:
-            case GST_MESSAGE_CLOCK_LOST:
-            case GST_MESSAGE_NEW_CLOCK:
-            case GST_MESSAGE_STRUCTURE_CHANGE:
-            case GST_MESSAGE_APPLICATION:
-            case GST_MESSAGE_ELEMENT:
                 break;
-            case GST_MESSAGE_SEGMENT_START:
-            case GST_MESSAGE_SEGMENT_DONE:
-                break;
-            case GST_MESSAGE_LATENCY:
-#if (GST_VERSION_MAJOR >= 0) &&  (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 13)
-            case GST_MESSAGE_ASYNC_START:
-            case GST_MESSAGE_ASYNC_DONE:
-#if GST_VERSION_MICRO >= 23
-            case GST_MESSAGE_REQUEST_STATE:
-#endif
 #endif
-            case GST_MESSAGE_ANY:
-                break;
             default:
                 break;
             }
@@ -315,6 +272,10 @@ void QGstreamerAudioDecoderSession::setSourceFilename(const QString &fileName)
 {
     stop();
     mDevice = 0;
+    if (m_appSrc)
+        m_appSrc->deleteLater();
+    m_appSrc = 0;
+
     bool isSignalRequired = (mSource != fileName);
     mSource = fileName;
     if (isSignalRequired)
@@ -343,9 +304,12 @@ void QGstreamerAudioDecoderSession::start()
         return;
     }
 
+    addAppSink();
+
     if (!mSource.isEmpty()) {
         g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL);
     } else if (mDevice) {
+#if defined(HAVE_GST_APPSRC)
         // make sure we can read from device
         if (!mDevice->isOpen() || !mDevice->isReadable()) {
             processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device");
@@ -357,16 +321,23 @@ void QGstreamerAudioDecoderSession::start()
         m_appSrc = new QGstAppSrc(this);
         m_appSrc->setStream(mDevice);
 
-        g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderSession::configureAppSrcElement, (gpointer)this);
         g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL);
+#endif
     } else {
         return;
     }
 
     // Set audio format
     if (m_appSink) {
-        GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat);
-        gst_app_sink_set_caps(m_appSink, caps); // appsink unrefs caps
+        if (mFormat.isValid()) {
+            setAudioFlags(false);
+            GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat);
+            gst_app_sink_set_caps(m_appSink, caps); // appsink unrefs caps
+        } else {
+            // We want whatever the native audio format is
+            setAudioFlags(true);
+            gst_app_sink_set_caps(m_appSink, NULL);
+        }
     }
 
     m_pendingState = QAudioDecoder::DecodingState;
@@ -382,6 +353,7 @@ void QGstreamerAudioDecoderSession::stop()
 {
     if (m_playbin) {
         gst_element_set_state(m_playbin, GST_STATE_NULL);
+        removeAppSink();
 
         QAudioDecoder::State oldState = m_state;
         m_pendingState = m_state = QAudioDecoder::StoppedState;
@@ -476,4 +448,48 @@ GstFlowReturn QGstreamerAudioDecoderSession::new_buffer(GstAppSink *, gpointer u
     return GST_FLOW_OK;
 }
 
+void QGstreamerAudioDecoderSession::setAudioFlags(bool wantNativeAudio)
+{
+    int flags = 0;
+    if (m_playbin) {
+        g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL);
+        // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired
+        // it prevents audio format conversion
+        flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO);
+        flags |= GST_PLAY_FLAG_AUDIO;
+        if (wantNativeAudio)
+            flags |= GST_PLAY_FLAG_NATIVE_AUDIO;
+        g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL);
+    }
+}
+
+void QGstreamerAudioDecoderSession::addAppSink()
+{
+    if (m_appSink)
+        return;
+
+    m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL);
+
+    GstAppSinkCallbacks callbacks;
+    memset(&callbacks, 0, sizeof(callbacks));
+    callbacks.new_buffer = &new_buffer;
+    gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL);
+    gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE);
+    gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE);
+
+    gst_bin_add(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink));
+    gst_element_link(m_audioConvert, GST_ELEMENT(m_appSink));
+}
+
+void QGstreamerAudioDecoderSession::removeAppSink()
+{
+    if (!m_appSink)
+        return;
+
+    gst_element_unlink(m_audioConvert, GST_ELEMENT(m_appSink));
+    gst_bin_remove(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink));
+
+    m_appSink = 0;
+}
+
 QT_END_NAMESPACE
index df8c8e8..2ec6e34 100644 (file)
@@ -111,6 +111,10 @@ signals:
 
 private:
 
+    void setAudioFlags(bool wantNativeAudio);
+    void addAppSink();
+    void removeAppSink();
+
     void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString);
 
     QAudioDecoder::State m_state;
@@ -118,6 +122,8 @@ private:
     QGstreamerBusHelper *m_busHelper;
     GstBus *m_bus;
     GstElement *m_playbin;
+    GstElement *m_outputBin;
+    GstElement *m_audioConvert;
     GstAppSink *m_appSink;
 
 #if defined(HAVE_GST_APPSRC)
index 08791ce..7dfe957 100644 (file)
@@ -167,6 +167,10 @@ void QGstAppSrc::pushDataToAppSrc()
                 qWarning()<<"appsrc: push buffer resend";
             }
         }
+
+        // After reading we might be all done
+        if (m_stream->atEnd())
+            sendEOS();
     } else if (m_stream->atEnd()) {
         sendEOS();
     }
index 286a320..d3f885a 100644 (file)
@@ -1,6 +1,7 @@
 
 TEMPLATE = subdirs
 SUBDIRS += \
+    qaudiodecoderbackend \
     qaudiodeviceinfo \
     qaudioinput \
     qaudiooutput \
index cf4e89a..f71062a 100644 (file)
@@ -50,7 +50,7 @@ QT_USE_NAMESPACE
 /*
  This is the backend conformance test.
 
- Since it relies on platform media framework and sound hardware
+ Since it relies on platform media framework
  it may be less stable.
 */
 
@@ -63,7 +63,8 @@ public slots:
     void initTestCase();
 
 private slots:
-    void decoderTest();
+    void fileTest();
+    void deviceTest();
 };
 
 void tst_QAudioDecoderBackend::init()
@@ -78,12 +79,19 @@ void tst_QAudioDecoderBackend::cleanup()
 {
 }
 
-void tst_QAudioDecoderBackend::decoderTest()
+void tst_QAudioDecoderBackend::fileTest()
 {
     QAudioDecoder d;
+    bool ok;
+    QAudioBuffer buffer;
+    quint64 duration = 0;
+    int byteCount = 0;
+    int sampleCount = 0;
+
     QVERIFY(d.state() == QAudioDecoder::StoppedState);
     QVERIFY(d.bufferAvailable() == false);
     QCOMPARE(d.sourceFilename(), QString(""));
+    QVERIFY(d.audioFormat() == QAudioFormat());
 
     // Test local file
     QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
@@ -98,19 +106,56 @@ void tst_QAudioDecoderBackend::decoderTest()
     QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
 
     d.start();
+    QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
     QTRY_VERIFY(!stateSpy.isEmpty());
     QTRY_VERIFY(!readySpy.isEmpty());
     QTRY_VERIFY(!bufferChangedSpy.isEmpty());
     QVERIFY(d.bufferAvailable());
 
-    bool ok;
-    QAudioBuffer buffer = d.read(&ok);
+    buffer = d.read(&ok);
     QVERIFY(ok);
     QVERIFY(buffer.isValid());
-    QCOMPARE(buffer.format(), d.audioFormat());
+
+    // Test file is 44.1K 16bit mono, 44094 samples
+    QCOMPARE(buffer.format().channelCount(), 1);
+    QCOMPARE(buffer.format().sampleRate(), 44100);
+    QCOMPARE(buffer.format().sampleSize(), 16);
+    QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
+    QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
+    QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono
+
+    // The decoder should still have no format set
+    QVERIFY(d.audioFormat() == QAudioFormat());
 
     QVERIFY(errorSpy.isEmpty());
 
+    duration += buffer.duration();
+    sampleCount += buffer.sampleCount();
+    byteCount += buffer.byteCount();
+
+    // Now drain the decoder
+    if (sampleCount < 44094) {
+        QTRY_COMPARE(d.bufferAvailable(), true);
+    }
+
+    while (d.bufferAvailable()) {
+        buffer = d.read(&ok);
+        QVERIFY(ok);
+        QVERIFY(buffer.isValid());
+        duration += buffer.duration();
+        sampleCount += buffer.sampleCount();
+        byteCount += buffer.byteCount();
+
+        if (sampleCount < 44094) {
+            QTRY_COMPARE(d.bufferAvailable(), true);
+        }
+    }
+
+    // Make sure the duration is roughly correct (+/- 20ms)
+    QCOMPARE(sampleCount, 44094);
+    QCOMPARE(byteCount, 44094 * 2);
+    QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
+
     d.stop();
     QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
     QVERIFY(!d.bufferAvailable());
@@ -118,21 +163,171 @@ void tst_QAudioDecoderBackend::decoderTest()
     bufferChangedSpy.clear();
     stateSpy.clear();
 
-    // Test source device
+    // change output audio format
+    QAudioFormat format;
+    format.setChannels(2);
+    format.setSampleSize(8);
+    format.setFrequency(11050);
+    format.setCodec("audio/pcm");
+    format.setSampleType(QAudioFormat::SignedInt);
+
+    d.setAudioFormat(format);
+
+    // We expect 1 second still, at 11050 * 2 samples == 22k samples.
+    // (at 1 byte/sample -> 22kb)
+
+    // Make sure it stuck
+    QVERIFY(d.audioFormat() == format);
+
+    duration = 0;
+    sampleCount = 0;
+    byteCount = 0;
+
+    d.start();
+    QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
+    QTRY_VERIFY(!stateSpy.isEmpty());
+    QTRY_VERIFY(!readySpy.isEmpty());
+    QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+    QVERIFY(d.bufferAvailable());
+
+    buffer = d.read(&ok);
+    QVERIFY(ok);
+    QVERIFY(buffer.isValid());
+    // See if we got the right format
+    QVERIFY(buffer.format() == format);
+
+    // The decoder should still have the same format
+    QVERIFY(d.audioFormat() == format);
+
+    QVERIFY(errorSpy.isEmpty());
+
+    duration += buffer.duration();
+    sampleCount += buffer.sampleCount();
+    byteCount += buffer.byteCount();
+
+    // Now drain the decoder
+    if (duration < 998000) {
+        QTRY_COMPARE(d.bufferAvailable(), true);
+    }
+
+    while (d.bufferAvailable()) {
+        buffer = d.read(&ok);
+        QVERIFY(ok);
+        QVERIFY(buffer.isValid());
+        duration += buffer.duration();
+        sampleCount += buffer.sampleCount();
+        byteCount += buffer.byteCount();
+
+        if (duration < 998000) {
+            QTRY_COMPARE(d.bufferAvailable(), true);
+        }
+    }
+
+    // Resampling might end up with fewer or more samples
+    // so be a bit sloppy
+    QVERIFY(qAbs(sampleCount - 22047) < 100);
+    QVERIFY(qAbs(byteCount - 22047) < 100);
+    QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
+
+    d.stop();
+    QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+    QVERIFY(!d.bufferAvailable());
+}
+
+void tst_QAudioDecoderBackend::deviceTest()
+{
+    QAudioDecoder d;
+    bool ok;
+    QAudioBuffer buffer;
+    quint64 duration = 0;
+    int sampleCount = 0;
+
+    QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
+    QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+    QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
+    QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
+
+    QVERIFY(d.state() == QAudioDecoder::StoppedState);
+    QVERIFY(d.bufferAvailable() == false);
+    QCOMPARE(d.sourceFilename(), QString(""));
+    QVERIFY(d.audioFormat() == QAudioFormat());
+
+    QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
     QFile file(fileInfo.absoluteFilePath());
     QVERIFY(file.open(QIODevice::ReadOnly));
     d.setSourceDevice(&file);
 
-    // change output audio format
+    QVERIFY(d.sourceDevice() == &file);
+    QVERIFY(d.sourceFilename().isEmpty());
+
+    // We haven't set the format yet
+    QVERIFY(d.audioFormat() == QAudioFormat());
+
+    d.start();
+    QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
+    QTRY_VERIFY(!stateSpy.isEmpty());
+    QTRY_VERIFY(!readySpy.isEmpty());
+    QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+    QVERIFY(d.bufferAvailable());
+
+    buffer = d.read(&ok);
+    QVERIFY(ok);
+    QVERIFY(buffer.isValid());
+
+    // Test file is 44.1K 16bit mono
+    QCOMPARE(buffer.format().channelCount(), 1);
+    QCOMPARE(buffer.format().sampleRate(), 44100);
+    QCOMPARE(buffer.format().sampleSize(), 16);
+    QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
+    QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
+
+    QVERIFY(errorSpy.isEmpty());
+
+    duration += buffer.duration();
+    sampleCount += buffer.sampleCount();
+
+    // Now drain the decoder
+    if (sampleCount < 44094) {
+        QTRY_COMPARE(d.bufferAvailable(), true);
+    }
+
+    while (d.bufferAvailable()) {
+        buffer = d.read(&ok);
+        QVERIFY(ok);
+        QVERIFY(buffer.isValid());
+        duration += buffer.duration();
+        sampleCount += buffer.sampleCount();
+        if (sampleCount < 44094) {
+            QTRY_COMPARE(d.bufferAvailable(), true);
+        }
+    }
+
+    // Make sure the duration is roughly correct (+/- 20ms)
+    QCOMPARE(sampleCount, 44094);
+    QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
+
+    d.stop();
+    QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+    QVERIFY(!d.bufferAvailable());
+    readySpy.clear();
+    bufferChangedSpy.clear();
+    stateSpy.clear();
+
+    // Now try changing formats
     QAudioFormat format;
-    format.setChannels(1);
+    format.setChannels(2);
     format.setSampleSize(8);
     format.setFrequency(8000);
     format.setCodec("audio/pcm");
     format.setSampleType(QAudioFormat::SignedInt);
+
     d.setAudioFormat(format);
 
+    // Make sure it stuck
+    QVERIFY(d.audioFormat() == format);
+
     d.start();
+    QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
     QTRY_VERIFY(!stateSpy.isEmpty());
     QTRY_VERIFY(!readySpy.isEmpty());
     QTRY_VERIFY(!bufferChangedSpy.isEmpty());
@@ -141,9 +336,18 @@ void tst_QAudioDecoderBackend::decoderTest()
     buffer = d.read(&ok);
     QVERIFY(ok);
     QVERIFY(buffer.isValid());
-    QCOMPARE(buffer.format(), d.audioFormat());
+    // See if we got the right format
+    QVERIFY(buffer.format() == format);
+
+    // The decoder should still have the same format
+    QVERIFY(d.audioFormat() == format);
 
     QVERIFY(errorSpy.isEmpty());
+
+    d.stop();
+    QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+    QVERIFY(!d.bufferAvailable());
+
 }
 
 QTEST_MAIN(tst_QAudioDecoderBackend)