Skip to content

Commit 528f92d

Browse files
m0dBm0dB
authored andcommitted
improved comments, renamed function to makeStreamInactiveAndWait, revised conditions in close()
1 parent ec3c95b commit 528f92d

File tree

2 files changed

+53
-47
lines changed

2 files changed

+53
-47
lines changed

src/soundio/sounddeviceportaudio.cpp

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
7874
const QRegularExpression kAlsaHwDeviceRegex("(.*) \\((plug)?(hw:(\\d)+(,(\\d)+))?\\)");
7975

8076
const QString kAppGroup = QStringLiteral("[App]");
8177
} // anonymous namespace
8278

79+
void paFinishedCallback(void* soundDevice);
80+
8381
SoundDevicePortAudio::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+
416422
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.
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) {

src/soundio/sounddeviceportaudio.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ class SoundDevicePortAudio : public SoundDevice {
6262
SINT framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo);
6363
void updateAudioLatencyUsage(const SINT framesPerBuffer);
6464

65-
// Wait until finishedCallback has been called.
66-
void waitForFinished();
65+
void makeStreamInactiveAndWait();
6766

6867
// PortAudio stream for this device.
6968
std::atomic<PaStream*> m_pStream;

0 commit comments

Comments
 (0)