Android AudioFlinger(四)—— 揭开PlaybackThread面纱

前言:

继上一篇Android AudioFlinger(三)—— AndroidAudio Flinger 之设备管理我们知道PlaybackThread继承自Re’fBase, 在被第一次引用的时候就会调用onFirstRef,实现如下:

void AudioFlinger::PlaybackThread::onFirstRef()
{run(mThreadName, ANDROID_PRIORITY_URGENT_AUDIO);
}

很简单就调用了一个run方法去开起了一个ThreadLoop线程:

bool AudioFlinger::PlaybackThread::threadLoop()
{
...
}

接下来我们进一步研究下PlaybackThread的循环主题具体做了什么?

揭开PlaybackThread面纱

当进入到threadloop就说明playbackthread的音频事务正式开启了。代码比较多,但是我们如果仔细看的话会发现关键代码就几处,而且都是threadLoop_前缀的,threadLoop_standby\threadLoop_mix\threadLoop_sleepTime\threadLoop_write等,这也代表这些函数都是threadLoop内部调用的。

bool AudioFlinger::PlaybackThread::threadLoop()
{
...while (!exitPending()){...{ // scope for mLock//这个地方框起来主要就是限制自动锁_l的生命周期,Mutex::Autolock _l(mLock);//处理config事件processConfigEvents_l();            if ((!mActiveTracks.size() && systemTime() > mStandbyTimeNs) ||isSuspended()) {if (shouldStandby_l()) {//进入standby状态节省能耗threadLoop_standby();}}//准备音频流mMixerStatus = prepareTracks_l(&tracksToRemove);} // mLock scope ends...if (mBytesRemaining == 0) {mCurrentWriteLength = 0;if (mMixerStatus == MIXER_TRACKS_READY) {//读取所有active设备数据,混音器开始混音threadLoop_mix();} else if ((mMixerStatus != MIXER_DRAIN_TRACK)&& (mMixerStatus != MIXER_DRAIN_ALL)) {//进入休眠threadLoop_sleepTime();}}...if (!waitingAsyncCallback()) {if (mSleepTimeUs == 0) {if (mBytesRemaining) {//把混音器处理好的数据写入到输出流设备ret = threadLoop_write();} else if ((mMixerStatus == MIXER_DRAIN_TRACK) ||(mMixerStatus == MIXER_DRAIN_ALL)) {threadLoop_drain();}...}}
... //移除相关的trackthreadLoop_removeTracks(tracksToRemove);tracksToRemove.clear();clearOutputTracks();effectChains.clear();}threadLoop_exit();if (!mStandby) {threadLoop_standby();mStandby = true;}releaseWakeLock();return false;
}

首先exitPending是threadloop循环的条件,这个函数是Thread的内部函数,它主要就是通过判断mExitPending来决定是否退出线程,这个值默认为false,在收到requestExit或者requestExitAndWait的时候会变为true,然后就会退出循环。
Thread PATH:/system/core/libutils/Threads.cpp

processConfigEvents_l: 处理config时间,当有配置发声变化的时候会调用sendConfigEvent_l来把事件添加到mConfigEvents中,最终processConfigEvents_l检测到就会去处理对应的配置。

threadLoop_standby: 判断当前是否符合standby条件,符合就调用threadLoop_standby,最终的实现其实是hal层实现,会做出关闭音频流等操作。

prepareTracks_l: 这个函数非常复杂,我们简单概括下,挑几个重点谈一谈


// prepareTracks_l() must be called with ThreadBase::mLock held
AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(Vector< sp<Track> > *tracksToRemove)
{//获取当前活跃的track数量size_t count = mActiveTracks.size();for (size_t i=0 ; i<count ; i++) {//循环每个活跃的trackconst sp<Track> t = mActiveTracks[i];// this const just means the local variable doesn't changeTrack* const track = t.get();// process fast tracksif (track->isFastTrack()) {//如果是fasttrack改如何处理}
...{   // local variable scope to avoid goto warning//数据块准备操作audio_track_cblk_t* cblk = track->cblk();//获取track的音频信息const uint32_t sampleRate = track->mAudioTrackServerProxy->getSampleRate();AudioPlaybackRate playbackRate = track->mAudioTrackServerProxy->getPlaybackRate();desiredFrames = sourceFramesNeededWithTimestretch(sampleRate, mNormalFrameCount, mSampleRate, playbackRate.mSpeed);desiredFrames += mAudioMixer->getUnreleasedFrames(track->name());uint32_t minFrames = 1;if ((track->sharedBuffer() == 0) && !track->isStopped() && !track->isPausing() &&(mMixerStatusIgnoringFastTracks == MIXER_TRACKS_READY)) {//至少需要准备的音频帧数minFrames = desiredFrames;}size_t framesReady = track->framesReady();if ((framesReady >= minFrames) && track->isReady() &&!track->isPaused() && !track->isTerminated()){mixedTracks++;// compute volume for this trackuint32_t vl, vr;       // in U8.24 integer formatfloat vlf, vrf, vaf;   // in [0.0, 1.0] float format//左声道,右声道,aux level音量// read original volumes with volume controlfloat typeVolume = mStreamTypes[track->streamType()].volume;//获取每个stream类型的音频音量float v = masterVolume * typeVolume;//主音量和类型音量相乘if (track->isPausing() || mStreamTypes[track->streamType()].mute) {vl = vr = 0;vlf = vrf = vaf = 0.;//设置0,代表静音操作if (track->isPausing()) {track->setPaused();//track设置暂停}} else {sp<AudioTrackServerProxy> proxy = track->mAudioTrackServerProxy;gain_minifloat_packed_t vlr = proxy->getVolumeLR();//得到音量的增益值vlf = float_from_gain(gain_minifloat_unpack_left(vlr));vrf = float_from_gain(gain_minifloat_unpack_right(vlr));//转换为浮点值// track volumes come from shared memory, so can't be trusted and must be clamped//判断是否在合理范围内if (vlf > GAIN_FLOAT_UNITY) {ALOGV("Track left volume out of range: %.3g", vlf);vlf = GAIN_FLOAT_UNITY;}if (vrf > GAIN_FLOAT_UNITY) {ALOGV("Track right volume out of range: %.3g", vrf);vrf = GAIN_FLOAT_UNITY;}const float vh = track->getVolumeHandler()->getVolume(track->mAudioTrackServerProxy->framesReleased()).first;// now apply the master volume and stream type volume and shaper volumevlf *= v * vh;vrf *= v * vh;// assuming master volume and stream type volume each go up to 1.0,// then derive vl and vr as U8.24 versions for the effect chainconst float scaleto8_24 = MAX_GAIN_INT * MAX_GAIN_INT;vl = (uint32_t) (scaleto8_24 * vlf);vr = (uint32_t) (scaleto8_24 * vrf);// vl and vr are now in U8.24 formatuint16_t sendLevel = proxy->getSendLevel_U4_12();// send level comes from shared memory and so may be corruptif (sendLevel > MAX_GAIN_INT) {ALOGV("Track send level out of range: %04X", sendLevel);sendLevel = MAX_GAIN_INT;}// vaf is represented as [0.0, 1.0] float by rescaling sendLevelvaf = v * sendLevel * (1. / MAX_GAIN_INT);}track->setFinalVolume((vrf + vlf) / 2.f);// XXX: these things DON'T need to be done each timemAudioMixer->setBufferProvider(name, track);mAudioMixer->enable(name);mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, &vlf);mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, &vrf);mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, &vaf);...} else {...}}   // local variable scope to avoid goto warning}return mixerStatus;
}

mActiveTracks记录了当前处于活跃状态的track,接着就是循环遍历每一个track进行处理,获取对应的音频参数。

audio_track_cblk_t是音频数据块,后面我们会扩展讲解。

在之后minFrames代表了此次音频播放所需要的最小帧数,他的初始值为1。当track->sharedBuffer() == 0的时候,说明这个AudioTrack不是STATIC模式(数据不是一次性传送完成的)。

getUnreleasedFrames用来获取音频缓冲区中尚未被音频硬件处理的帧数。

当我们计算出minFrames之后,就开始判断当前音频的各种指标是否符合标准。

vlf, vrf, vaf分别表示,左声道音量,右声道音量,AUX level音量,浮点数表示。

根据streamType获取对应stream类型音频的音量,然后进行判断是否在合理范围内,最终经过计算设置到AudioMixer对象中。当准备工作完成后,就进入到了真正的混音操作中了。

threadloop_mix:主要就是调用AudioMixer的process函数进行处理,这样就进入了audiomixer。


void AudioFlinger::MixerThread::threadLoop_mix()
{// 启动混音mAudioMixer->process();mCurrentWriteLength = mSinkBufferSize;//当应用程序欠载情况清除时,逐步增加睡眠时间。//仅当混频器连续两次准备就绪时才增加睡眠时间,//以避免交替的就绪/未就绪条件的稳定状态保持睡眠时间,从而导致音频 HAL 欠载。if ((mSleepTimeUs == 0) && (sleepTimeShift > 0)) {sleepTimeShift--;}mSleepTimeUs = 0;mStandbyTimeNs = systemTime() + mStandbyDelayNs;//TODO: delay standby when effects have a tail}

最后就是将数据写入HAL层了,threadloop_write。
当mNormalSink存在的时候调用他的write函数写入,不存在就调用mOutput的write函数,mOutput就是 AudioStreamOut。

ssize_t AudioFlinger::PlaybackThread::threadLoop_write()
{ssize_t bytesWritten;// If an NBAIO sink is present, use it to write the normal mixer's submixif (mNormalSink != 0) {ssize_t framesWritten = mNormalSink->write((char *)mSinkBuffer + offset, count);ATRACE_END();if (framesWritten > 0) {bytesWritten = framesWritten * mFrameSize;} else {bytesWritten = framesWritten;}// otherwise use the HAL / AudioStreamOut directly} else {bytesWritten = mOutput->write((char *)mSinkBuffer + offset, mBytesRemaining);}return bytesWritten;
}

写入完成后调用各种清理的函数,remove,clear等。

// Finally let go of removed track(s), without the lock held
// since we can't guarantee the destructors won't acquire that
// same lock.  This will also mutate and push a new fast mixer state.
threadLoop_removeTracks(tracksToRemove);
tracksToRemove.clear();// FIXME I don't understand the need for this here;
//       it was in the original code but maybe the
//       assignment in saveOutputTracks() makes this unnecessary?
clearOutputTracks();// Effect chains will be actually deleted here if they were removed from
// mEffectChains list during mixing or effects processing
effectChains.clear();
void AudioFlinger::PlaybackThread::threadLoop_removeTracks(const Vector< sp<Track> >& tracksToRemove)
{size_t count = tracksToRemove.size();if (count > 0) {for (size_t i = 0 ; i < count ; i++) {const sp<Track>& track = tracksToRemove.itemAt(i);if (track->isExternalTrack()) {AudioSystem::stopOutput(mId, track->streamType(),track->sessionId());if (track->isTerminated()) {AudioSystem::releaseOutput(mId, track->streamType(),track->sessionId());}}}}
}

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

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

相关文章

个人电脑本地部署DeepSeek来离线使用

文章目录 前言软件下载DeepSeek部署ChatBox集成 前言 最近这段时间&#xff0c;“DeepSeek”&#xff08;深度求索&#xff09;人工智能平台非常的火爆&#xff0c;正确的使用可以帮我们做很多很多事情&#xff0c;通常我们是在浏览器网页或手机APP使用&#xff0c;但是有时会…

第一:goland安装

GOPROXY (会话临时性)&#xff0c;长久的可以在配置文件中配置 go env -w GOPROXYhttps://goproxy.cn,direct 长久的&#xff0c;在~/.bashrc文件中添加&#xff1a; export GOPROXYhttps://goproxy.cn,direct &#xff0d;&#xff0d;&#xff0d;&#xff0d;&#xff0d…

介绍一下Qt中的事件过滤

在 Qt 中&#xff0c;事件过滤&#xff08;Event Filter&#xff09;是一种强大的机制&#xff0c;它允许一个对象拦截并处理另一个对象接收到的事件。通过事件过滤&#xff0c;可以在事件到达目标对象之前对其进行监控和修改&#xff0c;这在很多场景下都非常有用&#xff0c;…

Go红队开发—格式导出

文章目录 输出功能CSV输出CSV 转 结构体结构体 转 CSV端口扫描结果使用CSV格式导出 HTML输出Sqlite输出nmap扫描 JSONmap转json结构体转jsonjson写入文件json编解码json转结构体json转mapjson转string练习&#xff1a;nmap扫描结果导出json格式 输出功能 在我们使用安全工具的…

SwanLab简明教程:从萌新到高手

目录 1. 什么是SwanLab&#xff1f; 1.1 核心特性 2. 安装SwanLab 3. 登录SwanLab账号&#xff08;云端版&#xff09; 4. 5分钟快速上手 更多案例 5. SwanLab功能组件 5.1 图表视图 5.2 表格视图 5.3 硬件监控 5.4 环境记录 5.5 组织协同 6. 训练框架集成 6.1 基…

2025天梯训练1

PTA | L3-1 直捣黄龙 30分 思路&#xff1a;多关键字最短路&#xff0c;同时还要记录最短路径条数。 typedef struct node{int from,d,pass,kl;bool operator<(const node &x)const{if(d!x.d) return d>x.d;if(pass!x.pass) return pass<x.pass;return kl<x.…

EasyRTC嵌入式视频通话SDK的跨平台适配,构建web浏览器、Linux、ARM、安卓等终端的低延迟音视频通信

1、技术背景 WebRTC是一项开源项目&#xff0c;旨在通过简单的API为浏览器和移动应用程序提供实时通信&#xff08;RTC&#xff09;功能。它允许在无需安装插件或软件的情况下&#xff0c;实现点对点的音频、视频和数据传输。 WebRTC由三个核心组件构成&#xff1a; GetUserM…

【git】ssh配置提交 gitcode-ssh提交

【git】ssh配置提交 gitcode-ssh提交 之前一直用的是gitee和阿里云的仓库&#xff0c;前两天想在gitcode上面备份一下我的打洞代码和一些资料 就直接使用http克隆了下来 。 在提交的时候他一直会让我输入账号和密码&#xff0c;但是我之前根本没有设置过这个&#xff0c;根本没…

Dify部署踩坑指南(Windows+Mac)

组件说明 Dify踩坑及解决方案 ⚠️ 除了修改镜像版本&#xff0c;nginx端口不要直接修改docker-compose.yaml &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 1、更换镜像版本 这个文件是由.env自动生成的&#xff0c;在.env配置 …

Linux进程调度与管理:(五)进程的调度之调度节拍

《Linux6.5源码分析&#xff1a;进程管理与调度系列文章》 本系列文章将对进程管理与调度进行知识梳理与源码分析&#xff0c;重点放在linux源码分析上&#xff0c;并结合eBPF程序对内核中进程调度机制进行数据实时拿取与分析。 在进行正式介绍之前&#xff0c;有必要对文章引…

K8S学习之基础十七:k8s的蓝绿部署

蓝绿部署概述 ​ 蓝绿部署中&#xff0c;一共有两套系统&#xff0c;一套是正在提供服务的系统&#xff0c;一套是准备发布的系统。两套系统都是功能完善、正在运行的系统&#xff0c;只是版本和对外服务情况不同。 ​ 开发新版本&#xff0c;要用新版本替换线上的旧版本&…

【定制开发】碰一碰发视频系统定制开发,支持OEM

在短视频营销爆发的2025年&#xff0c;"碰一碰发视频"技术已成为实体商家引流标配。某连锁餐饮品牌通过定制化开发&#xff0c;单月视频发布量突破10万条&#xff0c;获客成本降低80%&#xff01;本文将深入解析该系统的技术架构与开发要点&#xff0c;助你快速搭建高…

[Lc7_分治-快排] 快速选择排序 | 数组中的第K个最大元素 | 库存管理 III

目录 1. 数组中的第K个最大元素 题解 代码 2.库存管理 III 代码 1. 数组中的第K个最大元素 题目链接&#xff1a;215. 数组中的第K个最大元素 题目分析&#xff1a; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要…

AI视频生成工具清单(附网址与免费说明)

以下是一份详细的AI视频制作网站总结清单&#xff0c;包含免费/付费信息及核心功能说明&#xff1a; AI视频生成工具清单&#xff08;附网址与免费说明&#xff09; 1. Synthesia 网址&#xff1a;https://www.synthesia.io是否免费&#xff1a;免费试用&#xff08;生成视频…

dp_走方格(包含dfs分析,记忆化搜索)

类似题目解析&#xff1a;dp_最长上升子序列&#xff08;包含dfs分析&#xff0c;记忆化搜索&#xff09;-CSDN博客 题目链接&#xff1a;2067. 走方格 - AcWing题库 题目图片&#xff1a; 分析题目&#xff08;dfs&#xff09; 这个题目说有一个行为n行&#xff0c;列为m列…

Windows系统安装python2025最新安装包,包括环境配置,以及安装python编程软件PyCharm2024.3.3免费社区版本,详细全流程

一、python安装包安装 1、python安装包下载 浏览器打开官网&#xff0c;最好是谷歌浏览器 https://www.python.org/downloads/windows/ 下载安装包&#xff08;注意处理器是32位还是64位&#xff09; 注意&#xff1a;下载完成后&#xff0c;找到安装包并双击运行。在安装向导…

【GPT入门】第3课 客服会话质检(思维链)

【GPT入门】第3课 客服会话质检 1.质检任务2. 代码3.核心 1.质检任务 任务本质是检查客服与用户的对话是否有不合规的地方 质检是电信运营商和金融券商大规模使用的一项技术 每个涉及到服务合规的检查点称为一个质检项 我们选一个质检项&#xff0c;产品信息准确性&#xff0…

ubuntu 20.04 C++ 源码编译 cuda版本 opencv4.5.0

前提条件是安装好了cuda和cudnn 点击下载&#xff1a; opencv_contrib4.5.0 opencv 4.5.0 解压重命名后 进入opencv目录&#xff0c;创建build目录 “CUDA_ARCH_BIN ?” 这里要根据显卡查询一下,我的cuda是11&#xff0c;显卡1650&#xff0c;所以是7.5 查询方法1&#xff1…

K8s 1.27.1 实战系列(四)验证集群及应用部署测试

一、验证集群可用性 1、检查节点 kubectl get nodes ------------------------------------------------------ NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 3h48m v1.27.1 k8s-node1 Ready <none> …

【C++设计模式】第七篇:桥接模式(Bridge)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 抽象与实现的解耦之道 1. 模式定义与用途​​ 核心思想​ ​桥接模式&#xff1a;将抽象部分与实现部分分离&#xff0c;使二者可以独立变化。​关键用途&#xff1a; ​1.拆分复杂继承…