Fix a few audiodecoder things.

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>
This commit is contained in:
Michael Goddard
2012-02-17 19:10:35 +10:00
committed by Qt by Nokia
parent b9e2410a2a
commit 2ab74b7ff2
6 changed files with 355 additions and 120 deletions

View File

@@ -84,11 +84,15 @@ GstMessage* QGstreamerMessage::rawMessage() const
QGstreamerMessage& QGstreamerMessage::operator=(QGstreamerMessage const& rhs) QGstreamerMessage& QGstreamerMessage::operator=(QGstreamerMessage const& rhs)
{ {
if (rhs.m_message != m_message) {
if (rhs.m_message != 0)
gst_message_ref(rhs.m_message);
if (m_message != 0) if (m_message != 0)
gst_message_unref(m_message); gst_message_unref(m_message);
if ((m_message = rhs.m_message) != 0) m_message = rhs.m_message;
gst_message_ref(m_message); }
return *this; return *this;
} }

View File

@@ -38,6 +38,7 @@
** $QT_END_LICENSE$ ** $QT_END_LICENSE$
** **
****************************************************************************/ ****************************************************************************/
//#define DEBUG_DECODER
#include "qgstreameraudiodecodersession.h" #include "qgstreameraudiodecodersession.h"
#include <private/qgstreamerbushelper_p.h> #include <private/qgstreamerbushelper_p.h>
@@ -56,7 +57,7 @@
#include <QtCore/qstandardpaths.h> #include <QtCore/qstandardpaths.h>
#include <QtCore/qurl.h> #include <QtCore/qurl.h>
#define MAX_BUFFERS_IN_QUEUE 5 #define MAX_BUFFERS_IN_QUEUE 4
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -79,6 +80,8 @@ QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent)
m_busHelper(0), m_busHelper(0),
m_bus(0), m_bus(0),
m_playbin(0), m_playbin(0),
m_outputBin(0),
m_audioConvert(0),
m_appSink(0), m_appSink(0),
#if defined(HAVE_GST_APPSRC) #if defined(HAVE_GST_APPSRC)
m_appSrc(0), m_appSrc(0),
@@ -86,55 +89,31 @@ QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent)
mDevice(0), mDevice(0),
m_buffersAvailable(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 // Create pipeline here
m_playbin = gst_element_factory_make("playbin2", NULL); m_playbin = gst_element_factory_make("playbin2", NULL);
if (m_playbin != 0) { if (m_playbin != 0) {
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);
GstElement *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));
// add ghostpad
GstPad *pad = gst_element_get_static_pad(audioConvert, "sink");
Q_ASSERT(pad);
gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("sink", pad));
gst_object_unref(GST_OBJECT(pad));
// Sort out messages // Sort out messages
m_bus = gst_element_get_bus(m_playbin); m_bus = gst_element_get_bus(m_playbin);
m_busHelper = new QGstreamerBusHelper(m_bus, this); m_busHelper = new QGstreamerBusHelper(m_bus, this);
m_busHelper->installMessageFilter(this); m_busHelper->installMessageFilter(this);
g_object_set(G_OBJECT(m_playbin), "audio-sink", bin, NULL); // Set the rest of the pipeline up
setAudioFlags(true);
m_audioConvert = gst_element_factory_make("audioconvert", NULL);
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(m_audioConvert, "sink");
Q_ASSERT(pad);
gst_element_add_pad(GST_ELEMENT(m_outputBin), gst_ghost_pad_new("sink", pad));
gst_object_unref(GST_OBJECT(pad));
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% // Set volume to 100%
gdouble volume = 1.0; gdouble volume = 1.0;
@@ -148,9 +127,11 @@ QGstreamerAudioDecoderSession::~QGstreamerAudioDecoderSession()
stop(); stop();
delete m_busHelper; 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_bus));
gst_object_unref(GST_OBJECT(m_playbin)); 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(object);
Q_UNUSED(pspec); Q_UNUSED(pspec);
// In case we switch from appsrc to not
if (!self->appsrc())
return;
if (self->appsrc()->isReady()) if (self->appsrc()->isReady())
return; return;
@@ -185,14 +170,14 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
gst_message_parse_state_changed(gm, &oldState, &newState, &pending); gst_message_parse_state_changed(gm, &oldState, &newState, &pending);
#ifdef DEBUG_PLAYBIN #ifdef DEBUG_DECODER
QStringList states; QStringList states;
states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; 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") \ qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \
.arg(states[oldState]) \ .arg(states[oldState]) \
.arg(states[newState]) \ .arg(states[newState]) \
.arg(states[pending]); .arg(states[pending]) << "internal" << m_state;
#endif #endif
switch (newState) { switch (newState) {
@@ -208,7 +193,6 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
case GST_STATE_PLAYING: case GST_STATE_PLAYING:
if (m_state != QAudioDecoder::DecodingState) if (m_state != QAudioDecoder::DecodingState)
emit stateChanged(m_state = QAudioDecoder::DecodingState); emit stateChanged(m_state = QAudioDecoder::DecodingState);
break; break;
case GST_STATE_PAUSED: case GST_STATE_PAUSED:
if (m_state != QAudioDecoder::WaitingState) if (m_state != QAudioDecoder::WaitingState)
@@ -222,10 +206,6 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
emit stateChanged(m_state = QAudioDecoder::StoppedState); emit stateChanged(m_state = QAudioDecoder::StoppedState);
break; break;
case GST_MESSAGE_TAG:
case GST_MESSAGE_STREAM_STATUS:
case GST_MESSAGE_UNKNOWN:
break;
case GST_MESSAGE_ERROR: { case GST_MESSAGE_ERROR: {
GError *err; GError *err;
gchar *debug; gchar *debug;
@@ -249,8 +229,8 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
g_free (debug); g_free (debug);
} }
break; break;
#ifdef DEBUG_DECODER
case GST_MESSAGE_INFO: case GST_MESSAGE_INFO:
#ifdef DEBUG_PLAYBIN
{ {
GError *err; GError *err;
gchar *debug; gchar *debug;
@@ -259,31 +239,8 @@ bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &m
g_error_free (err); g_error_free (err);
g_free (debug); g_free (debug);
} }
break;
#endif #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: default:
break; break;
} }
@@ -315,6 +272,10 @@ void QGstreamerAudioDecoderSession::setSourceFilename(const QString &fileName)
{ {
stop(); stop();
mDevice = 0; mDevice = 0;
if (m_appSrc)
m_appSrc->deleteLater();
m_appSrc = 0;
bool isSignalRequired = (mSource != fileName); bool isSignalRequired = (mSource != fileName);
mSource = fileName; mSource = fileName;
if (isSignalRequired) if (isSignalRequired)
@@ -343,9 +304,12 @@ void QGstreamerAudioDecoderSession::start()
return; return;
} }
addAppSink();
if (!mSource.isEmpty()) { if (!mSource.isEmpty()) {
g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL); g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL);
} else if (mDevice) { } else if (mDevice) {
#if defined(HAVE_GST_APPSRC)
// make sure we can read from device // make sure we can read from device
if (!mDevice->isOpen() || !mDevice->isReadable()) { if (!mDevice->isOpen() || !mDevice->isReadable()) {
processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device"); processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device");
@@ -357,16 +321,23 @@ void QGstreamerAudioDecoderSession::start()
m_appSrc = new QGstAppSrc(this); m_appSrc = new QGstAppSrc(this);
m_appSrc->setStream(mDevice); 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); g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL);
#endif
} else { } else {
return; return;
} }
// Set audio format // Set audio format
if (m_appSink) { if (m_appSink) {
if (mFormat.isValid()) {
setAudioFlags(false);
GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat); GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat);
gst_app_sink_set_caps(m_appSink, caps); // appsink unrefs caps 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; m_pendingState = QAudioDecoder::DecodingState;
@@ -382,6 +353,7 @@ void QGstreamerAudioDecoderSession::stop()
{ {
if (m_playbin) { if (m_playbin) {
gst_element_set_state(m_playbin, GST_STATE_NULL); gst_element_set_state(m_playbin, GST_STATE_NULL);
removeAppSink();
QAudioDecoder::State oldState = m_state; QAudioDecoder::State oldState = m_state;
m_pendingState = m_state = QAudioDecoder::StoppedState; m_pendingState = m_state = QAudioDecoder::StoppedState;
@@ -476,4 +448,48 @@ GstFlowReturn QGstreamerAudioDecoderSession::new_buffer(GstAppSink *, gpointer u
return GST_FLOW_OK; 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 QT_END_NAMESPACE

View File

@@ -111,6 +111,10 @@ signals:
private: private:
void setAudioFlags(bool wantNativeAudio);
void addAppSink();
void removeAppSink();
void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString); void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString);
QAudioDecoder::State m_state; QAudioDecoder::State m_state;
@@ -118,6 +122,8 @@ private:
QGstreamerBusHelper *m_busHelper; QGstreamerBusHelper *m_busHelper;
GstBus *m_bus; GstBus *m_bus;
GstElement *m_playbin; GstElement *m_playbin;
GstElement *m_outputBin;
GstElement *m_audioConvert;
GstAppSink *m_appSink; GstAppSink *m_appSink;
#if defined(HAVE_GST_APPSRC) #if defined(HAVE_GST_APPSRC)

View File

@@ -167,6 +167,10 @@ void QGstAppSrc::pushDataToAppSrc()
qWarning()<<"appsrc: push buffer resend"; qWarning()<<"appsrc: push buffer resend";
} }
} }
// After reading we might be all done
if (m_stream->atEnd())
sendEOS();
} else if (m_stream->atEnd()) { } else if (m_stream->atEnd()) {
sendEOS(); sendEOS();
} }

View File

@@ -1,6 +1,7 @@
TEMPLATE = subdirs TEMPLATE = subdirs
SUBDIRS += \ SUBDIRS += \
qaudiodecoderbackend \
qaudiodeviceinfo \ qaudiodeviceinfo \
qaudioinput \ qaudioinput \
qaudiooutput \ qaudiooutput \

View File

@@ -50,7 +50,7 @@ QT_USE_NAMESPACE
/* /*
This is the backend conformance test. 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. it may be less stable.
*/ */
@@ -63,7 +63,8 @@ public slots:
void initTestCase(); void initTestCase();
private slots: private slots:
void decoderTest(); void fileTest();
void deviceTest();
}; };
void tst_QAudioDecoderBackend::init() void tst_QAudioDecoderBackend::init()
@@ -78,12 +79,19 @@ void tst_QAudioDecoderBackend::cleanup()
{ {
} }
void tst_QAudioDecoderBackend::decoderTest() void tst_QAudioDecoderBackend::fileTest()
{ {
QAudioDecoder d; QAudioDecoder d;
bool ok;
QAudioBuffer buffer;
quint64 duration = 0;
int byteCount = 0;
int sampleCount = 0;
QVERIFY(d.state() == QAudioDecoder::StoppedState); QVERIFY(d.state() == QAudioDecoder::StoppedState);
QVERIFY(d.bufferAvailable() == false); QVERIFY(d.bufferAvailable() == false);
QCOMPARE(d.sourceFilename(), QString("")); QCOMPARE(d.sourceFilename(), QString(""));
QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file // Test local file
QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME)); QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
@@ -98,41 +106,7 @@ void tst_QAudioDecoderBackend::decoderTest()
QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State))); QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
d.start(); d.start();
QTRY_VERIFY(!stateSpy.isEmpty()); QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty());
QVERIFY(d.bufferAvailable());
bool ok;
QAudioBuffer buffer = d.read(&ok);
QVERIFY(ok);
QVERIFY(buffer.isValid());
QCOMPARE(buffer.format(), d.audioFormat());
QVERIFY(errorSpy.isEmpty());
d.stop();
QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
QVERIFY(!d.bufferAvailable());
readySpy.clear();
bufferChangedSpy.clear();
stateSpy.clear();
// Test source device
QFile file(fileInfo.absoluteFilePath());
QVERIFY(file.open(QIODevice::ReadOnly));
d.setSourceDevice(&file);
// change output audio format
QAudioFormat format;
format.setChannels(1);
format.setSampleSize(8);
format.setFrequency(8000);
format.setCodec("audio/pcm");
format.setSampleType(QAudioFormat::SignedInt);
d.setAudioFormat(format);
d.start();
QTRY_VERIFY(!stateSpy.isEmpty()); QTRY_VERIFY(!stateSpy.isEmpty());
QTRY_VERIFY(!readySpy.isEmpty()); QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty()); QTRY_VERIFY(!bufferChangedSpy.isEmpty());
@@ -141,9 +115,239 @@ void tst_QAudioDecoderBackend::decoderTest()
buffer = d.read(&ok); buffer = d.read(&ok);
QVERIFY(ok); QVERIFY(ok);
QVERIFY(buffer.isValid()); 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()); 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());
readySpy.clear();
bufferChangedSpy.clear();
stateSpy.clear();
// 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);
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(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());
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());
d.stop();
QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
QVERIFY(!d.bufferAvailable());
} }
QTEST_MAIN(tst_QAudioDecoderBackend) QTEST_MAIN(tst_QAudioDecoderBackend)