From 93c10a9f721f7b7135f42c510c5c825d62ef1564 Mon Sep 17 00:00:00 2001 From: Niellune Date: Tue, 24 Mar 2026 04:47:14 +0200 Subject: [PATCH] Audio rework --- settings.txt | 7 +- src/pcm_audio.cpp | 311 +++++++++++++++++++++++----------------------- src/pcm_audio.h | 43 +++---- src/settings.h | 3 +- 4 files changed, 179 insertions(+), 185 deletions(-) diff --git a/settings.txt b/settings.txt index 7f3c851..5916491 100644 --- a/settings.txt +++ b/settings.txt @@ -135,7 +135,7 @@ # USB read pipeline tuning, number of libusb bulk transfers kept in flight and size of each USB transfer # Increase amount of async-usb-calls if you have issues with audio and video lagging behind #async-usb-calls = 16 -#usb-buffer-size = 2048 +#usb-buffer-size = 20480 # Number of buffered USB data slots queued for processing #usb-buffer = 64 @@ -154,6 +154,11 @@ # Reduction level from 1 (no reduction) to 0 (fully silenced) #audio-fade = 0.3 +# Delay navidation audio in milliseconds. +# Adjust value so navigation guidance do not start before main audio fade out. +# If you experience audio artifacts with high values increase audio-buffer-size +#audio-aux-delay = 200 + # Basic number of audio sample frames in the output buffer that is scaled by sample rate. # This controls the size of the audio buffer used by the audio device. # The buffer time is ~ 1/10 buffer size in ms, depending on sample rate e.g. 2048 = ~200ms diff --git a/src/pcm_audio.cpp b/src/pcm_audio.cpp index 959fa7b..f49a3a2 100644 --- a/src/pcm_audio.cpp +++ b/src/pcm_audio.cpp @@ -6,32 +6,24 @@ // Add sample size (buffer size in samples) to ChannelConfig ChannelConfig PcmAudio::_configTable[] = { - {8000, 1, 1}, // type = 3, ~256ms - {48000, 2, 4}, // type = 4, ~170ms - {16000, 1, 2}, // type = 5, ~256ms - {24000, 1, 2}, // type = 6, ~170ms - {16000, 2, 2}, // type = 7, ~256ms + {8000, 1, 1}, // type = 3, ~256ms + {48000, 2, 4}, // type = 4, ~170ms + {16000, 1, 2}, // type = 5, ~256ms + {24000, 1, 2}, // type = 6, ~170ms + {16000, 2, 2}, // type = 7, ~256ms }; -// Implementation -PcmAudio::PcmAudio(const char *name) +PcmAudio::PcmAudio(const char *name) : _name("default"), + _fader(nullptr), + _playing(false), + _active(false), + _fade(false), + _config({0, 0, 0}), + _volume(1), + _fadedVolume(Settings::audioFade) { - if (!name || strlen(name) < 1) - { - _name = "[Audio]"; - return; - } - _name = name; - _fade = false; - _faded = false; - _volume = 1.0; - _fadeVolume = Settings::audioFade; - _config.channels = 0; - _config.rate = 0; - if (_fadeVolume < 0) - _fadeVolume = 0; - if (_fadeVolume > 1) - _fadeVolume = 1; + if (name && strlen(name) > 0) + _name = name; } PcmAudio::~PcmAudio() @@ -41,115 +33,6 @@ PcmAudio::~PcmAudio() _thread.join(); } -void PcmAudio::fadecpy(uint8_t *target, uint8_t *source, size_t len) -{ - if (!_fade && !_faded) - { - std::memcpy(target, source, len); - return; - } - - int16_t *src = reinterpret_cast(source); - int16_t *dst = reinterpret_cast(target); - _faded = true; - for (size_t i = 0; i < len / 2; i++) - { - if (_fade) - { - if (_volume - FADE_OUT_SPEED >= _fadeVolume) - _volume = _volume - FADE_OUT_SPEED; - } - else - { - if (_volume + FADE_IN_SPEED <= 1) - _volume = _volume + FADE_IN_SPEED; - } - dst[i] = src[i] * _volume; - } - _faded = _volume + FADE_IN_SPEED <= 1; -} - -void PcmAudio::callback(void *userdata, Uint8 *stream, int len) -{ - PcmAudio *self = static_cast(userdata); - const Message *segment = self->_data->peek(); - if (segment && self->_offset == 0 && getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)) != self->_config) - { - self->_paused = true; - self->_cv.notify_one(); - } - - if (self->_underflow) - { - int count = self->_data->count(); - if (count > self->_prefill) - self->_underflow = false; - else if (count == self->_lastCount) - self->_underflowCount++; - else - self->_lastCount = count; - } - - while (len > 0) - { - if(segment == nullptr && !self->_underflow) - { - if (self->_fader) - self->_fader->Fade(false); - self->_underflowCount = 0; - self->_underflow = true; - } - - if (self->_underflow) - { - std::fill_n(stream, len, 0); - self->_faded = self->_fade; - self->_volume = self->_faded ? Settings::audioFade : 1; - if (self->_underflowCount < BUFFER_WAIT_COUNT) - return; - self->_data->clear(); - self->_paused = true; - self->_cv.notify_one(); - return; - } - - int remain = segment->length() - self->_offset; - uint8_t *data = segment->data() + self->_offset; - - if (remain > len) - { - self->fadecpy(stream, data, len); - self->_offset = self->_offset + len; - return; - } - - self->fadecpy(stream, data, remain); - len = len - remain; - stream = stream + remain; - self->_data->pop(); - self->_offset = 0; - segment = self->_data->peek(); - if (segment && getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)) != self->_config) - { - self->_paused = true; - self->_cv.notify_one(); - } - } -} - -ChannelConfig PcmAudio::getConfig(int type) -{ - if (type >= 3 && type <= 7) - return _configTable[type - 3]; - // Default: 44100Hz, stereo, 4096 samples - return {44100, 2, 4}; -} - -void PcmAudio::Fade(bool enable) -{ - _fade = enable; -} - void PcmAudio::start(AtomicQueue *data, PcmAudio *fader) { if (_active) @@ -157,7 +40,7 @@ void PcmAudio::start(AtomicQueue *data, PcmAudio *fader) _fader = fader; _data = data; _active = true; - _thread = std::thread(&PcmAudio::runner, this); + _thread = std::thread(&PcmAudio::loop, this); } void PcmAudio::stop() @@ -166,14 +49,138 @@ void PcmAudio::stop() return; _active = false; _data->notify(); - _cv.notify_all(); } -void PcmAudio::runner() +ChannelConfig PcmAudio::getConfig(const Message *msg) +{ + uint8_t type = 0; + if (msg) + { + type = msg->getInt(OFFSET_AUDIO_FORMAT); + } + + if (type >= 3 && type <= 7) + return _configTable[type - 3]; + // Default: 44100Hz, stereo, 4096 samples + return {44100, 2, 4}; +} + +bool PcmAudio::isZero(const Message *msg) +{ + const uint64_t *p = (const uint64_t *)msg->data(); + int n = msg->length() / 8; + + for (int i = 0; i < n; ++i) + { + if (p[i] != 0) + return false; + } + + return true; +} + +void PcmAudio::fade(bool enable) +{ + _fade.store(enable); + if (!_playing) + { + _volume = enable ? Settings::audioFade : 1.0; + } +} + +void PcmAudio::fade(uint8_t *data, int32_t length) +{ + bool fade = _fade.load(); + if (!fade && _volume >= 1) + return; + + int16_t *buf = reinterpret_cast(data); + for (size_t i = 0; i < length / 2; i++) + { + if (fade) + { + if (_volume - FADE_OUT_SPEED >= _fadedVolume) + _volume = _volume - FADE_OUT_SPEED; + } + else + { + if (_volume + FADE_IN_SPEED <= 1) + _volume = _volume + FADE_IN_SPEED; + } + if (_volume < 1) + buf[i] = buf[i] * _volume; + } +} + +void PcmAudio::play(SDL_AudioDeviceID device, ChannelConfig config, int32_t segmentSize) +{ + uint8_t zeroSegments = 0; + bool nonZero = false; + int prefill = config.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay; + int segmentTimeMs = 1000.0 * segmentSize / (config.rate * config.channels * 2.0); + + log_i("Prepare to play %s %dkHz %s prefill %d ~%dms chunk %d", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono"), prefill, segmentTimeMs, segmentSize); + + if (!_data->waitFor(_active, segmentTimeMs * (prefill + 1) * 3, prefill)) + { + _data->clear(); + log_w("Not enough data to play %s %dkHz %s prefill %d ~%dms chunk %d", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono"), prefill, segmentTimeMs, segmentSize); + return; + } + + if (_fader && !_fader->faded()) + { + SDL_Delay(Settings::audioAuxDelay); + } + + while (_active) + { + std::unique_ptr segment = _data->pop(); + if (!segment) + return; + if (config != getConfig(segment.get())) + return; + + fade(segment->data(), segment->length()); + SDL_QueueAudio(device, segment->data(), segment->length()); + + if (!_playing) + { + log_d("Start playing %s %dkHz %s", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono")); + SDL_PauseAudioDevice(device, 0); + _playing = true; + } + + if (_fader) + { + if (isZero(segment.get())) + { + if (nonZero && ++zeroSegments == FADE_ZERO_SEGMENTS) + { + log_d("Audio %s is zeroes, fade other channel in", _name.c_str()); + _fader->fade(false); + } + } + else + { + nonZero = true; + zeroSegments = 0; + _fader->fade(true); + } + } + + if (!_data->waitFor(_active, segmentTimeMs * 3)) + return; + } +} + +void PcmAudio::loop() { std::string threadName = "audio-" + _name; setThreadName(threadName.c_str()); + log_d("Audio %s thread started", _name.c_str()); + SDL_AudioDeviceID device = 0; SDL_AudioSpec spec; @@ -183,12 +190,13 @@ void PcmAudio::runner() if (!segment) continue; - ChannelConfig config = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)); + ChannelConfig config = getConfig(segment); if (_config != config) { if (device != 0) { SDL_PauseAudioDevice(device, 1); + SDL_ClearQueuedAudio(device); SDL_CloseAudioDevice(device); device = 0; } @@ -198,45 +206,34 @@ void PcmAudio::runner() spec.freq = config.rate; spec.format = AUDIO_S16SYS; spec.channels = config.channels; - spec.samples = Settings::audioBuffer*config.scale; - spec.callback = callback; - spec.userdata = this; + spec.samples = Settings::audioBuffer * config.scale; + spec.callback = nullptr; + spec.userdata = nullptr; device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); if (device == 0) { - log_w("Failed to open audio %s > %s", _name.c_str(), SDL_GetError()); + log_w("Failed to open audio %s > %s", _name.c_str(), SDL_GetError()); SDL_Delay(100); continue; } _config = config; } - _offset = 0; - _paused = false; - _underflow = true; - _underflowCount = 0; - _lastCount = 0; - _prefill = spec.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay; - - SDL_PauseAudioDevice(device, 0); - log_i("Start playing %s %dkHz %s", _name.c_str(), _config.rate, (_config.channels == 2 ? "stereo" : "mono")); if (_fader) - _fader->Fade(true); - - std::unique_lock lock(_mtx); - _cv.wait(lock, [&] - { return _paused.load() || !_active.load(); }); - + _fader->fade(true); + play(device, config, segment->length()); + _playing = false; + if (_fader) + _fader->fade(false); SDL_PauseAudioDevice(device, 1); log_i("Stop playing %s", _name.c_str()); - if (_fader) - _fader->Fade(false); } if (device) { SDL_PauseAudioDevice(device, 1); + SDL_ClearQueuedAudio(device); SDL_CloseAudioDevice(device); } } diff --git a/src/pcm_audio.h b/src/pcm_audio.h index 80da1ba..585caf1 100644 --- a/src/pcm_audio.h +++ b/src/pcm_audio.h @@ -12,7 +12,7 @@ #define FADE_IN_SPEED 0.00001 #define FADE_OUT_SPEED 0.0001 -#define BUFFER_WAIT_COUNT 5 +#define FADE_ZERO_SEGMENTS 10 struct ChannelConfig { @@ -41,36 +41,27 @@ public: void start(AtomicQueue *data, PcmAudio *fader = nullptr); void stop(); - void Fade(bool enble); - private: - void runner(); - void loop(SDL_AudioDeviceID device); - void fadecpy(uint8_t *target, uint8_t *source, size_t len); - - static void callback(void *userdata, Uint8 *stream, int len); + static ChannelConfig getConfig(const Message *msg); static ChannelConfig _configTable[]; - static ChannelConfig getConfig(int type); - AtomicQueue *_data; - ChannelConfig _config; - int _offset; - PcmAudio *_fader; - bool _fade; - bool _faded; - float _volume; - float _fadeVolume; - bool _underflow; - int _underflowCount; - int _lastCount; - int _prefill; + void fade(bool enble); + void loop(); + void play(SDL_AudioDeviceID device, ChannelConfig config, int32_t segmentSize); + bool isZero(const Message *msg); + void fade(uint8_t *data, int32_t length); + bool faded() const { return _volume <= _fadedVolume; } - std::thread _thread; - std::mutex _mtx; - std::condition_variable _cv; - std::atomic _paused{false}; - std::atomic _active{false}; std::string _name; + PcmAudio *_fader; + std::thread _thread; + std::atomic _playing; + std::atomic _active; + std::atomic _fade; + ChannelConfig _config; + AtomicQueue *_data; + float _volume; + float _fadedVolume; }; #endif /* SRC_PCM_AUDIO */ diff --git a/src/settings.h b/src/settings.h index 4dcf5fa..dab6298 100644 --- a/src/settings.h +++ b/src/settings.h @@ -47,13 +47,14 @@ public: static inline Setting alternativeRendering{"alternative-rendering", false}; static inline Setting fastScale{"fast-render-scale", false}; static inline Setting usbQueue{"async-usb-calls", 16}; - static inline Setting usbTransferSize{"usb-buffer-size", 2048}; + static inline Setting usbTransferSize{"usb-buffer-size", 20480}; static inline Setting usbBuffer{"usb-buffer", 64}; static inline Setting videoQueue{"video-buffer-size", 64}; static inline Setting audioQueue{"audio-buffer-size", 64}; static inline Setting audioDelay{"audio-buffer-wait", 2}; static inline Setting audioDelayCall{"audio-buffer-wait-call", 6}; static inline Setting audioFade{"audio-fade", 0.3}; + static inline Setting audioAuxDelay{"audio-aux-delay", 200}; static inline Setting audioBuffer{"audio-buffer-samples", 512}; static inline Setting audioDriver{"audio-driver", ""}; static inline Setting onConnect{"on-connect-script", ""};