安卓 Audio Thread 分析

一、PlaybackThread::threadLoop_write

1.变量

mFramesWritten
类型: int64_t
作用: 记录从线程启动以来已写入音频设备的帧数(不包括挂起状态下的写入)

mSuspendedFrames
类型: int64_t
作用: 记录线程在挂起(suspended)状态下模拟写入的帧数

mBytesRemaining
作用: 用于实时跟踪待写入的剩余字节数。初始化时等于mCurrentWriteLength,表示本轮需写入的总量;写入过程中递减,直至归零

mCurrentWriteLength
混音模式​:threadLoop_mix()计算混音数据后,设置mCurrentWriteLength为需写入的字节数。
直通模式​:当需要立即写入时,mCurrentWriteLength设为mSinkBufferSize(硬件接收缓冲区大小)

2.代码段

mBytesRemaining !=0
if (mBytesRemaining) {// 如果还有未写入的字节数据(mBytesRemaining > 0)// FIXME: 重构代码以减少系统调用次数(当前实现可能效率不高)const int64_t lastIoBeginNs = systemTime(); // 记录开始写入操作的时间点(纳秒)ret = threadLoop_write(); // 执行实际的音频数据写入操作const int64_t lastIoEndNs = systemTime(); // 记录写入操作完成的时间点(纳秒)if (ret < 0) {// 写入失败(ret < 0)mBytesRemaining = 0; // 清零剩余字节数(标记写入失败)} else if (ret > 0) {// 成功写入部分数据(ret > 0)mBytesWritten += ret; // 更新总写入字节数mBytesRemaining -= ret; // 减少剩余待写入字节数const int64_t frames = ret / mFrameSize; // 计算实际写入的音频帧数mFramesWritten += frames; // 更新总写入帧数writePeriodNs = lastIoEndNs - mLastIoEndNs; // 计算本次写入操作的时间间隔(纳秒)// 仅对线性PCM格式音频进行处理(帧大小固定)if (audio_has_proportional_frames(mFormat)) {// 检查是否处于连续混音周期(混音器状态就绪且是连续写入)if (mMixerStatus == MIXER_TRACKS_READY &&loopCount == lastLoopCountWritten + 1) {// 计算抖动(jitter):实际写入时间与理论时间的偏差const double jitterMs =TimestampVerifier<int64_t, int64_t>::computeJitterMs({frames, writePeriodNs}, // 本次写入的帧数和时间间隔{0, 0} /* 上次时间戳 */, // 首次计算使用零值mSampleRate); // 音频采样率// 计算处理时间(从上次写入结束到本次写入开始的时间间隔)const double processMs = (lastIoBeginNs - mLastIoEndNs) * 1e-6;// 加锁修改线程内部状态audio_utils::lock_guard _l(mutex());mIoJitterMs.add(jitterMs); // 记录抖动数据用于统计mProcessTimeMs.add(processMs); // 记录处理时间用于统计// 如果使用MonoPipe(环形缓冲区)if (mPipeSink.get() != nullptr) {MonoPipe* monoPipe = static_cast<MonoPipe*>(mPipeSink.get());const ssize_t availableToWrite = mPipeSink->availableToWrite(); // 获取可写空间const size_t pipeFrames = monoPipe->maxFrames(); // 管道总容量(帧)// 计算管道中已占用的帧数(总容量 - 可写空间)const size_t remainingFrames = pipeFrames - max(availableToWrite, 0);mMonopipePipeDepthStats.add(remainingFrames); // 记录管道使用深度}}// 检测写入阻塞(耗时过长)const int64_t deltaWriteNs = lastIoEndNs - lastIoBeginNs; // 实际写入耗时if ((mType == MIXER || mType == SPATIALIZER) && // 仅检查混音/空间音频线程deltaWriteNs > maxPeriod) { // 超过最大允许时间mNumDelayedWrites++; // 增加延迟写入计数// 避免频繁告警(超过告警间隔才触发)if ((lastIoEndNs - lastWarning) > kWarningThrottleNs) {ATRACE_NAME("underrun"); // 性能分析标记ALOGW("write blocked for %lld msecs, %d delayed writes, thread %d",(long long)deltaWriteNs / NANOS_PER_MILLISECOND, // 纳秒转毫秒mNumDelayedWrites, // 延迟计数mId); // 线程IDlastWarning = lastIoEndNs; // 更新最后告警时间}}}// 更新时间追踪变量mLastIoBeginNs = lastIoBeginNs; // 更新最后写入开始时间mLastIoEndNs = lastIoEndNs; // 更新最后写入结束时间lastLoopCountWritten = loopCount; // 更新最后成功写入的循环计数}
}
mBytesRemaining == 0
// 检查当前没有剩余字节需要写入(mBytesRemaining == 0)
if (mBytesRemaining == 0) {// 重置当前写入长度为0mCurrentWriteLength = 0;// 如果混音器状态为TRACKS_READY(有活跃音轨且数据就绪)if (mMixerStatus == MIXER_TRACKS_READY) {// 执行混音操作:将各音轨数据混合到混音缓冲区// 同时会设置mCurrentWriteLength为实际写入长度threadLoop_mix();} // 如果混音器状态不是DRAIN_TRACK或DRAIN_ALL(非排空状态)else if ((mMixerStatus != MIXER_DRAIN_TRACK) && (mMixerStatus != MIXER_DRAIN_ALL)) {// 计算线程需要休眠的时间(可能为0)threadLoop_sleepTime();// 如果休眠时间为0(需要立即写入)if (mSleepTimeUs == 0) {// 设置当前写入长度为整个接收缓冲区大小mCurrentWriteLength = mSinkBufferSize;// 统计所有活跃音轨的欠载(underrun)情况:for (const auto& track : activeTracks) {// 只处理活跃且未停止/暂停/终止的音轨if (track->fillingStatus() == IAfTrack::FS_ACTIVE&& !track->isStopped()&& !track->isPaused()&& !track->isTerminated()) {// 记录因线程休眠导致的欠载ALOGV("%s: track(%d) %s underrun due to thread sleep of %zu frames",__func__, track->id(), track->getTrackStateAsString(),mNormalFrameCount);// 在音轨代理中累加欠载帧数track->audioTrackServerProxy()->tallyUnderrunFrames(mNormalFrameCount);}}}}// 以下处理混音后的数据:// ----------------------------------------------------------------// 检查混音缓冲区是否有效且需要复制到效果缓冲区或接收缓冲区if (mMixerBufferValid && (mEffectBufferValid || !mHasDataCopiedToSinkBuffer)) {// 确定目标缓冲区(效果缓冲区优先,否则直接到接收缓冲区)void *buffer = mEffectBufferValid ? mEffectBuffer : mSinkBuffer;// 确定目标格式(效果缓冲区格式优先,否则使用设备格式)audio_format_t format = mEffectBufferValid ? mEffectBufferFormat : mFormat;// 如果不需要效果处理(直通模式),需额外处理:if (!mEffectBufferValid) {// 单声道混合处理(如果需要)if (requireMonoBlend()) {mono_blend(mMixerBuffer, mMixerBufferFormat, mChannelCount,mNormalFrameCount, true /*limit*/);}// 声道平衡处理(如果没有FastMixer)if (!hasFastMixer()) {mBalance.setBalance(mMasterBalance.load());mBalance.process((float *)mMixerBuffer, mNormalFrameCount);}}// 格式转换:将混音缓冲区的数据转换格式后复制到目标缓冲区memcpy_by_audio_format(buffer, format, mMixerBuffer, mMixerBufferFormat,mNormalFrameCount * (mixerChannelCount + mHapticChannelCount));// 特殊处理:如果有触觉通道且直通输出if (!mEffectBufferValid && mHapticChannelCount > 0) {// 调整通道布局(分离音频和触觉数据)adjust_channels_non_destructive(buffer, mChannelCount, buffer,mChannelCount + mHapticChannelCount,audio_bytes_per_sample(format),audio_bytes_per_frame(mChannelCount, format) * mNormalFrameCount);}}// 更新剩余字节数(准备写入)mBytesRemaining = mCurrentWriteLength;// 如果设备处于挂起状态(如蓝牙通话)if (isSuspended()) {// 计算休眠时间(模拟写入完成)mSleepTimeUs = suspendSleepTimeUs();// 计算剩余帧数const size_t framesRemaining = mBytesRemaining / mFrameSize;// 更新统计信息mBytesWritten += mBytesRemaining;mFramesWritten += framesRemaining;mSuspendedFrames += framesRemaining; // 用于调整内核位置mBytesRemaining = 0; // 重置剩余字节}
}

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

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

相关文章

JavaWeb_原始项目初识(一)

Students2025项目&#xff08;一&#xff09; 原始ServletJSP架构项目初步搭建 jsp项目已被淘汰&#xff0c;在此学习目的是了解未来学习的新技术的底层原理项目结构&#xff1a;项目结构介绍&#xff1a; 目前阶段只完成了初始化的后端搭建&#xff0c;实现从本地数据库获取数…

前端_CSS复习

文章目录CSS复习1. css三种引入方式1.1 行内样式常用样式&#xff1a;1.2页内样式常见选择器&#xff1a;1. 标记选择器2. id选择器3. 类选择器&#xff08;最常用&#xff09;4. 星号选择器&#xff0c;频率很低5. 复合选择器6. 伪类选择器&#xff1a;7. 子元素伪类1.3引入外…

工业互联网时代,如何通过混合SD-WAN提升煤炭行业智能化网络安全

1. 背景&#xff1a;煤炭行业智能化转型的网络挑战随着工业互联网技术的普及&#xff0c;煤炭行业智能化转型进入加速期。选煤厂作为煤炭生产的核心环节&#xff0c;需要构建一套既安全又高效的网络系统&#xff0c;以满足工业控制系统&#xff08;ICS&#xff09;、智能设备和…

AI浪潮下数据中心的突围者:台达DPH Gen3系列UPS如何重构供电架构

2025年6月13日&#xff0c;台达-中达电通资通讯基础设施事业部联合中国数据中心工作组&#xff08;CDCC&#xff09;在江苏吴江举办"数据中心供配电技术革新与AI算力基础设施未来展望研讨会"&#xff0c;同时开展CDCC专家组工厂参观。盛会汇聚了数据中心行业专家、互…

DiffServ服务模型与DS码点详解

1. DiffServ概述 DiffServ(Differentiated Services&#xff0c;差异化服务)是IETF定义的一种QoS(Quality of Service)体系结构&#xff0c;旨在为IP网络提供可扩展的服务区分能力。与传统的IntServ(集成服务)模型不同&#xff0c;DiffServ采用简单、粗粒度的流量分类机制&…

基于 PIC16 系列的多功能电子烟(温控 + 电压控制 + 多模式)方案

基于 PIC16 系列的多功能电子烟&#xff08;温控 电压控制 多模式&#xff09;方案 一、芯片与最小系统推荐型号&#xff1a;PIC16F18313/18323 8-bit 内核&#xff0c;14/20-pin 小封装&#xff0c;成本低28 MHz 内部振荡&#xff0c;带 10-bit ADC&#xff08;12 通道&…

小模数齿轮的加工方法有哪些?

小模数齿轮(一般指0.3≤Mn≤1)的加工方法有哪些呢&#xff1f;小模数齿轮的加工方法主要分为减材、增材、变形加工三类&#xff1a; 去材料制造 有铣齿、滚齿、插齿、刨齿、剃齿、拉齿、冲齿、研磨、珩齿、磨齿及其抛光、线切割等。 增材制造 有注塑&#xff08;塑料、尼龙&…

若依前后端分离版学习笔记(二)——系统菜单介绍

前言&#xff1a; 这一节是将ruoyi的前端界面过一遍&#xff0c;查看所有系统菜单及页面功能&#xff0c;为后续代码学习做准备。&#xff08;注意&#xff1a;文中包含大量截图&#xff0c;截图为从本地启动的3.9.0 vue3的前端界面。&#xff09; 一 系统管理 1 用户管理 主要…

VRRP技术-设备备份技术

一、VRRP的概念及应用场景1.定义在 VRRP&#xff08;虚拟路由冗余协议&#xff09;中&#xff0c;将多个路由器逻辑上看作一个路由器时所使用的虚拟 IP 地址&#xff0c;需要满足以下要求&#xff1a;这个虚拟 IP 地址必须与该 VRRP 组内所有物理路由器的接口 IP 地址处于同一网…

VUE2 学习笔记5 动态绑定class、条件渲染、列表过滤与排序

动态绑定class样式&#xff1a;先设置css&#xff1a;<style>.styleBackgroundColor{background-color: aqua;}.styleContent{width:300px;height: 200px;}.styleBorder{border: 2px black solid;}</style>vue模版中&#xff0c;使用动态类名绑定&#xff0c;一般可…

推客系统全栈开发指南:从架构设计到高并发实战

一、推客系统概述与市场前景推客系统&#xff08;也称为"推客营销系统"或"社交电商系统"&#xff09;是近年来快速崛起的社交化营销工具&#xff0c;它通过整合社交网络与电子商务功能&#xff0c;让每个用户都能成为产品的推广者并获得相应奖励。市场数据…

RabbitMQ有多少种Exchange?

面试回答模板 “RabbitMQ 在 AMQP 协议中预定义了 四种常用交换机 两种特殊类型&#xff0c;共 6 种&#xff1a; Direct&#xff1a;routing-key 全等匹配&#xff1b;Fanout &#xff1a;广播&#xff0c;忽略 key&#xff1b;Topic&#xff1a;按 *.# 通配符匹配&#xff1…

ctfshow pwn43

1. 分析程序首先检查程序相关保护&#xff0c;发现程序为32位且只开启了一个NX保护checksec pwn使用IDA进行逆向分析代码&#xff0c;查看漏洞触发点&#xff1a;在main函数中&#xff0c;有一个ctfshow函数&#xff0c;这里我们跟进ctfshow()发现存在一个gets()函数&#xff0…

内网IM:BeeWorks私有化部署的安全通讯解决方案

在当今数字化办公环境中&#xff0c;内网IM已成为企业保障数据安全的核心工具。BeeWorks作为一款支持私有化部署的内网IM解决方案&#xff0c;能够帮助企业构建完全自主可控的通讯系统。无论是政府机构、金融机构&#xff0c;还是对数据安全要求极高的企业&#xff0c;BeeWorks…

SHA512算法详解

SHA-512 是 SHA-2&#xff08;Secure Hash Algorithm 2&#xff09;系列密码散列函数的重要成员&#xff0c;由美国国家安全局&#xff08;NSA&#xff09;设计&#xff0c;2001 年被纳入 NIST&#xff08;美国国家标准与技术研究院&#xff09;的 FIPS 180 标准&#xff0c;后…

通过python管理vcenter中的虚拟机

通过python管理vcenter中的虚拟机因业务需要&#xff0c;需在夜间关闭虚拟机&#xff0c;随通过计划任务远程管理开机、关机虚拟机一、通过docker配置python3.9环境 Dockerfile FROM python:3.9 RUN pip3 install pyvmomi7.0.0创建自定义镜像 docker build -t pyvmomi7:v1 .二…

AWS S3 生命周期管理最佳实践:IoT Core 日志的智能存储优化

在现代物联网应用中,设备日志数据的管理是一个重要挑战。随着设备数量的增长,日志数据量呈指数级增长,如何有效管理这些数据的存储成本成为关键问题。本文将分享如何为 AWS IoT Core 日志实施智能生命周期管理策略。 背景与挑战 IoT 设备产生的日志数据具有以下特点: 数据…

18.TaskExecutor获取ResourceManagerGateway

TaskExecutor获取ResourceManagerGatewayTaskExecutor 与 ResourceManager 之间的交互机制较为复杂&#xff0c;核心可以拆分为三个阶段&#xff1a; 首次发现与注册连接建立心跳维持 本文聚焦连接建立阶段&#xff0c;详细分析底层 RPC 连接的实现原理。回顾&#xff1a;start…

kafka查看消息的具体内容 kafka-dump-log.sh

目录kafka 消息查看1. 直接查看日志文件内容步骤&#xff1a;2. 使用 Kafka 工具查看日志主要参数说明常用命令&#xff1a;输出说明&#xff1a;3. 注意事项kafka 消息日志文件详解我们有时候遇到这样的需求&#xff0c;需要查看下kafka消息的内容。 kafka 消息查看 查看 Ka…

Spring Cloud OpenFeign 常用注解_笔记

Spring Cloud OpenFeign 提供了一种声明式、模板化的HTTP客户端&#xff0c;可以通过简单的接口描述远程调用&#xff0c;而不必手动编写低级的 HTTP 客户端代码。FeignClient用法参考&#xff1a;FeignClient用法-笔记-CSDN博客。这里梳理Spring Cloud OpenFeign 常用注解。 1…