转载请注明出处: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;}//连接audioif ( (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耳机通讯,向外发送广播并向蓝牙耳机发送通话状态。

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

Android 蓝牙开发(七)hfp音频连接相关推荐

  1. Android 蓝牙开发(二) --手机与蓝牙音箱配对,并播放音频

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 上一章中,我们已经学习了传统蓝牙的开发,这一章,我们来学习如 ...

  2. Android蓝牙开发音频焦点

    在车机开发中,蓝牙模块一般是定制的,而蓝牙的音频输出,包括蓝牙电话,蓝牙音乐,都要制定声音策略,进行音频焦点的管理. 音频焦点的管理,这一点类似于android多媒体开发时的音频焦点管理,也是通过Au ...

  3. Android蓝牙开发教程(二)——连接蓝牙设备

    在上一篇中已经介绍如何搜索附近可连接的蓝牙设备,如果你还没阅读过,建议先看看上一篇文章Android蓝牙开发教程(一)--搜索蓝牙设备 获取到设备后就可以开始处理蓝牙设备之间的连接. 在上一篇教程中我 ...

  4. Android蓝牙开发系列文章-蓝牙音箱连接

    经过一段时间的折腾,我的Android Studio终于可以正常工作了,期间遇到的坑记录在了文章<创建Android Studio 3.5第一个工程遇到的坑>. 我们在<Androi ...

  5. Android蓝牙开发 — 经典蓝牙BLE蓝牙

    一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI): 支持移动电话.笔记本电脑.无线耳机等设备之间进行信息的交换: Android支持的蓝牙协议栈:Bl ...

  6. Android 蓝牙开发(扫描设备、绑定、解绑)Kotlin版

    Kotlin版 蓝牙开发 (扫描设备.绑定.解绑) 前言 运行效果图 正文 ① 配置项目 ② 布局和样式 ③ 编码 1. 通知栏样式修改 2. 蓝牙设备列表适配器编写 3. 权限请求 4. 初始化蓝牙 ...

  7. Android 蓝牙开发(扫描设备、绑定、解绑)

    Android 蓝牙开发(扫描设备.绑定.解绑) 前言 效果图 一.配置项目 二.布局和样式 三.编码 四.源码 前言 公司最近给我丢了一个蓝牙开发的项目,不了解怎么办呢,那当然是从最基础的开始了,所 ...

  8. Android 蓝牙开发(一) -- 传统蓝牙聊天室

    Android 蓝牙开发(一) – 传统蓝牙聊天室 Android 蓝牙开发(三) – 低功耗蓝牙开发 项目工程BluetoothDemo 一.蓝牙概览 以下是蓝牙的介绍,来自维基百科: 蓝牙(英语: ...

  9. Android蓝牙开发(一)蓝牙模块及核心API

    本文主要介绍Android蓝牙开发中基础知识:蓝牙模块及核心API. 关于蓝牙的连接及通讯功能实现,欢迎查阅下一篇文章:Android蓝牙开发(二)蓝牙消息传输实现. 蓝牙模块 从蓝牙4.0开始包含两 ...

  10. Android蓝牙开发系列文章-其实你的手机可以变成一个蓝牙音箱

    本文是蓝牙音频相关的第3篇文章,查阅其他内容,请点击<Android蓝牙开发系列文章-策划篇>. 目前a2dp相关的内容有: <Android蓝牙开发系列文章-AudioTrack播 ...

最新文章

  1. mongoose手动生成ObjectId
  2. uiswitchbutton 点击不改变状态_Redux 包教包会(一):解救 React 状态危机
  3. 重置MYSQL的root 密码
  4. mysql简单概述_MySQL入门很简单: 1 数据库概述
  5. php 打印测试技巧
  6. python语法_嵌套
  7. java ean13 条形码_【教程】Spire.Barcode 教程:如何在C#中创建EAN-13条码
  8. Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)
  9. c# string 转 datetime_tesseract || PDF转PNG转txt
  10. VCHECK(每日报告)
  11. 水晶报表基础入门——4.水晶报表排序、分组技术
  12. GPS围栏两个多边形相交问题的奇葩解法
  13. Linux内核hlist数据结构分析
  14. Java自定义拦截器详细教程
  15. GIT可视化工具 代码上传工具
  16. IT,大一,这里我有点建议
  17. C# 西门子PLC上位机开发环境搭建
  18. 电力系统104规约帧报文解析
  19. julia安装源_[julia]本地离线安装package
  20. 面试送命题,你为什么从上家公司离职?

热门文章

  1. opencv实现鼠标画矩形框、显示十字线、缩放图片
  2. 【方法】Matlab求解方程,带参数,方程组,不等式(2021更新)
  3. 分形(factal)的基本概念
  4. Connect city
  5. Jenkins部署瘦身jar包
  6. linux下RTP编程(使用JRTPLIB)(转)
  7. 【Error】Error running process: CreateProcess failed. Code 2
  8. mysql解决[HY000][1366] Incorrect string value: ‘\xE4\xB8\xAD\xE6\x96\x87‘ for column ‘title‘ at row 1
  9. 无纸化会议交互系统部署
  10. 同一类型的计算机指令系统,不同类型的CPU指令集不同,指令系统一样吗?