1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtMultimedia module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qaudioinput_symbian_p.h"
43 
44 QT_BEGIN_NAMESPACE
45 
46 //-----------------------------------------------------------------------------
47 // Constants
48 //-----------------------------------------------------------------------------
49 
50 const int PushInterval = 50; // ms
51 
52 
53 //-----------------------------------------------------------------------------
54 // Private class
55 //-----------------------------------------------------------------------------
56 
SymbianAudioInputPrivate(QAudioInputPrivate * audioDevice)57 SymbianAudioInputPrivate::SymbianAudioInputPrivate(
58                               QAudioInputPrivate *audioDevice)
59     :   m_audioDevice(audioDevice)
60 {
61 
62 }
63 
~SymbianAudioInputPrivate()64 SymbianAudioInputPrivate::~SymbianAudioInputPrivate()
65 {
66 
67 }
68 
readData(char * data,qint64 len)69 qint64 SymbianAudioInputPrivate::readData(char *data, qint64 len)
70 {
71     qint64 totalRead = 0;
72 
73     if (m_audioDevice->state() == QAudio::ActiveState ||
74         m_audioDevice->state() == QAudio::IdleState) {
75 
76         while (totalRead < len) {
77             const qint64 read = m_audioDevice->read(data + totalRead,
78                                                     len - totalRead);
79             if (read > 0)
80                 totalRead += read;
81             else
82                 break;
83         }
84     }
85 
86     return totalRead;
87 }
88 
writeData(const char * data,qint64 len)89 qint64 SymbianAudioInputPrivate::writeData(const char *data, qint64 len)
90 {
91     Q_UNUSED(data)
92     Q_UNUSED(len)
93     return 0;
94 }
95 
dataReady()96 void SymbianAudioInputPrivate::dataReady()
97 {
98     emit readyRead();
99 }
100 
101 
102 //-----------------------------------------------------------------------------
103 // Public functions
104 //-----------------------------------------------------------------------------
105 
QAudioInputPrivate(const QByteArray & device,const QAudioFormat & format)106 QAudioInputPrivate::QAudioInputPrivate(const QByteArray &device,
107                                      const QAudioFormat &format)
108     :   m_device(device)
109     ,   m_format(format)
110     ,   m_clientBufferSize(SymbianAudio::DefaultBufferSize)
111     ,   m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
112     ,   m_notifyTimer(new QTimer(this))
113     ,   m_error(QAudio::NoError)
114     ,   m_internalState(SymbianAudio::ClosedState)
115     ,   m_externalState(QAudio::StoppedState)
116     ,   m_pullMode(false)
117     ,   m_sink(0)
118     ,   m_pullTimer(new QTimer(this))
119     ,   m_devSound(0)
120     ,   m_devSoundBuffer(0)
121     ,   m_devSoundBufferSize(0)
122     ,   m_totalBytesReady(0)
123     ,   m_devSoundBufferPos(0)
124     ,   m_totalSamplesRecorded(0)
125 {
126     qRegisterMetaType<CMMFBuffer *>("CMMFBuffer *");
127 
128     connect(m_notifyTimer.data(), SIGNAL(timeout()), this, SIGNAL(notify()));
129 
130     m_pullTimer->setInterval(PushInterval);
131     connect(m_pullTimer.data(), SIGNAL(timeout()), this, SLOT(pullData()));
132 }
133 
~QAudioInputPrivate()134 QAudioInputPrivate::~QAudioInputPrivate()
135 {
136     close();
137 }
138 
start(QIODevice * device)139 QIODevice* QAudioInputPrivate::start(QIODevice *device)
140 {
141     stop();
142 
143     open();
144     if (SymbianAudio::ClosedState != m_internalState) {
145         if (device) {
146             m_pullMode = true;
147             m_sink = device;
148         } else {
149             m_sink = new SymbianAudioInputPrivate(this);
150             m_sink->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
151         }
152 
153         m_elapsed.restart();
154     }
155 
156     return m_sink;
157 }
158 
stop()159 void QAudioInputPrivate::stop()
160 {
161     close();
162 }
163 
reset()164 void QAudioInputPrivate::reset()
165 {
166     m_totalSamplesRecorded += getSamplesRecorded();
167     m_devSound->stop();
168     startRecording();
169 }
170 
suspend()171 void QAudioInputPrivate::suspend()
172 {
173     if (SymbianAudio::ActiveState == m_internalState
174         || SymbianAudio::IdleState == m_internalState) {
175         m_notifyTimer->stop();
176         m_pullTimer->stop();
177         const qint64 samplesRecorded = getSamplesRecorded();
178         m_totalSamplesRecorded += samplesRecorded;
179 
180         const bool paused = m_devSound->pause();
181         if (paused) {
182             if (m_devSoundBuffer)
183                 m_devSoundBufferQ.append(m_devSoundBuffer);
184             m_devSoundBuffer = 0;
185             setState(SymbianAudio::SuspendedPausedState);
186         } else {
187             m_devSoundBuffer = 0;
188             m_devSoundBufferQ.clear();
189             m_devSoundBufferPos = 0;
190             setState(SymbianAudio::SuspendedStoppedState);
191         }
192     }
193 }
194 
resume()195 void QAudioInputPrivate::resume()
196 {
197     if (QAudio::SuspendedState == m_externalState) {
198         if (SymbianAudio::SuspendedPausedState == m_internalState)
199             m_devSound->resume();
200         else
201             m_devSound->start();
202         startDataTransfer();
203     }
204 }
205 
bytesReady() const206 int QAudioInputPrivate::bytesReady() const
207 {
208     Q_ASSERT(m_devSoundBufferPos <= m_totalBytesReady);
209     return m_totalBytesReady - m_devSoundBufferPos;
210 }
211 
periodSize() const212 int QAudioInputPrivate::periodSize() const
213 {
214     return bufferSize();
215 }
216 
setBufferSize(int value)217 void QAudioInputPrivate::setBufferSize(int value)
218 {
219     // Note that DevSound does not allow its client to specify the buffer size.
220     // This functionality is available via custom interfaces, but since these
221     // cannot be guaranteed to work across all DevSound implementations, we
222     // do not use them here.
223     // In order to comply with the expected bevahiour of QAudioInput, we store
224     // the value and return it from bufferSize(), but the underlying DevSound
225     // buffer size remains unchanged.
226     if (value > 0)
227         m_clientBufferSize = value;
228 }
229 
bufferSize() const230 int QAudioInputPrivate::bufferSize() const
231 {
232     return m_devSoundBufferSize ? m_devSoundBufferSize : m_clientBufferSize;
233 }
234 
setNotifyInterval(int ms)235 void QAudioInputPrivate::setNotifyInterval(int ms)
236 {
237     if (ms >= 0) {
238         const int oldNotifyInterval = m_notifyInterval;
239         m_notifyInterval = ms;
240         if (m_notifyInterval && (SymbianAudio::ActiveState == m_internalState ||
241                                  SymbianAudio::IdleState == m_internalState))
242             m_notifyTimer->start(m_notifyInterval);
243         else
244             m_notifyTimer->stop();
245     }
246 }
247 
notifyInterval() const248 int QAudioInputPrivate::notifyInterval() const
249 {
250     return m_notifyInterval;
251 }
252 
processedUSecs() const253 qint64 QAudioInputPrivate::processedUSecs() const
254 {
255     int samplesPlayed = 0;
256     if (m_devSound && QAudio::SuspendedState != m_externalState)
257         samplesPlayed = getSamplesRecorded();
258 
259     // Protect against division by zero
260     Q_ASSERT_X(m_format.frequency() > 0, Q_FUNC_INFO, "Invalid frequency");
261 
262     const qint64 result = qint64(1000000) *
263                           (samplesPlayed + m_totalSamplesRecorded)
264                         / m_format.frequency();
265 
266     return result;
267 }
268 
elapsedUSecs() const269 qint64 QAudioInputPrivate::elapsedUSecs() const
270 {
271     const qint64 result = (QAudio::StoppedState == state()) ?
272                               0 : m_elapsed.elapsed() * 1000;
273     return result;
274 }
275 
error() const276 QAudio::Error QAudioInputPrivate::error() const
277 {
278     return m_error;
279 }
280 
state() const281 QAudio::State QAudioInputPrivate::state() const
282 {
283     return m_externalState;
284 }
285 
format() const286 QAudioFormat QAudioInputPrivate::format() const
287 {
288     return m_format;
289 }
290 
291 
292 //-----------------------------------------------------------------------------
293 // Private functions
294 //-----------------------------------------------------------------------------
295 
open()296 void QAudioInputPrivate::open()
297 {
298     Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
299         Q_FUNC_INFO, "DevSound already opened");
300 
301     Q_ASSERT(!m_devSound);
302     m_devSound = new SymbianAudio::DevSoundWrapper(QAudio::AudioInput, this);
303 
304     connect(m_devSound, SIGNAL(initializeComplete(int)),
305             this, SLOT(devsoundInitializeComplete(int)));
306     connect(m_devSound, SIGNAL(bufferToBeProcessed(CMMFBuffer *)),
307             this, SLOT(devsoundBufferToBeEmptied(CMMFBuffer *)));
308     connect(m_devSound, SIGNAL(processingError(int)),
309             this, SLOT(devsoundRecordError(int)));
310 
311     setState(SymbianAudio::InitializingState);
312     m_devSound->initialize(m_format.codec());
313 }
314 
startRecording()315 void QAudioInputPrivate::startRecording()
316 {
317     const int samplesRecorded = m_devSound->samplesProcessed();
318     Q_ASSERT(samplesRecorded == 0);
319 
320     bool ok = m_devSound->setFormat(m_format);
321     if (ok)
322         ok = m_devSound->start();
323 
324     if (ok) {
325         startDataTransfer();
326     } else {
327         setError(QAudio::OpenError);
328         close();
329     }
330 }
331 
startDataTransfer()332 void QAudioInputPrivate::startDataTransfer()
333 {
334     if (m_notifyInterval)
335         m_notifyTimer->start(m_notifyInterval);
336 
337     if (m_pullMode)
338         m_pullTimer->start();
339 
340     if (bytesReady()) {
341         setState(SymbianAudio::ActiveState);
342         if (!m_pullMode)
343             pushData();
344     } else {
345         if (QAudio::SuspendedState == m_externalState)
346             setState(SymbianAudio::ActiveState);
347         else
348             setState(SymbianAudio::IdleState);
349     }
350 }
351 
currentBuffer() const352 CMMFDataBuffer* QAudioInputPrivate::currentBuffer() const
353 {
354     CMMFDataBuffer *result = m_devSoundBuffer;
355     if (!result && !m_devSoundBufferQ.empty())
356         result = m_devSoundBufferQ.front();
357     return result;
358 }
359 
pushData()360 void QAudioInputPrivate::pushData()
361 {
362     Q_ASSERT_X(bytesReady(), Q_FUNC_INFO, "No data available");
363     Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO, "pushData called when in pull mode");
364     qobject_cast<SymbianAudioInputPrivate *>(m_sink)->dataReady();
365 }
366 
read(char * data,qint64 len)367 qint64 QAudioInputPrivate::read(char *data, qint64 len)
368 {
369     // SymbianAudioInputPrivate is ready to read data
370 
371     Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO,
372         "read called when in pull mode");
373 
374     qint64 bytesRead = 0;
375 
376     CMMFDataBuffer *buffer = 0;
377     while ((buffer = currentBuffer()) && (bytesRead < len)) {
378         if (SymbianAudio::IdleState == m_internalState)
379             setState(SymbianAudio::ActiveState);
380 
381         TDesC8 &inputBuffer = buffer->Data();
382 
383         Q_ASSERT(inputBuffer.Length() >= m_devSoundBufferPos);
384         const qint64 inputBytes = inputBuffer.Length() - m_devSoundBufferPos;
385         const qint64 outputBytes = len - bytesRead;
386         const qint64 copyBytes = outputBytes < inputBytes ?
387                                      outputBytes : inputBytes;
388 
389         memcpy(data, inputBuffer.Ptr() + m_devSoundBufferPos, copyBytes);
390 
391         m_devSoundBufferPos += copyBytes;
392         data += copyBytes;
393         bytesRead += copyBytes;
394 
395         if (inputBytes == copyBytes)
396             bufferEmptied();
397     }
398 
399     return bytesRead;
400 }
401 
pullData()402 void QAudioInputPrivate::pullData()
403 {
404     Q_ASSERT_X(m_pullMode, Q_FUNC_INFO,
405         "pullData called when in push mode");
406 
407     CMMFDataBuffer *buffer = 0;
408     while (buffer = currentBuffer()) {
409         if (SymbianAudio::IdleState == m_internalState)
410             setState(SymbianAudio::ActiveState);
411 
412         TDesC8 &inputBuffer = buffer->Data();
413 
414         Q_ASSERT(inputBuffer.Length() >= m_devSoundBufferPos);
415         const qint64 inputBytes = inputBuffer.Length() - m_devSoundBufferPos;
416         const qint64 bytesPushed = m_sink->write(
417             (char*)inputBuffer.Ptr() + m_devSoundBufferPos, inputBytes);
418 
419         m_devSoundBufferPos += bytesPushed;
420 
421         if (inputBytes == bytesPushed)
422             bufferEmptied();
423 
424         if (!bytesPushed)
425             break;
426     }
427 }
428 
devsoundInitializeComplete(int err)429 void QAudioInputPrivate::devsoundInitializeComplete(int err)
430 {
431     Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
432         Q_FUNC_INFO, "Invalid state");
433 
434     if (!err && m_devSound->isFormatSupported(m_format))
435         startRecording();
436     else
437         setError(QAudio::OpenError);
438 }
439 
devsoundBufferToBeEmptied(CMMFBuffer * baseBuffer)440 void QAudioInputPrivate::devsoundBufferToBeEmptied(CMMFBuffer *baseBuffer)
441 {
442     // Following receipt of this signal, DevSound should not provide another
443     // buffer until we have returned the current one.
444     Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held");
445 
446     CMMFDataBuffer *const buffer = static_cast<CMMFDataBuffer*>(baseBuffer);
447 
448     if (!m_devSoundBufferSize)
449         m_devSoundBufferSize = buffer->Data().MaxLength();
450 
451     m_totalBytesReady += buffer->Data().Length();
452 
453     if (SymbianAudio::SuspendedPausedState == m_internalState) {
454         m_devSoundBufferQ.append(buffer);
455     } else {
456         // Will be returned to DevSoundWrapper by bufferProcessed().
457         m_devSoundBuffer = buffer;
458         m_devSoundBufferPos = 0;
459 
460         if (bytesReady() && !m_pullMode)
461             pushData();
462     }
463 }
464 
devsoundRecordError(int err)465 void QAudioInputPrivate::devsoundRecordError(int err)
466 {
467     Q_UNUSED(err)
468     setError(QAudio::IOError);
469 }
470 
bufferEmptied()471 void QAudioInputPrivate::bufferEmptied()
472 {
473     m_devSoundBufferPos = 0;
474 
475     if (m_devSoundBuffer) {
476         m_totalBytesReady -= m_devSoundBuffer->Data().Length();
477         m_devSoundBuffer = 0;
478         m_devSound->bufferProcessed();
479     } else {
480         Q_ASSERT(!m_devSoundBufferQ.empty());
481         m_totalBytesReady -= m_devSoundBufferQ.front()->Data().Length();
482         m_devSoundBufferQ.erase(m_devSoundBufferQ.begin());
483 
484         // If the queue has been emptied, resume transfer from the hardware
485         if (m_devSoundBufferQ.empty())
486             if (!m_devSound->start())
487                 setError(QAudio::IOError);
488     }
489 
490     Q_ASSERT(m_totalBytesReady >= 0);
491 }
492 
close()493 void QAudioInputPrivate::close()
494 {
495     m_notifyTimer->stop();
496     m_pullTimer->stop();
497 
498     m_error = QAudio::NoError;
499 
500     if (m_devSound)
501         m_devSound->stop();
502     delete m_devSound;
503     m_devSound = 0;
504 
505     m_devSoundBuffer = 0;
506     m_devSoundBufferSize = 0;
507     m_totalBytesReady = 0;
508 
509     if (!m_pullMode) // m_sink is owned
510         delete m_sink;
511     m_pullMode = false;
512     m_sink = 0;
513 
514     m_devSoundBufferQ.clear();
515     m_devSoundBufferPos = 0;
516     m_totalSamplesRecorded = 0;
517 
518     setState(SymbianAudio::ClosedState);
519 }
520 
getSamplesRecorded() const521 qint64 QAudioInputPrivate::getSamplesRecorded() const
522 {
523     qint64 result = 0;
524     if (m_devSound)
525         result = qint64(m_devSound->samplesProcessed());
526     return result;
527 }
528 
setError(QAudio::Error error)529 void QAudioInputPrivate::setError(QAudio::Error error)
530 {
531     m_error = error;
532 
533     // Although no state transition actually occurs here, a stateChanged event
534     // must be emitted to inform the client that the call to start() was
535     // unsuccessful.
536     if (QAudio::OpenError == error) {
537         emit stateChanged(QAudio::StoppedState);
538     } else {
539         if (QAudio::UnderrunError == error)
540             setState(SymbianAudio::IdleState);
541         else
542             // Close the DevSound instance.  This causes a transition to
543             // StoppedState.  This must be done asynchronously in case the
544             // current function was called from a DevSound event handler, in which
545             // case deleting the DevSound instance may cause an exception.
546             QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
547     }
548 }
549 
setState(SymbianAudio::State newInternalState)550 void QAudioInputPrivate::setState(SymbianAudio::State newInternalState)
551 {
552     const QAudio::State oldExternalState = m_externalState;
553     m_internalState = newInternalState;
554     m_externalState = SymbianAudio::Utils::stateNativeToQt(m_internalState);
555 
556     if (m_externalState != oldExternalState)
557         emit stateChanged(m_externalState);
558 }
559 
560 QT_END_NAMESPACE
561