0

在我的应用程序中,我创建了一个 WebRtcAudioManager 来处理用户可用的多个输出,以便他可以重定向他选择的声音。我创建的经理类如下。

class WebRtcAudioManagerImpl(private val appContext: Context) : WebRtcAudioManager {

    /**
     * AudioDevice is the names of possible audio devices that we currently
     * support.
     */
    enum class AudioDevice {
        SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE
    }

    /** AudioManager state.  */
    enum class AudioManagerState {
        UNINITIALIZED, PREINITIALIZED, RUNNING
    }

    /** Selected audio device change event.  */
    interface AudioManagerEvents {
        // Callback fired once audio device is changed or list of available audio devices changed.
        fun onAudioDeviceChanged(
            selectedAudioDevice: AudioDevice?, availableAudioDevices: Set<AudioDevice?>?
        )
    }

    private var audioManager: AudioManager

    @Nullable
    private var audioManagerEvents: AudioManagerEvents? = null
    private var amState: AudioManagerState
    private var savedAudioMode = AudioManager.MODE_INVALID
    private var savedIsSpeakerPhoneOn = false
    private var savedIsMicrophoneMute = false
    private var hasWiredHeadset = false

    // Default audio device; speaker phone for video calls or earpiece for audio
    // only calls.
    private var defaultAudioDevice: AudioDevice? = null

    // Contains the currently selected audio device.
    // This device is changed automatically using a certain scheme where e.g.
    // a wired headset "wins" over speaker phone. It is also possible for a
    // user to explicitly select a device (and override any predefined scheme).
    // See |userSelectedAudioDevice| for details.
    private var selectedAudioDevice: AudioDevice? = null

    // Contains the user-selected audio device which overrides the predefined
    // selection scheme.
    private var userSelectedAudioDevice: AudioDevice? = null

    private var mBluetoothAdapter: BluetoothAdapter? = null

    // Contains speakerphone setting: auto, true or false
    @Nullable
    private val useSpeakerphone: String?


    // Contains a list of available audio devices. A Set collection is used to
    // avoid duplicate elements.
    private var audioDevices: MutableSet<AudioDevice?> = HashSet()
    private val _audioDevices = MutableLiveData<List<AudioDevice?>>(emptyList())
    override val devices: LiveData<List<AudioDevice?>>
        get() = _audioDevices

    // Broadcast receiver for wired headset intent broadcasts.
    private val wiredHeadsetReceiver: BroadcastReceiver

    // Broadcast receiver for bluetooth headset intent broadcasts.
    private val bluetoothReceiver: BroadcastReceiver

    // Callback method for changes in audio focus.
    @Nullable
    private var audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null


    /* Receiver which handles changes in wired headset availability. */
    private inner class WiredHeadsetReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            val state = intent.getIntExtra("state", STATE_UNPLUGGED)
            val microphone = intent.getIntExtra("microphone", HAS_NO_MIC)
            val name = intent.getStringExtra("name")
            Log.d(
                TAG, "WiredHeadsetReceiver.onReceive"
                        + ": " + "a=" + intent.action.toString() + ", s=" +
                        (if (state == STATE_UNPLUGGED) "unplugged" else "plugged").toString()
                        + ", m=" + (if (microphone == HAS_MIC) "mic" else "no mic").toString()
                        + ", n=" + name.toString() + ", sb=" + isInitialStickyBroadcast
            )
            hasWiredHeadset = (state == STATE_PLUGGED)
            updateAudioDeviceState()
        }

        private val STATE_UNPLUGGED = 0
        private val STATE_PLUGGED = 1
        private val HAS_NO_MIC = 0
        private val HAS_MIC = 1
    }

    var mBluetoothA2dp: BluetoothA2dp? = null
    private val mProfileListener: BluetoothProfile.ServiceListener =
        object : BluetoothProfile.ServiceListener {
            override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
                if (profile == BluetoothProfile.A2DP) {
                    updateAudioDeviceState()
                }
            }

            override fun onServiceDisconnected(profile: Int) {
                if (profile == BluetoothProfile.A2DP) {
                    mBluetoothA2dp = null
                }
                updateAudioDeviceState()
            }
        }

    private inner class BluetoothHeadsetReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent) {
            val action = intent.action
            val state: Int

            if (action == BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED) {
                state = intent.getIntExtra(
                    BluetoothHeadset.EXTRA_STATE,
                    BluetoothHeadset.STATE_DISCONNECTED
                )
                if (state == BluetoothHeadset.STATE_CONNECTED) {
                    updateAudioDeviceState()
                } else if (state == BluetoothHeadset.STATE_DISCONNECTED) {
                    updateAudioDeviceState()
                }
            } else if (action == BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED) {
                updateAudioDeviceState()
            }
        }
    }

    override fun start(audioManagerEvents: AudioManagerEvents?) {
        Log.d(TAG, "start")
        ThreadUtils.checkIsOnMainThread()
        if (amState == AudioManagerState.RUNNING) {
            Log.e(TAG, "AudioManager is already active")
            return
        }

        this.audioManagerEvents = audioManagerEvents
        amState = AudioManagerState.RUNNING

        // Get the default adapter
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        // Establish connection to the proxy.
        mBluetoothAdapter?.getProfileProxy(appContext, mProfileListener, BluetoothProfile.A2DP)

        // Store current audio state so we can restore it when stop() is called.
        savedAudioMode = audioManager.mode
        savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn
        savedIsMicrophoneMute = audioManager.isMicrophoneMute
        hasWiredHeadset = hasWiredHeadset()

        // Create an AudioManager.OnAudioFocusChangeListener instance.
        audioFocusChangeListener =
            AudioManager.OnAudioFocusChangeListener { focusChange ->

                // Called on the listener to notify if the audio focus for this listener has been changed.
                // The |focusChange| value indicates whether the focus was gained, whether the focus was lost,
                // and whether that loss is transient, or whether the new focus holder will hold it for an
                // unknown amount of time.
                val typeOfChange = when (focusChange) {
                    AudioManager.AUDIOFOCUS_GAIN -> "AUDIOFOCUS_GAIN"
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> "AUDIOFOCUS_GAIN_TRANSIENT"
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE"
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK -> "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK"
                    AudioManager.AUDIOFOCUS_LOSS -> "AUDIOFOCUS_LOSS"
                    AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> "AUDIOFOCUS_LOSS_TRANSIENT"
                    AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"
                    else -> "AUDIOFOCUS_INVALID"
                }
                Log.d(TAG, "onAudioFocusChange: $typeOfChange")
            }

        // Request audio play out focus (without ducking) and install listener for changes in focus.
        val result = audioManager.requestAudioFocus(
            audioFocusChangeListener,
            AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
        )
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            Log.d(TAG, "Audio focus request granted for VOICE_CALL streams")
        } else {
            Log.e(TAG, "Audio focus request failed")
        }

        // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
        // required to be in this mode when playout and/or recording starts for
        // best possible VoIP performance.
        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION

        // Always disable microphone mute during a WebRTC call.
        setMicrophoneMute(false)

        // Set initial device states.
        userSelectedAudioDevice = AudioDevice.NONE
        selectedAudioDevice = AudioDevice.NONE
        audioDevices.clear()

        // Do initial selection of audio device. This setting can later be changed
        // either by adding/removing a BT or wired headset or by covering/uncovering
        // the proximity sensor.
        updateAudioDeviceState()

        // Register receiver for broadcast intents related to adding/removing a
        // wired headset.
        registerReceiver(wiredHeadsetReceiver, IntentFilter(Intent.ACTION_HEADSET_PLUG))
        registerReceiver(
            bluetoothReceiver,
            IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
        )
        registerReceiver(
            bluetoothReceiver,
            IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)
        )
        Log.d(TAG, "AudioManager started")
    }

    override fun stop() {
        Log.d(TAG, "stop")
        ThreadUtils.checkIsOnMainThread()
        if (amState != AudioManagerState.RUNNING) {
            Log.e(
                TAG,
                "Trying to stop AudioManager in incorrect state: $amState"
            )
            return
        }
        amState = AudioManagerState.UNINITIALIZED
        unregisterReceiver(wiredHeadsetReceiver)
        unregisterReceiver(bluetoothReceiver)

        // Restore previously stored audio states.
        setSpeakerphoneOn(savedIsSpeakerPhoneOn)
        setMicrophoneMute(savedIsMicrophoneMute)
        // audioManager.mode = savedAudioMode

        // Abandon audio focus. Gives the previous focus owner, if any, focus.
        audioManager.abandonAudioFocus(audioFocusChangeListener)
        audioFocusChangeListener = null
        Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams")

        audioManagerEvents = null
        Log.d(TAG, "AudioManager stopped")
    }

    /** Changes selection of the currently active audio device.  */
    private fun setAudioDeviceInternal(device: AudioDevice?) {
        Log.d(TAG, "setAudioDeviceInternal(device=$device)")
        if (audioDevices.contains(device)) {
            when (device) {
                AudioDevice.SPEAKER_PHONE -> setSpeakerphoneOn(true)
                AudioDevice.EARPIECE -> setSpeakerphoneOn(false)
                AudioDevice.WIRED_HEADSET -> setSpeakerphoneOn(false)
                AudioDevice.BLUETOOTH -> setSpeakerphoneOn(false)
                else -> Log.e(TAG, "Invalid audio device selection")
            }
        }
        selectedAudioDevice = device
    }

    /**
     * Changes default audio device.
     */
    override fun setDefaultAudioDevice(defaultDevice: AudioDevice?) {
        ThreadUtils.checkIsOnMainThread()
        when (defaultDevice) {
            AudioDevice.BLUETOOTH -> defaultAudioDevice = defaultDevice
            AudioDevice.SPEAKER_PHONE -> defaultAudioDevice = defaultDevice
            AudioDevice.EARPIECE -> defaultAudioDevice =
                if (hasEarpiece()) defaultDevice else AudioDevice.SPEAKER_PHONE
            else -> Log.e(TAG, "Invalid default audio device selection")
        }
        Log.d(TAG, "setDefaultAudioDevice(device=$defaultAudioDevice)")
        updateAudioDeviceState()
    }

    /** Changes selection of the currently active audio device.  */
    override fun selectAudioDevice(device: AudioDevice) {
        ThreadUtils.checkIsOnMainThread()
        if (!audioDevices.contains(device)) {
            Log.e(
                TAG,
                "Can not select $device from available $audioDevices"
            )
        }
        userSelectedAudioDevice = device
        selectedAudioDevice = device
        updateAudioDeviceState()
    }

    /** Helper method for receiver registration.  */
    private fun registerReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
        appContext.registerReceiver(receiver, filter)
    }

    /** Helper method for unregistration of an existing receiver.  */
    private fun unregisterReceiver(receiver: BroadcastReceiver) {
        appContext.unregisterReceiver(receiver)
    }

    /** Sets the speaker phone mode.  */
    private fun setSpeakerphoneOn(on: Boolean) {
        audioManager.isSpeakerphoneOn = on
    }

    /** Sets the microphone mute state.  */
    private fun setMicrophoneMute(on: Boolean) {
        audioManager.isMicrophoneMute = on
    }

    /** Gets the current earpiece state.  */
    private fun hasEarpiece(): Boolean {
        return appContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
    }

    /**
     * Checks whether a wired headset is connected or not.
     * This is not a valid indication that audio playback is actually over
     * the wired headset as audio routing depends on other conditions. We
     * only use it as an early indicator (during initialization) of an attached
     * wired headset.
     */
    @Deprecated("")
    private fun hasWiredHeadset(): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return audioManager.isWiredHeadsetOn
        } else {
            val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
            for (device: AudioDeviceInfo in devices) {
                val type = device.type
                if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
                    Log.d(TAG, "hasWiredHeadset: found wired headset")
                    return true
                } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
                    Log.d(TAG, "hasWiredHeadset: found USB audio device")
                    return true
                }
            }
            return false
        }
    }

    private fun hasBluetoothHeadset(): Boolean =
        mBluetoothAdapter?.let {
            it.isEnabled && (it.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothHeadset.STATE_CONNECTED
                    || it.getProfileConnectionState(BluetoothHeadset.A2DP) == BluetoothHeadset.STATE_CONNECTED)
        } ?: false

    /**
     * Updates list of possible audio devices and make new device selection.
     */
    fun updateAudioDeviceState() {
        ThreadUtils.checkIsOnMainThread()
        
        Log.d(
            TAG, ("Device status: "
                    + "available=" + audioDevices + ", "
                    + "selected=" + selectedAudioDevice + ", "
                    + "user selected=" + userSelectedAudioDevice)
        )

        // If a wired headset is connected, then it is the only possible option.
        // Else if the bluetooth is connected then it is the only possible option
        // In any other case we deal with the device audio hardware
        // No wired headset, hence the audio-device list can contain speaker
        // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
        // Update the set of available audio devices.
        val hasBluetoothHeadset = hasBluetoothHeadset()
        val newAudioDevices: MutableSet<AudioDevice?> = HashSet()
        if (hasWiredHeadset) newAudioDevices.add(AudioDevice.WIRED_HEADSET)
        if (hasBluetoothHeadset) newAudioDevices.add(AudioDevice.BLUETOOTH)
        if (hasEarpiece()) newAudioDevices.add(AudioDevice.EARPIECE)
        newAudioDevices.add(AudioDevice.SPEAKER_PHONE)

        // Store state which is set to true if the device list has changed.
        // And update the existing audio device set.
        val audioDeviceSetUpdated = audioDevices != newAudioDevices
        audioDevices = newAudioDevices
        _audioDevices.postValue(audioDevices.toMutableList())

        setAudioDeviceInternal(selectedAudioDevice)
        audioManagerEvents?.onAudioDeviceChanged(selectedAudioDevice, audioDevices)
        Log.d(TAG, "--- updateAudioDeviceState done")
    }

    companion object {
        private val TAG = "WebRtcAudioManager"
        private val SPEAKERPHONE_AUTO = "auto"
        private val SPEAKERPHONE_TRUE = "true"
        private val SPEAKERPHONE_FALSE = "false"
    }

    init {
        ThreadUtils.checkIsOnMainThread()
        audioManager = SystemServiceUtils.getAudioManager(appContext)
        wiredHeadsetReceiver = WiredHeadsetReceiver()
        bluetoothReceiver = BluetoothHeadsetReceiver()
        amState = AudioManagerState.UNINITIALIZED
        useSpeakerphone = SPEAKERPHONE_AUTO
        defaultAudioDevice =
            if (useSpeakerphone == SPEAKERPHONE_FALSE) AudioDevice.EARPIECE else AudioDevice.SPEAKER_PHONE

        Log.d(TAG, "useSpeakerphone: $useSpeakerphone")
        Log.d(TAG, "defaultAudioDevice: $defaultAudioDevice")
    }

}

从该类中,用户可以在活动中观察当前选择哪个设备作为声音输出,他也可以选择声音输出。这是根据声音输出设备重定向声音的活动部分

roomViewModel.webRtcAudioManager.start(object : WebRtcAudioManagerImpl.AudioManagerEvents {
            override fun onAudioDeviceChanged(
                selectedAudioDevice: WebRtcAudioManagerImpl.AudioDevice?,
                availableAudioDevices: Set<WebRtcAudioManagerImpl.AudioDevice?>?
            ) {
                when (selectedAudioDevice) {
                    WebRtcAudioManagerImpl.AudioDevice.WIRED_HEADSET -> {
                        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
                        audioManager.stopBluetoothSco()
                        audioManager.isBluetoothScoOn = false
                        audioManager.isSpeakerphoneOn = false
                    }
                    WebRtcAudioManagerImpl.AudioDevice.BLUETOOTH -> {
                        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
                        audioManager.startBluetoothSco()
                        audioManager.isBluetoothScoOn = true
                        audioManager.isSpeakerphoneOn = false
                    }
                    WebRtcAudioManagerImpl.AudioDevice.EARPIECE -> {
                        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
                        audioManager.stopBluetoothSco()
                        audioManager.isBluetoothScoOn = false
                        audioManager.isSpeakerphoneOn = false
                    }
                    WebRtcAudioManagerImpl.AudioDevice.SPEAKER_PHONE -> {
                        audioManager.mode = AudioManager.MODE_NORMAL
                        audioManager.stopBluetoothSco()
                        audioManager.isBluetoothScoOn = false
                        audioManager.isSpeakerphoneOn = true
                    }
                    else -> {
                        audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
                        audioManager.stopBluetoothSco()
                        audioManager.isBluetoothScoOn = false
                        audioManager.isSpeakerphoneOn = false
                    }
                }
            }
        })

在代码下方的某个地方,在 listView 中显示了一个设备选择

viewAdapter = AudioDeviceAdapter(object: RecyclerCallback.AudioDeviceCallback {
            override fun onAudioDeviceClicked(audioDevice: WebRtcAudioManagerImpl.AudioDevice) {
                // Here we select BLUETOOTH / WIRED_HEADSET / SPEAKER / EARPIECE
                roomViewModel.webRtcAudioManager.selectAudioDevice(audioDevice)
            }
        })

我有两个问题。

  1. 每当我连接了蓝牙设备时,当蓝牙设备连接到手机时,声音就会进入蓝牙。一旦我选择另一个输出(例如扬声器),声音就会按预期进入扬声器。从那时起,如果我想通过选择蓝牙作为输出设备再次听到蓝牙的声音,声音只会传到手机的听筒。

  2. 当蓝牙设备可用时,我希望蓝牙设备成为默认设备,否则手机的扬声器。

有人可以弄清楚如何解决这些问题吗?谢谢

4

0 回答 0