Fix T86851: PulseAudio randomly asserts in background rendering

Upstream fix from Audaspace with simplified PulseAudio code.

Maniphest Tasks: T86851

Differential Revision: https://developer.blender.org/D10840
This commit is contained in:
Joerg Mueller 2021-03-27 12:22:23 +01:00
parent ae9d61e7fe
commit 35cf34de6d
Notes: blender-bot 2023-02-14 09:43:37 +01:00
Referenced by issue #86851, PulseAudio randomly asserts in background rendering
10 changed files with 267 additions and 219 deletions

View File

@ -42,6 +42,7 @@ set(SRC
src/devices/NULLDevice.cpp
src/devices/ReadDevice.cpp
src/devices/SoftwareDevice.cpp
src/devices/ThreadedDevice.cpp
src/Exception.cpp
src/file/File.cpp
src/file/FileManager.cpp
@ -148,6 +149,7 @@ set(PUBLIC_HDR
include/devices/NULLDevice.h
include/devices/ReadDevice.h
include/devices/SoftwareDevice.h
include/devices/ThreadedDevice.h
include/Exception.h
include/file/File.h
include/file/FileManager.h

View File

@ -255,6 +255,7 @@ protected:
/**
* This function tells the device, to start or pause playback.
* \param playing True if device should playback.
* \note This method is only called when the device is locked.
*/
virtual void playing(bool playing)=0;

View File

@ -0,0 +1,95 @@
/*******************************************************************************
* Copyright 2009-2016 Jörg Müller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
#pragma once
/**
* @file ThreadedDevice.h
* @ingroup plugin
* The ThreadedDevice class.
*/
#include "devices/SoftwareDevice.h"
#include <thread>
AUD_NAMESPACE_BEGIN
/**
* This device extends the SoftwareDevice with code for running mixing in a separate thread.
*/
class AUD_PLUGIN_API ThreadedDevice : public SoftwareDevice
{
private:
/**
* Whether there is currently playback.
*/
bool m_playing;
/**
* Whether the current playback should stop.
*/
bool m_stop;
/**
* The streaming thread.
*/
std::thread m_thread;
/**
* Starts the streaming thread.
*/
AUD_LOCAL void start();
/**
* Streaming thread main function.
*/
AUD_LOCAL virtual void runMixingThread()=0;
// delete copy constructor and operator=
ThreadedDevice(const ThreadedDevice&) = delete;
ThreadedDevice& operator=(const ThreadedDevice&) = delete;
protected:
virtual void playing(bool playing);
/**
* Empty default constructor. To setup the device call the function create()
* and to uninitialize call destroy().
*/
ThreadedDevice();
/**
* Indicates that the mixing thread should be stopped.
* \return Whether the mixing thread should be stopping.
* \warning For thread safety, the device needs to be locked, when this method is called.
*/
inline bool shouldStop() { return m_stop; }
/**
* This method needs to be called when the mixing thread is stopping.
* \warning For thread safety, the device needs to be locked, when this method is called.
*/
inline void doStop() { m_stop = m_playing = false; }
/**
* Stops all playback and notifies the mixing thread to stop.
* \warning The device has to be unlocked to not run into a deadlock.
*/
void stopMixingThread();
};
AUD_NAMESPACE_END

View File

@ -27,9 +27,9 @@ void PulseAudioDevice::PulseAudio_state_callback(pa_context *context, void *data
{
PulseAudioDevice* device = (PulseAudioDevice*)data;
device->m_state = AUD_pa_context_get_state(context);
std::lock_guard<ILockable> lock(*device);
AUD_pa_threaded_mainloop_signal(device->m_mainloop, 0);
device->m_state = AUD_pa_context_get_state(context);
}
void PulseAudioDevice::PulseAudio_request(pa_stream *stream, size_t num_bytes, void *data)
@ -68,29 +68,40 @@ void PulseAudioDevice::PulseAudio_underflow(pa_stream *stream, void *data)
}
}
void PulseAudioDevice::playing(bool playing)
void PulseAudioDevice::runMixingThread()
{
m_playback = playing;
for(;;)
{
{
std::lock_guard<ILockable> lock(*this);
AUD_pa_stream_cork(m_stream, playing ? 0 : 1, nullptr, nullptr);
if(shouldStop())
{
AUD_pa_stream_cork(m_stream, 1, nullptr, nullptr);
doStop();
return;
}
}
if(AUD_pa_stream_is_corked(m_stream))
AUD_pa_stream_cork(m_stream, 0, nullptr, nullptr);
AUD_pa_mainloop_iterate(m_mainloop, true, nullptr);
}
}
PulseAudioDevice::PulseAudioDevice(std::string name, DeviceSpecs specs, int buffersize) :
m_playback(false),
m_state(PA_CONTEXT_UNCONNECTED),
m_buffersize(buffersize),
m_underflows(0)
{
m_mainloop = AUD_pa_threaded_mainloop_new();
m_mainloop = AUD_pa_mainloop_new();
AUD_pa_threaded_mainloop_lock(m_mainloop);
m_context = AUD_pa_context_new(AUD_pa_threaded_mainloop_get_api(m_mainloop), name.c_str());
m_context = AUD_pa_context_new(AUD_pa_mainloop_get_api(m_mainloop), name.c_str());
if(!m_context)
{
AUD_pa_threaded_mainloop_unlock(m_mainloop);
AUD_pa_threaded_mainloop_free(m_mainloop);
AUD_pa_mainloop_free(m_mainloop);
AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
}
@ -99,26 +110,21 @@ PulseAudioDevice::PulseAudioDevice(std::string name, DeviceSpecs specs, int buff
AUD_pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
AUD_pa_threaded_mainloop_start(m_mainloop);
while(m_state != PA_CONTEXT_READY)
{
switch(m_state)
{
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
AUD_pa_threaded_mainloop_unlock(m_mainloop);
AUD_pa_threaded_mainloop_stop(m_mainloop);
AUD_pa_context_disconnect(m_context);
AUD_pa_context_unref(m_context);
AUD_pa_threaded_mainloop_free(m_mainloop);
AUD_pa_mainloop_free(m_mainloop);
AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
break;
default:
AUD_pa_threaded_mainloop_wait(m_mainloop);
AUD_pa_mainloop_iterate(m_mainloop, true, nullptr);
break;
}
}
@ -166,13 +172,10 @@ PulseAudioDevice::PulseAudioDevice(std::string name, DeviceSpecs specs, int buff
if(!m_stream)
{
AUD_pa_threaded_mainloop_unlock(m_mainloop);
AUD_pa_threaded_mainloop_stop(m_mainloop);
AUD_pa_context_disconnect(m_context);
AUD_pa_context_unref(m_context);
AUD_pa_threaded_mainloop_free(m_mainloop);
AUD_pa_mainloop_free(m_mainloop);
AUD_THROW(DeviceException, "Could not create PulseAudio stream.");
}
@ -188,32 +191,27 @@ PulseAudioDevice::PulseAudioDevice(std::string name, DeviceSpecs specs, int buff
buffer_attr.prebuf = -1U;
buffer_attr.tlength = buffersize;
if(AUD_pa_stream_connect_playback(m_stream, nullptr, &buffer_attr, static_cast<pa_stream_flags_t>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE), nullptr, nullptr) < 0)
if(AUD_pa_stream_connect_playback(m_stream, nullptr, &buffer_attr, static_cast<pa_stream_flags_t>(PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE), nullptr, nullptr) < 0)
{
AUD_pa_threaded_mainloop_unlock(m_mainloop);
AUD_pa_threaded_mainloop_stop(m_mainloop);
AUD_pa_context_disconnect(m_context);
AUD_pa_context_unref(m_context);
AUD_pa_threaded_mainloop_free(m_mainloop);
AUD_pa_mainloop_free(m_mainloop);
AUD_THROW(DeviceException, "Could not connect PulseAudio stream.");
}
AUD_pa_threaded_mainloop_unlock(m_mainloop);
create();
}
PulseAudioDevice::~PulseAudioDevice()
{
AUD_pa_threaded_mainloop_stop(m_mainloop);
stopMixingThread();
AUD_pa_context_disconnect(m_context);
AUD_pa_context_unref(m_context);
AUD_pa_threaded_mainloop_free(m_mainloop);
AUD_pa_mainloop_free(m_mainloop);
destroy();
}

View File

@ -26,7 +26,7 @@
* The PulseAudioDevice class.
*/
#include "devices/SoftwareDevice.h"
#include "devices/ThreadedDevice.h"
#include <pulse/pulseaudio.h>
@ -35,15 +35,10 @@ AUD_NAMESPACE_BEGIN
/**
* This device plays back through PulseAudio, the simple direct media layer.
*/
class AUD_PLUGIN_API PulseAudioDevice : public SoftwareDevice
class AUD_PLUGIN_API PulseAudioDevice : public ThreadedDevice
{
private:
/**
* Whether there is currently playback.
*/
volatile bool m_playback;
pa_threaded_mainloop* m_mainloop;
pa_mainloop* m_mainloop;
pa_context* m_context;
pa_stream* m_stream;
pa_context_state_t m_state;
@ -74,13 +69,15 @@ private:
*/
AUD_LOCAL static void PulseAudio_underflow(pa_stream* stream, void* data);
/**
* Streaming thread main function.
*/
AUD_LOCAL void runMixingThread();
// delete copy constructor and operator=
PulseAudioDevice(const PulseAudioDevice&) = delete;
PulseAudioDevice& operator=(const PulseAudioDevice&) = delete;
protected:
virtual void playing(bool playing);
public:
/**
* Opens the PulseAudio audio device for playback.

View File

@ -24,18 +24,14 @@ PULSEAUDIO_SYMBOL(pa_context_unref);
PULSEAUDIO_SYMBOL(pa_stream_begin_write);
PULSEAUDIO_SYMBOL(pa_stream_connect_playback);
PULSEAUDIO_SYMBOL(pa_stream_cork);
PULSEAUDIO_SYMBOL(pa_stream_is_corked);
PULSEAUDIO_SYMBOL(pa_stream_new);
PULSEAUDIO_SYMBOL(pa_stream_set_buffer_attr);
PULSEAUDIO_SYMBOL(pa_stream_set_underflow_callback);
PULSEAUDIO_SYMBOL(pa_stream_set_write_callback);
PULSEAUDIO_SYMBOL(pa_stream_write);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_free);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_get_api);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_lock);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_new);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_signal);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_start);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_stop);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_unlock);
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_wait);
PULSEAUDIO_SYMBOL(pa_mainloop_free);
PULSEAUDIO_SYMBOL(pa_mainloop_get_api);
PULSEAUDIO_SYMBOL(pa_mainloop_new);
PULSEAUDIO_SYMBOL(pa_mainloop_iterate);

View File

@ -31,159 +31,83 @@ template <class T> void SafeRelease(T **ppT)
}
}
void WASAPIDevice::start()
{
lock();
// thread is still running, we can abort stopping it
if(m_stop)
m_stop = false;
// thread is not running, let's start it
else if(!m_playing)
{
if(m_thread.joinable())
m_thread.join();
m_playing = true;
m_thread = std::thread(&WASAPIDevice::updateStream, this);
}
unlock();
}
void WASAPIDevice::updateStream()
void WASAPIDevice::runMixingThread()
{
UINT32 buffer_size;
UINT32 padding;
UINT32 length;
data_t* buffer;
lock();
if(FAILED(m_audio_client->GetBufferSize(&buffer_size)))
{
m_playing = false;
m_stop = false;
unlock();
return;
}
IAudioRenderClient* render_client = nullptr;
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
if(FAILED(m_audio_client->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(&render_client))))
{
m_playing = false;
m_stop = false;
unlock();
return;
std::lock_guard<ILockable> lock(*this);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
if(FAILED(m_audio_client->GetBufferSize(&buffer_size)))
goto init_error;
if(FAILED(m_audio_client->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(&render_client))))
goto init_error;
if(FAILED(m_audio_client->GetCurrentPadding(&padding)))
goto init_error;
length = buffer_size - padding;
if(FAILED(render_client->GetBuffer(length, &buffer)))
goto init_error;
mix((data_t*)buffer, length);
if(FAILED(render_client->ReleaseBuffer(length, 0)))
{
init_error:
SafeRelease(&render_client);
doStop();
return;
}
}
UINT32 padding;
if(FAILED(m_audio_client->GetCurrentPadding(&padding)))
{
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
UINT32 length = buffer_size - padding;
if(FAILED(render_client->GetBuffer(length, &buffer)))
{
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
mix((data_t*)buffer, length);
if(FAILED(render_client->ReleaseBuffer(length, 0)))
{
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
unlock();
m_audio_client->Start();
auto sleepDuration = std::chrono::milliseconds(buffer_size * 1000 / int(m_specs.rate) / 2);
for(;;)
{
lock();
if(FAILED(m_audio_client->GetCurrentPadding(&padding)))
{
m_audio_client->Stop();
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
std::lock_guard<ILockable> lock(*this);
if(FAILED(m_audio_client->GetCurrentPadding(&padding)))
goto stop_thread;
length = buffer_size - padding;
if(FAILED(render_client->GetBuffer(length, &buffer)))
goto stop_thread;
mix((data_t*)buffer, length);
if(FAILED(render_client->ReleaseBuffer(length, 0)))
goto stop_thread;
// stop thread
if(shouldStop())
{
stop_thread:
m_audio_client->Stop();
SafeRelease(&render_client);
doStop();
return;
}
}
length = buffer_size - padding;
if(FAILED(render_client->GetBuffer(length, &buffer)))
{
m_audio_client->Stop();
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
mix((data_t*)buffer, length);
if(FAILED(render_client->ReleaseBuffer(length, 0)))
{
m_audio_client->Stop();
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
// stop thread
if(m_stop)
{
m_audio_client->Stop();
SafeRelease(&render_client);
m_playing = false;
m_stop = false;
unlock();
return;
}
unlock();
std::this_thread::sleep_for(sleepDuration);
}
}
void WASAPIDevice::playing(bool playing)
{
if((!m_playing || m_stop) && playing)
start();
else
m_stop = true;
}
WASAPIDevice::WASAPIDevice(DeviceSpecs specs, int buffersize) :
m_playing(false),
m_stop(false),
m_imm_device_enumerator(nullptr),
m_imm_device(nullptr),
m_audio_client(nullptr),
@ -361,14 +285,7 @@ WASAPIDevice::WASAPIDevice(DeviceSpecs specs, int buffersize) :
WASAPIDevice::~WASAPIDevice()
{
lock();
stopAll();
unlock();
if(m_thread.joinable())
m_thread.join();
stopMixingThread();
SafeRelease(&m_audio_client);
SafeRelease(&m_imm_device);

View File

@ -26,7 +26,7 @@
* The WASAPIDevice class.
*/
#include "devices/SoftwareDevice.h"
#include "devices/ThreadedDevice.h"
#include <thread>
@ -40,46 +40,23 @@ AUD_NAMESPACE_BEGIN
/**
* This device plays back through WASAPI, the Windows audio API.
*/
class AUD_PLUGIN_API WASAPIDevice : public SoftwareDevice
class AUD_PLUGIN_API WASAPIDevice : public ThreadedDevice
{
private:
/**
* Whether there is currently playback.
*/
bool m_playing;
/**
* Whether the current playback should stop.
*/
bool m_stop;
IMMDeviceEnumerator* m_imm_device_enumerator;
IMMDevice* m_imm_device;
IAudioClient* m_audio_client;
WAVEFORMATEXTENSIBLE m_wave_format_extensible;
/**
* The streaming thread.
*/
std::thread m_thread;
/**
* Starts the streaming thread.
*/
AUD_LOCAL void start();
/**
* Streaming thread main function.
*/
AUD_LOCAL void updateStream();
AUD_LOCAL void runMixingThread();
// delete copy constructor and operator=
WASAPIDevice(const WASAPIDevice&) = delete;
WASAPIDevice& operator=(const WASAPIDevice&) = delete;
protected:
virtual void playing(bool playing);
public:
/**
* Opens the WASAPI audio device for playback.

View File

@ -737,7 +737,7 @@ void SoftwareDevice::mix(data_t* buffer, int length)
{
m_buffer.assureSize(length * AUD_SAMPLE_SIZE(m_specs));
std::lock_guard<std::recursive_mutex> lock(m_mutex);
std::lock_guard<ILockable> lock(*this);
{
std::shared_ptr<SoftwareDevice::SoftwareHandle> sound;
@ -880,7 +880,7 @@ std::shared_ptr<IHandle> SoftwareDevice::play(std::shared_ptr<IReader> reader, b
// play sound
std::shared_ptr<SoftwareDevice::SoftwareHandle> sound = std::shared_ptr<SoftwareDevice::SoftwareHandle>(new SoftwareDevice::SoftwareHandle(this, reader, pitch, resampler, mapper, keep));
std::lock_guard<std::recursive_mutex> lock(m_mutex);
std::lock_guard<ILockable> lock(*this);
m_playingSounds.push_back(sound);
@ -897,7 +897,7 @@ std::shared_ptr<IHandle> SoftwareDevice::play(std::shared_ptr<ISound> sound, boo
void SoftwareDevice::stopAll()
{
std::lock_guard<std::recursive_mutex> lock(m_mutex);
std::lock_guard<ILockable> lock(*this);
while(!m_playingSounds.empty())
m_playingSounds.front()->stop();

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright 2009-2016 Jörg Müller
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
#include "devices/ThreadedDevice.h"
#include <mutex>
AUD_NAMESPACE_BEGIN
void ThreadedDevice::start()
{
std::lock_guard<ILockable> lock(*this);
// thread is still running, we can abort stopping it
if(m_stop)
m_stop = false;
// thread is not running, let's start it
else if(!m_playing)
{
if(m_thread.joinable())
m_thread.join();
m_playing = true;
m_thread = std::thread(&ThreadedDevice::runMixingThread, this);
}
}
void ThreadedDevice::playing(bool playing)
{
if((!m_playing || m_stop) && playing)
start();
else
m_stop = true;
}
ThreadedDevice::ThreadedDevice() :
m_playing(false),
m_stop(false)
{
}
void aud::ThreadedDevice::stopMixingThread()
{
stopAll();
if(m_thread.joinable())
m_thread.join();
}
AUD_NAMESPACE_END