mirror of https://github.com/jetkvm/kvm.git
Fix critical audio race conditions and align configuration defaults
Critical Fixes: - Fix race condition in handleInputTrackForSession by reloading source inside mutex - Fix ALSA handle cleanup atomicity (nullify before close to prevent use-after-free) - Bounds check for opus buffer already present (verified) Configuration Alignment: - Align audio bitrate default to 192 kbps across all layers (C, Go defaults, config) - Align audio complexity default to 8 across all layers - Align DTX default to enabled (true/1) across all layers for bandwidth efficiency Documentation Improvements: - Update C header comment to reflect accurate 192 kbps default - Clarify NEON requirement (not just "always available") - Fix ALSA device mapping comments to reflect environment variable usage - Document fallback behavior in playback init Code Quality: - Add validation logging for out-of-range audio configuration values - Improve error visibility for configuration issues All changes thoroughly analyzed before implementation.
This commit is contained in:
parent
da4c6c70d2
commit
c88b98c1f0
28
audio.go
28
audio.go
|
|
@ -51,20 +51,30 @@ func getAudioConfig() audio.AudioConfig {
|
|||
cfg := audio.DefaultAudioConfig()
|
||||
if config.AudioBitrate >= 64 && config.AudioBitrate <= 256 {
|
||||
cfg.Bitrate = uint16(config.AudioBitrate)
|
||||
} else if config.AudioBitrate != 0 {
|
||||
audioLogger.Warn().Int("bitrate", config.AudioBitrate).Uint16("default", cfg.Bitrate).Msg("Invalid audio bitrate, using default")
|
||||
}
|
||||
if config.AudioComplexity >= 0 && config.AudioComplexity <= 10 {
|
||||
cfg.Complexity = uint8(config.AudioComplexity)
|
||||
} else {
|
||||
audioLogger.Warn().Int("complexity", config.AudioComplexity).Uint8("default", cfg.Complexity).Msg("Invalid audio complexity, using default")
|
||||
}
|
||||
cfg.DTXEnabled = config.AudioDTXEnabled
|
||||
cfg.FECEnabled = config.AudioFECEnabled
|
||||
if config.AudioBufferPeriods >= 2 && config.AudioBufferPeriods <= 24 {
|
||||
cfg.BufferPeriods = uint8(config.AudioBufferPeriods)
|
||||
} else if config.AudioBufferPeriods != 0 {
|
||||
audioLogger.Warn().Int("buffer_periods", config.AudioBufferPeriods).Uint8("default", cfg.BufferPeriods).Msg("Invalid buffer periods, using default")
|
||||
}
|
||||
if config.AudioSampleRate == 32000 || config.AudioSampleRate == 44100 || config.AudioSampleRate == 48000 || config.AudioSampleRate == 96000 {
|
||||
cfg.SampleRate = uint32(config.AudioSampleRate)
|
||||
} else if config.AudioSampleRate != 0 {
|
||||
audioLogger.Warn().Int("sample_rate", config.AudioSampleRate).Uint32("default", cfg.SampleRate).Msg("Invalid sample rate, using default")
|
||||
}
|
||||
if config.AudioPacketLossPerc >= 0 && config.AudioPacketLossPerc <= 100 {
|
||||
cfg.PacketLossPerc = uint8(config.AudioPacketLossPerc)
|
||||
} else {
|
||||
audioLogger.Warn().Int("packet_loss_perc", config.AudioPacketLossPerc).Uint8("default", cfg.PacketLossPerc).Msg("Invalid packet loss percentage, using default")
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
|
@ -307,12 +317,19 @@ func handleInputTrackForSession(track *webrtc.TrackRemote) {
|
|||
continue
|
||||
}
|
||||
|
||||
source := inputSource.Load()
|
||||
if source == nil {
|
||||
// Early check to avoid mutex acquisition if source is nil (optimization)
|
||||
if inputSource.Load() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
inputSourceMutex.Lock()
|
||||
// Reload source inside mutex to ensure we have the currently active source
|
||||
// This prevents races with startInputAudioUnderMutex swapping the source
|
||||
source := inputSource.Load()
|
||||
if source == nil {
|
||||
inputSourceMutex.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
if !(*source).IsConnected() {
|
||||
if err := (*source).Connect(); err != nil {
|
||||
|
|
@ -321,11 +338,12 @@ func handleInputTrackForSession(track *webrtc.TrackRemote) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := (*source).WriteMessage(0, opusData); err != nil {
|
||||
err = (*source).WriteMessage(0, opusData)
|
||||
inputSourceMutex.Unlock()
|
||||
|
||||
if err != nil {
|
||||
audioLogger.Warn().Err(err).Msg("failed to write audio message")
|
||||
(*source).Disconnect()
|
||||
}
|
||||
|
||||
inputSourceMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,8 +193,8 @@ func getDefaultConfig() Config {
|
|||
AudioInputAutoEnable: false,
|
||||
AudioOutputEnabled: true,
|
||||
AudioOutputSource: "usb",
|
||||
AudioBitrate: 128,
|
||||
AudioComplexity: 5,
|
||||
AudioBitrate: 192,
|
||||
AudioComplexity: 8,
|
||||
AudioDTXEnabled: true,
|
||||
AudioFECEnabled: true,
|
||||
AudioBufferPeriods: 12,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Bidirectional audio processing optimized for ARM NEON SIMD:
|
||||
* - OUTPUT PATH: TC358743 HDMI or USB Gadget audio → Client speakers
|
||||
* Pipeline: ALSA hw:0,0 or hw:1,0 capture → Opus encode (128kbps, FEC enabled)
|
||||
* Pipeline: ALSA hw:0,0 or hw:1,0 capture → Opus encode (192kbps, FEC enabled)
|
||||
*
|
||||
* - INPUT PATH: Client microphone → Device speakers
|
||||
* Pipeline: Opus decode (with FEC) → ALSA hw:1,0 playback
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
|
||||
// ARM NEON SIMD support (always available on JetKVM's ARM Cortex-A7)
|
||||
// ARM NEON SIMD support (required - JetKVM hardware provides ARM Cortex-A7 with NEON)
|
||||
#include <arm_neon.h>
|
||||
|
||||
// RV1106 (Cortex-A7) has 64-byte cache lines
|
||||
|
|
@ -60,7 +60,7 @@ static uint16_t max_packet_size = 1500;
|
|||
#define OPUS_BANDWIDTH 1104
|
||||
#define OPUS_LSB_DEPTH 16
|
||||
|
||||
static uint8_t opus_dtx_enabled = 0;
|
||||
static uint8_t opus_dtx_enabled = 1;
|
||||
static uint8_t opus_fec_enabled = 1;
|
||||
static uint8_t opus_packet_loss_perc = 0;
|
||||
static uint8_t buffer_period_count = 24;
|
||||
|
|
@ -98,7 +98,7 @@ void update_audio_constants(uint32_t bitrate, uint8_t complexity,
|
|||
uint32_t sr, uint8_t ch, uint16_t fs, uint16_t max_pkt,
|
||||
uint32_t sleep_us, uint8_t max_attempts, uint32_t max_backoff,
|
||||
uint8_t dtx_enabled, uint8_t fec_enabled, uint8_t buf_periods, uint8_t pkt_loss_perc) {
|
||||
opus_bitrate = (bitrate >= 64000 && bitrate <= 256000) ? bitrate : 128000;
|
||||
opus_bitrate = (bitrate >= 64000 && bitrate <= 256000) ? bitrate : 192000;
|
||||
opus_complexity = (complexity <= 10) ? complexity : 5;
|
||||
sample_rate = sr > 0 ? sr : 48000;
|
||||
capture_channels = (ch == 1 || ch == 2) ? ch : 2;
|
||||
|
|
@ -429,8 +429,9 @@ static int configure_alsa_device(snd_pcm_t *handle, const char *device_name, uin
|
|||
// 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
|
||||
* 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)
|
||||
* and creates Opus encoder with optimized settings
|
||||
* @return 0 on success, -EBUSY if initializing, -1/-2/-3 on errors
|
||||
*/
|
||||
int jetkvm_audio_capture_init() {
|
||||
|
|
@ -484,8 +485,9 @@ int jetkvm_audio_capture_init() {
|
|||
uint16_t actual_frame_size = 0;
|
||||
err = configure_alsa_device(pcm_capture_handle, "capture", capture_channels, &actual_rate, &actual_frame_size);
|
||||
if (err < 0) {
|
||||
snd_pcm_close(pcm_capture_handle);
|
||||
snd_pcm_t *handle = pcm_capture_handle;
|
||||
pcm_capture_handle = NULL;
|
||||
snd_pcm_close(handle);
|
||||
capture_stop_requested = 0;
|
||||
capture_initializing = 0;
|
||||
return -2;
|
||||
|
|
@ -499,8 +501,9 @@ int jetkvm_audio_capture_init() {
|
|||
encoder = opus_encoder_create(actual_rate, capture_channels, OPUS_APPLICATION_AUDIO, &opus_err);
|
||||
if (!encoder || opus_err != OPUS_OK) {
|
||||
if (pcm_capture_handle) {
|
||||
snd_pcm_close(pcm_capture_handle);
|
||||
snd_pcm_t *handle = pcm_capture_handle;
|
||||
pcm_capture_handle = NULL;
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
capture_stop_requested = 0;
|
||||
capture_initializing = 0;
|
||||
|
|
@ -613,7 +616,8 @@ retry_read:
|
|||
|
||||
/**
|
||||
* Initialize INPUT path (Opus decoder → device speakers)
|
||||
* Opens hw:1,0 (USB gadget) or "default" and creates Opus decoder
|
||||
* Opens ALSA playback device from ALSA_PLAYBACK_DEVICE env (default: hw:1,0), falls back to "default" on error
|
||||
* and creates Opus decoder
|
||||
* @return 0 on success, -EBUSY if initializing, -1/-2 on errors
|
||||
*/
|
||||
int jetkvm_audio_playback_init() {
|
||||
|
|
@ -670,8 +674,9 @@ int jetkvm_audio_playback_init() {
|
|||
uint16_t actual_frame_size = 0;
|
||||
err = configure_alsa_device(pcm_playback_handle, "playback", playback_channels, &actual_rate, &actual_frame_size);
|
||||
if (err < 0) {
|
||||
snd_pcm_close(pcm_playback_handle);
|
||||
snd_pcm_t *handle = pcm_playback_handle;
|
||||
pcm_playback_handle = NULL;
|
||||
snd_pcm_close(handle);
|
||||
playback_stop_requested = 0;
|
||||
playback_initializing = 0;
|
||||
return -1;
|
||||
|
|
@ -684,8 +689,9 @@ int jetkvm_audio_playback_init() {
|
|||
int opus_err = 0;
|
||||
decoder = opus_decoder_create(actual_rate, playback_channels, &opus_err);
|
||||
if (!decoder || opus_err != OPUS_OK) {
|
||||
snd_pcm_close(pcm_playback_handle);
|
||||
snd_pcm_t *handle = pcm_playback_handle;
|
||||
pcm_playback_handle = NULL;
|
||||
snd_pcm_close(handle);
|
||||
playback_stop_requested = 0;
|
||||
playback_initializing = 0;
|
||||
return -2;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func DefaultAudioConfig() AudioConfig {
|
|||
Bitrate: 192,
|
||||
Complexity: 8,
|
||||
BufferPeriods: 12,
|
||||
DTXEnabled: false,
|
||||
DTXEnabled: true,
|
||||
FECEnabled: true,
|
||||
SampleRate: 48000,
|
||||
PacketLossPerc: 0,
|
||||
|
|
|
|||
Loading…
Reference in New Issue