天天看點

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

在上小節中,簡單的使用C++編寫了一個錄音程式,并且嘗試去運作兩個程式去錄音,然後發現第二個程式不能錄音。不能運作。如果我們想去解決這個問題,我們需要深入的去了解一下android系統中錄音的架構,看一下這個架構是否合理,能否進行改進。

先來回顧一下,播放聲音時的音頻架構:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

假設系統中有兩個聲霸卡,每個聲霸卡有輸入,輸出功能,對于他的輸出功能,我們稱為輸出通道output,AudioFlinger中有一個PlaybackThread與他對應。應用程式想要播放聲音的時候,建立一個AudioTrack,同時指定聲音的類型,AudioFlinger會根據這個聲音的類型,找到對應的聲霸卡,再找到對應的PlaybackThread播放線程,然後在PlaybackThread中建立一個Track,這個Track與AudioTrack對應,他們之間通過共享記憶體傳遞資料,PlaybackThread播放聲音的時候,會查詢内部的一個或者多個Track,取出裡面的資料混合在一起發送給聲霸卡。

對個APP對應AudioFlinger中的一個PlaybackThread,APP需要播放聲音時,傳遞給PlaybackThread,PlaybackThread混合之後進行播放。這樣就不存在多個APP競争一個輸出通道的問題。以上講解的是android系統中播放聲音的架構。

那麼我們能不能模仿播放的架構,完成一個錄音的理想礦建,圖示如下:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

對于每個聲霸卡都有錄制聲霸卡的inpout通道,如果我們在AudioFlinger對每個通道建立一個RecordThread。當APP需要錄制聲音的時候,其建立AduioRecord指定錄音的來源,然後找到指定的聲霸卡,這樣就找了RecordThread線程,在RecordThread中建立track,其與APP中的AduioRecord一一對應。即RecordThread從硬體擷取資料發送給每個track,track同步到AduioRecord(通過共享記憶體)。這樣就能實作多APP同時錄音的功能。這裡也是多個APP對應一個線程,這樣就不存在多個APP程序一個線程,或者說一個output的問題。

下面我們在來看看現實的架構,如下圖:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

如上所示,當應用程式錄音的時候,會建立一個AduioRecord,其會指定聲音來源,根據聲音來源找到聲霸卡,然後建立一個RecordThread,如果第二個APP想要錄制聲音,其也會在AudioFlinger中建立一個RecordThread,從圖中我們可以知道一個AduioRecord對應一個RecordThread,這樣就就會導緻一個聲霸卡對應多個RecordThread,這些線程會競争使用聲霸卡的問題,兩個線程之間需要互斥的通路聲霸卡,進行錄音。

為了解決這個問題,android系統做了一個簡單粗暴的方法,禁止多個APP同時錄音,同一時間,隻能存在一個APP進行錄音,下面是文字性的一些總結

a. APP建立、設定AudioRecord, 
   指定了聲音來源: inputSource, 比如: AUDIO_SOURCE_MIC
   還指定了采樣率、通道數、格式等參數
b. AudioPolicyManager根據inputSource等參數确定錄音裝置: device
c. AudioFlinger建立一個RecordThread, 以後該線程将從上述device讀取聲音資料
d. 在RecordThread内部為APP的AudioRecord建立一個對應的RecordTrack
   APP的AudioRecord 與 RecordThread内部的RecordTrack 通過共享記憶體傳遞資料
e. RecordThread從HAL中得到資料, 再通過内部的RecordTrack把資料傳給APP的AudioRecord

注意: 
在原生代碼中,
APP的一個AudioRecord會導緻建立一個RecordThread,
在一個device上有可能存在多個RecordThread,
任意時刻隻能有一個RecordThread在運作,
是以隻能有一個APP在錄音,不能多個APP同時錄音
           

下面是錄音架構的流程圖:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

注意看流程中的紅色字型标記的函數,其為重要函數,如下:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

通過以上的流程,我們直接以知道,隻要應用程式建立了AudioRecord,并且制定了來源,那麼就會有一個對應的RecordThread線程被建立。

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

結合源碼,我們可以知道圖上的AudioFlinger中,存在一個AudioIputDescriptor數組,以及一個mInputs,其每個mInput與一個線程意義對應,mInput中存在一個索引值,根據索引值,可以找到對應的AudioIputDescriptor。當然,也能根據這個索引值找到RecordThread。

在上小節中,錯誤是發生在第二個程式的if(pAudioRecord->start()!= android::NO_ERROR)處出現了問題,pAudioRecord->start()的調用流程,在時序圖中已經給出,其最終會調用到:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

其上流程,就是導緻錯誤的原因,如果我們隻是簡單的吧錯誤的函數去掉,這樣可以嗎?

是不行的,其會到導緻多個線程,同時對聲霸卡進行通路,假設有兩個應用程式同時錄音,則APP1錄制部分聲音,APP2錄制部分聲音。他們錄音時得到的聲音都不完全。每個APP隻能得到部分聲音資料,則并不是我們想要的結果,下小節我們講解如何修改代碼。

我們繼續分析一下架構,假設上面的pAudioRecord->start()執行成功,應用程式開始讀取資料,其會調用read函數:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

沒有資料的時候,其會進入休眠等待資料,提供資料的為thraadLoop線程:

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第002節_錄音架構及代碼流程

其會從硬體上讀取資料。在Thread.cpp中,找到函數:

bool AudioFlinger::RecordThread::threadLoop()
      // otherwise use the HAL / AudioStreamIn directly
     } else {
     	/*使用HAL層讀取資料*/
     	ssize_t bytesRead = mInput->stream->read(mInput->stream,(uint8_t*)mRsmpInBuffer + rear * mFrameSize, mBufferSize);
     		/*儲存資料*/
     		framesRead = bytesRead;
      // loop over each active track,得到每個active track
      for (size_t i = 0; i < size; i++) {
           

在讀取帶資料之後,會分發給每一個活躍的Track,即發送給一個或者多個APP。可以知道AudioFlinger層是支援多APP錄音的,隻是在AudioPolicyManager在禁止了。下小節我們修改AudioPolicyManager的代碼。

下面是該小節的總結:

錄音架構及代碼流程

a. APP建立、設定AudioRecord, 
   指定了聲音來源: inputSource, 比如: AUDIO_SOURCE_MIC
   還指定了采樣率、通道數、格式等參數
b. AudioPolicyManager根據inputSource等參數确定錄音裝置: device
c. AudioFlinger建立一個RecordThread, 以後該線程将從上述device讀取聲音資料
d. 在RecordThread内部為APP的AudioRecord建立一個對應的RecordTrack
   APP的AudioRecord 與 RecordThread内部的RecordTrack 通過共享記憶體傳遞資料
e. RecordThread從HAL中得到資料, 再通過内部的RecordTrack把資料傳給APP的AudioRecord

注意: 
在原生代碼中,
APP的一個AudioRecord會導緻建立一個RecordThread,
在一個device上有可能存在多個RecordThread,
任意時刻隻能有一個RecordThread在運作,
是以隻能有一個APP在錄音,不能多個APP同時錄音
           

繼續閱讀