Skip to content

Commit

Permalink
Restart audio after ALSA stall.
Browse files Browse the repository at this point in the history
  • Loading branch information
rerdavies committed Nov 30, 2024
1 parent 5544057 commit 4a4bd12
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/AlsaDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1657,7 +1657,7 @@ namespace pipedal
Lv2Log::error(e.what());
Lv2Log::error("ALSA audio thread terminated abnormally.");
}
this->driverHost->OnAudioStopped();
this->driverHost->OnAlsaDriverStopped();

// if we terminated abnormally, pump messages until we have been terminated.
if (!terminateAudio())
Expand Down
2 changes: 1 addition & 1 deletion src/AlsaDriverTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class AlsaTester: private AudioDriverHost {

}

virtual void OnAudioStopped() {
virtual void OnAlsaDriverStopped() {
}
virtual void OnProcess(size_t nFrames) {
if (testType == TestType::NullTest) return;
Expand Down
2 changes: 1 addition & 1 deletion src/AudioDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace pipedal {
public:
virtual void OnProcess(size_t nFrames) = 0;
virtual void OnUnderrun() = 0;
virtual void OnAudioStopped() = 0;
virtual void OnAlsaDriverStopped() = 0;
virtual void OnAudioTerminated() = 0;


Expand Down
44 changes: 26 additions & 18 deletions src/AudioHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ namespace pipedal
reader.read(&vProperty);
if (!vProperty.is_null())
{
try {
try
{
vProperty = pluginHost.MapPath(vProperty);

// now to atom format (what we want on the rt thread0)
Expand All @@ -194,7 +195,8 @@ namespace pipedal
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
} catch (const std::exception &e)
}
catch (const std::exception &e)
{
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
}
Expand All @@ -207,7 +209,7 @@ namespace pipedal
void ApplyValues(IEffect *effect)
{

effect->SetBypass(this->enabled);
effect->SetBypass(this->enabled);
if (effect != pEffect)
{
throw std::runtime_error("Wrong effect");
Expand Down Expand Up @@ -907,7 +909,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW

bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
{
//eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);
// eventBufferWriter.writeMidiEvent(iterator, 0, event.size, event.buffer);

this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
if (listenForMidiEvent)
Expand Down Expand Up @@ -1100,10 +1102,12 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
{
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
}
virtual void OnAudioStopped() override
virtual void OnAlsaDriverStopped() override
{

this->audioStopped = true;
Lv2Log::info("Audio stopped.");
this->realtimeWriter.AudioTerminatedAbnormally();
}
virtual void OnAudioTerminated() override
{
Expand Down Expand Up @@ -1256,12 +1260,14 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
return this->sampleRate;
}

void OnAudioComplete()
void HandleAudioTerminatedAbnormally()
{
// there is actually no compelling circumstance in which this should ever happen.

Lv2Log::error("Audio processing terminated unexpectedly.");
realtimeWriter.AudioStopped();

if (pNotifyCallbacks)
{
pNotifyCallbacks->OnAlsaDriverTerminatedAbnormally();
}
}
std::vector<uint8_t> atomBuffer;
std::vector<uint8_t> realtimeAtomBuffer;
Expand All @@ -1271,7 +1277,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
{
SetThreadName("rtsvc");
SetThreadPriority(SchedulerPriority::AudioService);

int underrunMessagesGiven = 0;
try
{
Expand Down Expand Up @@ -1456,7 +1462,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
{
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
//this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
// this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
}
}
}
Expand Down Expand Up @@ -1492,11 +1498,11 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
hostReader.read(&buffer);
OnPathPropertyReceived(buffer);
}
else if (command == RingBufferCommand::AudioStopped)
else if (command == RingBufferCommand::AudioTerminatedAbnormally)
{
AudioStoppedBody body;
hostReader.read(&body);
OnAudioComplete();
HandleAudioTerminatedAbnormally();
return;
}
else if (command == RingBufferCommand::
Expand Down Expand Up @@ -1621,7 +1627,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
if (jackServerSettings.IsDummyAudioDevice())
{
this->isDummyAudioDriver = true;
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice()));
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
}
else
{
Expand Down Expand Up @@ -1927,7 +1933,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
}

private:
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
// virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;

class RestartThread
Expand Down Expand Up @@ -2051,7 +2057,7 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW

result.msSinceLastUnderrun_ = (uint64_t)dt;

result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC()*1000));
result.temperaturemC_ = (int32_t)(std::round(cpuTemperatureMonitor->GetTemperatureC() * 1000));

result.active_ = IsAudioRunning();
result.restarting_ = this->restarting;
Expand All @@ -2065,7 +2071,9 @@ class AudioHostImpl : public AudioHost, private AudioDriverHost, private IPatchW
if (result.hasCpuGovernor_)
{
result.governor_ = GetGovernor();
} else {
}
else
{
result.governor_ = "";
}

Expand Down Expand Up @@ -2110,7 +2118,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
else if (i->symbol() == "prevBank")
{
this->prevBankMidiBinding.SetBinding(*i);
}
}
else if (i->symbol() == "nextProgram")
{
this->nextPresetMidiBinding.SetBinding(*i);
Expand Down
2 changes: 2 additions & 0 deletions src/AudioHost.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ namespace pipedal
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;

virtual void OnAlsaDriverTerminatedAbnormally() = 0;
};

class JackHostStatus
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ target_link_libraries(pipedal_kconfig
PRIVATE ftxui::dom
PRIVATE ftxui::component # Not needed for this example.
PRIVATE PiPedalCommon
systemd
)

#################################
Expand Down
2 changes: 1 addition & 1 deletion src/PiLatencyMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ class AlsaTester : private AudioDriverHost
Oscillator oscillator;
LatencyMonitor latencyMonitor;

virtual void OnAudioStopped()
virtual void OnAlsaDriverStopped()
{
}
virtual void OnAudioTerminated()
Expand Down
70 changes: 69 additions & 1 deletion src/PiPedalModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ void PiPedalModel::Close()
}
closed = true;

CancelAudioRetry();

if (avahiService)
{
this->avahiService = nullptr; // and close.
Expand Down Expand Up @@ -1301,6 +1303,10 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
{
try
{
if (useDummyAudioDriver)
{
CancelAudioRetry();
}
if (this->audioHost->IsOpen())
{

Expand Down Expand Up @@ -1380,6 +1386,8 @@ void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSe
this->storage.SetJackChannelSelection(channelSelection);

this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);

CancelAudioRetry();
}

RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
Expand Down Expand Up @@ -1791,6 +1799,8 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet

FireJackConfigurationChanged(this->jackConfiguration);

CancelAudioRetry();

guard.unlock();
RestartAudio();

Expand Down Expand Up @@ -2735,4 +2745,62 @@ std::map<std::string,std::string> PiPedalModel::GetWifiRegulatoryDomains()
Lv2Log::warning(SS("Unable to query Wifi Regulatory domains. " << e.what()));
}
return result;
}
}

void PiPedalModel::CancelAudioRetry() {
std::lock_guard<std::recursive_mutex> lock(mutex);
if (audioRetryPostHandle)
{
// don't think this can ever happen, but if it did, it would be bad.
this->CancelPost(audioRetryPostHandle);
audioRetryPostHandle = 0;
}

}
void PiPedalModel::OnAlsaDriverTerminatedAbnormally() {
// notification from the realtime thread, via the audiohost that the
// ALSA stream has broken. We want to restart.

// get off the service thread as promptly as possible
this->Post([&]() {
std::lock_guard<std::recursive_mutex> lock(mutex);
if (closed) return;

auto now = clock::now();
clock::duration timeSinceLastRetry = now-this->lastRestartTime;
this->lastRestartTime = now;
if (timeSinceLastRetry > std::chrono::duration_cast<clock::duration>(std::chrono::milliseconds(1000))) {
audioRestartRetries = 0;
}
CancelAudioRetry();

if (audioRestartRetries == 0)
{
this->audioRetryPostHandle = this->Post(
// No lock to avoid deadlocks!
[this]() {
Lv2Log::info("Restarting audio.");
this->RestartAudio();
});
} else if (audioRestartRetries < 3)
{
this->audioRetryPostHandle = this->PostDelayed(
std::chrono::milliseconds(100 * audioRestartRetries),
[this]() {
if (closed) {
return;
}
Lv2Log::info(SS("Restarting audio. (retry " << audioRestartRetries << ")"));

RestartAudio();
});
} else {
this->audioRetryPostHandle = this->Post(
// No lock to avoid deadlocks!
[this]() {
Lv2Log::info(SS("Switching to dummy driver."));
RestartAudio(true); // switch to the dummy driver.
});
}
});
}
7 changes: 7 additions & 0 deletions src/PiPedalModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ namespace pipedal
using NetworkChangedListener = std::function<void(void)>;

private:

void CancelAudioRetry();
clock::time_point lastRestartTime = clock::time_point::min();
int audioRestartRetries = 0;
PostHandle audioRetryPostHandle = 0;

bool hasWifi = false;

void SetHasWifi(bool hasWifi);
Expand Down Expand Up @@ -230,6 +236,7 @@ namespace pipedal
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
virtual void OnAlsaDriverTerminatedAbnormally() override;

void OnNotifyPathPatchPropertyReceived(
int64_t instanceId,
Expand Down
7 changes: 4 additions & 3 deletions src/RingBufferReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ namespace pipedal
EffectReplaced,
SetValue,
SetBypass,
AudioStopped,
//AudioStopped,
AudioTerminatedAbnormally, // specifically for an ALSA loss of connection.
SetVuSubscriptions,
FreeVuSubscriptions,

Expand Down Expand Up @@ -507,10 +508,10 @@ namespace pipedal
write(RingBufferCommand::ReplaceEffect, pedalboard);
}

void AudioStopped()
void AudioTerminatedAbnormally()
{
AudioStoppedBody body;
write(RingBufferCommand::AudioStopped, body);
write(RingBufferCommand::AudioTerminatedAbnormally, body);
}

void EffectReplaced(Lv2Pedalboard *pedalboard)
Expand Down
2 changes: 1 addition & 1 deletion src/kconfigMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ std::string spinnerText(const Clock::time_point &startTime)

int64_t position = (ms * MAX_LENGTH / 3000) % (MAX_LENGTH + N_DOTS);

char result[MAX_LENGTH - 1];
char result[MAX_LENGTH + 1];
for (int64_t i = 0; i < MAX_LENGTH; ++i)
{
if (i >= position && i < position + N_DOTS)
Expand Down

0 comments on commit 4a4bd12

Please sign in to comment.