Android音频(6)——音频系统分析
一、AudioPolicyService啟動過程分析
1. 播放聲音,聲音從哪個設備播放出來是由audio policy決定的。
2.在每一個聲卡,對應一個output,一個output對應系統中都有一個線程與其對應。
3.對硬件的訪問操作是由AudioFlinger來完成的
4.AudioPolicyService在啟動時會去讀取解析配置文件/system/etc/audio_policy.conf 根據配置文件來操作AudioFlinger來打開output,創建線程。
5.tiny4412上的配置文件
# cat /system/etc/audio_policy.conf
global_configuration {
attached_output_devices AUDIO_DEVICE_OUT_SPEAKER
default_output_device AUDIO_DEVICE_OUT_SPEAKER
attached_input_devices AUDIO_DEVICE_IN_BUILTIN_MIC
}
audio_hw_modules { //注意這里是modules,里面的每一項都表示一個module
primary { //這是一個module,一個module對一個廠家提供的一個.so文件
outputs { //注意這是outputs,里面的每一項都是一個output
primary { //這是一個output,里面的參數是對這個output的配置,格式“name value”
sampling_rates 44100
channel_masks AUDIO_CHANNEL_OUT_STEREO
formats AUDIO_FORMAT_PCM_16_BIT
devices AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_EARPIECE|AUDIO_DEVICE_OUT_WIRED_HEADSET|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|AUDIO_DEVICE_OUT_ALL_SCO|AUDIO_DEVICE_OUT_AUX_DIGITAL
flags AUDIO_OUTPUT_FLAG_PRIMARY
}
}
inputs {
primary {
sampling_rates 8000|11025|12000|16000|22050|24000|32000|44100|48000
channel_masks AUDIO_CHANNEL_IN_MONO|AUDIO_CHANNEL_IN_STEREO
formats AUDIO_FORMAT_PCM_16_BIT
devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_WIRED_HEADSET|AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET|AUDIO_DEVICE_IN_AUX_DIGITAL|AUDIO_DEVICE_IN_VOICE_CALL
}
}
}
}
6.AudioPolicyService啟動過程分析
a. 加載解析/vendor/etc/audio_policy.conf或/system/etc/audio_policy.conf 對于配置文件里的每一個module項, new HwModule(name), 放入mHwModules數組 對于module里的每一個output, new IOProfile, 放入module的mOutputProfiles 對于module里的每一個input, new IOProfile, 放入module的mInputProfiles b. 根據module的name加載廠家提供的so文件 (通過AudioFlinger來加載) 這里很重要!!! c. 打開對應的output (通過AudioFlinger來open output)
7.HwModule是來自配置文件中的audio_hw_modules中的成員primary
8.instantiate()用于注冊服務
main() //framework/main_mediaserver.cpp
AudioPolicyService::instantiate(); //framework/main_mediaserver.cpp
這個instantiate()實現在BinderService類中,用于add Service.
二、AudioFlinger啟動過程分析
參考002 UML
1.加載的.so文件在/system/lib/hw下
2 AudioFlinger啟動過程分析
a. 注冊AudioFlinger服務
b. 被AudioPolicyService調用以打開廠家提供的so文件
b.1 加載哪個so文件? 文件名是什么? 文件名從何而來?
名字從/system/etc/audio_policy.conf得到module的名字為: primary,所以so文件就是 : audio.primary.XXX.so, eg. audio.primary.tiny4412.so(來自audio_hw_hal.cpp和AudioHardware.cpp)
b.2 該so文件由什么源文件組成? 查看Android.mk
audio.primary.$(TARGET_DEVICE) : device/friendly-arm/common/libaudio/AudioHardware.cpp
libhardware_legacy
libhardware_legacy : hardware/libhardware_legacy/audio/audio_hw_hal.cpp
結論:主要是由AudioHardware.cpp和audio_hw_hal.cpp構成。
b.3 對硬件的封裝:
AudioFlinger.cpp : 把硬件封裝成AudioHwDevice (放入mAudioHwDevs數組中)
audio_hw_hal.cpp : 把硬件封裝成audio_hw_device
廠家 : 把硬件封裝成AudioHardware (派生自: AudioHardwareInterface)
AudioHwDevice是對audio_hw_device的封裝,
audio_hw_device中函數的實現要通過AudioHardware類對象
c. 被AudioPolicyService調用來open output、創建playback thread
三、AudioTrack創建過程
1.AudioTrack::AudioTrack()調用的set()中創建一個AudioTrack并與硬件掛鉤
2.Android中使用一個output來描述聲卡的輸出通道。一個output對應一個playbackThread,這些信息由AudioFilnger決定
3.AudioTrack創建過程概述
a.1 C++實現測試程序:
frameworks/base/media/tests/audiotests/shared_mem_test.cpp
內容為自己構造一個正弦波音頻,一直播放知道Ctrl+C結束。
a.2 java實現的測試程序:(目前還沒測試)
frameworks/base/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioTrackTest.java
播放聲音時都要創建AudioTrack對象(這個AudioTrack對象可以是C++實現的,也可以是Java實現的),
java的AudioTrack對象創建時會導致C++的AudioTrack對象被創建; 所以分析的核心是c++的AudioTrack類, 創建AudioTrack時涉及一個重要函數: set
b. 猜測創建過程的主要工作
b.1 set()使用AudioTrack的屬性, 根據AudioPolicy找到對應的output、playbackThread(一個output就對應一個playbackThread)
b.2 在playbackThread中創建對應的track
b.3 APP的AudioTrack 和 playbackThread的mTracks中的track之間建立共享內存
c. 源碼時序圖 003 UML
四、AudioTrack創建過程_選擇output
1. AudioTrack創建過程_選擇output
a. APP構造AudioTrack時指定了 stream type
b. AudioTrack::setAttributesFromStreamType 根據stream type設置屬性
c. AudioPolicyManager::getStrategyForAttr 根據屬性獲得聲音的strategy(類別)
d. AudioPolicyManager::getDeviceForStrategy 根據類別選擇聲音從哪個device里面播放
e. AudioPolicyManager::getOutputForDevice 根據device獲得output.
//可能有多個output支持同一類別的一個device
e.1 AudioPolicyManager::getOutputsForDevice //獲取這個device的所有output
e.2 output = selectOutput(outputs, flags, format);//從這個device的多個output中選擇某一個output
2.AudioPolicyManager.mOutputs對應于已打開的output,每一項都含有mProfile,這個mProfile是來自/system/etc/audio_policy.conf,mProfile中指明該output支持哪些device。
DefaultKeyedVector<audio_io_handle_t, sp<AudioOutputDescriptor> > mOutputs;
3.有可能有多個output都支持某個device,怎么從多個output中取出最合適的output?
AudioPolicyManager::selectOutput中 首先匹配flag,找出最吻合的output: a.APP創建AudioTrack時會傳入flag b.output對應的profile中也有flag(來自/system/etc/audio_policy.conf中指定的flag), c.使用上述a,b的flag進行比較,取出吻合度最高的output. 如果吻合度相同: 如果primary output支持該設備,選它,否則取出第一個output。
五、AudioTrack創建過程_Track和共享內存
1.一個output對應一個播放設備(聲卡),也對應一個播放線程。音頻數據流向playbackThread ---> output ---> 聲卡設備
2.應用程序中的AudioTrack和播放線程中的mTrack中的成員是一一對應的。它們之間通過共享內存來傳遞數據。
3. AudioTrack創建過程_Track和共享內存
回顧:
a. APP創建AudioTrack <-----------------> AudioFlinger中PlaybackThread創建對應的Track
b. APP給AudioTrack提供音頻數據有2種方式: 一次性提供(MODE_STATIC)、邊播放邊提供(MODE_STREAM)
問:
a. 音頻數據存在buffer中, 這個buffer由誰提供? APP 還是 PlaybackThread ?
(1)MODE_STATIC:若是App一次性提供音頻數據,那么buffer是由App創建的,因為App更方便知道buffer的大小屬性。
(2)MODE_STREAM:若App是邊播放邊提供音頻數據,那么就由playbackThread創建,這樣App實現起來比較方便。
b. APP提供數據, PlaybackThread消耗數據, 如何同步?
(1)MODE_STATIC:一次性提供,就不需要同步,這是一前一后的事情。
(2)MODE_STREAM:邊播放邊提供音頻數據的時候,這是典型的生產者消費者問題,使用環形緩沖區來同步。
4.playbackThread中的mTracks是個鏈表,上面的每一個Track都對應應用程序中的一個AudioTrack. 也正是由于App創建了
AudioTrack才導致mTracks鏈表上的track被創建。
5.App和playbackThread是處于不同的進程中的,音頻數據可以通過binder通信,但是效率不是很高,所以使用共享內存來傳遞數據。
6.測試程序shared_mem_test.cpp中使用的就是一次性提供:
AudioTrackTest::Test01()
iMem = heap->allocate(BUF_SZ*sizeof(short));
memcpy(p, smpBuf, BUF_SZ*sizeof(short));
sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
rate,
AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
AUDIO_CHANNEL_OUT_MONO,
iMem); /*這里一次性把音頻數據傳下去了*/
對于C++的實現,參數iMem若是不為NULL就表示這個共享內存是由App創建的,之后就會使用MODE_STATIC這個方式傳輸數據;若為NULL,表明App并沒有去創建這個buffer,將由playbackThread來創建這個共享內存,之后將通過MODE_STREAM方式傳輸數據;
AudioTrack它并沒有一個模式來分辨是使用MODE_STATIC還是MODE_STREAM,它通過參數iMem是NULL還是非NULL來分辨使用哪種方式。
C++的AudioTrack類不需要指定模式,但是Java的AudioTrack需要:
MediaAudioTrackTest.java
testSetPlaybackHeadPositionTooFar()
int TEST_MODE = AudioTrack.MODE_STREAM;
AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, 2*minBuffSize, TEST_MODE);
testSetPlaybackRateUninit()
int TEST_MODE = AudioTrack.MODE_STATIC;
AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, minBuffSize, TEST_MODE);
7.AudioTrack構造函數:
AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId) //AudioTrack.java
//本地初始化
native_setup(new WeakReference<AudioTrack>(this), mAttributes,
mSampleRate, mChannels, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session);
//對應這個JNI函數
android_media_AudioTrack_setup //android_media_AudioTrack.cpp
//本地的AudioTrack類對象,沒有傳入任何參數
sp<AudioTrack> lpTrack = new AudioTrack();
case MODE_STREAM:
//對于MODE_STREAM模式,直接調用set(),沒有在App進程中分配內存
lpTrack->set()
case MODE_STATIC:
//對于MODE_STATIC模式,會在App進程中分配buffer
lpJniStorage->allocSharedMem(buffSizeInBytes)
lpTrack->set()
六、音頻數據的傳遞
1.App這邊相關的代碼在AudioTrack.cpp中,playbackTread的處理函數在Track.cpp中。
2.mProxy用于App端管理共享內存
// frameworksavmedialibmediaAudioTrack.cpp
AudioTrack::createTrack_l()
// update proxy
if (mSharedBuffer == 0) {
mStaticProxy.clear();
mProxy = new AudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
} else {
mStaticProxy = new StaticAudioTrackClientProxy(cblk, buffers, frameCount, mFrameSizeAF);
mProxy = mStaticProxy;
}
mServerProxy類是用于給播放線程管理內存的
// frameworksavservicesaudioflingerTracks.cpp
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;
3.音頻數據的傳遞總結
a. APP創建AudioTrack, playbackThread創建對應的Track,它們之間通過共享內存傳遞音頻數據 b. APP有2種使用共享內存的方式: b.1 MODE_STATIC: APP創建共享內存, APP一次性填充數據 b.2 MODE_STREAM: APP使用obtainBuffer獲得空白內存, 填充數據后使用releaseBuffer釋放內存 c. playbackThread使用obtainBuffer獲得含有數據的內存, 使用數據后使用releaseBuffer釋放內存 d. App端的AudioTrack中含有mProxy,它被用來管理共享內存,里面含有obtainBuffer,releaseBuffer函數; 播放線程端的Track中含有mServerProxy,它被用來管理共享內存,里面含有obtainBuffer, releaseBuffer函數 對于不同的MODE, 這些Proxy指向不同的對象。 e. 對于MODE_STREAM, APP和playbackThread使用環型緩沖區的方式傳遞數據。
5.環形緩沖區講解
a.初始R=0,W=0,buff的長度為LEN b.寫入一個數據 w = W % LEN; buff[w] = data; W++; c.讀出一個數據 r = R % LEN; data = buff[r]; R++; 判斷緩沖區滿:R + LEN == W 判斷緩沖區空:R == W 由上可以看出R和W是一直遞增的。 判斷溢出時,R和W同時都減少LEN的整數倍大小,將不影響結果 改進: 將LEN圓整到2的n次方,此時W%LEN這個可能比較慢的操作就可以轉換為w=W&(LEN-1)
七、PlaybackThread處理流程
1.聲卡往往只支持一種格式的音頻數據(如2-Channel,44K-Sample, 16bit-Deep)。但是App可能會傳遞下來不同格式的音頻數據,
這些數據在在playbackThread中進行重采樣,采樣到聲卡支持的格式。mAudioMixer對象負責這一工作。重采樣之后還要把各個App
的音頻數據混合起來,稱為混音,也是由mAudioMixer負責的。
2.hook成員指向不同的處理函數
//frameworksavservicesaudioflingerAudioMixer.cpp process__nop():手機靜音了,不做任何處理 process__genericNoResampling(): 不需要重采樣 process__genericResampling(): 應該就是進行重采樣
3. PlaybackThread 處理流程
AudioFlinger::PlaybackThread::threadLoop() frameworksavservicesaudioflingerThreads.cpp a. prepareTracks_l : 確定enabled track, disabled track。對于enabled track, 設置mState.tracks[x]中的參數 b. threadLoop_mix : 處理數據(比如重采樣)、混音 確定hook: 逐個分析mState.tracks[x]的數據, 根據它的格式確定tracks[x].hook。再確定總的mState.hook 調用hook: 調用總的mState.hook即可, 它會再去調用每一個mState.tracks[x].hook 混音后的數據會放在mState.outputTemp臨時BUFFER中然后轉換格式后存入 thread.mMixerBuffer c. memcpy_by_audio_format : 把數據從thread.mMixerBuffer或thread.mEffectBuffer復制到thread.mSinkBuffer d. threadLoop_write: 把thread.mSinkBuffer寫到聲卡上 e. threadLoop_exit
4.總結
原始數據會保存在共享內存中,經過重采樣等處理后存放在一個臨時的outputTemp buffer中,這個臨時buffer中的數據保存的是重采樣后的數據,數據來源是多個Track。最終的可播放的數據經過混音后存放在mMixerBuffer這個buffer中。如果需要進行音效處理(比如加強低音),還有一部分數據會被放在mEffectBuffer中。之后會把mMixerBuffer和mEffectBuffer中的音頻數據都輸出到mSinkBuffer中,然后從mSinkBuffer中將數據發給聲卡硬件。
總結
以上是生活随笔為你收集整理的Android音频(6)——音频系统分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SAP Cloud for Custom
- 下一篇: PRD文档怎么写