Audio系統(tǒng)在Android中負(fù)責(zé)音頻方面的數(shù)據(jù)流傳輸和控制功能,也負(fù)責(zé)音頻設(shè)備的管理。

Audio系統(tǒng)是Android平臺(tái)的重要組成部分,它主要包含三方面的內(nèi)容:
· AudioRcorder和AudioTrack:這兩個(gè)類屬于Audio系統(tǒng)對外提供的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è)備的選擇和切換、音量控制等功能。
本文主要分享AudioRcorder和AudioTrack;
AudioRcorder在之前的文章中已經(jīng)寫過了,這篇文章主要寫AudioTrack
AudioTrack
1.用例分析
// 依據(jù)音頻數(shù)據(jù)的特性來確定所要分配的緩沖區(qū)的最小size
private int mPrimePlaySize = 0; //較優(yōu)播放塊大小
public int mChannel = AudioFormat.CHANNEL_OUT_STEREO; // 聲道
int minBufSize = AudioTrack.getMinBufferSize(AudioParam.AUDIO_RATE,
mChannel, AudioParam.AUDIO_FORMAT);
mPrimePlaySize = minBufSize;
// 創(chuàng)建AudioTrack
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, AudioParam.AUDIO_RATE,
mChannel, AudioParam.AUDIO_FORMAT, mPrimePlaySize, AudioTrack.MODE_STREAM);
//開始播放
if (mAudioTrackThread == null) {
mAudioTrackThread = new AudioTrackThread(context, audioFile);
mAudioTrackThread.start();//開始播放
}
// 調(diào)用write寫數(shù)據(jù)
if (mAudioTrack != null) {
Log.d(TAG, "mAudioTrack.play() ");
mAudioTrack.play();
FileInputStream inputStream = null;
byte[] bytes = new byte[1024 * 2];
try {
inputStream = new FileInputStream(audioFile);
int read;
while ((read = inputStream.read(bytes)) > 0) {
Log.d(TAG, "start write audioFile");
mAudioTrack.write(bytes, 0, read);//向track中寫數(shù)據(jù)
}
} catch (RuntimeException | IOException e) {
Log.d(TAG, "start write audioFile fail");
e.printStackTrace();
}
}
//停止播放和釋放資源
if (mAudioTrack != null) {
mAudioTrack.stop();//停止播放
mAudioTrack.release();//釋放底層資源
}
創(chuàng)建AudioTrack時(shí),其中有三個(gè)概念,一個(gè)是數(shù)據(jù)載入模式。一個(gè)是音頻流類型,還有一個(gè)是Buffer分配和Frame,接下來進(jìn)行具體介紹
1. AudioTrack的數(shù)據(jù)載入模式
AudioTrack有兩種數(shù)據(jù)載入模式:MODE_STREAM和MODE_STATIC。它們相應(yīng)著兩種全然不同的使用場景。
· MODE_STREAM:在這樣的模式下。通過write一次次把音頻數(shù)據(jù)寫到AudioTrack中。這和平時(shí)通過write系統(tǒng)調(diào)用往文件里寫數(shù)據(jù)相似,但這樣的工作方式每次都須要把數(shù)據(jù)從用戶提供的Buffer中復(fù)制到AudioTrack內(nèi)部的Buffer中,這在一定程度上會(huì)使引入延時(shí)。
為解決這一問題,AudioTrack就引入了另外一種模式。
· MODE_STATIC:這樣的模式下,在play之前僅僅須要把全部數(shù)據(jù)通過一次write調(diào)用傳遞到AudioTrack中的內(nèi)部緩沖區(qū),興許就不必再傳遞數(shù)據(jù)了。
這樣的模式適用于像鈴聲這樣的內(nèi)存占用量較小。延時(shí)要求較高的文件。但它也有一個(gè)缺點(diǎn)。就是一次write的數(shù)據(jù)不能太多,否則系統(tǒng)無法分配足夠的內(nèi)存來存儲(chǔ)全部數(shù)據(jù)。
這兩種模式中以MODE_STREAM模式相對常見和復(fù)雜。我們的分析將以它為主。
注意:假設(shè)採用STATIC模式,須先調(diào)用write寫數(shù)據(jù),然后再調(diào)用play。
2. 音頻流的類型
在AudioTrack構(gòu)造函數(shù)中,會(huì)接觸到AudioManager.STREAM_MUSIC這個(gè)參數(shù)。它的含義與Android系統(tǒng)對音頻流的管理和分類有關(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)對音頻的管理策略有關(guān)。其具體作用,在以后的分析中再做具體介紹。
在當(dāng)前的用例中,把它當(dāng)做一個(gè)普通數(shù)值就可以。
3. Buffer分配和Frame的概念
在用例中碰到的第一個(gè)重要函數(shù)就是getMinBufferSize。這個(gè)函數(shù)對于確定應(yīng)用層分配多大的數(shù)據(jù)Buffer具有重要指導(dǎo)意義。先回想一下它的調(diào)用方式:
[AudioTrackAPI使用樣例(Java層)]
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;
}
//對採樣頻率也有要求,太低或太高都不行。
if( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) )
return AudioTrack.ERROR_BAD_VALUE;
/*
調(diào)用Native函數(shù),先想想為什么,假設(shè)是簡單計(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對象的一些信息,并確認(rèn)它們是否支持這些採樣率和採樣精度。
說明:HAL對象的具體實(shí)現(xiàn)和硬件廠商有關(guān)系,假設(shè)沒有特殊說明,我們則把硬件和HAL作為一種東西討論。
來看Native的native_get_min_buff_size函數(shù)。它在android_media_AudioTrack.cpp中。
/*
注意我們傳入的參數(shù)是:
sampleRateInHertz = 8000。nbChannels = 2
audioFormat = AudioFormat.ENCODING_PCM_16BIT
*/
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz,
jint sampleRateInHertz, jint channelCount, jint audioFormat) {
size_t frameCount;
const status_t status = AudioTrack::getMinFrameCount ( & frameCount, AUDIO_STREAM_DEFAULT, sampleRateInHertz);
if (status != NO_ERROR) {
ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d",
sampleRateInHertz, status);
return -1;
}
const audio_format_t format = audioFormatToNative(audioFormat);
if (audio_has_proportional_frames(format)) {
const size_t bytesPerSample = audio_bytes_per_sample(format);
return frameCount * channelCount * bytesPerSample;
} else {
return frameCount;
}
}
......
這里有必要插入內(nèi)容,由于代碼中出現(xiàn)了音頻系統(tǒng)中的一個(gè)重要概念:Frame(幀)。
說明:Frame是一個(gè)單位。經(jīng)多方查尋,終于在ALSA的wiki中找到了對它的解釋。
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)僅僅針對一個(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了。
AudioTrack(Java空間)的分析
注意:Java空間的分析包含JNI這一層,由于它們二者的關(guān)系最為緊密。
1. AudioTrack的構(gòu)造
回想一下用例中調(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í)如今AudioTrack.java中。來看這個(gè)函數(shù):
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)查一下,非常easy
*/
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
....
}
native_setup相應(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對象,它保存了一些信息,后面將具體分析
AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
......
//②創(chuàng)建Native層的AudioTrack對象
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) {
//假設(shè)是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對象指針保存到Java對象的一個(gè)變量中,
這樣就把JNI層的AudioTrack對象和Java層的AudioTrack對象關(guān)聯(lián)起來了,
這是Android的經(jīng)常使用技法。
*/
env->SetIntField(thiz,javaAudioTrackFields.nativeTrackInJavaObj,
(int)lpTrack);
// lpJniStorage對象指針也保存到Java對象中
env->SetIntField(thiz, javaAudioTrackFields.jniData,(int)lpJniStorage);
}
上邊的代碼列出了三個(gè)要點(diǎn),這一節(jié)僅分析AudioTrackJniStorage這個(gè)類,其余的作為Native AudioTrack部分放在后面進(jìn)行分析。
2. AudioTrackJniStorage分析
AudioTrackJniStorage是一個(gè)輔助類,當(dāng)中有一些有關(guān)共享內(nèi)存方面的較重要的知識(shí),這里先簡介一下。
(1) 共享內(nèi)存介紹
共享內(nèi)存。作為進(jìn)程間數(shù)據(jù)傳遞的一種手段。在AudioTrack和AudioFlinger中被大量使用。先簡單了解一下有關(guān)共享內(nèi)存的知識(shí):
· 每個(gè)進(jìn)程的內(nèi)存空間是4GB,這個(gè)4GB是由指針長度決定的。假設(shè)指針長度為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)存地址之間建立了一種相應(yīng)關(guān)系。
也就是說,進(jìn)程中操作的0x12345678這塊內(nèi)存的地址,在經(jīng)過OS內(nèi)存管理機(jī)制的轉(zhuǎn)換后,它實(shí)際相應(yīng)的物理地址可能會(huì)是0x87654321。
當(dāng)然,這一切對進(jìn)程來說都是透明的。這些活都由操作系統(tǒng)悄悄地完畢了。
這和我們的共享內(nèi)存會(huì)有什么關(guān)系嗎?
當(dāng)然有,共享內(nèi)存和內(nèi)存映射有著重要關(guān)系。來看圖1“共享內(nèi)存示意圖”:

圖1 共享內(nèi)存示意圖
圖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)如今已經(jīng)映射到了進(jìn)程A中。
可它能同一時(shí)候映射到進(jìn)程B中嗎?假設(shè)能。那么在進(jìn)程A中,對這塊內(nèi)存頁所寫的數(shù)據(jù)在進(jìn)程B中就能看見了。這豈不就做到了內(nèi)存在兩個(gè)進(jìn)程間共享嗎?
事實(shí)確實(shí)如此,否則我們的生活就不會(huì)像如今這么美好了。這個(gè)機(jī)制是由操作系統(tǒng)提供和實(shí)現(xiàn)的。原理非常easy。實(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è)文件描寫敘述符所相應(yīng)的驅(qū)動(dòng)或內(nèi)核模塊來完畢。
除上述一般方法外。Linux還有System V的共享內(nèi)存創(chuàng)建方法。這里就不再介紹了??傊?。AT和AF之間的數(shù)據(jù)傳遞,就是通過共享內(nèi)存方式來完畢的。
這樣的方式對于跨進(jìn)程的大數(shù)據(jù)量傳輸來說。是非常高效的。
(2) MemoryHeapBase和MemoryBase類介紹
AudioTrackJniStorage用到了Android對共享內(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類的使用方法。在介紹它們之前,先來看圖2中與這兩個(gè)Memory有關(guān)的家譜。

圖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有兩個(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ù)都非常easy,僅把它們的作用描寫敘述一下就可以。
MemoryHeapBase::getBaseID() //返回mFd。假設(shè)為負(fù)數(shù),表明剛才創(chuàng)建共享內(nèi)存失敗了
MemoryHeapBase::getBase() //共享內(nèi)存起始地址
MemoryHeapBase::getSize() //返回mSize。表示內(nèi)存大小
MemoryHeapBase確實(shí)比較簡單,它通過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)容。感興趣的讀者還能夠自行對其研究。
那么,MemoryBase是何物?它又有什么作用?
MemoryBase也是一個(gè)基于Binder通信的類。它比起MemoryHeapBase就更顯簡單了??雌饋砀袷且粋€(gè)輔助類。
它的聲明在MemoryBase.h中。一起來看:
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對象
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í)蚝唵伟桑靠偨Y(jié)起來只是是:
· 分配了一塊共享內(nèi)存。這樣兩個(gè)進(jìn)程能夠共享這塊內(nèi)存。
· 基于Binder通信。這樣使用這兩個(gè)類的進(jìn)程就能夠交互了。
這兩個(gè)類在興許的解說中會(huì)頻繁碰到,但不必對它們做深入分析,僅僅需把它當(dāng)成普通的共享內(nèi)存看待就可以。
提醒:這兩個(gè)類沒有提供同步對象來保護(hù)這塊共享內(nèi)存,所以興許在使用這塊內(nèi)存時(shí)。必定須要一個(gè)跨進(jìn)程的同步對象來保護(hù)它。這一點(diǎn)。是我在AT中第一次見到它們時(shí)想到的。不知道你是否注意過這個(gè)問題。
- play和write的分析
還記得用例中的③和④關(guān)鍵代碼行嗎?
//③ 開始播放
trackplayer.play() ;
//④ 調(diào)用write寫數(shù)據(jù)
trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中寫數(shù)據(jù)
如今就來分析它們。我們要直接轉(zhuǎn)向JNI層來進(jìn)行分析。
相信你,如今已有能力從Java層直接跳轉(zhuǎn)至JNI層了。
(1) play的分析
先看看play函數(shù)相應(yīng)的JNI層函數(shù),它是android_media_AudioTrack_start。
static void android_media_AudioTrack_start(JNIEnv *env,jobject thiz)
{
/*
從Java的AudioTrack對象中獲取相應(yīng)Native層的AudioTrack對象指針。
從int類型直接轉(zhuǎn)換成指針,只是要是以后ARM平臺(tái)支持64位指針了。代碼就得大修改了。
*/
AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(
thiz,javaAudioTrackFields.nativeTrackInJavaObj);
lpTrack->start(); //非常easy的調(diào)用
}
play函數(shù)太簡單了,至于它調(diào)用的start。等到Native層進(jìn)行AudioTrack分析時(shí),我們再去觀察。
(2) write的分析
Java層的write函數(shù)有兩個(gè):
· 一個(gè)是用來寫PCM16數(shù)據(jù)的。它相應(yīng)的一個(gè)採樣點(diǎn)的數(shù)據(jù)量是兩個(gè)字節(jié)。
· 另外一個(gè)用來寫PCM8數(shù)據(jù)的,它相應(yīng)的一個(gè)採樣點(diǎn)的數(shù)據(jù)量是一個(gè)字節(jié)。
我們的用例中採用的是PCM16數(shù)據(jù)。
它相應(yīng)的JNI層函數(shù)是android_media_AudioTrack_native_write_short,一起來看:
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ù)。
jint writeToTrack(AudioTrack* pTrack, jintaudioFormat,
jbyte*data,jint offsetInBytes, jint sizeInBytes) {
ssize_t written = 0;
/*
假設(shè)是STATIC模式。sharedBuffer()返回不為空
假設(shè)是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) {
//假設(shè)是PCM8數(shù)據(jù),則先轉(zhuǎn)換成PCM16數(shù)據(jù)再拷貝
......
}
returnwritten;
}
看上去。play和write這兩個(gè)函數(shù)還真是比較簡單,須知,大部分工作還都是由Native的AudioTrack來完畢的。
繼續(xù)Java層的分析。
- release的分析
當(dāng)數(shù)據(jù)都write完后,須要調(diào)用stop停止播放?;蛘咧苯诱{(diào)用release來釋放相關(guān)資源。由于release和stop有一定的相關(guān)性。這里僅僅分析release調(diào)用。
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對象中的指針變量此時(shí)都要設(shè)置為零
env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0);
env->SetIntField(thiz, javaAudioTrackFields.jniData, 0);
}
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ù)
}
......
}
至此,在Java空間的分析工作就完畢了。
- AudioTrack(Java空間)的分析總結(jié)
AudioTrack在JNI層使用了Native的AudioTrack對象,總結(jié)一下調(diào)用Native對象的流程:
· 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對象的delete。
參考文章:
https://developer.android.com/reference/android/media/AudioRecord
https://www.cnblogs.com/yutingliuyl/p/6882171.html