119 , m_lastBuffer(false) |
120 , m_lastBuffer(false) |
120 , m_underflowTimer(new QTimer(this)) |
121 , m_underflowTimer(new QTimer(this)) |
121 , m_samplesPlayed(0) |
122 , m_samplesPlayed(0) |
122 , m_totalSamplesPlayed(0) |
123 , m_totalSamplesPlayed(0) |
123 { |
124 { |
|
125 qRegisterMetaType<CMMFBuffer *>("CMMFBuffer *"); |
|
126 |
124 connect(m_notifyTimer.data(), SIGNAL(timeout()), this, SIGNAL(notify())); |
127 connect(m_notifyTimer.data(), SIGNAL(timeout()), this, SIGNAL(notify())); |
125 |
|
126 SymbianAudio::Utils::formatQtToNative(m_format, m_nativeFourCC, |
|
127 m_nativeFormat); |
|
128 |
128 |
129 m_underflowTimer->setInterval(UnderflowTimerInterval); |
129 m_underflowTimer->setInterval(UnderflowTimerInterval); |
130 connect(m_underflowTimer.data(), SIGNAL(timeout()), this, |
130 connect(m_underflowTimer.data(), SIGNAL(timeout()), this, |
131 SLOT(underflowTimerExpired())); |
131 SLOT(underflowTimerExpired())); |
132 } |
132 } |
194 // implementations, for play-mode DevSound sessions. We therefore |
192 // implementations, for play-mode DevSound sessions. We therefore |
195 // have to implement suspend() by calling CMMFDevSound::Stop(). |
193 // have to implement suspend() by calling CMMFDevSound::Stop(). |
196 // Because this causes buffered data to be dropped, we replace the |
194 // Because this causes buffered data to be dropped, we replace the |
197 // lost data with silence following a call to resume(), in order to |
195 // lost data with silence following a call to resume(), in order to |
198 // ensure that processedUSecs() returns the correct value. |
196 // ensure that processedUSecs() returns the correct value. |
199 m_devSound->Stop(); |
197 m_devSound->stop(); |
200 m_totalSamplesPlayed += samplesPlayed; |
198 m_totalSamplesPlayed += samplesPlayed; |
201 |
199 |
202 // Calculate the amount of data dropped |
200 // Calculate the amount of data dropped |
203 const qint64 paddingSamples = samplesWritten - samplesPlayed; |
201 const qint64 paddingSamples = samplesWritten - samplesPlayed; |
|
202 Q_ASSERT(paddingSamples >= 0); |
204 m_bytesPadding = SymbianAudio::Utils::samplesToBytes(m_format, |
203 m_bytesPadding = SymbianAudio::Utils::samplesToBytes(m_format, |
205 paddingSamples); |
204 paddingSamples); |
206 |
205 |
207 setState(SymbianAudio::SuspendedState); |
206 setState(SymbianAudio::SuspendedState); |
208 } |
207 } |
209 } |
208 } |
210 |
209 |
211 void QAudioOutputPrivate::resume() |
210 void QAudioOutputPrivate::resume() |
212 { |
211 { |
213 if (SymbianAudio::SuspendedState == m_internalState) |
212 if (SymbianAudio::SuspendedState == m_internalState) { |
|
213 if (!m_pullMode && m_devSoundBuffer && m_devSoundBuffer->Data().Length()) |
|
214 bufferFilled(); |
214 startPlayback(); |
215 startPlayback(); |
|
216 } |
215 } |
217 } |
216 |
218 |
217 int QAudioOutputPrivate::bytesFree() const |
219 int QAudioOutputPrivate::bytesFree() const |
218 { |
220 { |
219 int result = 0; |
221 int result = 0; |
298 QAudioFormat QAudioOutputPrivate::format() const |
303 QAudioFormat QAudioOutputPrivate::format() const |
299 { |
304 { |
300 return m_format; |
305 return m_format; |
301 } |
306 } |
302 |
307 |
303 //----------------------------------------------------------------------------- |
308 |
304 // MDevSoundObserver implementation |
309 //----------------------------------------------------------------------------- |
305 //----------------------------------------------------------------------------- |
310 // Private functions |
306 |
311 //----------------------------------------------------------------------------- |
307 void QAudioOutputPrivate::InitializeComplete(TInt aError) |
312 |
|
313 void QAudioOutputPrivate::dataReady() |
|
314 { |
|
315 // Client-provided QIODevice has data ready to read. |
|
316 |
|
317 Q_ASSERT_X(m_source->bytesAvailable(), Q_FUNC_INFO, |
|
318 "readyRead signal received, but no data available"); |
|
319 |
|
320 if (!m_bytesPadding) |
|
321 pullData(); |
|
322 } |
|
323 |
|
324 void QAudioOutputPrivate::underflowTimerExpired() |
|
325 { |
|
326 const TInt samplesPlayed = getSamplesPlayed(); |
|
327 if (m_samplesPlayed && (samplesPlayed == m_samplesPlayed)) { |
|
328 setError(QAudio::UnderrunError); |
|
329 } else { |
|
330 m_samplesPlayed = samplesPlayed; |
|
331 m_underflowTimer->start(); |
|
332 } |
|
333 } |
|
334 |
|
335 void QAudioOutputPrivate::devsoundInitializeComplete(int err) |
308 { |
336 { |
309 Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState, |
337 Q_ASSERT_X(SymbianAudio::InitializingState == m_internalState, |
310 Q_FUNC_INFO, "Invalid state"); |
338 Q_FUNC_INFO, "Invalid state"); |
311 |
339 |
312 if (KErrNone == aError) |
340 if (!err && m_devSound->isFormatSupported(m_format)) |
313 startPlayback(); |
341 startPlayback(); |
314 } |
342 else |
315 |
343 setError(QAudio::OpenError); |
316 void QAudioOutputPrivate::ToneFinished(TInt aError) |
344 } |
317 { |
345 |
318 Q_UNUSED(aError) |
346 void QAudioOutputPrivate::devsoundBufferToBeFilled(CMMFBuffer *bufferBase) |
319 // This class doesn't use DevSound's tone playback functions, so should |
347 { |
320 // never receive this callback. |
348 // Following receipt of this signal, DevSound should not provide another |
321 Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback"); |
|
322 } |
|
323 |
|
324 void QAudioOutputPrivate::BufferToBeFilled(CMMFBuffer *aBuffer) |
|
325 { |
|
326 // Following receipt of this callback, DevSound should not provide another |
|
327 // buffer until we have returned the current one. |
349 // buffer until we have returned the current one. |
328 Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held"); |
350 Q_ASSERT_X(!m_devSoundBuffer, Q_FUNC_INFO, "Buffer already held"); |
329 |
351 |
330 // Will be returned to DevSound by bufferFilled(). |
352 // Will be returned to DevSoundWrapper by bufferProcessed(). |
331 m_devSoundBuffer = static_cast<CMMFDataBuffer*>(aBuffer); |
353 m_devSoundBuffer = static_cast<CMMFDataBuffer*>(bufferBase); |
332 |
354 |
333 if (!m_devSoundBufferSize) |
355 if (!m_devSoundBufferSize) |
334 m_devSoundBufferSize = m_devSoundBuffer->Data().MaxLength(); |
356 m_devSoundBufferSize = m_devSoundBuffer->Data().MaxLength(); |
335 |
357 |
336 writePaddingData(); |
358 writePaddingData(); |
337 |
359 |
338 if (m_pullMode && isDataReady() && !m_bytesPadding) |
360 if (m_pullMode && isDataReady() && !m_bytesPadding) |
339 pullData(); |
361 pullData(); |
340 } |
362 } |
341 |
363 |
342 void QAudioOutputPrivate::PlayError(TInt aError) |
364 void QAudioOutputPrivate::devsoundPlayError(int err) |
343 { |
365 { |
344 switch (aError) { |
366 switch (err) { |
345 case KErrUnderflow: |
367 case KErrUnderflow: |
346 m_underflow = true; |
368 m_underflow = true; |
347 if (m_pullMode && !m_lastBuffer) |
369 if (m_pullMode && !m_lastBuffer) |
348 setError(QAudio::UnderrunError); |
370 setError(QAudio::UnderrunError); |
349 else |
371 else |
353 setError(QAudio::IOError); |
375 setError(QAudio::IOError); |
354 break; |
376 break; |
355 } |
377 } |
356 } |
378 } |
357 |
379 |
358 void QAudioOutputPrivate::BufferToBeEmptied(CMMFBuffer *aBuffer) |
|
359 { |
|
360 Q_UNUSED(aBuffer) |
|
361 // This class doesn't use DevSound in record mode, so should never receive |
|
362 // this callback. |
|
363 Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback"); |
|
364 } |
|
365 |
|
366 void QAudioOutputPrivate::RecordError(TInt aError) |
|
367 { |
|
368 Q_UNUSED(aError) |
|
369 // This class doesn't use DevSound in record mode, so should never receive |
|
370 // this callback. |
|
371 Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback"); |
|
372 } |
|
373 |
|
374 void QAudioOutputPrivate::ConvertError(TInt aError) |
|
375 { |
|
376 Q_UNUSED(aError) |
|
377 // This class doesn't use DevSound's format conversion functions, so |
|
378 // should never receive this callback. |
|
379 Q_ASSERT_X(false, Q_FUNC_INFO, "Unexpected callback"); |
|
380 } |
|
381 |
|
382 void QAudioOutputPrivate::DeviceMessage(TUid aMessageType, const TDesC8 &aMsg) |
|
383 { |
|
384 Q_UNUSED(aMessageType) |
|
385 Q_UNUSED(aMsg) |
|
386 // Ignore this callback. |
|
387 } |
|
388 |
|
389 //----------------------------------------------------------------------------- |
|
390 // Private functions |
|
391 //----------------------------------------------------------------------------- |
|
392 |
|
393 void QAudioOutputPrivate::dataReady() |
|
394 { |
|
395 // Client-provided QIODevice has data ready to read. |
|
396 |
|
397 Q_ASSERT_X(m_source->bytesAvailable(), Q_FUNC_INFO, |
|
398 "readyRead signal received, but no data available"); |
|
399 |
|
400 if (!m_bytesPadding) |
|
401 pullData(); |
|
402 } |
|
403 |
|
404 void QAudioOutputPrivate::underflowTimerExpired() |
|
405 { |
|
406 const TInt samplesPlayed = getSamplesPlayed(); |
|
407 if (m_samplesPlayed && (samplesPlayed == m_samplesPlayed)) { |
|
408 setError(QAudio::UnderrunError); |
|
409 } else { |
|
410 m_samplesPlayed = samplesPlayed; |
|
411 m_underflowTimer->start(); |
|
412 } |
|
413 } |
|
414 |
|
415 void QAudioOutputPrivate::open() |
380 void QAudioOutputPrivate::open() |
416 { |
381 { |
417 Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState, |
382 Q_ASSERT_X(SymbianAudio::ClosedState == m_internalState, |
418 Q_FUNC_INFO, "DevSound already opened"); |
383 Q_FUNC_INFO, "DevSound already opened"); |
419 |
384 |
420 QT_TRAP_THROWING( m_devSound.reset(CMMFDevSound::NewL()) ) |
385 Q_ASSERT(!m_devSound); |
421 |
386 m_devSound = new SymbianAudio::DevSoundWrapper(QAudio::AudioOutput, this); |
422 QScopedPointer<SymbianAudio::DevSoundCapabilities> caps( |
387 |
423 new SymbianAudio::DevSoundCapabilities(*m_devSound, |
388 connect(m_devSound, SIGNAL(initializeComplete(int)), |
424 QAudio::AudioOutput)); |
389 this, SLOT(devsoundInitializeComplete(int))); |
425 |
390 connect(m_devSound, SIGNAL(bufferToBeProcessed(CMMFBuffer *)), |
426 int err = SymbianAudio::Utils::isFormatSupported(m_format, *caps) ? |
391 this, SLOT(devsoundBufferToBeFilled(CMMFBuffer *))); |
427 KErrNone : KErrNotSupported; |
392 connect(m_devSound, SIGNAL(processingError(int)), |
428 |
393 this, SLOT(devsoundPlayError(int))); |
429 if (KErrNone == err) { |
394 |
430 setState(SymbianAudio::InitializingState); |
395 setState(SymbianAudio::InitializingState); |
431 TRAP(err, m_devSound->InitializeL(*this, m_nativeFourCC, |
396 m_devSound->initialize(m_format.codec()); |
432 EMMFStatePlaying)); |
|
433 } |
|
434 |
|
435 if (KErrNone != err) { |
|
436 setError(QAudio::OpenError); |
|
437 m_devSound.reset(); |
|
438 } |
|
439 } |
397 } |
440 |
398 |
441 void QAudioOutputPrivate::startPlayback() |
399 void QAudioOutputPrivate::startPlayback() |
442 { |
400 { |
443 TRAPD(err, startDevSoundL()); |
401 bool ok = m_devSound->setFormat(m_format); |
444 if (KErrNone == err) { |
402 if (ok) |
|
403 ok = m_devSound->start(); |
|
404 |
|
405 if (ok) { |
445 if (isDataReady()) |
406 if (isDataReady()) |
446 setState(SymbianAudio::ActiveState); |
407 setState(SymbianAudio::ActiveState); |
447 else |
408 else |
448 setState(SymbianAudio::IdleState); |
409 setState(SymbianAudio::IdleState); |
449 |
410 |
450 m_notifyTimer->start(m_notifyInterval); |
411 if (m_notifyInterval) |
|
412 m_notifyTimer->start(m_notifyInterval); |
451 m_underflow = false; |
413 m_underflow = false; |
452 |
414 |
453 Q_ASSERT(m_devSound->SamplesPlayed() == 0); |
415 Q_ASSERT(m_devSound->samplesProcessed() == 0); |
454 |
416 |
455 writePaddingData(); |
417 writePaddingData(); |
456 |
418 |
457 if (m_pullMode && m_source->bytesAvailable() && !m_bytesPadding) |
419 if (m_pullMode && m_source->bytesAvailable() && !m_bytesPadding) |
458 dataReady(); |
420 dataReady(); |
459 } else { |
421 } else { |
460 setError(QAudio::OpenError); |
422 setError(QAudio::OpenError); |
461 close(); |
423 close(); |
462 } |
424 } |
463 } |
|
464 |
|
465 void QAudioOutputPrivate::startDevSoundL() |
|
466 { |
|
467 TMMFCapabilities nativeFormat = m_devSound->Config(); |
|
468 m_nativeFormat.iBufferSize = nativeFormat.iBufferSize; |
|
469 m_devSound->SetConfigL(m_nativeFormat); |
|
470 m_devSound->PlayInitL(); |
|
471 } |
425 } |
472 |
426 |
473 void QAudioOutputPrivate::writePaddingData() |
427 void QAudioOutputPrivate::writePaddingData() |
474 { |
428 { |
475 // See comments in suspend() |
429 // See comments in suspend() |
656 m_error = error; |
610 m_error = error; |
657 |
611 |
658 // Although no state transition actually occurs here, a stateChanged event |
612 // Although no state transition actually occurs here, a stateChanged event |
659 // must be emitted to inform the client that the call to start() was |
613 // must be emitted to inform the client that the call to start() was |
660 // unsuccessful. |
614 // unsuccessful. |
661 if (QAudio::OpenError == error) |
615 if (QAudio::OpenError == error) { |
662 emit stateChanged(QAudio::StoppedState); |
616 emit stateChanged(QAudio::StoppedState); |
663 |
617 } else { |
664 if (QAudio::UnderrunError == error) |
618 if (QAudio::UnderrunError == error) |
665 setState(SymbianAudio::IdleState); |
619 setState(SymbianAudio::IdleState); |
666 else |
620 else |
667 // Close the DevSound instance. This causes a transition to |
621 // Close the DevSound instance. This causes a transition to |
668 // StoppedState. This must be done asynchronously in case the |
622 // StoppedState. This must be done asynchronously in case the |
669 // current function was called from a DevSound event handler, in which |
623 // current function was called from a DevSound event handler, in which |
670 // case deleting the DevSound instance may cause an exception. |
624 // case deleting the DevSound instance may cause an exception. |
671 QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); |
625 QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); |
|
626 } |
672 } |
627 } |
673 |
628 |
674 void QAudioOutputPrivate::setState(SymbianAudio::State newInternalState) |
629 void QAudioOutputPrivate::setState(SymbianAudio::State newInternalState) |
675 { |
630 { |
676 const QAudio::State oldExternalState = m_externalState; |
631 const QAudio::State oldExternalState = m_externalState; |
677 m_internalState = newInternalState; |
632 m_internalState = newInternalState; |
678 m_externalState = SymbianAudio::Utils::stateNativeToQt( |
633 m_externalState = SymbianAudio::Utils::stateNativeToQt(m_internalState); |
679 m_internalState, initializingState()); |
|
680 |
634 |
681 if (m_externalState != oldExternalState) |
635 if (m_externalState != oldExternalState) |
682 emit stateChanged(m_externalState); |
636 emit stateChanged(m_externalState); |
683 } |
637 } |
684 |
638 |