AudioManager MODE_IN_COMMUNICATION decreases the sound quality and causing crackling/popping sound

483 Views Asked by At

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?

  1. 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.
  1. 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.
  1. 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()
    }
}
0

There are 0 best solutions below