595 lines
16 KiB
C++
595 lines
16 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
|
** All rights reserved.
|
|
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
**
|
|
** This file is part of the Qt Mobility Components.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** GNU Lesser General Public License Usage
|
|
** This file may be used under the terms of the GNU Lesser General Public
|
|
** License version 2.1 as published by the Free Software Foundation and
|
|
** appearing in the file LICENSE.LGPL included in the packaging of this
|
|
** file. Please review the following information to ensure the GNU Lesser
|
|
** General Public License version 2.1 requirements will be met:
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU General
|
|
** Public License version 3.0 as published by the Free Software Foundation
|
|
** and appearing in the file LICENSE.GPL included in the packaging of this
|
|
** file. Please review the following information to ensure the GNU General
|
|
** Public License version 3.0 requirements will be met:
|
|
** http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
** Other Usage
|
|
** Alternatively, this file may be used in accordance with the terms and
|
|
** conditions contained in a signed written agreement between you and Nokia.
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "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)
|
|
: m_device(device)
|
|
, m_clientBufferSize(SymbianAudio::DefaultBufferSize)
|
|
, m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
|
|
, m_notifyTimer(new QTimer(this))
|
|
, m_lastNotifyPosition(0)
|
|
, m_error(QAudio::NoError)
|
|
, m_internalState(SymbianAudio::ClosedState)
|
|
, m_externalState(QAudio::StoppedState)
|
|
, m_pullMode(false)
|
|
, m_sink(0)
|
|
, m_pullTimer(new QTimer(this))
|
|
, m_devSound(0)
|
|
, m_devSoundBuffer(0)
|
|
, m_devSoundBufferSize(0)
|
|
, m_totalBytesReady(0)
|
|
, m_devSoundBufferPos(0)
|
|
, m_totalSamplesRecorded(0)
|
|
{
|
|
qRegisterMetaType<CMMFBuffer *>("CMMFBuffer *");
|
|
|
|
connect(m_notifyTimer.data(), SIGNAL(timeout()),
|
|
this, SIGNAL(notifyTimerExpired()));
|
|
|
|
m_pullTimer->setInterval(PushInterval);
|
|
connect(m_pullTimer.data(), SIGNAL(timeout()), this, SLOT(pullData()));
|
|
}
|
|
|
|
void QAudioInputPrivate::setFormat(const QAudioFormat& fmt)
|
|
{
|
|
m_format = fmt;
|
|
}
|
|
|
|
QAudioInputPrivate::~QAudioInputPrivate()
|
|
{
|
|
close();
|
|
}
|
|
|
|
void QAudioInputPrivate::start(QIODevice *device)
|
|
{
|
|
stop();
|
|
|
|
open();
|
|
if (SymbianAudio::ClosedState != m_internalState) {
|
|
m_pullMode = true;
|
|
m_sink = device;
|
|
m_elapsed.restart();
|
|
}
|
|
}
|
|
|
|
QIODevice* QAudioInputPrivate::start()
|
|
{
|
|
stop();
|
|
|
|
open();
|
|
if (SymbianAudio::ClosedState != m_internalState) {
|
|
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_pullTimer->stop();
|
|
const qint64 samplesRecorded = getSamplesRecorded();
|
|
m_totalSamplesRecorded += samplesRecorded;
|
|
|
|
const bool paused = m_devSound->pause();
|
|
if (paused) {
|
|
if (m_devSoundBuffer)
|
|
m_devSoundBufferQ.append(m_devSoundBuffer);
|
|
m_devSoundBuffer = 0;
|
|
setState(SymbianAudio::SuspendedPausedState);
|
|
} else {
|
|
m_devSoundBuffer = 0;
|
|
m_devSoundBufferQ.clear();
|
|
m_devSoundBufferPos = 0;
|
|
setState(SymbianAudio::SuspendedStoppedState);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::resume()
|
|
{
|
|
if (QAudio::SuspendedState == m_externalState) {
|
|
if (SymbianAudio::SuspendedPausedState == m_internalState)
|
|
m_devSound->resume();
|
|
else
|
|
m_devSound->start();
|
|
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_notifyInterval && (SymbianAudio::ActiveState == m_internalState ||
|
|
SymbianAudio::IdleState == m_internalState))
|
|
m_notifyTimer->start(m_notifyInterval);
|
|
else
|
|
m_notifyTimer->stop();
|
|
}
|
|
}
|
|
|
|
int QAudioInputPrivate::notifyInterval() const
|
|
{
|
|
return m_notifyInterval;
|
|
}
|
|
|
|
qint64 QAudioInputPrivate::processedUSecs() const
|
|
{
|
|
int samplesPlayed = 0;
|
|
if (m_devSound && QAudio::SuspendedState != m_externalState)
|
|
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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Private functions
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void QAudioInputPrivate::open()
|
|
{
|
|
Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
|
|
Q_FUNC_INFO, "DevSound already opened");
|
|
|
|
Q_ASSERT(!m_devSound);
|
|
m_devSound = new SymbianAudio::DevSoundWrapper(QAudio::AudioInput, this);
|
|
|
|
connect(m_devSound, SIGNAL(initializeComplete(int)),
|
|
this, SLOT(devsoundInitializeComplete(int)));
|
|
connect(m_devSound, SIGNAL(bufferToBeProcessed(CMMFBuffer *)),
|
|
this, SLOT(devsoundBufferToBeEmptied(CMMFBuffer *)));
|
|
connect(m_devSound, SIGNAL(processingError(int)),
|
|
this, SLOT(devsoundRecordError(int)));
|
|
|
|
setState(SymbianAudio::InitializingState);
|
|
m_devSound->initialize(m_format.codec());
|
|
}
|
|
|
|
void QAudioInputPrivate::startRecording()
|
|
{
|
|
const int samplesRecorded = m_devSound->samplesProcessed();
|
|
Q_ASSERT(samplesRecorded == 0);
|
|
|
|
bool ok = m_devSound->setFormat(m_format);
|
|
if (ok)
|
|
ok = m_devSound->start();
|
|
|
|
if (ok) {
|
|
startDataTransfer();
|
|
} else {
|
|
setError(QAudio::OpenError);
|
|
close();
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::startDataTransfer()
|
|
{
|
|
if (m_notifyInterval)
|
|
m_notifyTimer->start(m_notifyInterval);
|
|
|
|
if (m_pullMode)
|
|
m_pullTimer->start();
|
|
|
|
if (bytesReady()) {
|
|
setState(SymbianAudio::ActiveState);
|
|
if (!m_pullMode)
|
|
pushData();
|
|
} else {
|
|
if (QAudio::SuspendedState == m_externalState)
|
|
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;
|
|
buffer = currentBuffer();
|
|
while (buffer && (bytesRead < len)) {
|
|
if (SymbianAudio::IdleState == m_internalState)
|
|
setState(SymbianAudio::ActiveState);
|
|
|
|
TDesC8 &inputBuffer = buffer->Data();
|
|
|
|
Q_ASSERT(inputBuffer.Length() >= m_devSoundBufferPos);
|
|
const qint64 inputBytes = inputBuffer.Length() - m_devSoundBufferPos;
|
|
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 (inputBytes == copyBytes)
|
|
bufferEmptied();
|
|
|
|
buffer = currentBuffer();
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
void QAudioInputPrivate::notifyTimerExpired()
|
|
{
|
|
const qint64 pos = processedUSecs();
|
|
if (pos > m_lastNotifyPosition) {
|
|
int count = (pos - m_lastNotifyPosition) / (m_notifyInterval * 1000);
|
|
while (count--) {
|
|
emit notify();
|
|
m_lastNotifyPosition += m_notifyInterval * 1000;
|
|
}
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::pullData()
|
|
{
|
|
Q_ASSERT_X(m_pullMode, Q_FUNC_INFO,
|
|
"pullData called when in push mode");
|
|
|
|
CMMFDataBuffer *buffer = 0;
|
|
buffer = currentBuffer();
|
|
while (buffer) {
|
|
if (SymbianAudio::IdleState == m_internalState)
|
|
setState(SymbianAudio::ActiveState);
|
|
|
|
TDesC8 &inputBuffer = buffer->Data();
|
|
|
|
Q_ASSERT(inputBuffer.Length() >= m_devSoundBufferPos);
|
|
const qint64 inputBytes = inputBuffer.Length() - m_devSoundBufferPos;
|
|
const qint64 bytesPushed = m_sink->write(
|
|
(char*)inputBuffer.Ptr() + m_devSoundBufferPos, inputBytes);
|
|
|
|
m_devSoundBufferPos += bytesPushed;
|
|
|
|
if (inputBytes == bytesPushed)
|
|
bufferEmptied();
|
|
|
|
if (!bytesPushed)
|
|
break;
|
|
|
|
buffer = currentBuffer();
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::devsoundInitializeComplete(int err)
|
|
{
|
|
Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
|
|
Q_FUNC_INFO, "Invalid state");
|
|
|
|
if (!err && m_devSound->isFormatSupported(m_format))
|
|
startRecording();
|
|
else
|
|
setError(QAudio::OpenError);
|
|
}
|
|
|
|
void QAudioInputPrivate::devsoundBufferToBeEmptied(CMMFBuffer *baseBuffer)
|
|
{
|
|
// Following receipt of this signal, DevSound should not provide another
|
|
// buffer until we have returned the current one.
|
|
Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held");
|
|
|
|
CMMFDataBuffer *const buffer = static_cast<CMMFDataBuffer*>(baseBuffer);
|
|
|
|
if (!m_devSoundBufferSize)
|
|
m_devSoundBufferSize = buffer->Data().MaxLength();
|
|
|
|
m_totalBytesReady += buffer->Data().Length();
|
|
|
|
if (SymbianAudio::SuspendedPausedState == m_internalState) {
|
|
m_devSoundBufferQ.append(buffer);
|
|
} else {
|
|
// Will be returned to DevSoundWrapper by bufferProcessed().
|
|
m_devSoundBuffer = buffer;
|
|
m_devSoundBufferPos = 0;
|
|
|
|
if (bytesReady() && !m_pullMode)
|
|
pushData();
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::devsoundRecordError(int err)
|
|
{
|
|
Q_UNUSED(err)
|
|
setError(QAudio::IOError);
|
|
}
|
|
|
|
void QAudioInputPrivate::bufferEmptied()
|
|
{
|
|
m_devSoundBufferPos = 0;
|
|
|
|
if (m_devSoundBuffer) {
|
|
m_totalBytesReady -= m_devSoundBuffer->Data().Length();
|
|
m_devSoundBuffer = 0;
|
|
m_devSound->bufferProcessed();
|
|
} 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())
|
|
if (!m_devSound->start())
|
|
setError(QAudio::IOError);
|
|
}
|
|
|
|
Q_ASSERT(m_totalBytesReady >= 0);
|
|
}
|
|
|
|
void QAudioInputPrivate::close()
|
|
{
|
|
m_lastNotifyPosition = 0;
|
|
m_pullTimer->stop();
|
|
|
|
m_error = QAudio::NoError;
|
|
|
|
if (m_devSound)
|
|
m_devSound->stop();
|
|
delete m_devSound;
|
|
m_devSound = 0;
|
|
|
|
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->samplesProcessed());
|
|
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);
|
|
} else {
|
|
if (QAudio::UnderrunError == error)
|
|
setState(SymbianAudio::IdleState);
|
|
else
|
|
// Close the DevSound instance. This causes a transition to
|
|
// StoppedState. This must be done asynchronously in case the
|
|
// current function was called from a DevSound event handler, in which
|
|
// case deleting the DevSound instance may cause an exception.
|
|
QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void QAudioInputPrivate::setState(SymbianAudio::State newInternalState)
|
|
{
|
|
const QAudio::State oldExternalState = m_externalState;
|
|
m_internalState = newInternalState;
|
|
m_externalState = SymbianAudio::Utils::stateNativeToQt(m_internalState);
|
|
|
|
if (m_externalState != QAudio::ActiveState &&
|
|
m_externalState != QAudio::IdleState)
|
|
m_notifyTimer->stop();
|
|
|
|
if (m_externalState != oldExternalState)
|
|
emit stateChanged(m_externalState);
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qaudioinput_symbian_p.cpp"
|