Description of the problem:
When the app is connected through Bluetooth headsets, I'm starting the audioManager.startBluetoothSco()
. You can check the code below. The connection works well, but I have a problem with the crackling sounds that are played by MediaPlayer. Whenever an action sound is played, the quality is bad or a crackling sound happens. (eg. R.raw.record_start, R.raw.success_action, R.raw.failure_action)
Devices that I used to test:
- OpenComm by AfterShokx and Jabra Evolve 65
- Samsung A52, Samsung A50, Pixel 3, OnePlus 6 Pro
What I've tried so far?
- I tried to change
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
to AudioManager.MODE_IN_CALL or AudioManager.MODE_NORMAL.
- Changing to MODE_IN_CALL doesn't change the
audioManager.mode
. The value stays as 0 which is MODE_NORMAL.
- Changing to MODE_NORMAL only works on Samsung A52. No crackling sounds everything works perfectly. This is what I want to achieve,
- BUT all other phones switch to a different stream or something else; that's why I can't HEAR any media sounds.
- Let's say I use Pixel 3 and connected with Jabra Evolve 65 in MODE_NORMAL. I can't hear any kind of media sounds anymore including other apps like Youtube, Spotify, and system sounds, but it works in MODE_IN_COMMUNICATION with a crackling sound.
I tried to change MediaPlayer's AudioAttributes but still bad quality.
val actionSound = if (triggerErrorEarcon) R.raw.failure_action else if (longRecordingBreak) R.raw.success_action else R.raw.record_start val md = MediaPlayer.create(context, actionSound) val streamType = if (audioDeviceManager.headsetConnected) AudioManager.STREAM_VOICE_CALL else AudioManager.STREAM_MUSIC md.setAudioAttributes( AudioAttributes.Builder() .setLegacyStreamType(streamType) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) md.play()
Possible Solutions?
- I don't know how but fixing
audioManager.mode
or some other properties of audioManager. - Keep using MODE_IN_COMMUNICATION but a fix on MediaPlayer properties to disable bad quality of sound on Bluetooth device.
- I can't think of anything else from this point. I hope you could help me with this problem, thanks in advance.
AudioDeviceManager.kt:
class AudioDeviceManager(val context: Context) {
internal val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
internal val headsetConnectedSubject = BehaviorSubject.createDefault(false)
internal val headsetConnected: Boolean get() = headsetConnectedSubject.value ?: false
private val intentFilter = IntentFilter().apply { addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) }
private val audioDeviceCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesAdded(addedDevices)
updateBluetoothHeadsetState()
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesRemoved(removedDevices)
updateBluetoothHeadsetState()
}
}
private val scoReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1) == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
// SCO now connected
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.isSpeakerphoneOn = false
audioManager.isBluetoothScoOn = true
}
}
}
fun start() {
if (headsetConnected.not())
audioManager.registerAudioDeviceCallback(audioDeviceCallback, null)
}
fun stop(unregisterAudioDeviceCallback: Boolean = false) {
if (unregisterAudioDeviceCallback)
unregisterDeviceCallback()
audioManager.mode = AudioManager.MODE_NORMAL
audioManager.isSpeakerphoneOn = true
audioManager.isBluetoothScoOn = false
audioManager.stopBluetoothSco()
}
private fun unregisterDeviceCallback() = audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
fun updateBluetoothHeadsetState() {
val headset = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)?.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
val headsetConnected = headset != null
headsetConnectedSubject.onNext(headsetConnected)
if (headsetConnected) {
audioManager.startBluetoothSco()
context.registerReceiver(scoReceiver, intentFilter)
} else {
audioManager.mode = AudioManager.MODE_NORMAL
audioManager.isSpeakerphoneOn = true
audioManager.isBluetoothScoOn = false
audioManager.stopBluetoothSco()
}
}
}
Playing an action sound in the app:
MediaPlayer.create(context, R.raw.record_start)
.play()
.subscribe()
.addTo(disposable)
Extension function:
fun MediaPlayer.play(): Completable {
return Completable.create { emitter ->
var isCancelled = false
emitter.setCancellable {
isCancelled = true
}
setOnCompletionListener {
GlobalScope.launch {
// release the sound a bit later. Listener is triggering so fast!
delay(3000)
it.release()
}
if (isCancelled) { return@setOnCompletionListener }
emitter.onComplete()
}
start()
}
}