Android 蓝牙开发(七)hfp音频连接

转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/71374935

本文已授权微信公众号 fanfan程序媛 独家发布 扫一扫文章底部的二维码或在微信搜索 fanfan程序媛 即可关注

接着上一篇hfp连接继续,查看蓝牙通话时如何进行处理的。hfp连接有两个连接,一个是hfp连接(在设置界面显示的是手机音频),另一个是蓝牙通话时进行的音频连接。这篇说下第二个连接,音频连接处理过程。
该文章是基于Android源码4.3的


1 连接音频

在手机音频正常连接时,接通电话,并选择蓝牙通话。从系统应用Phone开始分析。
代码路径:packages/apps/Phone/src/com/Android/phone/InCallScreen.Java
手机通话可以选择扬声器、听筒、蓝牙,我们选择蓝牙。
这里写图片描述

public void switchInCallAudio(InCallAudioMode newMode) {
    switch (newMode) {
        case SPEAKER: break; //扬声器    
        case BLUETOOTH: //蓝牙
            // 检查hfp是否连接着(蓝牙耳机是否连接可用),检查蓝牙耳机的音频是否连接
            if (isBluetoothAvailable() && !isBluetoothAudioConnected()) {
                if (PhoneUtils.isSpeakerOn(this)) { //关闭扬声器
                    PhoneUtils.turnOnSpeaker(this, false, true);
                }
                connectBluetoothAudio(); //连接蓝牙音频
            }
            break;
        case EARPIECE:break; //听筒     
        default: break;
    }
    updateInCallTouchUi(); //更新ui
}

蓝牙通话时选择蓝牙,会调到switchInCallAudio(),对于蓝牙通话模式,检查是否连接蓝牙耳机 headset(手机音频),检查蓝牙通话音频是否连接,如果有连接的蓝牙耳机,并且没有连接蓝牙音频(这个连接并不是设置界面中的手机音频连接,这是通话是需要的连接,该连接的前提是需要进行手机音频的连接),则满足条件。
如果扬声器开着,则先关闭扬声器,然后连接蓝牙音频。接着看connectBluetoothAudio()函数。

/* package */ void connectBluetoothAudio() {
    if (mBluetoothHeadset != null) {
        mBluetoothHeadset.connectAudio();
    }
    //注意:蓝牙连接不会立即发生;connectAudio()调用立即返回,但实际它在另一个线程中工作。
    //mBluetoothConnectionPending标志只是一个标志,以确保屏幕UI立即更新。
    mBluetoothConnectionPending = true;
    mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
}

mBluetoothHeadset是通过getProfileProxy获取的BluetoothHeadset代理对象。通过代理对象连接音频。mBluetoothHeadset.connectAudio()会跳到应用Settings中HeadsetService内部类BluetoothHeadsetBinder中的connectAudio()方法,然后又跳到HeadsetService的connectAudio()函数中。
HeadsetService的connectAudio()函数如下:

boolean connectAudio() {
    enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    if (!mStateMachine.isConnected()) { //检查手机音频是否连接
        return false;
    }
    if (mStateMachine.isAudioOn()) { //检查音频是否连接
        return false;
    } //向状态机发送消息
    mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
    return true;
}

在HeadsetService的connectAudio()函数中检查headset是否连接,音频是否连接。向状态机发送连接音频的消息。此时headset是连接的,HeadsetStateMachine中的状态是Connected。
接收到后CONNECT_AUDIO的消息进行如下处理:

//mCurrentDevice表示状态改变前连接的设备。
connectAudioNative(getByteAddress(mCurrentDevice));

mCurrentDevice表示状态改变前连接的设备。通过getByteAddress获取该设备的蓝牙地址。然后调用native方法connectAudioNative连接音频,该方法会调用jni目录下的
com_android_bluetooth_hfp.cpp中的connectAudioNative函数。

static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {
    jbyte *addr;
    bt_status_t status;
    if (!sBluetoothHfpInterface) return JNI_FALSE;
    //将byte数组类型的地址转换为jbyte*类型
    addr = env->GetByteArrayElements(address, NULL);
    if (!addr) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }
    //连接audio
    if ( (status = sBluetoothHfpInterface->connect_audio((bt_bdaddr_t *)addr)) !=
         BT_STATUS_SUCCESS) {
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

将byte数组类型的地址转换成jbyte*类型,然后向hardware、协议栈下进行连接。


2 音频连接状态

当音频连接状态改变会回调com_android_bluetooth_hfp.cpp中audio_state_callback函数。
audio_state_callback函数如下:

static void audio_state_callback(bthf_audio_state_t state, bt_bdaddr_t* bd_addr) {
    jbyteArray addr;

    CHECK_CALLBACK_ENV
    //获取蓝牙地址
    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (!addr) {
        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
        return;
    }
    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
    //调用method_onAudioStateChanged对应的方法。
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, (jint) state, addr);
    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
    sCallbackEnv->DeleteLocalRef(addr);
}

audio_state_callback中参数state表示音频连接状态,address表示蓝牙的地址。将address转换为jbyteArray类型,然后调用java层代码,调用HeadSetStateMachine中的onAudioStateChanged函数。onAudioStateChanged代码如下:

private void onAudioStateChanged(int state, byte[] address) {
    StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
    event.valueInt = state;
    event.device = getDevice(address);
    sendMessage(STACK_EVENT, event); //发送消息
}

onAudioStateChanged向状态机发送消息。此时状态机处于Connected状态,收到该消息调用processAudioEvent(event.valueInt, event.device)函数。processAudioEvent代码如下:

private void processAudioEvent(int state, BluetoothDevice device) {
    if (!mCurrentDevice.equals(device)) { //查看是否是之前连接的设备
        return;
    }
    switch (state) {
        case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
            mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
            //设置蓝牙SCO进行通信。
            mAudioManager.setBluetoothScoOn(true);
            broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
                                BluetoothHeadset.STATE_AUDIO_CONNECTING);
            transitionTo(mAudioOn); //切换到AudioOn状态
            break;
        case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
            mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
            broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
            break;
        default:
            break;
    }
}

音频连接回调,状态是HeadsetHalConstants.AUDIO_STATE_CONNECTING或HeadsetHalConstants.AUDIO_STATE_CONNECTED,向外发送audio连接状态改变的广播。状态是HeadsetHalConstants.AUDIO_STATE_CONNECTED,通过AudioManager设置蓝牙SCO进行音频通信,将状态机切换到AudioOn状态。

可以通过广播接收者注册BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,监听音频到连接状态的改变。


3 音频断开连接

蓝牙通话状态下,切换到听筒、扬声器或者停止通话,都会将音频断开连接。在应用Phone中的InCallScreen.java中调用disconnectBluetoothAudio,代码如下:

/* package */ void disconnectBluetoothAudio() {
    if (mBluetoothHeadset != null) {
        //断开音频连接
        mBluetoothHeadset.disconnectAudio();
    }
    mBluetoothConnectionPending = false;
}

mBluetoothHeadset.disconnectAudio()通过代理对象调用disconnectAudio(),跳转到应用Bluetooth的HeadSetService内部类BluetoothHeadsetBinder中的disconnectAudio()中,然后跳到HeadSetService的disconnectAudio()函数中。

boolean disconnectAudio() {
    enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
    //判断状态机状态是否处于AudioOn状态
    if (!mStateMachine.isAudioOn()) {
        return false;
    } //发送DISCONNECT_AUDIO消息
    mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
    return true;
}

此时HeadsetStateMachine状态为AudioOn,接收到消息后处理如下:

case DISCONNECT_AUDIO:
    if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
        mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
        //音频管理关闭蓝牙SCO。
        mAudioManager.setBluetoothScoOn(false);
        //发送广播
        broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                            BluetoothHeadset.STATE_AUDIO_CONNECTED);
    }
    break;

disconnectAudioNative为native方法,调用到jni关闭音频连接。关闭蓝牙SCO耳机通讯,向外发送广播并向蓝牙耳机发送通话状态。

欢迎扫一扫关注我的微信公众号,定期推送优质技术文章:

这里写图片描述

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页