diff options
Diffstat (limited to 'src/multimedia')
-rw-r--r-- | src/multimedia/audio/audio.pri | 15 | ||||
-rw-r--r-- | src/multimedia/audio/qaudio_symbian_p.cpp | 395 | ||||
-rw-r--r-- | src/multimedia/audio/qaudio_symbian_p.h | 156 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiodevicefactory.cpp | 16 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiodeviceinfo_symbian_p.cpp | 200 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiodeviceinfo_symbian_p.h | 107 | ||||
-rw-r--r-- | src/multimedia/audio/qaudioinput_symbian_p.cpp | 594 | ||||
-rw-r--r-- | src/multimedia/audio/qaudioinput_symbian_p.h | 188 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiooutput_symbian_p.cpp | 696 | ||||
-rw-r--r-- | src/multimedia/audio/qaudiooutput_symbian_p.h | 210 |
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 |