Restart audio after ALSA stall.
This commit is contained in:
+1
-1
@@ -1657,7 +1657,7 @@ 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->OnAudioStopped();
|
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())
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ public:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnAudioStopped() {
|
virtual void OnAlsaDriverStopped() {
|
||||||
}
|
}
|
||||||
virtual void OnProcess(size_t nFrames) {
|
virtual void OnProcess(size_t nFrames) {
|
||||||
if (testType == TestType::NullTest) return;
|
if (testType == TestType::NullTest) return;
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ namespace pipedal {
|
|||||||
public:
|
public:
|
||||||
virtual void OnProcess(size_t nFrames) = 0;
|
virtual void OnProcess(size_t nFrames) = 0;
|
||||||
virtual void OnUnderrun() = 0;
|
virtual void OnUnderrun() = 0;
|
||||||
virtual void OnAudioStopped() = 0;
|
virtual void OnAlsaDriverStopped() = 0;
|
||||||
virtual void OnAudioTerminated() = 0;
|
virtual void OnAudioTerminated() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+26
-18
@@ -183,7 +183,8 @@ namespace pipedal
|
|||||||
reader.read(&vProperty);
|
reader.read(&vProperty);
|
||||||
if (!vProperty.is_null())
|
if (!vProperty.is_null())
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
vProperty = pluginHost.MapPath(vProperty);
|
vProperty = pluginHost.MapPath(vProperty);
|
||||||
|
|
||||||
// now to atom format (what we want on the rt thread0)
|
// now to atom format (what we want on the rt thread0)
|
||||||
@@ -194,7 +195,8 @@ namespace pipedal
|
|||||||
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
|
pathPatchProperty.atomBuffer.resize(atomValue->size + sizeof(LV2_Atom));
|
||||||
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
|
memcpy(pathPatchProperty.atomBuffer.data(), atomValue, pathPatchProperty.atomBuffer.size());
|
||||||
this->pathPatchProperties.push_back(std::move(pathPatchProperty));
|
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()));
|
Lv2Log::info(SS("IndexedSnapshotValue: Failed to map path property " << pathProperty.first << ". " << e.what()));
|
||||||
}
|
}
|
||||||
@@ -207,7 +209,7 @@ namespace pipedal
|
|||||||
void ApplyValues(IEffect *effect)
|
void ApplyValues(IEffect *effect)
|
||||||
{
|
{
|
||||||
|
|
||||||
effect->SetBypass(this->enabled);
|
effect->SetBypass(this->enabled);
|
||||||
if (effect != pEffect)
|
if (effect != pEffect)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Wrong effect");
|
throw std::runtime_error("Wrong effect");
|
||||||
@@ -907,7 +909,7 @@ private:
|
|||||||
|
|
||||||
bool onMidiEvent(Lv2EventBufferWriter &eventBufferWriter, Lv2EventBufferWriter::LV2_EvBuf_Iterator &iterator, MidiEvent &event)
|
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);
|
this->realtimeActivePedalboard->OnMidiMessage(event.size, event.buffer, this, fnMidiValueChanged);
|
||||||
if (listenForMidiEvent)
|
if (listenForMidiEvent)
|
||||||
@@ -1100,10 +1102,12 @@ private:
|
|||||||
{
|
{
|
||||||
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
return this->active && (!this->audioStopped) && !this->isDummyAudioDriver;
|
||||||
}
|
}
|
||||||
virtual void OnAudioStopped() override
|
virtual void OnAlsaDriverStopped() override
|
||||||
{
|
{
|
||||||
|
|
||||||
this->audioStopped = true;
|
this->audioStopped = true;
|
||||||
Lv2Log::info("Audio stopped.");
|
Lv2Log::info("Audio stopped.");
|
||||||
|
this->realtimeWriter.AudioTerminatedAbnormally();
|
||||||
}
|
}
|
||||||
virtual void OnAudioTerminated() override
|
virtual void OnAudioTerminated() override
|
||||||
{
|
{
|
||||||
@@ -1256,12 +1260,14 @@ public:
|
|||||||
return this->sampleRate;
|
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.");
|
Lv2Log::error("Audio processing terminated unexpectedly.");
|
||||||
realtimeWriter.AudioStopped();
|
|
||||||
|
if (pNotifyCallbacks)
|
||||||
|
{
|
||||||
|
pNotifyCallbacks->OnAlsaDriverTerminatedAbnormally();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> atomBuffer;
|
std::vector<uint8_t> atomBuffer;
|
||||||
std::vector<uint8_t> realtimeAtomBuffer;
|
std::vector<uint8_t> realtimeAtomBuffer;
|
||||||
@@ -1271,7 +1277,7 @@ public:
|
|||||||
{
|
{
|
||||||
SetThreadName("rtsvc");
|
SetThreadName("rtsvc");
|
||||||
SetThreadPriority(SchedulerPriority::AudioService);
|
SetThreadPriority(SchedulerPriority::AudioService);
|
||||||
|
|
||||||
int underrunMessagesGiven = 0;
|
int underrunMessagesGiven = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1456,7 +1462,7 @@ public:
|
|||||||
{
|
{
|
||||||
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
|
LV2_URID propertyUrid = ((LV2_Atom_URID *)property)->body;
|
||||||
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
|
this->pNotifyCallbacks->OnPatchSetReply(instanceId, propertyUrid, value);
|
||||||
//this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
|
// this->pNotifyCallbacks->OnNotifyMaybeLv2StateChanged(instanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1492,11 +1498,11 @@ public:
|
|||||||
hostReader.read(&buffer);
|
hostReader.read(&buffer);
|
||||||
OnPathPropertyReceived(buffer);
|
OnPathPropertyReceived(buffer);
|
||||||
}
|
}
|
||||||
else if (command == RingBufferCommand::AudioStopped)
|
else if (command == RingBufferCommand::AudioTerminatedAbnormally)
|
||||||
{
|
{
|
||||||
AudioStoppedBody body;
|
AudioStoppedBody body;
|
||||||
hostReader.read(&body);
|
hostReader.read(&body);
|
||||||
OnAudioComplete();
|
HandleAudioTerminatedAbnormally();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (command == RingBufferCommand::
|
else if (command == RingBufferCommand::
|
||||||
@@ -1621,7 +1627,7 @@ public:
|
|||||||
if (jackServerSettings.IsDummyAudioDevice())
|
if (jackServerSettings.IsDummyAudioDevice())
|
||||||
{
|
{
|
||||||
this->isDummyAudioDriver = true;
|
this->isDummyAudioDriver = true;
|
||||||
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this,jackServerSettings.GetAlsaInputDevice()));
|
this->audioDriver = std::unique_ptr<AudioDriver>(CreateDummyAudioDriver(this, jackServerSettings.GetAlsaInputDevice()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1927,7 +1933,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
|
// virtual bool UpdatePluginStates(Pedalboard &pedalboard) override;
|
||||||
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
|
virtual bool UpdatePluginState(PedalboardItem &pedalboardItem) override;
|
||||||
|
|
||||||
class RestartThread
|
class RestartThread
|
||||||
@@ -2051,7 +2057,7 @@ public:
|
|||||||
|
|
||||||
result.msSinceLastUnderrun_ = (uint64_t)dt;
|
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.active_ = IsAudioRunning();
|
||||||
result.restarting_ = this->restarting;
|
result.restarting_ = this->restarting;
|
||||||
@@ -2065,7 +2071,9 @@ public:
|
|||||||
if (result.hasCpuGovernor_)
|
if (result.hasCpuGovernor_)
|
||||||
{
|
{
|
||||||
result.governor_ = GetGovernor();
|
result.governor_ = GetGovernor();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
result.governor_ = "";
|
result.governor_ = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2110,7 +2118,7 @@ void AudioHostImpl::SetSystemMidiBindings(const std::vector<MidiBinding> &bindin
|
|||||||
else if (i->symbol() == "prevBank")
|
else if (i->symbol() == "prevBank")
|
||||||
{
|
{
|
||||||
this->prevBankMidiBinding.SetBinding(*i);
|
this->prevBankMidiBinding.SetBinding(*i);
|
||||||
}
|
}
|
||||||
else if (i->symbol() == "nextProgram")
|
else if (i->symbol() == "nextProgram")
|
||||||
{
|
{
|
||||||
this->nextPresetMidiBinding.SetBinding(*i);
|
this->nextPresetMidiBinding.SetBinding(*i);
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ namespace pipedal
|
|||||||
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
|
virtual void OnNotifyLv2RealtimeError(int64_t instanceId, const std::string &error) = 0;
|
||||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) = 0;
|
||||||
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
|
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) = 0;
|
||||||
|
|
||||||
|
virtual void OnAlsaDriverTerminatedAbnormally() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class JackHostStatus
|
class JackHostStatus
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ target_link_libraries(pipedal_kconfig
|
|||||||
PRIVATE ftxui::dom
|
PRIVATE ftxui::dom
|
||||||
PRIVATE ftxui::component # Not needed for this example.
|
PRIVATE ftxui::component # Not needed for this example.
|
||||||
PRIVATE PiPedalCommon
|
PRIVATE PiPedalCommon
|
||||||
|
systemd
|
||||||
)
|
)
|
||||||
|
|
||||||
#################################
|
#################################
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ public:
|
|||||||
Oscillator oscillator;
|
Oscillator oscillator;
|
||||||
LatencyMonitor latencyMonitor;
|
LatencyMonitor latencyMonitor;
|
||||||
|
|
||||||
virtual void OnAudioStopped()
|
virtual void OnAlsaDriverStopped()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
virtual void OnAudioTerminated()
|
virtual void OnAudioTerminated()
|
||||||
|
|||||||
+69
-1
@@ -138,6 +138,8 @@ void PiPedalModel::Close()
|
|||||||
}
|
}
|
||||||
closed = true;
|
closed = true;
|
||||||
|
|
||||||
|
CancelAudioRetry();
|
||||||
|
|
||||||
if (avahiService)
|
if (avahiService)
|
||||||
{
|
{
|
||||||
this->avahiService = nullptr; // and close.
|
this->avahiService = nullptr; // and close.
|
||||||
@@ -1301,6 +1303,10 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (useDummyAudioDriver)
|
||||||
|
{
|
||||||
|
CancelAudioRetry();
|
||||||
|
}
|
||||||
if (this->audioHost->IsOpen())
|
if (this->audioHost->IsOpen())
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -1380,6 +1386,8 @@ void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSe
|
|||||||
this->storage.SetJackChannelSelection(channelSelection);
|
this->storage.SetJackChannelSelection(channelSelection);
|
||||||
|
|
||||||
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
|
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
|
||||||
|
|
||||||
|
CancelAudioRetry();
|
||||||
}
|
}
|
||||||
|
|
||||||
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
|
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
|
||||||
@@ -1791,6 +1799,8 @@ void PiPedalModel::SetJackServerSettings(const JackServerSettings &jackServerSet
|
|||||||
|
|
||||||
FireJackConfigurationChanged(this->jackConfiguration);
|
FireJackConfigurationChanged(this->jackConfiguration);
|
||||||
|
|
||||||
|
CancelAudioRetry();
|
||||||
|
|
||||||
guard.unlock();
|
guard.unlock();
|
||||||
RestartAudio();
|
RestartAudio();
|
||||||
|
|
||||||
@@ -2735,4 +2745,62 @@ std::map<std::string,std::string> PiPedalModel::GetWifiRegulatoryDomains()
|
|||||||
Lv2Log::warning(SS("Unable to query Wifi Regulatory domains. " << e.what()));
|
Lv2Log::warning(SS("Unable to query Wifi Regulatory domains. " << e.what()));
|
||||||
}
|
}
|
||||||
return result;
|
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.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ namespace pipedal
|
|||||||
using NetworkChangedListener = std::function<void(void)>;
|
using NetworkChangedListener = std::function<void(void)>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void CancelAudioRetry();
|
||||||
|
clock::time_point lastRestartTime = clock::time_point::min();
|
||||||
|
int audioRestartRetries = 0;
|
||||||
|
PostHandle audioRetryPostHandle = 0;
|
||||||
|
|
||||||
bool hasWifi = false;
|
bool hasWifi = false;
|
||||||
|
|
||||||
void SetHasWifi(bool hasWifi);
|
void SetHasWifi(bool hasWifi);
|
||||||
@@ -230,6 +236,7 @@ namespace pipedal
|
|||||||
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
virtual void OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue) override;
|
||||||
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
virtual void OnNotifyMidiRealtimeEvent(RealtimeMidiEventType eventType) override;
|
||||||
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
|
virtual void OnNotifyMidiRealtimeSnapshotRequest(int32_t snapshotIndex,int64_t snapshotRequestId) override;
|
||||||
|
virtual void OnAlsaDriverTerminatedAbnormally() override;
|
||||||
|
|
||||||
void OnNotifyPathPatchPropertyReceived(
|
void OnNotifyPathPatchPropertyReceived(
|
||||||
int64_t instanceId,
|
int64_t instanceId,
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ namespace pipedal
|
|||||||
EffectReplaced,
|
EffectReplaced,
|
||||||
SetValue,
|
SetValue,
|
||||||
SetBypass,
|
SetBypass,
|
||||||
AudioStopped,
|
//AudioStopped,
|
||||||
|
AudioTerminatedAbnormally, // specifically for an ALSA loss of connection.
|
||||||
SetVuSubscriptions,
|
SetVuSubscriptions,
|
||||||
FreeVuSubscriptions,
|
FreeVuSubscriptions,
|
||||||
|
|
||||||
@@ -507,10 +508,10 @@ namespace pipedal
|
|||||||
write(RingBufferCommand::ReplaceEffect, pedalboard);
|
write(RingBufferCommand::ReplaceEffect, pedalboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStopped()
|
void AudioTerminatedAbnormally()
|
||||||
{
|
{
|
||||||
AudioStoppedBody body;
|
AudioStoppedBody body;
|
||||||
write(RingBufferCommand::AudioStopped, body);
|
write(RingBufferCommand::AudioTerminatedAbnormally, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectReplaced(Lv2Pedalboard *pedalboard)
|
void EffectReplaced(Lv2Pedalboard *pedalboard)
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ std::string spinnerText(const Clock::time_point &startTime)
|
|||||||
|
|
||||||
int64_t position = (ms * MAX_LENGTH / 3000) % (MAX_LENGTH + N_DOTS);
|
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)
|
for (int64_t i = 0; i < MAX_LENGTH; ++i)
|
||||||
{
|
{
|
||||||
if (i >= position && i < position + N_DOTS)
|
if (i >= position && i < position + N_DOTS)
|
||||||
|
|||||||
Reference in New Issue
Block a user