Android 音频录制核心:ServerProxy::obtainBuffer揭秘

上篇(Android音频录制核心:RecordThread::threadLoop解析)讲到在 Android AudioFlinger 的录音通路(RecordThread)中,存在两个层级的环形缓冲区,下面对第二个环形缓冲区(Track应用读取的缓冲区)写入数据的核心代码进行分析;关键在于getNextBuffer是如何拿到activeTrack->sinkBuffer() 。

代码追踪:

frameworks/av/services/audioflinger/Threads.cpp
    -->bool RecordThread::threadLoop()
        -->status_t status = activeTrack->getNextBuffer(&activeTrack->sinkBuffer());

frameworks/av/services/audioflinger/Tracks.cpp
    -->status_t RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
        -->mServerProxy->obtainBuffer(&buf);

frameworks/av/media/libaudioclient/AudioTrackShared.cpp
    -->status_t ServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush)

追到在ServerProxy::obtainBuffer()中获取到buffer指针;ServerProxy 是 Android AudioFlinger 共享内存缓冲区(环形buffer)的服务端代理,负责音频服务端(AudioFlinger/RecordThread 等)与客户端(AudioTrack/AudioRecord)之间的数据交互。
obtainBuffer(Buffer_ buffer, bool ackFlush)用于服务端获取一段可用的 buffer 区域(写入或读取),并返回其内存指针与可用帧数(frameCount)。写入指的是录音,读取则是播放。
这是“生产者-消费者”模式的核心函数之一,保证多线程并发安全与高效。

下面对ServerProxy::obtainBuffer 函数是如何得到buffer,进行详细分步解析,重点说明其数据结构、流程、同步机制、以及每个关键变量和分支的作用,Record和Playback都会调用到这个函数,所以会一并讲解,分析Playback时也可以阅读此文。
源码:

status_t ServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush)
{
    LOG_ALWAYS_FATAL_IF(buffer == NULL || buffer->mFrameCount == 0,
            "%s: null or zero frame buffer, buffer:%p", __func__, buffer);
    if (mIsShutdown) {
        goto no_init;
    }
    {
    audio_track_cblk_t* cblk = mCblk;
    // compute number of frames available to write (AudioTrack) or read (AudioRecord),
    // or use previous cached value from framesReady(), with added barrier if it omits.
    int32_t front;
    int32_t rear;
    // See notes on barriers at ClientProxy::obtainBuffer()
    if (mIsOut) {
        flushBufferIfNeeded(); // might modify mFront
        rear = getRear();
        front = cblk->u.mStreaming.mFront;
    } else {
        front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
        rear = cblk->u.mStreaming.mRear;
    }
    ssize_t filled = audio_utils::safe_sub_overflow(rear, front);
    // pipe should not already be overfull
    if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
        ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
                filled, mFrameCount);
        mIsShutdown = true;
    }
    if (mIsShutdown) {
        goto no_init;
    }
    // don't allow filling pipe beyond the nominal size
    size_t availToServer;
    if (mIsOut) {
        availToServer = filled;
        mAvailToClient = mFrameCount - filled;
    } else {
        availToServer = mFrameCount - filled;
        mAvailToClient = filled;
    }
    // 'availToServer' may be non-contiguous, so return only the first contiguous chunk
    size_t part1;
    if (mIsOut) {
        front &= mFrameCountP2 - 1;
        part1 = mFrameCountP2 - front;
    } else {
        rear &= mFrameCountP2 - 1;
        part1 = mFrameCountP2 - rear;
    }
    if (part1 > availToServer) {
        part1 = availToServer;
    }
    size_t ask = buffer->mFrameCount;
    if (part1 > ask) {
        part1 = ask;
    }
    // is assignment redundant in some cases?
    buffer->mFrameCount = part1;
    buffer->mRaw = part1 > 0 ?
            &((char *) mBuffers)[(mIsOut ? front : rear) * mFrameSize] : NULL;
    buffer->mNonContig = availToServer - part1;
    // After flush(), allow releaseBuffer() on a previously obtained buffer;
    // see "Acknowledge any pending flush()" in audioflinger/Tracks.cpp.
    if (!ackFlush) {
        mUnreleased = part1;
    }
    return part1 > 0 ? NO_ERROR : WOULD_BLOCK;
    }
no_init:
    buffer->mFrameCount = 0;
    buffer->mRaw = NULL;
    buffer->mNonContig = 0;
    mUnreleased = 0;
    return NO_INIT;
}


1. 参数说明
Buffer buffer
    要获取的buffer描述结构体(in/out),输入时mFrameCount为你申请的帧数,输出时mRaw指向实际可用内存,mFrameCount返回实际可用帧数。


bool ackFlush
    是否在flush场景下特殊处理(通常为false,特殊情况下为true)。

 2. 函数分步详解

A. 入参校验

LOG_ALWAYS_FATAL_IF(buffer == NULL || buffer->mFrameCount == 0,
        "%s: null or zero frame buffer, buffer:%p", __func__, buffer);

buffer指针非空且申请帧数>0,否则crash。


B. 是否已shutdown

if (mIsShutdown) {
    goto no_init;
}

如果ServerProxy已被判定为异常(如shm损坏等),直接返回NO_INIT。

C. 读写指针与可用帧数计算

audio_track_cblk_t* cblk = mCblk;
int32_t front;
int32_t rear;

cblk是共享内存控制块,管理buffer的front(读指针)和rear(写指针),这里拿到线程的全局mCblk。

1. 方向判定(mIsOut)

mIsOut==true:AudioTrack(播放流),服务端写入,客户端读取;
mIsOut==false:AudioRecord(录音流),服务端读取,客户端写入。

2. 获取front和rear指针


mIsOut==true(播放)
先调用flushBufferIfNeeded()处理flush场景;
        rear = getRear()(服务端写指针), 

        front = cblk->u.mStreaming.mFront(客户端读指针


mIsOut==false(录音)
        front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront)(服务端读指针);
         rear = cblk->u.mStreaming.mRear(客户端写指针)。

3. 计算 filled

ssize_t filled = audio_utils::safe_sub_overflow(rear, front);

表示buffer中已有的数据量(帧数)。
播放流:filled=rear-front,表示还有多少数据客户端未消费;
录音流:filled=rear-front,表示客户端写入而服务端还未读取的数据量。

4. 检查buffer完整性

if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
    ALOGE(...);
    mIsShutdown = true;
}
if (mIsShutdown) {
    goto no_init;
}


filled必须在[0, mFrameCount]之间,否则buffer指针错乱,判定为损坏、shutdown。

D. 计算服务端和客户端各自可用空间

size_t availToServer;
if (mIsOut) {
    availToServer = filled;
    mAvailToClient = mFrameCount - filled;
} else {
    availToServer = mFrameCount - filled;
    mAvailToClient = filled;
}


mIsOut==true(播放流):服务端可用=filled,客户端可用=mFrameCount-filled
mIsOut==false(录音流):服务端可用=mFrameCount-filled,客户端可用=filled

解释
“服务端可用”:服务端本次最多能写/读多少帧

“客户端可用”:客户端还能写/还能读多少帧

E. 计算首段(part1)可用帧数

size_t part1;
if (mIsOut) {
    front &= mFrameCountP2 - 1;
    part1 = mFrameCountP2 - front;
} else {
    rear &= mFrameCountP2 - 1;
    part1 = mFrameCountP2 - rear;
}
if (part1 > availToServer) {
    part1 = availToServer;
}
size_t ask = buffer->mFrameCount;
if (part1 > ask) {
    part1 = ask;
}

环形缓冲区可能“环绕”,首段最大容量为 mFrameCountP2-front/rear 到buffer尾部的距离。
取 min(实际可用, 申请帧数) 作为本次可操作的最大帧数。
part1 表示本次能连续操作(不跨环绕)的最大帧数。

F. 填充输出buffer结构体

buffer->mFrameCount = part1;
buffer->mRaw = part1 > 0 ?
        &((char *) mBuffers)[(mIsOut ? front : rear) * mFrameSize] : NULL;
buffer->mNonContig = availToServer - part1;

mFrameCount:本次可操作的帧数
mRaw:实际内存指针
mNonContig:如果数据分两段(环绕),第二段的帧数


G. flush处理与mUnreleased设置

if (!ackFlush) {
    mUnreleased = part1;
}


H. 返回值处理

return part1 > 0 ? NO_ERROR : WOULD_BLOCK;

有数据可操作返回NO_ERROR,否则WOULD_BLOCK(需要等待)。

I. 错误/未初始化

no_init:
    buffer->mFrameCount = 0;
    buffer->mRaw = NULL;
    buffer->mNonContig = 0;
    mUnreleased = 0;
    return NO_INIT;

如果初始化失败或已shutdown,清空输出buffer并返回NO_INIT。


4. 整体流程总结
1. 校验参数、Shutdown检测
2. 读取共享内存区front/rear指针
3. 计算缓冲区内已用/可用空间
4. 判断缓冲区是否损坏
5. 计算本次可操作的最大帧数和内存指针
6. 输出buffer结构,返回给调用者
7. 错误处理/特殊场景处理

5. 环形缓冲区“首段”原理图
假设buffer容量8,front=6,rear=2(录音流):

[0][1][2][3][4][5][6][7]
       ^           ^
      rear        front


此时part1 = 8 - rear = 6
如果availToServer=3,则part1=3


7. 实际用途
对于AudioRecord(录音流),服务端(AudioFlinger)调用 obtainBuffer 获取可读的buffer区域,从mRaw读取数据;
对于AudioTrack(播放流),服务端获取可写buffer区域,把下发给HAL的数据写入mRaw。

8. 小结与重点
obtainBuffer是环形缓冲区多线程安全读写的关键函数。
它保证了读写指针正确、环形结构下的连续/非连续段处理、并发时内存可见性。也是音频系统“为什么有两个环形buffer的重要机制基础。

下一篇重点讲解上层应用是如何一步一步,最后拿到录到的音频数据的。

《深入理解Android:卷2》是“深入理解Android”系列的第2本,第1本书上市后获得广大读者高度评价,在Android开发者社群内口口相传。《深入理解Android:卷2》不仅继承了第1本书的优点并改正了其在细微处存在的一些不足,而且还在写作的总体思想上进行了创新,更强调从系统设计者的角度去分析Android系统中各个模块内部的实现原理和工作机制。从具体内容上讲,重点是Android Framework的Java层,对Java层涉及的核心模块和服务进行了深入而细致的分析。通过《深入理解Android:卷2》,读者不仅能对Android系统本身有更深入的理解,而且还能掌握分析大型复杂源代码的能力。《深入理解Android:卷2》共8章:第1章介绍了阅读本书所需要做的准备工作,包括Android 4.0源码的下载和编译、Eclipse环境的搭建,以及Android系统进程(system_process)的调试等;第2章对Java Binder和MessageQueue的实现进行了深入分析;第3章仔细剖析了SystemServer的工作原理,这些服务包括EntropyService、DropboxManagerService、DiskStatsService、DeviceStorageMonitorService、SamplingProfilerService和ClipboardService;第4章对系统中负责Package信息查询和APK安装、卸载、更新等工作的服务PackageManagerService进行了详细分析;第5章则对Android系统中负责电源管理的核心服务 PowerManagerService的原理进行了一番深入的分析;第6章以ActivityManagerService为分析重点,它的启动、Activity的创建和启动、BroadcastReceiver的工作原理、Android中的进程管理等内容展开了较为深入的研究;第7章对ContentProvider的创建和启动、SQLite、Cursor query和close的实现等进行了深入分析;第8章以ContentService和AccountManagerService为分析对象,介绍了数据更新通知机制的实现,以及账户管理和数据同步等相关知识。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值