1.1 AudioTrack
1.1.1 AudioTrack應用實例
對於Android應用開發人員來講,音頻回放最熟悉的莫過於MediaPlayer,而AudioTrack相信用的人相對會少很多。這是因爲MediaPlayer提供了更完整的封裝和狀態控制,使得我們用很少的代碼就可以實現一個簡單的音樂播放器。而相比MediaPlayer,AudioTrack更爲精練、高效,實際上MediaPlayerService的內部實現就是使用了AudioTrack。
AudioTrack被用於PCM音頻流的回放,在數據傳送上它有兩種方式:
Ø 調用write(byte[],int,int)或write(short[],int,int)把音頻數據“push”到AudioTrack中。
Ø 與之相對的,當然就是“pull”形式的數據獲取,即數據接收方主動索取的過程,如下圖所示:
圖 13‑20 “push”和“pull”兩種數據傳送模式
除此之外,AudioTrack還同時支持static和streaming兩種模式:
§ static
靜態的言下之意就是數據一次性交付給接收方。好處是簡單高效,只需要進行一次操作就完成了數據的傳遞;缺點當然也很明顯,對於數據量較大的音頻回放,顯然它是無法勝任的,因而通常只用於播放鈴聲、系統提醒等對內存小的操作
§ streaming
流模式和網絡上播放視頻是類似的,即數據是按照一定規律不斷地傳遞給接收方的。理論上它可用於任何音頻播放的場景,不過我們一般在以下情況下采用:
Ø 音頻文件過大
Ø 音頻屬性要求高,比如採樣率高、深度大的數據
Ø 音頻數據是實時產生的,這種情況就只能用流模式了
下面我們選取AudioTrackTest.java爲例來講解,先從使用者的角度來了解下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();
}
這個TestCase是測試立體聲左右聲道最大音量的。順便提一下,關於自動化測試的更多描述,可以參照本書最後一個篇章。用例中涉及到AudioTrack的常規操作都用高顯標示出來了。可見大概是這麼幾個步驟:
Step1@ testSetStereoVolumeMax,getMinBufferSize
字面意思就是獲取最小的buffer大小,這個buffer將用於後面AudioTrack的構造函數。它是AudioTrack可以正常使用的一個最低保障,根據官方的建議如果音頻文件本身要求較高的話,最好可以採用比MinBufferSize更大的數值。這個函數的實現相對簡單:
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:
…
}
首先得出聲道數,目前最多隻支持雙聲道。
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);
…
}
也就是說最小buffer大小取決於採樣率、聲道數和採樣深度三個屬性。那麼具體是如何計算的呢?我們接着看下native層代碼實現:
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);
}
這裏又調用了getMinFrameCount,這個函數用於確定至少需要多少Frame才能保證音頻正常播放。那麼Frame代表了什麼意思呢?可以想象一下視頻中幀的概念,它代表了某個時間點的一幅圖像。這裏的Frame也是類似的,它應該是指某個特定時間點時的音頻數據量,所以android_media_AudioTrack_get_min_buff_size中最後採用的計算公式就是:
至少需要多少幀*每幀數據量
= frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
公式中frameCount就是需要的幀數,每一幀的數據量又等於:
Channel數*每個Channel數據量= nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1)
層層返回getMinBufferSize就得到了保障AudioTrack正常工作的最小緩衝區大小了。
Step2@ testSetStereoVolumeMax,創建AudioTrack實例
有了minBuffSize後,我們就可以創建一個AudioTrack對象了。它的構造函數原型是:
public AudioTrack (int streamType, int sampleRateInHz, intchannelConfig, int audioFormat, int bufferSizeInBytes, int mode)
除了倒數第二個參數是計算出來的,其它入參在這個TestCase中都是直接指定的。比如streamType是STREAM_MUSIC,sampleRateInHz是22050等等。如果是編寫一個音樂播放器,這些參數自然都是需要通過分析音頻文件得出來的,所幸的是Android提供了更加易用的MediaPlayer,使得我們不需要理會這些瑣碎細節。
創建AudioTrack的一個重要任務就是和AudioFlinger建立聯繫,它是由native層的代碼來實現的:
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);
…
}
這裏調用了native_setup來創建一個本地AudioTrack對象,如下:
/*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();
創建一個Storage對象,直覺告訴我們這可能是存儲音頻數據的地方,後面我們再詳細分析。
…
if (memoryMode== javaAudioTrackFields.MODE_STREAM) {
lpTrack->set(…
audioCallback, //回調函數
&(lpJniStorage->mCallbackData),//回調數據
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結束
函數native_setup首先新建了一個AudioTrack(native)對象,然後進行各種屬性的計算,最後調用set函數爲AudioTrack設置這些屬性——我們只保留兩種內存模式(STATIC和STREAM)有差異的地方,便於大家比對理解。對於靜態數據,入參中的倒數第三個是lpJniStorage->mMemBase,而STREAM類型時爲null(0)。
到目前爲止我們還沒有看到它與AudioFlinger有交互的地方,看來謎底應該就在這個set函數中了。
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;
}
當不設置streamType時,會被改爲默認的AUDIO_STREAM_MUSIC。
…
if (format ==AUDIO_FORMAT_DEFAULT) {
format =AUDIO_FORMAT_PCM_16_BIT;
}
if (channelMask == 0) {
channelMask =AUDIO_CHANNEL_OUT_STEREO;
}
採樣深度和聲道數默認爲16bit和立體聲
…
if (format ==AUDIO_FORMAT_PCM_8_BIT && sharedBuffer != 0) {
ALOGE("8-bit datain shared memory is not supported");
return BAD_VALUE;
}
當sharedBuffer!=0時表明是STATIC模式,也就是說靜態數據模式下只支持16bit深度否則就報錯直接返回,這點要特別注意。
…
audio_io_handle_t output = AudioSystem::getOutput(streamType,sampleRate, format, channelMask, flags);
通過上述的有效性檢查後,AudioTrack接着就可以使用底層的音頻服務了。那麼是直接調用AudioFlinger服務提供的接口嗎?理論上這樣子做也是可以的,但Android系統考慮得更細,它在AudioTrack與底層服務間又提供了AudioSystem和AudioService。其中前者同時提供了Java和native兩層的實現,而AudioService則只有native層的實現。這樣子就降低了使用者(AudioTrack)與底層實現(AudioPolicyService、AudioFlinger等等)間的藕合。換句話說,不同版本的Android音頻系統通常改動很大,但只要AudioSystem和AudioService向上的接口不變,那麼AudioTrack就不需要做修改。
所以上面的getOutput是由AudioSystem來提供的,可以猜測到其內部只是做了些簡單的中轉功能,最終還是得由AudioPolicyService/AudioFlinger來實現。這個getOutput尋找最適合當前AudioTrack的audio interface以及Output輸出(也就是前面通過openOutput打開的通道),然後AudioTrack會向這個Output申請一個Track。
…
mVolume[LEFT] = 1.0f;
mVolume[RIGHT] = 1.0f; /*左右聲道的初始值都是最大,另外還有其它成員變量都會在這裏賦值,
我們直接省略掉了*/
…
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函數結束
因爲cbf是audioCallback不爲空,所以這裏會啓動一個AudioTrack線程。這個線程是用於AudioTrack(native)與AudioTrack(java)間的數據事件通知的,這就爲上層應用處理事件提供了一個入口,包括:
EVENT_MORE_DATA = 0, /*請求寫入更多數據*/
EVENT_UNDERRUN = 1, /*PCM 緩衝發生了underrun*/
EVENT_LOOP_END = 2, /*到達loop end,如果loop count不爲空的話
將從loop start重新開始回放*/
EVENT_MARKER = 3, /*Playback head在指定的位置,參考setMarkerPosition*/
EVENT_NEW_POS = 4, /*Playback head在一個新的位置,參考setPositionUpdatePeriod */
EVENT_BUFFER_END = 5 /*Playback head在buffer末尾*/
AudioTrack在AudioFlinger中是以Track來管理的。不過因爲它們之間是跨進程的關係,自然需要一個“橋樑”來維護,這個溝通的媒介是IAudioTrack(這有點類似於顯示系統中的IWindow)。函數createTrack_l除了爲AudioTrack在AudioFlinger中申請一個Track外,還會建立兩者間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服務,還記得上一小節的介紹嗎?AudioFlinger在ServiceManager中註冊,以“media.audio_flinger”爲服務名。
…
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);
//未完待續…
利用AudioFlinger創建一個IAudioTrack,這是它與AudioTrack之間的跨進程通道。除此之處,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中最重要的幾個步驟,即:
· AudioFlinger::checkPlaybackThread_l
在AudioFlinger::openOutput時,產生了全局唯一的audio_io_handle_t值,這個值是與PlaybackThread相對應的,它作爲mPlaybackThreads鍵值對的key值存在。
當AudioTrack調用createTrack時,需要傳入這個全局標記值,checkPlaybackThread_l藉此找到匹配的PlaybackThread
· PlaybackThread::createTrack_l
找到匹配的PlaybackThread後,還需要在其內部創建一個PlaybackThread::Track對象(所有Track都由PlaybackThread::mTracks全局變量管理),這些工作由PlaybackThread::createTrack_l完成。我們以下圖表示它們之間的關係:
圖 13‑21 PlaybackThread:Track的管理
· new TrackHandle
TrackHandle實際上就是IAudioTrack,後續AudioTrack將利用這個binder服務來調用getCblk等接口
我們再接着前面AudioTrack::createTrack_l“未完待續”的部分往下看。
…
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;
}
事實上當PlaybackThread創建一個PlaybackThread::Track對象時,所需的緩衝區空間就已經分配了。這塊空間是可以跨進程共享的,所以AudioTrack可以通過track->getCblk()來獲取。看起來很簡單的一句話,但其中涉及到很多的細節,我們會在後面的數據流小節做集中分析。
到目前爲止,AudioTrack已經可以通過IAudioTrack(即上面代碼段中的track變量)來調用AudioFlinger提供的服務了。我們以序列圖來總結這一小節:
圖 13‑22 AudioTrack的創建流程
創建了AudioTrack後,應用實例通過不斷寫入(AudioTrack::write)數據來回放音頻,這部分代碼與音頻數據流有關,我們也放在後面小節中分析。
原文地址:http://blog.csdn.net/xuesen_lin/article/details/8805168