portaudiolayer: protect concurrent streams array access

Hotplug events may trigger a race condition when accessing the streams
array. This commit protects the access to the streams array by locking
a mutex. Locks are only acquired when streams are added, removed, or
initialized.

Gitlab: #1130
Change-Id: Ide0f2bfba47bf597981b26cce8d37ca076c7173b
This commit is contained in:
Andreas Traczyk
2025-05-26 18:08:32 -04:00
parent 1bca75aa4e
commit 018a4227f5

View File

@ -24,9 +24,10 @@
#include <portaudio.h>
#include <algorithm>
#include <windows.h>
#include <algorithm>
namespace jami {
enum Direction { Input = 0, Output = 1, IO = 2, End = 3 };
@ -61,7 +62,9 @@ struct PortAudioLayer::PortAudioLayerImpl
bool outputInitialized_ {false};
std::array<PaStream*, static_cast<int>(Direction::End)> streams_;
mutable std::mutex streamsMutex_;
bool paStopStream(Direction streamDirection);
bool hasFullDuplexStream() const;
AudioDeviceNotificationClientPtr audioDeviceNotificationClient_;
// The following flag used to debounce the device state changes,
@ -214,56 +217,53 @@ PortAudioLayer::startStream(AudioDeviceType stream)
void
PortAudioLayer::stopStream(AudioDeviceType stream)
{
auto stopPlayback = [this](bool fullDuplexMode = false) -> bool {
std::lock_guard lock(mutex_);
if (status_.load() != Status::Started)
return false;
bool stopped = false;
if (fullDuplexMode)
stopped = pimpl_->paStopStream(Direction::IO);
else
stopped = pimpl_->paStopStream(Direction::Output);
if (stopped)
status_.store(Status::Idle);
return stopped;
};
return;
bool stopped = false;
switch (stream) {
case AudioDeviceType::ALL:
if (pimpl_->streams_[Direction::IO]) {
stopped = stopPlayback(true);
if (pimpl_->hasFullDuplexStream()) {
stopped = pimpl_->paStopStream(Direction::IO);
JAMI_DBG("PortAudioLayer full-duplex stream stopped");
} else {
stopped = pimpl_->paStopStream(Direction::Input) && stopPlayback();
}
if (stopped) {
recordChanged(false);
if (pimpl_->paStopStream(Direction::Output)) {
playbackChanged(false);
JAMI_DBG("PortAudioLayer I/O streams stopped");
} else
return;
stopped = true;
JAMI_DBG("PortAudioLayer output stream stopped");
}
if (pimpl_->paStopStream(Direction::Input)) {
recordChanged(false);
stopped = true;
JAMI_DBG("PortAudioLayer input stream stopped");
}
}
break;
case AudioDeviceType::CAPTURE:
if (pimpl_->paStopStream(Direction::Input)) {
recordChanged(false);
JAMI_DBG("PortAudioLayer input stream stopped");
} else
return;
stopped = true;
}
break;
case AudioDeviceType::PLAYBACK:
case AudioDeviceType::RINGTONE:
if (stopPlayback()) {
if (pimpl_->paStopStream(Direction::Output)) {
playbackChanged(false);
stopped = true;
JAMI_DBG("PortAudioLayer output stream stopped");
} else
return;
}
break;
}
// Flush the ring buffers
// Flush the ring buffers if any streams were stopped
if (stopped) {
JAMI_DBG("PortAudioLayer streams stopped, flushing buffers");
flushUrgent();
flushMain();
}
}
void
PortAudioLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
@ -417,6 +417,7 @@ PortAudioLayer::PortAudioLayerImpl::init(PortAudioLayer& parent)
initInput(parent);
initOutput(parent);
std::lock_guard lock(streamsMutex_);
std::fill(std::begin(streams_), std::end(streams_), nullptr);
}
@ -601,11 +602,12 @@ bool
PortAudioLayer::PortAudioLayerImpl::initInputStream(PortAudioLayer& parent)
{
JAMI_DBG("Open PortAudio Input Stream");
std::lock_guard lock(streamsMutex_);
auto& stream = streams_[Direction::Input];
auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE);
if (apiIndex != paNoDevice) {
openStreamDevice(
&streams_[Direction::Input],
&stream,
apiIndex,
Direction::Input,
[](const void* inputBuffer,
@ -643,6 +645,7 @@ bool
PortAudioLayer::PortAudioLayerImpl::initOutputStream(PortAudioLayer& parent)
{
JAMI_DBG("Open PortAudio Output Stream");
std::lock_guard lock(streamsMutex_);
auto& stream = streams_[Direction::Output];
auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK);
if (apiIndex != paNoDevice) {
@ -692,6 +695,7 @@ PortAudioLayer::PortAudioLayerImpl::initFullDuplexStream(PortAudioLayer& parent)
}
JAMI_DBG("Open PortAudio Full-duplex input/output stream");
std::lock_guard lock(streamsMutex_);
auto& stream = streams_[Direction::IO];
openFullDuplexStream(
&stream,
@ -728,6 +732,7 @@ PortAudioLayer::PortAudioLayerImpl::initFullDuplexStream(PortAudioLayer& parent)
bool
PortAudioLayer::PortAudioLayerImpl::paStopStream(Direction streamDirection)
{
std::lock_guard lock(streamsMutex_);
PaStream* paStream = streams_[streamDirection];
if (!paStream)
return false;
@ -754,6 +759,12 @@ PortAudioLayer::PortAudioLayerImpl::paStopStream(Direction streamDirection)
return true;
};
bool PortAudioLayer::PortAudioLayerImpl::hasFullDuplexStream() const
{
std::lock_guard lock(streamsMutex_);
return streams_[Direction::IO] != nullptr;
}
int
PortAudioLayer::PortAudioLayerImpl::paOutputCallback(PortAudioLayer& parent,
const int16_t* inputBuffer,