Android音頻系統之AudioTrack(一)

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

發佈了6 篇原創文章 · 獲贊 15 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章