Enable ALSA rate resampling for configurable audio sample rates

Changes the audio subsystem from hw: (direct hardware access) to plughw:
(plugin layer with rate conversion) to enable configurable sample rates.

Changes:
- Update ALSA build to include plug,rate,linear,copy plugins
- Change device names from hw: to plughw: in C and Go code
- Remove 48kHz hardcoding for HDMI audio output
- Keep USB at 48kHz since hardware is fixed at that rate
- Update all comments to reflect plughw usage

Technical details:
- hw: devices bypass all ALSA plugins and require exact hardware rate match
- plughw: devices enable the ALSA plugin layer for automatic rate conversion
- Hardware still receives at native rate (48kHz), resampling happens in userspace
- HDMI can now use 8k/12k/16k/24k/48kHz, USB remains at 48kHz
- NEON-optimized resampling provides good performance on Cortex-A7

Requires rebuilding ALSA library with updated plugin configuration.
This commit is contained in:
Alex P 2025-11-21 02:22:22 +02:00
parent 2040db6094
commit 0be9dbcc6c
4 changed files with 18 additions and 15 deletions

View File

@ -48,7 +48,7 @@ if [ ! -f .built ]; then
# Use minimal ALSA configuration to avoid FD_SETSIZE issues in devcontainer # Use minimal ALSA configuration to avoid FD_SETSIZE issues in devcontainer
CFLAGS="$OPTIM_CFLAGS" ./configure --host $BUILDKIT_FLAVOR \ CFLAGS="$OPTIM_CFLAGS" ./configure --host $BUILDKIT_FLAVOR \
--enable-static=yes --enable-shared=no \ --enable-static=yes --enable-shared=no \
--with-pcm-plugins=rate,linear \ --with-pcm-plugins=plug,rate,linear,copy \
--disable-seq --disable-rawmidi --disable-ucm \ --disable-seq --disable-rawmidi --disable-ucm \
--disable-python --disable-old-symbols \ --disable-python --disable-old-symbols \
--disable-topology --disable-hwdep --disable-mixer \ --disable-topology --disable-hwdep --disable-mixer \

View File

@ -30,9 +30,9 @@ var (
func getAlsaDevice(source string) string { func getAlsaDevice(source string) string {
if source == "hdmi" { if source == "hdmi" {
return "hw:0,0" return "plughw:0,0"
} }
return "hw:1,0" return "plughw:1,0"
} }
func initAudio() { func initAudio() {

View File

@ -3,16 +3,16 @@
* *
* Bidirectional audio processing optimized for ARM NEON SIMD: * Bidirectional audio processing optimized for ARM NEON SIMD:
* - OUTPUT PATH: TC358743 HDMI or USB Gadget audio Client speakers * - OUTPUT PATH: TC358743 HDMI or USB Gadget audio Client speakers
* Pipeline: ALSA hw:0,0 or hw:1,0 capture Opus encode (192kbps, FEC enabled) * Pipeline: ALSA plughw:0,0 or plughw:1,0 capture Opus encode (192kbps, FEC enabled)
* *
* - INPUT PATH: Client microphone Device speakers * - INPUT PATH: Client microphone Device speakers
* Pipeline: Opus decode (with FEC) ALSA hw:1,0 playback * Pipeline: Opus decode (with FEC) ALSA plughw:1,0 playback
* *
* Key features: * Key features:
* - ARM NEON SIMD optimization for all audio operations * - ARM NEON SIMD optimization for all audio operations
* - Opus in-band FEC for packet loss resilience * - Opus in-band FEC for packet loss resilience
* - S16_LE stereo, 20ms frames (sample rate configurable: 8k/12k/16k/24k/48kHz) * - S16_LE stereo, 20ms frames (sample rate configurable: 8k/12k/16k/24k/48kHz)
* - ALSA rate plugin resamples hardware output to match requested Opus-compatible rate * - ALSA plughw layer provides automatic rate conversion from hardware to Opus rate
*/ */
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@ -140,19 +140,19 @@ void update_audio_decoder_constants(uint32_t sr, uint8_t ch, uint16_t fs, uint16
* Must be called before jetkvm_audio_capture_init or jetkvm_audio_playback_init * Must be called before jetkvm_audio_capture_init or jetkvm_audio_playback_init
* *
* Device mapping (set via ALSA_CAPTURE_DEVICE/ALSA_PLAYBACK_DEVICE): * Device mapping (set via ALSA_CAPTURE_DEVICE/ALSA_PLAYBACK_DEVICE):
* hw:0,0 = TC358743 HDMI audio input (for OUTPUT path capture) * plughw:0,0 = TC358743 HDMI audio with rate conversion (for OUTPUT path capture)
* hw:1,0 = USB Audio Gadget (for OUTPUT path capture or INPUT path playback) * plughw:1,0 = USB Audio Gadget with rate conversion (for OUTPUT path capture or INPUT path playback)
*/ */
static void init_alsa_devices_from_env(void) { static void init_alsa_devices_from_env(void) {
// Always read from environment to support device switching // Always read from environment to support device switching
alsa_capture_device = getenv("ALSA_CAPTURE_DEVICE"); alsa_capture_device = getenv("ALSA_CAPTURE_DEVICE");
if (alsa_capture_device == NULL || alsa_capture_device[0] == '\0') { if (alsa_capture_device == NULL || alsa_capture_device[0] == '\0') {
alsa_capture_device = "hw:1,0"; // Default: USB gadget audio for capture alsa_capture_device = "plughw:1,0"; // Default: USB gadget audio for capture with rate conversion
} }
alsa_playback_device = getenv("ALSA_PLAYBACK_DEVICE"); alsa_playback_device = getenv("ALSA_PLAYBACK_DEVICE");
if (alsa_playback_device == NULL || alsa_playback_device[0] == '\0') { if (alsa_playback_device == NULL || alsa_playback_device[0] == '\0') {
alsa_playback_device = "hw:1,0"; // Default: USB gadget audio for playback alsa_playback_device = "plughw:1,0"; // Default: USB gadget audio for playback with rate conversion
} }
} }
@ -430,7 +430,7 @@ static int configure_alsa_device(snd_pcm_t *handle, const char *device_name, uin
/** /**
* Initialize OUTPUT path (HDMI or USB Gadget audio capture Opus encoder) * Initialize OUTPUT path (HDMI or USB Gadget audio capture Opus encoder)
* Opens ALSA capture device from ALSA_CAPTURE_DEVICE env (default: hw:1,0, set to hw:0,0 for TC358743 HDMI) * Opens ALSA capture device from ALSA_CAPTURE_DEVICE env (default: plughw:1,0, set to plughw:0,0 for HDMI)
* and creates Opus encoder with optimized settings * and creates Opus encoder with optimized settings
* @return 0 on success, -EBUSY if initializing, -1/-2/-3 on errors * @return 0 on success, -EBUSY if initializing, -1/-2/-3 on errors
*/ */
@ -606,7 +606,7 @@ retry_read:
/** /**
* Initialize INPUT path (Opus decoder device speakers) * Initialize INPUT path (Opus decoder device speakers)
* Opens ALSA playback device from ALSA_PLAYBACK_DEVICE env (default: hw:1,0) * Opens ALSA playback device from ALSA_PLAYBACK_DEVICE env (default: plughw:1,0)
* and creates Opus decoder. Returns immediately on device open failure (no fallback). * and creates Opus decoder. Returns immediately on device open failure (no fallback).
* @return 0 on success, -EBUSY if initializing, -1/-2 on errors * @return 0 on success, -EBUSY if initializing, -1/-2 on errors
*/ */

View File

@ -83,10 +83,13 @@ func (c *CgoSource) Connect() error {
func (c *CgoSource) connectOutput() error { func (c *CgoSource) connectOutput() error {
os.Setenv("ALSA_CAPTURE_DEVICE", c.alsaDevice) os.Setenv("ALSA_CAPTURE_DEVICE", c.alsaDevice)
// USB Audio Gadget (hw:1,0) only supports 48kHz // Using plughw: enables ALSA rate conversion plugin
// For HDMI (hw:0,0), use configured sample rate // USB Gadget hardware is fixed at 48kHz (configfs hardcoded), so keep it at 48kHz
// HDMI can use configured rate - plughw resamples from hardware rate to Opus rate
sampleRate := c.config.SampleRate sampleRate := c.config.SampleRate
if c.alsaDevice == "hw:1,0" { if c.alsaDevice == "plughw:1,0" {
sampleRate = 48000
} else if sampleRate == 0 {
sampleRate = 48000 sampleRate = 48000
} }
frameSize := uint16(sampleRate * 20 / 1000) frameSize := uint16(sampleRate * 20 / 1000)