source: trunk/src/multimedia/audio/qaudiooutput_symbian_p.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 19.3 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtMultimedia module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qaudiooutput_symbian_p.h"
43
44QT_BEGIN_NAMESPACE
45
46//-----------------------------------------------------------------------------
47// Constants
48//-----------------------------------------------------------------------------
49
50const int UnderflowTimerInterval = 50; // ms
51
52
53//-----------------------------------------------------------------------------
54// Private class
55//-----------------------------------------------------------------------------
56
57SymbianAudioOutputPrivate::SymbianAudioOutputPrivate(
58 QAudioOutputPrivate *audioDevice)
59 : m_audioDevice(audioDevice)
60{
61
62}
63
64SymbianAudioOutputPrivate::~SymbianAudioOutputPrivate()
65{
66
67}
68
69qint64 SymbianAudioOutputPrivate::readData(char *data, qint64 len)
70{
71 Q_UNUSED(data)
72 Q_UNUSED(len)
73 return 0;
74}
75
76qint64 SymbianAudioOutputPrivate::writeData(const char *data, qint64 len)
77{
78 qint64 totalWritten = 0;
79
80 if (m_audioDevice->state() == QAudio::ActiveState ||
81 m_audioDevice->state() == QAudio::IdleState) {
82
83 while (totalWritten < len) {
84 const qint64 written = m_audioDevice->pushData(data + totalWritten,
85 len - totalWritten);
86 if (written > 0)
87 totalWritten += written;
88 else
89 break;
90 }
91 }
92
93 return totalWritten;
94}
95
96
97//-----------------------------------------------------------------------------
98// Public functions
99//-----------------------------------------------------------------------------
100
101QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray &device,
102 const QAudioFormat &format)
103 : m_device(device)
104 , m_format(format)
105 , m_clientBufferSize(SymbianAudio::DefaultBufferSize)
106 , m_notifyInterval(SymbianAudio::DefaultNotifyInterval)
107 , m_notifyTimer(new QTimer(this))
108 , m_error(QAudio::NoError)
109 , m_internalState(SymbianAudio::ClosedState)
110 , m_externalState(QAudio::StoppedState)
111 , m_pullMode(false)
112 , m_source(0)
113 , m_devSound(0)
114 , m_devSoundBuffer(0)
115 , m_devSoundBufferSize(0)
116 , m_bytesWritten(0)
117 , m_pushDataReady(false)
118 , m_bytesPadding(0)
119 , m_underflow(false)
120 , m_lastBuffer(false)
121 , m_underflowTimer(new QTimer(this))
122 , m_samplesPlayed(0)
123 , m_totalSamplesPlayed(0)
124{
125 qRegisterMetaType<CMMFBuffer *>("CMMFBuffer *");
126
127 connect(m_notifyTimer.data(), SIGNAL(timeout()), this, SIGNAL(notify()));
128
129 m_underflowTimer->setInterval(UnderflowTimerInterval);
130 connect(m_underflowTimer.data(), SIGNAL(timeout()), this,
131 SLOT(underflowTimerExpired()));
132}
133
134QAudioOutputPrivate::~QAudioOutputPrivate()
135{
136 close();
137}
138
139QIODevice* QAudioOutputPrivate::start(QIODevice *device)
140{
141 stop();
142
143 if (device) {
144 m_pullMode = true;
145 m_source = device;
146 }
147
148 open();
149
150 if (SymbianAudio::ClosedState != m_internalState) {
151 if (device) {
152 connect(m_source, SIGNAL(readyRead()), this, SLOT(dataReady()));
153 } else {
154 m_source = new SymbianAudioOutputPrivate(this);
155 m_source->open(QIODevice::WriteOnly | QIODevice::Unbuffered);
156 }
157
158 m_elapsed.restart();
159 }
160
161 return m_source;
162}
163
164void QAudioOutputPrivate::stop()
165{
166 close();
167}
168
169void QAudioOutputPrivate::reset()
170{
171 m_totalSamplesPlayed += getSamplesPlayed();
172 m_devSound->stop();
173 m_bytesPadding = 0;
174 startPlayback();
175}
176
177void QAudioOutputPrivate::suspend()
178{
179 if (SymbianAudio::ActiveState == m_internalState
180 || SymbianAudio::IdleState == m_internalState) {
181 m_notifyTimer->stop();
182 m_underflowTimer->stop();
183 const qint64 samplesWritten = SymbianAudio::Utils::bytesToSamples(
184 m_format, m_bytesWritten);
185 const qint64 samplesPlayed = getSamplesPlayed();
186 m_totalSamplesPlayed += samplesPlayed;
187 m_bytesWritten = 0;
188 const bool paused = m_devSound->pause();
189 if (paused) {
190 setState(SymbianAudio::SuspendedPausedState);
191 } else {
192 m_devSoundBuffer = 0;
193 // Calculate the amount of data dropped
194 const qint64 paddingSamples = samplesWritten - samplesPlayed;
195 Q_ASSERT(paddingSamples >= 0);
196 m_bytesPadding = SymbianAudio::Utils::samplesToBytes(m_format,
197 paddingSamples);
198 setState(SymbianAudio::SuspendedStoppedState);
199 }
200 }
201}
202
203void QAudioOutputPrivate::resume()
204{
205 if (QAudio::SuspendedState == m_externalState) {
206 if (SymbianAudio::SuspendedPausedState == m_internalState)
207 m_devSound->resume();
208 else
209 startPlayback();
210 }
211}
212
213int QAudioOutputPrivate::bytesFree() const
214{
215 int result = 0;
216 if (m_devSoundBuffer) {
217 const TDes8 &outputBuffer = m_devSoundBuffer->Data();
218 result = outputBuffer.MaxLength() - outputBuffer.Length();
219 }
220 return result;
221}
222
223int QAudioOutputPrivate::periodSize() const
224{
225 return bufferSize();
226}
227
228void QAudioOutputPrivate::setBufferSize(int value)
229{
230 // Note that DevSound does not allow its client to specify the buffer size.
231 // This functionality is available via custom interfaces, but since these
232 // cannot be guaranteed to work across all DevSound implementations, we
233 // do not use them here.
234 // In order to comply with the expected bevahiour of QAudioOutput, we store
235 // the value and return it from bufferSize(), but the underlying DevSound
236 // buffer size remains unchanged.
237 if (value > 0)
238 m_clientBufferSize = value;
239}
240
241int QAudioOutputPrivate::bufferSize() const
242{
243 return m_devSoundBufferSize ? m_devSoundBufferSize : m_clientBufferSize;
244}
245
246void QAudioOutputPrivate::setNotifyInterval(int ms)
247{
248 if (ms >= 0) {
249 const int oldNotifyInterval = m_notifyInterval;
250 m_notifyInterval = ms;
251 if (m_notifyInterval && (SymbianAudio::ActiveState == m_internalState ||
252 SymbianAudio::IdleState == m_internalState))
253 m_notifyTimer->start(m_notifyInterval);
254 else
255 m_notifyTimer->stop();
256 }
257}
258
259int QAudioOutputPrivate::notifyInterval() const
260{
261 return m_notifyInterval;
262}
263
264qint64 QAudioOutputPrivate::processedUSecs() const
265{
266 int samplesPlayed = 0;
267 if (m_devSound && QAudio::SuspendedState != m_externalState)
268 samplesPlayed = getSamplesPlayed();
269
270 // Protect against division by zero
271 Q_ASSERT_X(m_format.frequency() > 0, Q_FUNC_INFO, "Invalid frequency");
272
273 const qint64 result = qint64(1000000) *
274 (samplesPlayed + m_totalSamplesPlayed)
275 / m_format.frequency();
276
277 return result;
278}
279
280qint64 QAudioOutputPrivate::elapsedUSecs() const
281{
282 const qint64 result = (QAudio::StoppedState == state()) ?
283 0 : m_elapsed.elapsed() * 1000;
284 return result;
285}
286
287QAudio::Error QAudioOutputPrivate::error() const
288{
289 return m_error;
290}
291
292QAudio::State QAudioOutputPrivate::state() const
293{
294 return m_externalState;
295}
296
297QAudioFormat QAudioOutputPrivate::format() const
298{
299 return m_format;
300}
301
302
303//-----------------------------------------------------------------------------
304// Private functions
305//-----------------------------------------------------------------------------
306
307void QAudioOutputPrivate::dataReady()
308{
309 // Client-provided QIODevice has data ready to read.
310
311 Q_ASSERT_X(m_source->bytesAvailable(), Q_FUNC_INFO,
312 "readyRead signal received, but no data available");
313
314 if (!m_bytesPadding)
315 pullData();
316}
317
318void QAudioOutputPrivate::underflowTimerExpired()
319{
320 const TInt samplesPlayed = getSamplesPlayed();
321 if (m_samplesPlayed && (samplesPlayed == m_samplesPlayed)) {
322 setError(QAudio::UnderrunError);
323 } else {
324 m_samplesPlayed = samplesPlayed;
325 m_underflowTimer->start();
326 }
327}
328
329void QAudioOutputPrivate::devsoundInitializeComplete(int err)
330{
331 Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState,
332 Q_FUNC_INFO, "Invalid state");
333
334 if (!err && m_devSound->isFormatSupported(m_format))
335 startPlayback();
336 else
337 setError(QAudio::OpenError);
338}
339
340void QAudioOutputPrivate::devsoundBufferToBeFilled(CMMFBuffer *bufferBase)
341{
342 // Following receipt of this signal, DevSound should not provide another
343 // buffer until we have returned the current one.
344 Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held");
345
346 // Will be returned to DevSoundWrapper by bufferProcessed().
347 m_devSoundBuffer = static_cast<CMMFDataBuffer*>(bufferBase);
348
349 if (!m_devSoundBufferSize)
350 m_devSoundBufferSize = m_devSoundBuffer->Data().MaxLength();
351
352 writePaddingData();
353
354 if (m_pullMode && isDataReady() && !m_bytesPadding)
355 pullData();
356}
357
358void QAudioOutputPrivate::devsoundPlayError(int err)
359{
360 switch (err) {
361 case KErrUnderflow:
362 m_underflow = true;
363 if (m_pullMode && !m_lastBuffer)
364 setError(QAudio::UnderrunError);
365 else
366 setState(SymbianAudio::IdleState);
367 break;
368 case KErrOverflow:
369 // Silently consume this error when in playback mode
370 break;
371 default:
372 setError(QAudio::IOError);
373 break;
374 }
375}
376
377void QAudioOutputPrivate::open()
378{
379 Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState,
380 Q_FUNC_INFO, "DevSound already opened");
381
382 Q_ASSERT(!m_devSound);
383 m_devSound = new SymbianAudio::DevSoundWrapper(QAudio::AudioOutput, this);
384
385 connect(m_devSound, SIGNAL(initializeComplete(int)),
386 this, SLOT(devsoundInitializeComplete(int)));
387 connect(m_devSound, SIGNAL(bufferToBeProcessed(CMMFBuffer *)),
388 this, SLOT(devsoundBufferToBeFilled(CMMFBuffer *)));
389 connect(m_devSound, SIGNAL(processingError(int)),
390 this, SLOT(devsoundPlayError(int)));
391
392 setState(SymbianAudio::InitializingState);
393 m_devSound->initialize(m_format.codec());
394}
395
396void QAudioOutputPrivate::startPlayback()
397{
398 bool ok = m_devSound->setFormat(m_format);
399 if (ok)
400 ok = m_devSound->start();
401
402 if (ok) {
403 if (isDataReady())
404 setState(SymbianAudio::ActiveState);
405 else
406 setState(SymbianAudio::IdleState);
407
408 if (m_notifyInterval)
409 m_notifyTimer->start(m_notifyInterval);
410 m_underflow = false;
411
412 Q_ASSERT(m_devSound->samplesProcessed() == 0);
413
414 writePaddingData();
415
416 if (m_pullMode && m_source->bytesAvailable() && !m_bytesPadding)
417 dataReady();
418 } else {
419 setError(QAudio::OpenError);
420 close();
421 }
422}
423
424void QAudioOutputPrivate::writePaddingData()
425{
426 // See comments in suspend()
427
428 while (m_devSoundBuffer && m_bytesPadding) {
429 if (SymbianAudio::IdleState == m_internalState)
430 setState(SymbianAudio::ActiveState);
431
432 TDes8 &outputBuffer = m_devSoundBuffer->Data();
433 const qint64 outputBytes = bytesFree();
434 const qint64 paddingBytes = outputBytes < m_bytesPadding ?
435 outputBytes : m_bytesPadding;
436 unsigned char *ptr = const_cast<unsigned char*>(outputBuffer.Ptr());
437 Mem::FillZ(ptr, paddingBytes);
438 outputBuffer.SetLength(outputBuffer.Length() + paddingBytes);
439 m_bytesPadding -= paddingBytes;
440 Q_ASSERT(m_bytesPadding >= 0);
441
442 if (m_pullMode && m_source->atEnd())
443 lastBufferFilled();
444 if ((paddingBytes == outputBytes) || !m_bytesPadding)
445 bufferFilled();
446 }
447}
448
449qint64 QAudioOutputPrivate::pushData(const char *data, qint64 len)
450{
451 // Data has been written to SymbianAudioOutputPrivate
452
453 Q_ASSERT_X(!m_pullMode, Q_FUNC_INFO,
454 "pushData called when in pull mode");
455
456 const unsigned char *const inputPtr =
457 reinterpret_cast<const unsigned char*>(data);
458 qint64 bytesWritten = 0;
459
460 if (SymbianAudio::IdleState == m_internalState)
461 setState(SymbianAudio::ActiveState);
462
463 while (m_devSoundBuffer && (bytesWritten < len)) {
464 // writePaddingData() is called from BufferToBeFilled(), so we should
465 // never have any padding data left at this point.
466 Q_ASSERT_X(0 == m_bytesPadding, Q_FUNC_INFO,
467 "Padding bytes remaining in pushData");
468
469 TDes8 &outputBuffer = m_devSoundBuffer->Data();
470
471 const qint64 outputBytes = bytesFree();
472 const qint64 inputBytes = len - bytesWritten;
473 const qint64 copyBytes = outputBytes < inputBytes ?
474 outputBytes : inputBytes;
475
476 outputBuffer.Append(inputPtr + bytesWritten, copyBytes);
477 bytesWritten += copyBytes;
478
479 bufferFilled();
480 }
481
482 m_pushDataReady = (bytesWritten < len);
483
484 // If DevSound is still initializing (m_internalState == InitializingState),
485 // we cannot transition m_internalState to ActiveState, but we must emit
486 // an (external) state change from IdleState to ActiveState. The following
487 // call triggers this signal.
488 setState(m_internalState);
489
490 return bytesWritten;
491}
492
493void QAudioOutputPrivate::pullData()
494{
495 Q_ASSERT_X(m_pullMode, Q_FUNC_INFO,
496 "pullData called when in push mode");
497
498 // writePaddingData() is called by BufferToBeFilled() before pullData(),
499 // so we should never have any padding data left at this point.
500 Q_ASSERT_X(0 == m_bytesPadding, Q_FUNC_INFO,
501 "Padding bytes remaining in pullData");
502
503 qint64 inputBytes = m_source->bytesAvailable();
504 while (m_devSoundBuffer && inputBytes) {
505 if (SymbianAudio::IdleState == m_internalState)
506 setState(SymbianAudio::ActiveState);
507
508 TDes8 &outputBuffer = m_devSoundBuffer->Data();
509
510 const qint64 outputBytes = bytesFree();
511 const qint64 copyBytes = outputBytes < inputBytes ?
512 outputBytes : inputBytes;
513
514 char *outputPtr = (char*)(outputBuffer.Ptr() + outputBuffer.Length());
515 const qint64 bytesCopied = m_source->read(outputPtr, copyBytes);
516 Q_ASSERT(bytesCopied == copyBytes);
517 outputBuffer.SetLength(outputBuffer.Length() + bytesCopied);
518 inputBytes -= bytesCopied;
519
520 if (m_source->atEnd())
521 lastBufferFilled();
522 else if (copyBytes == outputBytes)
523 bufferFilled();
524 }
525}
526
527void QAudioOutputPrivate::bufferFilled()
528{
529 Q_ASSERT_X(m_devSoundBuffer, Q_FUNC_INFO, "No buffer to return");
530
531 const TDes8 &outputBuffer = m_devSoundBuffer->Data();
532 m_bytesWritten += outputBuffer.Length();
533
534 m_devSoundBuffer = 0;
535
536 m_samplesPlayed = getSamplesPlayed();
537 m_underflowTimer->start();
538
539 if (QAudio::UnderrunError == m_error)
540 m_error = QAudio::NoError;
541
542 m_devSound->bufferProcessed();
543}
544
545void QAudioOutputPrivate::lastBufferFilled()
546{
547 Q_ASSERT_X(m_devSoundBuffer, Q_FUNC_INFO, "No buffer to fill");
548 Q_ASSERT_X(!m_lastBuffer, Q_FUNC_INFO, "Last buffer already sent");
549 m_lastBuffer = true;
550 m_devSoundBuffer->SetLastBuffer(ETrue);
551 bufferFilled();
552}
553
554void QAudioOutputPrivate::close()
555{
556 m_notifyTimer->stop();
557 m_underflowTimer->stop();
558
559 m_error = QAudio::NoError;
560
561 if (m_devSound)
562 m_devSound->stop();
563 delete m_devSound;
564 m_devSound = 0;
565
566 m_devSoundBuffer = 0;
567 m_devSoundBufferSize = 0;
568
569 if (!m_pullMode) // m_source is owned
570 delete m_source;
571 m_pullMode = false;
572 m_source = 0;
573
574 m_bytesWritten = 0;
575 m_pushDataReady = false;
576 m_bytesPadding = 0;
577 m_underflow = false;
578 m_lastBuffer = false;
579 m_samplesPlayed = 0;
580 m_totalSamplesPlayed = 0;
581
582 setState(SymbianAudio::ClosedState);
583}
584
585qint64 QAudioOutputPrivate::getSamplesPlayed() const
586{
587 qint64 result = 0;
588 if (m_devSound) {
589 const qint64 samplesWritten = SymbianAudio::Utils::bytesToSamples(
590 m_format, m_bytesWritten);
591
592 if (m_underflow) {
593 result = samplesWritten;
594 } else {
595 // This is necessary because some DevSound implementations report
596 // that they have played more data than has actually been provided to them
597 // by the client.
598 const qint64 devSoundSamplesPlayed(m_devSound->samplesProcessed());
599 result = qMin(devSoundSamplesPlayed, samplesWritten);
600 }
601 }
602 return result;
603}
604
605void QAudioOutputPrivate::setError(QAudio::Error error)
606{
607 m_error = error;
608
609 // Although no state transition actually occurs here, a stateChanged event
610 // must be emitted to inform the client that the call to start() was
611 // unsuccessful.
612 if (QAudio::OpenError == error) {
613 emit stateChanged(QAudio::StoppedState);
614 } else {
615 if (QAudio::UnderrunError == error)
616 setState(SymbianAudio::IdleState);
617 else
618 // Close the DevSound instance. This causes a transition to
619 // StoppedState. This must be done asynchronously in case the
620 // current function was called from a DevSound event handler, in which
621 // case deleting the DevSound instance may cause an exception.
622 QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection);
623 }
624}
625
626void QAudioOutputPrivate::setState(SymbianAudio::State newInternalState)
627{
628 const QAudio::State oldExternalState = m_externalState;
629 m_internalState = newInternalState;
630 m_externalState = SymbianAudio::Utils::stateNativeToQt(m_internalState);
631
632 if (m_externalState != oldExternalState)
633 emit stateChanged(m_externalState);
634}
635
636bool QAudioOutputPrivate::isDataReady() const
637{
638 return (m_source && m_source->bytesAvailable())
639 || m_bytesPadding
640 || m_pushDataReady;
641}
642
643QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.