I’m working on an Android app that includes hands-free voice interaction using the SpeechRecognizer API. It must be compatible from SDK 23 to 35.
In most usage scenarios, the app runs outdoors with Bluetooth headphones, so I’ve implemented Bluetooth audio profile switching (SCO) to ensure that the microphone used is the Bluetooth one.
🎧 The issue
On some devices (e.g. Google Pixel 9), when I switch to the SCO profile and start the speech recognizer after SCO is confirmed active, I notice:
The start sound (beep) played by the system when startListening() is called is either inaudible or very low
On other devices, the sound does play but with low volume, especially in SCO mode
I’ve tried increasing the voice call stream volume before triggering speech recognition, but it didn’t help.
🧪 What I’ve implemented
🔁 Bluetooth audio profile switching (simplified):
public void requestBluetoothMicRouting(@NonNull AudioManager audioManager, @NonNull BluetoothSCOAudioStateListener listener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
boolean result = selectBluetoothMic(audioManager);
if (!result) {
listener.onBluetoothSCOAudioStateChanged(AudioManager.SCO_AUDIO_STATE_ERROR);
}
} else {
audioManager.startBluetoothSco();
}
}
@RequiresApi(api = Build.VERSION_CODES.S)
private boolean selectBluetoothMic(@NonNull AudioManager audioManager) {
for (AudioDeviceInfo deviceInfo : audioManager.getAvailableCommunicationDevices()) {
if (deviceInfo.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
return audioManager.setCommunicationDevice(deviceInfo);
}
}
return false;
}
I wait for ACTION_SCO_AUDIO_STATE_UPDATED to confirm SCO activation before starting the recognizer.
🗣️ Starting speech recognition:
if (BluetoothHeadsetHelper.getInstance().hasBluetoothHeadphonesConnected()) {
BluetoothHeadsetHelper.getInstance().requestBluetoothMicRouting(audioManager, state -> {
if (state != AudioManager.SCO_AUDIO_STATE_ERROR) {
MySpeechRecognizer.getInstance().startListening(Locale.getDefault().toLanguageTag());
}
});
} else {
MySpeechRecognizer.getInstance().startListening(Locale.getDefault().toLanguageTag());
}
🧠 The recognizer implementation:
public void startListening(String language) {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
speechRecognizer.startListening(intent);
}
📈 What I’ve tried
Setting the STREAM_VOICE_CALL volume to maximum:
audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL), 0);
Playing my own sound using MediaPlayer on the VOICE_CALL stream in onReadyForSpeech → sound plays, but is barely audible in SCO mode
Reversing the order: calling startListening() before switching audio profile → doesn’t help
❓ My question
What causes the system start sound to be missing or barely audible when using Bluetooth SCO audio routing?
Is there a reliable way to ensure the speech recognizer start sound is clearly audible across devices, or to replace it with a custom one without overlap?
Thanks a lot for any guidance! I'm open to workarounds or internal tricks, as long as they work across most Android versions.
setCommunicationDeviceon SDK 31+ orstartBluetoothScoon earlier versions), the system prioritizes it for voice communication, such as calls or speech recognition. However, system sounds like the SpeechRecognizer's start beep are typically played on the STREAM_SYSTEM or STREAM_NOTIFICATION audio stream by default, not STREAM_VOICE_CALL. SCO routing is optimized for low-latency voice input/output, which can inadvertently suppress or reroute non-voice streams to prevent interference.