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:
committed by
Qt by Nokia
parent
b9e2410a2a
commit
2ab74b7ff2
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
TEMPLATE = subdirs
|
TEMPLATE = subdirs
|
||||||
SUBDIRS += \
|
SUBDIRS += \
|
||||||
|
qaudiodecoderbackend \
|
||||||
qaudiodeviceinfo \
|
qaudiodeviceinfo \
|
||||||
qaudioinput \
|
qaudioinput \
|
||||||
qaudiooutput \
|
qaudiooutput \
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user