This commit is contained in:
Robin E. R. Davies
2025-08-10 08:07:27 -04:00
+82 -53
View File
@@ -24,6 +24,7 @@
#include "pch.h" #include "pch.h"
#include "util.hpp" #include "util.hpp"
#include <cmath>
#include "Finally.hpp" #include "Finally.hpp"
#include <bit> #include <bit>
#include <memory> #include <memory>
@@ -56,6 +57,7 @@ using namespace pipedal;
namespace pipedal namespace pipedal
{ {
#define TRACE_BUFFER_POSITIONS 1
static bool ShouldForceStereoChannels(snd_pcm_t *pcmHandle, snd_pcm_hw_params_t *hwParams, unsigned int channelsMin, unsigned int channelsMax) static bool ShouldForceStereoChannels(snd_pcm_t *pcmHandle, snd_pcm_hw_params_t *hwParams, unsigned int channelsMin, unsigned int channelsMax)
{ {
// The problem: old IC2 drivers seem to return 1-8 channels, but 8 channels is non-functinal. The assumption is that legacy drivers // The problem: old IC2 drivers seem to return 1-8 channels, but 8 channels is non-functinal. The assumption is that legacy drivers
@@ -245,7 +247,41 @@ namespace pipedal
class AlsaDriverImpl : public AudioDriver class AlsaDriverImpl : public AudioDriver
{ {
private: private:
void TraceBuffers(size_t framesInBuffers, char code = ' '); struct BufferTrace
{
uint64_t time;
snd_pcm_sframes_t inAvail;
snd_pcm_sframes_t outAvail;
snd_pcm_sframes_t buffered;
snd_pcm_sframes_t total;
char code;
};
std::vector<BufferTrace> bufferTraces{1000};
size_t bufferTraceIndex = 0;
inline void TraceBufferPositions(size_t framesInBuffer, char code = ' ')
{
#if TRACE_BUFFER_POSITIONS
uint64_t time = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
auto inAvail = snd_pcm_avail_update(this->captureHandle);
auto outAvail = snd_pcm_avail_update(this->playbackHandle);
auto total = inAvail + outAvail + framesInBuffer;
bufferTraces[bufferTraceIndex++] = {
time,
inAvail,
outAvail,
(snd_pcm_sframes_t)framesInBuffer,
(snd_pcm_sframes_t)total,
code};
if (bufferTraceIndex == bufferTraces.size())
{
bufferTraceIndex = 0;
}
#endif
}
std::recursive_mutex restartMutex; std::recursive_mutex restartMutex;
pipedal::CpuUse cpuUse; pipedal::CpuUse cpuUse;
@@ -257,8 +293,15 @@ namespace pipedal
#endif #endif
uint32_t sampleRate = 0; uint32_t sampleRate = 0;
uint32_t bufferSize; uint32_t bufferSize = 0;
uint32_t numberOfBuffers; uint32_t numberOfBuffers = 0;
unsigned int capturePeriods = 0;
unsigned int playbackPeriods = 0;
uint32_t captureHardwarePeriodSize = 0;
uint32_t playbackHardwarePeriodSize = 0;
;
int playbackChannels = 0; int playbackChannels = 0;
int captureChannels = 0; int captureChannels = 0;
@@ -357,8 +400,6 @@ namespace pipedal
snd_pcm_t *playbackHandle = nullptr; snd_pcm_t *playbackHandle = nullptr;
snd_pcm_t *captureHandle = nullptr; snd_pcm_t *captureHandle = nullptr;
unsigned int periods = 0;
snd_pcm_hw_params_t *captureHwParams = nullptr; snd_pcm_hw_params_t *captureHwParams = nullptr;
snd_pcm_sw_params_t *captureSwParams = nullptr; snd_pcm_sw_params_t *captureSwParams = nullptr;
snd_pcm_hw_params_t *playbackHwParams = nullptr; snd_pcm_hw_params_t *playbackHwParams = nullptr;
@@ -437,11 +478,14 @@ namespace pipedal
snd_pcm_hw_params_t *hwParams, snd_pcm_hw_params_t *hwParams,
snd_pcm_sw_params_t *swParams, snd_pcm_sw_params_t *swParams,
int *channels, int *channels,
unsigned int *periods) unsigned int *periods,
unsigned int *hwPeriodSize)
{ {
int err; int err;
snd_pcm_uframes_t stop_th; snd_pcm_uframes_t stop_th;
bool isCaptureStream = strcmp(streamType, "capture") == 0;
if ((err = snd_pcm_hw_params_any(handle, hwParams)) < 0) if ((err = snd_pcm_hw_params_any(handle, hwParams)) < 0)
{ {
AlsaError(SS("No playback configurations available (" << snd_strerror(err) << ")")); AlsaError(SS("No playback configurations available (" << snd_strerror(err) << ")"));
@@ -513,7 +557,7 @@ namespace pipedal
{ {
AlsaError(SS("Can't set period size to " << this->bufferSize << " (" << alsa_device_name << "/" << streamType << ")")); AlsaError(SS("Can't set period size to " << this->bufferSize << " (" << alsa_device_name << "/" << streamType << ")"));
} }
this->bufferSize = effectivePeriodSize; *hwPeriodSize = effectivePeriodSize;
*periods = this->numberOfBuffers; *periods = this->numberOfBuffers;
dir = 0; dir = 0;
@@ -547,7 +591,7 @@ namespace pipedal
snd_pcm_sw_params_current(handle, swParams); snd_pcm_sw_params_current(handle, swParams);
if (handle == this->captureHandle) if (isCaptureStream)
{ {
if ((err = snd_pcm_sw_params_set_start_threshold(handle, swParams, if ((err = snd_pcm_sw_params_set_start_threshold(handle, swParams,
0)) < 0) 0)) < 0)
@@ -564,7 +608,7 @@ namespace pipedal
} }
} }
stop_th = *periods * this->bufferSize; stop_th = *periods * *hwPeriodSize;
if (this->soft_mode) if (this->soft_mode)
{ {
stop_th = (snd_pcm_uframes_t)-1; stop_th = (snd_pcm_uframes_t)-1;
@@ -582,13 +626,19 @@ namespace pipedal
AlsaError(SS("Cannot set silence threshold for " << alsa_device_name)); AlsaError(SS("Cannot set silence threshold for " << alsa_device_name));
} }
if (handle == this->playbackHandle) if (!isCaptureStream)
{
// For playback, set avail_min to one buffer size to minimize latency
// while ensuring we have enough buffered data to prevent underruns
snd_pcm_uframes_t playback_avail_min = this->bufferSize;
err = snd_pcm_sw_params_set_avail_min( err = snd_pcm_sw_params_set_avail_min(
handle, swParams, handle, swParams, playback_avail_min);
this->bufferSize * (*periods - this->numberOfBuffers + 1)); }
else else
{
err = snd_pcm_sw_params_set_avail_min( err = snd_pcm_sw_params_set_avail_min(
handle, swParams, this->bufferSize); handle, swParams, this->bufferSize);
}
if (err < 0) if (err < 0)
{ {
@@ -636,7 +686,8 @@ namespace pipedal
captureHwParams, captureHwParams,
captureSwParams, captureSwParams,
&captureChannels, &captureChannels,
&this->periods); &this->capturePeriods,
&this->captureHardwarePeriodSize);
} }
if (this->playbackHandle) if (this->playbackHandle)
{ {
@@ -647,7 +698,8 @@ namespace pipedal
playbackHwParams, playbackHwParams,
playbackSwParams, playbackSwParams,
&playbackChannels, &playbackChannels,
&this->periods); &this->playbackPeriods,
&this->playbackHardwarePeriodSize);
} }
#ifdef ALSADRIVER_CONFIG_DBG #ifdef ALSADRIVER_CONFIG_DBG
@@ -1186,8 +1238,8 @@ namespace pipedal
} }
captureFrameSize = captureSampleSize * captureChannels; captureFrameSize = captureSampleSize * captureChannels;
rawCaptureBuffer.resize(captureFrameSize * bufferSize); rawCaptureBuffer.resize(captureFrameSize * bufferSize * 2);
memset(rawCaptureBuffer.data(), 0, captureFrameSize * bufferSize); memset(rawCaptureBuffer.data(), 0, rawCaptureBuffer.size());
AllocateBuffers(captureBuffers, captureChannels); AllocateBuffers(captureBuffers, captureChannels);
} }
@@ -1401,17 +1453,22 @@ namespace pipedal
{ {
validate_capture_handle(); validate_capture_handle();
memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize); memset(rawPlaybackBuffer.data(), 0, rawPlaybackBuffer.size());
int retry = 0; int retry = 0;
while (true) while (true)
{ {
auto avail = snd_pcm_avail(this->playbackHandle); auto avail = snd_pcm_avail(this->playbackHandle);
if (avail < 0) if (avail < 0)
{ {
if (avail == -EAGAIN)
{
return;
}
if (++retry >= 5) // kinda sus code. let's make sure we don't spin forever. if (++retry >= 5) // kinda sus code. let's make sure we don't spin forever.
{ {
throw std::runtime_error("Timed out trying to fill the audio output buffer."); throw std::runtime_error("Timed out trying to fill the audio output buffer.");
} }
int err = snd_pcm_prepare(playbackHandle); int err = snd_pcm_prepare(playbackHandle);
if (err < 0) if (err < 0)
{ {
@@ -1422,8 +1479,9 @@ namespace pipedal
} }
if (avail == 0) if (avail == 0)
break; break;
if (avail > this->bufferSize)
avail = this->bufferSize; if (avail * playbackFrameSize > this->rawPlaybackBuffer.size())
this->rawPlaybackBuffer.resize(avail * playbackFrameSize);
ssize_t err = WriteBuffer(playbackHandle, rawPlaybackBuffer.data(), avail); ssize_t err = WriteBuffer(playbackHandle, rawPlaybackBuffer.data(), avail);
if (err < 0) if (err < 0)
@@ -1439,7 +1497,7 @@ namespace pipedal
try try
{ {
TraceBuffers(framesRead,'w'); TraceBufferPositions(framesRead, 'w');
if (err == -EPIPE) if (err == -EPIPE)
{ {
err = snd_pcm_prepare(playback_handle); err = snd_pcm_prepare(playback_handle);
@@ -1471,7 +1529,7 @@ namespace pipedal
try try
{ {
TraceBuffers(bufferedFrames,'r'); TraceBufferPositions(bufferedFrames, 'r');
if (err == -EPIPE) if (err == -EPIPE)
{ {
@@ -1575,7 +1633,7 @@ namespace pipedal
{ {
snd_pcm_wait(captureHandle, 1); snd_pcm_wait(captureHandle, 1);
} }
TraceBuffers(framesRead); TraceBufferPositions(framesRead);
} while (frames > 0); } while (frames > 0);
return framesRead; return framesRead;
} }
@@ -1659,7 +1717,7 @@ namespace pipedal
int err; int err;
if ((err = snd_pcm_start(captureHandle)) < 0) if ((err = snd_pcm_start(captureHandle)) < 0)
{ {
throw PiPedalStateException("Unable to start ALSA capture."); throw PiPedalStateException(SS("Unable to start ALSA capture. " << snd_strerror(err)));
} }
CrashGuardLock crashGuardLock; CrashGuardLock crashGuardLock;
@@ -1733,6 +1791,7 @@ namespace pipedal
this->driverHost->OnUnderrun(); this->driverHost->OnUnderrun();
recover_from_output_underrun(captureHandle, playbackHandle, err, framesRead); recover_from_output_underrun(captureHandle, playbackHandle, err, framesRead);
framesRead = 0;
} }
cpuUse.AddSample(ProfileCategory::Write); cpuUse.AddSample(ProfileCategory::Write);
} }
@@ -2270,36 +2329,6 @@ namespace pipedal
#endif #endif
} }
struct BufferTrace
{
snd_pcm_sframes_t inAvail;
snd_pcm_sframes_t outAvail;
snd_pcm_sframes_t buffered;
snd_pcm_sframes_t total;
char code;
};
std::vector<BufferTrace> bufferTraces(1000);
size_t bufferTraceIndex;
void AlsaDriverImpl::TraceBuffers(size_t framesInBuffer,char code)
{
auto inAvail = snd_pcm_avail_update(this->captureHandle);
auto outAvail = snd_pcm_avail_update(this->playbackHandle);
auto total = inAvail + outAvail + framesInBuffer;
bufferTraces[bufferTraceIndex++] = {
inAvail,
outAvail,
(snd_pcm_sframes_t)framesInBuffer,
(snd_pcm_sframes_t)total,
code};
if (bufferTraceIndex == bufferTraces.size())
{
bufferTraceIndex = 0;
}
}
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.