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