通过前面的学习我们了解了蓝牙开发中的各个协议,同时也知道蓝牙音乐的开发需要使用的是蓝牙的 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)以及连接的蓝牙设备,应用已经开始了音频播放,从而准备好接收和处理与音频播放相关的媒体按键事件(如播放/暂停、上一曲、下一曲等)。