fix(bluetooth): use Shared mode for Oboe + delay restart for BT route
Two fixes for BT audio silence: 1. Switch Oboe streams from Exclusive to Shared sharing mode. Exclusive mode bypasses Oboe's internal resampler, so opening a 48kHz stream against a BT SCO device (8/16kHz only) fails at the AudioPolicy level. Shared mode lets Oboe's resampler bridge the gap. 2. Add 500ms post-SCO delay before Oboe restart. The audio policy needs time to apply the bt-sco route after setCommunicationDevice returns. Without the delay, Oboe opens against the old device (handset). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -254,16 +254,15 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
|||||||
oboe::AudioStreamBuilder captureBuilder;
|
oboe::AudioStreamBuilder captureBuilder;
|
||||||
captureBuilder.setDirection(oboe::Direction::Input)
|
captureBuilder.setDirection(oboe::Direction::Input)
|
||||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||||
->setSharingMode(oboe::SharingMode::Exclusive)
|
// Shared mode allows Oboe's internal resampler to bridge 48kHz to
|
||||||
|
// the hardware rate (8/16kHz for BT SCO). Exclusive mode bypasses
|
||||||
|
// the resampler and fails with "getInputProfile could not find profile".
|
||||||
|
->setSharingMode(oboe::SharingMode::Shared)
|
||||||
->setFormat(oboe::AudioFormat::I16)
|
->setFormat(oboe::AudioFormat::I16)
|
||||||
->setChannelCount(config->channel_count)
|
->setChannelCount(config->channel_count)
|
||||||
->setSampleRate(config->sample_rate)
|
->setSampleRate(config->sample_rate)
|
||||||
->setFramesPerDataCallback(config->frames_per_burst)
|
->setFramesPerDataCallback(config->frames_per_burst)
|
||||||
->setInputPreset(oboe::InputPreset::VoiceCommunication)
|
->setInputPreset(oboe::InputPreset::VoiceCommunication)
|
||||||
// Bluetooth SCO only supports 8/16kHz. Without resampling, opening
|
|
||||||
// a 48kHz capture stream against a BT device fails with
|
|
||||||
// "getInputProfile could not find profile". Oboe resamples internally
|
|
||||||
// so our ring buffers stay at 48kHz regardless of the hardware rate.
|
|
||||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Best)
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Best)
|
||||||
->setDataCallback(&g_capture_cb);
|
->setDataCallback(&g_capture_cb);
|
||||||
|
|
||||||
@@ -319,13 +318,12 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
|||||||
oboe::AudioStreamBuilder playoutBuilder;
|
oboe::AudioStreamBuilder playoutBuilder;
|
||||||
playoutBuilder.setDirection(oboe::Direction::Output)
|
playoutBuilder.setDirection(oboe::Direction::Output)
|
||||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||||
->setSharingMode(oboe::SharingMode::Exclusive)
|
->setSharingMode(oboe::SharingMode::Shared)
|
||||||
->setFormat(oboe::AudioFormat::I16)
|
->setFormat(oboe::AudioFormat::I16)
|
||||||
->setChannelCount(config->channel_count)
|
->setChannelCount(config->channel_count)
|
||||||
->setSampleRate(config->sample_rate)
|
->setSampleRate(config->sample_rate)
|
||||||
->setFramesPerDataCallback(config->frames_per_burst)
|
->setFramesPerDataCallback(config->frames_per_burst)
|
||||||
->setUsage(oboe::Usage::VoiceCommunication)
|
->setUsage(oboe::Usage::VoiceCommunication)
|
||||||
// Match capture: Oboe resamples 48kHz ↔ device rate (8/16kHz for BT SCO)
|
|
||||||
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Best)
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Best)
|
||||||
->setDataCallback(&g_playout_cb);
|
->setDataCallback(&g_playout_cb);
|
||||||
|
|
||||||
|
|||||||
@@ -794,7 +794,7 @@ async fn set_bluetooth_sco(on: bool) -> Result<(), String> {
|
|||||||
// startBluetoothSco() is async — jumping straight to Oboe restart
|
// startBluetoothSco() is async — jumping straight to Oboe restart
|
||||||
// would open streams against earpiece, not the BT device.
|
// would open streams against earpiece, not the BT device.
|
||||||
let mut connected = false;
|
let mut connected = false;
|
||||||
for i in 0..30 {
|
for i in 0..50 {
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
if android_audio::is_bluetooth_sco_on().unwrap_or(false) {
|
if android_audio::is_bluetooth_sco_on().unwrap_or(false) {
|
||||||
tracing::info!(polls = i + 1, "set_bluetooth_sco: SCO connected");
|
tracing::info!(polls = i + 1, "set_bluetooth_sco: SCO connected");
|
||||||
@@ -803,8 +803,12 @@ async fn set_bluetooth_sco(on: bool) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !connected {
|
if !connected {
|
||||||
tracing::warn!("set_bluetooth_sco: SCO did not connect within 3s, proceeding anyway");
|
tracing::warn!("set_bluetooth_sco: SCO did not connect within 5s, proceeding anyway");
|
||||||
}
|
}
|
||||||
|
// Extra delay: even after getCommunicationDevice reports BT,
|
||||||
|
// the audio policy needs ~500ms to apply the bt-sco route.
|
||||||
|
// Without this, Oboe opens against the old device.
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
} else {
|
} else {
|
||||||
android_audio::stop_bluetooth_sco()?;
|
android_audio::stop_bluetooth_sco()?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user