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

AudioTrack音频播放流程深度解析

上一篇我们搭好了"舞台"——分析了AudioFlinger的整体架构。这一篇我们来追一个具体的"演员":AudioTrack

如果你曾经用过MediaPlayer播放一首歌,或者用SoundPool触发游戏音效,甚至用AudioTrack直接写PCM数据,那这些操作的底层全都绕不开今天要讲的内容。AudioTrack 就是 App 进程和 audioserver 进程之间的"音频快递员",负责把你的音频数据高效、低延迟地送达声卡。

本文主要内容:

  1. AudioTrack API核心参数和播放模式解析
  2. 创建流程:从 JavaBuilder到 AudioFlinger 完整调用链
  3. 共享内存 Ring Buffer:零拷贝数据传输的核心机制
  4. 播放控制状态机:play/pause/stop/flush 的内部实现
  5. FAST Track低延迟路径:20ms 内延迟的秘密
  6. 实战案例:PCM播放器 + 低延迟游戏音效

源码版本:Android 15 AOSP
关键路径frameworks/base/media/java/android/media/AudioTrack.javaframeworks/av/media/libaudioclient/AudioTrack.cpp


AudioTrack API 基础

两种播放模式的本质区别

AudioTrack 有两种播放模式,选错了轻则多占内存,重则产生明显延迟:

// MODE_STREAM:流式播放,适合长音频AudioTrackstreamTrack=newAudioTrack.Builder().setAudioFormat(newAudioFormat.Builder().setSampleRate(44100).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build()).setBufferSizeInBytes(minBufferSize).setTransferMode(AudioTrack.MODE_STREAM).build();// MODE_STATIC:静态缓冲,适合短音效AudioTrackstaticTrack=newAudioTrack.Builder().setBufferSizeInBytes(pcmData.length).setTransferMode(AudioTrack.MODE_STATIC).build();staticTrack.write(pcmData,0,pcmData.length);// 一次性写入全部数据
特性MODE_STREAMMODE_STATIC
适用场景音乐、长视频、实时录音回放游戏音效、短提示音(< 1MB)
内存使用按缓冲区大小分配全部PCM数据一次性加载
延迟取决于缓冲区大小极低(数据已在内存)
数据写入持续调用write()play()前一次写完
缓冲区位置进程间共享内存AudioFlinger 内部

经验之谈:游戏开发者经常犯一个错误——用MODE_STREAM播放子弹飞的"嗖"声,然后发现延迟明显。对于 < 1MB 的短音效,MODE_STATIC+ 提前加载才是正道。

关键参数详解

// 获取最小缓冲区大小(这是API强制要求的下限)intminBufferSize=AudioTrack.getMinBufferSize(sampleRate,// 采样率:8000/16000/22050/44100/48000 HzchannelConfig,// 声道配置:CHANNEL_OUT_MONO / STEREO / 5_1audioFormat// 数据格式:PCM_8BIT / PCM_16BIT / PCM_FLOAT);

getMinBufferSize()的内部逻辑值得一提。它并不是简单地返回一个固定值,而是查询 AudioFlinger 得到设备支持的最小帧数,再乘以frameSize(单帧字节数)。这个值直接反映了硬件的能力上限。

为何不能设太小?缓冲区太小意味着write()阻塞频繁,CPU 调度开销增大;更糟糕的是,一旦数据供给跟不上,AudioFlinger 就会产生underrun(欠载),你会听到断音或爆音。

AudioAttributes:音频属性的隐藏影响

很多开发者不知道,AudioAttributes不只是标签,它直接影响音频路由焦点管理

AudioAttributesattrs=newAudioAttributes.Builder().setUsage(AudioAttributes.USAGE_GAME)// 用途:游戏.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();// USAGE_GAME 会影响:// 1. AudioPolicyService 的路由决策// 2. 音量控制归属(游戏音量流 vs 媒体音量流)// 3. 音频焦点的优先级

AudioTrack 创建流程深度解析

用一张图来看整个创建过程:

这个创建链路跨越了3个进程边界,我们逐层拆解。

第一层:Java Builder 模式

AudioTrack.Builder.build()最终调用的是AudioTrack的私有构造函数,然后触发native_setup()

// frameworks/base/media/java/android/media/AudioTrack.javaprivateAudioTrack(AudioAttributesattributes,AudioFormatformat,intbufferSizeInBytes,intmode,intsessionId,booleanoffload,intencapsulationMode,@NullableTunerConfigurationtunerConfiguration){// ...参数校验...// 核心:调用 Native 层初始化intinitResult=native_setup(newWeakReference<AudioTrack>(this),mAttributes,sampleRate,channelMask,channelIndexMask,audioFormat,buffSizeInBytes,mode,sessionId,0/*nativeAudioTrack*/,offload,encapsulationMode,tunerConfiguration,getCurrentOpPackageName());if(initResult!=SUCCESS){loge("Error code "+initResult+" when initializing AudioTrack.");return;}}

值得注意的是native_setup的第一个参数:new WeakReference<AudioTrack>(this)。Native 层持有的是 Java 对象的弱引用,这样可以防止 Native 层阻止 Java 层的 GC 回收。这是 Android JNI 中的标准实践。

第二层:JNI 桥接

JNI 层在android_media_AudioTrack.cpp中,这里做了几件关键事情:

// frameworks/base/core/jni/android_media_AudioTrack.cppstaticjintandroid_media_AudioTrack_setup(JNIEnv*env,jobject thiz,jobject weak_this,jobject jaa,jintArray jSampleRate,...){// 1. 创建 Native AudioTrack 对象sp<AudioTrack>lpTrack=newAudioTrack();// 2. 初始化(这里开始跨进程)status_t status=lpTrack->set(AUDIO_STREAM_DEFAULT,sampleRateInHertz,format,nativeChannelMask,frameCount,(AudioTrack::transfer_type)transferType,...);// 3. 将 Native 指针存入 Java 对象setAudioTrack(env,thiz,lpTrack);return(jint)AUDIO_JAVA_SUCCESS;}

第三层:Native AudioTrack::set()

这是最关键的环节。AudioTrack::set()做了两件大事:参数校验与格式协商与 AudioFlinger 建立连接

// frameworks/av/media/libaudioclient/AudioTrack.cppstatus_tAudioTrack::set(audio_stream_type_t streamType,uint32_tsampleRate,audio_format_t format,audio_channel_mask_t channelMask,size_t frameCount,...){// 1. 参数校验:格式、采样率、声道数合法性检查if(!audio_is_valid_format(format)){ALOGE("Invalid format %#x",format);returnBAD_VALUE;}// 2. 格式协商:查询 AudioFlinger 确定最终参数// 例如:App 请求 44100Hz,硬件支持 48000Hz// AudioFlinger 会返回实际的采样率status_t status=createTrack_l();if(status!=NO_ERROR){returnstatus;}// 3. 注册播放完成回调if(cbf!=NULL){mAudioTrackThread=newAudioTrackThread(*this);}returnNO_ERROR;}

第四层:createTrack_l() —— 最关键的 Binder 调用

// frameworks/av/media/libaudioclient/AudioTrack.cppstatus_tAudioTrack::createTrack_l(){// 1. 获取 AudioFlinger 的 Binder 代理constsp<IAudioFlinger>&audioFlinger=AudioSystem::get_audio_flinger();
http://www.jsqmd.com/news/494853/

相关文章:

  • 蓝牙相关技术
  • 【Linux】网络编程基础—套接字
  • 优选算法_丢失的数字_位运算_C++
  • TestTimeScaling机制深度剖析与LLM生产级加速工程实践
  • NoSQL注入
  • 写给普通人,关于网络安全行业的认识
  • 用户关注功能
  • Git急救手册:30秒拯救误操作
  • Java面试高频考点MySQL索引优化与问题处理指南
  • 【从零开始学Java | 第十一篇】包、final、权限修饰符
  • 基于Simscape的双PID比例调速阀与比例溢流阀液压控制系统建模与仿真
  • 【路径规划】基于时空A星算法求解带时间约束的多机器人路径规划问题附matlab代码
  • 动静态库原理与ELF文件详解
  • 2026 年最新漏洞挖掘终极指南|从基础到精通,收藏这一篇足够
  • 【C++】C++设计心得--轮询、事件、异步
  • 2026年最值得用的降AI率工具实测:认准这几款就够了
  • 大模型函数调用(Function Calling)
  • 简单中文分词工具scws的安装和使用方法
  • 2026年深圳4G监控品牌优选:海康威视与大华权威评测与推荐
  • Flink知识点(一)|Flink中的双流关联
  • TCL发布会解析:Q9M Pro领衔,T7M系列双星登场,163吋Micro LED双曜压轴
  • 森林防火系统早期烟雾识别的误报率控制:面向测试工程师的实战指南
  • 【LLM基础】6. LLM 推理时的温度值、top_p、top_k等采样算法原理
  • 『NAS』将NAS变成单词收割机-QwertyLearner
  • 基于深度学习的表格识别技术:通过多模态预处理、神经网络分析和高精度OCR识别,实现复杂银行流水的自动化解析
  • 【第10篇】Mamba 100篇合集 · 从入门到天花板
  • 少走弯路:10个降AI率网站开源免费测评与推荐
  • java面试题总结2
  • LeetCode 1727.重新排列后的最大子矩阵:枚举矩形底边是哪一行 + 排序
  • 2026年塑料瓶粉碎机厂家实力榜TOP3,谁是行业领头羊?