對(duì)于Android應(yīng)用開(kāi)發(fā)人員來(lái)講,音頻回放最熟悉的莫過(guò)于MediaPlayer,而AudioTrack相信用的人相對(duì)會(huì)少很多。這是因?yàn)镸ediaPlayer提供了更完整的封裝和狀態(tài)控制,使得我們用很少的代碼就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的音樂(lè)播放器。而相比MediaPlayer,AudioTrack更為精練、高效,實(shí)際上MediaPlayerService的內(nèi)部實(shí)現(xiàn)就是使用了AudioTrack。
AudioTrack被用于PCM音頻流的回放,在數(shù)據(jù)傳送上它有兩種方式:
? 調(diào)用write(byte[],int,int)或write(short[],int,int)把音頻數(shù)據(jù)“push”到AudioTrack中。
? 與之相對(duì)的,當(dāng)然就是“pull”形式的數(shù)據(jù)獲取,即數(shù)據(jù)接收方主動(dòng)索取的過(guò)程,如下圖所示:
除此之外,AudioTrack還同時(shí)支持static和streaming兩種模式:
§ static
靜態(tài)的言下之意就是數(shù)據(jù)一次性交付給接收方。好處是簡(jiǎn)單高效,只需要進(jìn)行一次操作就完成了數(shù)據(jù)的傳遞;缺點(diǎn)當(dāng)然也很明顯,對(duì)于數(shù)據(jù)量較大的音頻回放,顯然它是無(wú)法勝任的,因而通常只用于播放鈴聲、系統(tǒng)提醒等對(duì)內(nèi)存小的操作
§ streaming
流模式和網(wǎng)絡(luò)上播放視頻是類(lèi)似的,即數(shù)據(jù)是按照一定規(guī)律不斷地傳遞給接收方的。理論上它可用于任何音頻播放的場(chǎng)景,不過(guò)我們一般在以下情況下采用:
? 音頻文件過(guò)大
? 音頻屬性要求高,比如采樣率高、深度大的數(shù)據(jù)
? 音頻數(shù)據(jù)是實(shí)時(shí)產(chǎn)生的,這種情況就只能用流模式了
下面我們選取AudioTrackTest.java為例來(lái)講解,先從使用者的角度來(lái)了解下AudioTrack。
/*cts/tests/tests/media/src/android/media/cts*/
public voidtestSetStereoVolumeMax() throwsException {
final String TEST_NAME= "testSetStereoVolumeMax";
final int TEST_SR =22050;
final int TEST_CONF =AudioFormat.CHANNEL_CONFIGURATION_STEREO;
final int TEST_FORMAT= AudioFormat.ENCODING_PCM_16BIT;
final int TEST_MODE =AudioTrack.MODE_STREAM;
final intTEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
// --------initialization --------------
/*Step1.*/
int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
/*Step 2.*/
AudioTrack track = newAudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
TEST_FORMAT, 2 * minBuffSize,TEST_MODE);
byte data[] = newbyte[minBuffSize];
// -------- test--------------
track.write(data, OFFSET_DEFAULT, data.length);
track.write(data, OFFSET_DEFAULT, data.length);
track.play();
float maxVol =AudioTrack.getMaxVolume();
assertTrue(TEST_NAME, track.setStereoVolume(maxVol, maxVol) == AudioTrack.SUCCESS);
// -------- tear down--------------
track.release();
}
這個(gè)TestCase是測(cè)試立體聲左右聲道最大音量的。順便提一下,關(guān)于自動(dòng)化測(cè)試的更多描述,可以參照本書(shū)最后一個(gè)篇章。用例中涉及到AudioTrack的常規(guī)操作都用高顯標(biāo)示出來(lái)了。可見(jiàn)大概是這么幾個(gè)步驟:
Step1@ testSetStereoVolumeMax,getMinBufferSize
字面意思就是獲取最小的buffer大小,這個(gè)buffer將用于后面AudioTrack的構(gòu)造函數(shù)。它是AudioTrack可以正常使用的一個(gè)最低保障,根據(jù)官方的建議如果音頻文件本身要求較高的話,最好可以采用比MinBufferSize更大的數(shù)值。這個(gè)函數(shù)的實(shí)現(xiàn)相對(duì)簡(jiǎn)單:
static public int getMinBufferSize(int sampleRateInHz, int channelConfig,int audioFormat) {
int channelCount = 0;
switch(channelConfig){
caseAudioFormat.CHANNEL_OUT_MONO:
caseAudioFormat.CHANNEL_CONFIGURATION_MONO:
channelCount = 1;
break;
case AudioFormat.CHANNEL_OUT_STEREO:
caseAudioFormat.CHANNEL_CONFIGURATION_STEREO:
channelCount = 2;
break;
default:
…
}
首先得出聲道數(shù),目前最多只支持雙聲道。
if ((audioFormat !=AudioFormat.ENCODING_PCM_16BIT)
&& (audioFormat !=AudioFormat.ENCODING_PCM_8BIT)) {
returnAudioTrack.ERROR_BAD_VALUE;
}
得出音頻采樣深度,只支持8bit和16bit兩種。
// sample rate, notethese values are subject to change
if ( (sampleRateInHz< 4000) || (sampleRateInHz > 48000) ) {
loge("getMinBufferSize(): " + sampleRateInHz +"Hz is nota supported sample rate.");
returnAudioTrack.ERROR_BAD_VALUE;
}
得出采樣頻率,支持的范圍是4k-48kHZ.
int size =native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
…
}
也就是說(shuō)最小buffer大小取決于采樣率、聲道數(shù)和采樣深度三個(gè)屬性。那么具體是如何計(jì)算的呢?我們接著看下native層代碼實(shí)現(xiàn):
frameworks/base/core/jni/android_media_AudioTrack.cpp
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv*env, jobject thiz,
jint sampleRateInHertz,jint nbChannels, jint audioFormat) {
int frameCount = 0;
if(AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,
sampleRateInHertz) != NO_ERROR) {
return -1;
}
return frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
}
這里又調(diào)用了getMinFrameCount,這個(gè)函數(shù)用于確定至少需要多少Frame才能保證音頻正常播放。那么Frame代表了什么意思呢?可以想象一下視頻中幀的概念,它代表了某個(gè)時(shí)間點(diǎn)的一幅圖像。這里的Frame也是類(lèi)似的,它應(yīng)該是指某個(gè)特定時(shí)間點(diǎn)時(shí)的音頻數(shù)據(jù)量,所以android_media_AudioTrack_get_min_buff_size中最后采用的計(jì)算公式就是:
至少需要多少幀*每幀數(shù)據(jù)量
= frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
公式中frameCount就是需要的幀數(shù),每一幀的數(shù)據(jù)量又等于:
Channel數(shù)*每個(gè)Channel數(shù)據(jù)量= nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1)
層層返回getMinBufferSize就得到了保障AudioTrack正常工作的最小緩沖區(qū)大小了。
Step2@ testSetStereoVolumeMax,創(chuàng)建AudioTrack實(shí)例
有了minBuffSize后,我們就可以創(chuàng)建一個(gè)AudioTrack對(duì)象了。它的構(gòu)造函數(shù)原型是:
public AudioTrack (int streamType, int sampleRateInHz, intchannelConfig, int audioFormat, int bufferSizeInBytes, int mode)
除了倒數(shù)第二個(gè)參數(shù)是計(jì)算出來(lái)的,其它入?yún)⒃谶@個(gè)TestCase中都是直接指定的。比如streamType是STREAM_MUSIC,sampleRateInHz是22050等等。如果是編寫(xiě)一個(gè)音樂(lè)播放器,這些參數(shù)自然都是需要通過(guò)分析音頻文件得出來(lái)的,所幸的是Android提供了更加易用的MediaPlayer,使得我們不需要理會(huì)這些瑣碎細(xì)節(jié)。
創(chuàng)建AudioTrack的一個(gè)重要任務(wù)就是和AudioFlinger建立聯(lián)系,它是由native層的代碼來(lái)實(shí)現(xiàn)的:
public AudioTrack(intstreamType, int sampleRateInHz, int channelConfig, int audioFormat,
intbufferSizeInBytes, int mode, int sessionId)
throwsIllegalArgumentException {
…
int initResult = native_setup(new WeakReference<AudioTrack>(this),
mStreamType,mSampleRate, mChannels, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session);
…
}
這里調(diào)用了native_setup來(lái)創(chuàng)建一個(gè)本地AudioTrack對(duì)象,如下:
/*frameworks/base/core/jni/android_media_AudioTrack.cpp*/
static int android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz, jobject weak_this,
jint streamType, jintsampleRateInHertz, jint javaChannelMask,
jint audioFormat, jintbuffSizeInBytes, jint memoryMode, jintArray jSession)
{
…
sp<AudioTrack>lpTrack = new AudioTrack();
…
AudioTrackJniStorage* lpJniStorage =new AudioTrackJniStorage();
創(chuàng)建一個(gè)Storage對(duì)象,直覺(jué)告訴我們這可能是存儲(chǔ)音頻數(shù)據(jù)的地方,后面我們?cè)僭敿?xì)分析。
…
if (memoryMode== javaAudioTrackFields.MODE_STREAM) {
lpTrack->set(…
audioCallback, //回調(diào)函數(shù)
&(lpJniStorage->mCallbackData),//回調(diào)數(shù)據(jù)
0,
0,//shared mem
true,// thread cancall Java
sessionId);//audio session ID
} else if (memoryMode ==javaAudioTrackFields.MODE_STATIC) {
…
lpTrack->set(…
audioCallback, &(lpJniStorage->mCallbackData),0,
lpJniStorage->mMemBase,// shared mem
true,// thread cancall Java
sessionId);//audio session ID
}
…// native_setup結(jié)束
函數(shù)native_setup首先新建了一個(gè)AudioTrack(native)對(duì)象,然后進(jìn)行各種屬性的計(jì)算,最后調(diào)用set函數(shù)為AudioTrack設(shè)置這些屬性——我們只保留兩種內(nèi)存模式(STATIC和STREAM)有差異的地方,便于大家比對(duì)理解。對(duì)于靜態(tài)數(shù)據(jù),入?yún)⒅械牡箶?shù)第三個(gè)是lpJniStorage->mMemBase,而STREAM類(lèi)型時(shí)為null(0)。
到目前為止我們還沒(méi)有看到它與AudioFlinger有交互的地方,看來(lái)謎底應(yīng)該就在這個(gè)set函數(shù)中了。
status_t AudioTrack::set(…
callback_t cbf, void* user, int notificationFrames, constsp<IMemory>& sharedBuffer,
boolthreadCanCallJava, int sessionId)
{
AutoMutex lock(mLock);
…
if (streamType ==AUDIO_STREAM_DEFAULT) {
streamType =AUDIO_STREAM_MUSIC;
}
當(dāng)不設(shè)置streamType時(shí),會(huì)被改為默認(rèn)的AUDIO_STREAM_MUSIC。
…
if (format ==AUDIO_FORMAT_DEFAULT) {
format =AUDIO_FORMAT_PCM_16_BIT;
}
if (channelMask == 0) {
channelMask =AUDIO_CHANNEL_OUT_STEREO;
}
采樣深度和聲道數(shù)默認(rèn)為16bit和立體聲
…
if (format ==AUDIO_FORMAT_PCM_8_BIT && sharedBuffer != 0) {
ALOGE("8-bit datain shared memory is not supported");
return BAD_VALUE;
}
當(dāng)sharedBuffer!=0時(shí)表明是STATIC模式,也就是說(shuō)靜態(tài)數(shù)據(jù)模式下只支持16bit深度否則就報(bào)錯(cuò)直接返回,這點(diǎn)要特別注意。
…
audio_io_handle_t output = AudioSystem::getOutput(streamType,sampleRate, format, channelMask, flags);
通過(guò)上述的有效性檢查后,AudioTrack接著就可以使用底層的音頻服務(wù)了。那么是直接調(diào)用AudioFlinger服務(wù)提供的接口嗎?理論上這樣子做也是可以的,但Android系統(tǒng)考慮得更細(xì),它在AudioTrack與底層服務(wù)間又提供了AudioSystem和AudioService。其中前者同時(shí)提供了Java和native兩層的實(shí)現(xiàn),而AudioService則只有native層的實(shí)現(xiàn)。這樣子就降低了使用者(AudioTrack)與底層實(shí)現(xiàn)(AudioPolicyService、AudioFlinger等等)間的藕合。換句話說(shuō),不同版本的Android音頻系統(tǒng)通常改動(dòng)很大,但只要AudioSystem和AudioService向上的接口不變,那么AudioTrack就不需要做修改。
所以上面的getOutput是由AudioSystem來(lái)提供的,可以猜測(cè)到其內(nèi)部只是做了些簡(jiǎn)單的中轉(zhuǎn)功能,最終還是得由AudioPolicyService/AudioFlinger來(lái)實(shí)現(xiàn)。這個(gè)getOutput尋找最適合當(dāng)前AudioTrack的audio interface以及Output輸出(也就是前面通過(guò)openOutput打開(kāi)的通道),然后AudioTrack會(huì)向這個(gè)Output申請(qǐng)一個(gè)Track。
…
mVolume[LEFT] = 1.0f;
mVolume[RIGHT] = 1.0f; /*左右聲道的初始值都是最大,另外還有其它成員變量都會(huì)在這里賦值,
我們直接省略掉了*/
…
if (cbf != NULL) {
mAudioTrackThread =new AudioTrackThread(*this, threadCanCallJava);
mAudioTrackThread->run("AudioTrack",ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
}
status_t status = createTrack_l(…sharedBuffer, output);
…
} //AudioTrack::set函數(shù)結(jié)束
因?yàn)閏bf是audioCallback不為空,所以這里會(huì)啟動(dòng)一個(gè)AudioTrack線程。這個(gè)線程是用于AudioTrack(native)與AudioTrack(java)間的數(shù)據(jù)事件通知的,這就為上層應(yīng)用處理事件提供了一個(gè)入口,包括:
EVENT_MORE_DATA = 0, /*請(qǐng)求寫(xiě)入更多數(shù)據(jù)*/
EVENT_UNDERRUN = 1, /*PCM 緩沖發(fā)生了underrun*/
EVENT_LOOP_END = 2, /*到達(dá)loop end,如果loop count不為空的話
將從loop start重新開(kāi)始回放*/
EVENT_MARKER = 3, /*Playback head在指定的位置,參考setMarkerPosition*/
EVENT_NEW_POS = 4, /*Playback head在一個(gè)新的位置,參考setPositionUpdatePeriod */
EVENT_BUFFER_END = 5 /*Playback head在buffer末尾*/
AudioTrack在AudioFlinger中是以Track來(lái)管理的。不過(guò)因?yàn)樗鼈冎g是跨進(jìn)程的關(guān)系,自然需要一個(gè)“橋梁”來(lái)維護(hù),這個(gè)溝通的媒介是IAudioTrack(這有點(diǎn)類(lèi)似于顯示系統(tǒng)中的IWindow)。函數(shù)createTrack_l除了為AudioTrack在AudioFlinger中申請(qǐng)一個(gè)Track外,還會(huì)建立兩者間IAudioTrack橋梁:
status_t AudioTrack::createTrack_l(
audio_stream_type_t streamType,uint32_t sampleRate, audio_format_tformat, uint32_t channelMask,
int frameCount,audio_output_flags_t flags, const sp<IMemory>& sharedBuffer,
audio_io_handle_t output)
{
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
獲得AudioFlinger服務(wù),還記得上一小節(jié)的介紹嗎?AudioFlinger在ServiceManager中注冊(cè),以“media.audio_flinger”為服務(wù)名。
…
IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
…
sp<IAudioTrack> track =audioFlinger->createTrack(getpid(),streamType, sampleRate,format,
channelMask, frameCount, trackFlags, sharedBuffer, output, tid,&mSessionId, &status);
//未完待續(xù)…
利用AudioFlinger創(chuàng)建一個(gè)IAudioTrack,這是它與AudioTrack之間的跨進(jìn)程通道。除此之處,AudioFlinger還做了什么?我們深入AudioFlinger分析下。
sp<IAudioTrack> AudioFlinger::createTrack(…
constsp<IMemory>& sharedBuffer, audio_io_handle_t output,
pid_t tid, int*sessionId, status_t *status)
{
sp<PlaybackThread::Track> track;
sp<TrackHandle>trackHandle;
…
PlaybackThread *thread = checkPlaybackThread_l(output);
PlaybackThread*effectThread = NULL;
…
track = thread->createTrack_l(client, streamType, sampleRate, format,
channelMask,frameCount, sharedBuffer, lSessionId, flags, tid, &lStatus);
…
if (lStatus == NO_ERROR) {
trackHandle = new TrackHandle(track);
} else {
client.clear();
track.clear();
}
return trackHandle;
}
我們只留下createTrack中最重要的幾個(gè)步驟,即:
· AudioFlinger::checkPlaybackThread_l
在AudioFlinger::openOutput時(shí),產(chǎn)生了全局唯一的audio_io_handle_t值,這個(gè)值是與PlaybackThread相對(duì)應(yīng)的,它作為mPlaybackThreads鍵值對(duì)的key值存在。
當(dāng)AudioTrack調(diào)用createTrack時(shí),需要傳入這個(gè)全局標(biāo)記值,checkPlaybackThread_l借此找到匹配的PlaybackThread
· PlaybackThread::createTrack_l
找到匹配的PlaybackThread后,還需要在其內(nèi)部創(chuàng)建一個(gè)PlaybackThread::Track對(duì)象(所有Track都由PlaybackThread::mTracks全局變量管理),這些工作由PlaybackThread::createTrack_l完成。我們以下圖表示它們之間的關(guān)系:
· new TrackHandle
TrackHandle實(shí)際上就是IAudioTrack,后續(xù)AudioTrack將利用這個(gè)binder服務(wù)來(lái)調(diào)用getCblk等接口
我們?cè)俳又懊鍭udioTrack::createTrack_l“未完待續(xù)”的部分往下看。
…
sp<IMemory> cblk =track->getCblk();
…
mAudioTrack = track;
mCblkMemory = cblk;
mCblk= static_cast<audio_track_cblk_t*>(cblk->pointer());
…
if (sharedBuffer == 0) {
mCblk->buffers =(char*)mCblk + sizeof(audio_track_cblk_t);
} else {
mCblk->buffers =sharedBuffer->pointer();
mCblk->stepUser(mCblk->frameCount);
}
…
return NO_ERROR;
}
事實(shí)上當(dāng)PlaybackThread創(chuàng)建一個(gè)PlaybackThread::Track對(duì)象時(shí),所需的緩沖區(qū)空間就已經(jīng)分配了。這塊空間是可以跨進(jìn)程共享的,所以AudioTrack可以通過(guò)track->getCblk()來(lái)獲取。看起來(lái)很簡(jiǎn)單的一句話,但其中涉及到很多的細(xì)節(jié),我們會(huì)在后面的數(shù)據(jù)流小節(jié)做集中分析。
到目前為止,AudioTrack已經(jīng)可以通過(guò)IAudioTrack(即上面代碼段中的track變量)來(lái)調(diào)用AudioFlinger提供的服務(wù)了。我們以序列圖來(lái)總結(jié)這一小節(jié):
圖 13?22 AudioTrack的創(chuàng)建流程
創(chuàng)建了AudioTrack后,應(yīng)用實(shí)例通過(guò)不斷寫(xiě)入(AudioTrack::write)數(shù)據(jù)來(lái)回放音頻,這部分代碼與音頻數(shù)據(jù)流有關(guān),我們也放在后面小節(jié)中分析。
聯(lián)系客服