Rework audio thread restart.

This commit is contained in:
Robin E. R. Davies
2025-07-23 15:06:06 -04:00
parent d5ec594957
commit 090ffc9a9e
5 changed files with 176 additions and 76 deletions
+136 -73
View File
@@ -245,6 +245,8 @@ namespace pipedal
class AlsaDriverImpl : public AudioDriver
{
private:
std::recursive_mutex restartMutex;
pipedal::CpuUse cpuUse;
#ifdef ALSADRIVER_CONFIG_DBG
@@ -378,16 +380,21 @@ namespace pipedal
}
private:
void AlsaCleanup()
void AlsaCloseAudio()
{
std::lock_guard lock{restartMutex};
if (captureHandle)
{
Lv2Log::debug("ALSA capture handle closed.");
snd_pcm_drain(captureHandle);
snd_pcm_close(captureHandle);
captureHandle = nullptr;
}
if (playbackHandle)
{
Lv2Log::debug("ALSA playback handle closed.");
snd_pcm_drain(playbackHandle);
snd_pcm_close(playbackHandle);
playbackHandle = nullptr;
}
@@ -411,7 +418,11 @@ namespace pipedal
snd_pcm_sw_params_free(playbackSwParams);
playbackSwParams = nullptr;
}
this->alsaSequencer = nullptr;
}
void AlsaCleanup()
{
AlsaCloseAudio();
}
std::string discover_alsa_using_apps()
@@ -1085,6 +1096,34 @@ namespace pipedal
}
}
void RestartAlsa()
{
std::lock_guard lock{restartMutex};
Lv2Log::info("Restarting ALSA devices.");
try {
AlsaCloseAudio();
} catch (const std::exception &e) {
Lv2Log::error(SS("Error cleaning up ALSA: " << e.what()));
throw std::runtime_error("Unable to restart the audio stream.");
}
try {
OpenAudio(this->jackServerSettings, this->channelSelection);
validate_capture_handle();
FillOutputBuffer();
audioRunning = true;
} catch (const std::exception &e) {
Lv2Log::error(SS("Error opening ALSA: " << e.what()));
throw std::runtime_error("Unable to restart the audio stream.");
}
int err;
if ((err = snd_pcm_start(captureHandle)) < 0)
{
throw PiPedalStateException("Unable to restart ALSA capture.");
}
}
void PrepareCaptureFunctions(snd_pcm_format_t captureFormat)
{
this->captureFormat = captureFormat;
@@ -1221,6 +1260,8 @@ namespace pipedal
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
{
std::lock_guard lock{ restartMutex };
int err;
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
@@ -1389,18 +1430,25 @@ namespace pipedal
void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
{
validate_capture_handle();
if (err == -EPIPE)
{
err = snd_pcm_prepare(playback_handle);
if (err < 0)
try {
if (err == -EPIPE)
{
throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
err = snd_pcm_prepare(playback_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
}
FillOutputBuffer();
}
FillOutputBuffer();
}
else
{
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
else
{
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
}
} catch (const std::exception &e) {
Lv2Log::info(SS("Soft audio restart failed. " << e.what()));
RestartAlsa();
audioRunning = true;
}
validate_capture_handle();
}
@@ -1408,72 +1456,79 @@ namespace pipedal
{
validate_capture_handle();
if (err == -EPIPE)
{
try {
if (err == -EPIPE)
{
// Unlink the streams before recovery
snd_pcm_unlink(capture_handle);
// Unlink the streams before recovery
snd_pcm_unlink(capture_handle);
err = snd_pcm_drop(capture_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
err = snd_pcm_drop(playback_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
// Prepare both streams
if ((err = snd_pcm_prepare(playback_handle)) < 0)
{
throw std::runtime_error(SS("Cannot prepare playback stream: " << snd_strerror(err)));
}
if ((err = snd_pcm_prepare(capture_handle)) < 0)
{
throw std::runtime_error(SS("Cannot prepare capture stream: " << snd_strerror(err)));
}
// Fill the playback buffer with silence
FillOutputBuffer();
// Resynchronize the streams
if ((err = snd_pcm_link(capture_handle, playback_handle)) < 0)
{
throw std::runtime_error(SS("Cannot relink streams: " << snd_strerror(err)));
}
// Start the streams
if ((err = snd_pcm_start(capture_handle)) < 0)
{
throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
}
validate_capture_handle();
}
else if (err == ESTRPIPE)
{
audioRunning = false;
validate_capture_handle();
while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
{
sleep(1);
}
if (err < 0)
{
err = snd_pcm_prepare(capture_handle);
err = snd_pcm_drop(capture_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA suspend. (" << snd_strerror(err) << ")"));
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
err = snd_pcm_drop(playback_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
// Prepare both streams
if ((err = snd_pcm_prepare(playback_handle)) < 0)
{
throw std::runtime_error(SS("Cannot prepare playback stream: " << snd_strerror(err)));
}
if ((err = snd_pcm_prepare(capture_handle)) < 0)
{
throw std::runtime_error(SS("Cannot prepare capture stream: " << snd_strerror(err)));
}
// Fill the playback buffer with silence
FillOutputBuffer();
// Resynchronize the streams
if ((err = snd_pcm_link(capture_handle, playback_handle)) < 0)
{
throw std::runtime_error(SS("Cannot relink streams: " << snd_strerror(err)));
}
// Start the streams
if ((err = snd_pcm_start(capture_handle)) < 0)
{
throw std::runtime_error(SS("Cannot restart capture stream: " << snd_strerror(err)));
}
validate_capture_handle();
}
else if (err == ESTRPIPE)
{
audioRunning = false;
validate_capture_handle();
while ((err = snd_pcm_resume(capture_handle)) == -EAGAIN)
{
sleep(1);
}
if (err < 0)
{
err = snd_pcm_prepare(capture_handle);
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA suspend. (" << snd_strerror(err) << ")"));
}
}
audioRunning = true;
validate_capture_handle();
}
else
{
throw PiPedalStateException(SS("Can't recover from ALSA input error. (" << snd_strerror(err) << ")"));
}
} catch (const std::exception &e) {
Lv2Log::info(SS("Soft audio restart failed. " << e.what()));
RestartAlsa();
audioRunning = true;
validate_capture_handle();
}
else
{
throw std::runtime_error(SS("Can't restart audio: " << snd_strerror(err)));
}
}
@@ -1526,6 +1581,11 @@ namespace pipedal
AlsaMidiMessage message;
midiEventCount = 0;
auto alsaSequener = this->alsaSequencer; // take an addref
if (!alsaSequener)
{
return;
}
while (alsaSequencer->ReadMessage(message, 0))
{
size_t messageSize = message.size;
@@ -1675,11 +1735,11 @@ namespace pipedal
Lv2Log::error(e.what());
Lv2Log::error("ALSA audio thread terminated abnormally.");
}
this->driverHost->OnAlsaDriverStopped();
// if we terminated abnormally, pump messages until we have been terminated.
if (!terminateAudio())
{
this->driverHost->OnAlsaDriverStopped();
// zero out input buffers.
for (size_t i = 0; i < this->captureBuffers.size(); ++i)
{
@@ -1848,6 +1908,8 @@ namespace pipedal
Deactivate();
AlsaCleanup();
DeleteBuffers();
this->alsaSequencer = nullptr;
std::atomic_thread_fence(std::memory_order::release);
}
@@ -1940,7 +2002,7 @@ namespace pipedal
try
{
int err;
for (int retry = 0; retry < 2; ++retry)
for (int retry = 0; retry < 4; ++retry)
{
err = snd_pcm_open(&playbackHandle, alsaDeviceName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) // field report of a device that is present, but won't immediately open.
@@ -2202,6 +2264,7 @@ namespace pipedal
#endif
}
void FreeAlsaGlobals()
{
snd_config_update_free_global(); // to get a clean Valgrind report.
+1 -1
View File
@@ -104,7 +104,7 @@ static void SetPriority(int realtimePriority, const char *priorityName)
int result = sched_setscheduler(0, SCHED_RR, &param);
if (result == 0)
{
Lv2Log::debug("Service thread priority successfully boosted.");
Lv2Log::debug(SS("Service thread priority successfully boosted. (" << priorityName << ")"));
return;
}
else
+4 -2
View File
@@ -158,9 +158,11 @@ void Worker::WaitForAllResponses()
break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
// pump the plugin with a zero-length buffer.
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::chrono::milliseconds waitDuration = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now()-startTime);
if (waitDuration.count() > 250) {
if (waitDuration.count() > 1500) {
Lv2Log::error("Timed out waiting for a Worker task to complete.");
// better to leak than to terminate the application.
}