diff --git a/PiPedalCommon/src/AlsaSequencer.cpp b/PiPedalCommon/src/AlsaSequencer.cpp index 7506e02..4e21ee4 100644 --- a/PiPedalCommon/src/AlsaSequencer.cpp +++ b/PiPedalCommon/src/AlsaSequencer.cpp @@ -259,6 +259,8 @@ namespace pipedal // Open sequencer in input mode int rc; + Lv2Log::debug("Opening ALSA Sequencer"); + rc = snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_DUPLEX, 0); if (rc < 0) { diff --git a/PiPedalCommon/src/include/restrict.hpp b/PiPedalCommon/src/include/restrict.hpp new file mode 100644 index 0000000..3cf0756 --- /dev/null +++ b/PiPedalCommon/src/include/restrict.hpp @@ -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 \ No newline at end of file diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 195cec6..1f833be 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -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. diff --git a/src/SchedulerPriority.cpp b/src/SchedulerPriority.cpp index 0eb5194..8effce9 100644 --- a/src/SchedulerPriority.cpp +++ b/src/SchedulerPriority.cpp @@ -104,7 +104,7 @@ static void SetPriority(int realtimePriority, const char *priorityName) int result = sched_setscheduler(0, SCHED_RR, ¶m); if (result == 0) { - Lv2Log::debug("Service thread priority successfully boosted."); + Lv2Log::debug(SS("Service thread priority successfully boosted. (" << priorityName << ")")); return; } else diff --git a/src/Worker.cpp b/src/Worker.cpp index e102671..a1baea1 100644 --- a/src/Worker.cpp +++ b/src/Worker.cpp @@ -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(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. }