Android audio环形缓冲队列

news2024/12/24 12:49:47

1、背景

在学习audio的过程中,看到了大神zyuanyun的博客,在博客的结尾,大神留下了这些问题:
在这里插入图片描述
但是大神没有出后续的博文来说明audio环形缓冲队列的具体实现,这勾起了我强烈的好奇心。经过一段时间的走读代码,同时阅读其他大佬的博文,把环形缓冲队列的内容整理出来。

2、AudioPolicyService、AudioFlinger及相关类

AudioPolicyService,简称APS,是负责音频策略的制定者:比如什么时候打开音频接口设备、某种Stream类型的音频对应什么设备等等;AudioFlinger,简称AF,负责音频策略的具体执行,比如:如何与音频设备通信,如何维护现有系统中的音频设备,以及多个音频流的混音如何处理等。
环形缓冲队列大致可以以下面这幅图来描述其流程:
在这里插入图片描述

3、Track的创建

AudioTrack的创建经过漫长的调用链,最终是/frameworks/av/services/audioflinger/Tracks.cpp里完成创建的。

// TrackBase constructor must be called with AudioFlinger::mLock held
AudioFlinger::ThreadBase::TrackBase::TrackBase()

{
	//计算最小帧大小
    size_t minBufferSize = buffer == NULL ? roundup(frameCount) : frameCount;
	……
    minBufferSize *= mFrameSize;

    size_t size = sizeof(audio_track_cblk_t);
    if (buffer == NULL && alloc == ALLOC_CBLK) {
        // check overflow when computing allocation size for streaming tracks.
        if (size > SIZE_MAX - bufferSize) {
            android_errorWriteLog(0x534e4554, "34749571");
            return;
        }
        size += bufferSize;
    }

    if (client != 0) {
    	//为客户端分配内存
        mCblkMemory = client->heap()->allocate(size);
		……
    } else {
        mCblk = (audio_track_cblk_t *) malloc(size);
		……
    }

    // construct the shared structure in-place.
    if (mCblk != NULL) {
    	// 这是 C++ 的 placement new(定位创建对象)语法:new(@BUFFER) @CLASS();
        // 可以在特定内存位置上构造一个对象
        // 这里,在匿名共享内存首地址上构造了一个 audio_track_cblk_t 对象
        // 这样 AudioTrack 与 AudioFlinger 都能访问这个 audio_track_cblk_t 对象了
        new(mCblk) audio_track_cblk_t();
        switch (alloc) {
		……
        case ALLOC_CBLK:
            // clear all buffers
            if (buffer == NULL) {
            	// 数据 FIFO 的首地址紧靠控制块(audio_track_cblk_t)之后
                //   |                                                         |
                //   | -------------------> mCblkMemory <--------------------- |
                //   |                                                         |
                //   +--------------------+------------------------------------+
                //   | audio_track_cblk_t |             Buffer                 |
                //   +--------------------+------------------------------------+
                //   ^                    ^
                //   |                    |
                //   mCblk               mBuffer
                //这里mCblk被强制转型成占用内存1字节的char类型,这个"1"在后面会用到
                mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                memset(mBuffer, 0, bufferSize);
            } else {
            	// 数据传输模式为 MODE_STATIC/TRANSFER_SHARED 时,直接指向 sharedBuffer
                // sharedBuffer 是应用进程分配的匿名共享内存,应用进程已经一次性把数据
                // 写到 sharedBuffer 来了,AudioFlinger 可以直接从这里读取
                //   +--------------------+    +-----------------------------------+
                //   | audio_track_cblk_t |    |            sharedBuffer           |
                //   +--------------------+    +-----------------------------------+
                //   ^                         ^
                //   |                         |
                //   mCblk                    mBuffer
                mBuffer = buffer;
            }
            break;
			……
        }
		……
    }
}

4、生产者向共享内存写入数据

4.1

4.2 向共享内存写入数据

//framework/av/media/libaudioclient/AudioTrack.cpp
ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
	……

    size_t written = 0;
    Buffer audioBuffer;

    while (userSize >= mFrameSize) {
    	// 单帧数据量 frameSize = channelCount * bytesPerSample
        // 对于双声道,16位采样的音频数据来说,frameSize = 2 * 2 = 4(bytes)
        // 用户传入的数据帧数 frameCount = userSize / frameSize
        audioBuffer.frameCount = userSize / mFrameSize;

		// obtainBuffer() 从 FIFO 上得到一块可用区间
        status_t err = obtainBuffer(&audioBuffer,
                blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
		……
        size_t toWrite = audioBuffer.size;
        memcpy(audioBuffer.i8, buffer, toWrite);
        buffer = ((const char *) buffer) + toWrite;
        userSize -= toWrite;
        written += toWrite;

        releaseBuffer(&audioBuffer);
    }
	……
    return written;
}

status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, const struct timespec *requested,
        struct timespec *elapsed, size_t *nonContig)
{
    // previous and new IAudioTrack sequence numbers are used to detect track re-creation
    uint32_t oldSequence = 0;
    uint32_t newSequence;

    Proxy::Buffer buffer;
    status_t status = NO_ERROR;

    static const int32_t kMaxTries = 5;
    int32_t tryCounter = kMaxTries;

    do {
        // obtainBuffer() is called with mutex unlocked, so keep extra references to these fields to
        // keep them from going away if another thread re-creates the track during obtainBuffer()
        sp<AudioTrackClientProxy> proxy;
        sp<IMemory> iMem;

        {   // start of lock scope
            AutoMutex lock(mLock);
			……
            // Keep the extra references
            proxy = mProxy;
            iMem = mCblkMemory;
			……
        }   // end of lock scope

        buffer.mFrameCount = audioBuffer->frameCount;
        // FIXME starts the requested timeout and elapsed over from scratch
        status = proxy->obtainBuffer(&buffer, requested, elapsed);
    } while (((status == DEAD_OBJECT) || (status == NOT_ENOUGH_DATA)) && (tryCounter-- > 0));

    audioBuffer->frameCount = buffer.mFrameCount;
    audioBuffer->size = buffer.mFrameCount * mFrameSize;
    audioBuffer->raw = buffer.mRaw;
    if (nonContig != NULL) {
        *nonContig = buffer.mNonContig;
    }
    return status;
}
//framework/av/media/libaudioclient/AudioTrackShared.cpp
__attribute__((no_sanitize("integer")))
status_t ClientProxy::obtainBuffer(Buffer* buffer, const struct timespec *requested,
        struct timespec *elapsed)
{
	……
    struct timespec before;
    bool beforeIsValid = false;
    audio_track_cblk_t* cblk = mCblk;
    bool ignoreInitialPendingInterrupt = true;
    
    for (;;) {
        int32_t flags = android_atomic_and(~CBLK_INTERRUPT, &cblk->mFlags);
		……
        int32_t front;
        int32_t rear;
        if (mIsOut) {
            front = android_atomic_acquire_load(&cblk->u.mStreaming.mFront);
            rear = cblk->u.mStreaming.mRear;
        } else {
            // On the other hand, this barrier is required.
            rear = android_atomic_acquire_load(&cblk->u.mStreaming.mRear);
            front = cblk->u.mStreaming.mFront;
        }
        // write to rear, read from front
        ssize_t filled = audio_utils::safe_sub_overflow(rear, front);
		……
        ssize_t adjustableSize = (ssize_t) getBufferSizeInFrames();
        ssize_t avail =  (mIsOut) ? adjustableSize - filled : filled;
        if (avail < 0) {
            avail = 0;
        } else if (avail > 0) {
            // 'avail' may be non-contiguous, so return only the first contiguous chunk
            size_t part1;
            if (mIsOut) {
                rear &= mFrameCountP2 - 1;
                part1 = mFrameCountP2 - rear;
            } else {
                front &= mFrameCountP2 - 1;
                part1 = mFrameCountP2 - front;
            }
            if (part1 > (size_t)avail) {
                part1 = avail;
            }
            if (part1 > buffer->mFrameCount) {
                part1 = buffer->mFrameCount;
            }
            buffer->mFrameCount = part1;
            buffer->mRaw = part1 > 0 ?
                    &((char *) mBuffers)[(mIsOut ? rear : front) * mFrameSize] : NULL;
            buffer->mNonContig = avail - part1;
            mUnreleased = part1;
            status = NO_ERROR;
            break;
        }
        struct timespec remaining;
        const struct timespec *ts;
		……
        int32_t old = android_atomic_and(~CBLK_FUTEX_WAKE, &cblk->mFutex);
        if (!(old & CBLK_FUTEX_WAKE)) {
			……
            errno = 0;
            (void) syscall(__NR_futex, &cblk->mFutex,
                    mClientInServer ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, old & ~CBLK_FUTEX_WAKE, ts);
			……
        }
    }

end:
	……
    return status;
}

void ClientProxy::releaseBuffer(Buffer* buffer)
{
    size_t stepCount = buffer->mFrameCount;
	……
    mUnreleased -= stepCount;
    audio_track_cblk_t* cblk = mCblk;
    // Both of these barriers are required
    if (mIsOut) {
        int32_t rear = cblk->u.mStreaming.mRear;
        android_atomic_release_store(stepCount + rear, &cblk->u.mStreaming.mRear);
    } else {
        int32_t front = cblk->u.mStreaming.mFront;
        android_atomic_release_store(stepCount + front, &cblk->u.mStreaming.mFront);
    }
}

4、消费者从共享内存读数据

找到当前活动的track需要经过很长的准备及调用链,可以参考这篇博客。

4.1 PlaybackThread的混音

代码路径:frameworks/av/services/audioflinger/Threads.cpp

void AudioFlinger::MixerThread::threadLoop_mix()
{
    // mix buffers...
    mAudioMixer->process();
    mCurrentWriteLength = mSinkBufferSize;
	……
}

process()方法之后进入track的混音流程,代码位于frameworks/av/media/libaudioprocessing/AudioMixer.cpp。来看process()的定义:

	using process_hook_t = void(AudioMixer::*)();
	process_hook_t mHook = &AudioMixer::process__nop; 

	void invalidate() {
        mHook = &AudioMixer::process__validate;
    }

    void process__validate();
    void process__nop();
    void process__genericNoResampling();
    void process__genericResampling();
    void process__oneTrack16BitsStereoNoResampling();

    template <int MIXTYPE, typename TO, typename TI, typename TA>
    void process__noResampleOneTrack();

hook是一个函数指针,根据不同场景会分别指向不同函数实现。详细可以参考这篇博客,以及这篇博客。以process__nop方法为例:

void AudioMixer::process__nop()
{
    for (const auto &pair : mGroups) {
        const auto &group = pair.second;

        const std::shared_ptr<Track> &t = mTracks[group[0]];
        memset(t->mainBuffer, 0,
                mFrameCount * audio_bytes_per_frame(
                        t->mMixerChannelCount + t->mMixerHapticChannelCount, t->mMixerFormat));

        // now consume data
        for (const int name : group) {
            const std::shared_ptr<Track> &t = mTracks[name];
            size_t outFrames = mFrameCount;
            while (outFrames) {
                t->buffer.frameCount = outFrames;
                t->bufferProvider->getNextBuffer(&t->buffer);
                if (t->buffer.raw == NULL) break;
                outFrames -= t->buffer.frameCount;
                t->bufferProvider->releaseBuffer(&t->buffer);
            }
        }
    }
}

frameworks/av/services/audioflinger/Tracks.cpp

status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
{
    ServerProxy::Buffer buf;
    size_t desiredFrames = buffer->frameCount;
    buf.mFrameCount = desiredFrames;
    status_t status = mServerProxy->obtainBuffer(&buf);
    buffer->frameCount = buf.mFrameCount;
    buffer->raw = buf.mRaw;
    if (buf.mFrameCount == 0 && !isStopping() && !isStopped() && !isPaused()) {
        mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);
    } else {
        mAudioTrackServerProxy->tallyUnderrunFrames(0);
    }
    return status;
}

void AudioFlinger::PlaybackThread::Track::releaseBuffer(AudioBufferProvider::Buffer* buffer)
{
    interceptBuffer(*buffer);
    TrackBase::releaseBuffer(buffer);
}

/frameworks/av/media/libaudioclient/AudioTrackShared.cpp

status_t ServerProxy::obtainBuffer(Buffer* buffer, bool ackFlush)
{
    {
    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;
    }
	……
    // '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;
	……
}

int32_t AudioTrackServerProxy::getRear() const
{
    const int32_t stop = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop);
    const int32_t rear = android_atomic_acquire_load(&mCblk->u.mStreaming.mRear);
    const int32_t stopLast = mStopLast.load(std::memory_order_acquire);
	……
    return rear;
}

5、总结

经过一段时间的代码走读,能够回答文章开头提出的部分问题,但是诸如“读写指针线程安全”、“Futex同步机制”等问题现阶段还回答不上来,以后有机会再深入研究下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1289246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【日常总结】树莓派导致的公司无法上网 - 广播风暴

一、场景 二、问题 三、分析原因 四、解决方案 方案一&#xff1a;更换树莓派后ping路由器恢复正常 方案二&#xff1a;配置交换机 交换机广播风暴配置 也可以通过PPS来限速 查看配置 一、场景 宽带&#xff1a;公司3条500M光纤-联通 路由器&#xff1a;锐捷 在线用户…

Memory-augmented Deep Autoencoder for Unsupervised Anomaly Detection 论文阅读

Memorizing Normality to Detect Anomaly: Memory-augmented Deep Autoencoder for Unsupervised Anomaly Detection 摘要1.介绍2.相关工作异常检测Memory networks 3. Memory-augmented Autoencoder3.1概述3.2. Encoder and Decoder3.3. Memory Module with Attention-based S…

el-table全部选择和全部取消

el-table实现全部选择和全部取消 其实非常简单&#xff0c;el-table自带的都有方法toggleAllSelection()和clearSelection() 具体代码如下&#xff1a; <el-button typesuccess clickcheckAll sizesmall>全选</el-button> <el-button typesuccess clickcancel…

【往届见刊检索速度hin OK】 第五届计算机工程与应用国际学术会议 (ICCEA 2024)

第五届计算机工程与应用国际学术会议 (ICCEA 2024) 2024 5th International Conference on Computer Engineering and Application 2024年4月12-14日 中国-杭州 计算机工程与应用在人工智能、大数据、云计算、物联网、网络安全等领域发挥着重要作用&#xff0c;随着科技日…

docker 安装mysql 主从复制

一、搭建主服务器的mysql 1.1 先新建文件夹 mkdir -p /data/dockerData/mysql-master/conf 1.2 进入/data/dockerData/mysql-master/conf目录下新建my.config, [mysqld] ## 设置server_id&#xff0c;同一局域网中需要唯一 server_id101 ## 指定不需要同步的数据库名称 bin…

Redis7--基础篇7(哨兵sentinel)

1. 关于哨兵的介绍 1、监控redis运行状态&#xff0c;包括master和slave&#xff08;主从监控&#xff09; 2、哨兵可以将故障转移的结果发送给客户端&#xff08;消息通知&#xff09; 3、当master down机&#xff0c;能自动将slave切换成新master&#xff08;故障转移&#…

学生档案管理系统设计

摘要 随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要的作用。作为计算机应用的一部分,使用计算机对学生档案信息进行管理,具有着手工管理所无法比拟的优点.例如:检索迅速、查找方便、可靠性高、存储量…

20、pytest中的参数化

官方实例 # content of test_expectation.pyimport pytestpytest.mark.parametrize("test_input, expected",[("35",8),("24",6),("6*9",42)]) def test_eval(test_input, expected):assert eval(test_input) expected# content of …

Vmware虚拟机简介和安装

作者&#xff1a;余小小 常见的虚拟机 vmwarevirtualBox Vmware 运行在win系统上centos运行在Vm上 先安装vm&#xff0c;在安装centos系统 Vmware介绍 不用分区或者重开机&#xff0c;就可以在同一台pc上使用多种操作系统完全隔离&#xff0c;且保护不同的操作系统环境和文…

掌控安全 暖冬杯 CTF Writeup By AheadSec

本来结束时发到了学校AheadSec的群里面了的&#xff0c;觉得这比赛没啥好外发WP的&#xff0c;但是有些师傅来问了&#xff0c;所以还是发一下吧。 文章目录 Web签到&#xff1a;又一个计算题计算器PHP反序列化又一个PHP反序列化 Misc这是邹节伦的桌面背景图什么鬼&#xff1f;…

【分享】PDF文件不能编辑的3个原因

PDF文件具有很好的兼容性&#xff0c;可靠性&#xff0c;安全性&#xff0c;是很多人办公常用的电子文档格式。但有时候想要编辑PDF时&#xff0c;却发现不能编辑&#xff0c;是什么原因呢&#xff1f;下面小编来分享一下常见的3个原因。 原因1&#xff1a; PDF文件是扫描件&a…

035.Python面向对象_三大特性_封装、继承、多态

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

Android studio中如何生成jar包?

文章目录 需求背景目录结构gradle结构makeJar的语法解析 执行makeJar 任务拿到jar包 需求背景 别部门做C语言开发的同学开发了一个库&#xff0c;需要给我们Android端去调用。 我们拿到源码&#xff0c;首先需要做的是通过CMake去把C源码编译链接成动态库。 当然静态库也行&am…

NDIS协议驱动开发指南

文章目录 NDIS协议驱动开发指南1. 技术概览2. NDIS协议驱动2.1 BindAdapterHandlerEx2.2 SendNetBufferListsCompleteHandler2.3 ReceiveNetBufferListsHandler2.4 ProtocolNetPnpEvent 3. NET_BUFFER_LIST4. ndisprot实例5. 总结 NDIS协议驱动开发指南 我们知道&#xff0c;在…

Could not resolve all files for configuration ‘:app:debugCompileClasspath‘.

修改前 修改后 maven {url https://developer.huawei.com/repo/}

3DSEE:AI驱动的3D模型语义搜索引擎

3DSEE &#xff08;3D SEmantic Engine&#xff09;是基于 AI 技术的 3D 模型语义搜索引擎&#xff0c;可以自动提取 3D 模型内涵的语义信息并存储入库&#xff0c;以帮助用户使用自然语言或关键字高效地检索 3D 模型。3DSEE 提供完善的二次开发 API&#xff0c;无论使用Java、…

键盘打字盲打练习系列之反复练习——3

一.欢迎来到我的酒馆 盲打&#xff0c;反复练习&#xff01; 目录 一.欢迎来到我的酒馆二.数字符号键位指法 二.数字符号键位指法 前面的一个章节重点介绍了主键盘区字母键位的指法&#xff1a;基准键位指法、QWERTY字母键位指法、ZXCVBNM字母键位指法。大概练习一天的时间&…

【力扣】240.搜索二维矩阵

题目意思是从该矩阵之中查找出是否有和target一样的值&#xff0c;若有则返回true&#xff0c;无则返回false。这里我用的是java。总共有三种方法&#xff0c;分别是暴力解题法&#xff08;能过&#xff09;&#xff0c;二分查找法&#xff08;就是将二维数组拆分成m个二维数组…

2.8寸 ILI9341 TFTLCD 学习移植到STM32F103C8T6

2.8寸 ILI9341 TFTLCD 学习移植到STM32F103C8T6 文章目录 2.8寸 ILI9341 TFTLCD 学习移植到STM32F103C8T6前言第1章 LCD简介1.1 LCD硬件接口介绍 第2章 LCD指令介绍第3章 LCD 8080驱动方式3.1 8080写时序3.2 8080读时序 第4章 LCD 驱动代码部分4.1 修改代码部分4.2 代码工程下载…

【力扣】54. 螺旋矩阵

题解&#xff1a; 这里当然就不能只是通过双循环来解题了&#xff0c;因为会烦死。这道题的关键点在于左右边界的确定&#xff0c;参考官方解题法在这里写出我的解题思路。 首先看图&#xff0c;螺旋且顺时针&#xff0c;所以我们可以先遍历从左至右的&#xff0c;上边界加一…