kvm/internal/audio/c/audio.c

826 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* JetKVM Audio Processing Module
*
* Bidirectional audio processing optimized for ARM NEON SIMD:
* - OUTPUT PATH: TC358743 HDMI audio → Client speakers
* Pipeline: ALSA hw:0,0 capture → 2.5x gain → Opus encode (96kbps, FEC enabled)
*
* - INPUT PATH: Client microphone → Device speakers
* Pipeline: Opus decode (with FEC) → ALSA hw:1,0 playback
*
* Key features:
* - ARM NEON SIMD optimization for all audio operations
* - Opus in-band FEC for packet loss resilience
* - Ultra-low CPU usage (~0.5% on RV1106)
* - S16_LE @ 48kHz stereo, 20ms frames (960 samples)
*/
#include <alsa/asoundlib.h>
#include <opus.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
// ARM NEON SIMD support (always available on JetKVM's ARM Cortex-A7)
#include <arm_neon.h>
#define SIMD_ALIGN __attribute__((aligned(16)))
#define SIMD_PREFETCH(addr, rw, locality) __builtin_prefetch(addr, rw, locality)
static int trace_logging_enabled = 0;
static int simd_initialized = 0;
static void simd_init_once(void) {
if (simd_initialized) return;
simd_initialized = 1;
}
// ============================================================================
// GLOBAL STATE VARIABLES
// ============================================================================
// ALSA device handles
static snd_pcm_t *pcm_capture_handle = NULL; // OUTPUT: TC358743 HDMI audio → client
static snd_pcm_t *pcm_playback_handle = NULL; // INPUT: Client microphone → device speakers
// Opus codec instances
static OpusEncoder *encoder = NULL;
static OpusDecoder *decoder = NULL;
// Audio format (S16_LE @ 48kHz stereo)
static int sample_rate = 48000;
static int channels = 2;
static int frame_size = 960; // 20ms frames at 48kHz
// Opus encoder settings (optimized for minimal CPU ~0.5% on RV1106)
static int opus_bitrate = 96000; // 96 kbps - good quality/bandwidth balance
static int opus_complexity = 1; // Complexity 1 - minimal CPU usage
static int opus_vbr = 1; // Variable bitrate enabled
static int opus_vbr_constraint = 1; // Constrained VBR - predictable bandwidth
static int opus_signal_type = -1000; // OPUS_AUTO - automatic signal type detection
static int opus_bandwidth = 1103; // OPUS_BANDWIDTH_WIDEBAND (50-8000 Hz)
static int opus_dtx = 0; // DTX disabled - no discontinuous transmission
static int opus_lsb_depth = 16; // 16-bit depth - matches S16_LE format
// Network configuration
static int max_packet_size = 1500;
// ALSA retry configuration
static int sleep_microseconds = 1000;
static int max_attempts_global = 5;
static int max_backoff_us_global = 500000;
// ALSA buffer configuration (not currently used - kept for future optimization)
static const int optimized_buffer_size = 1;
// ============================================================================
// FUNCTION DECLARATIONS
// ============================================================================
int jetkvm_audio_capture_init();
void jetkvm_audio_capture_close();
int jetkvm_audio_read_encode(void *opus_buf);
int jetkvm_audio_playback_init();
void jetkvm_audio_playback_close();
int jetkvm_audio_decode_write(void *opus_buf, int opus_size);
void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint,
int signal_type, int bandwidth, int dtx, int lsb_depth, int sr, int ch,
int fs, int max_pkt, int sleep_us, int max_attempts, int max_backoff);
void set_trace_logging(int enabled);
int update_opus_encoder_params(int bitrate, int complexity, int vbr, int vbr_constraint,
int signal_type, int bandwidth, int dtx);
// ============================================================================
// CONFIGURATION FUNCTIONS
// ============================================================================
/**
* Sync configuration from Go to C
*/
void update_audio_constants(int bitrate, int complexity, int vbr, int vbr_constraint,
int signal_type, int bandwidth, int dtx, int lsb_depth, int sr, int ch,
int fs, int max_pkt, int sleep_us, int max_attempts, int max_backoff) {
opus_bitrate = bitrate;
opus_complexity = complexity;
opus_vbr = vbr;
opus_vbr_constraint = vbr_constraint;
opus_signal_type = signal_type;
opus_bandwidth = bandwidth;
opus_dtx = dtx;
opus_lsb_depth = lsb_depth;
sample_rate = sr;
channels = ch;
frame_size = fs;
max_packet_size = max_pkt;
sleep_microseconds = sleep_us;
max_attempts_global = max_attempts;
max_backoff_us_global = max_backoff;
}
/**
* Enable/disable trace logging (zero overhead when disabled)
*/
void set_trace_logging(int enabled) {
trace_logging_enabled = enabled;
}
// ============================================================================
// SIMD-OPTIMIZED BUFFER OPERATIONS (ARM NEON)
// ============================================================================
/**
* Clear audio buffer using NEON (8 samples/iteration)
* @param buffer Audio buffer to clear
* @param samples Number of samples to zero out
*/
static inline void simd_clear_samples_s16(short *buffer, int samples) {
simd_init_once();
int simd_samples = samples & ~7;
const int16x8_t zero = vdupq_n_s16(0);
// SIMD path: zero 8 samples per iteration
for (int i = 0; i < simd_samples; i += 8) {
vst1q_s16(&buffer[i], zero);
}
// Scalar path: handle remaining samples
for (int i = simd_samples; i < samples; i++) {
buffer[i] = 0;
}
}
/**
* Apply gain using NEON Q15 fixed-point math (8 samples/iteration)
* Uses vqrdmulhq_s16 for single-instruction saturating rounded multiply-high
* @param samples Audio buffer to scale in-place
* @param count Number of samples to process
* @param volume Gain multiplier (e.g., 2.5 for 2.5x gain)
*/
static inline void simd_scale_volume_s16(short *samples, int count, float volume) {
simd_init_once();
// Convert float gain to Q14 fixed-point for vqrdmulhq_s16
// vqrdmulhq_s16 extracts bits [30:15], so multiply by 16384 (2^14) instead of 32768 (2^15)
int16_t vol_fixed = (int16_t)(volume * 16384.0f);
int16x8_t vol_vec = vdupq_n_s16(vol_fixed);
int simd_count = count & ~7;
// SIMD path: process 8 samples per iteration
for (int i = 0; i < simd_count; i += 8) {
int16x8_t samples_vec = vld1q_s16(&samples[i]);
int16x8_t result = vqrdmulhq_s16(samples_vec, vol_vec);
vst1q_s16(&samples[i], result);
}
// Scalar path: handle remaining samples
for (int i = simd_count; i < count; i++) {
samples[i] = (short)((samples[i] * vol_fixed) >> 14);
}
}
// ============================================================================
// INITIALIZATION STATE TRACKING
// ============================================================================
static volatile int capture_initializing = 0;
static volatile int capture_initialized = 0;
static volatile int playback_initializing = 0;
static volatile int playback_initialized = 0;
/**
* Update Opus encoder settings at runtime (does NOT modify FEC settings)
* Note: FEC configuration remains unchanged - set at initialization
* @return 0 on success, -1 if not initialized, >0 if some settings failed
*/
int update_opus_encoder_params(int bitrate, int complexity, int vbr, int vbr_constraint,
int signal_type, int bandwidth, int dtx) {
if (!encoder || !capture_initialized) {
return -1;
}
// Update global configuration variables
opus_bitrate = bitrate;
opus_complexity = complexity;
opus_vbr = vbr;
opus_vbr_constraint = vbr_constraint;
opus_signal_type = signal_type;
opus_bandwidth = bandwidth;
opus_dtx = dtx;
// Apply settings to encoder (FEC settings not modified)
int result = 0;
result |= opus_encoder_ctl(encoder, OPUS_SET_BITRATE(opus_bitrate));
result |= opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(opus_complexity));
result |= opus_encoder_ctl(encoder, OPUS_SET_VBR(opus_vbr));
result |= opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(opus_vbr_constraint));
result |= opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(opus_signal_type));
result |= opus_encoder_ctl(encoder, OPUS_SET_BANDWIDTH(opus_bandwidth));
result |= opus_encoder_ctl(encoder, OPUS_SET_DTX(opus_dtx));
return result;
}
// ============================================================================
// ALSA UTILITY FUNCTIONS
// ============================================================================
/**
* Open ALSA device with exponential backoff retry
* @return 0 on success, negative error code on failure
*/
static int safe_alsa_open(snd_pcm_t **handle, const char *device, snd_pcm_stream_t stream) {
int attempt = 0;
int err;
int backoff_us = sleep_microseconds;
while (attempt < max_attempts_global) {
err = snd_pcm_open(handle, device, stream, SND_PCM_NONBLOCK);
if (err >= 0) {
snd_pcm_nonblock(*handle, 0);
return 0;
}
attempt++;
if (err == -EBUSY || err == -EAGAIN) {
usleep(backoff_us);
backoff_us = (backoff_us * 2 < max_backoff_us_global) ? backoff_us * 2 : max_backoff_us_global;
} else if (err == -ENODEV || err == -ENOENT) {
usleep(backoff_us * 2);
backoff_us = (backoff_us * 2 < max_backoff_us_global) ? backoff_us * 2 : max_backoff_us_global;
} else if (err == -EPERM || err == -EACCES) {
usleep(backoff_us / 2);
} else {
usleep(backoff_us);
backoff_us = (backoff_us * 2 < max_backoff_us_global) ? backoff_us * 2 : max_backoff_us_global;
}
}
return err;
}
/**
* Configure ALSA device (S16_LE @ 48kHz stereo with optimized buffering)
* @param handle ALSA PCM handle
* @param device_name Unused (for debugging only)
* @return 0 on success, negative error code on failure
*/
static int configure_alsa_device(snd_pcm_t *handle, const char *device_name) {
snd_pcm_hw_params_t *params;
snd_pcm_sw_params_t *sw_params;
int err;
if (!handle) return -1;
snd_pcm_hw_params_alloca(&params);
snd_pcm_sw_params_alloca(&sw_params);
err = snd_pcm_hw_params_any(handle, params);
if (err < 0) return err;
err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) return err;
err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
if (err < 0) return err;
err = snd_pcm_hw_params_set_channels(handle, params, channels);
if (err < 0) return err;
err = snd_pcm_hw_params_set_rate(handle, params, sample_rate, 0);
if (err < 0) {
unsigned int rate = sample_rate;
err = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
if (err < 0) return err;
}
snd_pcm_uframes_t period_size = optimized_buffer_size ? frame_size : frame_size / 2;
if (period_size < 64) period_size = 64;
err = snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, 0);
if (err < 0) return err;
snd_pcm_uframes_t buffer_size = optimized_buffer_size ? period_size * 2 : period_size * 4;
err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
if (err < 0) return err;
err = snd_pcm_hw_params(handle, params);
if (err < 0) return err;
err = snd_pcm_sw_params_current(handle, sw_params);
if (err < 0) return err;
err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, period_size);
if (err < 0) return err;
err = snd_pcm_sw_params_set_avail_min(handle, sw_params, period_size);
if (err < 0) return err;
err = snd_pcm_sw_params(handle, sw_params);
if (err < 0) return err;
return snd_pcm_prepare(handle);
}
// ============================================================================
// AUDIO OUTPUT PATH FUNCTIONS (TC358743 HDMI Audio → Client Speakers)
// ============================================================================
/**
* Initialize OUTPUT path (TC358743 HDMI capture → Opus encoder)
* Opens hw:0,0 (TC358743) and creates Opus encoder with optimized settings
* @return 0 on success, -EBUSY if initializing, -1/-2/-3 on errors
*/
int jetkvm_audio_capture_init() {
int err;
simd_init_once();
if (__sync_bool_compare_and_swap(&capture_initializing, 0, 1) == 0) {
return -EBUSY;
}
if (capture_initialized) {
capture_initializing = 0;
return 0;
}
if (encoder) {
opus_encoder_destroy(encoder);
encoder = NULL;
}
if (pcm_capture_handle) {
snd_pcm_close(pcm_capture_handle);
pcm_capture_handle = NULL;
}
err = safe_alsa_open(&pcm_capture_handle, "hw:0,0", SND_PCM_STREAM_CAPTURE);
if (err < 0) {
capture_initializing = 0;
return -1;
}
err = configure_alsa_device(pcm_capture_handle, "capture");
if (err < 0) {
snd_pcm_close(pcm_capture_handle);
pcm_capture_handle = NULL;
capture_initializing = 0;
return -2;
}
int opus_err = 0;
encoder = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_AUDIO, &opus_err);
if (!encoder || opus_err != OPUS_OK) {
if (pcm_capture_handle) {
snd_pcm_close(pcm_capture_handle);
pcm_capture_handle = NULL;
}
capture_initializing = 0;
return -3;
}
// Configure encoder with optimized settings
opus_encoder_ctl(encoder, OPUS_SET_BITRATE(opus_bitrate));
opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(opus_complexity));
opus_encoder_ctl(encoder, OPUS_SET_VBR(opus_vbr));
opus_encoder_ctl(encoder, OPUS_SET_VBR_CONSTRAINT(opus_vbr_constraint));
opus_encoder_ctl(encoder, OPUS_SET_SIGNAL(opus_signal_type));
opus_encoder_ctl(encoder, OPUS_SET_BANDWIDTH(opus_bandwidth));
opus_encoder_ctl(encoder, OPUS_SET_DTX(opus_dtx));
opus_encoder_ctl(encoder, OPUS_SET_LSB_DEPTH(opus_lsb_depth));
// Enable in-band FEC (Forward Error Correction) for network resilience
// Embeds redundant data in packets to recover from packet loss (adds ~2-5% bitrate overhead)
opus_encoder_ctl(encoder, OPUS_SET_INBAND_FEC(1));
opus_encoder_ctl(encoder, OPUS_SET_PACKET_LOSS_PERC(10)); // Optimize for 10% expected loss
capture_initialized = 1;
capture_initializing = 0;
return 0;
}
/**
* Read HDMI audio, encode to Opus (OUTPUT path hot function)
* Processing pipeline: ALSA capture → 2.5x gain → Opus encode
* @param opus_buf Output buffer for encoded Opus packet
* @return >0 = Opus packet size in bytes, -1 = error
*/
__attribute__((hot)) int jetkvm_audio_read_encode(void * __restrict__ opus_buf) {
// Static buffers persist across calls for better cache locality
static short SIMD_ALIGN pcm_buffer[1920]; // 960 frames × 2 channels
// Local variables
unsigned char * __restrict__ out = (unsigned char*)opus_buf;
int pcm_rc;
int err = 0;
int recovery_attempts = 0;
const int max_recovery_attempts = 3;
int nb_bytes;
// Prefetch output buffer for write
SIMD_PREFETCH(out, 1, 3);
SIMD_PREFETCH(pcm_buffer, 0, 3);
if (__builtin_expect(!capture_initialized || !pcm_capture_handle || !encoder || !opus_buf, 0)) {
if (trace_logging_enabled) {
printf("[AUDIO_OUTPUT] jetkvm_audio_read_encode: Failed safety checks - capture_initialized=%d, pcm_capture_handle=%p, encoder=%p, opus_buf=%p\n",
capture_initialized, pcm_capture_handle, encoder, opus_buf);
}
return -1;
}
retry_read:
// Read 960 frames (20ms) from ALSA capture device
pcm_rc = snd_pcm_readi(pcm_capture_handle, pcm_buffer, frame_size);
if (__builtin_expect(pcm_rc < 0, 0)) {
if (pcm_rc == -EPIPE) {
recovery_attempts++;
if (recovery_attempts > max_recovery_attempts) {
return -1;
}
err = snd_pcm_prepare(pcm_capture_handle);
if (err < 0) {
snd_pcm_drop(pcm_capture_handle);
err = snd_pcm_prepare(pcm_capture_handle);
if (err < 0) return -1;
}
goto retry_read;
} else if (pcm_rc == -EAGAIN) {
return 0;
} else if (pcm_rc == -ESTRPIPE) {
recovery_attempts++;
if (recovery_attempts > max_recovery_attempts) {
return -1;
}
int resume_attempts = 0;
while ((err = snd_pcm_resume(pcm_capture_handle)) == -EAGAIN && resume_attempts < 10) {
usleep(sleep_microseconds);
resume_attempts++;
}
if (err < 0) {
err = snd_pcm_prepare(pcm_capture_handle);
if (err < 0) return -1;
}
return 0;
} else if (pcm_rc == -ENODEV) {
return -1;
} else if (pcm_rc == -EIO) {
recovery_attempts++;
if (recovery_attempts <= max_recovery_attempts) {
snd_pcm_drop(pcm_capture_handle);
err = snd_pcm_prepare(pcm_capture_handle);
if (err >= 0) {
goto retry_read;
}
}
return -1;
} else {
recovery_attempts++;
if (recovery_attempts <= 1 && pcm_rc == -EINTR) {
goto retry_read;
} else if (recovery_attempts <= 1 && pcm_rc == -EBUSY) {
usleep(sleep_microseconds / 2);
goto retry_read;
}
return -1;
}
}
// Zero-pad if we got a short read
if (__builtin_expect(pcm_rc < frame_size, 0)) {
int remaining_samples = (frame_size - pcm_rc) * channels;
simd_clear_samples_s16(&pcm_buffer[pcm_rc * channels], remaining_samples);
}
// Apply 2.5x gain boost to prevent quantization noise at low volumes
// HDMI audio typically transmitted at -6 to -12dB; boost prevents Opus noise floor artifacts
simd_scale_volume_s16(pcm_buffer, frame_size * channels, 2.5f);
// Encode PCM to Opus (20ms frame → ~200 bytes at 96kbps)
nb_bytes = opus_encode(encoder, pcm_buffer, frame_size, out, max_packet_size);
if (trace_logging_enabled && nb_bytes > 0) {
printf("[AUDIO_OUTPUT] jetkvm_audio_read_encode: Successfully encoded %d PCM frames to %d Opus bytes\n", pcm_rc, nb_bytes);
}
return nb_bytes;
}
// ============================================================================
// AUDIO INPUT PATH FUNCTIONS (Client Microphone → Device Speakers)
// ============================================================================
/**
* Initialize INPUT path (Opus decoder → device speakers)
* Opens hw:1,0 (USB gadget) or "default" and creates Opus decoder
* @return 0 on success, -EBUSY if initializing, -1/-2 on errors
*/
int jetkvm_audio_playback_init() {
int err;
simd_init_once();
if (__sync_bool_compare_and_swap(&playback_initializing, 0, 1) == 0) {
return -EBUSY;
}
if (playback_initialized) {
playback_initializing = 0;
return 0;
}
if (decoder) {
opus_decoder_destroy(decoder);
decoder = NULL;
}
if (pcm_playback_handle) {
snd_pcm_close(pcm_playback_handle);
pcm_playback_handle = NULL;
}
err = safe_alsa_open(&pcm_playback_handle, "hw:1,0", SND_PCM_STREAM_PLAYBACK);
if (err < 0) {
err = safe_alsa_open(&pcm_playback_handle, "default", SND_PCM_STREAM_PLAYBACK);
if (err < 0) {
playback_initializing = 0;
return -1;
}
}
err = configure_alsa_device(pcm_playback_handle, "playback");
if (err < 0) {
snd_pcm_close(pcm_playback_handle);
pcm_playback_handle = NULL;
playback_initializing = 0;
return -1;
}
int opus_err = 0;
decoder = opus_decoder_create(sample_rate, channels, &opus_err);
if (!decoder || opus_err != OPUS_OK) {
snd_pcm_close(pcm_playback_handle);
pcm_playback_handle = NULL;
playback_initializing = 0;
return -2;
}
playback_initialized = 1;
playback_initializing = 0;
return 0;
}
/**
* Decode Opus, write to device speakers (INPUT path hot function)
* Processing pipeline: Opus decode (with FEC) → ALSA playback with error recovery
* @param opus_buf Encoded Opus packet from client
* @param opus_size Size of Opus packet in bytes
* @return >0 = PCM frames written, 0 = frame skipped, -1/-2 = error
*/
__attribute__((hot)) int jetkvm_audio_decode_write(void * __restrict__ opus_buf, int opus_size) {
// Static buffer persists across calls for better cache locality
static short SIMD_ALIGN pcm_buffer[1920]; // 960 frames × 2 channels
// Local variables
unsigned char * __restrict__ in = (unsigned char*)opus_buf;
int pcm_frames;
int pcm_rc;
int err = 0;
int recovery_attempts = 0;
const int max_recovery_attempts = 3;
// Prefetch input buffer for read
SIMD_PREFETCH(in, 0, 3);
if (__builtin_expect(!playback_initialized || !pcm_playback_handle || !decoder || !opus_buf || opus_size <= 0, 0)) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Failed safety checks - playback_initialized=%d, pcm_playback_handle=%p, decoder=%p, opus_buf=%p, opus_size=%d\n",
playback_initialized, pcm_playback_handle, decoder, opus_buf, opus_size);
}
return -1;
}
if (opus_size > max_packet_size) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Opus packet too large - size=%d, max=%d\n", opus_size, max_packet_size);
}
return -1;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Processing Opus packet - size=%d bytes\n", opus_size);
}
// Decode Opus packet to PCM (FEC automatically applied if embedded in packet)
// decode_fec=0 means normal decode (FEC data is used automatically when present)
pcm_frames = opus_decode(decoder, in, opus_size, pcm_buffer, frame_size, 0);
if (__builtin_expect(pcm_frames < 0, 0)) {
// Decode failed - attempt packet loss concealment using FEC from previous packet
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Opus decode failed with error %d, attempting packet loss concealment\n", pcm_frames);
}
// decode_fec=1 means use FEC data from the NEXT packet to reconstruct THIS lost packet
pcm_frames = opus_decode(decoder, NULL, 0, pcm_buffer, frame_size, 1);
if (pcm_frames < 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Packet loss concealment also failed with error %d\n", pcm_frames);
}
return -1;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Packet loss concealment succeeded, recovered %d frames\n", pcm_frames);
}
} else if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Opus decode successful - decoded %d PCM frames\n", pcm_frames);
}
retry_write:
// Write decoded PCM to ALSA playback device
pcm_rc = snd_pcm_writei(pcm_playback_handle, pcm_buffer, pcm_frames);
if (__builtin_expect(pcm_rc < 0, 0)) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: ALSA write failed with error %d (%s), attempt %d/%d\n",
pcm_rc, snd_strerror(pcm_rc), recovery_attempts + 1, max_recovery_attempts);
}
if (pcm_rc == -EPIPE) {
recovery_attempts++;
if (recovery_attempts > max_recovery_attempts) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Buffer underrun recovery failed after %d attempts\n", max_recovery_attempts);
}
return -2;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Buffer underrun detected, attempting recovery (attempt %d)\n", recovery_attempts);
}
err = snd_pcm_prepare(pcm_playback_handle);
if (err < 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: snd_pcm_prepare failed (%s), trying drop+prepare\n", snd_strerror(err));
}
snd_pcm_drop(pcm_playback_handle);
err = snd_pcm_prepare(pcm_playback_handle);
if (err < 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: drop+prepare recovery failed (%s)\n", snd_strerror(err));
}
return -2;
}
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Buffer underrun recovery successful, retrying write\n");
}
goto retry_write;
} else if (pcm_rc == -ESTRPIPE) {
recovery_attempts++;
if (recovery_attempts > max_recovery_attempts) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device suspend recovery failed after %d attempts\n", max_recovery_attempts);
}
return -2;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device suspended, attempting resume (attempt %d)\n", recovery_attempts);
}
int resume_attempts = 0;
while ((err = snd_pcm_resume(pcm_playback_handle)) == -EAGAIN && resume_attempts < 10) {
usleep(sleep_microseconds);
resume_attempts++;
}
if (err < 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device resume failed (%s), trying prepare fallback\n", snd_strerror(err));
}
err = snd_pcm_prepare(pcm_playback_handle);
if (err < 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Prepare fallback failed (%s)\n", snd_strerror(err));
}
return -2;
}
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device suspend recovery successful, skipping frame\n");
}
return 0;
} else if (pcm_rc == -ENODEV) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device disconnected (ENODEV) - critical error\n");
}
return -2;
} else if (pcm_rc == -EIO) {
recovery_attempts++;
if (recovery_attempts <= max_recovery_attempts) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: I/O error detected, attempting recovery\n");
}
snd_pcm_drop(pcm_playback_handle);
err = snd_pcm_prepare(pcm_playback_handle);
if (err >= 0) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: I/O error recovery successful, retrying write\n");
}
goto retry_write;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: I/O error recovery failed (%s)\n", snd_strerror(err));
}
}
return -2;
} else if (pcm_rc == -EAGAIN) {
recovery_attempts++;
if (recovery_attempts <= max_recovery_attempts) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device not ready (EAGAIN), waiting and retrying\n");
}
snd_pcm_wait(pcm_playback_handle, sleep_microseconds / 4000);
goto retry_write;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Device not ready recovery failed after %d attempts\n", max_recovery_attempts);
}
return -2;
} else {
recovery_attempts++;
if (recovery_attempts <= 1 && (pcm_rc == -EINTR || pcm_rc == -EBUSY)) {
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Transient error %d (%s), retrying once\n", pcm_rc, snd_strerror(pcm_rc));
}
usleep(sleep_microseconds / 2);
goto retry_write;
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Unrecoverable error %d (%s)\n", pcm_rc, snd_strerror(pcm_rc));
}
return -2;
}
}
if (trace_logging_enabled) {
printf("[AUDIO_INPUT] jetkvm_audio_decode_write: Successfully wrote %d PCM frames to device\n", pcm_frames);
}
return pcm_frames;
}
// ============================================================================
// CLEANUP FUNCTIONS
// ============================================================================
/**
* Close INPUT path (thread-safe with drain)
*/
void jetkvm_audio_playback_close() {
while (playback_initializing) {
usleep(sleep_microseconds);
}
if (__sync_bool_compare_and_swap(&playback_initialized, 1, 0) == 0) {
return;
}
if (decoder) {
opus_decoder_destroy(decoder);
decoder = NULL;
}
if (pcm_playback_handle) {
snd_pcm_drain(pcm_playback_handle);
snd_pcm_close(pcm_playback_handle);
pcm_playback_handle = NULL;
}
}
/**
* Close OUTPUT path (thread-safe with drain)
*/
void jetkvm_audio_capture_close() {
while (capture_initializing) {
usleep(sleep_microseconds);
}
if (__sync_bool_compare_and_swap(&capture_initialized, 1, 0) == 0) {
return;
}
if (encoder) {
opus_encoder_destroy(encoder);
encoder = NULL;
}
if (pcm_capture_handle) {
snd_pcm_drain(pcm_capture_handle);
snd_pcm_close(pcm_capture_handle);
pcm_capture_handle = NULL;
}
}