本章主要內(nèi)容
·? 詳細(xì)分析AudioTrack。
·? 詳細(xì)分析AudioFlinger。
·? 詳細(xì)分析AudioPolicyService。
本章涉及的源代碼文件名及位置
下面是本章分析的源碼文件名及其位置。
·? AudioTrack.java
framework/base/media/java/com/android/media/AudioTrack.java
·? android_media_track.cpp
framework/base/core/jni/android_media_track.cpp
·? MemoryHeapBase
framework/base/libs/binder/MemoryHeapBase.cpp
·? MemoryBase.h
framework/base/include/binder/MemoryBase.h
·? AudioTrack.cpp
framework/base/libmedia/AudioTrack.cpp
·? audio_track_cblk_t聲明
framework/base/include/private/media/AudioTrackShared.h
·? audio_track_cblk_t定義
framework/base/media/libmedia/AudioTrack.cpp
·? Main_MediaServer.cpp
framework/base/media/mediaserver/Main_MediaServer.cpp
·? AudioFlinger.cpp
framework/base/libs/audioFlinger/AudioFlinger.cpp
·? AudioHardwareInterface.h
hardware/libhardware_legacy/include/hardware_legacy/AudioHardwareInterface.h
·? AudioMixer.cpp
framework/base/libs/audioflinger/AudioMixer.cpp
·? AudioSystem.h
framework/base/include/media/AudioSystem.h
·? AudioSystem.cpp
framework/base/media/libmedia/AudioSystem.cpp
·? AudioPolicyInterface.h
hardware/libhardware_legacy/include/hardware_legacy
·? AudioPolicyManagerBase.cpp
framework/base/libs/audioflinger/AudioPolicyManagerBase.cpp
·? AudioService.java
framework/base/media/java/com/android/media/AudioService.java
·? Android_media_AudioSystem.cpp
framework/base/core/Jni/Android_media_AudioSystem.cpp
Audio系統(tǒng)是Android平臺(tái)的重要組成部分,它主要包括三方面的內(nèi)容:
·? AudioRcorder和AudioTrack:這兩個(gè)類屬于Audio系統(tǒng)對(duì)外提供的API類,通過它們可以完成Android平臺(tái)上音頻數(shù)據(jù)的采集和輸出任務(wù)。
·? AudioFlinger:它是Audio系統(tǒng)的工作引擎,管理著系統(tǒng)中的輸入輸出音頻流,并承擔(dān)音頻數(shù)據(jù)的混音,以及讀寫Audio硬件以實(shí)現(xiàn)數(shù)據(jù)的輸入輸出等工作。
·? AudioPolicyService,它是Audio系統(tǒng)的策略控制中心,具有掌管系統(tǒng)中聲音設(shè)備的選擇和切換、音量控制等功能。
Android的Audio系統(tǒng)是我們分析的第一個(gè)具有相當(dāng)難度的復(fù)雜系統(tǒng)。對(duì)于這種系統(tǒng),我采取的學(xué)習(xí)方法是,以一個(gè)常見用例為核心,沿著重要函數(shù)調(diào)用的步驟逐步進(jìn)行深入分析。中途若出現(xiàn)所需但并不熟悉的知識(shí),則以此為契機(jī),及時(shí)學(xué)習(xí)、思考、研究,當(dāng)不熟悉的知識(shí)逐漸被自己了解掌握時(shí),該系統(tǒng)的真面目也隨之清晰了。
下面是破解Audio系統(tǒng)的戰(zhàn)略步驟:
·? 首先,從API類的AudioTrack開始,從Java層到Native層一步步了解其工作原理。其中AudioTrack和AudioFlinger有較多的交互工作,但在這一步中,我們暫時(shí)只集中關(guān)注AudioTrack的流程。
·? 提煉上一步中AudioTrack和AudioFlinger的交互流程,以這個(gè)交互流程作為分析AudioFlinger的突破口。
·? 在前面兩個(gè)步驟中還會(huì)有一些剩余的“抵抗分子”,我們將在AudioPolicyService的破解過程中把它們徹底消滅掉。另外,在分析AudioPolicyService時(shí),還會(huì)通過一個(gè)耳機(jī)插入事件的處理實(shí)例來幫助分析AudioPolicyService的工作流程。
·? 最后,在本章的拓展部分,我們會(huì)介紹一下AudioFlinger中DuplicatingThread的工作原理。
說明:在下文中AudioTrack被簡(jiǎn)寫為AT,AudioFlinger被簡(jiǎn)寫為AF,AudioPolicyService被簡(jiǎn)寫為AP。
讓我們整裝上陣,開始代碼的征程吧!
?
AudioTrack屬于Audio系統(tǒng)對(duì)外提供的API類,所以它在Java層和Native層均有對(duì)應(yīng)類,先從Java層的用例開始。
這個(gè)用例很簡(jiǎn)單,但其中會(huì)有一些重要概念,應(yīng)注意理解。
注意:要了解AudioTrack Java API的具體信息,需要仔細(xì)閱讀Android API中的相關(guān)文檔。閱讀API文檔,是一個(gè)能快速掌握相關(guān)知識(shí)的好方法。
[-->AudioTrackAPI使用例子(Java層)]
//① 根據(jù)音頻數(shù)據(jù)的特性來確定所要分配的緩沖區(qū)的最小size
int bufsize =
???? ?????AudioTrack.getMinBufferSize(8000,//采樣率:每秒8K個(gè)點(diǎn)?????????????????????
?????AudioFormat.CHANNEL_CONFIGURATION_STEREO,//聲道數(shù):雙聲道???????????
??????? AudioFormat.ENCODING_PCM_16BIT//采樣精度:一個(gè)采樣點(diǎn)16比特,相當(dāng)于2個(gè)字節(jié)
);
?
//② 創(chuàng)建AudioTrack
AudioTrack trackplayer = new AudioTrack(
???????????????? AudioManager.STREAM_MUSIC,//音頻流類型
???????????????? 8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
??????????????? ?AudioFormat.ENCODING_PCM_16BIT, bufsize,
???????????????? AudioTrack.MODE_STREAM//數(shù)據(jù)加載模式);
??//③ 開始播放
trackplayer.play() ;
?
......
//④ 調(diào)用write寫數(shù)據(jù)
trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中寫數(shù)據(jù)
......
?//⑤ 停止播放和釋放資源
trackplayer.stop();//停止播放
trackplayer.release();//釋放底層資源
上面的用例引入了兩個(gè)新的概念,一個(gè)是數(shù)據(jù)加載模式,另一個(gè)是音頻流類型。下面進(jìn)行詳細(xì)介紹。
AudioTrack有兩種數(shù)據(jù)加載模式:MODE_STREAM和MODE_STATIC,它們對(duì)應(yīng)著兩種完全不同的使用場(chǎng)景。
·? MODE_STREAM:在這種模式下,通過write一次次把音頻數(shù)據(jù)寫到AudioTrack中。這和平時(shí)通過write系統(tǒng)調(diào)用往文件中寫數(shù)據(jù)類似,但這種工作方式每次都需要把數(shù)據(jù)從用戶提供的Buffer中拷貝到AudioTrack內(nèi)部的Buffer中,這在一定程度上會(huì)使引入延時(shí)。為解決這一問題,AudioTrack就引入了第二種模式。
·? MODE_STATIC:這種模式下,在play之前只需要把所有數(shù)據(jù)通過一次write調(diào)用傳遞到AudioTrack中的內(nèi)部緩沖區(qū),后續(xù)就不必再傳遞數(shù)據(jù)了。這種模式適用于像鈴聲這種內(nèi)存占用量較小,延時(shí)要求較高的文件。但它也有一個(gè)缺點(diǎn),就是一次write的數(shù)據(jù)不能太多,否則系統(tǒng)無法分配足夠的內(nèi)存來存儲(chǔ)全部數(shù)據(jù)。
這兩種模式中以MODE_STREAM模式相對(duì)常見和復(fù)雜,我們的分析將以它為主。
注意:如果采用STATIC模式,須先調(diào)用write寫數(shù)據(jù),然后再調(diào)用play。
在AudioTrack構(gòu)造函數(shù)中,會(huì)接觸到AudioManager.STREAM_MUSIC這個(gè)參數(shù)。它的含義與Android系統(tǒng)對(duì)音頻流的管理和分類有關(guān)。
Android將系統(tǒng)的聲音分為好幾種流類型,下面是幾個(gè)常見的:
·? STREAM_ALARM:警告聲
·? STREAM_MUSIC:音樂聲,例如music等
·? STREAM_RING:鈴聲
·? STREAM_SYSTEM:系統(tǒng)聲音,例如低電提示音,鎖屏音等
·? STREAM_VOCIE_CALL:通話聲
注意:上面這些類型的劃分和音頻數(shù)據(jù)本身并沒有關(guān)系。例如MUSIC和RING類型都可以是某首MP3歌曲。另外,聲音流類型的選擇沒有固定的標(biāo)準(zhǔn),例如,鈴聲預(yù)覽中的鈴聲可以設(shè)置為MUSIC類型。
音頻流類型的劃分和Audio系統(tǒng)對(duì)音頻的管理策略有關(guān)。其具體作用,在以后的分析中再做詳細(xì)介紹。在目前的用例中,把它當(dāng)做一個(gè)普通數(shù)值即可。
在用例中碰到的第一個(gè)重要函數(shù)就是getMinBufferSize。這個(gè)函數(shù)對(duì)于確定應(yīng)用層分配多大的數(shù)據(jù)Buffer具有重要指導(dǎo)意義。先回顧一下它的調(diào)用方式:
[-->AudioTrackAPI使用例子(Java層)]
//注意這些參數(shù)的值。想象我們正在一步步的Trace,這些參數(shù)都會(huì)派上用場(chǎng)
AudioTrack.getMinBufferSize(8000,//每秒8K個(gè)點(diǎn)??????????????????????????????
? ????AudioFormat.CHANNEL_CONFIGURATION_STEREO,//雙聲道??????????????????
??????? AudioFormat.ENCODING_PCM_16BIT);
?
來看這個(gè)函數(shù)的實(shí)現(xiàn):
[-->AudioTrack.java]
static public int getMinBufferSize(intsampleRateInHz, int channelConfig,??
????????????????????????????????? intaudioFormat) {
???????int channelCount = 0;
???????switch(channelConfig) {
???????case AudioFormat.CHANNEL_OUT_MONO:
??????? caseAudioFormat.CHANNEL_CONFIGURATION_MONO:
???????????channelCount = 1;
???????????break;
???????case AudioFormat.CHANNEL_OUT_STEREO:
???????case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
???????????channelCount = 2;//目前最多支持雙聲道
???????????break;
??????? default:
???? ??????return AudioTrack.ERROR_BAD_VALUE;
??????? }
??? ????//目前只支持PCM8和PCM16精度的音頻數(shù)據(jù)???
??????? if((audioFormat != AudioFormat.ENCODING_PCM_16BIT)
???????????&& (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) {
???????????return AudioTrack.ERROR_BAD_VALUE;
??????? }
????? //對(duì)采樣頻率也有要求,太低或太高都不行。
??????? if( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) )
????????????return AudioTrack.ERROR_BAD_VALUE;
??????
?????? /*
??????? 調(diào)用Native函數(shù),先想想為什么,如果是簡(jiǎn)單計(jì)算,那么Java層做不到嗎?
??????? 原來,還需要確認(rèn)硬件是否支持這些參數(shù),當(dāng)然得進(jìn)入Native層查詢了
??????? */
???????int size = native_get_min_buff_size(sampleRateInHz, ????????
???????????????? channelCount,audioFormat);???????????????????????
??????? if((size == -1) || (size == 0)) {
? ???????????return AudioTrack.ERROR;
??????? }
???????else {
???????????return size;
????? ??}
}
Native的函數(shù)將查詢Audio系統(tǒng)中音頻輸出硬件HAL對(duì)象的一些信息,并確認(rèn)它們是否支持這些采樣率和采樣精度。
說明:HAL對(duì)象的具體實(shí)現(xiàn)和硬件廠商有關(guān)系,如果沒有特殊說明,我們則把硬件和HAL作為一種東西討論。
來看Native的native_get_min_buff_size函數(shù)。它在android_media_track.cpp中。
[-->android_media_track.cpp]
/*
注意我們傳入的參數(shù)是:
sampleRateInHertz = 8000,nbChannels = 2
audioFormat = AudioFormat.ENCODING_PCM_16BIT
*/
static jintandroid_media_AudioTrack_get_min_buff_size(????????????????????
???????????????? JNIEnv*env,? jobject thiz,
???????????????? jintsampleRateInHertz, jint nbChannels, jint audioFormat)
{
??? intafSamplingRate;
??? intafFrameCount;
? ??uint32_t afLatency;
???? /*
??????? 下面這些調(diào)用涉及了AudioSystem,這個(gè)和AudioPolicy有關(guān)系。這里僅把它們看成是
信息查詢即可
??? */
?? //查詢采樣率,一般返回的是所支持的最高采樣率,例如44100
??? if(AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {
???????return -1;
}
???? //① 查詢硬件內(nèi)部緩沖的大小,以Frame為單位。什么是Frame?
??? if(AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {
???????return -1;
??? }
??? //查詢硬件的延時(shí)時(shí)間
??? if(AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {
???????return -1;
}
......
這里有必要插入內(nèi)容,因?yàn)榇a中出現(xiàn)了音頻系統(tǒng)中的一個(gè)重要概念:Frame(幀)。
說明:Frame是一個(gè)單位,經(jīng)多方查尋,最終在ALSA的wiki中找到了對(duì)它的解釋。Frame直觀上用來描述數(shù)據(jù)量的多少,例如,一幀等于多少字節(jié)。1單位的Frame等于1個(gè)采樣點(diǎn)的字節(jié)數(shù)×聲道數(shù)(比如PCM16,雙聲道的1個(gè)Frame等于2×2=4字節(jié))。
我們知道,1個(gè)采樣點(diǎn)只針對(duì)一個(gè)聲道,而實(shí)際上可能會(huì)有一或多個(gè)聲道。由于不能用一個(gè)獨(dú)立的單位來表示全部聲道一次采樣的數(shù)據(jù)量,也就引出了Frame的概念。Frame的大小,就是一個(gè)采樣點(diǎn)的字節(jié)數(shù)×聲道數(shù)。另外,在目前的聲卡驅(qū)動(dòng)程序中,其內(nèi)部緩沖區(qū)也是采用Frame作為單位來分配和管理的。
? OK,繼續(xù)native_get_min_buff_size函數(shù)。
?? ......
?? // minBufCount表示緩沖區(qū)的最少個(gè)數(shù),它以Frame作為單位
???uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate);
??? if(minBufCount < 2) minBufCount = 2;//至少要兩個(gè)緩沖
?
?? //計(jì)算最小幀個(gè)數(shù)
?? uint32_tminFrameCount =???????????????
???????? (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;
? //下面根據(jù)最小的FrameCount計(jì)算最小的緩沖大小???
?? intminBuffSize = minFrameCount //計(jì)算方法完全符合我們前面關(guān)于Frame的介紹
???????????* (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)
???????????* nbChannels;
?
??? returnminBuffSize;
}
getMinBufSize會(huì)綜合考慮硬件的情況(諸如是否支持采樣率,硬件本身的延遲情況等)后,得出一個(gè)最小緩沖區(qū)的大小。一般我們分配的緩沖大小會(huì)是它的整數(shù)倍。
好了,介紹完一些基本概念后,開始要分析AudioTrack了。
注意:Java空間的分析包括JNI這一層,因?yàn)樗鼈兌叩年P(guān)系最為緊密。
回顧一下用例中調(diào)用AudioTrack構(gòu)造函數(shù)的代碼:
AudioTrack trackplayer = new AudioTrack(
???????????????? AudioManager.STREAM_MUSIC,
???????????????? 8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,
??????????????? ?AudioFormat.ENCODING_PCM_16BIT,bufsize,
???????????????? AudioTrack.MODE_STREAM);
AudioTrack構(gòu)造函數(shù)的實(shí)現(xiàn)在AudioTrack.java中。來看這個(gè)函數(shù):
[-->AudioTrack.java]
public AudioTrack(int streamType, intsampleRateInHz, int channelConfig,
???????????????? intaudioFormat,int bufferSizeInBytes, int mode)
?? ????????????? ?throws IllegalArgumentException {
???????
??????? mState= STATE_UNINITIALIZED;
??????? //檢查參數(shù)是否合法
???????audioParamCheck(streamType, sampleRateInHz, channelConfig,
???????????????????????? audioFormat,mode);
?? ????//bufferSizeInBytes是通過getMinBufferSize得到的,所以下面的檢查肯定能通過
???????audioBuffSizeCheck(bufferSizeInBytes);
?
??????? /*
?????????? 調(diào)用native層的native_setup,構(gòu)造一個(gè)WeakReference傳進(jìn)去。
???? ??????不了解Java WeakReference讀者可以上網(wǎng)查一下,很簡(jiǎn)單
?????? */
???????int initResult = native_setup(new WeakReference<AudioTrack>(this),
???????? mStreamType,//這個(gè)值是AudioManager.STREAM_MUSIC???
???????? mSampleRate, //這個(gè)值是8000???????
??????? mChannels,???//這個(gè)值是2
??????? mAudioFormat,//這個(gè)值是AudioFormat.ENCODING_PCM_16BIT??????
??????? ?mNativeBufferSizeInBytes,//這個(gè)值等于bufferSizeInBytes??????????????
??????? mDataLoadMode);//DataLoadMode是MODE_STREAM???????
???????? ....
}
OK,native_setup對(duì)應(yīng)的JNI層函數(shù)是android_media_AudioTrack_native_setup。一起來看:
[-->android_media_AudioTrack.cpp]
static int
android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz,???
???????????????????????? jobjectweak_this,jint streamType, ???????
???????????????????????? jintsampleRateInHertz, jint channels,???
???????????????????????? jintaudioFormat, jint buffSizeInBytes,??
???????????????????????? jintmemoryMode)?????????????????
{
??? intafSampleRate;
??? intafFrameCount;
?? ?//進(jìn)行一些信息查詢
??AudioSystem::getOutputFrameCount(&afFrameCount, streamType);
??AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);
??AudioSystem::isOutputChannel(channels);
?? //popCount用于統(tǒng)計(jì)一個(gè)整數(shù)中有多少位為1,有很多經(jīng)典的算法
int nbChannels = AudioSystem::popCount(channels);
??? //Java層的值和JNI層的值轉(zhuǎn)換
??? if(streamType == javaAudioTrackFields.STREAM_MUSIC)
?????? ??atStreamType = AudioSystem::MUSIC;
???
?? intbytesPerSample = audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1;
?? intformat = audioFormat == javaAudioTrackFields.PCM16 ?
????????????????? AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT;
???
??? //計(jì)算以幀為單位的緩沖大小
??? intframeCount = buffSizeInBytes / (nbChannels * bytesPerSample);
?
???? //① AudioTrackJniStorage對(duì)象,它保存了一些信息,后面將詳細(xì)分析
???AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
??? ??......
???? //②創(chuàng)建Native層的AudioTrack對(duì)象
???AudioTrack* lpTrack = new AudioTrack();
?????? if(memoryMode == javaAudioTrackFields.MODE_STREAM) {
? ?????//③STREAM模式
??????lpTrack->set(
???????????atStreamType,//指定流類型
???????????sampleRateInHertz,
???????????format,// 采樣點(diǎn)的精度,一般為PCM16或者PCM8
???????????channels,
???????????frameCount,
???????????0,// flags
???????????audioCallback, //該回調(diào)函數(shù)定義在android_media_AudioTrack.cpp中???
??????? &(lpJniStorage->mCallbackData),
???????????0,
???????????0,// 共享內(nèi)存,STREAM模式下為空。實(shí)際使用的共享內(nèi)存由AF創(chuàng)建
???????????true);//內(nèi)部線程可以調(diào)用JNI函數(shù),還記得“zygote偷梁換柱”那一節(jié)嗎?
?????????} else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {
??????? ??//如果是static模式,需要先創(chuàng)建共享內(nèi)存
?????????lpJniStorage->allocSharedMem(buffSizeInBytes);
?????????lpTrack->set(
???????????atStreamType,// stream type
???????????sampleRateInHertz,
???????????format,// word length, PCM
???????????channels,
???????????frameCount,
???????????0,// flags
???????????audioCallback,
??????? ? &(lpJniStorage->mCallbackData),
???????????0,
???????????lpJniStorage->mMemBase, //STATIC模式下,需要傳遞該共享內(nèi)存
???????????true);
??? }
?
??? ......
??? /*
????? 把JNI層中new出來的AudioTrack對(duì)象指針保存到Java對(duì)象的一個(gè)變量中,
????? 這樣就把JNI層的AudioTrack對(duì)象和Java層的AudioTrack對(duì)象關(guān)聯(lián)起來了,
???? 這是Android的常用技法。? ??????????????????????
?? */???
???? env->SetIntField(thiz,javaAudioTrackFields.nativeTrackInJavaObj,
???????????????? ??? (int)lpTrack);
?? // lpJniStorage對(duì)象指針也保存到Java對(duì)象中
???env->SetIntField(thiz, javaAudioTrackFields.jniData,(int)lpJniStorage);
? }
上邊的代碼列出了三個(gè)要點(diǎn),這一節(jié)僅分析AudioTrackJniStorage這個(gè)類,其余的作為Native AudioTrack部分放在后面進(jìn)行分析。
AudioTrackJniStorage是一個(gè)輔助類,其中有一些有關(guān)共享內(nèi)存方面的較重要的知識(shí),這里先簡(jiǎn)單介紹一下。
共享內(nèi)存,作為進(jìn)程間數(shù)據(jù)傳遞的一種手段,在AudioTrack和AudioFlinger中被大量使用。先簡(jiǎn)單了解一下有關(guān)共享內(nèi)存的知識(shí):
·? 每個(gè)進(jìn)程的內(nèi)存空間是4GB,這個(gè)4GB是由指針長度決定的,如果指針長度為32位,那么地址的最大編號(hào)就是0xFFFFFFFF,為4GB。
·? 上面說的內(nèi)存空間是進(jìn)程的虛擬地址空間。換言之,在應(yīng)用程序中使用的指針其實(shí)是指向虛擬空間地址的。那么,如何通過這個(gè)虛地址找到存儲(chǔ)在真實(shí)物理內(nèi)存中的數(shù)據(jù)呢?
上面的問題,引出了內(nèi)存映射的概念。內(nèi)存映射讓虛擬空間中的內(nèi)存地址和真實(shí)物理內(nèi)存地址之間建立了一種對(duì)應(yīng)關(guān)系。也就是說,進(jìn)程中操作的0x12345678這塊內(nèi)存的地址,在經(jīng)過OS內(nèi)存管理機(jī)制的轉(zhuǎn)換后,它實(shí)際對(duì)應(yīng)的物理地址可能會(huì)是0x87654321。當(dāng)然,這一切對(duì)進(jìn)程來說都是透明的,這些活都由操作系統(tǒng)悄悄地完成了。這和我們的共享內(nèi)存會(huì)有什么關(guān)系嗎?
當(dāng)然有,共享內(nèi)存和內(nèi)存映射有著重要關(guān)系。來看圖7-1“共享內(nèi)存示意圖”:
http://wiki.jikexueyuan.com/project/deep-android-v1/images/chapter7/image001.png" alt="image" />
圖7-1 ?共享內(nèi)存示意圖
圖7-1提出了一個(gè)關(guān)鍵性問題,即真實(shí)內(nèi)存中0x87654321標(biāo)志的這塊內(nèi)存頁(OS的內(nèi)存管理機(jī)制將物理內(nèi)存分成了一個(gè)個(gè)的內(nèi)存頁,一塊內(nèi)存頁的大小一般是4KB)現(xiàn)在已經(jīng)映射到了進(jìn)程A中??伤芡瑫r(shí)映射到進(jìn)程B中嗎?如果能,那么在進(jìn)程A中,對(duì)這塊內(nèi)存頁所寫的數(shù)據(jù)在進(jìn)程B中就能看見了,這豈不就做到了內(nèi)存在兩個(gè)進(jìn)程間共享嗎?
事實(shí)確實(shí)如此,否則我們的生活就不會(huì)像現(xiàn)在這么美好了。這個(gè)機(jī)制是由操作系統(tǒng)提供和實(shí)現(xiàn)的,原理很簡(jiǎn)單,實(shí)現(xiàn)起來卻很復(fù)雜,這里就不深究了。
如何創(chuàng)建和共享內(nèi)存呢?不同系統(tǒng)會(huì)有不同的方法。Linux平臺(tái)的一般做法是:
·? 進(jìn)程A創(chuàng)建并打開一個(gè)文件,得到一個(gè)文件描述符fd。
·? 通過mmap調(diào)用將fd映射成內(nèi)存映射文件。在mmap調(diào)用中指定特定參數(shù)表示要?jiǎng)?chuàng)建進(jìn)程間共享內(nèi)存。
·? 進(jìn)程B打開同一個(gè)文件,也得到一個(gè)文件描述符,這樣A和B就打開了同一個(gè)文件。
·? 進(jìn)程B也要用mmap調(diào)用指定參數(shù)表示想使用共享內(nèi)存,并傳遞打開的fd。這樣A和B就通過打開同一個(gè)文件并構(gòu)造內(nèi)存映射,實(shí)現(xiàn)了進(jìn)程間內(nèi)存共享。
注意,這個(gè)文件也可以是設(shè)備文件。一般來說,mmap函數(shù)的具體工作由參數(shù)中的那個(gè)文件描述符所對(duì)應(yīng)的驅(qū)動(dòng)或內(nèi)核模塊來完成。
除上述一般方法外,Linux還有System V的共享內(nèi)存創(chuàng)建方法,這里就不再介紹了。總之,AT和AF之間的數(shù)據(jù)傳遞,就是通過共享內(nèi)存方式來完成的。這種方式對(duì)于跨進(jìn)程的大數(shù)據(jù)量傳輸來說,是非常高效的。
AudioTrackJniStorage用到了Android對(duì)共享內(nèi)存機(jī)制的封裝類。所以我們有必要先看看AudioTrackJniStorage的內(nèi)容。
[-->android_media_AudioTrack.cpp::AudioTrackJniStorage相關(guān)]
//下面這個(gè)結(jié)構(gòu)就是保存一些變量,沒有什么特別的作用
struct audiotrack_callback_cookie {
???jclass????? audioTrack_class;
???jobject???? audioTrack_ref;
?};
class AudioTrackJniStorage {
???public:
???????sp<MemoryHeapBase>????mMemHeap;//這兩個(gè)Memory很重要
???????sp<MemoryBase>?????????mMemBase;
?
???????audiotrack_callback_cookie mCallbackData;
???????int???????????????????????mStreamType;
?
????? boolallocSharedMem(int sizeInBytes) {
???? /*?
???? ?注意關(guān)于MemoryHeapBase和MemoryBase的用法。???
????? 先new一個(gè)MemoryHeapBase,再以它為參數(shù)new一個(gè)MemoryBase????????
??? */??
?? //① MemoryHeapBase
?? ?mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");
? ?//②MemoryBase
??? mMemBase= new MemoryBase(mMemHeap, 0, sizeInBytes);
??
?? ?return true;
?? }
};
注意代碼中所標(biāo)識(shí)的地方,它們很好地展示了這兩個(gè)Memory類的用法。在介紹它們之前,先來看圖7-2中與這兩個(gè)Memory有關(guān)的家譜。
http://wiki.jikexueyuan.com/project/deep-android-v1/images/chapter7/image002.png" alt="image" />
圖7-2 ?MemoryHeapBase和MemoryBase的家譜
MemoryHeapBase是一個(gè)基于Binder通信的類,根據(jù)前面的Binder知識(shí),BpMemoryHeapBase由客戶端使用,而MemoryHeapBase完成BnMemoryHeapBase的業(yè)務(wù)工作。
從MemoryHeapBase開始分析。它的使用方法是:
mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");
它的代碼在MemoryHeapBase.cpp中。
[-->MemoryHeapBase.cpp]
/*
?? MemoryHeapBase有兩個(gè)構(gòu)造函數(shù),我們用的是第一個(gè)。
? size表示共享內(nèi)存大小,flags為0,name為"AudioTrackHeap Base"
*/
MemoryHeapBase::MemoryHeapBase(size_t size,uint32_t flags,char const * name)
??? :mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
?????mDevice(0), mNeedUnmap(false)
{
??? constsize_t pagesize = getpagesize();//獲取系統(tǒng)中的內(nèi)存頁大小,一般為4KB
??? size =((size + pagesize-1) & ~(pagesize-1));
?? /*???
???? 創(chuàng)建共享內(nèi)存,ashmem_create_region函數(shù)由libcutils提供。
??? ?在真實(shí)設(shè)備上將打開/dev/ashmem設(shè)備得到一個(gè)文件描述符,在模擬器上則創(chuàng)建一個(gè)tmp文件????
? */
?? int fd= ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
? //下面這個(gè)函數(shù)將通過mmap方式得到內(nèi)存地址,這是Linux的標(biāo)準(zhǔn)做法,有興趣的讀者可以看看
?? mapfd(fd,size);
}
MemoryHeapBase構(gòu)造完后,得到了以下結(jié)果:
·? mBase變量指向共享內(nèi)存的起始位置。
·? mSize是所要求分配的內(nèi)存大小。
·? mFd是ashmem_create_region返回的文件描述符。
另外,MemoryHeapBase提供了以下幾個(gè)函數(shù),可以獲取共享內(nèi)存的大小和位置。由于這些函數(shù)都很簡(jiǎn)單,僅把它們的作用描述一下即可。
MemoryHeapBase::getBaseID() //返回mFd,如果為負(fù)數(shù),表明剛才創(chuàng)建共享內(nèi)存失敗了
MemoryHeapBase::getBase()? //共享內(nèi)存起始地址
MemoryHeapBase::getSize() //返回mSize,表示內(nèi)存大小
MemoryHeapBase確實(shí)比較簡(jiǎn)單,它通過ashmem_create_region得到一個(gè)文件描述符。
說明:Android系統(tǒng)通過ashmem創(chuàng)建共享內(nèi)存的原理,和Linux系統(tǒng)中通過打開文件創(chuàng)建共享內(nèi)存的原理類似,但ashmem設(shè)備驅(qū)動(dòng)在這方面做了較大的改進(jìn),例如增加了引用計(jì)數(shù)、延時(shí)分配物理內(nèi)存的機(jī)制(即真正使用的時(shí)候才去分配內(nèi)存)等。這些內(nèi)容,感興趣的讀者還可以自行對(duì)其研究。
那么,MemoryBase是何物?它又有什么作用?
MemoryBase也是一個(gè)基于Binder通信的類,它比起MemoryHeapBase就更顯簡(jiǎn)單了,看起來更像是一個(gè)輔助類。它的聲明在MemoryBase.h中。一起來看:
[-->MemoryBase.h::MemoryBase聲明]
class MemoryBase : public BnMemory
{
public:
???MemoryBase(const sp<IMemoryHeap>& heap,ssize_t offset, size_tsize);
???virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size)const;
?
protected:
??? size_tgetSize() const { return mSize; }//返回大小
??? ssize_tgetOffset() const { return mOffset;}//返回偏移量
??? //返回MemoryHeapBase對(duì)象?????
??? constsp<IMemoryHeap>& getHeap() const { return mHeap;}
};
//MemoryBase的構(gòu)造函數(shù)
MemoryBase::MemoryBase(constsp<IMemoryHeap>& heap,ssize_t offset, size_t size)
??? :mSize(size), mOffset(offset), mHeap(heap)
{
}
MemoryHeapBase和MemoryBase都?jí)蚝?jiǎn)單吧?總結(jié)起來不過是:
·? 分配了一塊共享內(nèi)存,這樣兩個(gè)進(jìn)程可以共享這塊內(nèi)存。
·? 基于Binder通信,這樣使用這兩個(gè)類的進(jìn)程就可以交互了。
這兩個(gè)類在后續(xù)的講解中會(huì)頻繁碰到,但不必對(duì)它們做深入分析,只需把它當(dāng)成普通的共享內(nèi)存看待即可。
提醒:這兩個(gè)類沒有提供同步對(duì)象來保護(hù)這塊共享內(nèi)存,所以后續(xù)在使用這塊內(nèi)存時(shí),必然需要一個(gè)跨進(jìn)程的同步對(duì)象來保護(hù)它。這一點(diǎn),是我在AT中第一次見到它們時(shí)想到的,不知道你是否注意過這個(gè)問題。
?
還記得用例中的③和④關(guān)鍵代碼行嗎?
//③ 開始播放
trackplayer.play() ;
//④ 調(diào)用write寫數(shù)據(jù)
trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中寫數(shù)據(jù)
現(xiàn)在就來分析它們。我們要直接轉(zhuǎn)向JNI層來進(jìn)行分析。相信你,現(xiàn)在已有能力從Java層直接跳轉(zhuǎn)至JNI層了。
先看看play函數(shù)對(duì)應(yīng)的JNI層函數(shù),它是android_media_AudioTrack_start。
[-->android_media_AudioTrack.cpp]
static void
android_media_AudioTrack_start(JNIEnv *env,jobject thiz)
{
/*
? 從Java的AudioTrack對(duì)象中獲取對(duì)應(yīng)Native層的AudioTrack對(duì)象指針。
?從int類型直接轉(zhuǎn)換成指針,不過要是以后ARM平臺(tái)支持64位指針了,代碼就得大修改了。
*/
???AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
??????? thiz,javaAudioTrackFields.nativeTrackInJavaObj);
???lpTrack->start(); //很簡(jiǎn)單的調(diào)用
}
play函數(shù)太簡(jiǎn)單了,至于它調(diào)用的start,等到Native層進(jìn)行AudioTrack分析時(shí),我們?cè)偃ビ^察。
Java層的write函數(shù)有兩個(gè):
·? 一個(gè)是用來寫PCM16數(shù)據(jù)的,它對(duì)應(yīng)的一個(gè)采樣點(diǎn)的數(shù)據(jù)量是兩個(gè)字節(jié)。
·? 另外一個(gè)用來寫PCM8數(shù)據(jù)的,它對(duì)應(yīng)的一個(gè)采樣點(diǎn)的數(shù)據(jù)量是一個(gè)字節(jié)。
我們的用例中采用的是PCM16數(shù)據(jù)。它對(duì)應(yīng)的JNI層函數(shù)是android_media_AudioTrack_native_write_short,一起來看:
[-->android_media_AudioTrack.cpp]
static jint android_media_AudioTrack_native_write_short(
???????????????? JNIEnv*env,? jobject thiz,
???????????????? jshortArrayjavaAudioData,jint offsetInShorts,
???????????????? jintsizeInShorts,jint javaAudioFormat) {
?
??????? return(android_media_AudioTrack_native_write(
???????????????? env,thiz,(jbyteArray)javaAudioData,offsetInShorts*2,
???????????????? sizeInShorts*2,javaAudioFormat)/ 2);
}
無論P(yáng)CM16還是PCM8數(shù)據(jù),最終都會(huì)調(diào)用writeToTrack函數(shù)。
[-->android_media_AudioTrack.cpp]
jint writeToTrack(AudioTrack* pTrack, jintaudioFormat,
???????????????? jbyte*data,jint offsetInBytes, jint sizeInBytes) {
????
?? ssize_t written = 0;
? /*
???? 如果是STATIC模式,sharedBuffer()返回不為空
???? 如果是STREAM模式,sharedBuffer()返回空
? */
?? ???if (pTrack->sharedBuffer() == 0) {
??? ?????//我們的用例是STREAM模式,調(diào)用write函數(shù)寫數(shù)據(jù)
???????written = pTrack->write(data + offsetInBytes, sizeInBytes);
??? } else{
?????? ?if (audioFormat == javaAudioTrackFields.PCM16){
???????????if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) {
???????????????sizeInBytes = pTrack->sharedBuffer()->size();
???????????}
????????//在STATIC模式下,直接把數(shù)據(jù)memcpy到共享內(nèi)存,記住在這種模式下要先調(diào)用write
??????? //后調(diào)用play
??????????memcpy(pTrack->sharedBuffer()->pointer(),
???????????????????????? data+ offsetInBytes, sizeInBytes);
???????????written = sizeInBytes;
??????? }else if (audioFormat == javaAudioTrackFields.PCM8) {
??????????//如果是PCM8數(shù)據(jù),則先轉(zhuǎn)換成PCM16數(shù)據(jù)再拷貝
???????????......
??? }
??? returnwritten;
}
看上去,play和write這兩個(gè)函數(shù)還真是比較簡(jiǎn)單,須知,大部分工作還都是由Native的AudioTrack來完成的。繼續(xù)Java層的分析。
當(dāng)數(shù)據(jù)都write完后,需要調(diào)用stop停止播放,或者直接調(diào)用release來釋放相關(guān)資源。由于release和stop有一定的相關(guān)性,這里只分析release調(diào)用。
[-->android_media_AudioTrack.cpp]
static voidandroid_media_AudioTrack_native_release(JNIEnv *env,? jobject thiz) {
??????
??? //調(diào)用android_media_AudioTrack_native_finalize真正釋放資源
???android_media_AudioTrack_native_finalize(env, thiz);
??? //之前保存在Java對(duì)象中的指針變量此時(shí)都要設(shè)置為零
???env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0);
???env->SetIntField(thiz, javaAudioTrackFields.jniData, 0);
}
[-->android_media_AudioTrack.cpp]
static voidandroid_media_AudioTrack_native_finalize(JNIEnv *env, jobject thiz) {
???AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
?????? ?????????????????? ?thiz, javaAudioTrackFields.nativeTrackInJavaObj);
??? if(lpTrack) {
???????lpTrack->stop();//調(diào)用stop
???????delete lpTrack; //調(diào)用AudioTrack的析構(gòu)函數(shù)
}
......
}
掃尾工作也很簡(jiǎn)單,沒什么需要特別注意的。
至此,在Java空間的分析工作就完成了。但在進(jìn)入Native空間的分析之前,要總結(jié)一下Java空間使用Native的AudioTrack的流程,只有這樣,在進(jìn)行Native空間分析時(shí)才能有章可循。
AudioTrack在JNI層使用了Native的AudioTrack對(duì)象,總結(jié)一下調(diào)用Native對(duì)象的流程:
·? new一個(gè)AudioTrack,使用無參的構(gòu)造函數(shù)。
·? 調(diào)用set函數(shù),把Java層的參數(shù)傳進(jìn)去,另外還設(shè)置了一個(gè)audiocallback回調(diào)函數(shù)。
·? 調(diào)用了AudioTrack的start函數(shù)。
·? 調(diào)用AudioTrack的write函數(shù)。
·? 工作完畢后,調(diào)用stop。
·? 最后就是Native對(duì)象的delete。
說明:為什么要總結(jié)流程呢?
第一:控制了流程,就把握了系統(tǒng)工作的命脈,這一點(diǎn)至關(guān)重要。
第二:有些功能的實(shí)現(xiàn)縱跨Java/Native層,橫跨兩個(gè)進(jìn)程,這中間有很多封裝、很多的特殊處理,但是其基本流程是不變的。通過精簡(jiǎn)流程,我們才能把注意力集中在關(guān)鍵點(diǎn)上。
?
Native的AudioTrack代碼在AudioTrack.cpp中。這一節(jié),分析它的構(gòu)造函數(shù)和set調(diào)用。
[-->AudioTrack.cpp]
AudioTrack::AudioTrack()//我們使用無參構(gòu)造函數(shù)
??? :mStatus(NO_INIT)
{
? //把狀態(tài)初始化成NO_INIT。Android的很多類都采用了這種狀態(tài)控制
}
再看看set調(diào)用,這個(gè)函數(shù)有很多內(nèi)容。
[-->AudioTrack.cpp]
/*?
?? 還記得我們傳入的參數(shù)嗎?
?streamType=STREAM_MUSIC,sampleRate=8000,format=PCM_16
?channels=2,frameCount由計(jì)算得來,可以假設(shè)一個(gè)值,例如1024,不影響分析。
? flags=0,cbf=audiocallback, user為cbf的參數(shù),notificationFrames=0
? 因?yàn)槭橇髂J剑詓haredBuffer=0。threadCanCallJava 為true
*/
status_t AudioTrack::set(int streamType,uint32_t sampleRate,int format,
???????int channels,int frameCount,uint32_t flags,callback_t cbf,void* user,
???????int notificationFrames,const sp<IMemory>& sharedBuffer,
??????? boolthreadCanCallJava)
{
? ??//前面有一些判斷,都是和AudioSystem有關(guān)的,以后再分析
??? ......
? /*
???? audio_io_handle_t是一個(gè)int類型,通過typedef定義,這個(gè)值的來歷非常復(fù)雜,
涉及AudioFlinger和AudioPolicyService, 后邊的分析試將其解釋清楚。
這個(gè)值主要被AudioFlinger使用,用來表示內(nèi)部的工作線程索引號(hào)。AudioFlinger會(huì)根據(jù)
情況創(chuàng)建幾個(gè)工作線程,下面的AudioSystem::getOutput會(huì)根據(jù)流類型等其他參數(shù)最終選
取一個(gè)合適的工作線程,并返回它在AF中的索引號(hào)。
而AudioTrack一般使用混音線程(Mixer Thread)
?*/
??? audio_io_handle_toutput = AudioSystem::getOutput(
???????????????????????? (AudioSystem::stream_type)streamType,
????????????????????????? sampleRate,format, channels,
???????????????????????? (AudioSystem::output_flags)flags);
? ?//調(diào)用creatTrack
???status_t status = createTrack(streamType, sampleRate, format,channelCount,
????????????????????????????????? frameCount,flags, sharedBuffer, output);
? ??
?
//cbf是JNI層傳入的回調(diào)函數(shù)audioCallback,如果用戶設(shè)置了回調(diào)函數(shù),則啟動(dòng)一個(gè)線程
? if (cbf!= 0) {
???????mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
?? }
?? returnNO_ERROR;
}
再看createTrack函數(shù):
[-->AudioTrack.cpp]
status_t AudioTrack::createTrack(intstreamType,uint32_t sampleRate,
???????int format,int channelCount,int frameCount, uint32_t flags,
???????const sp<IMemory>& sharedBuffer, audio_io_handle_t output)
{
?? status_tstatus;
?
? /*
??? 得到AudioFlinger的Binder代理端BpAudioFlinger。
??? 關(guān)于這部分內(nèi)容,我們已經(jīng)很熟悉了,以后的講解會(huì)跨過Binder,直接分析Bn端的實(shí)現(xiàn)
? */
? constsp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
??
?/*
向AudioFinger發(fā)送createTrack請(qǐng)求。注意其中的幾個(gè)參數(shù),
在STREAM模式下sharedBuffer為空
??? output為AudioSystem::getOutput得到一個(gè)值,代表AF中的線程索引號(hào)
該函數(shù)返回IAudioTrack(實(shí)際類型是BpAudioTrack)對(duì)象,后續(xù)AF和AT的交互就是
圍繞IAudioTrack進(jìn)行的
*/
???sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
???? ???streamType,sampleRate,format,channelCount,frameCount,
????????((uint16_t)flags) << 16,sharedBuffer,output,&status);
?
?? /*
???? 在STREAM模式下,沒有在AT端創(chuàng)建共享內(nèi)存,但前面提到了AT和AF的數(shù)據(jù)交互是
通過共享內(nèi)存完成的,這塊共享內(nèi)存最終由AF的createTrack創(chuàng)建。我們以后分析AF時(shí)
再做介紹。下面這個(gè)調(diào)用會(huì)取出AF創(chuàng)建的共享內(nèi)存
*/
???sp<IMemory> cblk = track->getCblk();
???mAudioTrack.clear();//sp的clear
??? mAudioTrack= track;
???mCblkMemory.clear();
??? mCblkMemory= cblk;//cblk是control block的簡(jiǎn)寫
? /*
??? IMemory的pointer在此處將返回共享內(nèi)存的首地址,類型為void*,
??? static_cast直接把這個(gè)void*類型轉(zhuǎn)成audio_track_cblk_t,表明這塊內(nèi)存的首部中存在
???? audio_track_cblk_t這個(gè)對(duì)象
? */
??? mCblk= static_cast<audio_track_cblk_t*>(cblk->pointer());
???mCblk->out = 1;//out為1表示輸出,out為0表示輸入
???mFrameCount = mCblk->frameCount;
??? if(sharedBuffer == 0) {
?????? //buffers指向數(shù)據(jù)空間,它的起始位置是共享內(nèi)存的首部加上audio_track_cblk_t的大小
?????? mCblk->buffers= (char*)mCblk + sizeof(audio_track_cblk_t);
? } else {
??? ???//STATIC模式下的處理
?????? ?mCblk->buffers =sharedBuffer->pointer();
?? ?????mCblk->stepUser(mFrameCount);//更新數(shù)據(jù)位置,后面要分析stepUser的作用
??? }
??? returnNO_ERROR;
}
上面的createTrack函數(shù)中突然冒出來一個(gè)新面孔,叫IAudioTrack。關(guān)于它和AT及AF的關(guān)系,我們用圖7-3來表示:
http://wiki.jikexueyuan.com/project/deep-android-v1/images/chapter7/image003.png" alt="image" />
圖7-3 ?IAudioTrack和AT、AF的關(guān)系
從圖7-3中可以發(fā)現(xiàn):
·? IAudioTrack是聯(lián)系A(chǔ)T和AF的關(guān)鍵紐帶。
至于IAudioTrack在AF端到底是什么,在分析AF時(shí)會(huì)有詳細(xì)解釋。
通過前面的代碼分析,我們發(fā)現(xiàn)IAudioTrack中有一塊共享內(nèi)存,其頭部是一個(gè)audio_track_cblk_t(簡(jiǎn)稱CB)對(duì)象,在該對(duì)象之后才是數(shù)據(jù)緩沖。這個(gè)CB對(duì)象有什么作用呢?
還記得前面提到的那個(gè)深層次思考的問題嗎?即MemoryHeapBase和MemoryBase都沒有提供同步對(duì)象,那么,AT和AF作為典型的數(shù)據(jù)生產(chǎn)者和消費(fèi)者,如何正確協(xié)調(diào)二者生產(chǎn)和消費(fèi)的步調(diào)呢?
Android為順應(yīng)民意,便創(chuàng)造出了這個(gè)CB對(duì)象,其主要目的就是協(xié)調(diào)和管理AT和AF二者數(shù)據(jù)生產(chǎn)和消費(fèi)的步伐。先來看CB都管理些什么內(nèi)容。它的聲明在AudioTrackShared.h中,而定義卻在AudioTrack.cpp中。
[-->AudioTrackShared.h::audio_track_cblk_t聲明]
struct audio_track_cblk_t
{
?
?????? Mutex?????? lock;
?????? Condition?? cv;//這是兩個(gè)同步變量,初始化的時(shí)候會(huì)設(shè)置為支持跨進(jìn)程共享
? /*
? ?一塊數(shù)據(jù)緩沖同時(shí)被生產(chǎn)者和消費(fèi)者使用,最重要的就是維護(hù)它的讀寫位置了。
? ?下面定義的這些變量就和讀寫的位置有關(guān),雖然它們的名字并不是那么直觀。
?? 另外,這里提一個(gè)擴(kuò)展問題,讀者可以思考一下:
??volatile支持跨進(jìn)程嗎?要回答這個(gè)問題需要理解volatile、CPU Cache機(jī)制和共享內(nèi)存的本質(zhì)
?*/
???volatile??? uint32_t??? user;???//當(dāng)前寫位置(即生產(chǎn)者已經(jīng)寫到什么位置了)
??? volatile??? uint32_t???server;? //當(dāng)前讀位置
?/*
??? userBase和serverBase要和user及server結(jié)合起來用。
??? CB巧妙地通過上面幾個(gè)變量把一塊線性緩沖當(dāng)做環(huán)形緩沖來使用,以后將單獨(dú)分析這個(gè)問題
? */
???????????????uint32_t??? userBase;? //
???????????????uint32_t??? serverBase;
???void*?????? buffers; //指向數(shù)據(jù)緩沖的首地址
???uint32_t??? frameCount;//數(shù)據(jù)緩沖的總大小,以Frame為單位