Android 蓝牙实战——蓝牙音乐播放/暂停调用(二十一)

embedded/2024/10/20 21:07:25/

        通过前面的学习我们了解了蓝牙开发中的各个协议,同时也知道蓝牙音乐的开发需要使用的是蓝牙的 a2dp 和 avrcp,而对蓝牙音乐的控制使用的是 avrcp,这里我们就梳理一下蓝牙音乐播放的调用流程。

一、调用流程

        在前面的 Avrcp 协议中,我们了解 App 可以通过 MediaSession 框架下发对应的指令,对于 FW 层会在 AvrcpControllerStateMachine 的 MediaSessionCompat.Callback 中处理指令。

1、AvrcpControllerStateMachine

源码位置:/packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java

MediaSessionCompat.Callback mSessionCallbacks = new MediaSessionCompat.Callback() {@Overridepublic void onPlay() {logD("onPlay");onPrepare();sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PLAY);}@Overridepublic void onPause() {logD("onPause");sendMessage(MSG_AVRCP_PASSTHRU, AvrcpControllerService.PASS_THRU_CMD_ID_PAUSE);}……@Overridepublic void onPrepare() {logD("onPrepare");A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();if (a2dpSinkService != null) {// 申请焦点a2dpSinkService.requestAudioFocus(mDevice, true);}}……
}

        这里对于播放流程,首先需要获取音频焦点,然后播放/暂停均发送了一个消息——MSG_AVRCP_PASSTHRU。这里对于焦点申请我们放到后面分析,先看一下命令的发送流程。

protected final AvrcpControllerService mService;class Connected extends State {……@Overridepublic boolean processMessage(Message msg) {logD(STATE_TAG + " processMessage " + msg.what);、switch (msg.what) {……case MSG_AVRCP_PASSTHRU:passThru(msg.arg1);return true;……}}……private synchronized void passThru(int cmd) {logD("msgPassThru " + cmd);// 有些键应该保留到下次活动if (mCurrentlyHeldKey != 0) {mService.sendPassThroughCommandNative(mDeviceAddress, mCurrentlyHeldKey,AvrcpControllerService.KEY_STATE_RELEASED);if (mCurrentlyHeldKey == cmd) {// 返回以防止再次启动FF/FR操作mCurrentlyHeldKey = 0;return;} else {// FF/FR正在进行中,需要进行其他操作,因此在停止FF/FR后,不返回,以便可以为所需操作发送命令。mCurrentlyHeldKey = 0;}}// Send the pass through.mService.sendPassThroughCommandNative(mDeviceAddress, cmd, AvrcpControllerService.KEY_STATE_PRESSED);if (isHoldableKey(cmd)) {// 下次发送命令时释放cmdmCurrentlyHeldKey = cmd;} else {mService.sendPassThroughCommandNative(mDeviceAddress, cmd, AvrcpControllerService.KEY_STATE_RELEASED);}}……
}

        这里处理了 MSG_AVRCP_PASSTHRU 消息并调用了 passThru() 方法,最后调用了 AvrcpControllerService 中的 sendPassThroughCommandNative() 方法。

2、AvrcpControllerService

源码位置:/packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerService.java、

/*** 发送按钮按命令到地址设备** @param keyCode  密钥代码在AVRCP规范中定义* @param keyState 0 =键按下,1 =键释放* @return 命令已发出*/
public native boolean sendPassThroughCommandNative(byte[] address, int keyCode, int keyState);

        这里是一个 Native 接口,Native 层实现在 com_android_bluetooth_avrcp_controller.cpp 中实现。

3、com_android_bluetooth_avrcp_controller.cpp

源码位置:/packages/apps/Bluetooth/jni/com_android_bluetooth_avrcp_controller.cpp

static const btrc_ctrl_interface_t* sBluetoothAvrcpInterface = NULL;static jboolean sendPassThroughCommandNative(JNIEnv* env, jobject object, jbyteArray address, jint key_code, jint key_state) {……RawAddress rawAddress;rawAddress.FromOctets((uint8_t*)addr);bt_status_t status = sBluetoothAvrcpInterface->send_pass_through_cmd(rawAddress, (uint8_t)key_code, (uint8_t)key_state);……env->ReleaseByteArrayElements(address, addr, 0);return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

        这里最终将命令发送到底层协议栈。包括蓝牙音乐的上/下一曲、停止播放、设置播放进度等都是上面的调用流程。

4、bt_rc.h

 /system/bt/include/hardware/bt_rc.h

bt_status_t (*send_pass_through_cmd)(const RawAddress& bd_addr, uint8_t key_code, uint8_t key_state);

二、焦点申请

                在前面调用播放指令的时候,先申请了音频焦点,这里我们看一下这个调用流程。

1、A2dpSinkService

源码位置:/packages/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java

private A2dpSinkStreamHandler mA2dpSinkStreamHandler;/*** 请求音频焦点,以便指定的设备可以流式传输音频*/
public void requestAudioFocus(BluetoothDevice device, boolean request) {synchronized (mStreamHandlerLock) {if (mA2dpSinkStreamHandler == null) return;mA2dpSinkStreamHandler.requestAudioFocus(request);}
}

        这里调用到 A2dpSinkStreamHandler 的焦点请求方法 requestAudioFocus()。

2、A2dpSinkStreamHandler 

源码位置: /packages/apps/Bluetooth/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandler.java

void requestAudioFocus(boolean request) {obtainMessage(REQUEST_FOCUS, request).sendToTarget();
}//焦点请求消息处理
case REQUEST_FOCUS:requestAudioFocusIfNone();break;

        A2dpSinkStreamHandler 继承了 Handler,这里发送并处理了 REQUEST_FOCUS 消息,最终调用 requestAudioFocusIfNone() 方法。

requestAudioFocusIfNone

private void requestAudioFocusIfNone() {if (DBG) Log.d(TAG, "requestAudioFocusIfNone()");if (mAudioFocus != AudioManager.AUDIOFOCUS_GAIN) {requestAudioFocus();}
}

        这里判断焦点是否已经申请,如果没有,则调用焦点申请方法。 

requestAudioFocus 

private synchronized int requestAudioFocus() {if (DBG) Log.d(TAG, "requestAudioFocus()");// 蓝牙A2DP可能携带音乐、有声书、导航或其他声音,因此标记内容类型未知。AudioAttributes streamAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).build();// 蓝牙躲避是在AudioManager的请求下在本机层处理的。AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(streamAttributes).setOnAudioFocusChangeListener(mAudioFocusListener, this).build();// 申请焦点int focusRequestStatus = mAudioManager.requestAudioFocus(focusRequest);// 焦点申请成功if (focusRequestStatus == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {startFluorideStreaming();mAudioFocus = AudioManager.AUDIOFOCUS_GAIN;}return focusRequestStatus;
}

         这里是标准的焦点申请方法,最后焦点申请成功调用 startFluorideStreaming() 方法。

startFluorideStreaming

private void startFluorideStreaming() {// 通知当前音频焦点状态到底层mA2dpSinkService.informAudioFocusStateNative(STATE_FOCUS_GRANTED);// 设置音量增益mA2dpSinkService.informAudioTrackGainNative(1.0f);requestMediaKeyFocus();
}

        我们主要看一下 requestMediaKeyFocus() 方法。

requestMediaKeyFocus

/*** 播放一个无声的音频样本,这样MediaSessionService就会知道蓝牙正在播放音频。** 如果MediaPlayer不存在,则创建一个新的MediaPlayer。重复调用此函数是安全的,将再次导致静音音频采样。* 这允许AVRCP控制器中的MediaSession路由媒体键事件,如果我们选择使用它。*/
private synchronized void requestMediaKeyFocus() {if (DBG) Log.d(TAG, "requestMediaKeyFocus()");if (mMediaPlayer == null) {AudioAttributes attrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent, attrs, mAudioManager.generateAudioSessionId());if (mMediaPlayer == null) {// 初始化媒体播放器失败。return;}mMediaPlayer.setLooping(false);mMediaPlayer.setOnErrorListener((mp, what, extra) -> {Log.e(TAG, "Silent media player error: " + what + ", " + extra);releaseMediaKeyFocus();return false;});}mMediaPlayer.start();BluetoothMediaBrowserService.setActive(true);
}

        这里创建了 MediaPlayer,并调用了 MediaPlayer 的 start() 方法开始播放一个默认的音源。可以看到这里并不是真正的开始播放蓝牙音乐, 而是播放一个无声的音频样本,来占用蓝牙的音频通道。这样做的目的是通知系统(特别是 MediaSessionService)以及连接的蓝牙设备,应用已经开始了音频播放,从而准备好接收和处理与音频播放相关的媒体按键事件(如播放/暂停、上一曲、下一曲等)。


http://www.ppmy.cn/embedded/37587.html

相关文章

1984. 学生分数的最小差值C++

给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。 从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。 返回可能的 最小差值 。 示例 1: 输入&…

无人机+垂直起降:微型共轴双旋翼无人机技术详解

微型共轴双旋翼无人机技术是一种独特的无人机设计,它结合了垂直起降(VTOL)能力和微型无人机的灵活性。这种设计允许无人机在无需跑道的情况下垂直起降,并具备在空中悬停和执行各种飞行动作的能力。 适用于集群控制,荷载…

大型语言模型的新挑战:AMR语义表示的神秘力量

DeepVisionary 每日深度学习前沿科技推送&顶会论文&数学建模与科技信息前沿资讯分享,与你一起了解前沿科技知识! 引言:AMR在大型语言模型中的作用 在自然语言处理(NLP)的领域中,抽象意义表示&…

bfs之八数码

文章目录 八数码解题思路图解举例算法思路 代码CPP代码Java代码 八数码 在一个 33的网格中,1∼8这 8个数字和一个 x 恰好不重不漏地分布在这 33 的网格中。 例如: 1 2 3 x 4 6 7 5 8在游戏过程中,可以把 x 与其上、下、左、右四个方向之一…

我独自升级崛起下载方法分享 下载教程

《我独自升级:崛起》这款精彩绝伦的动作角色扮演游戏,灵感来源于大热网络漫画,让玩家亲自踏上主角程肖宇的征途,从觉醒初阶到实力飞跃,每一步成长都扣人心弦。值得注意的是,尽管全球正式发布日期定在了五月…

C++笔记-makefile添加第三方.h和.cpp及添加.h和lib库模板

目文件结构如下所示时: project/├── main.cpp├── test.cpp├── DIRA/│ ├── A.cpp│ └── A.h├── DIRBLIB/│ └── libB.so└── include/└── B.h Makefile如下所示: # 编译器设置 CXX g CXXFLAGS -stdc11 -Wall# 目录…

DrissionPage

声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!本文章未经许…

简单聊下 Vue 3.0 和 React 18 框架有什么区别

Vue3 vs React 18:前端框架比较 随着Vue3和React 18的相继发布,前端开发领域再次迎来了技术革新的热潮。这两款框架各自迭代升级,不仅优化了原有特性,还引入了许多新概念,使得开发者在构建现代Web应用时拥有更多选择。…