@@ -71,15 +71,13 @@ 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-
7874const QRegularExpression kAlsaHwDeviceRegex (" (.*) \\ ((plug)?(hw:(\\ d)+(,(\\ d)+))?\\ )" );
7975
8076const QString kAppGroup = QStringLiteral(" [App]" );
8177} // anonymous namespace
8278
79+ void paFinishedCallback (void * soundDevice);
80+
8381SoundDevicePortAudio::SoundDevicePortAudio (UserSettingsPointer config,
8482 SoundManager* sm,
8583 const PaDeviceInfo* deviceInfo,
@@ -364,7 +362,7 @@ SoundDeviceStatus SoundDevicePortAudio::open(bool isClkRefDevice, int syncBuffer
364362 }
365363#endif
366364
367- // Set the finished callback, used when stopping the stream
365+ // Set the finished callback. See makeStreamInactiveAndWait()
368366 m_bFinished = false ;
369367 Pa_SetStreamFinishedCallback (pStream, paFinishedCallback);
370368
@@ -413,22 +411,42 @@ bool SoundDevicePortAudio::isOpen() const {
413411 return m_pStream.load () != nullptr ;
414412}
415413
414+ void paFinishedCallback (void * soundDevice) {
415+ // This callback is called by PortAudio when Pa_IsStreamStopped becomes true.
416+ // This is triggered when Mixxx sets the return value from the process callback
417+ // to paAbort to indicate the stream has to finish. The return value is set in
418+ // the SoundDevicePortAudio::close() method.
419+ return ((SoundDevicePortAudio*)soundDevice)->finishedCallback ();
420+ }
421+
416422void 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.
423+ // Called by paFinishedCallback
420424 std::unique_lock lock (m_finishedMutex);
421425 m_bFinished = true ;
422426 m_finishedCV.notify_one ();
423427}
424428
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" ;
429+ void SoundDevicePortAudio::makeStreamInactiveAndWait () {
430+ // Tell the process callback to return paAbort, which will cause
431+ // PortAudio to make the stream inactive as soon as possible. This
432+ // has the advantage over calling Pa_StopStream directly, that it
433+ // will not wait until all the buffers have been flushed, which
434+ // can take a few (annoying) seconds when you're doing soundcard input.
435+ m_callbackResult.store (paAbort, std::memory_order_release);
436+
437+ // We wait until the stream has become inactive, by waiting until the
438+ // finishedCallback - as set with Pa_SetStreamFinishedCallback - has
439+ // been called, using m_bFinished and conditional variable m_finishedCV.
440+ // Without this, we can have a deadlock when we call Pa_StopStream
441+ // later on.
442+ {
443+ std::unique_lock lock (m_finishedMutex);
444+ if (!m_finishedCV.wait_for (lock, std::chrono::seconds (1 ), [&] { return m_bFinished; })) {
445+ // The timeout should not be reached, because when the process
446+ // callback returns paAbort, the stream will finish as soon as possible.
447+ // We have it as a last result in case inactivating the stream stalls.
448+ qWarning () << " PortAudio: Timeout reached when waiting for PortAudio finish callback" ;
449+ }
432450 }
433451}
434452
@@ -439,43 +457,32 @@ SoundDeviceStatus SoundDevicePortAudio::close() {
439457 if (pStream) {
440458 // Make sure the stream is not stopped before we try stopping it.
441459 PaError err = Pa_IsStreamStopped (pStream);
442- // 1 means the stream is stopped. 0 means active.
443- if (err == 1 ) {
444- // qDebug() << "PortAudio: Stream already stopped, but no error.";
445- return SoundDeviceStatus::Ok;
446- }
447- // Real PaErrors are always negative.
460+ // 1 means the stream is stopped. 0 means active, negative means error
448461 if (err < 0 ) {
449- qWarning () << " PortAudio: Stream already stopped :"
462+ qWarning () << " PortAudio: Pa_IsStreamStopped returned error :"
450463 << Pa_GetErrorText (err) << m_deviceId;
451- m_pStream.store (nullptr , std::memory_order_release);
452464 return SoundDeviceStatus::Error;
453465 }
466+ if (err == 1 ) {
467+ qWarning () << " PortAudio: Stream already stopped" ;
468+ } else {
469+ // Tell the process callback to return paAbort and wait for the
470+ // callback set with Pa_SetStreamFinishedCallback to be called.
471+ makeStreamInactiveAndWait ();
472+
473+ // We can now safely call Pa_StopStream, which should not block as the
474+ // stream has become inactive.
475+ err = Pa_StopStream (m_pStream);
476+ if (err != paNoError) {
477+ qWarning () << " PortAudio: Stop stream error:"
478+ << Pa_GetErrorText (err) << m_deviceId;
479+ return SoundDeviceStatus::Error;
480+ }
481+ }
454482
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);
461-
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.
468- err = Pa_StopStream (pStream);
469-
470- // We should now be able to safely set m_pStream to null
483+ // We can now safely set m_pStream to null
471484 m_pStream.store (nullptr , std::memory_order_release);
472485
473- if (err != paNoError) {
474- qWarning () << " PortAudio: Stop stream error:"
475- << Pa_GetErrorText (err) << m_deviceId;
476- return SoundDeviceStatus::Error;
477- }
478-
479486 // Close stream
480487 err = Pa_CloseStream (pStream);
481488 if (err != paNoError) {
0 commit comments