7.2.3 AudioTrack(Native空间)分析

最后更新于:2022-04-02 05:51:20

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& 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& sharedBuffer, audio_io_handle_t output) { status_tstatus; /* 得到AudioFlinger的Binder代理端BpAudioFlinger。 关于这部分内容,我们已经很熟悉了,以后的讲解会跨过Binder,直接分析Bn端的实现 */ constsp& audioFlinger = AudioSystem::get_audio_flinger(); /* 向AudioFinger发送createTrack请求。注意其中的几个参数, 在STREAM模式下sharedBuffer为空 output为AudioSystem::getOutput得到一个值,代表AF中的线程索引号 该函数返回IAudioTrack(实际类型是BpAudioTrack)对象,后续AF和AT的交互就是 围绕IAudioTrack进行的 */ sp track = audioFlinger->createTrack(getpid(), streamType,sampleRate,format,channelCount,frameCount, ((uint16_t)flags) << 16,sharedBuffer,output,&status); /* 在STREAM模式下,没有在AT端创建共享内存,但前面提到了AT和AF的数据交互是 通过共享内存完成的,这块共享内存最终由AF的createTrack创建。我们以后分析AF时 再做介绍。下面这个调用会取出AF创建的共享内存 */ sp cblk = track->getCblk(); mAudioTrack.clear();//sp的clear mAudioTrack= track; mCblkMemory.clear(); mCblkMemory= cblk;//cblk是control block的简写 /* IMemory的pointer在此处将返回共享内存的首地址,类型为void*, static_cast直接把这个void*类型转成audio_track_cblk_t,表明这块内存的首部中存在 audio_track_cblk_t这个对象 */ mCblk= static_cast(cblk->pointer()); mCblk->out = 1;//out为1表示输出,out为0表示输入 mFrameCount = mCblk->frameCount; if(sharedBuffer == 0) { //buffers指向数据空间,它的起始位置是共享内存的首部加上audio_track_cblk_t的大小 mCblk->buffers= (char*)mCblk + sizeof(audio_track_cblk_t); } else { //STATIC模式下的处理 mCblk->buffers =sharedBuffer->pointer(); mCblk->stepUser(mFrameCount);//更新数据位置,后面要分析stepUser的作用 } returnNO_ERROR; } ~~~ (1)IAudioTrack和AT、AF的关系 上面的createTrack函数中突然冒出来一个新面孔,叫IAudioTrack。关于它和AT及AF的关系,我们用图7-3来表示: :-: ![](http://img.blog.csdn.net/20150802160437437?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-3 IAudioTrack和AT、AF的关系 从图7-3中可以发现: - IAudioTrack是联系AT和AF的关键纽带。 至于IAudioTrack在AF端到底是什么,在分析AF时会有详细解释。 (2)共享内存及其Control Block 通过前面的代码分析,我们发现IAudioTrack中有一块共享内存,其头部是一个audio_track_cblk_t(简称CB)对象,在该对象之后才是数据缓冲。这个CB对象有什么作用呢? 还记得前面提到的那个深层次思考的问题吗?即MemoryHeapBase和MemoryBase都没有提供同步对象,那么,AT和AF作为典型的数据生产者和消费者,如何正确协调二者生产和消费的步调呢? Android为顺应民意,便创造出了这个CB对象,其主要目的就是协调和管理AT和AF二者数据生产和消费的步伐。先来看CB都管理些什么内容。它的声明在AudioTrackShared.h中,而定义却在AudioTrack.cpp中。 **AudioTrackShared.h::audio_track_cblk_t声明** ~~~ struct audio_track_cblk_t { Mutex lock; Condition cv;//这是两个同步变量,初始化的时候会设置为支持跨进程共享 /* 一块数据缓冲同时被生产者和消费者使用,最重要的就是维护它的读写位置了。 下面定义的这些变量就和读写的位置有关,虽然它们的名字并不是那么直观。 另外,这里提一个扩展问题,读者可以思考一下: volatile支持跨进程吗?要回答这个问题需要理解volatile、CPU Cache机制和共享内存的本质 */ volatile uint32_t user; //当前写位置(即生产者已经写到什么位置了) volatile uint32_t server; //当前读位置 /* userBase和serverBase要和user及server结合起来用。 CB巧妙地通过上面几个变量把一块线性缓冲当做环形缓冲来使用,以后将单独分析这个问题 */ uint32_t userBase; // uint32_t serverBase; void* buffers; //指向数据缓冲的首地址 uint32_t frameCount;//数据缓冲的总大小,以Frame为单位 uint32_t loopStart; //设置打点播放(即设置播放的起点和终点) uint32_t loopEnd; int loopCount;//循环播放的次数 volatile union { uint16_t volume[2]; uint32_t volumeLR; }; //和音量有关系,可以不管它 uint32_t sampleRate;//采样率 uint32_t frameSize;//一单位Frame的数据大小 uint8_t channels;//声道数 uint8_t flowControlFlag;//控制标志,见下文分析 uint8_t out; // AudioTrack为1,AudioRecord为0 uint8_t forceReady; uint16_t bufferTimeoutMs; uint16_t waitTimeMs; //下面这几个函数很重要,后续会详细介绍它们 uint32_t stepUser(uint32_tframeCount);//更新写位置 bool stepServer(uint32_tframeCount);//更新读位置 void* buffer(uint32_toffset) const;//返回可写空间起始位置 uint32_t framesAvailable();//还剩多少空间可写 uint32_t framesAvailable_l(); uint32_t framesReady();//是否有可读数据 } ~~~ 关于CB对象,这里要专门讲解一下其中flowControlFlag的意思: - 对于音频输出来说,flowControlFlag对应着underrun状态,underrun状态是指生产者提供数据的速度跟不上消费者使用数据的速度。这里的消费者指的是音频输出设备。由于音频输出设备采用环形缓冲方式管理,当生产者没有及时提供新数据时,输出设备就会循环使用缓冲中的数据,这样就会听到一段重复的声音。这种现象一般被称作“machinegun”。对于这种情况,一般的处理方法是暂停输出,等数据准备好后再恢复输出。 - 对于音频输入来说,flowControlFlag对于着overrun状态,它的意思和underrun一样,只是这里的生产者变成了音频输入设备,而消费者变成了Audio系统的AudioRecord。 * * * * * **说明**:目前这个参数并不直接和音频输入输出设备的状态有关系。它在AT和AF中的作用必须结合具体情况,才能分析。 * * * * * 图7-4表示CB对象和它所驻留的共享内存间的关系: :-: ![](http://img.blog.csdn.net/20150802160453692?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图7-4 共享内存和CB的关系 * * * * * **注意**:CB实际是按照环形缓冲来处理数据读写的,所以user和server的真实作用还需要结合userBase和serverBase。图7-4只是一个示意图。 * * * * * 另外,关于CB,还有一个神秘的问题。先看下面这行代码: ~~~ mCblk =static_cast(cblk->pointer()); ~~~ 这看起来很简单,但仔细琢磨会发现其中有一个很难解释的问题: - cblk->pointer返回的是共享内存的首地址,怎么把audio_track_cblk_t对象塞到这块内存中呢? 这个问题将通过对AudioFlinger的分析,得到答案。 * * * * * **说明**:关于audio_track_cblk_t的使用方式,后文会有详细分析。 * * * * * (3)数据的Push or Pull 在JNI层的代码中可以发现,在构造AudioTrack时,传入了一个回调函数audioCallback。由于它的存在,导致了Native的AudioTrack还将创建另一个线程AudioTrackThread。它有什么用呢? 这个线程与外界数据的输入方式有关系,AudioTrack支持两种数据输入方式: - Push方式:用户主动调用write写数据,这相当于数据被push到AudioTrack。MediaPlayerService一般使用这种这方式提供数据。 - Pull方式:AudioTrackThread将利用这个回调函数,以EVENT_MORE_DATA为参数主动从用户那pull数据。ToneGenerator使用这种方式为AudioTrack提供数据。 这两种方式都可以使用,不过回调函数除了EVENT_MORE_DATA外,还能表达其他许多意图,这是通过回调函数的第一个参数来表明的。一起来看: **AudioTrack.h::event_type** ~~~ enum event_type { EVENT_MORE_DATA = 0, //表示AudioTrack需要更多数据 EVENT_UNDERRUN = 1,//这是Audio的一个术语,表示Audio硬件处于低负荷状态 //AT可以设置打点播放,即设置播放的起点和终点,LOOP_END表示已经到达播放终点 EVENT_LOOP_END= 2, /* 数据使用警戒通知。该值可通过setMarkerPosition ()设置。 当数据使用超过这个值时,AT会且仅通知一次,有点像WaterMarker。 这里所说的数据使用,是针对消费者AF消费的数据量而言的 */ EVENT_MARKER = 3, /* 数据使用进度通知。进度通知值由setPositionUpdatePeriod()设置, 例如每使用500帧通知一次 */ EVENT_NEW_POS = 4, EVENT_BUFFER_END = 5 //数据全部被消耗 }; ~~~ 请看AudioTrackThread的线程函数threadLoop。 **AudioTrack.cpp** ~~~ bool AudioTrack::AudioTrackThread::threadLoop() { //mReceiver就是创建该线程的AudioTrack returnmReceiver.processAudioBuffer(this); } ~~~ **AudioTrack.cpp** ~~~ bool AudioTrack::processAudioBuffer(constsp& thread) { BufferaudioBuffer; uint32_t frames; size_twrittenSize; //处理underun的情况 if(mActive && (mCblk->framesReady() == 0)) { if(mCblk->flowControlFlag == 0) { mCbf(EVENT_UNDERRUN, mUserData, 0);//under run 通知 if (mCblk->server == mCblk->frameCount) { /* server是读位置,frameCount是buffer中的数据总和 当读位置等于数据总和时,表示数据都已经使用完了 */ mCbf(EVENT_BUFFER_END, mUserData, 0); } mCblk->flowControlFlag = 1; if (mSharedBuffer != 0) return false; } } // 循环播放通知 while(mLoopCount > mCblk->loopCount) { int loopCount = -1; mLoopCount--; if(mLoopCount >= 0) loopCount = mLoopCount; //一次循环播放完毕,loopCount表示还剩多少次 mCbf(EVENT_LOOP_END, mUserData, (void *)&loopCount); } if(!mMarkerReached && (mMarkerPosition > 0)) { if(mCblk->server >= mMarkerPosition) { //如果数据使用超过警戒值,则通知用户 mCbf(EVENT_MARKER, mUserData, (void *)&mMarkerPosition); //只通知一次,因为该值被设为true mMarkerReached = true; } } if(mUpdatePeriod > 0) { while (mCblk->server >= mNewPosition) { /* 进度通知,但它不是以时间为基准,而是以帧数为基准的。 例如设置每500帧通知一次,假设消费者一次就读了1500帧,那么这个循环会连续通知3次 */ mCbf(EVENT_NEW_POS, mUserData, (void *)&mNewPosition); mNewPosition += mUpdatePeriod; } } if(mSharedBuffer != 0) { frames = 0; } else{ frames = mRemainingFrames; } do { audioBuffer.frameCount = frames; //得到一块可写的缓冲 status_t err = obtainBuffer(&audioBuffer, 1); ...... //从用户那pull数据 mCbf(EVENT_MORE_DATA, mUserData, &audioBuffer); writtenSize = audioBuffer.size; ...... if(writtenSize > reqSize) writtenSize = reqSize; //PCM8数据转PCM16 ....... audioBuffer.size = writtenSize; audioBuffer.frameCount = writtenSize/mCblk->frameSize; frames -= audioBuffer.frameCount; releaseBuffer(&audioBuffer);//写完毕,释放这块缓冲 } while(frames);  ...... returntrue; } ~~~ 关于obtainBuffer和releaseBuffer,后面再分析。这里有一个问题值得思考: - 用例会调用write函数写数据,AudioTrackThread的回调函数也让我们提供数据。难道我们同时在使用Push和Pull模式? 这太奇怪了!来查看这个回调函数的实现,了解一下究竟是怎么回事。该回调函数是通过set调用传入的,对应的函数是audioCallback。 **android_media_AudioTrack.cpp** ~~~ static void audioCallback(int event, void* user,void *info) { if(event == AudioTrack::EVENT_MORE_DATA) { //很好,没有提供数据,也就是说,虽然AudioTrackThread通知了EVENT_MORE_DATA, //但是我们并没有提供数据给它 AudioTrack::Buffer* pBuff = (AudioTrack::Buffer*)info; pBuff->size = 0; } ...... ~~~ 悬着的心终于放下来了,还是老老实实地看Push模式下的数据输入吧。 2. write输入数据 write函数涉及Audio系统中最重要的关于数据如何传输的问题,在分析它的时候,不妨先思考一下它会怎么做。回顾一下我们已了解的信息: - 有一块共享内存。 - 有一个控制结构,里边有一些支持跨进程的同步变量。 有了这些东西,write的工作方式就非常简单了: - 通过共享内存传递数据。 - 通过控制结构协调生产者和消费者的步调。 >[info] **重点强调**:带着问题和思考来分析代码相当于“智取”,它比一上来就直接扎入源码的“强攻”要高明得多。希望我们能掌握这种思路和方法。 好了,现在开始分析write,看看它的实现是不是如所想的那样。 **AudioTrack.cpp** ~~~ ssize_t AudioTrack::write(const void* buffer,size_t userSize) { if(mSharedBuffer != 0) return INVALID_OPERATION; if(ssize_t(userSize) < 0) { returnBAD_VALUE; } ssize_t written = 0; constint8_t *src = (const int8_t *)buffer; BufferaudioBuffer; // Buffer是一个辅助性的结构 do { //以帧为单位 audioBuffer.frameCount = userSize/frameSize(); //obtainBuffer从共享内存中得到一块空闲的数据块 status_terr = obtainBuffer(&audioBuffer, -1); ...... size_t toWrite; if(mFormat == AudioSystem::PCM_8_BIT && !(mFlags &AudioSystem::OUTPUT_FLAG_DIRECT)) { //PCM8数据转PCM16 }else { //空闲数据缓冲的大小是audioBuffer.size。 //地址在audioBuffer.i8中,数据传递通过memcpy完成 toWrite = audioBuffer.size; memcpy(audioBuffer.i8, src, toWrite); src += toWrite; } userSize -= toWrite; written += toWrite; //releaseBuffer更新写位置,同时会触发消费者 releaseBuffer(&audioBuffer); }while (userSize); returnwritten; } ~~~ 通过write函数,会发现数据的传递其实是很简单的memcpy,但消费者和生产者的协调,则是通过obtainBuffer与releaseBuffer来完成的。现在来看这两个函数。 3. obtainBuffer和releaseBuffer 这两个函数展示了做为生产者的AT和CB对象的交互方法。先简单看看,然后把它们之间交互的流程记录下来,以后在CB对象的单独分析部分,我们再来做详细介绍。 **AudioTrack.cpp** ~~~ status_t AudioTrack::obtainBuffer(Buffer*audioBuffer, int32_t waitCount) { intactive; status_t result; audio_track_cblk_t* cblk = mCblk; ...... //①调用framesAvailable,得到当前可写的空间大小 uint32_t framesAvail = cblk->framesAvailable(); if(framesAvail == 0) { ...... //如果没有可写空间,则要等待一段时间 result= cblk->cv.waitRelative(cblk->lock,milliseconds(waitTimeMs)); ...... } cblk->waitTimeMs = 0; if(framesReq > framesAvail) { framesReq = framesAvail; } //user为可写空间起始地址 uint32_t u = cblk->user; uint32_tbufferEnd = cblk->userBase + cblk->frameCount; if (u+ framesReq > bufferEnd) { framesReq = bufferEnd - u; } ...... //②调用buffer,得到可写空间的首地址 audioBuffer->raw = (int8_t *)cblk->buffer(u); active= mActive; returnactive ? status_t(NO_ERROR) : status_t(STOPPED); } ~~~ obtainBuffer的功能,就是从CB管理的数据缓冲中得到一块可写空间,而releaseBuffer,则是在使用完这块空间后更新写指针的位置。 **AudioTrack.cpp** ~~~ void AudioTrack::releaseBuffer(Buffer*audioBuffer) { audio_track_cblk_t* cblk = mCblk; cblk->stepUser(audioBuffer->frameCount);// ③调用stepUser更新写位置 } ~~~ obtainBuffer和releaseBuffer与CB交互,一共会有三个函数调用,如下所示: - framesAvailable判断是否有可写空间。 - buffer得到写空间起始地址。 - stepUser更新写位置。 请记住这些流程,以后在分析CB时会发现它们有重要作用。 4. delete AudioTrack 到这里,AudioTrack的使命就进入倒计时阶段了。来看在它生命的最后还会做一些什么工作。 **AudioTrack.cpp** ~~~ AudioTrack::~AudioTrack() { if(mStatus == NO_ERROR) { stop();//调用stop if(mAudioTrackThread != 0) { //通知AudioTrackThread退出 mAudioTrackThread->requestExitAndWait(); mAudioTrackThread.clear(); } mAudioTrack.clear(); //将残留在IPCThreadState 发送缓冲区的信息发送出去 IPCThreadState::self()->flushCommands(); } } ~~~ 如果不调用stop,析构函数也会先调用stop,这个做法很周到。 **AudioTrack.cpp** ~~~ void AudioTrack::stop() { sp t = mAudioTrackThread; if (t!= 0) { t->mLock.lock(); } if(android_atomic_and(~1, &mActive) == 1) { mCblk->cv.signal(); /* mAudioTrack是IAudioTrack类型,其stop的最终处理在AudioFlinger端 */ mAudioTrack->stop(); //清空循环播放设置 setLoop(0, 0, 0); mMarkerReached = false; if (mSharedBuffer != 0) { flush(); } if(t != 0) { t->requestExit();//请求退出AudioTrackThread }else { setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_NORMAL); } } if (t!= 0) { t->mLock.unlock(); } } ~~~ stop的工作比较简单,就是调用IAudioTrack的stop,并且还要求退出回调线程。要重点关注IAudioTrack的stop函数,这个将做为AT和AF交互流程中的一个步骤来分析。
';