@@ -71,6 +71,10 @@ int paV19CallbackClkRef(const void *inputBuffer, void *outputBuffer,
7171 (const CSAMPLE*) inputBuffer, timeInfo, statusFlags);
7272}
7373
74+ void paFinishedCallback (void * soundDevice) {
75+ return ((SoundDevicePortAudio*)soundDevice)->finishedCallback ();
76+ }
77+
7478const QRegularExpression kAlsaHwDeviceRegex (" (.*) \\ ((plug)?(hw:(\\ d)+(,(\\ d)+))?\\ )" );
7579
7680const QString kAppGroup = QStringLiteral(" [App]" );
@@ -360,12 +364,14 @@ SoundDeviceStatus SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffer
360364 }
361365#endif
362366
363- // Start stream
367+ // Set the finished callback, used when stopping the stream
368+ m_bFinished = false ;
369+ Pa_SetStreamFinishedCallback (pStream, paFinishedCallback);
364370
365- // Tell the callback that we are starting but not ready yet.
366- // The callback will return paContinue but not access m_pStream
367- // yet.
371+ // Tell the callback to return paContinue, once running.
368372 m_callbackResult.store (paContinue, std::memory_order_release);
373+
374+ // Start stream
369375 err = Pa_StartStream (pStream);
370376 if (err != paNoError) {
371377 qWarning () << " PortAudio: Start stream error:" << Pa_GetErrorText (err);
@@ -407,12 +413,28 @@ bool SoundDevicePortAudio::isOpen() const {
407413 return m_pStream.load () != nullptr ;
408414}
409415
416+ void SoundDevicePortAudio::finishedCallback () {
417+ // This callback is called by PortAudio when the process callback returns
418+ // paAbort to indicate the stream has to finish. This is triggered in the
419+ // SoundDevicePortAudio::close() method.
420+ std::unique_lock lock (m_finishedMutex);
421+ m_bFinished = true ;
422+ m_finishedCV.notify_one ();
423+ }
424+
425+ void SoundDevicePortAudio::waitForFinished () {
426+ // Wait until finishedCallback has been called. The timeout should never be
427+ // reached, because when the process callback return paAbort, the stream
428+ // will finish as soon as possible.
429+ std::unique_lock lock (m_finishedMutex);
430+ if (!m_finishedCV.wait_for (lock, std::chrono::seconds (1 ), [&] { return m_bFinished; })) {
431+ qWarning () << " Timeout reached when waiting for PortAudio stream to finish" ;
432+ }
433+ }
434+
410435SoundDeviceStatus SoundDevicePortAudio::close () {
411436 // qDebug() << "SoundDevicePortAudio::close()" << m_deviceId;
412437
413- // Tell the callback to return paAbort. This stops the stream as soon as possible.
414- // After stopping the stream using this technique, Pa_StopStream() still has to be called.
415- m_callbackResult.store (paAbort, std::memory_order_release);
416438 PaStream* pStream = m_pStream.load (std::memory_order_relaxed);
417439 if (pStream) {
418440 // Make sure the stream is not stopped before we try stopping it.
@@ -430,27 +452,24 @@ SoundDeviceStatus SoundDevicePortAudio::close() {
430452 return SoundDeviceStatus::Error;
431453 }
432454
433- // Stop the stream. Since the start of this function we are returning
434- // paAbort from the callback. paAbort stops the stream as soon as possible.
435- // When stopping the stream using this technique, we still need to call
436- // Pa_StopStream() before starting the stream again.
455+ // Tell the process callback to return paAbort, which will cause
456+ // PortAudio to stop the stream as soon as possible. This has the
457+ // advantage over calling Pa_StopStream that it will not wait until all
458+ // the buffers have been flushed, which can take a few (annoying)
459+ // seconds when you're doing soundcard input.
460+ m_callbackResult.store (paAbort, std::memory_order_release);
437461
462+ // We wait until the callback set with Pa_SetStreamFinishedCallback has
463+ // been called. Without this, there is the risk of a deadlock when we
464+ // call Pa_StopStream and Pa_CloseStream below.
465+ waitForFinished ();
466+
467+ // Call Pa_StopStream() in case the above failed.
438468 err = Pa_StopStream (pStream);
469+
439470 // We should now be able to safely set m_pStream to null
440471 m_pStream.store (nullptr , std::memory_order_release);
441472
442- // Note: With the use of return value paAbort as described above,
443- // the comment below is not relevant anymore, but I am leaving it
444- // because of the BIG FAT WARNING, just in case.
445- //
446- // Trying Pa_AbortStream instead, because StopStream seems to wait
447- // until all the buffers have been flushed, which can take a
448- // few (annoying) seconds when you're doing soundcard input.
449- // (it flushes the input buffer, and then some, or something)
450- // BIG FAT WARNING: Pa_AbortStream() will kill threads while they're
451- // waiting on a mutex, which will leave the mutex in an screwy
452- // state. Don't use it!
453-
454473 if (err != paNoError) {
455474 qWarning () << " PortAudio: Stop stream error:"
456475 << Pa_GetErrorText (err) << m_deviceId;
0 commit comments