fix(bluetooth): BT SCO mode skips 48kHz + VoiceCommunication on capture
Root cause: Oboe capture at 48kHz with InputPreset::VoiceCommunication cannot open against a BT SCO device (only supports 8/16kHz). The stream silently falls back to builtin mic, delivering zeros. Fix: add bt_active flag to WzpOboeConfig. When set, capture skips setSampleRate and setInputPreset, letting the system route to BT SCO at its native rate. Oboe's SampleRateConversionQuality::Best resamples to 48kHz for our ring buffers. Playout uses Usage::Media in BT mode. New API: wzp_native_audio_start_bt() for BT mode, called from set_bluetooth_sco(on=true). Normal audio_start() restores the standard config when switching back to earpiece/speaker. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -814,10 +814,18 @@ async fn set_bluetooth_sco(on: bool) -> Result<(), String> {
|
||||
}
|
||||
if wzp_native::is_loaded() && wzp_native::audio_is_running() {
|
||||
tracing::info!(on, "set_bluetooth_sco: restarting Oboe for route change");
|
||||
tokio::task::spawn_blocking(|| {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
wzp_native::audio_stop();
|
||||
wzp_native::audio_start()
|
||||
.map_err(|code| format!("audio_start after BT toggle: code {code}"))
|
||||
if on {
|
||||
// BT mode: skip sample rate + input preset on capture
|
||||
// so the system can route to the BT SCO device natively.
|
||||
wzp_native::audio_start_bt()
|
||||
.map_err(|code| format!("audio_start_bt after BT on: code {code}"))
|
||||
} else {
|
||||
// Normal mode: restore 48kHz + VoiceCommunication preset.
|
||||
wzp_native::audio_start()
|
||||
.map_err(|code| format!("audio_start after BT off: code {code}"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking join: {e}"))??;
|
||||
|
||||
@@ -26,6 +26,7 @@ static LIB: OnceLock<libloading::Library> = OnceLock::new();
|
||||
static VERSION: OnceLock<unsafe extern "C" fn() -> i32> = OnceLock::new();
|
||||
static HELLO: OnceLock<unsafe extern "C" fn(*mut u8, usize) -> usize> = OnceLock::new();
|
||||
static AUDIO_START: OnceLock<unsafe extern "C" fn() -> i32> = OnceLock::new();
|
||||
static AUDIO_START_BT: OnceLock<unsafe extern "C" fn() -> i32> = OnceLock::new();
|
||||
static AUDIO_STOP: OnceLock<unsafe extern "C" fn()> = OnceLock::new();
|
||||
static AUDIO_READ_CAPTURE: OnceLock<unsafe extern "C" fn(*mut i16, usize) -> usize> = OnceLock::new();
|
||||
static AUDIO_WRITE_PLAYOUT: OnceLock<unsafe extern "C" fn(*const i16, usize) -> usize> = OnceLock::new();
|
||||
@@ -65,6 +66,7 @@ pub fn init() -> Result<(), String> {
|
||||
resolve!(VERSION, unsafe extern "C" fn() -> i32, b"wzp_native_version");
|
||||
resolve!(HELLO, unsafe extern "C" fn(*mut u8, usize) -> usize, b"wzp_native_hello");
|
||||
resolve!(AUDIO_START, unsafe extern "C" fn() -> i32, b"wzp_native_audio_start");
|
||||
resolve!(AUDIO_START_BT, unsafe extern "C" fn() -> i32, b"wzp_native_audio_start_bt");
|
||||
resolve!(AUDIO_STOP, unsafe extern "C" fn(), b"wzp_native_audio_stop");
|
||||
resolve!(AUDIO_READ_CAPTURE, unsafe extern "C" fn(*mut i16, usize) -> usize, b"wzp_native_audio_read_capture");
|
||||
resolve!(AUDIO_WRITE_PLAYOUT, unsafe extern "C" fn(*const i16, usize) -> usize, b"wzp_native_audio_write_playout");
|
||||
@@ -104,6 +106,14 @@ pub fn audio_start() -> Result<(), i32> {
|
||||
if ret == 0 { Ok(()) } else { Err(ret) }
|
||||
}
|
||||
|
||||
/// Start Oboe in Bluetooth SCO mode — capture skips sample rate and
|
||||
/// input preset so the system routes to the BT SCO device natively.
|
||||
pub fn audio_start_bt() -> Result<(), i32> {
|
||||
let f = AUDIO_START_BT.get().ok_or(-100_i32)?;
|
||||
let ret = unsafe { f() };
|
||||
if ret == 0 { Ok(()) } else { Err(ret) }
|
||||
}
|
||||
|
||||
/// Stop both streams. Safe to call even if not running.
|
||||
pub fn audio_stop() {
|
||||
if let Some(f) = AUDIO_STOP.get() {
|
||||
|
||||
Reference in New Issue
Block a user