Audio梳理(一)

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


image.png

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中。

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ù):

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)查一下,非常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)存示意圖”:

image.png

圖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)的家譜。

image.png

圖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ù)都非常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中。一起來看:

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對象     
    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è)問題。

  1. 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。

android_media_AudioTrack.cpp

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,一起來看:

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;
  /*
     假設(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層的分析。

  1. release的分析
    當(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對象中的指針變量此時(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ù)
}

......

}

至此,在Java空間的分析工作就完畢了。

  1. 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容