阿拉神农 · 更新于 2018-11-28 11:00:43

第7章  深入理解Audio系统

本章主要内容

·  详细分析AudioTrack。

·  详细分析AudioFlinger。

·  详细分析AudioPolicyService。

本章涉及的源代码文件名及位置

下面是本章分析的源码文件名及其位置。

·  AudioTrack.java

framework/base/media/java/com/android/media/AudioTrack.java

·  android_media_track.cpp

framework/base/core/jni/android_media_track.cpp

·  MemoryHeapBase

framework/base/libs/binder/MemoryHeapBase.cpp

·  MemoryBase.h

framework/base/include/binder/MemoryBase.h

·  AudioTrack.cpp

framework/base/libmedia/AudioTrack.cpp

·  audio_track_cblk_t声明

framework/base/include/private/media/AudioTrackShared.h

·  audio_track_cblk_t定义

framework/base/media/libmedia/AudioTrack.cpp

·  Main_MediaServer.cpp

framework/base/media/mediaserver/Main_MediaServer.cpp

·  AudioFlinger.cpp

framework/base/libs/audioFlinger/AudioFlinger.cpp

·  AudioHardwareInterface.h

hardware/libhardware_legacy/include/hardware_legacy/AudioHardwareInterface.h

·  AudioMixer.cpp

framework/base/libs/audioflinger/AudioMixer.cpp

·  AudioSystem.h

framework/base/include/media/AudioSystem.h

·  AudioSystem.cpp

framework/base/media/libmedia/AudioSystem.cpp

·  AudioPolicyInterface.h

hardware/libhardware_legacy/include/hardware_legacy

·  AudioPolicyManagerBase.cpp

framework/base/libs/audioflinger/AudioPolicyManagerBase.cpp

·  AudioService.java

framework/base/media/java/com/android/media/AudioService.java

·  Android_media_AudioSystem.cpp

framework/base/core/Jni/Android_media_AudioSystem.cpp

7.1  综述

Audio系统是Android平台的重要组成部分,它主要包括三方面的内容:

·  AudioRcorder和AudioTrack:这两个类属于Audio系统对外提供的API类,通过它们可以完成Android平台上音频数据的采集和输出任务。

·  AudioFlinger:它是Audio系统的工作引擎,管理着系统中的输入输出音频流,并承担音频数据的混音,以及读写Audio硬件以实现数据的输入输出等工作。

·  AudioPolicyService,它是Audio系统的策略控制中心,具有掌管系统中声音设备的选择和切换、音量控制等功能。

Android的Audio系统是我们分析的第一个具有相当难度的复杂系统。对于这种系统,我采取的学习方法是,以一个常见用例为核心,沿着重要函数调用的步骤逐步进行深入分析。中途若出现所需但并不熟悉的知识,则以此为契机,及时学习、思考、研究,当不熟悉的知识逐渐被自己了解掌握时,该系统的真面目也随之清晰了。

下面是破解Audio系统的战略步骤:

·  首先,从API类的AudioTrack开始,从Java层到Native层一步步了解其工作原理。其中AudioTrack和AudioFlinger有较多的交互工作,但在这一步中,我们暂时只集中关注AudioTrack的流程。

·  提炼上一步中AudioTrack和AudioFlinger的交互流程,以这个交互流程作为分析AudioFlinger的突破口。

·  在前面两个步骤中还会有一些剩余的“抵抗分子”,我们将在AudioPolicyService的破解过程中把它们彻底消灭掉。另外,在分析AudioPolicyService时,还会通过一个耳机插入事件的处理实例来帮助分析AudioPolicyService的工作流程。

·  最后,在本章的拓展部分,我们会介绍一下AudioFlinger中DuplicatingThread的工作原理。

说明:在下文中AudioTrack被简写为AT,AudioFlinger被简写为AF,AudioPolicyService被简写为AP。

让我们整装上阵,开始代码的征程吧!

 

7.2  AudioTrack的破解

AudioTrack属于Audio系统对外提供的API类,所以它在Java层和Native层均有对应类,先从Java层的用例开始。

7.2.1 用例介绍

这个用例很简单,但其中会有一些重要概念,应注意理解。

注意:要了解AudioTrack Java API的具体信息,需要仔细阅读Android API中的相关文档。阅读API文档,是一个能快速掌握相关知识的好方法。

[-->AudioTrackAPI使用例子(Java层)]

//① 根据音频数据的特性来确定所要分配的缓冲区的最小size

int bufsize =

          AudioTrack.getMinBufferSize(8000,//采样率:每秒8K个点                     

        AudioFormat.CHANNEL_CONFIGURATION_STEREO,//声道数:双声道           

        AudioFormat.ENCODING_PCM_16BIT//采样精度:一个采样点16比特,相当于2个字节

);

 

//② 创建AudioTrack

AudioTrack trackplayer = new AudioTrack(

                 AudioManager.STREAM_MUSIC,//音频流类型

                 8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,

                   AudioFormat.ENCODING_PCM_16BIT, bufsize,

                 AudioTrack.MODE_STREAM//数据加载模式);

  //③ 开始播放

trackplayer.play() ;

 

......

//④ 调用write写数据

trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中写数据

......

 //⑤ 停止播放和释放资源

trackplayer.stop();//停止播放

trackplayer.release();//释放底层资源

上面的用例引入了两个新的概念,一个是数据加载模式,另一个是音频流类型。下面进行详细介绍。

1. AudioTrack的数据加载模式

AudioTrack有两种数据加载模式:MODE_STREAM和MODE_STATIC,它们对应着两种完全不同的使用场景。

·  MODE_STREAM:在这种模式下,通过write一次次把音频数据写到AudioTrack中。这和平时通过write系统调用往文件中写数据类似,但这种工作方式每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。

·  MODE_STATIC:这种模式下,在play之前只需要把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。这种模式适用于像铃声这种内存占用量较小,延时要求较高的文件。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

这两种模式中以MODE_STREAM模式相对常见和复杂,我们的分析将以它为主。

注意:如果采用STATIC模式,须先调用write写数据,然后再调用play。

2. 音频流的类型

在AudioTrack构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。它的含义与Android系统对音频流的管理和分类有关。

Android将系统的声音分为好几种流类型,下面是几个常见的:

·  STREAM_ALARM:警告声

·  STREAM_MUSIC:音乐声,例如music等

·  STREAM_RING:铃声

·  STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等

·  STREAM_VOCIE_CALL:通话声

注意:上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。

音频流类型的划分和Audio系统对音频的管理策略有关。其具体作用,在以后的分析中再做详细介绍。在目前的用例中,把它当做一个普通数值即可。

3. Buffer分配和Frame的概念

在用例中碰到的第一个重要函数就是getMinBufferSize。这个函数对于确定应用层分配多大的数据Buffer具有重要指导意义。先回顾一下它的调用方式:

[-->AudioTrackAPI使用例子(Java层)]

//注意这些参数的值。想象我们正在一步步的Trace,这些参数都会派上用场

AudioTrack.getMinBufferSize(8000,//每秒8K个点                              

        AudioFormat.CHANNEL_CONFIGURATION_STEREO,//双声道                  

        AudioFormat.ENCODING_PCM_16BIT);

 

来看这个函数的实现:

[-->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精度的音频数据   

        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;

      

       /*

        调用Native函数,先想想为什么,如果是简单计算,那么Java层做不到吗?

        原来,还需要确认硬件是否支持这些参数,当然得进入Native层查询了

        */

       int size = native_get_min_buff_size(sampleRateInHz,         

                 channelCount,audioFormat);                       

        if((size == -1) || (size == 0)) {

             return AudioTrack.ERROR;

        }

       else {

           return size;

        }

}

Native的函数将查询Audio系统中音频输出硬件HAL对象的一些信息,并确认它们是否支持这些采样率和采样精度。

说明:HAL对象的具体实现和硬件厂商有关系,如果没有特殊说明,我们则把硬件和HAL作为一种东西讨论。

来看Native的native_get_min_buff_size函数。它在android_media_track.cpp中。

[-->android_media_track.cpp]

/*

注意我们传入的参数是:

sampleRateInHertz = 8000,nbChannels = 2

audioFormat = AudioFormat.ENCODING_PCM_16BIT

*/

static jintandroid_media_AudioTrack_get_min_buff_size(                    

                 JNIEnv*env,  jobject thiz,

                 jintsampleRateInHertz, jint nbChannels, jint audioFormat)

{

    intafSamplingRate;

    intafFrameCount;

    uint32_t afLatency;

     /*

        下面这些调用涉及了AudioSystem,这个和AudioPolicy有关系。这里仅把它们看成是

信息查询即可

    */

   //查询采样率,一般返回的是所支持的最高采样率,例如44100

    if(AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) {

       return -1;

}

     //① 查询硬件内部缓冲的大小,以Frame为单位。什么是Frame?

    if(AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) {

       return -1;

    }

    //查询硬件的延时时间

    if(AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) {

       return -1;

}

......

这里有必要插入内容,因为代码中出现了音频系统中的一个重要概念:Frame(帧)。

说明:Frame是一个单位,经多方查寻,最终在ALSA的wiki中找到了对它的解释。Frame直观上用来描述数据量的多少,例如,一帧等于多少字节。1单位的Frame等于1个采样点的字节数×声道数(比如PCM16,双声道的1个Frame等于2×2=4字节)。

我们知道,1个采样点只针对一个声道,而实际上可能会有一或多个声道。由于不能用一个独立的单位来表示全部声道一次采样的数据量,也就引出了Frame的概念。Frame的大小,就是一个采样点的字节数×声道数。另外,在目前的声卡驱动程序中,其内部缓冲区也是采用Frame作为单位来分配和管理的。

  OK,继续native_get_min_buff_size函数。

   ......

   // minBufCount表示缓冲区的最少个数,它以Frame作为单位

   uint32_t minBufCount = afLatency / ((1000 *afFrameCount)/afSamplingRate);

    if(minBufCount < 2) minBufCount = 2;//至少要两个缓冲

 

   //计算最小帧个数

   uint32_tminFrameCount =               

         (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate;

  //下面根据最小的FrameCount计算最小的缓冲大小   

   intminBuffSize = minFrameCount //计算方法完全符合我们前面关于Frame的介绍

           * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1)

           * nbChannels;

 

    returnminBuffSize;

}

getMinBufSize会综合考虑硬件的情况(诸如是否支持采样率,硬件本身的延迟情况等)后,得出一个最小缓冲区的大小。一般我们分配的缓冲大小会是它的整数倍。

好了,介绍完一些基本概念后,开始要分析AudioTrack了。

7.2.2  AudioTrack(Java空间)的分析

注意:Java空间的分析包括JNI这一层,因为它们二者的关系最为紧密。

1. AudioTrack的构造

回顾一下用例中调用AudioTrack构造函数的代码:

AudioTrack trackplayer = new AudioTrack(

                 AudioManager.STREAM_MUSIC,

                 8000,AudioFormat.CHANNEL_CONFIGURATION_ STEREO,

                   AudioFormat.ENCODING_PCM_16BIT,bufsize,

                 AudioTrack.MODE_STREAM);

AudioTrack构造函数的实现在AudioTrack.java中。来看这个函数:

[-->AudioTrack.java]

public AudioTrack(int streamType, intsampleRateInHz, int channelConfig,

                 intaudioFormat,int bufferSizeInBytes, int mode)

                  throws IllegalArgumentException {

       

        mState= STATE_UNINITIALIZED;

        //检查参数是否合法

       audioParamCheck(streamType, sampleRateInHz, channelConfig,

                         audioFormat,mode);

       //bufferSizeInBytes是通过getMinBufferSize得到的,所以下面的检查肯定能通过

       audioBuffSizeCheck(bufferSizeInBytes);

 

        /*

           调用native层的native_setup,构造一个WeakReference传进去。

           不了解Java WeakReference读者可以上网查一下,很简单

       */

       int initResult = native_setup(new WeakReference<AudioTrack>(this),

         mStreamType,//这个值是AudioManager.STREAM_MUSIC   

         mSampleRate, //这个值是8000       

        mChannels,   //这个值是2

        mAudioFormat,//这个值是AudioFormat.ENCODING_PCM_16BIT      

         mNativeBufferSizeInBytes,//这个值等于bufferSizeInBytes              

        mDataLoadMode);//DataLoadMode是MODE_STREAM       

         ....

}

OK,native_setup对应的JNI层函数是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;

    //进行一些信息查询

  AudioSystem::getOutputFrameCount(&afFrameCount, streamType);

  AudioSystem::getOutputSamplingRate(&afSampleRate, streamType);

  AudioSystem::isOutputChannel(channels);

   //popCount用于统计一个整数中有多少位为1,有很多经典的算法

int nbChannels = AudioSystem::popCount(channels);

    //Java层的值和JNI层的值转换

    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;

   

    //计算以帧为单位的缓冲大小

    intframeCount = buffSizeInBytes / (nbChannels * bytesPerSample);

 

     //① AudioTrackJniStorage对象,它保存了一些信息,后面将详细分析

   AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();

      ......

     //②创建Native层的AudioTrack对象

   AudioTrack* lpTrack = new AudioTrack();

       if(memoryMode == javaAudioTrackFields.MODE_STREAM) {

       //③STREAM模式

      lpTrack->set(

           atStreamType,//指定流类型

           sampleRateInHertz,

           format,// 采样点的精度,一般为PCM16或者PCM8

           channels,

           frameCount,

           0,// flags

           audioCallback, //该回调函数定义在android_media_AudioTrack.cpp中   

        &(lpJniStorage->mCallbackData),

           0,

           0,// 共享内存,STREAM模式下为空。实际使用的共享内存由AF创建

           true);//内部线程可以调用JNI函数,还记得“zygote偷梁换柱”那一节吗?

         } else if (memoryMode == javaAudioTrackFields.MODE_STATIC) {

          //如果是static模式,需要先创建共享内存

         lpJniStorage->allocSharedMem(buffSizeInBytes);

         lpTrack->set(

           atStreamType,// stream type

           sampleRateInHertz,

           format,// word length, PCM

           channels,

           frameCount,

           0,// flags

           audioCallback,

          &(lpJniStorage->mCallbackData),

           0,

           lpJniStorage->mMemBase, //STATIC模式下,需要传递该共享内存

           true);

    }

 

    ......

    /*

      把JNI层中new出来的AudioTrack对象指针保存到Java对象的一个变量中,

      这样就把JNI层的AudioTrack对象和Java层的AudioTrack对象关联起来了,

     这是Android的常用技法。                        

   */   

     env->SetIntField(thiz,javaAudioTrackFields.nativeTrackInJavaObj,

                     (int)lpTrack);

   // lpJniStorage对象指针也保存到Java对象中

   env->SetIntField(thiz, javaAudioTrackFields.jniData,(int)lpJniStorage);

  }

上边的代码列出了三个要点,这一节仅分析AudioTrackJniStorage这个类,其余的作为Native AudioTrack部分放在后面进行分析。

2. AudioTrackJniStorage分析

AudioTrackJniStorage是一个辅助类,其中有一些有关共享内存方面的较重要的知识,这里先简单介绍一下。

(1) 共享内存介绍

共享内存,作为进程间数据传递的一种手段,在AudioTrack和AudioFlinger中被大量使用。先简单了解一下有关共享内存的知识:

·  每个进程的内存空间是4GB,这个4GB是由指针长度决定的,如果指针长度为32位,那么地址的最大编号就是0xFFFFFFFF,为4GB。

·  上面说的内存空间是进程的虚拟地址空间。换言之,在应用程序中使用的指针其实是指向虚拟空间地址的。那么,如何通过这个虚地址找到存储在真实物理内存中的数据呢?

上面的问题,引出了内存映射的概念。内存映射让虚拟空间中的内存地址和真实物理内存地址之间建立了一种对应关系。也就是说,进程中操作的0x12345678这块内存的地址,在经过OS内存管理机制的转换后,它实际对应的物理地址可能会是0x87654321。当然,这一切对进程来说都是透明的,这些活都由操作系统悄悄地完成了。这和我们的共享内存会有什么关系吗?

当然有,共享内存和内存映射有着重要关系。来看图7-1“共享内存示意图”:

image

图7-1  共享内存示意图

图7-1提出了一个关键性问题,即真实内存中0x87654321标志的这块内存页(OS的内存管理机制将物理内存分成了一个个的内存页,一块内存页的大小一般是4KB)现在已经映射到了进程A中。可它能同时映射到进程B中吗?如果能,那么在进程A中,对这块内存页所写的数据在进程B中就能看见了,这岂不就做到了内存在两个进程间共享吗?

事实确实如此,否则我们的生活就不会像现在这么美好了。这个机制是由操作系统提供和实现的,原理很简单,实现起来却很复杂,这里就不深究了。

如何创建和共享内存呢?不同系统会有不同的方法。Linux平台的一般做法是:

·  进程A创建并打开一个文件,得到一个文件描述符fd。

·  通过mmap调用将fd映射成内存映射文件。在mmap调用中指定特定参数表示要创建进程间共享内存。

·  进程B打开同一个文件,也得到一个文件描述符,这样A和B就打开了同一个文件。

·  进程B也要用mmap调用指定参数表示想使用共享内存,并传递打开的fd。这样A和B就通过打开同一个文件并构造内存映射,实现了进程间内存共享。

注意,这个文件也可以是设备文件。一般来说,mmap函数的具体工作由参数中的那个文件描述符所对应的驱动或内核模块来完成。

除上述一般方法外,Linux还有System V的共享内存创建方法,这里就不再介绍了。总之,AT和AF之间的数据传递,就是通过共享内存方式来完成的。这种方式对于跨进程的大数据量传输来说,是非常高效的。

(2) MemoryHeapBase和MemoryBase类介绍

AudioTrackJniStorage用到了Android对共享内存机制的封装类。所以我们有必要先看看AudioTrackJniStorage的内容。

[-->android_media_AudioTrack.cpp::AudioTrackJniStorage相关]

//下面这个结构就是保存一些变量,没有什么特别的作用

struct audiotrack_callback_cookie {

   jclass      audioTrack_class;

   jobject     audioTrack_ref;

 };

class AudioTrackJniStorage {

   public:

       sp<MemoryHeapBase>    mMemHeap;//这两个Memory很重要

       sp<MemoryBase>         mMemBase;

 

       audiotrack_callback_cookie mCallbackData;

       int                       mStreamType;

 

      boolallocSharedMem(int sizeInBytes) {

     /* 

      注意关于MemoryHeapBase和MemoryBase的用法。   

      先new一个MemoryHeapBase,再以它为参数new一个MemoryBase        

    */  

   //① MemoryHeapBase

    mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");

   //②MemoryBase

    mMemBase= new MemoryBase(mMemHeap, 0, sizeInBytes);

  

    return true;

   }

};

注意代码中所标识的地方,它们很好地展示了这两个Memory类的用法。在介绍它们之前,先来看图7-2中与这两个Memory有关的家谱。

image

图7-2  MemoryHeapBase和MemoryBase的家谱

MemoryHeapBase是一个基于Binder通信的类,根据前面的Binder知识,BpMemoryHeapBase由客户端使用,而MemoryHeapBase完成BnMemoryHeapBase的业务工作。

从MemoryHeapBase开始分析。它的使用方法是:

mMemHeap = new MemoryHeapBase(sizeInBytes, 0,"AudioTrack Heap Base");

它的代码在MemoryHeapBase.cpp中。

[-->MemoryHeapBase.cpp]

/*

   MemoryHeapBase有两个构造函数,我们用的是第一个。

  size表示共享内存大小,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();//获取系统中的内存页大小,一般为4KB

    size =((size + pagesize-1) & ~(pagesize-1));

   /*   

     创建共享内存,ashmem_create_region函数由libcutils提供。

     在真实设备上将打开/dev/ashmem设备得到一个文件描述符,在模拟器上则创建一个tmp文件    

  */

   int fd= ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);

  //下面这个函数将通过mmap方式得到内存地址,这是Linux的标准做法,有兴趣的读者可以看看

   mapfd(fd,size);

}

MemoryHeapBase构造完后,得到了以下结果:

·  mBase变量指向共享内存的起始位置。

·  mSize是所要求分配的内存大小。

·  mFd是ashmem_create_region返回的文件描述符。

另外,MemoryHeapBase提供了以下几个函数,可以获取共享内存的大小和位置。由于这些函数都很简单,仅把它们的作用描述一下即可。

MemoryHeapBase::getBaseID() //返回mFd,如果为负数,表明刚才创建共享内存失败了

MemoryHeapBase::getBase()  //共享内存起始地址

MemoryHeapBase::getSize() //返回mSize,表示内存大小

MemoryHeapBase确实比较简单,它通过ashmem_create_region得到一个文件描述符。

说明:Android系统通过ashmem创建共享内存的原理,和Linux系统中通过打开文件创建共享内存的原理类似,但ashmem设备驱动在这方面做了较大的改进,例如增加了引用计数、延时分配物理内存的机制(即真正使用的时候才去分配内存)等。这些内容,感兴趣的读者还可以自行对其研究。

那么,MemoryBase是何物?它又有什么作用?

MemoryBase也是一个基于Binder通信的类,它比起MemoryHeapBase就更显简单了,看起来更像是一个辅助类。它的声明在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的构造函数

MemoryBase::MemoryBase(constsp<IMemoryHeap>& heap,ssize_t offset, size_t size)

    :mSize(size), mOffset(offset), mHeap(heap)

{

}

MemoryHeapBase和MemoryBase都够简单吧?总结起来不过是:

·  分配了一块共享内存,这样两个进程可以共享这块内存。

·  基于Binder通信,这样使用这两个类的进程就可以交互了。

这两个类在后续的讲解中会频繁碰到,但不必对它们做深入分析,只需把它当成普通的共享内存看待即可。

提醒:这两个类没有提供同步对象来保护这块共享内存,所以后续在使用这块内存时,必然需要一个跨进程的同步对象来保护它。这一点,是我在AT中第一次见到它们时想到的,不知道你是否注意过这个问题。

 

3. play和write的分析

还记得用例中的③和④关键代码行吗?

//③ 开始播放

trackplayer.play() ;

//④ 调用write写数据

trackplayer.write(bytes_pkg, 0,bytes_pkg.length) ;//往track中写数据

现在就来分析它们。我们要直接转向JNI层来进行分析。相信你,现在已有能力从Java层直接跳转至JNI层了。

(1) play的分析

先看看play函数对应的JNI层函数,它是android_media_AudioTrack_start。

[-->android_media_AudioTrack.cpp]

static void

android_media_AudioTrack_start(JNIEnv *env,jobject thiz)

{

/*

  从Java的AudioTrack对象中获取对应Native层的AudioTrack对象指针。

 从int类型直接转换成指针,不过要是以后ARM平台支持64位指针了,代码就得大修改了。

*/

   AudioTrack *lpTrack = (AudioTrack *)env->GetIntField(

        thiz,javaAudioTrackFields.nativeTrackInJavaObj);

   lpTrack->start(); //很简单的调用

}

play函数太简单了,至于它调用的start,等到Native层进行AudioTrack分析时,我们再去观察。

(2) write的分析

Java层的write函数有两个:

·  一个是用来写PCM16数据的,它对应的一个采样点的数据量是两个字节。

·  另外一个用来写PCM8数据的,它对应的一个采样点的数据量是一个字节。

我们的用例中采用的是PCM16数据。它对应的JNI层函数是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);

}

无论PCM16还是PCM8数据,最终都会调用writeToTrack函数。

[-->android_media_AudioTrack.cpp]

jint writeToTrack(AudioTrack* pTrack, jintaudioFormat,

                 jbyte*data,jint offsetInBytes, jint sizeInBytes) {

    

   ssize_t written = 0;

  /*

     如果是STATIC模式,sharedBuffer()返回不为空

     如果是STREAM模式,sharedBuffer()返回空

  */

      if (pTrack->sharedBuffer() == 0) {

         //我们的用例是STREAM模式,调用write函数写数据

       written = pTrack->write(data + offsetInBytes, sizeInBytes);

    } else{

        if (audioFormat == javaAudioTrackFields.PCM16){

           if ((size_t)sizeInBytes > pTrack->sharedBuffer()->size()) {

               sizeInBytes = pTrack->sharedBuffer()->size();

           }

        //在STATIC模式下,直接把数据memcpy到共享内存,记住在这种模式下要先调用write

        //后调用play

          memcpy(pTrack->sharedBuffer()->pointer(),

                         data+ offsetInBytes, sizeInBytes);

           written = sizeInBytes;

        }else if (audioFormat == javaAudioTrackFields.PCM8) {

          //如果是PCM8数据,则先转换成PCM16数据再拷贝

           ......

    }

    returnwritten;

}

看上去,play和write这两个函数还真是比较简单,须知,大部分工作还都是由Native的AudioTrack来完成的。继续Java层的分析。

4. release的分析

当数据都write完后,需要调用stop停止播放,或者直接调用release来释放相关资源。由于release和stop有一定的相关性,这里只分析release调用。

[-->android_media_AudioTrack.cpp]

static voidandroid_media_AudioTrack_native_release(JNIEnv *env,  jobject thiz) {

      

    //调用android_media_AudioTrack_native_finalize真正释放资源

   android_media_AudioTrack_native_finalize(env, thiz);

    //之前保存在Java对象中的指针变量此时都要设置为零

   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();//调用stop

       delete lpTrack; //调用AudioTrack的析构函数

}

......

}

扫尾工作也很简单,没什么需要特别注意的。

至此,在Java空间的分析工作就完成了。但在进入Native空间的分析之前,要总结一下Java空间使用Native的AudioTrack的流程,只有这样,在进行Native空间分析时才能有章可循。

5. AudioTrack(Java空间)的分析总结

AudioTrack在JNI层使用了Native的AudioTrack对象,总结一下调用Native对象的流程:

·  new一个AudioTrack,使用无参的构造函数。

·  调用set函数,把Java层的参数传进去,另外还设置了一个audiocallback回调函数。

·  调用了AudioTrack的start函数。

·  调用AudioTrack的write函数。

·  工作完毕后,调用stop。

·  最后就是Native对象的delete。

说明:为什么要总结流程呢?

第一:控制了流程,就把握了系统工作的命脉,这一点至关重要。

第二:有些功能的实现纵跨Java/Native层,横跨两个进程,这中间有很多封装、很多的特殊处理,但是其基本流程是不变的。通过精简流程,我们才能把注意力集中在关键点上。

 

7.2.3  AudioTrack(Native空间)的分析

1. new AudioTrack和set分析

Native的AudioTrack代码在AudioTrack.cpp中。这一节,分析它的构造函数和set调用。

[-->AudioTrack.cpp]

AudioTrack::AudioTrack()//我们使用无参构造函数

    :mStatus(NO_INIT)

{

  //把状态初始化成NO_INIT。Android的很多类都采用了这种状态控制

}

再看看set调用,这个函数有很多内容。

[-->AudioTrack.cpp]

/* 

   还记得我们传入的参数吗?

 streamType=STREAM_MUSIC,sampleRate=8000,format=PCM_16

 channels=2,frameCount由计算得来,可以假设一个值,例如1024,不影响分析。

  flags=0,cbf=audiocallback, user为cbf的参数,notificationFrames=0

  因为是流模式,所以sharedBuffer=0。threadCanCallJava 为true

*/

status_t AudioTrack::set(int streamType,uint32_t sampleRate,int format,

       int channels,int frameCount,uint32_t flags,callback_t cbf,void* user,

       int notificationFrames,const sp<IMemory>& sharedBuffer,

        boolthreadCanCallJava)

{

    //前面有一些判断,都是和AudioSystem有关的,以后再分析

    ......

  /*

     audio_io_handle_t是一个int类型,通过typedef定义,这个值的来历非常复杂,

涉及AudioFlinger和AudioPolicyService, 后边的分析试将其解释清楚。

这个值主要被AudioFlinger使用,用来表示内部的工作线程索引号。AudioFlinger会根据

情况创建几个工作线程,下面的AudioSystem::getOutput会根据流类型等其他参数最终选

取一个合适的工作线程,并返回它在AF中的索引号。

而AudioTrack一般使用混音线程(Mixer Thread)

 */

    audio_io_handle_toutput = AudioSystem::getOutput(

                         (AudioSystem::stream_type)streamType,

                          sampleRate,format, channels,

                         (AudioSystem::output_flags)flags);

   //调用creatTrack

   status_t status = createTrack(streamType, sampleRate, format,channelCount,

                                  frameCount,flags, sharedBuffer, output);

    

 

//cbf是JNI层传入的回调函数audioCallback,如果用户设置了回调函数,则启动一个线程

  if (cbf!= 0) {

       mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);

   }

   returnNO_ERROR;

}

再看createTrack函数:

[-->AudioTrack.cpp]

status_t AudioTrack::createTrack(intstreamType,uint32_t sampleRate,

       int format,int channelCount,int frameCount, uint32_t flags,

       const sp<IMemory>& sharedBuffer, audio_io_handle_t output)

{

   status_tstatus;

 

  /*

    得到AudioFlinger的Binder代理端BpAudioFlinger。

    关于这部分内容,我们已经很熟悉了,以后的讲解会跨过Binder,直接分析Bn端的实现

  */

  constsp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();

  

 /*

向AudioFinger发送createTrack请求。注意其中的几个参数,

在STREAM模式下sharedBuffer为空

    output为AudioSystem::getOutput得到一个值,代表AF中的线程索引号

该函数返回IAudioTrack(实际类型是BpAudioTrack)对象,后续AF和AT的交互就是

围绕IAudioTrack进行的

*/

   sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),

        streamType,sampleRate,format,channelCount,frameCount,

        ((uint16_t)flags) << 16,sharedBuffer,output,&status);

 

   /*

     在STREAM模式下,没有在AT