Android 차량용 Bluetooth 음악에 디스크 제어 기능 추가

40896 단어 안드로이드Audio
1. 핸들 이벤트 전환
만약에 핸들이 라인 버스를 통해 전환된다고 가정하면 최종적으로 안드로이드 옆에 오면 표준 키 이벤트:
/** 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 단순 분석)은 위에서 정의한 키코드로 이벤트를 보고합니다.에스컬레이션 이후
PhoneWindow Manager에서 응답하는 항목:
//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
앞에서 우리는 이벤트 배달 작업이 MediaSession Manager를 통해 MediaSession 서비스로 유전되는 것을 토론했다.
//MediaSessionService.java
public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
    ...
        //              
        //    active     
        //dispatchMediaKeyEventLocked    、/
       if (!isGlobalPriorityActive &&
         isVoiceKey(keyEvent.getKeyCode())) {
           handleVoiceKeyEventLocked(keyEvent, needWakeLock);
       } else {
           dispatchMediaKeyEventLocked(keyEvent, needWakeLock);
       }
    ...
}

디스패치 미디어 Key Event Locked 호출은 키 Event 인자만 있습니다.여기는 단지 무슨 사건이 발생했는지 표명했을 뿐입니까?누르는 단추는 재생, 정지, 다음, 이전 등등입니다.그럼 이걸 누구한테 나눠줘야 하나요?어디 보자.
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();

봐봐, mPriority Stack이 뭐야?
mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);

세션 스택입니다.이 Stack에 Media Session이 한 무더기 저장되어 있다.이 Stack의 mAudio Playback Monitor:
mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);

mAudioPlaybackMonitor는 단일 예입니다.전체 시스템이 시작된 후 단지 하나뿐이다.바로 Audio Service의 그 Audio Playback Monitor입니다.
mPriority Stack은 그것을 가지고 무엇을 합니까?
//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;
            }
        }
    }

mPriority Stack은 mAudio Playback Monitor를 미디어세션과 비추고 오디오의 재생 상태 변화에 따라 리셋합니다.
앞에 있는 업데이트 미디어 Button Session IfNeeded 함수는 app에서 add Session을 호출할 때 호출됩니다.
public void addSession(MediaSessionRecord record) {
    mSessions.add(record);
    updateMediaButtonSessionIfNeeded();
}

응용 프로그램이 만든 MediaSession을 MediaSessionStack의 mSessions에 추가하여 관리하고 업데이트합니다
mMediaButtonSession.이렇게 앞에 mPriority Stack.getMediaButtonSession();호출이 왔을 때만 디스크 제어 이벤트에 응답할 수 있는 미디어 세션을 제공할 수 있습니다.
3. 블루투스 음악을 싣는데 왜 안 돼
앞에서 1,2는 이렇게 많은 상황 개요를 말했는데 앱이 디스크 제어를 사용하고 싶어 하는 것 같다. new는 미디어 세션이고add Session이 리셋을 해서 디스크 제어 사건을 감시하면 된다.실제 상황은 그렇지 않습니다.구체적인 연유는 우선 내가 천천히 말하는 것을 들어라.
앞의 두 번째 절에서 언급한 함수는 다음과 같다. get Sorted Audio Playback Client Uids.
//AudioPlaybackMonitor.java
public IntArray getSortedAudioPlaybackClientUids() {
        Log.d(TAG,"getSortedAudioPlaybackClientUids");
        IntArray sortedAudioPlaybackClientUids = new IntArray();
        synchronized (mLock) { sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
        }
        return sortedAudioPlaybackClientUids;
    }

주로 이 mSortedAudioPlaybackClientUid와 관련이 있습니다.
차에 블루투스 음악을 싣고 재생할 때(핸드폰을 연결하고 핸드폰에서 재생하며 핸드폰에서 블루투스 역할을 하는 것은sink).mSortedAudioPlaybackClientUids의 사이즈는 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 Player Base.약간의 공공 능력을 얻었다. 예를 들면,
//PlayerBase.java
void baseStart() {
    ...
                mState = AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
    //   ,     playerEvent  
                getService().playerEvent(mPlayerIId, mState);
   ...
    }

getService에서 IAudioService를 반환했습니다.
네, 바로 System Server에서 시작되는 audio 입니다.service.
2. Audio Track의 플레이 방법은 Player Base의 1에서 말한 이 베이스 Start 방법을 사용하고, Media Player의 start 방법도 사용하며, 물론SoundPool의 플레이 방법도 사용한다.
즉, 앱이 안드로이드의 이 세 가지 중 하나를 호출해서 소리를 재생하면 Player Base의baseStart 방법, 즉Audio Service의player Event 방법으로 자동으로 호출된다는 것이다.
3. 첫 번째 베이스스타트에서 호출된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(...  )
    ...
}

성공적으로 합류했기 때문에, 나는 많은 세부 사항을 소홀히 했다.많은 코드를 생략했다.
즉 앱에서 자바의 오디오 트랙이나 미디어플레이어나 사운드폴을 호출하면앞서 논의한 DispatchPlaybackConfigChange 방법이 호출됩니다.
앞서 말씀드렸듯이 디스패치 플레이백 Config Change는 현재 재생되고 있는 미디어세션을 추가합니다.
mSortedAudioPlaybackClientUids.
다른 건 일단 얘기 안 할게요.저희가 오늘 얘기한 건 블루투스 뮤직!
자동차 블루투스 음악은 왜 들어가지 않습니까?설마 그들이 사용한 것은 자바의 오디오 트랙, 미디어플레이어, 사운드폴이 아니겠지?주의, 내가 강조하는 것은 자바!!!
내가 찾은 증거를 보여줘.
//system/bt/btif/src/btif_avrcp_audio_track.cc
//       ,               ,        ,     
//    a2dp    ,        sink
void* BtifAvrcpAudioTrackCreate(    ){
    ...
    track = new android::AudioTrack(    );
    ...
}

다른 얘기는 그만하고 블루투스 영역에 들어갔으니까 내가 잘하는 게 아니야.
여기서 내가 말하고 싶은 것은 그들이 호출한 것은 네이티브의 오디오 트랙 종류이다.이것이 문제다. 구글은 이런 상황을 고려하지 않았을 것이다.아무튼 오디오 트랙으로.cpp 이 파일의 native Audio Track 클래스가 재생되는 app는 재생 상태가 track에 의해 재생되지 않습니다.
4. 문제 해결
우리가 문제점을 찾았으니우리는 Audio Service의player Event 방법을 어떻게 사용하든지 방법을 강구하면 된다. (왜 Player Base가 아니냐고 묻지 마라. 나는 Audio Track이 Player Base 종류를 계승하도록 해야 한다. 힘들다.)
PlayerBase의 소스를 살펴보니 다음과 같습니다.
//PlayerBase.cpp
PlayerBase::PlayerBase() {
    ...
        sp<IBinder> binder = defaultServiceManager()->checkService(String16("audio"));
    	mAudioManager = interface_cast<IAudioManager>(binder);
    ...
}

네, 잘못 보지 않았습니다. cpp 코드는 바로bindjava의 서비스입니다.
잠깐 기다리다
주제 밖의 말을 하다.
//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 호출이 죽어도 성공하지 못하는 문제를 해결하게 했다.
IAudio Manager 방법의 정의는 자바 층의 IAudio 서비스와 같습니다.aidl 일치 유지.주로 방법의 편이이다
예를 들어 이것:
IBinder::FIRST_CALL_TRANSACTION + 70,//         70

FIRST_CALL_TRANSACTION은 첫 번째 방법인 ADJUSTSUGGESTEDSTREAMVOLUME를 가리킨다. 만약에 자바층의IAudio 서비스가 있다면.aidl에 추가 방법이 있습니다. (예를 들어 Audio Manager가 앱에 새로운 인터페이스를 추가하면 Audio 서비스는 대응하는 방법을 추가해야 하고 IAudio 서비스.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 이 서비스는 유쾌하게 호출할 수 있습니다.
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();
    ...
}

그리고 대응하는 Btif Avrcp Audio Track Create가 호출되는 곳에도 그에게 유일하게 정의된 매개 변수를 추가합니다.이 점에서 말하자면 C++의 이 기본 매개 변수 메커니즘은 정말 쓰기 좋다.함수의 정의는 변할 수 있고, 파라미터를 추가할 수 있으며, 이전에 이 함수를 호출한 코드에 영향을 주지 않는다.우박, 우박!
좋아, 문제 해결, 글 다 쓰고 끝내자!

좋은 웹페이지 즐겨찾기