美文网首页
Audio梳理(一)

Audio梳理(一)

作者: 程序猿想吃肉 | 来源:发表于2023-05-16 17:09 被阅读0次

    Audio系统在Android中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。


    image.png

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

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

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

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

    本文主要分享AudioRcorder和AudioTrack;
    AudioRcorder在之前的文章中已经写过了,这篇文章主要写AudioTrack

    AudioTrack

    1.用例分析

    // 依据音频数据的特性来确定所要分配的缓冲区的最小size
    private int mPrimePlaySize = 0;      //较优播放块大小
    public int mChannel = AudioFormat.CHANNEL_OUT_STEREO;      //  声道
    int minBufSize = AudioTrack.getMinBufferSize(AudioParam.AUDIO_RATE,
                        mChannel, AudioParam.AUDIO_FORMAT);
                mPrimePlaySize = minBufSize;
                
    
    // 创建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();//开始播放
            }
    
    // 调用write写数据
     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中写数据
                        }
                    } catch (RuntimeException | IOException e) {
                        Log.d(TAG, "start write audioFile fail");
                        e.printStackTrace();
                    }
                }
    
     //停止播放和释放资源
      if (mAudioTrack != null) {
                mAudioTrack.stop();//停止播放
                mAudioTrack.release();//释放底层资源
            }
    

    创建AudioTrack时,其中有三个概念,一个是数据载入模式。一个是音频流类型,还有一个是Buffer分配和Frame,接下来进行具体介绍

    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层)]

    
    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_AudioTrack.cpp中。

    android_media_AudioTrack.cpp

    /*
    
    注意我们传入的参数是:
    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;
            }
        }
    ......
    

    这里有必要插入内容,由于代码中出现了音频系统中的一个重要概念: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了。

    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读者能够上网查一下,非常easy
           */
           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       
             ....
    }
    

    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。

    当然,这一切对进程来说都是透明的。这些活都由操作系统悄悄地完毕了。

    这和我们的共享内存会有什么关系吗?

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

    image.png

    图1 共享内存示意图

    图1提出了一个关键性问题,即真实内存中0x87654321标志的这块内存页(OS的内存管理机制将物理内存分成了一个个的内存页,一块内存页的大小通常是4KB)如今已经映射到了进程A中。

    可它能同一时候映射到进程B中吗?假设能。那么在进程A中,对这块内存页所写的数据在进程B中就能看见了。这岂不就做到了内存在两个进程间共享吗?

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

    怎样创建和共享内存呢?不同系统会有不同的方法。

    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类的使用方法。在介绍它们之前,先来看图2中与这两个Memory有关的家谱。

    image.png

    图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提供了以下几个函数,能够获取共享内存的大小和位置。由于这些函数都非常easy,仅把它们的作用描写叙述一下就可以。

    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中第一次见到它们时想到的。不知道你是否注意过这个问题。

    1. 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(); //非常easy的调用
    }
    

    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层的分析。

    1. 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空间的分析工作就完毕了。

    1. AudioTrack(Java空间)的分析总结
      AudioTrack在JNI层使用了Native的AudioTrack对象,总结一下调用Native对象的流程:

    · new一个AudioTrack。使用无參的构造函数。
    · 调用set函数,把Java层的參数传进去,另外还设置了一个audiocallback回调函数。
    · 调用了AudioTrack的start函数。
    · 调用AudioTrack的write函数。
    · 工作完毕后,调用stop。
    · 最后就是Native对象的delete。

    参考文章:
    https://developer.android.com/reference/android/media/AudioRecord
    https://www.cnblogs.com/yutingliuyl/p/6882171.html

    相关文章

      网友评论

          本文标题:Audio梳理(一)

          本文链接:https://www.haomeiwen.com/subject/zutwddtx.html