Android audio环形缓冲队列

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 buffersif (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                    mBuffermBuffer = 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 / frameSizeaudioBuffer.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-creationuint32_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 scopeAutoMutex lock(mLock);……// Keep the extra referencesproxy = mProxy;iMem = mCblkMemory;……}   // end of lock scopebuffer.mFrameCount = audioBuffer->frameCount;// FIXME starts the requested timeout and elapsed over from scratchstatus = 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 frontssize_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 chunksize_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 requiredif (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 datafor (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 mFrontrear = 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 chunksize_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.mzph.cn/news/210300.shtml

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

相关文章

朴素贝叶斯 朴素贝叶斯原理

朴素贝叶斯 朴素贝叶斯原理 判别模型和生成模型 监督学习方法又分生成方法 (Generative approach) 和判别方法 (Discriminative approach)所学到的模型分别称为生成模型 (Generative Model) 和判别模型 (Discriminative Model)。 朴素贝叶斯原理 朴素贝叶斯法是典型的生成学习…

深度学习之全面了解网络架构

在这篇文章中&#xff0c;我们将和大家探讨“深度学习中的网络架构”这个主题&#xff0c;解释相关背景知识&#xff0c;并就一些问题进行解答。 我选择的问题反映的是常见用法&#xff0c;而不是学术用例。我将概括介绍该主题&#xff0c;然后探讨以下四个问题&#xff1a; …

Java的I/O演进之路

文章目录 通信技术整体解决的问题1 I/O 模型基本说明2 I/O模型Java BIOJava NIOJava AIO 3 BIO、NIO、AIO 适用场景分析 通信技术整体解决的问题 局域网内的通信要求。多系统间的底层消息传递机制。高并发下&#xff0c;大数据量的通信场景需要。游戏行业。无论是手游服务端&a…

【出现模块node_modules里面包找不到】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 一、出现的问题二、解决办法三、其它可供参考 一、出现的问题 在本地运行 npm run docs:dev之后&#xff0c;出现 Error [ERR_MODULE_NOT_FOUND]: Cannot find package Z:\Blog\docs\node_modules\htmlparser2\ imported from Z:\Blo…

入门Redis学习总结

记录之前刚学习Redis 的笔记&#xff0c; 主要包括Redis的基本数据结构、Redis 发布订阅机制、Redis 事务、Redis 服务器相关及采用Spring Boot 集成Redis 实现增删改查基本功能 一&#xff1a;常用命令及数据结构 1.Redis 键(key) # 设置key和value 127.0.0.1:6379> set …

解释AI决策,这10个强大的 Python 库记得收藏!

本文整理了10个常用于可解释AI的Python库&#xff0c;方便我们更好的理解AI模型的决策。 什么是XAI&#xff1f; XAI&#xff08;Explainable AI&#xff09;的目标是为模型的行为和决策提供合理的解释&#xff0c;这有助于增加信任、提供问责制和模型决策的透明度。XAI 不仅…

《深入浅出进阶篇》洛谷P3197 越狱——集合

洛谷P3197 越狱 题目大意&#xff1a; 监狱有 n 个房间&#xff0c;每个房间关押一个犯人&#xff0c;有 m 种宗教&#xff0c;每个犯人会信仰其中一种。如果相邻房间的犯人的宗教相同&#xff0c;就可能发生越狱&#xff0c;求有多少种状态可能发生越狱。 答案对100,003 取模。…

Temu卖家如何获取流量?Temu新手卖家流量来源哪里?——站斧浏览器

流量对于每个平台来说都是很重要的&#xff0c;那么Temu卖家如何获取流量&#xff1f;流量来源哪里&#xff1f; Temu卖家如何获取流量&#xff1f; 1、优化产品标题和描述&#xff1a;在Temu平台上&#xff0c;买家通常通过搜索关键词来寻找他们感兴趣的产品。因此&#xff…

【数电笔记】58-同步D触发器

目录 说明&#xff1a; 1. 电路组成 2. 逻辑功能 3. 特性表、特性方程 4. 状态转移图 例题 5. 同步D触发器的特点 6. 集成同步D触发器&#xff1a;74LS375 74LS375内部原理 说明&#xff1a; 笔记配套视频来源&#xff1a;B站本系列笔记并未记录所有章节&#xff0c;…

Web 开发的 20 个实用网站

Web 开发的 20 个实用网站 作为一名前端开发工程师&#xff0c;我们一定使用过很多工具来提高自己的工作效率。它们可以是网站、文档或 JavaScript 库。 本文将分享30个有趣的网站。 JavaScript正则表达式可视化工具 https://jex.im/regulex/#!flags&re%5E(a%7Cb)*%3F%…

★102. 二叉树的层序遍历

102. 二叉树的层序遍历 很巧妙的&#xff0c;又学习了一种层次遍历的方法&#xff0c;就是说根据当前的队列的长度去遍历&#xff0c;遍历的当前队列的长度就是该层次的节点个数。 /*** Definition for a binary tree node.* public class TreeNode {* int val;* Tr…

AIGC专题报告:AIGC助力大规模对象存储服务OSS的能效提升

今天分享的AIGC系列深度研究报告&#xff1a;《AIGC专题报告&#xff1a;AIGC助力大规模对象存储服务OSS的能效提升》。 &#xff08;报告出品方&#xff1a;全球软件开发大会&#xff09; 报告共计&#xff1a;18页 结合AI的智能运维助力能效提升 场景1&#xff1a;通过 AI…

Python 网络爬虫(三):XPath 基础知识

《Python入门核心技术》专栏总目录・点这里 文章目录 1. XPath简介2. XPath语法2.1 选择节点2.2 路径分隔符2.3 谓语2.4 节点关系2.5 运算符3. 节点3.1 元素节点(Element Node)3.2 属性节点(Attribute Node)

前端vue3——实现二次元人物拼图校验

文章目录 ⭐前言⭐vue3拖拽实现拼图&#x1f496; 思路分解&#x1f496; 布局结构&#x1f496; 拖拽函数&#x1f496; 校验函数&#x1f496; inscode整体代码 ⭐运行效果&#x1f496; 随机顺序&#x1f496; 拖拽中&#x1f496; 校验失败&#x1f496; 校验通过 ⭐总结⭐…

苍穹外卖项目笔记(8)— 缓存商品、购物车功能

前言 代码链接&#xff1a; Echo0701/take-out⁤ (github.com) 1 缓存菜品 1.1 问题说明 【注】很多时候系统性能的瓶颈就在于数据库这端 1.2 实现思路 通过 Redis 来缓存数据&#xff0c;减少数据库查询操作 【注】Redis 基于内存来保存数据的&#xff0c;访问 Redis 数据…

LeetCode208.实现Trie(前缀树)

我一开始想题目叫前缀树&#xff0c;是要用树吗&#xff1f;但是不知道用树怎么写&#xff0c;然后我就花了10多分钟&#xff0c;用了HashMap解了。map的key是word&#xff0c;value是一个放了word的所有前缀的set&#xff0c;这样search方法就非常简单了&#xff0c;只要看has…

Leetcode—2048.下一个更大的数值平衡数【中等】

2023每日刷题&#xff08;五十四&#xff09; Leetcode—2048.下一个更大的数值平衡数 实现代码 class Solution { public:int nextBeautifulNumber(int n) {for(int x n 1; ; x) {vector<int> cnt(10, 0);for(int y x; y > 0; y / 10) {cnt[y%10];}bool ok tru…

TP5上传图片压缩尺寸

图片上传&#xff0c;最简单的就是&#xff0c; 方法一&#xff1a; 修改上传限制&#xff0c;不让上传大于多少多少的图片 改一下size即可&#xff0c;默认单位是B换算成M还需要除以两次1024 方法二&#xff1a; 对上传的图片进行缩放&#xff0c;此办法网上找了不少的代码…

HCIP —— BGP 基础 (下)

BGP 的状态机 --- 建立对等体之间的TCP会话&#xff1a;指定建立对等体的对象 六种状态机 Idle状态 Idle 等待状态&#xff08;相当于OSPF的down状态&#xff09;--- 采用TCP单播建邻 Idle 状态下&#xff0c;启动BGP协议后必须指定建立对等体的目标之后&#xff0c;才能进入…

数据结构 | 查漏补缺之哈希表、最短路径、二叉树与森林的转换

哈希表是什么&#xff1f; 或者说 设图采用邻接表的存储结构&#xff0c;写对图的删除顶点和删除边的算法步骤 删除边 删除点 最短路径问题 参考博文 迪杰斯特拉(Dijkstra)算法_dijkstra算法-CSDN博客 Dijkstra(迪杰斯特拉&#xff09;算法 定义一个点为源点&#xff0c;算源…