android audio buffer 分析
2016-02-20 19:58
441 查看
我们知道,播放audio的时候,音频数据是从AT传送到AF的,然后AF中的audiomixer来读取PCM数据做mix
下面对这个流程做大体分析。
首先,分析一下AT和AF之间传递数据使用的内存是在哪里分配的。
AT章节里面,我们分析AudioTrack::createTrack_l函数时,有这么一段代码:
我们是不是可以这么猜想:audioFlinger->createTrack中就完成了audio buffer的分配?
那我们看一下这个函数。
继续往里跟踪。
在TrackBase的构造函数AudioFlinger::ThreadBase::TrackBase::TrackBase里面,我们可以发现:
这里实际上就是在需要的size基础上,多申请一块内存,用来存放 audio_track_cblk_t 这个头信息。
audio_track_cblk_t 是个啥呢?
audio_track_cblk_t的主要数据成员:
user -- AudioTrack当前的写位置的偏移
userBase -- AudioTrack写偏移的基准位置,结合user的值方可确定真实的FIFO地址指针
server -- AudioFlinger当前的读位置的偏移
serverBase -- AudioFlinger读偏移的基准位置,结合server的值方可确定真实的FIFO地址指针
frameCount -- FIFO的大小,以音频数据的帧为单位,16bit的音频每帧的大小是2字节
buffers -- 指向FIFO的起始地址
out -- 音频流的方向,对于AudioTrack,out=1,对于AudioRecord,out=0
audio_track_cblk_t的主要成员函数:
framesAvailable_l()和framesAvailable()用于获取FIFO中可写的空闲空间的大小,只是加锁和不加锁的区别。playback 的场景中,主要 用于AT来write
framesReady()用于获取FIFO中可读取的空间大小。playback场景中,主要用于AF来read
我们可以用下面这个图来解释这块内存的结构:
也就是说, TrackBase里面申请了一块内存,内存的前部是一个audio_track_cblk_t信息,用于指导读写位置的同步工作。内存的后部才是真正的缓冲区。
然后我们回到client->heap()->allocate(size)看一下具体是从哪里分配的内存
这里的client就是client = registerPid_l(pid)创建的。
其构造函数里面:
我们知道,AF中最多支持32个Track,
在AudioFlinger::PlaybackThread::createTrack_l函数里,
可以看到,不管AT来自什么进程空间,都会创建Track,
也就是说,这32个Track可以来自不同的进程空间的不同AT,也可以来自同一进程空间同一AT的,也可以来自同一进程空间的不同AT,
我们分析registerPid_l函数就会发现,每个进程空间都会只有一个client,一个client只会申请1M的空间,
也就是说,每个进程空间不管有多少AT ,都会共用这1M的空间。具体每个AT使用多少,由client->heap()->allocate(size)来实报实销。
而不同的进程空间之间,则是各自独立的1M空间,互不干扰。
继续往下跟踪:
这也是为什么AT和AF处于两个不同的进程空间,但是却能够共享传递数据了。
现在我们知道了AT和AF之间是用的共享内存了,那么具体运行起来的时候,是什么样子的呢?
我们从AT的write函数来分析
这样音频数据就写入了AF中的共享内存了。
下面分析一下 obtainBuffer
,以及通过buffer函数确定了真正的buffer的起始地址
首先,通过调用framesAvailable函数,获得了整个buffer里面,可以用来写入的空闲空间大小。
这样,obtainBuffer中,framesAvailable_l之后,framesReq就初步预存了可用空间的值,然后又比较了framesReq和bufferEnd-u的值。
那么,bufferEnd-u又是什么呢?
在代码中,我们可以看到,bufferEnd=userBase+mFrameCount
那么我们的疑问就成了userBase+mFrameCount-u了
mFrameCount没有什么神秘的,就是整个buffer的大小而已。
那么整个公式具体代表什么意思呢?
前面我们介绍了一下audio_track_cblk_t中的几个数据变量的含义,
重点就是 user,userBase,server,serverBase 这四个变量。
需要注意的是,这四个变量的值并不是读写位置相对于buffer的起点,物理上的offset值,而是一个经过映射的虚拟值。
随着读写的进行,user,userBase,server,serverBase这四个值在不断的累加,
超过了buffer的实际的size之后,四个值会在一个虚拟的空间里继续累加,从而形成了一个环的组织形式。
其中,userBase,serverBase的累加单元就是buffer的实际的size frameCount.
user,userBase的累加单元,就是实际的读写的数量。
这个映射的关系,可以从下面图中表示出来
假设某一时刻的映射关系如下,我们有一段数据准备写入。
看完这个图,是不是明白了?
总结一下,实际上,这段buffer是按照环的形式来读写的,随着读写的继续,user和server都是在不断向后移动。
由于数据写入的时候,采用了memcpy, 而memcpy只能向一个方向单向拷贝,不可能扭回头去再继续拷贝,所以这里每次memcpy的操作,只能是针对user 指向的位置向后的那段空间。
而后面空间的终点,就是这段内存物理上的重点。映射过来,就是bufferEnd了。
那么这段空间的可用大小,就是bufferEnd-user了。也就是上图中橙色区域。
所以前面讲的,比较framesReq和bufferEnd-u的值,实际上就是为了获取到memcpy可以执行的size的大小。
现在obtainBuffer中,我们知道了memcpy可以执行的size大小,那么可以执行的目的地址怎么确定呢?
这就是mProxy->buffer(u)起到的作用了。
再乘上framesize,就得出了byte偏移量了,这样,buffer函数就可以得到user在实际data buffer中的位置了。
memcpy的目的地址也就知道了。
这样,obtainBuffer函数执行完毕后,就可以知道要写入的buffer的地址和可写入数据的大小了。
前面我们提到,user,userBase,server,serverBase这四个值在不断的累加,那么具体的累加操作是在哪里执行的呢?
这就是stepUser和stepServer函数的作用了。(这两个函数在releaseBuffer函数里调用,也就是说内存复制完毕之后,就会被调用了)
在stepUser的函数执行完毕后,整个buffer的映射关系如下:
这样,在下一次obtainBuffer操作之后,经过framesAvaliable函数计算出来的可用空间就是图中绿色区域。
发现没有?可用空间从buffer尾的橙色区域,转移到了buffer头的绿色区域,是不是实现了一个环的操作?
绿色和橙色之间的区域,就是AF尚未读取的数据区。
前面分析了AT的写操作。下面分析一下AF的读操作。
首先要解决的问题是,AF什么时候去读呢?
前面的AF章节中,我们介绍过playbackThread,没错,就是在AudioFlinger::PlaybackThread::threadLoop里面,循环的去读取的!
另外,我们重温一下obtainBuffer函数
经过binder调用,最后会调用到AF中的 AudioFlinger::PlaybackThread::Track::start, 这里实际上是把track添加到了playbackthread中,并且激活了track的状态。
然后AF就可以开始读了。
在obtainBuffer函数中,你会发现有很多lock,unlock的操作。这里主要是为了保护audio_track_cblk_t这个临界区中的变量。因为audio_track_cblk_t位于共享内存,可以被AF和AT两个进程访问,所以一定要加锁同步。
AF章节中,我们讲过,AudioFlinger::PlaybackThread::threadLoop进行audioMixer操作,
在audioMixer中,我们以process__genericNoResampling为例分析
status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
在buffer使用完毕之后,t.bufferProvider->releaseBuffer(&t.buffer)被调用,当中调用了stepServer函数,用来更新server,serverBase.
至此,AT与AF之间如何传递数据,以及数据是怎么共享的,两端如何同步的,就分析完了。
下面对这个流程做大体分析。
首先,分析一下AT和AF之间传递数据使用的内存是在哪里分配的。
AT章节里面,我们分析AudioTrack::createTrack_l函数时,有这么一段代码:
sp<IAudioTrack> track = audioFlinger->createTrack(streamType, //调用AF接口来在AF里面创建Track实例 sampleRate, format == AUDIO_FORMAT_PCM_8_BIT ? AUDIO_FORMAT_PCM_16_BIT : format, mChannelMask, frameCount, &trackFlags, sharedBuffer, output, tid, &mSessionId, mName, mClientUid, &status); sp<IMemory> iMem = track->getCblk(); audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMem->pointer());AT章节里,我们交代过, cblk实际上就是指向了audio buffer.
我们是不是可以这么猜想:audioFlinger->createTrack中就完成了audio buffer的分配?
那我们看一下这个函数。
sp<IAudioTrack> AudioFlinger::createTrack(...) { Mutex::Autolock _l(mLock); PlaybackThread *thread = checkPlaybackThread_l(output);//通过前面讲过的获得的output,来获得playbackThread client = registerPid_l(pid);//根据AT所在的进程PID, 来为每个AT所在进程创建一个client track = thread->createTrack_l(client, streamType, sampleRate, format, channelMask, frameCount, sharedBuffer, lSessionId, flags, tid, clientUid, &lStatus); //在这个playbackThread里面创建Track trackHandle = new TrackHandle(track); //提供给AT的handle,是IAudioTrack形式的 }这个函数里面最重要的一步就是track = thread->createTrack_l, 为每个AT在AF里面找到对应的PlaybackThread, 然后在playbackThread里面创建了Track.
继续往里跟踪。
AudioFlinger::PlaybackThread::createTrack_l { track = new Track(this, client, streamType, sampleRate, format, channelMask, frameCount, sharedBuffer, sessionId, uid, *flags); //实例化一个Track }Track这个类,实际上继承了TrackBase这个类。
在TrackBase的构造函数AudioFlinger::ThreadBase::TrackBase::TrackBase里面,我们可以发现:
size_t size = sizeof(audio_track_cblk_t); size_t bufferSize = (sharedBuffer == 0 ? roundup(frameCount) : frameCount) * mFrameSize; if (sharedBuffer == 0) { size += bufferSize; } mCblkMemory = client->heap()->allocate(size);看到了吗?这里就是申请audio buffer的地方,并且申请的长度是 sizeof(audio_track_cblk_t)+bufferSize
这里实际上就是在需要的size基础上,多申请一块内存,用来存放 audio_track_cblk_t 这个头信息。
audio_track_cblk_t 是个啥呢?
audio_track_cblk_t的主要数据成员:
user -- AudioTrack当前的写位置的偏移
userBase -- AudioTrack写偏移的基准位置,结合user的值方可确定真实的FIFO地址指针
server -- AudioFlinger当前的读位置的偏移
serverBase -- AudioFlinger读偏移的基准位置,结合server的值方可确定真实的FIFO地址指针
frameCount -- FIFO的大小,以音频数据的帧为单位,16bit的音频每帧的大小是2字节
buffers -- 指向FIFO的起始地址
out -- 音频流的方向,对于AudioTrack,out=1,对于AudioRecord,out=0
audio_track_cblk_t的主要成员函数:
framesAvailable_l()和framesAvailable()用于获取FIFO中可写的空闲空间的大小,只是加锁和不加锁的区别。playback 的场景中,主要 用于AT来write
framesReady()用于获取FIFO中可读取的空间大小。playback场景中,主要用于AF来read
我们可以用下面这个图来解释这块内存的结构:
也就是说, TrackBase里面申请了一块内存,内存的前部是一个audio_track_cblk_t信息,用于指导读写位置的同步工作。内存的后部才是真正的缓冲区。
然后我们回到client->heap()->allocate(size)看一下具体是从哪里分配的内存
这里的client就是client = registerPid_l(pid)创建的。
其构造函数里面:
AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid) : RefBase(), mAudioFlinger(audioFlinger), mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")), mPid(pid), mTimedTrackCount(0) {}可以看到,这里申请了1M bytes的空间。
我们知道,AF中最多支持32个Track,
在AudioFlinger::PlaybackThread::createTrack_l函数里,
可以看到,不管AT来自什么进程空间,都会创建Track,
也就是说,这32个Track可以来自不同的进程空间的不同AT,也可以来自同一进程空间同一AT的,也可以来自同一进程空间的不同AT,
我们分析registerPid_l函数就会发现,每个进程空间都会只有一个client,一个client只会申请1M的空间,
也就是说,每个进程空间不管有多少AT ,都会共用这1M的空间。具体每个AT使用多少,由client->heap()->allocate(size)来实报实销。
而不同的进程空间之间,则是各自独立的1M空间,互不干扰。
继续往下跟踪:
MemoryDealer::MemoryDealer(size_t size, const char* name) : mHeap(new MemoryHeapBase(size, 0, name)), mAllocator(new SimpleBestFitAllocator(size)) { } MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) { int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size); }看到了吗?这1M的空间是使用了android的ashmem匿名共享内存机制来分配的。
这也是为什么AT和AF处于两个不同的进程空间,但是却能够共享传递数据了。
现在我们知道了AT和AF之间是用的共享内存了,那么具体运行起来的时候,是什么样子的呢?
我们从AT的write函数来分析
ssize_t AudioTrack::write(const void* buffer, size_t userSize) { while (userSize >= mFrameSize) { audioBuffer.frameCount = userSize / mFrameSize; status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever); //获取可用buffer memcpy(audioBuffer.i8, buffer, toWrite); //写入AF releaseBuffer(&audioBuffer); //释放 } return written; }对其简化后,可以知道AT通过obtainBuffer来获取可用空间。然后进行memcpy。最后释放这段空间。
这样音频数据就写入了AF中的共享内存了。
下面分析一下 obtainBuffer
status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) { uint32_t framesReq = audioBuffer->frameCount; size_t framesAvail = mProxy->framesAvailable(); if (framesAvail == 0) { while (framesAvail == 0) { if (cblk->user < cblk->loopEnd) { cblk->lock.unlock(); result = mAudioTrack->start(); cblk->lock.lock(); } } } if (framesReq > framesAvail) { framesReq = framesAvail; } uint32_t u = cblk->user; uint32_t bufferEnd = cblk->userBase + mFrameCount; if (framesReq > bufferEnd - u) { framesReq = bufferEnd - u; } audioBuffer->frameCount = framesReq; audioBuffer->size = framesReq * mFrameSizeAF; audioBuffer->raw = mProxy->buffer(u); active = mActive; return active ? status_t(NO_ERROR) : status_t(STOPPED); }可以看到,这个函数最重要的工作就是确定了framesReq这个变量的值
,以及通过buffer函数确定了真正的buffer的起始地址
首先,通过调用framesAvailable函数,获得了整个buffer里面,可以用来写入的空闲空间大小。
uint32_t audio_track_cblk_t::framesAvailable_l() { uint32_t u = this->user; uint32_t s = this->server; if (out) { uint32_t limit = (s < loopStart) ? s : loopStart; return limit + frameCount - u; // 实际上就是 frameCount - (u-s) } else { return frameCount + u - s; } }可以知道,u-s 实际上就是buffer中,还没有被读取完的数据,frameCount-(u-s)实际上就是两侧可以使用的空间
这样,obtainBuffer中,framesAvailable_l之后,framesReq就初步预存了可用空间的值,然后又比较了framesReq和bufferEnd-u的值。
那么,bufferEnd-u又是什么呢?
在代码中,我们可以看到,bufferEnd=userBase+mFrameCount
那么我们的疑问就成了userBase+mFrameCount-u了
mFrameCount没有什么神秘的,就是整个buffer的大小而已。
那么整个公式具体代表什么意思呢?
前面我们介绍了一下audio_track_cblk_t中的几个数据变量的含义,
重点就是 user,userBase,server,serverBase 这四个变量。
需要注意的是,这四个变量的值并不是读写位置相对于buffer的起点,物理上的offset值,而是一个经过映射的虚拟值。
随着读写的进行,user,userBase,server,serverBase这四个值在不断的累加,
超过了buffer的实际的size之后,四个值会在一个虚拟的空间里继续累加,从而形成了一个环的组织形式。
其中,userBase,serverBase的累加单元就是buffer的实际的size frameCount.
user,userBase的累加单元,就是实际的读写的数量。
这个映射的关系,可以从下面图中表示出来
假设某一时刻的映射关系如下,我们有一段数据准备写入。
看完这个图,是不是明白了?
总结一下,实际上,这段buffer是按照环的形式来读写的,随着读写的继续,user和server都是在不断向后移动。
由于数据写入的时候,采用了memcpy, 而memcpy只能向一个方向单向拷贝,不可能扭回头去再继续拷贝,所以这里每次memcpy的操作,只能是针对user 指向的位置向后的那段空间。
而后面空间的终点,就是这段内存物理上的重点。映射过来,就是bufferEnd了。
那么这段空间的可用大小,就是bufferEnd-user了。也就是上图中橙色区域。
所以前面讲的,比较framesReq和bufferEnd-u的值,实际上就是为了获取到memcpy可以执行的size的大小。
现在obtainBuffer中,我们知道了memcpy可以执行的size大小,那么可以执行的目的地址怎么确定呢?
这就是mProxy->buffer(u)起到的作用了。
void* audio_track_cblk_t::buffer(void *buffers, size_t frameSize, uint32_t offset) const { return (int8_t *)buffers + (offset - userBase) * frameSize; }代码中,buffers指向的是实际data buffer的指针,(offset-userBase)实际上就是(user-userBase),也就是user在实际data buffer中相对data buffer头部的偏移量,当然,这个偏移量是以帧为单位的。
再乘上framesize,就得出了byte偏移量了,这样,buffer函数就可以得到user在实际data buffer中的位置了。
memcpy的目的地址也就知道了。
这样,obtainBuffer函数执行完毕后,就可以知道要写入的buffer的地址和可写入数据的大小了。
前面我们提到,user,userBase,server,serverBase这四个值在不断的累加,那么具体的累加操作是在哪里执行的呢?
这就是stepUser和stepServer函数的作用了。(这两个函数在releaseBuffer函数里调用,也就是说内存复制完毕之后,就会被调用了)
uint32_t audio_track_cblk_t::stepUser(size_t stepCount, size_t frameCount, bool isOut) { uint32_t u = user; u += stepCount; if (isOut) { if (bufferTimeoutMs == MAX_STARTUP_TIMEOUT_MS-1) { bufferTimeoutMs = MAX_RUN_TIMEOUT_MS; } } else if (u > server) { u = server; } if (u >= frameCount) { if (u - frameCount >= userBase ) { userBase += frameCount; } } else if (u >= userBase + frameCount) { userBase += frameCount; } user = u; return u; }
bool audio_track_cblk_t::stepServer(size_t stepCount, size_t frameCount, bool isOut) { uint32_t s = server; bool flushed = (s == user); s += stepCount; if (isOut) { if (flushed) { s = user; } } if (s >= loopEnd) { s = loopStart; if (--loopCount == 0) { loopEnd = UINT_MAX; loopStart = UINT_MAX; } } if (s >= frameCount) { if (s - frameCount >= serverBase ) { serverBase += frameCount; } } else if (s >= serverBase + frameCount) { serverBase += frameCount; } server = s; return true; }从这两个函数中可以看到,对user,server累加了实际的读写数值,对userBase,serverBase累加了buffer的实际长度frameCount.
在stepUser的函数执行完毕后,整个buffer的映射关系如下:
这样,在下一次obtainBuffer操作之后,经过framesAvaliable函数计算出来的可用空间就是图中绿色区域。
发现没有?可用空间从buffer尾的橙色区域,转移到了buffer头的绿色区域,是不是实现了一个环的操作?
绿色和橙色之间的区域,就是AF尚未读取的数据区。
前面分析了AT的写操作。下面分析一下AF的读操作。
首先要解决的问题是,AF什么时候去读呢?
前面的AF章节中,我们介绍过playbackThread,没错,就是在AudioFlinger::PlaybackThread::threadLoop里面,循环的去读取的!
另外,我们重温一下obtainBuffer函数
status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) { if (framesAvail == 0) { while (framesAvail == 0) { if (cblk->user < cblk->loopEnd) { cblk->lock.unlock(); result = mAudioTrack->start(); cblk->lock.lock(); } } } ........ }看见了吗?当发现framesAvalil为0,即没有空间可以写的时候,说明AF那端停了,或者压根没启动,所以这里会循环等待,并且会触发mAudioTrack->start函数
经过binder调用,最后会调用到AF中的 AudioFlinger::PlaybackThread::Track::start, 这里实际上是把track添加到了playbackthread中,并且激活了track的状态。
然后AF就可以开始读了。
在obtainBuffer函数中,你会发现有很多lock,unlock的操作。这里主要是为了保护audio_track_cblk_t这个临界区中的变量。因为audio_track_cblk_t位于共享内存,可以被AF和AT两个进程访问,所以一定要加锁同步。
AF章节中,我们讲过,AudioFlinger::PlaybackThread::threadLoop进行audioMixer操作,
在audioMixer中,我们以process__genericNoResampling为例分析
void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts) { uint32_t enabledTracks = state->enabledTracks; uint32_t e0 = enabledTracks; while (e0) { const int i = 31 - __builtin_clz(e0); e0 &= ~(1<<i); track_t& t = state->tracks[i]; t.buffer.frameCount = state->frameCount; t.bufferProvider->getNextBuffer(&t.buffer, pts); //获取databuffer t.frameCount = t.buffer.frameCount; t.in = t.buffer.raw; if (t.in == NULL) enabledTracks &= ~(1<<i); } ...... e0 = enabledTracks; while (e0) { const int i = 31 - __builtin_clz(e0); e0 &= ~(1<<i); track_t& t = state->tracks[i]; t.bufferProvider->releaseBuffer(&t.buffer); //释放buffer,当中调用了stepServer函数 }可以看到实际上是getNextBuffer这个函数在从buffer中取数据
status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
AudioBufferProvider::Buffer* buffer, int64_t pts) { audio_track_cblk_t* cblk = this->cblk(); uint32_t framesReady; uint32_t framesReq = buffer->frameCount; framesReady = mServerProxy->framesReady(); if (CC_LIKELY(framesReady)) { uint32_t s = cblk->server; uint32_t bufferEnd = cblk->serverBase + mFrameCount; bufferEnd = (cblk->loopEnd < bufferEnd) ? cblk->loopEnd : bufferEnd; if (framesReq > framesReady) { framesReq = framesReady; } if (framesReq > bufferEnd - s) { framesReq = bufferEnd - s; } buffer->raw = getBuffer(s, framesReq); buffer->frameCount = framesReq; return NO_ERROR; } getNextBuffer_exit: buffer->raw = NULL; buffer->frameCount = 0; return NOT_ENOUGH_DATA; }这里实际上是和obtainBuffer类似的操作。只不过从写buffer,变成了读buffer,从framesAvailable变成了framesReady , 这里就不多讲了。
在buffer使用完毕之后,t.bufferProvider->releaseBuffer(&t.buffer)被调用,当中调用了stepServer函数,用来更新server,serverBase.
至此,AT与AF之间如何传递数据,以及数据是怎么共享的,两端如何同步的,就分析完了。
相关文章推荐
- 解决Android Studio Fetching Android SDK component information失败问题
- android audio分析
- android audio 音量设置分析
- android 三级菜单 BaseExpandableListAdapter
- Android Support V4, V7, V13的作用与用法
- Ubuntu的Android开发环境配置
- {Android} 测试Google Play In-App-Billing支付
- Android Studio快捷键指南(本文持续更新)
- Android Studio快捷键指南(本文持续更新)
- Android Studio快捷键指南(本文持续更新)
- Android Studio快捷键指南(本文持续更新)
- android xmpp
- Android通过JNI操作串口
- Android 性能测试_Monkey 实践【转】
- Android Monkey 测试策略【转】
- Android&java的成长之路之四(自定义字母索引)
- Android项目使用support v7时遇到的各种问题
- Android Widget学习笔记
- android 5.1预置apk
- Android 启动Activity的方式