From 2ab74b7ff298cb15d5c82476e15e568df6f3e26e Mon Sep 17 00:00:00 2001 From: Michael Goddard Date: Fri, 17 Feb 2012 19:10:35 +1000 Subject: [PATCH] Fix a few audiodecoder things. Beef up the autotest a little, and check the conversion. Change-Id: Ifffca118e092eb6c388db50a6eb12810a87aa32a Reviewed-by: Lev Zelenskiy Reviewed-by: Michael Goddard --- src/gsttools/qgstreamermessage.cpp | 12 +- .../qgstreameraudiodecodersession.cpp | 162 +++++++------ .../qgstreameraudiodecodersession.h | 6 + src/plugins/gstreamer/qgstappsrc.cpp | 4 + tests/auto/integration/multimedia.pro | 1 + .../tst_qaudiodecoderbackend.cpp | 224 +++++++++++++++++- 6 files changed, 322 insertions(+), 87 deletions(-) diff --git a/src/gsttools/qgstreamermessage.cpp b/src/gsttools/qgstreamermessage.cpp index 31618567..02b03a37 100644 --- a/src/gsttools/qgstreamermessage.cpp +++ b/src/gsttools/qgstreamermessage.cpp @@ -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; } diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp index 88f3a0e2..518bf778 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp @@ -38,6 +38,7 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ +//#define DEBUG_DECODER #include "qgstreameraudiodecodersession.h" #include @@ -56,7 +57,7 @@ #include #include -#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 diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h index df8c8e8d..2ec6e34a 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h @@ -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) diff --git a/src/plugins/gstreamer/qgstappsrc.cpp b/src/plugins/gstreamer/qgstappsrc.cpp index 08791ce6..7dfe9579 100644 --- a/src/plugins/gstreamer/qgstappsrc.cpp +++ b/src/plugins/gstreamer/qgstappsrc.cpp @@ -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(); } diff --git a/tests/auto/integration/multimedia.pro b/tests/auto/integration/multimedia.pro index 286a320b..d3f885a3 100644 --- a/tests/auto/integration/multimedia.pro +++ b/tests/auto/integration/multimedia.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS += \ + qaudiodecoderbackend \ qaudiodeviceinfo \ qaudioinput \ qaudiooutput \ diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp index cf4e89a7..f71062af 100644 --- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp +++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp @@ -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) -- 2.34.1