天天看點

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

在上小節中我們講解了,在AudioTrack建立過程中,他會選擇一個output,一個output對應一個播放裝置,他也對應着一個播放線程,該小節我們講解在這個線程之中,怎麼去建立一個Track,應用程式的AudioTrack與播放線程之中的Track是一一對應的,并且我們還會講解應用程式的AudioTrack與播放線程之中的Track他們之間是怎麼傳遞資料的,也就是說,他們是通過怎樣的記憶體傳遞資料的。

之前提到過,作為聲音的應用程式,他需要給playbackThread線程提供資料,他是怎麼提供的呢?APP給AudioTrack提供音頻資料有2種方式: 一次性提供(MODE_STATIC)、邊播放邊提供(MODE_STREAM)

對于簡單的提示音,其資料是十分的少的,我們可以把所有的資料,一次性提供給播放線程,那麼對于播放音樂,播放網絡傳遞過來的聲音,那麼我們需要邊播放,邊提供。

那麼有以下幾個問題:

1.音頻資料儲存在buffer中,這個buffer由誰提供呢?APP還是PlaybackTHread。

2.APP提供資料,PlaybackThread消耗資料,如何同步。

我們先來畫一個圖:

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

我們知道在android系統之中,其可能有多個聲霸卡,如上的聲霸卡1,聲霸卡2。每個聲霸卡對應一個output,同時每個output對應一個播放線程PlaybackThread,PlaybackThread中存在一個數組mTracks,其中包含一個或者多個Track,每一個Track都對應應用程式中建立的AudioTrack,正是因為應用程式APP建立了AudioTrack才會導緻PlaybackThread中Track的建立(注意:APP與PlaybackThread處于不同的程序)。

那麼他們是怎麼傳遞資料的呢?雖然可以使用binder通信,但是其通信的效率并不受很高,其實在聲音資料,APP與PlaybackThread聲音資料的傳遞是使用共享記憶體(即APP與PlaybackThread可以通路同一塊記憶體)。

現在我們先來解決第一個問題,這塊共享記憶體是由誰來建立的,是由APP還是由PlaybackThread呢?我們分為兩種情況

1.如果APP提供AudioTrack的資料是一次性提供(MODE_STATIC:一次性,提前提供資料),那麼顯然這個buffer當然是由應用程式提供。因為應用程式才知道這個buffer有多大。

2.如果應用程式,是一邊播放,一邊提供資料(MODE_STREAM),那麼就由PlaybackThread建立共享記憶體,因為這樣省事(為了簡單應用程式的程式設計)

那麼應用程式APP與PlaybackThread如何同步資料呢?

1.如果APP提供AudioTrack的資料是一次性提供(MODE_STATIC:一次性,提前提供資料),APP先構造,PlaybackThread再消費,則無需進行同步。

2.如果應用程式,是一邊播放,一邊提供資料(MODE_STREAM),使用環形緩沖區進行同步。

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

我們回答了以上問題,現在我們需要從源碼中,驗證以上結果。

先打開測試程式shared_mem_test.cpp檔案:

int AudioTrackTest::Test01() {
	/*首先自己配置設定了buffer*/
	iMem = heap->allocate(BUF_SZ*sizeof(short));
	
	/*設定好資料*/
	p = static_cast<uint8_t*>(iMem->pointer());
    memcpy(p, smpBuf, BUF_SZ*sizeof(short));

	/*建立AudioTrack,其會傳入共享記憶體iMem,iMem如果為NULL則MODE_STREAM模式,共享記憶體由PlaybackThread建立*/
	sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type,
			   rate,
               AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
               AUDIO_CHANNEL_OUT_MONO,
               iMem);

           

我們打開AudioTrack.cpp檢視AudioTrack的構造函數:

AudioTrack::AudioTrack(
	mStatus = set(streamType, sampleRate, format, channelMask.......
           

以上的是c++的測試程式,之前我們還提到過java的測試程式,打開MediaAudioTrackTest.java檔案,随便檢視一個函數如:

public void testSetStereoVolumeMax() throws Exception {
	final int TEST_MODE = AudioTrack.MODE_STREAM;
	 AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE);	
           

可以看到,其指定了TEST_MODE = AudioTrack.MODE_STREAM,即為一邊播放,一邊提供資料(MODE_STREAM)模式,對于C++中,我們是不需要指定的。

我們檢視java中AudioTrack的構造函數:

public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,int mode, int sessionId)
	int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
           

看到native_setup,我們就知道,其會調用C++中的函數,我們在源碼中進行搜尋native_setup,可以找到android_media_AudioTrack.cpp檔案,可以找到其對應函數:

android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this, jobject jaa,jintArray jSampleRate, jint channelPositionMask, jint channelIndexMask,jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession,jlong nativeAudioTrack) {
	lpTrack = new AudioTrack();
	case MODE_STREAM:
		status = lpTrack->set()

       case MODE_STATIC:   
       	/*配置設定共享記憶體*/   
       	if (!lpJniStorage->allocSharedMem(buffSizeInBytes)) {
		status = lpTrack->set()
           

可以看出,如果為MODE_STREAM沒事,則APP不會建立共享記憶體,如果MODE_STATIC(一次性),則會建立共享記憶體。

其上C++與java的調用過程總結如下圖:

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

可以知道無論C++的AudioTrack還是javaAudioTrack最終都是建立了一個c++的AudioTrack對象,即c++的構造函數被調用,進而其中的set函數也被調用。

之前我們強調,建立AudioTrack對象,就會導緻某個PlaybackThread中建立一個Track對象,這裡截圖一下之前的時序圖:

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

其調用關系就不進行重複了。最後得出結論,APP建立一個AudioTrack對象,導緻PlaybackThread中new Trac被建立。打開Tracks.cpp:

AudioFlinger::PlaybackThread::Track::Track(
            PlaybackThread *thread,
            const sp<Client>& client,
            audio_stream_type_t streamType,
            uint32_t sampleRate,
            audio_format_t format,
            audio_channel_mask_t channelMask,
            size_t frameCount,
            void *buffer,
            const sp<IMemory>& sharedBuffer,//該為應用程式建立的共享記憶體
            audio_session_t sessionId,
            int uid,
            audio_output_flags_t flags,
            track_type type)
     :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount,
                  (sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
                  sessionId, uid, true /*isOut*/,
                  (type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
                  type),
           

我們可以看到sharedBuffer,其為應用程式建立的共享記憶體,其也有可能為空,我們來想象一下Track執行個體化對象中,他會做什麼事情,如果應用程式已經建構了sharedBuffer,那麼他自己則不會再去建構,如果應用程式沒有建構,即傳入的sharedBuffer為空,則其會配置設定sharedBuffer。

從上可以看出,Track由TrackBase派生出來,我們檢視一下TrackBase的構造函數,還是在Tracks.cpp檔案中:

AudioFlinger::ThreadBase::TrackBase::TrackBase(
	/*如果buffer為空,并且alloc == ALLOC_CBLK*/
	if (buffer == NULL && alloc == ALLOC_CBLK) {
		/*size為一個頭部(起到控制作用),如果buffer為NULL,即應用程式沒有配置設定,則大小增加bufferSize*/
		size += bufferSize;
	/*配置設定記憶體*/
	mCblkMemory = client->heap()->allocate(size);
	//如果buffer為應用程式傳入
    case ALLOC_NONE:
       mBuffer = buffer; //則mBuffer指向應用程式提供的buffer	
           
08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

在TrackBase的構造函數被調用之後,會引起Track的構造函數被調用:

AudioFlinger::PlaybackThread::Track::Track(
	if (sharedBuffer == 0) {
        mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize, !isExternalTrack(), sampleRate);
    } else {
        mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                mFrameSize);
    }
    mServerProxy = mAudioTrackServerProxy;
           

從上知道,如果應用程式沒有建立sharedBuffer,那麼氣會建立一個AudioTrackServerProxy,用來管理Buffer,如果提供了sharedBuffer,則會建立StaticAudioTrackServerProxy,用來管理者一個buffer,到底如何管理呢?下小節為大家講解。

同樣,在應用程式那邊,AudioTrack肯定也需要對buffer進行管理,打開AudioTrack.cpp:

status_t AudioTrack::set(
	status_t status = createTrack_l();
	/*使用audioFlinger的服務建立Track,會導緻播放線程中建立Track*/
	sp<IAudioTrack> track = audioFlinger->createTrack()
	/*如果沒有提供buffer,指向PlaybackThread提供的buffer*/
    if (mSharedBuffer == 0) {
        buffers = cblk + 1;
    } else {
    	/*否則指向自己提供的buffer*/
        buffers = mSharedBuffer->pointer();
        if (buffers == NULL) {
            ALOGE("Could not get buffer pointer");
            return NO_INIT;
        }
    }                                                  
	
	/*管理共享記憶體*/
    // update proxy
    if (mSharedBuffer == 0) {
        mStaticProxy.clear();
        mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
    } else {
        mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSize);
        mProxy = mStaticProxy;
    }
           

可以看看,其與Track中的是意義對應的,AudioTrackClientProxy對應AudioTrackServerProxy,StaticAudioTrackClientProxy對應StaticAudioTrackServerProxy。都是對buffer,共享記憶體的管理。

APP建立得到AudioTrack對象之後,引起PlaybackThread中Track的建立,具體流程如下:

08.音頻系統:第004課_Android音頻系統詳解:第009節_AudioTrack建立過程_Track和共享記憶體

繼續閱讀