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