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

从Perfetto视角看Audio异常underrun问题的表现

背景

经常在会有测试会报一些app在某些时候会有声音卡顿,或者说录音的文件异常问题,这种情况下一般我们可以叫做Xrun

正常underrun & overrun定义

我们知道,在Audio模块中数据采用的是生产者-消费者模式,生产者负责生产数据,消费者用于消费数据,针对AudioTrack和AudioRecord,其对应的角色不同;

AudioTrack
生产者:AudioTrack,向sharedBuffer中添加数据;
消费者:PlaybackThread(AudioFlinger),从sharedBuffer中读取数据;
AudioRecord
生产者:AudioFlinger(输入设备),向指定sharedBuffer中添加数据;
消费者:AudioRecord,从sharedBuffer中读取数据;
欠载 ( UnderRun ) : 播放音频流时 , 如果当前现有数据已经播放完毕 , 新数据还没有来得及写入 , 此时会发生欠载情况,现象为断断续续,一卡一卡;
超限 ( OverRun ) : 录制音频流时 , 如果没有及时读取音频流数据 , 并且这些数据没有妥善保存 , 发生溢出 , 导致数据丢失 , 这种情况叫做超限,现象为“爆”音;
今天就以一个underrun的案例来展示一下这种underrun问题在Perfetto上的表现。

分析underrun方法

一般确定当前系统发出声音是否有underrun现象一般可以通过以下2种方式进行确定:
1、使用logcat抓日志方式,在日志中如果频繁出现大量的underrun关键字符就说明声音是underrun了。

adb logcat|grepunderrun

可以看到有频繁的underrun相关打印,而且是framesReady(192) < framesDesired(1090)即AudioTrack汇总准备好的buf数目远远小于预期的framesDesired数量。

V AudioFlinger: track(60)underrun, track state ACTIVE framesReady(192)<framesDesired(1090)


看到上面的打印再结合听到的一卡一卡现象,就可以断定系统当前声音处于underrun状态。

2、可以采用音频课程中给大家讲解的teesink方式,导出系统各个阶段的音频,然后使用audacity软件进行播放查看。
去放开teesink,然后采用dumpsys media.audio_flinger命令进行导出相关过程的音频文件。

导出各个阶段的teesink音频文件进行确认underrun问题


然后把他们都导入到Audacity软件,展示相关的波形情况:



上图明显可以看出AudioTrack的数据展示情况结论如下:
1、AudioTrack数据明显看波形和播放后都正常
2、MixerThread中写入hal的数据部分,明显看着波形间隔比较大,整个时间长也比AudioTrack大好多,而且听起来声音一卡一卡,没办法接受。

通过上面导出音频波形分析就可以直接确定声音一卡一卡问题其实是出在MixerThread的自身逻辑么?
哈哈,其实答案肯定不是出在MixerThread部分,具体答案继续看下面Perfetto部分对underrun的剖析及后续的文章或者课程的原因定位部分?

underrun情况下声音卡顿的Perfetto/systrace剖析

这块就需要结合源码和Perfetto systrace进行剖析
对比正常和异常情况播放声音抓取的Perfetto展示
正常模式下Perfetto展示:
异常underrun情况下展示:

明显和正常情况一样时间内多了5ms左右sleep时间

同样buf部分发现只有几百个,而且频繁变化,明显看出数据buf不足。
但是数据buf不足正常那就不足,正常我们想象就一直等待数据满足了后再进行thread_write写入数据到hal,那这样只是说数据要过一会才播放,能理解听到的效果一卡一卡,但是AudioTrack发送过来的数据应该是是按照顺序写入应该是要正常的,没有数据就不进行写入,这样来看那么MixerThread导出的wav文件也应该要正常?

那么为啥这里的MixerThread的数据导出的wav文件,发现数据就是一卡一卡,明显波形也是不对的。

这块就留给课程中马哥带大家进行剖析,具体关注马哥的这块讲解,或者大家也可以自行进行额外添加atrace打印进行Perfetto分析出原因。

当然这块马哥也加了大量的额外trace:

@@-1473,12+1473,14@@voidAudioMixerBase::process__noResampleOneTrack()TO*out=reinterpret_cast<TO*>(t->mainBuffer);TA*aux=reinterpret_cast<TA*>(t->auxBuffer);constboolramp=t->needsRamp();-+{+}for(size_t numFrames=mFrameCount;numFrames>0;){AudioBufferProvider::Buffer&b(t->buffer);// get input bufferb.frameCount=numFrames;t->bufferProvider->getNextBuffer(&b);+ATRACE_FORMAT("process__noResampleOneTrack mFrameCount %d b.frameCount %d",mFrameCount,b.frameCount);constTI*in=reinterpret_cast<TI*>(b.raw);// in == NULL can happen if the track was flushed just after having@@-1489,6+1491,7@@voidAudioMixerBase::process__noResampleOneTrack()ALOGE_IF((((uintptr_t)in)&3),"process__noResampleOneTrack: bus error: ""buffer %p track %p, channels %d, needs %#x",in,&t,t->channelCount,t->needs);+ATRACE_NAME("process__noResampleOneTrack in == NULL || (((uintptr_t)in) & 3)");return;}diff--git a/services/audioflinger/Configuration.h b/services/audioflinger/Configuration.h index845697a38c..1f8276c02d100644---a/services/audioflinger/Configuration.h+++b/services/audioflinger/Configuration.h @@-36,7+36,7@@//#define STATE_QUEUE_DUMP// uncomment to allow tee sink debugging to be enabled by property-//#define TEE_SINK+#define TEE_SINK// uncomment to log CPU statistics every n wall clock seconds//#define DEBUG_CPU_USAGE 10diff--git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp index c525c4a874..9daf1f9825100644---a/services/audioflinger/Threads.cpp+++b/services/audioflinger/Threads.cpp @@-3595,6+3595,9@@ ssize_tPlaybackThread::threadLoop_write()#ifdefTEE_SINKmTee.write((char*)mSinkBuffer+offset,framesWritten);#endif+{+ATRACE_FORMAT(" mTee.write %d",framesWritten);+}}else{bytesWritten=framesWritten;}@@-5937,11+5940,13@@ PlaybackThread::mixer_stateMixerThread::prepareTracks_l(std::stringtraceName("nRdy");traceName+=std::to_string(trackId);ATRACE_INT(traceName.c_str(),framesReady);+ATRACE_FORMAT("framesReady %d minFrames %d",(int)framesReady,minFrames);}if((framesReady>=minFrames)&&track->isReady()&&!track->isPaused()&&!track->isTerminated()){ALOGVV("track(%d) s=%08x [OK] on thread %p",trackId,cblk->mServer,this);+ATRACE_FORMAT("track(%d) s=%08x [OK] on thread %p",trackId,cblk->mServer,this);mixedTracks++;@@-6211,6+6216,8@@ PlaybackThread::mixer_stateMixerThread::prepareTracks_l(}else{size_t underrunFrames=0;if(framesReady<desiredFrames&&!track->isStopped()&&!track->isPaused()){+ATRACE_FORMAT("track(%d) underrun, track state %s framesReady(%zu) < framesDesired(%zd)",+trackId,track->getTrackStateAsString(),framesReady,desiredFrames);ALOGV("track(%d) underrun, track state %s framesReady(%zu) < framesDesired(%zd)",trackId,track->getTrackStateAsString(),framesReady,desiredFrames);underrunFrames=desiredFrames;@@-6223,6+6230,7@@ PlaybackThread::mixer_stateMixerThread::prepareTracks_l(if(chain!=0){chain->clearInputBuffer();}+ATRACE_FORMAT("track(%d) s=%08x [NOT READY] on thread %p",trackId,cblk->mServer,this);ALOGVV("track(%d) s=%08x [NOT READY] on thread %p",trackId,cblk->mServer,this);if((track->sharedBuffer()!=0)||track->isTerminated()||diff--git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp index f5d72e32cd..26858f5ad5100644---a/services/audioflinger/Tracks.cpp+++b/services/audioflinger/Tracks.cpp @@-1063,6+1063,7@@uint32_tTrack::sampleRate()const{// AudioBufferProvider interfacestatus_tTrack::getNextBuffer(AudioBufferProvider::Buffer*buffer){+ATRACE_NAME("Track::getNextBuffer");ServerProxy::Buffer buf;size_t desiredFrames=buffer->frameCount;buf.mFrameCount=desiredFrames;@@-1072,8+1073,12@@ status_tTrack::getNextBuffer(AudioBufferProvider::Buffer*buffer)if(buf.mFrameCount==0&&!isStopping()&&!isStopped()&&!isPaused()&&!isOffloaded()){ALOGV("%s(%d): underrun, framesReady(%zu) < framesDesired(%zd), state: %d",__func__,mId,buf.mFrameCount,desiredFrames,(int)mState);+ATRACE_FORMAT("%s(%d): underrun, framesReady(%zu) < framesDesired(%zd), state: %d",+__func__,mId,buf.mFrameCount,desiredFrames,(int)mState);+mAudioTrackServerProxy->tallyUnderrunFrames(desiredFrames);}else{+ATRACE_NAME("mAudioTrackServerProxy->tallyUnderrunFrames(0)");mAudioTrackServerProxy->tallyUnderrunFrames(0);}returnstatus;

更多framework实战开发干货,请关注下面公众号“千里马学框架”

http://www.jsqmd.com/news/475843/

相关文章:

  • [Redis小技巧11]Redis Key 过期策略与内存淘汰机制:深度解析与实战指南
  • 基于龙卷风优化算法(TOC) 的多个无人机协同路径规划(可以自定义无人机数量及起始点)附Matlab代码
  • 2026年知名的防水行程开关厂家推荐:2NC 1NO行程开关/TUV认证行程开关厂家热卖产品推荐(近期) - 品牌宣传支持者
  • 2025年全国行业职业技能竞赛第四届全国数据安全职业技能竞赛暨第四届安防行业职业技能竞赛“美亚柏科杯“数据安全管理员样题
  • 工业数字化提速,边缘计算存储如何减负?天硕工业级固态硬盘给出答案
  • 哪款减肥产品掉秤快还安全?2026 高性价比减脂代餐推荐:懒人“躺瘦”不反弹指南 - 企业推荐官【官方】
  • YOLO12教学演示指南:Gradio界面动态调参+检测效果对比教学
  • Fish-Speech-1.5与Vue.js前端集成:实时语音预览功能实现
  • 网络基础干货|域名/DNS/URL 一篇吃透
  • 提升效率:用快马生成Python脚本自动批量下载推特媒体
  • 纯硬件嵌入式鞭炮声播放系统设计
  • 哪款减肥代餐好用又安全?腰纪线(MetaSlim)全营养代餐,以精准控热+代谢重启,解锁长效减脂 - 企业推荐官【官方】
  • 红区之困:分布式光伏爆发背后的“逆流危机”
  • DDrawCompat深度剖析:经典游戏现代重生的技术解密
  • LuckyLilliaBot三阶配置能力提升指南:从基础搭建到企业级部署
  • 全球海运业趋势晴雨表——能源与数字转型进展评估 劳氏船级社 2025-3
  • 2026年质量好的球磨铁铸件品牌推荐:铸铁平台铸件/泊头机床床身铸件高口碑品牌推荐 - 品牌宣传支持者
  • 主板风扇控制异常深度解决方案:从硬件原理到智能调校
  • Phi-3-mini-128k-instruct行业应用:医疗问诊摘要、患者教育材料生成实践
  • 立知重排序模型在Dify上的应用:搭建智能搜索引擎优化工作流
  • 2026年靠谱的高端同步隐藏轨品牌推荐:缓冲同步隐藏轨/品牌同步隐藏轨/三节同步隐藏轨厂家口碑推荐汇总 - 品牌宣传支持者
  • 2026年口碑好的品牌厨房拉篮品牌推荐:橱柜厨房拉篮/调味厨房拉篮厂家实力参考 - 品牌宣传支持者
  • 1亿次真实操作训练出来的自动装卸车AI,有了!
  • 尴尬!龙虾之父指责腾讯“抄袭”,网友吐槽“这很腾讯”,腾讯回应 。。。
  • QWEN-AUDIO开箱即用指南:无需conda/pip,纯Docker镜像运行
  • Qwen3-ASR-1.7B模型安全:对抗样本攻击与防御研究
  • 舵轮 AGV 小车:舵轮工作原理与核心特点
  • GBase 8a数据库在线备份技术原理之库级全备流程解析
  • 基于GTE-Base-ZH的微信小程序开发:实现智能文档搜索
  • 3分钟上手的高性能Markdown解决方案:轻量级编辑器的跨环境部署指南