给Android车载蓝牙音乐添加盘控控制功能

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.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);
        }
    }

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对应的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.

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 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++的这个默认参数机制真心好用。函数的定义可以变,可以加参数,还不影响之前调用这个函数的那些代码。牛逼牛逼!

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

你可能感兴趣的:(安卓,Audio)