当前位置: 首页 > news >正文

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());}}}}
}

相关文章:

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

前言&#xff1a; 继上一篇Android AudioFlinger&#xff08;三&#xff09;—— AndroidAudio Flinger 之设备管理我们知道PlaybackThread继承自Re’fBase&#xff0c; 在被第一次引用的时候就会调用onFirstRef&#xff0c;实现如下&#xff1a; void AudioFlinger::Playbac…...

C语言基础系列【20】内存管理

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…...

JavaScript基础-递增和递减运算符

在JavaScript编程中&#xff0c;递增&#xff08;&#xff09;和递减&#xff08;--&#xff09;运算符是用于对数值进行加一或减一操作的基础工具。它们简洁且强大&#xff0c;但如果不正确地使用&#xff0c;可能会导致混淆或错误。本文将详细介绍这两种运算符的不同形式及其…...

计算机毕业设计SpringBoot+Vue.js社区医疗综合服务平台(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

3.6c语言

#define _CRT_SECURE_NO_WARNINGS #include <math.h> #include <stdio.h> int main() {int sum 0,i,j;for (j 1; j < 1000; j){sum 0;for (i 1; i < j; i){if (j % i 0){sum i;} }if (sum j){printf("%d是完数\n", j);}}return 0; }#de…...

Unity开发——CanvasGroup组件介绍和应用

CanvasGroup是Unity中用于控制UI的透明度、交互性和渲染顺序的组件。 一、常用属性的解释 1、alpha&#xff1a;控制UI的透明度 类型&#xff1a;float&#xff0c;0.0 ~1.0&#xff0c; 其中 0.0 完全透明&#xff0c;1.0 完全不透明。 通过调整alpha值可以实现UI的淡入淡…...

深度学习驱动的跨行业智能化革命:技术突破与实践创新

第一章 深度学习的技术范式演进与核心架构 1.1 从传统机器学习到深度神经网络的跨越 深度学习的核心在于通过多层次非线性变换自动提取数据特征,其发展历程可划分为三个阶段:符号主义时代的规则驱动(1950s-1980s)、连接主义时代的浅层网络(1990s-2000s)以及深度学习时代…...

php配置虚拟主机

在PHP中配置虚拟主机&#xff0c;通常是通过Apache或Nginx等Web服务器来进行设置的。下面我将分别介绍如何在Apache和Nginx中配置PHP虚拟主机。 1. Apache 配置虚拟主机 Apache是最常用的Web服务器之一&#xff0c;配置虚拟主机的步骤如下&#xff1a; 步骤一&#xff1a;确保A…...

RESTful API 设计指南

RESTful API 介绍 大佬的总结&#xff1a;RESTful API 设计指南 - 阮一峰的网络日志 json-server github地址 这里介绍一个快速搭建 REST API 服务的工具包 接口测试工具 介绍几个接口测试工具 apipost apifox postman https://www.apipost.cn/ (中文) https://www.apifox…...

在虚拟机上安装Hadoop

以下是在虚拟机上安装Hadoop的一般步骤&#xff1a; 准备工作 - 安装虚拟机软件&#xff1a;如VMware Workstation或VirtualBox等。 - 创建虚拟机&#xff1a;选择合适的操作系统镜像&#xff0c;如Ubuntu或CentOS等Linux发行版&#xff0c;为虚拟机分配足够的CPU、内存和磁盘…...

大白话JavaScript实现一个函数,将字符串中的每个单词首字母大写。

大白话JavaScript实现一个函数&#xff0c;将字符串中的每个单词首字母大写。 答题思路 理解需求&#xff1a;要写一个函数&#xff0c;它能接收一个字符串&#xff0c;然后把这个字符串里每个单词的第一个字母变成大写。分解步骤 拆分单词&#xff1a;一般单词之间是用空格隔…...

【VUE2】第三期——样式冲突、组件通信、异步更新

目录 1 scoped解决样式冲突 2 data写法 3 组件通信 3.1 父子关系 3.1.1 父向子传值 props 3.1.2 子向父传值 $emit 3.2 非父子关系 3.2.1 event bus 事件总线 3.2.2 跨层级共享数据 provide&inject 4 props 4.1 介绍 4.2 props校验完整写法 5 v-model原理 …...

深度学习代码解读——自用

代码来自&#xff1a;GitHub - ChuHan89/WSSS-Tissue 借助了一些人工智能 2_generate_PM.py 功能总结 该代码用于 生成弱监督语义分割&#xff08;WSSS&#xff09;所需的伪掩码&#xff08;Pseudo-Masks&#xff09;&#xff0c;是 Stage2 训练的前置步骤。其核心流程为&a…...

Linux 配置静态 IP

一、简介 在 Linux CentOS 系统中默认动态分配 IP 地址&#xff0c;每次启动虚拟机服务都是不一样的 IP&#xff0c;因此要配置静态 IP 地址避免每次都发生变化&#xff0c;下面将介绍配置静态 IP 的详细步骤。 首先先理解一下动态 IP 和静态 IP 的概念&#xff1a; 动态 IP…...

Oxidized收集H3C交换机网络配置报错,not matching configured prompt (?-mix:^(<CD>)$)

背景&#xff1a;问题如上标题&#xff0c;H3C所有交换机配置的model都是comware 解决方案&#xff1a; 1、找到compare.rb [rootoxidized model]# pwd /usr/local/lib/ruby/gems/3.1.0/gems/oxidized-0.29.1/lib/oxidized/model [rootoxidized model]# ll comware.rb -rw-r--…...

RAG技术深度解析:从基础Agent到复杂推理Deep Search的架构实践

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…...

6.过拟合处理:确保模型泛化能力的实践指南——大模型开发深度学习理论基础

在深度学习开发中&#xff0c;过拟合是一个常见且具有挑战性的问题。当模型在训练集上表现优秀&#xff0c;但在测试集或新数据上性能大幅下降时&#xff0c;就说明模型“记住”了训练数据中的噪声而非学习到泛化规律。本文将从实际开发角度系统讲解如何应对过拟合&#xff0c;…...

【玩转23种Java设计模式】结构型模式篇:组合模式

软件设计模式&#xff08;Design pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。 汇总目录链接&…...

专业工具,提供多种磁盘分区方案

随着时间的推移&#xff0c;电脑的磁盘空间往往会越来越紧张&#xff0c;许多人都经历过磁盘空间不足的困扰。虽然通过清理垃圾文件可以获得一定的改善&#xff0c;但随着文件和软件的增多&#xff0c;磁盘空间仍然可能显得捉襟见肘。在这种情况下&#xff0c;将其他磁盘的闲置…...

SELinux 概述

SELinux 概述 概念 SELinux&#xff08;Security-Enhanced Linux&#xff09;是美国国家安全局在 Linux 开源社区的帮助下开发的一个强制访问控制&#xff08;MAC&#xff0c;Mandatory Access Control&#xff09;的安全子系统。它确保服务进程仅能访问它们应有的资源。 例…...

【十三】Golang 通道

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 通道通道声明初始化缓冲机制无缓冲通道代码示例 带…...

DeepSeek专题:DeepSeek-V2核心知识点速览

AIGCmagic社区知识星球是国内首个以AIGC全栈技术与商业变现为主线的学习交流平台&#xff0c;涉及AI绘画、AI视频、大模型、AI多模态、数字人以及全行业AIGC赋能等100应用方向。星球内部包含海量学习资源、专业问答、前沿资讯、内推招聘、AI课程、AIGC模型、AIGC数据集和源码等…...

Oracle19c进入EM Express(Oracle企业管理器)详细步骤

以下是使用Oracle 19c进入Oracle Enterprise Manager Database Express&#xff08;EM Express&#xff09;的详细步骤&#xff1a; ### **步骤 1&#xff1a;确认EM Express配置状态** 1. **登录数据库服务器** 使用Oracle用户或管理员权限账户登录操作系统。 2. **查看EM…...

游戏引擎学习第140天

回顾并为今天的内容做准备 目前代码的进展到了声音混音的部分。昨天我详细解释了声音的处理方式&#xff0c;声音在技术上是一个非常特别的存在&#xff0c;但在游戏中进行声音混音的需求其实相对简单明了&#xff0c;所以今天的任务应该不会太具挑战性。 今天我们会编写一个…...

C++--迭代器(iterator)介绍---主要介绍vector和string中的迭代器

目录 一、迭代器&#xff08;iterator&#xff09;的定义 二、迭代器的类别 三、使用迭代器 3.1 迭代器运算符 3.2 迭代器的简单应用&#xff1a;使用迭代器将string对象的第一个字母改为大写 3.3 将迭代器从一个元素移动到另外一个元素 3.4 迭代器运算 3.5 迭代器的复…...

RuleOS:区块链开发的“新引擎”,点燃Web3创新之火

RuleOS&#xff1a;区块链开发的“新引擎”&#xff0c;点燃Web3创新之火 在区块链技术的浪潮中&#xff0c;RuleOS宛如一台强劲的“新引擎”&#xff0c;为个人和企业开发去中心化应用&#xff08;DApp&#xff09;注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…...

机器学习之强化学习

引言 在人工智能的众多分支中&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&#xff09; 因其独特的学习范式而备受关注。与依赖标注数据的监督学习或探索数据结构的无监督学习不同&#xff0c;强化学习的核心是智能体&#xff08;Agent&#xff09;通过与环境…...

基于 uni-app 和 Vue3 开发的汉字书写练习应用

基于 uni-app 和 Vue3 开发的汉字书写练习应用 前言 本文介绍了如何使用 uni-app Vue3 uview-plus 开发一个汉字书写练习应用。该应用支持笔画演示、书写练习、进度保存等功能&#xff0c;可以帮助用户学习汉字书写。 在线演示 演示地址: http://demo.xiyueta.com/case/w…...

每天五分钟深度学习PyTorch:向更深的卷积神经网络挑战的ResNet

本文重点 ResNet大名鼎鼎,它是由何恺明团队设计的,它获取了2015年ImageNet冠军,它很好的解决了当神经网络层数过多出现的难以训练的问题,它创造性的设计了跳跃连接的方式,使得卷积神经网络的层数出现了大幅度提升,设置可以达到上千层,可以说resnet对于网络模型的设计具…...

electron + vue3 + vite 主进程到渲染进程的单向通信

用示例讲解下主进程到渲染进程的单向通信 初始版本项目结构可参考项目&#xff1a;https://github.com/ylpxzx/electron-forge-project/tree/init_project 主进程到渲染进程&#xff08;单向&#xff09; 以Electron官方文档给出的”主进程主动触发动作&#xff0c;发送内容给渲…...