1. 方向盘事件转换

假设方向盘是通过lin总线转换的,最终来到安卓侧就是标准的keyevent:

/** Key code constant: Play/Pause media key. */
public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
/** Key code constant: Stop media key. */
public static final int KEYCODE_MEDIA_STOP      = 86;
/** Key code constant: Play Next media key. */
public static final int KEYCODE_MEDIA_NEXT      = 87;
/** Key code constant: Play Previous media key. */
public static final int KEYCODE_MEDIA_PREVIOUS  = 88;

按下方向盘的媒体控制键,安卓系统的inputdispatcher(相关介绍可以参考我的博客:

Android InputFlinger简单分析) 将会以上面定义的这种keycode上报事件。上报之后在

PhoneWindowManager中会进行响应:

//PhoneWindowManager.java
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {...case KeyEvent.KEYCODE_MEDIA_STOP:case KeyEvent.KEYCODE_MEDIA_NEXT:case KeyEvent.KEYCODE_MEDIA_PREVIOUS:if    (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {result &= ~ACTION_PASS_TO_USER;}if ((result & ACTION_PASS_TO_USER) == 0) {mBroadcastWakeLock.acquire();Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,new KeyEvent(event));msg.setAsynchronous(true);msg.sendToTarget();}break;    ...
}
...
case MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK:dispatchMediaKeyWithWakeLock((KeyEvent)msg.obj);
...//太多流转的代码就不列举了,不是本文重点//直接看MediaSessionManager.javapublic void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {                                                                                    try {mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);} catch (RemoteException e) {Log.e(TAG, "Failed to send key event.", e);}}

2.MediaSession

​ 前一节我们讨论了事件分发的操作通过MediaSessionManager往MediaSessionService流转。

//MediaSessionService.java
public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {...//一般全局优先的都是电话之类的//电话不是active状态的时候//dispatchMediaKeyEventLocked继续分发、/if (!isGlobalPriorityActive &&isVoiceKey(keyEvent.getKeyCode())) {handleVoiceKeyEventLocked(keyEvent, needWakeLock);} else {dispatchMediaKeyEventLocked(keyEvent, needWakeLock);}...
}

这里注意了,dispatchMediaKeyEventLocked的调用,只有keyEvent的参数。这里只是表明了发生了什么事件?按下的按钮是播放,暂停,下一个,上一个等等。那么这个分发应该发给谁呢?让我们看看:

private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock) {//找出MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();if (session != null) {//回调它} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null|| mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null){//回调它}
}//MediaSessionService.FullUserRecord
private MediaSessionRecord getMediaButtonSessionLocked() {return isGlobalPriorityActiveLocked()? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();}

getMediaButtonSessionLocked:全局高优先级的session没有active则返回:

mPriorityStack.getMediaButtonSession();

看看mPriorityStack是个啥?

mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);

是一个session stack.这个stack里存了一堆MediaSession.这个stack的mAudioPlaybackMonitor(总算和audio扯上关系了):

mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);

mAudioPlaybackMonitor是单例的。整个系统起来之后就只有一个。它就是AudioService的那个AudioPlaybackMonitor。

mPriorityStack拿它来作甚呢?

//MediaSessionStack.java
//(开始我的业余翻译)
//如果有需要,更新media button session
//media button session是那个会接收media button事件的session
//我们会把media button事件发送给the lastly played app(最后播放的app)
//如果那个app有media session,它的session将收到media buttion事件public void updateMediaButtonSessionIfNeeded() {//getSortedAudioPlaybackClientUids的注释://返回经过排序的有激活的audio playback的uid列表//The UID whose audio playback becomes active at the last comes first.//最后播放的放在最前面,后进先出嘛,所以这里的数据结构是栈//感觉自己好聪明IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();                                                                               for (int i = 0; i < audioPlaybackUids.size(); i++) {//找到udi对应的mediasessionMediaSessionRecord mediaButtonSession =findMediaButtonSession(audioPlaybackUids.get(i));if (mediaButtonSession != null) {// Found the media button session.    //然后cleanupmAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());if (mMediaButtonSession != mediaButtonSession) {//更新mMediaButtonSession的值updateMediaButtonSession(mediaButtonSession);}return;}}}

mPriorityStack把mAudioPlaybackMonitor用来和mediasession做一个映射,并且根据audio的播放状态的变化做一个刷新。

前面这个updateMediaButtonSessionIfNeeded函数会在app调用了addSession的时候调用:

public void addSession(MediaSessionRecord record) {mSessions.add(record);updateMediaButtonSessionIfNeeded();
}

将应用创建的MediaSession加到MediaSessionStack的mSessions中进行管理,并且更新

mMediaButtonSession.这样,前面mPriorityStack.getMediaButtonSession();调用过来的时候,才能给出一个可以响应盘控事件的MediaSession.

3.车载蓝牙音乐为什么不行

前面1,2说了这么多情景提要,貌似app想使用盘控,new一个MediaSession并,addSession,加个回调,监控盘控事件就可以了。可实际情况不是这样的。具体缘由,且听我慢慢道来。

前面第二节中提到过这么一个函数:getSortedAudioPlaybackClientUids.

//AudioPlaybackMonitor.java
public IntArray getSortedAudioPlaybackClientUids() {Log.d(TAG,"getSortedAudioPlaybackClientUids");IntArray sortedAudioPlaybackClientUids = new IntArray();synchronized (mLock) { sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);}return sortedAudioPlaybackClientUids;}

主要关联的是这个mSortedAudioPlaybackClientUids.

车载蓝牙音乐播放的时候(连接手机,在手机端播放,车机端蓝牙的角色是sink).mSortedAudioPlaybackClientUids的size为0.我擦,为什么是这样!!!let me find out why so it is!

mSortedAudioPlaybackClientUids是在什么地方添加的?

//直接搜索mSortedAudioPlaybackClientUids.add
//只有一个地方
//AudioPlaybackMonitor.java
//alled when the {@link AudioPlaybackConfiguration} is updated.
public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,boolean flush) {...mSortedAudioPlaybackClientUids.add(0, config.getClientUid());...
}

好的,按照惯例,捋一遍调用堆栈。过程就不聊了,直接上结论:

1.AudioTrack,MediaPlayer,SoundPool都extends PlayerBase .获得了一些公共能力,比如

//PlayerBase.java
void baseStart() {...mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;//请注意,并记住这个playerEvent方法getService().playerEvent(mPlayerIId, mState);...}

getService返回的是IAudioService.

是的,就是那个在在SystemServer中启动的audio_service.

2.AudioTrack的play方法会调用到PlayerBase的1中说的这个baseStart方法,MediaPlayer的start方法也是,当然SoundPool的play方法也是。

也就是说只要app调用了Android中这三者中的其中一个去播放声音,都会自动调用到PlayerBase的baseStart方法,也即AudioService的playerEvent方法

3.看看第一步baseStart中调用的playerEvent方法长什么样?

//AudioService.java
public void playerEvent(int piid, int event) {mPlaybackMonitor.playerEvent(piid, event, Binder.getCallingUid());                           }
//PlaybackActivityMonitor.java
public void playerEvent(int piid, int event, int binderUid) {...dispatchPlaybackChange(event == AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);...
}
private void dispatchPlaybackChange(boolean iplayerReleased) {...pmc.mDispatcherCb.dispatchPlaybackConfigChange(...省略)...
}

由于成功会师,所以我忽略了很多细节。省略了很多代码。

也就是说只要app调用了java的AudioTrack或者MediaPlayer或者SoundPool.就会调用我们在前面讨论的dispatchPlaybackConfigChange方法。

前面说过了,dispatchPlaybackConfigChange会把当前播放的MediaSession加入

mSortedAudioPlaybackClientUids。

其他先不展开讲了。我们今天聊的是车机蓝牙音乐!

车机蓝牙音乐为什么不会被加进去?难道他们用的不是java的AudioTrack,MediaPlayer,SoundPool?注意,我强调的是java!!!

展示我找到的证据吧:

//system/bt/btif/src/btif_avrcp_audio_track.cc
//这里再次明确下,我们本文聊的场景是车机蓝牙音乐,就是手机连接车机,手机的音频
//数据通过a2dp送到车机,车机的蓝牙角色是sink
void* BtifAvrcpAudioTrackCreate(参数省略){...track = new android::AudioTrack(参数省略);...
}

其他就先不聊了,毕竟已经进入了蓝牙领域了,不是我擅长的了。

这里我想说的是他们调用的是native的AudioTrack类。这就是问题所在了,谷歌估计是没考虑到这种情况吧。总之使用AudioTrack.cpp这个文件中的native AudioTrack类进行播放的app,播放状态都不会被track(这里是追踪的意思)。

4.解决问题

既然我们找到了问题所在。我们就想方设法调用AudioService的playerEvent方法就好了(不要问为什么不是playerBase,我还得让AudioTrack继承PlayerBase类,费劲!)。

我们查看了下PlayerBase的源码,发现:

//PlayerBase.cpp
PlayerBase::PlayerBase() {...sp<IBinder> binder = defaultServiceManager()->checkService(String16("audio"));mAudioManager = interface_cast<IAudioManager>(binder);...
}

是的,你没看错,cpp代码是直接可以bind java的service的。

稍等

说个题外话(binder调用使用到的接口文件):

//IAudioManager.h
class IAudioManager : public IInterface
{public:// These transaction IDs must be kept in sync with the method order from// IAudioService.aidl.// transaction IDs for the unsupported methods are commented outADJUSTSUGGESTEDSTREAMVOLUME           = IBinder::FIRST_CALL_TRANSACTION,ADJUSTSTREAMVOLUME                    = IBinder::FIRST_CALL_TRANSACTION + 1,PLAYER_EVENT                          = IBinder::FIRST_CALL_TRANSACTION + 70,
}

前面的两行注释让我解决了一个binder调用死活不成功的问题。

IAudioManager方法的定义要和java层的IAudioService.aidl保持一致。主要是方法的偏移

比如说这个:

IBinder::FIRST_CALL_TRANSACTION + 70,//我说的偏移就是这个70

FIRST_CALL_TRANSACTION指向第一个方法ADJUSTSUGGESTEDSTREAMVOLUME,如果java层的IAudioService.aidl有添加方法(比如说AudioManager添加新的接口给app调用,AudioService就需要添加对应的方法实现,IAudioService.aidl就要添加对应的方法定义)。这种时候PLAYER_EVENT对应的偏移,比如说原生是69,我这里改成70就是因为前面多了一个方法。我能想到比较笨的方法就是数方法个数。

回到这个问题的解决方法:

//AudioTrack.cpp
AudioTrack::AudioTrack(
...//这个参数是我自己加的默认参数,默认值就是false.这样,只有蓝牙改成true才会启动这个功能。这样能保证改动的影响最小。bool isNeedTrackInNative ) {...if(mIsNeedTrackInNative) {sp<IBinder> binder = defaultServiceManager()->checkService(String16("audio"));mAudioManager = interface_cast<IAudioManager>(binder);}...}

binding成功audio这个service就可以愉快地调用了。

status_t AudioTrack::start()
{...mAudioManager->playerEvent(mPIId,PLAYER_STATE_STARTED);...
}void AudioTrack::stop()
{...mAudioManager->playerEvent(mPIId,PLAYER_STATE_STOPPED);...
}void AudioTrack::pause()
{...mAudioManager->playerEvent(mPIId,PLAYER_STATE_PAUSED);...
}AudioTrack::~AudioTrack()
{...mAudioManager->releasePlayer(mPIId);mAudioManager.clear();...
}

然后,对应的BtifAvrcpAudioTrackCreate调用的地方也有加上给他唯一定义的参数。这点来说C++的这个默认参数机制真心好用。函数的定义可以变,可以加参数,还不影响之前调用这个函数的那些代码。牛逼牛逼!

好的,问题解决,文章写完,收工!

给Android车载蓝牙音乐添加盘控控制功能相关推荐

  1. android 动态添加删除控件,求教Android,动态添加到控件能动态删除吗?

    protected View createView() {//动态添加组件 Button btn = new Button(this);//动态创建按钮 btn.setId(index++); btn ...

  2. android开发中为MultiAutoCompleteTextView控件添加其他分隔符

    android开发中为MultiAutoCompleteTextView控件添加其他分隔符,例如:分隔符改为分号";" zs;ls;ww;xm //SemicolonTokeniz ...

  3. 在GLSurfaceView上添加Layout控件(android)

    查找了很久,才找出在GLSurfaceView上添加控件的方法.废话不说,本例实现了一个Native opengl es 程序,绘制了一个旋转三角形:当然主题是在GLSurfaceView上添加Lay ...

  4. Android蓝牙音乐获取歌曲信息

    由于我在蓝牙开发方面没有多少经验,如果只是获取一下蓝牙设备名称和连接状态那么前面的那篇文章就已经足够了,接下来的内容是转自一个在蓝牙音乐方面颇有经验的开发者的博客,他的这篇文章对我帮助很大. 今天,先 ...

  5. android蓝牙音乐之AVRCP介绍和使用

    引言 最近做的车载蓝牙音乐开发,遇到很多问题,记录一下.也是到处东拼西凑的,勉强看看吧. AVRCP:Audio/Video Remote Control Profile,音视频远端控制协议,所以该协 ...

  6. Android开源音乐播放器之播放器基本功能

    系列文章 Android开源在线音乐播放器--波尼音乐 Android开源音乐播放器之播放器基本功能 Android开源音乐播放器之高仿云音乐黑胶唱片 Android开源音乐播放器之自动滚动歌词 An ...

  7. Android 自定义音乐播放器实现

    Android自定义音乐播放器 一:首先介绍用了哪些Android的知识点: 1 MediaPlayer工具来播放音乐 2 Handle.因为存在定时任务(歌词切换,动画,歌词进度条变换等)需要由Ha ...

  8. 【android】音乐播放器之UI设计的点点滴滴

    学习Android有一个多月,看完了<第一行代码>以及mars老师的第一期视频通过音乐播放器小项目加深对知识点的理解.从本文开始,将详细的介绍简单仿多米音乐播放器的实现,以及网络解析数据获 ...

  9. android 小型音乐播放器(实现播放、下一首、上一首、自动播放、随机播放按钮、当前播放歌曲界面)

    该文章主要针对 android 的音乐播放器软件进行简单的功能添加:播放.断点播放.停止.上一曲.下一曲.随机播放.显示当前播放歌曲(后续会为当前显示的播放添加动画): 软件开发流程: 1.先向内存卡 ...

最新文章

  1. hung-yi lee_p1_机器学习是什么
  2. OKR和绩效管理如何一起工作?
  3. console java_Java Console format()方法与示例
  4. 有关 给Layout设置监听事件后,与Layout子控件的响应关系
  5. CentOS 6.5下Python3.5以及virtualenv、virtualenvwrapper的安装配置
  6. Ubuntu 14.04安装和卸载搜狗拼音输入法
  7. 用9种办法解决 for 循环取 i
  8. 一名南京985AI硕士,CSDN博客专家
  9. 详细vue脚手架安装教程
  10. vbs整人代码大集合
  11. 手机入侵修改服务器数据,入侵手游服务器修改数据库
  12. wireshark出现rst的原因_多次RST以及不同场景下的RST报文的差异
  13. 使用printf语句输出名言:“贵有恒,何必三更起五更睡:最无益,只怕一日曝十日寒。“
  14. SpringBoot集成JWT实现Token登录验证
  15. 普通话测试软件字体怎么调整,普通话考试常见问题解答
  16. 路由器wifi密码设置
  17. 数据库文件.mdf太大的解决方法
  18. 数字化转型2020,如何加速传统IT到企业云的过渡?
  19. Python对英汉词典的若干操作(#10)
  20. Vue源码之mustache模板引擎(一)

热门文章

  1. Nwafu-OJ-1410 Problem I C语言实习题二——4.判断是否能构成一个三角形
  2. 滚动条自动显示和隐藏
  3. realsense D435安装时出现Invoking cmake failed,not found ddynamic_reconfigure etc.
  4. 关闭linux防火墙永久
  5. 什么是着色器/Threejs如何使用着色器/Threejs使用着色器实现平面网格的动态效果案例
  6. 微型断路器的选择使用
  7. 用C语言统计给定文本文件中汉字的个数。
  8. 如何编写测试用例?(详细分析)
  9. prometheus监控zookeeper
  10. 超详细!图论最短路算法与极简c++代码(配题目)