-
-
Notifications
You must be signed in to change notification settings - Fork 964
Bluetooth mic routing fix #6436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
6d7a9f9
fc4a2cd
757a469
f950461
fc24d6b
4040f30
c081dfa
5e39f18
99574db
74cc0ce
33bcb89
9c76ad4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Other existing imports | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.media.AudioManager; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.telecom.Call; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.bluetooth.BluetoothAdapter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.bluetooth.BluetoothDevice; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Function to detect connected Bluetooth devices | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private AudioDevice getAudioSource() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (device.getType() == BluetoothDevice.DEVICE_TYPE_AUDIO) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // return audio source associated with the Bluetooth device | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return AudioDevice.BLUETOOTH; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback to default audio source if no Bluetooth devices are connected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return AudioDevice.DEFAULT; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import android.media.AudioManager; | |
| import android.telecom.Call; | |
| import android.bluetooth.BluetoothAdapter; | |
| import android.bluetooth.BluetoothDevice; | |
| // Function to detect connected Bluetooth devices | |
| private AudioDevice getAudioSource() { | |
| BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | |
| if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) { | |
| for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { | |
| if (device.getType() == BluetoothDevice.DEVICE_TYPE_AUDIO) { | |
| // return audio source associated with the Bluetooth device | |
| return AudioDevice.BLUETOOTH; | |
| } | |
| } | |
| } | |
| // Fallback to default audio source if no Bluetooth devices are connected | |
| return AudioDevice.DEFAULT; | |
| import android.media.AudioManager | |
| import android.telecom.Call | |
| import android.bluetooth.BluetoothAdapter | |
| import android.bluetooth.BluetoothDevice | |
| private enum class AudioDevice { | |
| BLUETOOTH, | |
| DEFAULT, | |
| } | |
| // Function to detect connected Bluetooth devices | |
| private fun getAudioSource(): AudioDevice { | |
| val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() | |
| if (bluetoothAdapter?.isEnabled == true) { | |
| val bondedDevices: Set<BluetoothDevice>? = bluetoothAdapter.bondedDevices | |
| if (!bondedDevices.isNullOrEmpty()) { | |
| // Return audio source associated with any bonded Bluetooth device | |
| return AudioDevice.BLUETOOTH | |
| } | |
| } | |
| // Fallback to default audio source if no Bluetooth devices are connected | |
| return AudioDevice.DEFAULT |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||
| package io.homeassistant.companion.android.common.util | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import android.annotation.SuppressLint | ||||||||||||||||||||||||||||
| import android.media.AudioDeviceInfo | ||||||||||||||||||||||||||||
| import android.media.AudioFormat | ||||||||||||||||||||||||||||
| import android.media.AudioManager | ||||||||||||||||||||||||||||
| import android.media.AudioManager.OnAudioFocusChangeListener | ||||||||||||||||||||||||||||
|
|
@@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow | |||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||||||||||||||||||||||||||
| import kotlinx.coroutines.isActive | ||||||||||||||||||||||||||||
| import kotlinx.coroutines.launch | ||||||||||||||||||||||||||||
| import android.os.Build | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Wrapper around [AudioRecord] providing pre-configured audio recording functionality. | ||||||||||||||||||||||||||||
|
|
@@ -30,7 +32,6 @@ class AudioRecorder(private val audioManager: AudioManager?) { | |||||||||||||||||||||||||||
| // Docs: only format '[g]uaranteed to be supported by devices' | ||||||||||||||||||||||||||||
| private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private const val AUDIO_SOURCE = AudioSource.MIC | ||||||||||||||||||||||||||||
| private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -50,6 +51,25 @@ class AudioRecorder(private val audioManager: AudioManager?) { | |||||||||||||||||||||||||||
| private var focusRequest: AudioFocusRequestCompat? = null | ||||||||||||||||||||||||||||
| private val focusListener = OnAudioFocusChangeListener { /* Not used */ } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Determine the appropriate audio source based on connected devices. | ||||||||||||||||||||||||||||
| * Prefers Bluetooth SCO if available, falls back to VOICE_COMMUNICATION, | ||||||||||||||||||||||||||||
| * and finally defaults to MIC if neither is available. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| private fun getAudioSource(): Int { | ||||||||||||||||||||||||||||
| if (audioManager == null) { | ||||||||||||||||||||||||||||
| return AudioSource.MIC | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Check if Bluetooth SCO is available | ||||||||||||||||||||||||||||
| return if (audioManager.isBluetoothScoAvailableOffCall()) { | ||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the property instead of the function
Suggested change
|
||||||||||||||||||||||||||||
| // Use VOICE_COMMUNICATION which is the standard for Bluetooth audio | ||||||||||||||||||||||||||||
| AudioSource.VOICE_COMMUNICATION | ||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason to not use that all the time, it might be beneficial to other scenario than only Bluetooth.
|
||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| AudioSource.MIC | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+88
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Start the recorder. After calling this function, data will be available via [audioBytes]. | ||||||||||||||||||||||||||||
| * @throws SecurityException when missing permission to record audio | ||||||||||||||||||||||||||||
|
|
@@ -101,8 +121,9 @@ class AudioRecorder(private val audioManager: AudioManager?) { | |||||||||||||||||||||||||||
| private fun setupRecorder() { | ||||||||||||||||||||||||||||
| if (recorder != null) stopRecording() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| val audioSource = getAudioSource() | ||||||||||||||||||||||||||||
| val bufferSize = minBufferSize() * 10 | ||||||||||||||||||||||||||||
| recorder = AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize) | ||||||||||||||||||||||||||||
| recorder = AudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private fun releaseRecorder() { | ||||||||||||||||||||||||||||
|
|
@@ -114,6 +135,14 @@ class AudioRecorder(private val audioManager: AudioManager?) { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private fun requestFocus() { | ||||||||||||||||||||||||||||
| if (audioManager == null) return | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Enable Bluetooth SCO if available (requires API 11+) | ||||||||||||||||||||||||||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { | ||||||||||||||||||||||||||||
| if (audioManager.isBluetoothScoAvailableOffCall()) { | ||||||||||||||||||||||||||||
| audioManager.startBluetoothSco() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (focusRequest == null) { | ||||||||||||||||||||||||||||
| focusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE).run { | ||||||||||||||||||||||||||||
| setAudioAttributes( | ||||||||||||||||||||||||||||
|
|
@@ -138,7 +167,15 @@ class AudioRecorder(private val audioManager: AudioManager?) { | |||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private fun abandonFocus() { | ||||||||||||||||||||||||||||
| if (audioManager == null || focusRequest == null) return | ||||||||||||||||||||||||||||
| AudioManagerCompat.abandonAudioFocusRequest(audioManager, focusRequest!!) | ||||||||||||||||||||||||||||
| if (audioManager == null) return | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Disable Bluetooth SCO (requires API 11+) | ||||||||||||||||||||||||||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { | ||||||||||||||||||||||||||||
| audioManager.stopBluetoothSco() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| // Disable Bluetooth SCO (requires API 11+) | |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { | |
| audioManager.stopBluetoothSco() | |
| } | |
| // Disable Bluetooth SCO (requires API 11+) | |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && | |
| audioManager.isBluetoothScoAvailableOffCall() | |
| ) { | |
| audioManager.stopBluetoothSco() | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this file.