/****************************************************************************
**
** 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