summaryrefslogtreecommitdiffstats
path: root/src/multimediakit/audio/qaudiooutput_symbian_p.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimediakit/audio/qaudiooutput_symbian_p.cpp')
-rw-r--r--src/multimediakit/audio/qaudiooutput_symbian_p.cpp713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/multimediakit/audio/qaudiooutput_symbian_p.cpp b/src/multimediakit/audio/qaudiooutput_symbian_p.cpp
new file mode 100644
index 000000000..3a1bc2424
--- /dev/null
+++ b/src/multimediakit/audio/qaudiooutput_symbian_p.cpp
@@ -0,0 +1,713 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Qt Mobility Components.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qaudiooutput_symbian_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+const int UnderflowTimerInterval = 50; // ms
+
+
+//-----------------------------------------------------------------------------
+// Private class
+//-----------------------------------------------------------------------------
+
+SymbianAudioOutputPrivate::SymbianAudioOutputPrivate(
+ QAudioOutputPrivate *audioDevice)
+ : m_audioDevice(audioDevice)
+{
+
+}
+
+SymbianAudioOutputPrivate::~SymbianAudioOutputPrivate()
+{
+
+}
+
+qint64 SymbianAudioOutputPrivate::readData(char *data, qint64 len)
+{
+ Q_UNUSED(data)
+ Q_UNUSED(len)
+ return 0;
+}
+
+qint64 SymbianAudioOutputPrivate::writeData(const char *data, qint64 len)
+{
+ qint64 totalWritten = 0;
+
+ if (m_audioDevice->state() == QAudio::ActiveState ||
+ m_audioDevice->state() == QAudio::IdleState) {
+
+ while (totalWritten < len) {
+ const qint64 written = m_audioDevice->pushData(data + totalWritten,
+ len - totalWritten);
+ if (written > 0)
+ totalWritten += written;
+ else
+ break;
+ }
+ }
+
+ return totalWritten;
+}
+
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray &device)
+ : m_device(device)
+ , m_clientBufferSize(SymbianAudio::DefaultBufferSize)
+ , m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
+ , m_notifyTimer(new QTimer(this))
+ , m_lastNotifyPosition(0)
+ , m_error(QAudio::NoError)
+ , m_internalState(SymbianAudio::ClosedState)
+ , m_externalState(QAudio::StoppedState)
+ , m_pullMode(false)
+ , m_source(0)
+ , m_devSound(0)
+ , m_devSoundBuffer(0)
+ , m_devSoundBufferSize(0)
+ , m_bytesWritten(0)
+ , m_pushDataReady(false)
+ , m_bytesPadding(0)
+ , m_underflow(false)
+ , m_lastBuffer(false)
+ , m_underflowTimer(new QTimer(this))
+ , m_samplesPlayed(0)
+ , m_totalSamplesPlayed(0)
+{
+ connect(m_notifyTimer.data(), SIGNAL(timeout()),
+ this, SLOT(notifyTimerExpired()));
+
+ m_underflowTimer->setInterval(UnderflowTimerInterval);
+
+ connect(m_underflowTimer.data(), SIGNAL(timeout()), this,
+ SLOT(underflowTimerExpired()));
+}
+
+QAudioOutputPrivate::~QAudioOutputPrivate()
+{
+ close();
+}
+
+void QAudioOutputPrivate::setFormat(const QAudioFormat& fmt)
+{
+ m_format = fmt;
+}
+
+void QAudioOutputPrivate::start(QIODevice *device)
+{
+ stop();
+
+ // We have to set these before the call to open() because of the
+ // logic in initializingState()
+ m_pullMode = true;
+ m_source = device;
+
+ open();
+
+ if (SymbianAudio::ClosedState != m_internalState) {
+ connect(m_source, SIGNAL(readyRead()), this, SLOT(dataReady()));
+ m_elapsed.restart();
+ }
+}
+
+QIODevice* QAudioOutputPrivate::start()
+{
+ stop();
+
+ open();
+
+ if (SymbianAudio::ClosedState != m_internalState) {
+ m_source = new SymbianAudioOutputPrivate(this);
+ m_source->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
+ m_elapsed.restart();
+ }
+
+ return m_source;
+}
+
+void QAudioOutputPrivate::stop()
+{
+ close();
+}
+
+void QAudioOutputPrivate::reset()
+{
+#ifndef PRE_S60_52_PLATFORM
+ int err = m_devSound->flush();
+ if (err != 0)
+ setError(QAudio::FatalError);
+#else
+ m_totalSamplesPlayed += getSamplesPlayed();
+ m_devSound->stop();
+ m_bytesPadding = 0;
+ startPlayback();
+#endif
+}
+
+void QAudioOutputPrivate::suspend()
+{
+ if (SymbianAudio::ActiveState == m_internalState
+ || SymbianAudio::IdleState == m_internalState) {
+ m_underflowTimer->stop();
+ const qint64 samplesWritten = SymbianAudio::Utils::bytesToSamples(
+ m_format, m_bytesWritten);
+ const qint64 samplesPlayed = getSamplesPlayed();
+#ifdef PRE_S60_52_PLATFORM
+ m_totalSamplesPlayed += samplesPlayed;
+ m_bytesWritten = 0;
+#endif
+ const bool paused = m_devSound->pause();
+ if (paused) {
+ setState(SymbianAudio::SuspendedPausedState);
+ } else {
+ m_devSoundBuffer = 0;
+ // Calculate the amount of data dropped
+ const qint64 paddingSamples = samplesWritten - samplesPlayed;
+ Q_ASSERT(paddingSamples >= 0);
+ m_bytesPadding = SymbianAudio::Utils::samplesToBytes(m_format,
+ paddingSamples);
+ setState(SymbianAudio::SuspendedStoppedState);
+ }
+ }
+}
+
+void QAudioOutputPrivate::resume()
+{
+ if (QAudio::SuspendedState == m_externalState) {
+ if (SymbianAudio::SuspendedPausedState == m_internalState) {
+#ifndef PRE_S60_52_PLATFORM
+ setState(SymbianAudio::ActiveState);
+ if (m_devSoundBuffer != 0)
+ devsoundBufferToBeFilled(m_devSoundBuffer);
+ m_devSound->start();
+#else
+//defined in else part of macro to enable compatibility of previous code
+ m_devSound->resume();
+#endif
+ } else {
+ startPlayback();
+ }
+ }
+}
+
+int QAudioOutputPrivate::bytesFree() const
+{
+ int result = 0;
+ if (m_devSoundBuffer) {
+ const TDes8 &outputBuffer = m_devSoundBuffer->Data();
+ result = outputBuffer.MaxLength() - outputBuffer.Length();
+ }
+ return result;
+}
+
+int QAudioOutputPrivate::periodSize() const
+{
+ return bufferSize();
+}
+
+void QAudioOutputPrivate::setBufferSize(int value)
+{
+ // Note that DevSound does not allow its client to specify the buffer size.
+ // This functionality is available via custom interfaces, but since these
+ // cannot be guaranteed to work across all DevSound implementations, we
+ // do not use them here.
+ // In order to comply with the expected bevahiour of QAudioOutput, we store
+ // the value and return it from bufferSize(), but the underlying DevSound
+ // buffer size remains unchanged.
+ if (value > 0)
+ m_clientBufferSize = value;
+}
+
+int QAudioOutputPrivate::bufferSize() const
+{
+ return m_devSoundBufferSize ? m_devSoundBufferSize : m_clientBufferSize;
+}
+
+void QAudioOutputPrivate::setNotifyInterval(int ms)
+{
+ if (ms >= 0) {
+ //const int oldNotifyInterval = m_notifyInterval;
+ m_notifyInterval = ms;
+ if (m_notifyInterval && (SymbianAudio::ActiveState == m_internalState ||
+ SymbianAudio::IdleState == m_internalState))
+ m_notifyTimer->start(m_notifyInterval);
+ else
+ m_notifyTimer->stop();
+ }
+}
+
+int QAudioOutputPrivate::notifyInterval() const
+{
+ return m_notifyInterval;
+}
+
+qint64 QAudioOutputPrivate::processedUSecs() const
+{
+ int samplesPlayed = 0;
+
+ if (!m_devSound)
+ return samplesPlayed;
+
+ if (QAudio::SuspendedState != m_externalState)
+ samplesPlayed = getSamplesPlayed();
+
+ // Protect against division by zero
+ Q_ASSERT_X(m_format.frequency() > 0, Q_FUNC_INFO, "Invalid frequency");
+
+#ifndef PRE_S60_52_PLATFORM
+ const qint64 devSoundSamplesPlayed(m_devSound->samplesProcessed());
+ const qint64 result = qint64(1000000) *
+ (devSoundSamplesPlayed)
+ / m_format.frequency();
+#else
+ const qint64 result = qint64(1000000) *
+ (samplesPlayed + m_totalSamplesPlayed)
+ / m_format.frequency();
+#endif
+ return result;
+}
+
+qint64 QAudioOutputPrivate::elapsedUSecs() const
+{
+ const qint64 result = (QAudio::StoppedState == state()) ?
+ 0 : m_elapsed.elapsed() * 1000;
+ return result;
+}
+
+QAudio::Error QAudioOutputPrivate::error() const
+{
+ return m_error;
+}
+
+QAudio::State QAudioOutputPrivate::state() const
+{
+ return m_externalState;
+}
+
+QAudioFormat QAudioOutputPrivate::format() const
+{
+ return m_format;
+}
+
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void QAudioOutputPrivate::notifyTimerExpired()
+{
+ const qint64 pos = processedUSecs();
+ if (pos > m_lastNotifyPosition) {
+ int count = (pos - m_lastNotifyPosition) / (m_notifyInterval * 1000);
+ while (count--) {
+ emit notify();
+ m_lastNotifyPosition += m_notifyInterval * 1000;
+ }
+ }
+}
+
+void QAudioOutputPrivate::dataReady()
+{
+ // Client-provided QIODevice has data ready to read.
+
+ Q_ASSERT_X(m_source->bytesAvailable(), Q_FUNC_INFO,
+ "readyRead signal received, but no data available");
+
+ if (!m_bytesPadding)
+ pullData();
+}
+
+void QAudioOutputPrivate::underflowTimerExpired()
+{
+ const TInt samplesPlayed = getSamplesPlayed();
+ if (m_samplesPlayed && (samplesPlayed == m_samplesPlayed)) {
+ setError(QAudio::UnderrunError);
+#ifndef PRE_S60_52_PLATFORM
+ m_underflowTimer->stop();
+#endif
+ } else {
+ m_samplesPlayed = samplesPlayed;
+ m_underflowTimer->start();
+ }
+}
+
+void QAudioOutputPrivate::devsoundInitializeComplete(int err)
+{
+ Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
+ Q_FUNC_INFO, "Invalid state");
+
+ if (!err && m_devSound->isFormatSupported(m_format))
+ startPlayback();
+ else
+ setError(QAudio::OpenError);
+}
+
+void QAudioOutputPrivate::devsoundBufferToBeFilled(CMMFBuffer *bufferBase)
+{
+ // Following receipt of this signal, DevSound should not provide another
+ // buffer until we have returned the current one.
+ Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held");
+
+ // Will be returned to DevSoundWrapper by bufferProcessed().
+ m_devSoundBuffer = static_cast<CMMFDataBuffer*>(bufferBase);
+#ifndef PRE_S60_52_PLATFORM
+ if (m_externalState == QAudio::SuspendedState) {
+ // This condition occurs when buffertobefilled callback is received after
+ // pause command is processed.
+ return;
+ }
+#endif
+
+ if (!m_devSoundBufferSize)
+ m_devSoundBufferSize = m_devSoundBuffer->Data().MaxLength();
+
+ writePaddingData();
+
+ if (m_pullMode && isDataReady() && !m_bytesPadding)
+ pullData();
+}
+
+void QAudioOutputPrivate::devsoundPlayError(int err)
+{
+ switch (err) {
+ case KErrUnderflow:
+ m_underflow = true;
+ if (m_pullMode && !m_lastBuffer)
+ setError(QAudio::UnderrunError);
+ else
+ setState(SymbianAudio::IdleState);
+ break;
+ case KErrOverflow:
+ // Silently consume this error when in playback mode
+ break;
+ default:
+ setError(QAudio::IOError);
+ break;
+ }
+}
+
+void QAudioOutputPrivate::open()
+{
+ Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
+ Q_FUNC_INFO, "DevSound already opened");
+
+ Q_ASSERT(!m_devSound);
+ m_devSound = new SymbianAudio::DevSoundWrapper(QAudio::AudioOutput, this);
+
+ connect(m_devSound, SIGNAL(initializeComplete(int)),
+ this, SLOT(devsoundInitializeComplete(int)));
+ connect(m_devSound, SIGNAL(bufferToBeProcessed(CMMFBuffer *)),
+ this, SLOT(devsoundBufferToBeFilled(CMMFBuffer *)));
+ connect(m_devSound, SIGNAL(processingError(int)),
+ this, SLOT(devsoundPlayError(int)));
+
+ setState(SymbianAudio::InitializingState);
+ m_devSound->initialize(m_format.codec());
+}
+
+void QAudioOutputPrivate::startPlayback()
+{
+ bool ok = m_devSound->setFormat(m_format);
+ if (ok)
+ ok = m_devSound->start();
+
+ if (ok) {
+ if (isDataReady())
+ setState(SymbianAudio::ActiveState);
+ else
+ setState(SymbianAudio::IdleState);
+
+ if (m_notifyInterval)
+ m_notifyTimer->start(m_notifyInterval);
+ m_underflow = false;
+
+ Q_ASSERT(m_devSound->samplesProcessed() == 0);
+
+ writePaddingData();
+
+ if (m_pullMode && m_source->bytesAvailable() && !m_bytesPadding)
+ dataReady();
+ } else {
+ setError(QAudio::OpenError);
+ close();
+ }
+}
+
+void QAudioOutputPrivate::writePaddingData()
+{
+ // See comments in suspend()
+
+ while (m_devSoundBuffer && m_bytesPadding) {
+ if (SymbianAudio::IdleState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+
+ TDes8 &outputBuffer = m_devSoundBuffer->Data();
+ const qint64 outputBytes = bytesFree();
+ const qint64 paddingBytes = outputBytes < m_bytesPadding ?
+ outputBytes : m_bytesPadding;
+ unsigned char *ptr = const_cast<unsigned char*>(outputBuffer.Ptr());
+ Mem::FillZ(ptr, paddingBytes);
+ outputBuffer.SetLength(outputBuffer.Length() + paddingBytes);
+ m_bytesPadding -= paddingBytes;
+ Q_ASSERT(m_bytesPadding >= 0);
+
+ if (m_pullMode && m_source->atEnd())
+ lastBufferFilled();
+ if ((paddingBytes == outputBytes) || !m_bytesPadding)
+ bufferFilled();
+ }
+}
+
+qint64 QAudioOutputPrivate::pushData(const char *data, qint64 len)
+{
+ // Data has been written to SymbianAudioOutputPrivate
+
+ Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO,
+ "pushData called when in pull mode");
+
+ const unsigned char *const inputPtr =
+ reinterpret_cast<const unsigned char*>(data);
+ qint64 bytesWritten = 0;
+
+ if (SymbianAudio::IdleState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+
+ while (m_devSoundBuffer && (bytesWritten < len)) {
+ // writePaddingData() is called from BufferToBeFilled(), so we should
+ // never have any padding data left at this point.
+ Q_ASSERT_X(0 == m_bytesPadding, Q_FUNC_INFO,
+ "Padding bytes remaining in pushData");
+
+ TDes8 &outputBuffer = m_devSoundBuffer->Data();
+
+ const qint64 outputBytes = bytesFree();
+ const qint64 inputBytes = len - bytesWritten;
+ const qint64 copyBytes = outputBytes < inputBytes ?
+ outputBytes : inputBytes;
+
+ outputBuffer.Append(inputPtr + bytesWritten, copyBytes);
+ bytesWritten += copyBytes;
+
+ bufferFilled();
+ }
+
+ m_pushDataReady = (bytesWritten < len);
+
+ // If DevSound is still initializing (m_internalState == InitializingState),
+ // we cannot transition m_internalState to ActiveState, but we must emit
+ // an (external) state change from IdleState to ActiveState. The following
+ // call triggers this signal.
+ setState(m_internalState);
+
+ return bytesWritten;
+}
+
+void QAudioOutputPrivate::pullData()
+{
+ Q_ASSERT_X(m_pullMode, Q_FUNC_INFO,
+ "pullData called when in push mode");
+
+ // writePaddingData() is called by BufferToBeFilled() before pullData(),
+ // so we should never have any padding data left at this point.
+ Q_ASSERT_X(0 == m_bytesPadding, Q_FUNC_INFO,
+ "Padding bytes remaining in pullData");
+
+ qint64 inputBytes = m_source->bytesAvailable();
+ while (m_devSoundBuffer && inputBytes) {
+ if (SymbianAudio::IdleState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+
+ TDes8 &outputBuffer = m_devSoundBuffer->Data();
+
+ const qint64 outputBytes = bytesFree();
+ const qint64 copyBytes = outputBytes < inputBytes ?
+ outputBytes : inputBytes;
+
+ char *outputPtr = (char*)(outputBuffer.Ptr() + outputBuffer.Length());
+ const qint64 bytesCopied = m_source->read(outputPtr, copyBytes);
+
+ //Partial buffers can be sent to DevSound. This assert not required.
+ //Q_ASSERT(bytesCopied == copyBytes);
+ outputBuffer.SetLength(outputBuffer.Length() + bytesCopied);
+ inputBytes -= bytesCopied;
+
+ if(bytesCopied == 0)
+ return;
+
+ if (m_source->atEnd())
+ lastBufferFilled();
+ else
+ bufferFilled();
+ }
+}
+
+void QAudioOutputPrivate::bufferFilled()
+{
+ Q_ASSERT_X(m_devSoundBuffer, Q_FUNC_INFO, "No buffer to return");
+
+ const TDes8 &outputBuffer = m_devSoundBuffer->Data();
+ m_bytesWritten += outputBuffer.Length();
+
+ m_devSoundBuffer = 0;
+
+ m_samplesPlayed = getSamplesPlayed();
+ m_underflowTimer->start();
+
+ if (QAudio::UnderrunError == m_error)
+ m_error = QAudio::NoError;
+
+ m_devSound->bufferProcessed();
+}
+
+void QAudioOutputPrivate::lastBufferFilled()
+{
+ Q_ASSERT_X(m_devSoundBuffer, Q_FUNC_INFO, "No buffer to fill");
+ Q_ASSERT_X(!m_lastBuffer, Q_FUNC_INFO, "Last buffer already sent");
+ m_lastBuffer = true;
+ m_devSoundBuffer->SetLastBuffer(ETrue);
+ bufferFilled();
+}
+
+void QAudioOutputPrivate::close()
+{
+ m_lastNotifyPosition = 0;
+ m_underflowTimer->stop();
+
+ m_error = QAudio::NoError;
+
+ if (m_devSound)
+ m_devSound->stop();
+ delete m_devSound;
+ m_devSound = 0;
+
+ m_devSoundBuffer = 0;
+ m_devSoundBufferSize = 0;
+
+ if (!m_pullMode) // m_source is owned
+ delete m_source;
+ m_pullMode = false;
+ m_source = 0;
+
+ m_bytesWritten = 0;
+ m_pushDataReady = false;
+ m_bytesPadding = 0;
+ m_underflow = false;
+ m_lastBuffer = false;
+ m_samplesPlayed = 0;
+ m_totalSamplesPlayed = 0;
+
+ setState(SymbianAudio::ClosedState);
+}
+
+qint64 QAudioOutputPrivate::getSamplesPlayed() const
+{
+ qint64 result = 0;
+ if (m_devSound) {
+ const qint64 samplesWritten = SymbianAudio::Utils::bytesToSamples(
+ m_format, m_bytesWritten);
+
+ if (m_underflow) {
+ result = samplesWritten;
+ } else {
+ // This is necessary because some DevSound implementations report
+ // that they have played more data than has actually been provided to them
+ // by the client.
+ const qint64 devSoundSamplesPlayed(m_devSound->samplesProcessed());
+ result = qMin(devSoundSamplesPlayed, samplesWritten);
+ }
+ }
+ return result;
+}
+
+void QAudioOutputPrivate::setError(QAudio::Error error)
+{
+ m_error = error;
+
+ // Although no state transition actually occurs here, a stateChanged event
+ // must be emitted to inform the client that the call to start() was
+ // unsuccessful.
+ if (QAudio::OpenError == error) {
+ emit stateChanged(QAudio::StoppedState);
+ } else {
+ if (QAudio::UnderrunError == error)
+ setState(SymbianAudio::IdleState);
+ else
+ // Close the DevSound instance. This causes a transition to
+ // StoppedState. This must be done asynchronously in case the
+ // current function was called from a DevSound event handler, in which
+ // case deleting the DevSound instance may cause an exception.
+ QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
+ }
+}
+
+void QAudioOutputPrivate::setState(SymbianAudio::State newInternalState)
+{
+ const QAudio::State oldExternalState = m_externalState;
+ m_internalState = newInternalState;
+ m_externalState = SymbianAudio::Utils::stateNativeToQt(m_internalState);
+
+ if (m_externalState != QAudio::ActiveState &&
+ m_externalState != QAudio::IdleState)
+ m_notifyTimer->stop();
+
+ if (m_externalState != oldExternalState)
+ emit stateChanged(m_externalState);
+}
+
+bool QAudioOutputPrivate::isDataReady() const
+{
+ return (m_source && m_source->bytesAvailable())
+ || m_bytesPadding
+ || m_pushDataReady;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qaudiooutput_symbian_p.cpp"