summaryrefslogtreecommitdiffstats
path: root/src/multimedia
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia')
-rw-r--r--src/multimedia/audio/audio.pri15
-rw-r--r--src/multimedia/audio/qaudio_symbian_p.cpp395
-rw-r--r--src/multimedia/audio/qaudio_symbian_p.h156
-rw-r--r--src/multimedia/audio/qaudiodevicefactory.cpp16
-rw-r--r--src/multimedia/audio/qaudiodeviceinfo_symbian_p.cpp200
-rw-r--r--src/multimedia/audio/qaudiodeviceinfo_symbian_p.h107
-rw-r--r--src/multimedia/audio/qaudioinput_symbian_p.cpp594
-rw-r--r--src/multimedia/audio/qaudioinput_symbian_p.h188
-rw-r--r--src/multimedia/audio/qaudiooutput_symbian_p.cpp696
-rw-r--r--src/multimedia/audio/qaudiooutput_symbian_p.h210
10 files changed, 2571 insertions, 6 deletions
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri
index 625b871c..ae28a26 100644
--- a/src/multimedia/audio/audio.pri
+++ b/src/multimedia/audio/audio.pri
@@ -41,6 +41,21 @@ mac {
!wince*:LIBS += -lwinmm
wince*:LIBS += -lcoredll
+} else:symbian {
+ INCLUDEPATH += /epoc32/include/mmf/common
+ INCLUDEPATH += /epoc32/include/mmf/server
+
+ HEADERS += $$PWD/qaudio_symbian_p.h \
+ $$PWD/qaudiodeviceinfo_symbian_p.h \
+ $$PWD/qaudioinput_symbian_p.h \
+ $$PWD/qaudiooutput_symbian_p.h
+
+ SOURCES += $$PWD/qaudio_symbian_p.cpp \
+ $$PWD/qaudiodeviceinfo_symbian_p.cpp \
+ $$PWD/qaudioinput_symbian_p.cpp \
+ $$PWD/qaudiooutput_symbian_p.cpp
+
+ LIBS += -lmmfdevsound
} else:unix {
unix:contains(QT_CONFIG, alsa) {
linux-*|freebsd-*|openbsd-*:{
diff --git a/src/multimedia/audio/qaudio_symbian_p.cpp b/src/multimedia/audio/qaudio_symbian_p.cpp
new file mode 100644
index 0000000..58e3745
--- /dev/null
+++ b/src/multimedia/audio/qaudio_symbian_p.cpp
@@ -0,0 +1,395 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qaudio_symbian_p.h"
+#include <mmffourcc.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace SymbianAudio {
+
+DevSoundCapabilities::DevSoundCapabilities(CMMFDevSound &devsound,
+ QAudio::Mode mode)
+{
+ QT_TRAP_THROWING(constructL(devsound, mode));
+}
+
+DevSoundCapabilities::~DevSoundCapabilities()
+{
+ m_fourCC.Close();
+}
+
+void DevSoundCapabilities::constructL(CMMFDevSound &devsound,
+ QAudio::Mode mode)
+{
+ m_caps = devsound.Capabilities();
+
+ TMMFPrioritySettings settings;
+
+ switch (mode) {
+ case QAudio::AudioOutput:
+ settings.iState = EMMFStatePlaying;
+ devsound.GetSupportedInputDataTypesL(m_fourCC, settings);
+ break;
+
+ case QAudio::AudioInput:
+ settings.iState = EMMFStateRecording;
+ devsound.GetSupportedInputDataTypesL(m_fourCC, settings);
+ break;
+
+ default:
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid mode");
+ }
+}
+
+namespace Utils {
+
+//-----------------------------------------------------------------------------
+// Static data
+//-----------------------------------------------------------------------------
+
+// Sample rate / frequency
+
+typedef TMMFSampleRate SampleRateNative;
+typedef int SampleRateQt;
+
+const int SampleRateCount = 12;
+
+const SampleRateNative SampleRateListNative[SampleRateCount] = {
+ EMMFSampleRate8000Hz
+ , EMMFSampleRate11025Hz
+ , EMMFSampleRate12000Hz
+ , EMMFSampleRate16000Hz
+ , EMMFSampleRate22050Hz
+ , EMMFSampleRate24000Hz
+ , EMMFSampleRate32000Hz
+ , EMMFSampleRate44100Hz
+ , EMMFSampleRate48000Hz
+ , EMMFSampleRate64000Hz
+ , EMMFSampleRate88200Hz
+ , EMMFSampleRate96000Hz
+};
+
+const SampleRateQt SampleRateListQt[SampleRateCount] = {
+ 8000
+ , 11025
+ , 12000
+ , 16000
+ , 22050
+ , 24000
+ , 32000
+ , 44100
+ , 48000
+ , 64000
+ , 88200
+ , 96000
+};
+
+// Channels
+
+typedef TMMFMonoStereo ChannelsNative;
+typedef int ChannelsQt;
+
+const int ChannelsCount = 2;
+
+const ChannelsNative ChannelsListNative[ChannelsCount] = {
+ EMMFMono
+ , EMMFStereo
+};
+
+const ChannelsQt ChannelsListQt[ChannelsCount] = {
+ 1
+ , 2
+};
+
+// Encoding
+
+const int EncodingCount = 6;
+
+const TUint32 EncodingFourCC[EncodingCount] = {
+ KMMFFourCCCodePCM8 // 0
+ , KMMFFourCCCodePCMU8 // 1
+ , KMMFFourCCCodePCM16 // 2
+ , KMMFFourCCCodePCMU16 // 3
+ , KMMFFourCCCodePCM16B // 4
+ , KMMFFourCCCodePCMU16B // 5
+};
+
+// The characterised DevSound API specification states that the iEncoding
+// field in TMMFCapabilities is ignored, and that the FourCC should be used
+// to specify the PCM encoding.
+// See "SGL.GT0287.102 Multimedia DevSound Baseline Compatibility.doc" in the
+// mm_info/mm_docs repository.
+const TMMFSoundEncoding EncodingNative[EncodingCount] = {
+ EMMFSoundEncoding16BitPCM // 0
+ , EMMFSoundEncoding16BitPCM // 1
+ , EMMFSoundEncoding16BitPCM // 2
+ , EMMFSoundEncoding16BitPCM // 3
+ , EMMFSoundEncoding16BitPCM // 4
+ , EMMFSoundEncoding16BitPCM // 5
+};
+
+
+const int EncodingSampleSize[EncodingCount] = {
+ 8 // 0
+ , 8 // 1
+ , 16 // 2
+ , 16 // 3
+ , 16 // 4
+ , 16 // 5
+};
+
+const QAudioFormat::Endian EncodingByteOrder[EncodingCount] = {
+ QAudioFormat::LittleEndian // 0
+ , QAudioFormat::LittleEndian // 1
+ , QAudioFormat::LittleEndian // 2
+ , QAudioFormat::LittleEndian // 3
+ , QAudioFormat::BigEndian // 4
+ , QAudioFormat::BigEndian // 5
+};
+
+const QAudioFormat::SampleType EncodingSampleType[EncodingCount] = {
+ QAudioFormat::SignedInt // 0
+ , QAudioFormat::UnSignedInt // 1
+ , QAudioFormat::SignedInt // 2
+ , QAudioFormat::UnSignedInt // 3
+ , QAudioFormat::SignedInt // 4
+ , QAudioFormat::UnSignedInt // 5
+};
+
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+// Helper functions for implementing parameter conversions
+
+template<typename Input>
+bool findValue(const Input *inputArray, int length, Input input, int &index) {
+ bool result = false;
+ for (int i=0; !result && i<length; ++i)
+ if (inputArray[i] == input) {
+ index = i;
+ result = true;
+ }
+ return result;
+}
+
+template<typename Input, typename Output>
+bool convertValue(const Input *inputArray, const Output *outputArray,
+ int length, Input input, Output &output) {
+ int index;
+ const bool result = findValue<Input>(inputArray, length, input, index);
+ if (result)
+ output = outputArray[index];
+ return result;
+}
+
+/**
+ * Macro which is used to generate the implementation of the conversion
+ * functions. The implementation is just a wrapper around the templated
+ * convertValue function, e.g.
+ *
+ * CONVERSION_FUNCTION_IMPL(SampleRate, Qt, Native)
+ *
+ * expands to
+ *
+ * bool SampleRateQtToNative(int input, TMMFSampleRate &output) {
+ * return convertValue<SampleRateQt, SampleRateNative>
+ * (SampleRateListQt, SampleRateListNative, SampleRateCount,
+ * input, output);
+ * }
+ */
+#define CONVERSION_FUNCTION_IMPL(FieldLc, Field, Input, Output) \
+bool FieldLc##Input##To##Output(Field##Input input, Field##Output &output) { \
+ return convertValue<Field##Input, Field##Output>(Field##List##Input, \
+ Field##List##Output, Field##Count, input, output); \
+}
+
+//-----------------------------------------------------------------------------
+// Local helper functions
+//-----------------------------------------------------------------------------
+
+CONVERSION_FUNCTION_IMPL(sampleRate, SampleRate, Qt, Native)
+CONVERSION_FUNCTION_IMPL(sampleRate, SampleRate, Native, Qt)
+CONVERSION_FUNCTION_IMPL(channels, Channels, Qt, Native)
+CONVERSION_FUNCTION_IMPL(channels, Channels, Native, Qt)
+
+bool sampleInfoQtToNative(int inputSampleSize,
+ QAudioFormat::Endian inputByteOrder,
+ QAudioFormat::SampleType inputSampleType,
+ TUint32 &outputFourCC,
+ TMMFSoundEncoding &outputEncoding) {
+
+ bool found = false;
+
+ for (int i=0; i<EncodingCount && !found; ++i) {
+ if ( EncodingSampleSize[i] == inputSampleSize
+ && EncodingByteOrder[i] == inputByteOrder
+ && EncodingSampleType[i] == inputSampleType) {
+ outputFourCC = EncodingFourCC[i];
+ outputEncoding = EncodingNative[i]; // EMMFSoundEncoding16BitPCM
+ found = true;
+ }
+ }
+
+ return found;
+}
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+void capabilitiesNativeToQt(const DevSoundCapabilities &caps,
+ QList<int> &frequencies,
+ QList<int> &channels,
+ QList<int> &sampleSizes,
+ QList<QAudioFormat::Endian> &byteOrders,
+ QList<QAudioFormat::SampleType> &sampleTypes) {
+
+ frequencies.clear();
+ sampleSizes.clear();
+ byteOrders.clear();
+ sampleTypes.clear();
+ channels.clear();
+
+ for (int i=0; i<SampleRateCount; ++i)
+ if (caps.caps().iRate & SampleRateListNative[i])
+ frequencies += SampleRateListQt[i];
+
+ for (int i=0; i<ChannelsCount; ++i)
+ if (caps.caps().iChannels & ChannelsListNative[i])
+ channels += ChannelsListQt[i];
+
+ for (int i=0; i<EncodingCount; ++i) {
+ if (caps.fourCC().Find(EncodingFourCC[i]) != KErrNotFound) {
+ sampleSizes += EncodingSampleSize[i];
+ byteOrders += EncodingByteOrder[i];
+ sampleTypes += EncodingSampleType[i];
+ }
+ }
+
+}
+
+bool isFormatSupported(const QAudioFormat &formatQt,
+ const DevSoundCapabilities &caps) {
+ TMMFCapabilities formatNative;
+ TUint32 fourCC;
+
+ bool result = false;
+ if (formatQt.codec() == "audio/pcm" &&
+ formatQtToNative(formatQt, fourCC, formatNative)) {
+ result =
+ (formatNative.iRate & caps.caps().iRate)
+ && (formatNative.iChannels & caps.caps().iChannels)
+ && (caps.fourCC().Find(fourCC) != KErrNotFound);
+ }
+ return result;
+}
+
+bool formatQtToNative(const QAudioFormat &inputFormat,
+ TUint32 &outputFourCC,
+ TMMFCapabilities &outputFormat) {
+
+ bool result = false;
+
+ // Need to use temporary variables because TMMFCapabilities fields are all
+ // TInt, rather than MMF enumerated types.
+ TMMFSampleRate outputSampleRate;
+ TMMFMonoStereo outputChannels;
+ TMMFSoundEncoding outputEncoding;
+
+ if (inputFormat.codec() == "audio/pcm") {
+ result =
+ sampleRateQtToNative(inputFormat.frequency(), outputSampleRate)
+ && channelsQtToNative(inputFormat.channels(), outputChannels)
+ && sampleInfoQtToNative(inputFormat.sampleSize(),
+ inputFormat.byteOrder(),
+ inputFormat.sampleType(),
+ outputFourCC,
+ outputEncoding);
+ }
+
+ if (result) {
+ outputFormat.iRate = outputSampleRate;
+ outputFormat.iChannels = outputChannels;
+ outputFormat.iEncoding = outputEncoding;
+ }
+
+ return result;
+}
+
+QAudio::State stateNativeToQt(State nativeState,
+ QAudio::State initializingState)
+{
+ switch (nativeState) {
+ case ClosedState:
+ return QAudio::StoppedState;
+ case InitializingState:
+ return initializingState;
+ case ActiveState:
+ return QAudio::ActiveState;
+ case IdleState:
+ return QAudio::IdleState;
+ case SuspendedState:
+ return QAudio::SuspendedState;
+ default:
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid state");
+ return QAudio::StoppedState; // suppress compiler warning
+ }
+}
+
+qint64 bytesToSamples(const QAudioFormat &format, qint64 length)
+{
+ return length / ((format.sampleSize() / 8) * format.channels());
+}
+
+qint64 samplesToBytes(const QAudioFormat &format, qint64 samples)
+{
+ return samples * (format.sampleSize() / 8) * format.channels();
+}
+
+} // namespace Utils
+} // namespace SymbianAudio
+
+QT_END_NAMESPACE
+
+
diff --git a/src/multimedia/audio/qaudio_symbian_p.h b/src/multimedia/audio/qaudio_symbian_p.h
new file mode 100644
index 0000000..d5238b4
--- /dev/null
+++ b/src/multimedia/audio/qaudio_symbian_p.h
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIO_SYMBIAN_P_H
+#define QAUDIO_SYMBIAN_P_H
+
+#include <QtCore/qnamespace.h>
+#include <QtMultimedia/qaudioformat.h>
+#include <QtMultimedia/qaudio.h>
+#include <sounddevice.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace SymbianAudio {
+
+/**
+ * Default values used by audio input and output classes, when underlying
+ * DevSound instance has not yet been created.
+ */
+
+const int DefaultBufferSize = 4096; // bytes
+const int DefaultNotifyInterval = 1000; // ms
+
+/**
+ * Enumeration used to track state of internal DevSound instances.
+ * Values are translated to the corresponding QAudio::State values by
+ * SymbianAudio::Utils::stateNativeToQt.
+ */
+enum State {
+ ClosedState
+ , InitializingState
+ , ActiveState
+ , IdleState
+ , SuspendedState
+};
+
+/*
+ * Helper class for querying DevSound codec / format support
+ */
+class DevSoundCapabilities {
+public:
+ DevSoundCapabilities(CMMFDevSound &devsound, QAudio::Mode mode);
+ ~DevSoundCapabilities();
+
+ const RArray<TFourCC>& fourCC() const { return m_fourCC; }
+ const TMMFCapabilities& caps() const { return m_caps; }
+
+private:
+ void constructL(CMMFDevSound &devsound, QAudio::Mode mode);
+
+private:
+ RArray<TFourCC> m_fourCC;
+ TMMFCapabilities m_caps;
+};
+
+namespace Utils {
+
+/**
+ * Convert native audio capabilities to QAudio lists.
+ */
+void capabilitiesNativeToQt(const DevSoundCapabilities &caps,
+ QList<int> &frequencies,
+ QList<int> &channels,
+ QList<int> &sampleSizes,
+ QList<QAudioFormat::Endian> &byteOrders,
+ QList<QAudioFormat::SampleType> &sampleTypes);
+
+/**
+ * Check whether format is supported.
+ */
+bool isFormatSupported(const QAudioFormat &format,
+ const DevSoundCapabilities &caps);
+
+/**
+ * Convert QAudioFormat to native format types.
+ *
+ * Note that, despite the name, DevSound uses TMMFCapabilities to specify
+ * single formats as well as capabilities.
+ *
+ * Note that this function does not modify outputFormat.iBufferSize.
+ */
+bool formatQtToNative(const QAudioFormat &inputFormat,
+ TUint32 &outputFourCC,
+ TMMFCapabilities &outputFormat);
+
+/**
+ * Convert internal states to QAudio states.
+ */
+QAudio::State stateNativeToQt(State nativeState,
+ QAudio::State initializingState);
+
+/**
+ * Convert data length to number of samples.
+ */
+qint64 bytesToSamples(const QAudioFormat &format, qint64 length);
+
+/**
+ * Convert number of samples to data length.
+ */
+qint64 samplesToBytes(const QAudioFormat &format, qint64 samples);
+
+} // namespace Utils
+} // namespace SymbianAudio
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/qaudiodevicefactory.cpp b/src/multimedia/audio/qaudiodevicefactory.cpp
index 459b7f5..4f45110 100644
--- a/src/multimedia/audio/qaudiodevicefactory.cpp
+++ b/src/multimedia/audio/qaudiodevicefactory.cpp
@@ -58,6 +58,10 @@
#include "qaudiodeviceinfo_alsa_p.h"
#include "qaudiooutput_alsa_p.h"
#include "qaudioinput_alsa_p.h"
+#elif defined(Q_OS_SYMBIAN)
+#include "qaudiodeviceinfo_symbian_p.h"
+#include "qaudiooutput_symbian_p.h"
+#include "qaudioinput_symbian_p.h"
#endif
#endif
@@ -128,7 +132,7 @@ QList<QAudioDeviceInfo> QAudioDeviceFactory::availableDevices(QAudio::Mode mode)
{
QList<QAudioDeviceInfo> devices;
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
foreach (const QByteArray &handle, QAudioDeviceInfoInternal::availableDevices(mode))
devices << QAudioDeviceInfo(QLatin1String("builtin"), handle, mode);
#endif
@@ -158,7 +162,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultInputDevice()
return QAudioDeviceInfo(QLatin1String("default"), list.at(0), QAudio::AudioInput);
}
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultInputDevice(), QAudio::AudioInput);
#endif
#endif
@@ -175,7 +179,7 @@ QAudioDeviceInfo QAudioDeviceFactory::defaultOutputDevice()
return QAudioDeviceInfo(QLatin1String("default"), list.at(0), QAudio::AudioOutput);
}
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
return QAudioDeviceInfo(QLatin1String("builtin"), QAudioDeviceInfoInternal::defaultOutputDevice(), QAudio::AudioOutput);
#endif
#endif
@@ -187,7 +191,7 @@ QAbstractAudioDeviceInfo* QAudioDeviceFactory::audioDeviceInfo(const QString &re
QAbstractAudioDeviceInfo *rc = 0;
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
if (realm == QLatin1String("builtin"))
return new QAudioDeviceInfoInternal(handle, mode);
#endif
@@ -216,7 +220,7 @@ QAbstractAudioInput* QAudioDeviceFactory::createInputDevice(QAudioDeviceInfo con
if (deviceInfo.isNull())
return new QNullInputDevice();
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
if (deviceInfo.realm() == QLatin1String("builtin"))
return new QAudioInputPrivate(deviceInfo.handle(), format);
#endif
@@ -235,7 +239,7 @@ QAbstractAudioOutput* QAudioDeviceFactory::createOutputDevice(QAudioDeviceInfo c
if (deviceInfo.isNull())
return new QNullOutputDevice();
#ifndef QT_NO_AUDIO_BACKEND
-#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA))
+#if (defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(HAS_ALSA) || defined(Q_OS_SYMBIAN))
if (deviceInfo.realm() == QLatin1String("builtin"))
return new QAudioOutputPrivate(deviceInfo.handle(), format);
#endif
diff --git a/src/multimedia/audio/qaudiodeviceinfo_symbian_p.cpp b/src/multimedia/audio/qaudiodeviceinfo_symbian_p.cpp
new file mode 100644
index 0000000..36284d3
--- /dev/null
+++ b/src/multimedia/audio/qaudiodeviceinfo_symbian_p.cpp
@@ -0,0 +1,200 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qaudiodeviceinfo_symbian_p.h"
+#include "qaudio_symbian_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAudioDeviceInfoInternal::QAudioDeviceInfoInternal(QByteArray device,
+ QAudio::Mode mode)
+ : m_deviceName(QLatin1String(device))
+ , m_mode(mode)
+ , m_updated(false)
+{
+ QT_TRAP_THROWING(m_devsound.reset(CMMFDevSound::NewL()));
+}
+
+QAudioDeviceInfoInternal::~QAudioDeviceInfoInternal()
+{
+
+}
+
+QAudioFormat QAudioDeviceInfoInternal::preferredFormat() const
+{
+ QAudioFormat format;
+ switch (m_mode) {
+ case QAudio::AudioOutput:
+ format.setFrequency(44100);
+ format.setChannels(2);
+ format.setSampleSize(16);
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setCodec(QLatin1String("audio/pcm"));
+ break;
+
+ case QAudio::AudioInput:
+ format.setFrequency(8000);
+ format.setChannels(1);
+ format.setSampleSize(16);
+ format.setByteOrder(QAudioFormat::LittleEndian);
+ format.setSampleType(QAudioFormat::SignedInt);
+ format.setCodec(QLatin1String("audio/pcm"));
+ break;
+
+ default:
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid mode");
+ }
+
+ if (!isFormatSupported(format)) {
+ if (m_frequencies.size())
+ format.setFrequency(m_frequencies[0]);
+ if (m_channels.size())
+ format.setChannels(m_channels[0]);
+ if (m_sampleSizes.size())
+ format.setSampleSize(m_sampleSizes[0]);
+ if (m_byteOrders.size())
+ format.setByteOrder(m_byteOrders[0]);
+ if (m_sampleTypes.size())
+ format.setSampleType(m_sampleTypes[0]);
+ }
+
+ return format;
+}
+
+bool QAudioDeviceInfoInternal::isFormatSupported(
+ const QAudioFormat &format) const
+{
+ getSupportedFormats();
+ const bool supported =
+ m_codecs.contains(format.codec())
+ && m_frequencies.contains(format.frequency())
+ && m_channels.contains(format.channels())
+ && m_sampleSizes.contains(format.sampleSize())
+ && m_byteOrders.contains(format.byteOrder())
+ && m_sampleTypes.contains(format.sampleType());
+
+ return supported;
+}
+
+QAudioFormat QAudioDeviceInfoInternal::nearestFormat(const QAudioFormat &format) const
+{
+ if (isFormatSupported(format))
+ return format;
+ else
+ return preferredFormat();
+}
+
+QString QAudioDeviceInfoInternal::deviceName() const
+{
+ return m_deviceName;
+}
+
+QStringList QAudioDeviceInfoInternal::codecList()
+{
+ getSupportedFormats();
+ return m_codecs;
+}
+
+QList<int> QAudioDeviceInfoInternal::frequencyList()
+{
+ getSupportedFormats();
+ return m_frequencies;
+}
+
+QList<int> QAudioDeviceInfoInternal::channelsList()
+{
+ getSupportedFormats();
+ return m_channels;
+}
+
+QList<int> QAudioDeviceInfoInternal::sampleSizeList()
+{
+ getSupportedFormats();
+ return m_sampleSizes;
+}
+
+QList<QAudioFormat::Endian> QAudioDeviceInfoInternal::byteOrderList()
+{
+ getSupportedFormats();
+ return m_byteOrders;
+}
+
+QList<QAudioFormat::SampleType> QAudioDeviceInfoInternal::sampleTypeList()
+{
+ getSupportedFormats();
+ return m_sampleTypes;
+}
+
+QByteArray QAudioDeviceInfoInternal::defaultInputDevice()
+{
+ return QByteArray("default");
+}
+
+QByteArray QAudioDeviceInfoInternal::defaultOutputDevice()
+{
+ return QByteArray("default");
+}
+
+QList<QByteArray> QAudioDeviceInfoInternal::availableDevices(QAudio::Mode)
+{
+ QList<QByteArray> result;
+ result += QByteArray("default");
+ return result;
+}
+
+void QAudioDeviceInfoInternal::getSupportedFormats() const
+{
+ if (!m_updated) {
+ QScopedPointer<SymbianAudio::DevSoundCapabilities> caps(
+ new SymbianAudio::DevSoundCapabilities(*m_devsound, m_mode));
+
+ SymbianAudio::Utils::capabilitiesNativeToQt(*caps,
+ m_frequencies, m_channels, m_sampleSizes,
+ m_byteOrders, m_sampleTypes);
+
+ m_codecs.append(QLatin1String("audio/pcm"));
+
+ m_updated = true;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/qaudiodeviceinfo_symbian_p.h b/src/multimedia/audio/qaudiodeviceinfo_symbian_p.h
new file mode 100644
index 0000000..89e539f
--- /dev/null
+++ b/src/multimedia/audio/qaudiodeviceinfo_symbian_p.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIODEVICEINFO_SYMBIAN_P_H
+#define QAUDIODEVICEINFO_SYMBIAN_P_H
+
+#include <QtMultimedia/qaudioengine.h>
+#include <sounddevice.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAudioDeviceInfoInternal
+ : public QAbstractAudioDeviceInfo
+{
+ Q_OBJECT
+
+public:
+ QAudioDeviceInfoInternal(QByteArray device, QAudio::Mode mode);
+ ~QAudioDeviceInfoInternal();
+
+ // QAbstractAudioDeviceInfo
+ QAudioFormat preferredFormat() const;
+ bool isFormatSupported(const QAudioFormat &format) const;
+ QAudioFormat nearestFormat(const QAudioFormat &format) const;
+ QString deviceName() const;
+ QStringList codecList();
+ QList<int> frequencyList();
+ QList<int> channelsList();
+ QList<int> sampleSizeList();
+ QList<QAudioFormat::Endian> byteOrderList();
+ QList<QAudioFormat::SampleType> sampleTypeList();
+ static QByteArray defaultInputDevice();
+ static QByteArray defaultOutputDevice();
+ static QList<QByteArray> availableDevices(QAudio::Mode);
+
+private:
+ void getSupportedFormats() const;
+
+private:
+ QScopedPointer<CMMFDevSound> m_devsound;
+
+ QString m_deviceName;
+ QAudio::Mode m_mode;
+
+ // Mutable to allow lazy initialization when called from const-qualified
+ // public functions (isFormatSupported, nearestFormat)
+ mutable bool m_updated;
+ mutable QStringList m_codecs;
+ mutable QList<int> m_frequencies;
+ mutable QList<int> m_channels;
+ mutable QList<int> m_sampleSizes;
+ mutable QList<QAudioFormat::Endian> m_byteOrders;
+ mutable QList<QAudioFormat::SampleType> m_sampleTypes;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/qaudioinput_symbian_p.cpp b/src/multimedia/audio/qaudioinput_symbian_p.cpp
new file mode 100644
index 0000000..52daa88
--- /dev/null
+++ b/src/multimedia/audio/qaudioinput_symbian_p.cpp
@@ -0,0 +1,594 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qaudioinput_symbian_p.h"
+
+QT_BEGIN_NAMESPACE
+
+//-----------------------------------------------------------------------------
+// Constants
+//-----------------------------------------------------------------------------
+
+const int PushInterval = 50; // ms
+
+
+//-----------------------------------------------------------------------------
+// Private class
+//-----------------------------------------------------------------------------
+
+SymbianAudioInputPrivate::SymbianAudioInputPrivate(
+ QAudioInputPrivate *audioDevice)
+ : m_audioDevice(audioDevice)
+{
+
+}
+
+SymbianAudioInputPrivate::~SymbianAudioInputPrivate()
+{
+
+}
+
+qint64 SymbianAudioInputPrivate::readData(char *data, qint64 len)
+{
+ qint64 totalRead = 0;
+
+ if (m_audioDevice->state() == QAudio::ActiveState ||
+ m_audioDevice->state() == QAudio::IdleState) {
+
+ while (totalRead < len) {
+ const qint64 read = m_audioDevice->read(data + totalRead,
+ len - totalRead);
+ if (read > 0)
+ totalRead += read;
+ else
+ break;
+ }
+ }
+
+ return totalRead;
+}
+
+qint64 SymbianAudioInputPrivate::writeData(const char *data, qint64 len)
+{
+ Q_UNUSED(data)
+ Q_UNUSED(len)
+ return 0;
+}
+
+void SymbianAudioInputPrivate::dataReady()
+{
+ emit readyRead();
+}
+
+
+//-----------------------------------------------------------------------------
+// Public functions
+//-----------------------------------------------------------------------------
+
+QAudioInputPrivate::QAudioInputPrivate(const QByteArray &device,
+ const QAudioFormat &format)
+ : m_device(device)
+ , m_format(format)
+ , m_clientBufferSize(SymbianAudio::DefaultBufferSize)
+ , m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
+ , m_notifyTimer(new QTimer(this))
+ , m_error(QAudio::NoError)
+ , m_internalState(SymbianAudio::ClosedState)
+ , m_externalState(QAudio::StoppedState)
+ , m_pullMode(false)
+ , m_sink(0)
+ , m_pullTimer(new QTimer(this))
+ , m_devSoundBuffer(0)
+ , m_devSoundBufferSize(0)
+ , m_totalBytesReady(0)
+ , m_devSoundBufferPos(0)
+ , m_totalSamplesRecorded(0)
+{
+ connect(m_notifyTimer.data(), SIGNAL(timeout()), this, SIGNAL(notify()));
+
+ SymbianAudio::Utils::formatQtToNative(m_format, m_nativeFourCC,
+ m_nativeFormat);
+
+ m_pullTimer->setInterval(PushInterval);
+ connect(m_pullTimer.data(), SIGNAL(timeout()), this, SLOT(pullData()));
+}
+
+QAudioInputPrivate::~QAudioInputPrivate()
+{
+ close();
+}
+
+QIODevice* QAudioInputPrivate::start(QIODevice *device)
+{
+ stop();
+
+ open();
+ if (SymbianAudio::ClosedState != m_internalState) {
+ if (device) {
+ m_pullMode = true;
+ m_sink = device;
+ } else {
+ m_sink = new SymbianAudioInputPrivate(this);
+ m_sink->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
+ }
+
+ m_elapsed.restart();
+ }
+
+ return m_sink;
+}
+
+void QAudioInputPrivate::stop()
+{
+ close();
+}
+
+void QAudioInputPrivate::reset()
+{
+ m_totalSamplesRecorded += getSamplesRecorded();
+ m_devSound->Stop();
+ startRecording();
+}
+
+void QAudioInputPrivate::suspend()
+{
+ if (SymbianAudio::ActiveState == m_internalState
+ || SymbianAudio::IdleState == m_internalState) {
+ m_notifyTimer->stop();
+ m_pullTimer->stop();
+ m_devSound->Pause();
+ const qint64 samplesRecorded = getSamplesRecorded();
+ m_totalSamplesRecorded += samplesRecorded;
+
+ if (m_devSoundBuffer) {
+ m_devSoundBufferQ.append(m_devSoundBuffer);
+ m_devSoundBuffer = 0;
+ }
+
+ setState(SymbianAudio::SuspendedState);
+ }
+}
+
+void QAudioInputPrivate::resume()
+{
+ if (SymbianAudio::SuspendedState == m_internalState)
+ startDataTransfer();
+}
+
+int QAudioInputPrivate::bytesReady() const
+{
+ Q_ASSERT(m_devSoundBufferPos <= m_totalBytesReady);
+ return m_totalBytesReady - m_devSoundBufferPos;
+}
+
+int QAudioInputPrivate::periodSize() const
+{
+ return bufferSize();
+}
+
+void QAudioInputPrivate::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 QAudioInput, we store
+ // the value and return it from bufferSize(), but the underlying DevSound
+ // buffer size remains unchanged.
+ if (value > 0)
+ m_clientBufferSize = value;
+}
+
+int QAudioInputPrivate::bufferSize() const
+{
+ return m_devSoundBufferSize ? m_devSoundBufferSize : m_clientBufferSize;
+}
+
+void QAudioInputPrivate::setNotifyInterval(int ms)
+{
+ if (ms > 0) {
+ const int oldNotifyInterval = m_notifyInterval;
+ m_notifyInterval = ms;
+ if (m_notifyTimer->isActive() && ms != oldNotifyInterval)
+ m_notifyTimer->start(m_notifyInterval);
+ }
+}
+
+int QAudioInputPrivate::notifyInterval() const
+{
+ return m_notifyInterval;
+}
+
+qint64 QAudioInputPrivate::processedUSecs() const
+{
+ int samplesPlayed = 0;
+ if (m_devSound && SymbianAudio::SuspendedState != m_internalState)
+ samplesPlayed = getSamplesRecorded();
+
+ // Protect against division by zero
+ Q_ASSERT_X(m_format.frequency() > 0, Q_FUNC_INFO, "Invalid frequency");
+
+ const qint64 result = qint64(1000000) *
+ (samplesPlayed + m_totalSamplesRecorded)
+ / m_format.frequency();
+
+ return result;
+}
+
+qint64 QAudioInputPrivate::elapsedUSecs() const
+{
+ const qint64 result = (QAudio::StoppedState == state()) ?
+ 0 : m_elapsed.elapsed() * 1000;
+ return result;
+}
+
+QAudio::Error QAudioInputPrivate::error() const
+{
+ return m_error;
+}
+
+QAudio::State QAudioInputPrivate::state() const
+{
+ return m_externalState;
+}
+
+QAudioFormat QAudioInputPrivate::format() const
+{
+ return m_format;
+}
+
+//-----------------------------------------------------------------------------
+// MDevSoundObserver implementation
+//-----------------------------------------------------------------------------
+
+void QAudioInputPrivate::InitializeComplete(TInt aError)
+{
+ Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
+ Q_FUNC_INFO, "Invalid state");
+
+ if (KErrNone == aError)
+ startRecording();
+}
+
+void QAudioInputPrivate::ToneFinished(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound's tone playback functions, so should
+ // never receive this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioInputPrivate::BufferToBeFilled(CMMFBuffer *aBuffer)
+{
+ Q_UNUSED(aBuffer)
+ // This class doesn't use DevSound in play mode, so should never receive
+ // this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioInputPrivate::PlayError(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound in play mode, so should never receive
+ // this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioInputPrivate::BufferToBeEmptied(CMMFBuffer *aBuffer)
+{
+ // Following receipt of this callback, DevSound should not provide another
+ // buffer until we have returned the current one.
+ Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held");
+
+ CMMFDataBuffer *const buffer = static_cast<CMMFDataBuffer*>(aBuffer);
+
+ if (!m_devSoundBufferSize)
+ m_devSoundBufferSize = buffer->Data().MaxLength();
+
+ m_totalBytesReady += buffer->Data().Length();
+
+ if (SymbianAudio::SuspendedState == m_internalState) {
+ m_devSoundBufferQ.append(buffer);
+ } else {
+ // Will be returned to DevSound by bufferEmptied().
+ m_devSoundBuffer = buffer;
+ m_devSoundBufferPos = 0;
+
+ if (bytesReady() && !m_pullMode)
+ pushData();
+ }
+}
+
+void QAudioInputPrivate::RecordError(TInt aError)
+{
+ Q_UNUSED(aError)
+ setError(QAudio::IOError);
+}
+
+void QAudioInputPrivate::ConvertError(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound's format conversion functions, so
+ // should never receive this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioInputPrivate::DeviceMessage(TUid aMessageType, const TDesC8 &aMsg)
+{
+ Q_UNUSED(aMessageType)
+ Q_UNUSED(aMsg)
+ // Ignore this callback.
+}
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+void QAudioInputPrivate::open()
+{
+ Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
+ Q_FUNC_INFO, "DevSound already opened");
+
+ QT_TRAP_THROWING( m_devSound.reset(CMMFDevSound::NewL()) )
+
+ QScopedPointer<SymbianAudio::DevSoundCapabilities> caps(
+ new SymbianAudio::DevSoundCapabilities(*m_devSound, QAudio::AudioInput));
+
+ int err = SymbianAudio::Utils::isFormatSupported(m_format, *caps) ?
+ KErrNone : KErrNotSupported;
+
+ if (KErrNone == err) {
+ setState(SymbianAudio::InitializingState);
+ TRAP(err, m_devSound->InitializeL(*this, m_nativeFourCC,
+ EMMFStateRecording));
+ }
+
+ if (KErrNone != err) {
+ setError(QAudio::OpenError);
+ m_devSound.reset();
+ }
+}
+
+void QAudioInputPrivate::startRecording()
+{
+ const int samplesRecorded = m_devSound->SamplesRecorded();
+ Q_ASSERT(samplesRecorded == 0);
+
+ TRAPD(err, startDevSoundL());
+ if (KErrNone == err) {
+ startDataTransfer();
+ } else {
+ setError(QAudio::OpenError);
+ close();
+ }
+}
+
+void QAudioInputPrivate::startDevSoundL()
+{
+ TMMFCapabilities nativeFormat = m_devSound->Config();
+ m_nativeFormat.iBufferSize = nativeFormat.iBufferSize;
+ m_devSound->SetConfigL(m_nativeFormat);
+ m_devSound->RecordInitL();
+}
+
+void QAudioInputPrivate::startDataTransfer()
+{
+ m_notifyTimer->start(m_notifyInterval);
+
+ if (m_pullMode)
+ m_pullTimer->start();
+
+ if (bytesReady()) {
+ setState(SymbianAudio::ActiveState);
+ if (!m_pullMode)
+ pushData();
+ } else {
+ if (SymbianAudio::SuspendedState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+ else
+ setState(SymbianAudio::IdleState);
+ }
+}
+
+CMMFDataBuffer* QAudioInputPrivate::currentBuffer() const
+{
+ CMMFDataBuffer *result = m_devSoundBuffer;
+ if (!result && !m_devSoundBufferQ.empty())
+ result = m_devSoundBufferQ.front();
+ return result;
+}
+
+void QAudioInputPrivate::pushData()
+{
+ Q_ASSERT_X(bytesReady(), Q_FUNC_INFO, "No data available");
+ Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO, "pushData called when in pull mode");
+ qobject_cast<SymbianAudioInputPrivate *>(m_sink)->dataReady();
+}
+
+qint64 QAudioInputPrivate::read(char *data, qint64 len)
+{
+ // SymbianAudioInputPrivate is ready to read data
+
+ Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO,
+ "read called when in pull mode");
+
+ qint64 bytesRead = 0;
+
+ CMMFDataBuffer *buffer = 0;
+ while ((buffer = currentBuffer()) && (bytesRead < len)) {
+ if (SymbianAudio::IdleState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+
+ TDesC8 &inputBuffer = buffer->Data();
+
+ const qint64 inputBytes = bytesReady();
+ const qint64 outputBytes = len - bytesRead;
+ const qint64 copyBytes = outputBytes < inputBytes ?
+ outputBytes : inputBytes;
+
+ memcpy(data, inputBuffer.Ptr() + m_devSoundBufferPos, copyBytes);
+
+ m_devSoundBufferPos += copyBytes;
+ data += copyBytes;
+ bytesRead += copyBytes;
+
+ if (!bytesReady())
+ bufferEmptied();
+ }
+
+ return bytesRead;
+}
+
+void QAudioInputPrivate::pullData()
+{
+ Q_ASSERT_X(m_pullMode, Q_FUNC_INFO,
+ "pullData called when in push mode");
+
+ CMMFDataBuffer *buffer = 0;
+ while (buffer = currentBuffer()) {
+ if (SymbianAudio::IdleState == m_internalState)
+ setState(SymbianAudio::ActiveState);
+
+ TDesC8 &inputBuffer = buffer->Data();
+
+ const qint64 inputBytes = bytesReady();
+ const qint64 bytesPushed = m_sink->write(
+ (char*)inputBuffer.Ptr() + m_devSoundBufferPos, inputBytes);
+
+ m_devSoundBufferPos += bytesPushed;
+
+ if (!bytesReady())
+ bufferEmptied();
+
+ if (!bytesPushed)
+ break;
+ }
+}
+
+void QAudioInputPrivate::bufferEmptied()
+{
+ m_devSoundBufferPos = 0;
+
+ if (m_devSoundBuffer) {
+ m_totalBytesReady -= m_devSoundBuffer->Data().Length();
+ m_devSoundBuffer = 0;
+ m_devSound->RecordData();
+ } else {
+ Q_ASSERT(!m_devSoundBufferQ.empty());
+ m_totalBytesReady -= m_devSoundBufferQ.front()->Data().Length();
+ m_devSoundBufferQ.erase(m_devSoundBufferQ.begin());
+
+ // If the queue has been emptied, resume transfer from the hardware
+ if (m_devSoundBufferQ.empty())
+ m_devSound->RecordInitL();
+ }
+
+ Q_ASSERT(m_totalBytesReady >= 0);
+}
+
+void QAudioInputPrivate::close()
+{
+ m_notifyTimer->stop();
+ m_pullTimer->stop();
+
+ m_error = QAudio::NoError;
+
+ if (m_devSound)
+ m_devSound->Stop();
+ m_devSound.reset();
+ m_devSoundBuffer = 0;
+ m_devSoundBufferSize = 0;
+ m_totalBytesReady = 0;
+
+ if (!m_pullMode) // m_sink is owned
+ delete m_sink;
+ m_pullMode = false;
+ m_sink = 0;
+
+ m_devSoundBufferQ.clear();
+ m_devSoundBufferPos = 0;
+ m_totalSamplesRecorded = 0;
+
+ setState(SymbianAudio::ClosedState);
+}
+
+qint64 QAudioInputPrivate::getSamplesRecorded() const
+{
+ qint64 result = 0;
+ if (m_devSound)
+ result = qint64(m_devSound->SamplesRecorded());
+ return result;
+}
+
+void QAudioInputPrivate::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);
+
+ // 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 QAudioInputPrivate::setState(SymbianAudio::State newInternalState)
+{
+ const QAudio::State oldExternalState = m_externalState;
+ m_internalState = newInternalState;
+ m_externalState = SymbianAudio::Utils::stateNativeToQt(
+ m_internalState, initializingState());
+
+ if (m_externalState != oldExternalState)
+ emit stateChanged(m_externalState);
+}
+
+QAudio::State QAudioInputPrivate::initializingState() const
+{
+ return QAudio::IdleState;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/qaudioinput_symbian_p.h b/src/multimedia/audio/qaudioinput_symbian_p.h
new file mode 100644
index 0000000..ca3ccf7
--- /dev/null
+++ b/src/multimedia/audio/qaudioinput_symbian_p.h
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIOINPUT_SYMBIAN_P_H
+#define QAUDIOINPUT_SYMBIAN_P_H
+
+#include <QtMultimedia/qaudioengine.h>
+#include <QTime>
+#include <QTimer>
+#include <sounddevice.h>
+#include "qaudio_symbian_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAudioInputPrivate;
+
+class SymbianAudioInputPrivate : public QIODevice
+{
+ friend class QAudioInputPrivate;
+ Q_OBJECT
+public:
+ SymbianAudioInputPrivate(QAudioInputPrivate *audio);
+ ~SymbianAudioInputPrivate();
+
+ qint64 readData(char *data, qint64 len);
+ qint64 writeData(const char *data, qint64 len);
+
+ void dataReady();
+
+private:
+ QAudioInputPrivate *const m_audioDevice;
+};
+
+class QAudioInputPrivate
+ : public QAbstractAudioInput
+ , public MDevSoundObserver
+{
+ friend class SymbianAudioInputPrivate;
+ Q_OBJECT
+public:
+ QAudioInputPrivate(const QByteArray &device,
+ const QAudioFormat &audioFormat);
+ ~QAudioInputPrivate();
+
+ // QAbstractAudioInput
+ QIODevice* start(QIODevice *device = 0);
+ void stop();
+ void reset();
+ void suspend();
+ void resume();
+ int bytesReady() const;
+ int periodSize() const;
+ void setBufferSize(int value);
+ int bufferSize() const;
+ void setNotifyInterval(int milliSeconds);
+ int notifyInterval() const;
+ qint64 processedUSecs() const;
+ qint64 elapsedUSecs() const;
+ QAudio::Error error() const;
+ QAudio::State state() const;
+ QAudioFormat format() const;
+
+ // MDevSoundObserver
+ void InitializeComplete(TInt aError);
+ void ToneFinished(TInt aError);
+ void BufferToBeFilled(CMMFBuffer *aBuffer);
+ void PlayError(TInt aError);
+ void BufferToBeEmptied(CMMFBuffer *aBuffer);
+ void RecordError(TInt aError);
+ void ConvertError(TInt aError);
+ void DeviceMessage(TUid aMessageType, const TDesC8 &aMsg);
+
+private slots:
+ void pullData();
+
+private:
+ void open();
+ void startRecording();
+ void startDevSoundL();
+ void startDataTransfer();
+ CMMFDataBuffer* currentBuffer() const;
+ void pushData();
+ qint64 read(char *data, qint64 len);
+ void bufferEmptied();
+ Q_INVOKABLE void close();
+
+ qint64 getSamplesRecorded() const;
+
+ void setError(QAudio::Error error);
+ void setState(SymbianAudio::State state);
+
+ QAudio::State initializingState() const;
+
+private:
+ const QByteArray m_device;
+ const QAudioFormat m_format;
+
+ int m_clientBufferSize;
+ int m_notifyInterval;
+ QScopedPointer<QTimer> m_notifyTimer;
+ QTime m_elapsed;
+ QAudio::Error m_error;
+
+ SymbianAudio::State m_internalState;
+ QAudio::State m_externalState;
+
+ bool m_pullMode;
+ QIODevice *m_sink;
+
+ QScopedPointer<QTimer> m_pullTimer;
+
+ QScopedPointer<CMMFDevSound> m_devSound;
+ TUint32 m_nativeFourCC;
+ TMMFCapabilities m_nativeFormat;
+
+ // Latest buffer provided by DevSound, to be empied of data.
+ CMMFDataBuffer *m_devSoundBuffer;
+
+ int m_devSoundBufferSize;
+
+ // Total amount of data in buffers provided by DevSound
+ int m_totalBytesReady;
+
+ // Queue of buffers returned after call to CMMFDevSound::Pause().
+ QList<CMMFDataBuffer *> m_devSoundBufferQ;
+
+ // Current read position within m_devSoundBuffer
+ qint64 m_devSoundBufferPos;
+
+ // Samples recorded up to the last call to suspend(). It is necessary
+ // to cache this because suspend() is implemented using
+ // CMMFDevSound::Stop(), which resets DevSound's SamplesRecorded() counter.
+ quint32 m_totalSamplesRecorded;
+
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/multimedia/audio/qaudiooutput_symbian_p.cpp b/src/multimedia/audio/qaudiooutput_symbian_p.cpp
new file mode 100644
index 0000000..945a08d
--- /dev/null
+++ b/src/multimedia/audio/qaudiooutput_symbian_p.cpp
@@ -0,0 +1,696 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $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,
+ const QAudioFormat &format)
+ : m_device(device)
+ , m_format(format)
+ , m_clientBufferSize(SymbianAudio::DefaultBufferSize)
+ , m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
+ , m_notifyTimer(new QTimer(this))
+ , m_error(QAudio::NoError)
+ , m_internalState(SymbianAudio::ClosedState)
+ , m_externalState(QAudio::StoppedState)
+ , m_pullMode(false)
+ , m_source(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, SIGNAL(notify()));
+
+ SymbianAudio::Utils::formatQtToNative(m_format, m_nativeFourCC,
+ m_nativeFormat);
+
+ m_underflowTimer->setInterval(UnderflowTimerInterval);
+ connect(m_underflowTimer.data(), SIGNAL(timeout()), this,
+ SLOT(underflowTimerExpired()));
+}
+
+QAudioOutputPrivate::~QAudioOutputPrivate()
+{
+ close();
+}
+
+QIODevice* QAudioOutputPrivate::start(QIODevice *device)
+{
+ stop();
+
+ // We have to set these before the call to open() because of the
+ // logic in initializingState()
+ if (device) {
+ m_pullMode = true;
+ m_source = device;
+ }
+
+ open();
+
+ if (SymbianAudio::ClosedState != m_internalState) {
+ if (device) {
+ connect(m_source, SIGNAL(readyRead()), this, SLOT(dataReady()));
+ } else {
+ 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()
+{
+ m_totalSamplesPlayed += getSamplesPlayed();
+ m_devSound->Stop();
+ m_bytesPadding = 0;
+ startPlayback();
+}
+
+void QAudioOutputPrivate::suspend()
+{
+ if (SymbianAudio::ActiveState == m_internalState
+ || SymbianAudio::IdleState == m_internalState) {
+ m_notifyTimer->stop();
+ m_underflowTimer->stop();
+
+ const qint64 samplesWritten = SymbianAudio::Utils::bytesToSamples(
+ m_format, m_bytesWritten);
+ m_bytesWritten = 0;
+
+ const qint64 samplesPlayed = getSamplesPlayed();
+
+ // CMMFDevSound::Pause() is not guaranteed to work correctly in all
+ // implementations, for play-mode DevSound sessions. We therefore
+ // have to implement suspend() by calling CMMFDevSound::Stop().
+ // Because this causes buffered data to be dropped, we replace the
+ // lost data with silence following a call to resume(), in order to
+ // ensure that processedUSecs() returns the correct value.
+ m_devSound->Stop();
+ m_totalSamplesPlayed += samplesPlayed;
+
+ // Calculate the amount of data dropped
+ const qint64 paddingSamples = samplesWritten - samplesPlayed;
+ m_bytesPadding = SymbianAudio::Utils::samplesToBytes(m_format,
+ paddingSamples);
+
+ setState(SymbianAudio::SuspendedState);
+ }
+}
+
+void QAudioOutputPrivate::resume()
+{
+ if (SymbianAudio::SuspendedState == m_internalState)
+ 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_notifyTimer->isActive() && ms != oldNotifyInterval)
+ m_notifyTimer->start(m_notifyInterval);
+ }
+}
+
+int QAudioOutputPrivate::notifyInterval() const
+{
+ return m_notifyInterval;
+}
+
+qint64 QAudioOutputPrivate::processedUSecs() const
+{
+ int samplesPlayed = 0;
+ if (m_devSound && SymbianAudio::SuspendedState != m_internalState)
+ samplesPlayed = getSamplesPlayed();
+
+ // Protect against division by zero
+ Q_ASSERT_X(m_format.frequency() > 0, Q_FUNC_INFO, "Invalid frequency");
+
+ const qint64 result = qint64(1000000) *
+ (samplesPlayed + m_totalSamplesPlayed)
+ / m_format.frequency();
+
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// MDevSoundObserver implementation
+//-----------------------------------------------------------------------------
+
+void QAudioOutputPrivate::InitializeComplete(TInt aError)
+{
+ Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
+ Q_FUNC_INFO, "Invalid state");
+
+ if (KErrNone == aError)
+ startPlayback();
+}
+
+void QAudioOutputPrivate::ToneFinished(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound's tone playback functions, so should
+ // never receive this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioOutputPrivate::BufferToBeFilled(CMMFBuffer *aBuffer)
+{
+ // Following receipt of this callback, 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 DevSound by bufferFilled().
+ m_devSoundBuffer = static_cast<CMMFDataBuffer*>(aBuffer);
+
+ if (!m_devSoundBufferSize)
+ m_devSoundBufferSize = m_devSoundBuffer->Data().MaxLength();
+
+ writePaddingData();
+
+ if (m_pullMode && isDataReady() && !m_bytesPadding)
+ pullData();
+}
+
+void QAudioOutputPrivate::PlayError(TInt aError)
+{
+ switch (aError) {
+ case KErrUnderflow:
+ m_underflow = true;
+ if (m_pullMode && !m_lastBuffer)
+ setError(QAudio::UnderrunError);
+ else
+ setState(SymbianAudio::IdleState);
+ break;
+ default:
+ setError(QAudio::IOError);
+ break;
+ }
+}
+
+void QAudioOutputPrivate::BufferToBeEmptied(CMMFBuffer *aBuffer)
+{
+ Q_UNUSED(aBuffer)
+ // This class doesn't use DevSound in record mode, so should never receive
+ // this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioOutputPrivate::RecordError(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound in record mode, so should never receive
+ // this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioOutputPrivate::ConvertError(TInt aError)
+{
+ Q_UNUSED(aError)
+ // This class doesn't use DevSound's format conversion functions, so
+ // should never receive this callback.
+ Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback");
+}
+
+void QAudioOutputPrivate::DeviceMessage(TUid aMessageType, const TDesC8 &aMsg)
+{
+ Q_UNUSED(aMessageType)
+ Q_UNUSED(aMsg)
+ // Ignore this callback.
+}
+
+//-----------------------------------------------------------------------------
+// Private functions
+//-----------------------------------------------------------------------------
+
+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);
+ } else {
+ m_samplesPlayed = samplesPlayed;
+ m_underflowTimer->start();
+ }
+}
+
+void QAudioOutputPrivate::open()
+{
+ Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
+ Q_FUNC_INFO, "DevSound already opened");
+
+ QT_TRAP_THROWING( m_devSound.reset(CMMFDevSound::NewL()) )
+
+ QScopedPointer<SymbianAudio::DevSoundCapabilities> caps(
+ new SymbianAudio::DevSoundCapabilities(*m_devSound,
+ QAudio::AudioOutput));
+
+ int err = SymbianAudio::Utils::isFormatSupported(m_format, *caps) ?
+ KErrNone : KErrNotSupported;
+
+ if (KErrNone == err) {
+ setState(SymbianAudio::InitializingState);
+ TRAP(err, m_devSound->InitializeL(*this, m_nativeFourCC,
+ EMMFStatePlaying));
+ }
+
+ if (KErrNone != err) {
+ setError(QAudio::OpenError);
+ m_devSound.reset();
+ }
+}
+
+void QAudioOutputPrivate::startPlayback()
+{
+ TRAPD(err, startDevSoundL());
+ if (KErrNone == err) {
+ if (isDataReady())
+ setState(SymbianAudio::ActiveState);
+ else
+ setState(SymbianAudio::IdleState);
+
+ m_notifyTimer->start(m_notifyInterval);
+ m_underflow = false;
+
+ Q_ASSERT(m_devSound->SamplesPlayed() == 0);
+
+ writePaddingData();
+
+ if (m_pullMode && m_source->bytesAvailable() && !m_bytesPadding)
+ dataReady();
+ } else {
+ setError(QAudio::OpenError);
+ close();
+ }
+}
+
+void QAudioOutputPrivate::startDevSoundL()
+{
+ TMMFCapabilities nativeFormat = m_devSound->Config();
+ m_nativeFormat.iBufferSize = nativeFormat.iBufferSize;
+ m_devSound->SetConfigL(m_nativeFormat);
+ m_devSound->PlayInitL();
+}
+
+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;
+
+ if (m_pullMode && m_source->atEnd())
+ lastBufferFilled();
+ if (paddingBytes == outputBytes)
+ 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");
+
+ if (m_bytesPadding)
+ m_bytesPadding = 1;
+
+ // 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);
+ Q_ASSERT(bytesCopied == copyBytes);
+ outputBuffer.SetLength(outputBuffer.Length() + bytesCopied);
+ inputBytes -= bytesCopied;
+
+ if (m_source->atEnd())
+ lastBufferFilled();
+ else if (copyBytes == outputBytes)
+ 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->PlayData();
+}
+
+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_notifyTimer->stop();
+ m_underflowTimer->stop();
+
+ m_error = QAudio::NoError;
+
+ if (m_devSound)
+ m_devSound->Stop();
+ m_devSound.reset();
+ 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->SamplesPlayed());
+ 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);
+
+ 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, initializingState());
+
+ if (m_externalState != oldExternalState)
+ emit stateChanged(m_externalState);
+}
+
+bool QAudioOutputPrivate::isDataReady() const
+{
+ return (m_source && m_source->bytesAvailable())
+ || m_bytesPadding
+ || m_pushDataReady;
+}
+
+QAudio::State QAudioOutputPrivate::initializingState() const
+{
+ return isDataReady() ? QAudio::ActiveState : QAudio::IdleState;
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/qaudiooutput_symbian_p.h b/src/multimedia/audio/qaudiooutput_symbian_p.h
new file mode 100644
index 0000000..00ccb24
--- /dev/null
+++ b/src/multimedia/audio/qaudiooutput_symbian_p.h
@@ -0,0 +1,210 @@
+/****************************************************************************
+**
+** 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 QtMultimedia module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, 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.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#ifndef QAUDIOOUTPUT_SYMBIAN_P_H
+#define QAUDIOOUTPUT_SYMBIAN_P_H
+
+#include <QtMultimedia/qaudioengine.h>
+#include <QTime>
+#include <QTimer>
+#include <sounddevice.h>
+#include "qaudio_symbian_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAudioOutputPrivate;
+
+class SymbianAudioOutputPrivate : public QIODevice
+{
+ friend class QAudioOutputPrivate;
+ Q_OBJECT
+public:
+ SymbianAudioOutputPrivate(QAudioOutputPrivate *audio);
+ ~SymbianAudioOutputPrivate();
+
+ qint64 readData(char *data, qint64 len);
+ qint64 writeData(const char *data, qint64 len);
+
+private:
+ QAudioOutputPrivate *const m_audioDevice;
+};
+
+class QAudioOutputPrivate
+ : public QAbstractAudioOutput
+ , public MDevSoundObserver
+{
+ friend class SymbianAudioOutputPrivate;
+ Q_OBJECT
+public:
+ QAudioOutputPrivate(const QByteArray &device,
+ const QAudioFormat &audioFormat);
+ ~QAudioOutputPrivate();
+
+ // QAbstractAudioOutput
+ QIODevice* start(QIODevice *device = 0);
+ void stop();
+ void reset();
+ void suspend();
+ void resume();
+ int bytesFree() const;
+ int periodSize() const;
+ void setBufferSize(int value);
+ int bufferSize() const;
+ void setNotifyInterval(int milliSeconds);
+ int notifyInterval() const;
+ qint64 processedUSecs() const;
+ qint64 elapsedUSecs() const;
+ QAudio::Error error() const;
+ QAudio::State state() const;
+ QAudioFormat format() const;
+
+ // MDevSoundObserver
+ void InitializeComplete(TInt aError);
+ void ToneFinished(TInt aError);
+ void BufferToBeFilled(CMMFBuffer *aBuffer);
+ void PlayError(TInt aError);
+ void BufferToBeEmptied(CMMFBuffer *aBuffer);
+ void RecordError(TInt aError);
+ void ConvertError(TInt aError);
+ void DeviceMessage(TUid aMessageType, const TDesC8 &aMsg);
+
+private slots:
+ void dataReady();
+ void underflowTimerExpired();
+
+private:
+ void open();
+ void startPlayback();
+ void startDevSoundL();
+ void writePaddingData();
+ qint64 pushData(const char *data, qint64 len);
+ void pullData();
+ void bufferFilled();
+ void lastBufferFilled();
+ Q_INVOKABLE void close();
+
+ qint64 getSamplesPlayed() const;
+
+ void setError(QAudio::Error error);
+ void setState(SymbianAudio::State state);
+
+ bool isDataReady() const;
+ QAudio::State initializingState() const;
+
+private:
+ const QByteArray m_device;
+ const QAudioFormat m_format;
+
+ int m_clientBufferSize;
+ int m_notifyInterval;
+ QScopedPointer<QTimer> m_notifyTimer;
+ QTime m_elapsed;
+ QAudio::Error m_error;
+
+ SymbianAudio::State m_internalState;
+ QAudio::State m_externalState;
+
+ bool m_pullMode;
+ QIODevice *m_source;
+
+ QScopedPointer<CMMFDevSound> m_devSound;
+ TUint32 m_nativeFourCC;
+ TMMFCapabilities m_nativeFormat;
+
+ // Buffer provided by DevSound, to be filled with data.
+ CMMFDataBuffer *m_devSoundBuffer;
+
+ int m_devSoundBufferSize;
+
+ // Number of bytes transferred from QIODevice to QAudioOutput. It is
+ // necessary to count this because data is dropped when suspend() is
+ // called. The difference between the position reported by DevSound and
+ // this value allows us to calculate m_bytesPadding;
+ quint32 m_bytesWritten;
+
+ // True if client has provided data while the audio subsystem was not
+ // ready to consume it.
+ bool m_pushDataReady;
+
+ // Number of zero bytes which will be written when client calls resume().
+ quint32 m_bytesPadding;
+
+ // True if PlayError(KErrUnderflow) has been called.
+ bool m_underflow;
+
+ // True if a buffer marked with the "last buffer" flag has been provided
+ // to DevSound.
+ bool m_lastBuffer;
+
+ // Some DevSound implementations ignore all underflow errors raised by the
+ // audio driver, unless the last buffer flag has been set by the client.
+ // In push-mode playback, this flag will never be set, so the underflow
+ // error will never be reported. In order to work around this, a timer
+ // is used, which gets reset every time the client provides more data. If
+ // the timer expires, an underflow error is raised by this object.
+ QScopedPointer<QTimer> m_underflowTimer;
+
+ // Result of previous call to CMMFDevSound::SamplesPlayed(). This value is
+ // used to determine whether, when m_underflowTimer expires, an
+ // underflow error has actually occurred.
+ quint32 m_samplesPlayed;
+
+ // Samples played up to the last call to suspend(). It is necessary
+ // to cache this because suspend() is implemented using
+ // CMMFDevSound::Stop(), which resets DevSound's SamplesPlayed() counter.
+ quint32 m_totalSamplesPlayed;
+
+};
+
+QT_END_NAMESPACE
+
+#endif