假设方向盘是通过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.java
public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
try {
mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send key event.", e);
}
}
前一节我们讨论了事件分发的操作通过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对应的mediasession
MediaSessionRecord mediaButtonSession =
findMediaButtonSession(audioPlaybackUids.get(i));
if (mediaButtonSession != null) {
// Found the media button session.
//然后cleanup
mAudioPlaybackMonitor.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.
前面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(这里是追踪的意思)。
既然我们找到了问题所在。我们就想方设法调用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 out
ADJUSTSUGGESTEDSTREAMVOLUME = 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++的这个默认参数机制真心好用。函数的定义可以变,可以加参数,还不影响之前调用这个函数的那些代码。牛逼牛逼!
好的,问题解决,文章写完,收工!