Android車のBluetooth音楽にディスク制御機能を追加

1.ハンドルイベント変換

ステアリング ホイールが lin バスを介して変換されると仮定すると、最終的には標準のキーイベントとして Android 側に渡されます。

/** 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;

ステアリング ホイールのメディア コントロール キー、Android システムの入力ディスパッチャーを押します (関連する紹介については、私のブログを参照してください:

Android InputFlinger の簡単な分析) は、上記で定義されたキーコードを使用してイベントを報告します。で報告した後、

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.メディアセッション

前節では、イベント配信の操作が 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: 優先度の高いグローバル セッションがアクティブでない場合は、次の値が返されます。

mPriorityStack.getMediaButtonSession();

mPriorityStack とは何かわかりますか?

mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);

これはセッション スタックです. このスタックには一連の MediaSession が格納されています. このスタックの mAudioPlaybackMonitor (最終的にオーディオと関係があります):

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

mAudioPlaybackMonitor はシングルトンです。システム全体が稼働した後は、1 つだけになります。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 とマッピングし、オーディオ再生ステータスの変化に応じて更新します。

前の updateMediaButtonSessionIfNeeded 関数は、アプリが addSession を呼び出すときに呼び出されます。

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

アプリで作成した MediaSession を MediaSessionStack の mSessions に追加して管理し、更新する

mMediaButtonSession. このように、前の mPriorityStack.getMediaButtonSession(); が呼び出されると、ディスク制御イベントに応答できる MediaSession を与えることができます。

3. 車の Bluetooth 音楽が機能しないのはなぜですか?

上記の 1 と 2 は非常に多くのコンテキストの要約であり、アプリはディスク コントロールを使用し、MediaSession を新規作成し、Session を追加し、コールバックを追加し、ディスク コントロール イベントを監視する必要があるようです。しかし、実際の状況はそうではありません。具体的な理由は、ゆっくりお話ししましょう。

このような関数は、上記の 2 番目のセクションで言及されています: getSortedAudioPlaybackClientUids.

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

主な関連付けは、この mSortedAudioPlaybackClientUids です。

車内で Bluetooth 音楽を再生する場合 (携帯電話に接続して携帯電話で再生、車側の Bluetooth の役割はシンク) mSortedAudioPlaybackClientUids のサイズは 0 です。なぜそうなのかを調べさせてください!

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 はすべて PlayerBase を拡張したもので、次のようないくつかの公開機能を取得しています。

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

getService は IAudioService を返します。

はい、それは SystemServer で開始された audio_service です。

2. AudioTrack の play メソッドは、MediaPlayer の start メソッド、そしてもちろん SoundPool の play メソッドと同様に、PlayerBase の 1 で述べた baseStart メソッドを呼び出します。

つまり、アプリが Android の 3 つのうちの 1 つを呼び出してサウンドを再生する限り、自動的に 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(...省略)
    ...
}

会議が成功したため、私は多くの詳細を無視しました。多くのコードが省略されています。

つまり、アプリが Java の AudioTrack または MediaPlayer または SoundPool を呼び出す限り、前述の dispatchPlaybackConfigChange メソッドを呼び出します。

前述のように、dispatchPlaybackConfigChange は現在再生中の MediaSession を追加します。

mSortedAudioPlaybackClientUids。

他の人については最初に話しません。今日は車のBluetooth音楽について話しています!

車の Bluetooth 音楽が追加されないのはなぜですか? 彼らは、Java の AudioTrack、MediaPlayer、SoundPool を使用していませんか? 注、私はJavaを強調しました!!!

私が見つけた証拠を示しましょう:

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

他の話はやめましょう、結局、私はすでに Bluetooth の分野に参入しており、それは私が得意ではない分野です。

ここで言いたいのは、ネイティブの AudioTrack クラスを呼び出すということです。これが問題であり、Google はおそらくこれを考慮していませんでした。つまり、AudioTrack.cpp ファイル内のネイティブ AudioTrack クラスを使用するアプリの再生ステータスは追跡されません (追跡の意味はここにあります)。

4. 問題を解決する

これで問題が見つかりました。AudioService の playerEvent メソッドを呼び出そうとするだけです (なぜ playerBase でないのか聞かないでください。AudioTrack に PlayerBase クラスを継承させる必要があります。難しいです!)。

PlayerBase のソース コードを確認したところ、次のことがわかりました。

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

はい、そのとおりです。cpp コードは Java サービスを直接バインドできます。

持続する

余談ですが (バインダー呼び出しで使用されるインターフェース ファイル):

//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,
}

前の 2 行のコメントにより、バインダー呼び出しが失敗したという問題を解決できました。

IAudioManager メソッドの定義は、Java 層の IAudioService.aidl と一致している必要があります。主にメソッドオフセット

たとえば、次のようになります。

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

FIRST_CALL_TRANSACTION は最初のメソッド ADJUSTTSUGGESTEDSTREAMVOLUME を指します。これは、Java 層の IAudioService.aidl にメソッドが追加されている場合です (たとえば、AudioManager はアプリ呼び出しに新しいインターフェイスを追加し、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);
    }
    ...
   }

バインディングが成功すると、オーディオ サービスを正常に呼び出すことができます。

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++ の既定のパラメーター メカニズムは非常に使いやすいです。以前に関数を呼び出したコードに影響を与えることなく、関数の定義を変更したり、パラメーターを追加したりできます。ニウビニウビ!

さて、問題は解決しました。記事は完成しました。おしまいにしましょう。

おすすめ

転載: blog.csdn.net/bberdong/article/details/88370351