Android蓝牙开发【七】hfp音频连接
来源:互联网 发布:js图片点击弹出层放大 编辑:程序博客网 时间:2024/06/07 07:50
该文章是基于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,接收到消息后处理如下:
ase 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耳机通讯,向外发送广播并向蓝牙耳机发送通话状态。
- Android蓝牙开发【七】hfp音频连接
- Android 蓝牙开发(七)hfp音频连接
- Android 蓝牙开发(六)hfp连接
- Android蓝牙开发【六】hfp连接
- android 蓝牙 HFP 总结
- Android 蓝牙开发(八)hfp接听、挂断电话
- Android蓝牙开发【八】hfp接听、挂断电话
- Android蓝牙开发音频焦点
- android 蓝牙hfp client实现简介
- android 蓝牙hfp server实现简介
- android蓝牙hfp client使用例
- 蓝牙的HFP协议
- 蓝牙 HFP AT集
- 蓝牙之八-HFP
- android蓝牙协议名词解释 OPP HFP HDP A2DP PAN
- 蓝牙的HFP协议笔记
- 蓝牙之十四 HFP profile
- android蓝牙开发 蓝牙设备的查找和连接
- AWS终于支持Kubernetes,Serverless容器服务AWS Fargate同时发布
- MyBatis动态SQL语句
- StromTrident和Hbase 实现热门搜索
- python数组的使用
- 缓存策略分析
- Android蓝牙开发【七】hfp音频连接
- 笔记5-拷贝赋值构造函数\运算符重载\
- 网络编程笔记一、Socket套接字
- python下videocaputure出错的问题
- WebStorm 2017 激活
- java cms springmvc mybatis + 实现UC浏览器文章功能
- 如何在IntelliJ IDEA中快速配置Tomcat
- Cloudera Hadoop 5&Hadoop高阶管理及调优课
- 大战结束,77%市场份额,Kubernetes让云巨头、软硬件IT大厂其靠拢