Rework audio thread restart.
This commit is contained in:
@@ -259,6 +259,8 @@ namespace pipedal
|
|||||||
// Open sequencer in input mode
|
// Open sequencer in input mode
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
Lv2Log::debug("Opening ALSA Sequencer");
|
||||||
|
|
||||||
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 Robin E. R. Davies
|
||||||
|
* All rights reserved.
|
||||||
|
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
// define restrict keyword for C++ compilers (which don't yet support it)
|
||||||
|
#ifndef restrict
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define restrict __restrict__
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
#define restrict __restrict
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
+136
-73
@@ -245,6 +245,8 @@ namespace pipedal
|
|||||||
class AlsaDriverImpl : public AudioDriver
|
class AlsaDriverImpl : public AudioDriver
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
std::recursive_mutex restartMutex;
|
||||||
|
|
||||||
pipedal::CpuUse cpuUse;
|
pipedal::CpuUse cpuUse;
|
||||||
|
|
||||||
#ifdef ALSADRIVER_CONFIG_DBG
|
#ifdef ALSADRIVER_CONFIG_DBG
|
||||||
@@ -378,16 +380,21 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void AlsaCleanup()
|
void AlsaCloseAudio()
|
||||||
{
|
{
|
||||||
|
std::lock_guard lock{restartMutex};
|
||||||
|
|
||||||
if (captureHandle)
|
if (captureHandle)
|
||||||
{
|
{
|
||||||
|
Lv2Log::debug("ALSA capture handle closed.");
|
||||||
|
snd_pcm_drain(captureHandle);
|
||||||
snd_pcm_close(captureHandle);
|
snd_pcm_close(captureHandle);
|
||||||
captureHandle = nullptr;
|
captureHandle = nullptr;
|
||||||
}
|
}
|
||||||
if (playbackHandle)
|
if (playbackHandle)
|
||||||
{
|
{
|
||||||
|
Lv2Log::debug("ALSA playback handle closed.");
|
||||||
|
snd_pcm_drain(playbackHandle);
|
||||||
snd_pcm_close(playbackHandle);
|
snd_pcm_close(playbackHandle);
|
||||||
playbackHandle = nullptr;
|
playbackHandle = nullptr;
|
||||||
}
|
}
|
||||||
@@ -411,7 +418,11 @@ namespace pipedal
|
|||||||
snd_pcm_sw_params_free(playbackSwParams);
|
snd_pcm_sw_params_free(playbackSwParams);
|
||||||
playbackSwParams = nullptr;
|
playbackSwParams = nullptr;
|
||||||
}
|
}
|
||||||
this->alsaSequencer = nullptr;
|
|
||||||
|
}
|
||||||
|
void AlsaCleanup()
|
||||||
|
{
|
||||||
|
AlsaCloseAudio();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string discover_alsa_using_apps()
|
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)
|
void PrepareCaptureFunctions(snd_pcm_format_t captureFormat)
|
||||||
{
|
{
|
||||||
this->captureFormat = captureFormat;
|
this->captureFormat = captureFormat;
|
||||||
@@ -1221,6 +1260,8 @@ namespace pipedal
|
|||||||
|
|
||||||
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
|
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
|
||||||
{
|
{
|
||||||
|
std::lock_guard lock{ restartMutex };
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
|
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)
|
void recover_from_output_underrun(snd_pcm_t *capture_handle, snd_pcm_t *playback_handle, int err)
|
||||||
{
|
{
|
||||||
validate_capture_handle();
|
validate_capture_handle();
|
||||||
if (err == -EPIPE)
|
try {
|
||||||
{
|
|
||||||
err = snd_pcm_prepare(playback_handle);
|
if (err == -EPIPE)
|
||||||
if (err < 0)
|
|
||||||
{
|
{
|
||||||
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
|
||||||
}
|
{
|
||||||
else
|
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
|
||||||
{
|
}
|
||||||
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();
|
validate_capture_handle();
|
||||||
}
|
}
|
||||||
@@ -1408,72 +1456,79 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
validate_capture_handle();
|
validate_capture_handle();
|
||||||
|
|
||||||
if (err == -EPIPE)
|
try {
|
||||||
{
|
if (err == -EPIPE)
|
||||||
|
{
|
||||||
|
|
||||||
// Unlink the streams before recovery
|
// Unlink the streams before recovery
|
||||||
snd_pcm_unlink(capture_handle);
|
snd_pcm_unlink(capture_handle);
|
||||||
|
|
||||||
err = snd_pcm_drop(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);
|
|
||||||
if (err < 0)
|
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;
|
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;
|
AlsaMidiMessage message;
|
||||||
|
|
||||||
midiEventCount = 0;
|
midiEventCount = 0;
|
||||||
|
auto alsaSequener = this->alsaSequencer; // take an addref
|
||||||
|
if (!alsaSequener)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
while (alsaSequencer->ReadMessage(message, 0))
|
while (alsaSequencer->ReadMessage(message, 0))
|
||||||
{
|
{
|
||||||
size_t messageSize = message.size;
|
size_t messageSize = message.size;
|
||||||
@@ -1675,11 +1735,11 @@ namespace pipedal
|
|||||||
Lv2Log::error(e.what());
|
Lv2Log::error(e.what());
|
||||||
Lv2Log::error("ALSA audio thread terminated abnormally.");
|
Lv2Log::error("ALSA audio thread terminated abnormally.");
|
||||||
}
|
}
|
||||||
this->driverHost->OnAlsaDriverStopped();
|
|
||||||
|
|
||||||
// if we terminated abnormally, pump messages until we have been terminated.
|
// if we terminated abnormally, pump messages until we have been terminated.
|
||||||
if (!terminateAudio())
|
if (!terminateAudio())
|
||||||
{
|
{
|
||||||
|
this->driverHost->OnAlsaDriverStopped();
|
||||||
// zero out input buffers.
|
// zero out input buffers.
|
||||||
for (size_t i = 0; i < this->captureBuffers.size(); ++i)
|
for (size_t i = 0; i < this->captureBuffers.size(); ++i)
|
||||||
{
|
{
|
||||||
@@ -1848,6 +1908,8 @@ namespace pipedal
|
|||||||
Deactivate();
|
Deactivate();
|
||||||
AlsaCleanup();
|
AlsaCleanup();
|
||||||
DeleteBuffers();
|
DeleteBuffers();
|
||||||
|
this->alsaSequencer = nullptr;
|
||||||
|
|
||||||
std::atomic_thread_fence(std::memory_order::release);
|
std::atomic_thread_fence(std::memory_order::release);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1940,7 +2002,7 @@ namespace pipedal
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int err;
|
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);
|
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.
|
if (err < 0) // field report of a device that is present, but won't immediately open.
|
||||||
@@ -2202,6 +2264,7 @@ namespace pipedal
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FreeAlsaGlobals()
|
void FreeAlsaGlobals()
|
||||||
{
|
{
|
||||||
snd_config_update_free_global(); // to get a clean Valgrind report.
|
snd_config_update_free_global(); // to get a clean Valgrind report.
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ static void SetPriority(int realtimePriority, const char *priorityName)
|
|||||||
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
int result = sched_setscheduler(0, SCHED_RR, ¶m);
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
Lv2Log::debug("Service thread priority successfully boosted.");
|
Lv2Log::debug(SS("Service thread priority successfully boosted. (" << priorityName << ")"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
+4
-2
@@ -158,9 +158,11 @@ void Worker::WaitForAllResponses()
|
|||||||
break;
|
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);
|
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.");
|
Lv2Log::error("Timed out waiting for a Worker task to complete.");
|
||||||
// better to leak than to terminate the application.
|
// better to leak than to terminate the application.
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user