Android Audio-FW
1、概述
1.1、音频框架分类
Android Audio Framework 分为 Audio Application Framework (Java Framework) 与 Audio Native Framework.
Audio Application Framework
主要实现音频焦点管理、通话模式控制、音频设备信息和连接状态管理、音量管理、与其他模块(input
系统、蓝牙等)交互等功能,并为应用提供使用音频各功能的 API 接口。
代码路径:
-
/frameworks/base/media/java/android/media/
-
/frameworks/base/services/core/java/com/android/server/audio/
Audio Native Framework
主要实现播放录制框架、音量控制、音频策略控制、设备管理、数据传输、数据处理、音效处理、与
HAL 层进行交互等。
代码路径:
- /frameworks/av/media/
- /frameworks/av/services/audiopolicy/
- /frameworks/av/services/audiopolicy
1.2、软件框架图

2、软件流程
2.1、播放流程
Android FW 音频播放流程如图所示

这张图展示的是 Android Audio Framework 中不同业务场景下的音频输出流分流机制(Output Flag Stream Routing)。
8种核心音频通道含义与作用详解
| 通道名称 (Output Flag) | 通道代表的含义与核心作用 |
|---|---|
PRIMARY (主音频通道) |
系统的通用默认通路。 承担系统绝大多数未指定特殊属性的普通音频播放。 |
FAST (极速低延迟通道) |
超低延迟的渲染通路 。 跳过繁重的重采样和音效处理,使用极小的 Buffer 和专有的 FastMixer 线程快速写入硬件。 |
DEEP_BUFFER (深缓冲区/省电通道) |
大缓存的低功耗播放通路。 系统分配数倍于普通通道的环形缓冲区,减少 CPU 被唤醒喂数据的频率。 |
VOIP_RX (网络语音通话下行通道) |
专为双向实时语音通话设计的下行通路。 数据送往底层时,系统硬件 DSP 会自动挂载针对人声通信特化的处理算法。 |
INCALL_MUSIC (通话中背景音注入通道) |
在移动电话(蜂窝网络通话)接通时,向通话链路混入音频的特殊通路。 |
GAPLESS_OFFLOAD (DSP 硬解码无缝直通通道) |
压缩音频(MP3/AAC)透传通路。 CPU 不做软件解码,直接将压缩字节流推给音频芯片(Audio DSP)进行硬件解码,且支持多曲目间无缝重放(Gapless)。 |
DIRECT (音频直通/高解析度通道) |
绕过 AudioFlinger 混音引擎(AudioMixer)的直接输出通路。 把原始 PCM 数据原封不动地直接推送给 Audio HAL 和 DAC 处理。 |
SPATIALIZER(空间音频/3D 环绕声通道) |
空间音频与头部追踪渲染通路。 专门用来处理多声道(5.1/7.1)或带有头部姿态跟踪元数据的 3D 环绕音效算法。 |
以下表格是APP层通过AudioAttributes配置来调用到具体的 OuptputFlag
| 底层 Output Flag | 核心应用场景 | App 层 AudioAttributes 配置 | App 层 AudioFormat & 性能模式配置 | AudioFlinger 对应线程 |
|---|---|---|---|---|
PRIMARY |
普通系统音、常规应用音频 | USAGE_MEDIA CONTENT_TYPE_UNKNOWN |
44.1k/48kHz, STEREO, PCM_16BIT PERFORMANCE_MODE_NONE |
MixerThread (primary output) |
FAST |
游戏音效、按键音、实时对讲 | USAGE_GAME CONTENT_TYPE_SONIFICATION FLAG_LOW_LATENCY |
匹配硬件 Native 采样率 (48kHz) PERFORMANCE_MODE_LOW_LATENCY |
MixerThread (fast output) |
DEEP_BUFFER |
后台音乐播放、听书、播客 | USAGE_MEDIA CONTENT_TYPE_MUSIC 或 SPEECH |
44.1k/48kHz, STEREO, PCM_16BIT PERFORMANCE_MODE_POWER_SAVING |
MixerThread (deep_buffer) |
VOIP_RX |
微信语音/视频通话、网络电话下行 | USAGE_VOICE_COMMUNICATION CONTENT_TYPE_SPEECH |
16kHz/48kHz, MONO (单声道) PERFORMANCE_MODE_NONE |
MixerThread (voip_rx) |
INCALL_MUSIC |
通话中播放背景音乐/录音 | USAGE_VOICE_COMMUNICATION_SIGNALLING CONTENT_TYPE_MUSIC |
常规 PCM 参数(需结合系统 Mode / 权限) | MixerThread (incall_music) |
GAPLESS_OFFLOAD |
MP3/AAC 硬解码省电无缝播放 | USAGE_MEDIA CONTENT_TYPE_MUSIC |
ENCODING_MP3 / ENCODING_AAC_LC setOffloadedPlayback(true) |
OffloadThread (compressed_offload) |
DIRECT |
Hi-Res 无损音频直通 | USAGE_MEDIA CONTENT_TYPE_MUSIC |
192kHz 超高采样率 / ENCODING_PCM_FLOAT PERFORMANCE_MODE_NONE |
DirectOutputThread (direct output) |
SPATIALIZER |
电影 3D 环绕声 / 空间音频 | USAGE_MEDIA CONTENT_TYPE_MOVIE |
CHANNEL_OUT_5point1 (多声道) |
SpatializerThread (Spatializer output) |
APP层调用接口示例:
java
// =========================================================================================
// 1. PRIMARY (主输出通道 - 普通音效)
// =========================================================================================
AudioTrack primaryTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(48000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build())
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_NONE)
.build();
// =========================================================================================
// 2. FAST (低延迟通道 - 游戏/按键音)
// =========================================================================================
AudioTrack fastTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setFlags(AudioAttributes.FLAG_LOW_LATENCY) // 声明低延迟 Flag
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(48000) // 匹配硬件 Native 采样率
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build())
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY) // 核心:触发 FastMixer
.build();
// =========================================================================================
// 3. DEEP_BUFFER (深缓冲区通道 - 后台长音乐播放省电)
// =========================================================================================
AudioTrack deepBufferTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build())
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_POWER_SAVING) // 核心:请求大缓存省电
.build();
// =========================================================================================
// 4. VOIP_RX (网络通话下行通道)
// =========================================================================================
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
AudioTrack voipTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(16000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build()) // 常用单声道
.build();
// =========================================================================================
// 5. INCALL_MUSIC (通话背景音注入通道)
// =========================================================================================
AudioTrack incallTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(8000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build())
.build();
// =========================================================================================
// 6. GAPLESS_OFFLOAD (DSP 硬解码直通无缝通道)
// =========================================================================================
AudioFormat mp3Format = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_MP3) // 核心:非 PCM 数据格式
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build();
AudioAttributes mediaAttr = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
if (AudioManager.isOffloadedPlaybackSupported(mp3Format, mediaAttr)) {
AudioTrack offloadTrack = new AudioTrack.Builder()
.setAudioAttributes(mediaAttr)
.setAudioFormat(mp3Format)
.setOffloadedPlayback(true) // 核心:开启 Offload 直通
.build();
}
// =========================================================================================
// 7. DIRECT (Hi-Res 高解析度无损直通通道)
// =========================================================================================
AudioTrack directTrack = new AudioTrack.Builder()
.setAudioAttributes(mediaAttr)
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(192000) // 核心:192kHz 采样率超出常规 Mixer 能力
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO).build())
.build();
// =========================================================================================
// 8. SPATIALIZER (空间音频 3D 环绕通道)
// =========================================================================================
AudioTrack spatialTrack = new AudioTrack.Builder()
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build())
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(48000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_5point1).build()) // 核心:多声道
.build();
2.2、录音流程

核心音频录制通道详细拆解与 App 层调用对照表
| 底层 Input Flag (AUDIO_INPUT_FLAG_*) | 核心应用场景与功能 | App 层 AudioSource 与配置参数 | AudioFlinger 对应线程与特点 |
|---|---|---|---|
NONE (PRIMARY) |
普通常规录音 (如系统录音机、微信普通语音消息) | AudioSource.MIC / DEFAULT PERFORMANCE_MODE_NONE |
RecordThread (primary input) 通用基础录音通道,使用标准缓冲区。 |
FAST |
低延迟录音 (如实时 K 歌返听、实时语音识别) | AudioSource.MIC / VOICE_RECOGNITION PERFORMANCE_MODE_LOW_LATENCY |
RecordThread (fast input) 配合 FastCapture 极速采集线程与小 Buffer,显著降低采集延迟。 |
HW_HOTWORD |
硬件语音唤醒 (如"小爱同学"、"Hey Google") | AudioSource.HOTWORD (需系统级权限 CAPTURE_AUDIO_HOTWORD) |
RecordThread (hotword input) 连接低功耗硬件 DSP (SoundTrigger),实现锁屏/休眠状态下的极低功耗唤醒词监听。 |
VOIP_TX / VOIP_RX |
网络电话上行麦克风 (如微信语音/视频通话采音) | AudioSource.VOICE_COMMUNICATION CONTENT_TYPE_SPEECH |
RecordThread (voip_tx) 开启底层硬件 AEC(回音消除)、NS(降噪)和 AGC(自动增益控制)。 |
NONE (FM Record) |
FM 收音机录音 (内部音频内录/Loopback) | AudioSource.RADIO_TUNER / REMOTE_SUBMIX (系统级硬件专用) |
RecordThread (fm_record) 直接将 FM 芯片或系统内录数据回传,不经过外部麦克风。 |
DIRECT |
原始 PCM 直通录音 / Hi-Res (如专业音频采集、非重采样录音) | AudioSource.UNPROCESSED 使用高采样率 (如 96k/192kHz) |
RecordThread (direct input) |
APP层调用接口示例:
java
// =========================================================================================
// 1. PRIMARY / NONE (标准通用录音 - 如普通录音机)
// =========================================================================================
AudioRecord primaryRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.MIC)
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(44100)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_STEREO).build())
.setBufferSizeInBytes(minBufferSize)
.setPerformanceMode(AudioRecord.PERFORMANCE_MODE_NONE)
.build();
// =========================================================================================
// 2. FAST (低延迟录音 - 如实时 K 歌返听/实时语音识别)
// =========================================================================================
AudioRecord fastRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION) // 语音识别源
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(48000) // 必须匹配硬件原生采样率
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
.setBufferSizeInBytes(minBufferSize)
.setPerformanceMode(AudioRecord.PERFORMANCE_MODE_LOW_LATENCY) // 核心:触发 FastCapture
.build();
// =========================================================================================
// 3. HW_HOTWORD (硬件语音唤醒 - 系统级语音助手专享)
// =========================================================================================
// 提示:需要 android.permission.CAPTURE_AUDIO_HOTWORD 权限
AudioRecord hotwordRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.HOTWORD) // 核心:触发 HW_HOTWORD
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(16000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
.build();
// =========================================================================================
// 4. VOIP_TX (网络电话麦克风录音 - 带硬件降噪与消回音)
// =========================================================================================
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); // 先切入通话模式
AudioRecord voipRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION) // 核心:触发 VOIP_TX
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(16000) // 16k 或 48k
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
.build();
// =========================================================================================
// 5. DIRECT / UNPROCESSED (无算法干预的原始麦克风数据采集)
// =========================================================================================
AudioRecord unprocessedRecord = new AudioRecord.Builder()
.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED) // 核心:绕过所有算法
.setAudioFormat(new AudioFormat.Builder()
.setSampleRate(48000)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_IN_STEREO).build())
.build();
2.3、流程调用
AudioTrack 调用 UML

函数对照表
| 阶段 | 关键函数 | 所在文件 | 作用 |
|---|---|---|---|
| Java 构造 | AudioTrack() → native_setup() |
AudioTrack.java |
传入 attr/format/flags |
| JNI | android_media_AudioTrack_setup() |
android_media_AudioTrack.cpp |
创建 Native AudioTrack,调 set() |
| Native 入口 | AudioTrack::set() |
AudioTrack.cpp |
参数校验、建 callback 线程 |
| Native 核心 | AudioTrack::createTrack_l() |
AudioTrack.cpp:1961 |
组装 input,发起 Binder |
| AF 入口 | AudioFlinger::createTrack() |
AudioFlinger.cpp:1148 |
调 APM 拿 output,再建 Track |
| 路由查询 | AudioSystem::getOutputForAttr() |
AudioSystem.cpp:1501 |
转 APS Binder |
| 策略决策 | AudioPolicyManager::getOutputForAttr() |
AudioPolicyManager.cpp:1845 |
选 output、调 flags、分配 portId |
| mixPort 匹配 | getProfileForOutput() / selectOutput() |
AudioPolicyManager.cpp |
匹配 XML <mixPort> → IOProfile |
| AF 建 Track | PlaybackThread::createTrack_l() |
Threads.cpp |
在选定 thread 上创建 Track |
| 播放启动 | startOutput(portId) |
后续 play 时 | 不在构造路径内 |
APM 内部从 getOutputForAttr() 到 selectOutput() 的细化图
1、总览流程(getOutputForAttr → getOutputForAttrInt)
#mermaid-svg-4ydXvkXSjGOAlWiD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4ydXvkXSjGOAlWiD .error-icon{fill:#552222;}#mermaid-svg-4ydXvkXSjGOAlWiD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4ydXvkXSjGOAlWiD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4ydXvkXSjGOAlWiD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4ydXvkXSjGOAlWiD .marker.cross{stroke:#333333;}#mermaid-svg-4ydXvkXSjGOAlWiD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4ydXvkXSjGOAlWiD p{margin:0;}#mermaid-svg-4ydXvkXSjGOAlWiD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster-label text{fill:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster-label span{color:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster-label span p{background-color:transparent;}#mermaid-svg-4ydXvkXSjGOAlWiD .label text,#mermaid-svg-4ydXvkXSjGOAlWiD span{fill:#333;color:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD .node rect,#mermaid-svg-4ydXvkXSjGOAlWiD .node circle,#mermaid-svg-4ydXvkXSjGOAlWiD .node ellipse,#mermaid-svg-4ydXvkXSjGOAlWiD .node polygon,#mermaid-svg-4ydXvkXSjGOAlWiD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4ydXvkXSjGOAlWiD .rough-node .label text,#mermaid-svg-4ydXvkXSjGOAlWiD .node .label text,#mermaid-svg-4ydXvkXSjGOAlWiD .image-shape .label,#mermaid-svg-4ydXvkXSjGOAlWiD .icon-shape .label{text-anchor:middle;}#mermaid-svg-4ydXvkXSjGOAlWiD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4ydXvkXSjGOAlWiD .rough-node .label,#mermaid-svg-4ydXvkXSjGOAlWiD .node .label,#mermaid-svg-4ydXvkXSjGOAlWiD .image-shape .label,#mermaid-svg-4ydXvkXSjGOAlWiD .icon-shape .label{text-align:center;}#mermaid-svg-4ydXvkXSjGOAlWiD .node.clickable{cursor:pointer;}#mermaid-svg-4ydXvkXSjGOAlWiD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4ydXvkXSjGOAlWiD .arrowheadPath{fill:#333333;}#mermaid-svg-4ydXvkXSjGOAlWiD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4ydXvkXSjGOAlWiD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4ydXvkXSjGOAlWiD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ydXvkXSjGOAlWiD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4ydXvkXSjGOAlWiD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ydXvkXSjGOAlWiD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster text{fill:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD .cluster span{color:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4ydXvkXSjGOAlWiD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4ydXvkXSjGOAlWiD rect.text{fill:none;stroke-width:0;}#mermaid-svg-4ydXvkXSjGOAlWiD .icon-shape,#mermaid-svg-4ydXvkXSjGOAlWiD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ydXvkXSjGOAlWiD .icon-shape p,#mermaid-svg-4ydXvkXSjGOAlWiD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4ydXvkXSjGOAlWiD .icon-shape .label rect,#mermaid-svg-4ydXvkXSjGOAlWiD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ydXvkXSjGOAlWiD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4ydXvkXSjGOAlWiD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4ydXvkXSjGOAlWiD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-4ydXvkXSjGOAlWiD .process>*{fill:#e1f5fe!important;stroke:#01579b!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .process span{fill:#e1f5fe!important;stroke:#01579b!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .decision>*{fill:#fff3e0!important;stroke:#e65100!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .decision span{fill:#fff3e0!important;stroke:#e65100!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .error>*{fill:#ffcdd2!important;stroke:#b71c1c!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .error span{fill:#ffcdd2!important;stroke:#b71c1c!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .success>*{fill:#c8e6c9!important;stroke:#1b5e20!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .success span{fill:#c8e6c9!important;stroke:#1b5e20!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .returnNode>*{fill:#f3e5f5!important;stroke:#4a148c!important;}#mermaid-svg-4ydXvkXSjGOAlWiD .returnNode span{fill:#f3e5f5!important;stroke:#4a148c!important;} 否
是
是
是
否
否
是且无 mix
否
是
是
否
否
是
否
getOutputForAttr()
AudioPolicyManager.cpp:1845
portId == NONE?
返回 INVALID_OPERATION
getOutputForAttrInt()
:1559
① getAudioAttributes()
attr → resultAttr + streamType
② mPolicyMixes.getOutputForAttr()
动态策略 / loopback 匹配
usePrimaryOutputFromPolicyMixes?
尝试 openDirectOutput()
绑定 primaryMix->output
policyDesc 有效?
返回 outputId
outputType=LEGACY/VIRTUAL
usage == VIRTUAL_SOURCE?
返回 BAD_VALUE
③ mEngine->getOutputDevicesForAttributes()
选出目标 DeviceVector
补充 flags
HW_AV_SYNC / INCALL_MUSIC 等
MSD 设备可用?
getOutputForDevices(msdDevices)
成功?
返回 MSD output
④ getOutputForDevices(devices)
核心 output + flags 决策
output == NONE?
getProfilesForDevices() 修正 config
返回 INVALID_OPERATION
填充 selectedDeviceIds
设置 outputType
回到 getOutputForAttr()
收集 secondaryOutputs
分配 portId = getNextUniqueId()
outputDesc->addClient(TrackClientDescriptor)
返回 outputId + portId + volume/muted
2、getOutputForDevices 内部(mixPort 匹配入口)
#mermaid-svg-DWzuvwgniOik7Hkm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DWzuvwgniOik7Hkm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DWzuvwgniOik7Hkm .error-icon{fill:#552222;}#mermaid-svg-DWzuvwgniOik7Hkm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DWzuvwgniOik7Hkm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DWzuvwgniOik7Hkm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DWzuvwgniOik7Hkm .marker.cross{stroke:#333333;}#mermaid-svg-DWzuvwgniOik7Hkm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DWzuvwgniOik7Hkm p{margin:0;}#mermaid-svg-DWzuvwgniOik7Hkm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DWzuvwgniOik7Hkm .cluster-label text{fill:#333;}#mermaid-svg-DWzuvwgniOik7Hkm .cluster-label span{color:#333;}#mermaid-svg-DWzuvwgniOik7Hkm .cluster-label span p{background-color:transparent;}#mermaid-svg-DWzuvwgniOik7Hkm .label text,#mermaid-svg-DWzuvwgniOik7Hkm span{fill:#333;color:#333;}#mermaid-svg-DWzuvwgniOik7Hkm .node rect,#mermaid-svg-DWzuvwgniOik7Hkm .node circle,#mermaid-svg-DWzuvwgniOik7Hkm .node ellipse,#mermaid-svg-DWzuvwgniOik7Hkm .node polygon,#mermaid-svg-DWzuvwgniOik7Hkm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DWzuvwgniOik7Hkm .rough-node .label text,#mermaid-svg-DWzuvwgniOik7Hkm .node .label text,#mermaid-svg-DWzuvwgniOik7Hkm .image-shape .label,#mermaid-svg-DWzuvwgniOik7Hkm .icon-shape .label{text-anchor:middle;}#mermaid-svg-DWzuvwgniOik7Hkm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DWzuvwgniOik7Hkm .rough-node .label,#mermaid-svg-DWzuvwgniOik7Hkm .node .label,#mermaid-svg-DWzuvwgniOik7Hkm .image-shape .label,#mermaid-svg-DWzuvwgniOik7Hkm .icon-shape .label{text-align:center;}#mermaid-svg-DWzuvwgniOik7Hkm .node.clickable{cursor:pointer;}#mermaid-svg-DWzuvwgniOik7Hkm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DWzuvwgniOik7Hkm .arrowheadPath{fill:#333333;}#mermaid-svg-DWzuvwgniOik7Hkm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DWzuvwgniOik7Hkm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DWzuvwgniOik7Hkm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DWzuvwgniOik7Hkm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DWzuvwgniOik7Hkm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DWzuvwgniOik7Hkm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DWzuvwgniOik7Hkm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DWzuvwgniOik7Hkm .cluster text{fill:#333;}#mermaid-svg-DWzuvwgniOik7Hkm .cluster span{color:#333;}#mermaid-svg-DWzuvwgniOik7Hkm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DWzuvwgniOik7Hkm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DWzuvwgniOik7Hkm rect.text{fill:none;stroke-width:0;}#mermaid-svg-DWzuvwgniOik7Hkm .icon-shape,#mermaid-svg-DWzuvwgniOik7Hkm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DWzuvwgniOik7Hkm .icon-shape p,#mermaid-svg-DWzuvwgniOik7Hkm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DWzuvwgniOik7Hkm .icon-shape .label rect,#mermaid-svg-DWzuvwgniOik7Hkm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DWzuvwgniOik7Hkm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DWzuvwgniOik7Hkm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DWzuvwgniOik7Hkm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-DWzuvwgniOik7Hkm .process>*{fill:#e3f2fd!important;stroke:#1565c0!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .process span{fill:#e3f2fd!important;stroke:#1565c0!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .decision>*{fill:#fff3e0!important;stroke:#e65100!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .decision span{fill:#fff3e0!important;stroke:#e65100!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .success>*{fill:#e8f5e9!important;stroke:#2e7d32!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .success span{fill:#e8f5e9!important;stroke:#2e7d32!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .failure>*{fill:#ffebee!important;stroke:#c62828!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .failure span{fill:#ffebee!important;stroke:#c62828!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .returnNode>*{fill:#f3e5f5!important;stroke:#6a1b9a!important;stroke-width:2px!important;}#mermaid-svg-DWzuvwgniOik7Hkm .returnNode span{fill:#f3e5f5!important;stroke:#6a1b9a!important;stroke-width:2px!important;} 是
否
是
否 NAME_NOT_FOUND
是
否
否
是
是
否
getOutputForDevices()
:2072
Flags 预处理
(修正 flags)
① OFFLOAD / HW_AV_SYNC → 加 DIRECT
② 非 MUSIC → 去掉 DEEP_BUFFER
③ MUSIC + flag=NONE → 可能设 DEEP_BUFFER
④ TTS → AUDIO_OUTPUT_FLAG_TTS
⑤ VOIP_RX / ULTRASOUND 等处理
Spatializer 可用
且非 direct/offload?
返回 mSpatializerOutput
尝试 openDirectOutput()
getProfileForOutput()
遍历 HwModule 的 IOProfile(mixPort)
IOProfile::getCompatibilityScore()
检查 device / flags / format / rate / channels
找到 direct profile?
open 并返回 direct outputId
flags 含 HW_AV_SYNC
或 MMAP_NOIRQ?
返回 NONE
不能 fallback 混音
format 是 PCM?
getOutputsForDevices()
过滤已 open 且支持目标设备的 output
有 PreferredMixerAttributes?
优先选/开匹配 profile 的 output
selectOutput() 打分选最优
返回 outputId
3、selectOutput 打分逻辑
#mermaid-svg-b3lPOJGhV1N5scMB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b3lPOJGhV1N5scMB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b3lPOJGhV1N5scMB .error-icon{fill:#552222;}#mermaid-svg-b3lPOJGhV1N5scMB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b3lPOJGhV1N5scMB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b3lPOJGhV1N5scMB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b3lPOJGhV1N5scMB .marker.cross{stroke:#333333;}#mermaid-svg-b3lPOJGhV1N5scMB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b3lPOJGhV1N5scMB p{margin:0;}#mermaid-svg-b3lPOJGhV1N5scMB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b3lPOJGhV1N5scMB .cluster-label text{fill:#333;}#mermaid-svg-b3lPOJGhV1N5scMB .cluster-label span{color:#333;}#mermaid-svg-b3lPOJGhV1N5scMB .cluster-label span p{background-color:transparent;}#mermaid-svg-b3lPOJGhV1N5scMB .label text,#mermaid-svg-b3lPOJGhV1N5scMB span{fill:#333;color:#333;}#mermaid-svg-b3lPOJGhV1N5scMB .node rect,#mermaid-svg-b3lPOJGhV1N5scMB .node circle,#mermaid-svg-b3lPOJGhV1N5scMB .node ellipse,#mermaid-svg-b3lPOJGhV1N5scMB .node polygon,#mermaid-svg-b3lPOJGhV1N5scMB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b3lPOJGhV1N5scMB .rough-node .label text,#mermaid-svg-b3lPOJGhV1N5scMB .node .label text,#mermaid-svg-b3lPOJGhV1N5scMB .image-shape .label,#mermaid-svg-b3lPOJGhV1N5scMB .icon-shape .label{text-anchor:middle;}#mermaid-svg-b3lPOJGhV1N5scMB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b3lPOJGhV1N5scMB .rough-node .label,#mermaid-svg-b3lPOJGhV1N5scMB .node .label,#mermaid-svg-b3lPOJGhV1N5scMB .image-shape .label,#mermaid-svg-b3lPOJGhV1N5scMB .icon-shape .label{text-align:center;}#mermaid-svg-b3lPOJGhV1N5scMB .node.clickable{cursor:pointer;}#mermaid-svg-b3lPOJGhV1N5scMB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b3lPOJGhV1N5scMB .arrowheadPath{fill:#333333;}#mermaid-svg-b3lPOJGhV1N5scMB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b3lPOJGhV1N5scMB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b3lPOJGhV1N5scMB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b3lPOJGhV1N5scMB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b3lPOJGhV1N5scMB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b3lPOJGhV1N5scMB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b3lPOJGhV1N5scMB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b3lPOJGhV1N5scMB .cluster text{fill:#333;}#mermaid-svg-b3lPOJGhV1N5scMB .cluster span{color:#333;}#mermaid-svg-b3lPOJGhV1N5scMB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b3lPOJGhV1N5scMB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b3lPOJGhV1N5scMB rect.text{fill:none;stroke-width:0;}#mermaid-svg-b3lPOJGhV1N5scMB .icon-shape,#mermaid-svg-b3lPOJGhV1N5scMB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b3lPOJGhV1N5scMB .icon-shape p,#mermaid-svg-b3lPOJGhV1N5scMB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b3lPOJGhV1N5scMB .icon-shape .label rect,#mermaid-svg-b3lPOJGhV1N5scMB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b3lPOJGhV1N5scMB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b3lPOJGhV1N5scMB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b3lPOJGhV1N5scMB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-b3lPOJGhV1N5scMB .process>*{fill:#e3f2fd!important;stroke:#1565c0!important;stroke-width:2px!important;}#mermaid-svg-b3lPOJGhV1N5scMB .process span{fill:#e3f2fd!important;stroke:#1565c0!important;stroke-width:2px!important;}#mermaid-svg-b3lPOJGhV1N5scMB .decision>*{fill:#fff3e0!important;stroke:#e65100!important;stroke-width:2px!important;}#mermaid-svg-b3lPOJGhV1N5scMB .decision span{fill:#fff3e0!important;stroke:#e65100!important;stroke-width:2px!important;}#mermaid-svg-b3lPOJGhV1N5scMB .skip>*{fill:#f5f5f5!important;stroke:#9e9e9e!important;stroke-width:1px!important;color:#757575!important;}#mermaid-svg-b3lPOJGhV1N5scMB .skip span{fill:#f5f5f5!important;stroke:#9e9e9e!important;stroke-width:1px!important;color:#757575!important;}#mermaid-svg-b3lPOJGhV1N5scMB .skip tspan{fill:#757575!important;}#mermaid-svg-b3lPOJGhV1N5scMB .success>*{fill:#e8f5e9!important;stroke:#2e7d32!important;stroke-width:2px!important;}#mermaid-svg-b3lPOJGhV1N5scMB .success span{fill:#e8f5e9!important;stroke:#2e7d32!important;stroke-width:2px!important;} 是
否
是
否
是
否
是
否
selectOutput(outputs, flags, format,
channelMask, sampleRate, session)
:2534
遍历每个 candidate output
是 duplicated output?
跳过该 output
含排除 flags?
DIRECT / HW_AV_SYNC / MMAP_NOIRQ
计算 8 维 matchCriteria
0 Haptic 通道匹配
1 Functional flags 匹配
VOIP_RX / INCALL_MUSIC / TTS
DIRECT_PCM / ULTRASOUND / SPATIALIZER
2 精确 channel mask 匹配
3 更高 channel count
4 更高 sample rate
(当请求 rate > 默认值)
5 Performance flags 匹配
FAST / DEEP_BUFFER / RAW / SYNC
6 bit depth 最接近
7 是否 primary output
currentCriteria
优于 bestCriteria?
更新 bestOutput
保持当前 best
还有下一个 output?
返回 bestOutput
或 outputs0 / NONE
4、IOProfile(mixPort)如何参与匹配
#mermaid-svg-p2gzncfFVpNKbELB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-p2gzncfFVpNKbELB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-p2gzncfFVpNKbELB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-p2gzncfFVpNKbELB .error-icon{fill:#552222;}#mermaid-svg-p2gzncfFVpNKbELB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-p2gzncfFVpNKbELB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-p2gzncfFVpNKbELB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-p2gzncfFVpNKbELB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-p2gzncfFVpNKbELB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-p2gzncfFVpNKbELB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-p2gzncfFVpNKbELB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-p2gzncfFVpNKbELB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-p2gzncfFVpNKbELB .marker.cross{stroke:#333333;}#mermaid-svg-p2gzncfFVpNKbELB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-p2gzncfFVpNKbELB p{margin:0;}#mermaid-svg-p2gzncfFVpNKbELB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-p2gzncfFVpNKbELB .cluster-label text{fill:#333;}#mermaid-svg-p2gzncfFVpNKbELB .cluster-label span{color:#333;}#mermaid-svg-p2gzncfFVpNKbELB .cluster-label span p{background-color:transparent;}#mermaid-svg-p2gzncfFVpNKbELB .label text,#mermaid-svg-p2gzncfFVpNKbELB span{fill:#333;color:#333;}#mermaid-svg-p2gzncfFVpNKbELB .node rect,#mermaid-svg-p2gzncfFVpNKbELB .node circle,#mermaid-svg-p2gzncfFVpNKbELB .node ellipse,#mermaid-svg-p2gzncfFVpNKbELB .node polygon,#mermaid-svg-p2gzncfFVpNKbELB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-p2gzncfFVpNKbELB .rough-node .label text,#mermaid-svg-p2gzncfFVpNKbELB .node .label text,#mermaid-svg-p2gzncfFVpNKbELB .image-shape .label,#mermaid-svg-p2gzncfFVpNKbELB .icon-shape .label{text-anchor:middle;}#mermaid-svg-p2gzncfFVpNKbELB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-p2gzncfFVpNKbELB .rough-node .label,#mermaid-svg-p2gzncfFVpNKbELB .node .label,#mermaid-svg-p2gzncfFVpNKbELB .image-shape .label,#mermaid-svg-p2gzncfFVpNKbELB .icon-shape .label{text-align:center;}#mermaid-svg-p2gzncfFVpNKbELB .node.clickable{cursor:pointer;}#mermaid-svg-p2gzncfFVpNKbELB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-p2gzncfFVpNKbELB .arrowheadPath{fill:#333333;}#mermaid-svg-p2gzncfFVpNKbELB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-p2gzncfFVpNKbELB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-p2gzncfFVpNKbELB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p2gzncfFVpNKbELB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-p2gzncfFVpNKbELB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p2gzncfFVpNKbELB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-p2gzncfFVpNKbELB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-p2gzncfFVpNKbELB .cluster text{fill:#333;}#mermaid-svg-p2gzncfFVpNKbELB .cluster span{color:#333;}#mermaid-svg-p2gzncfFVpNKbELB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-p2gzncfFVpNKbELB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-p2gzncfFVpNKbELB rect.text{fill:none;stroke-width:0;}#mermaid-svg-p2gzncfFVpNKbELB .icon-shape,#mermaid-svg-p2gzncfFVpNKbELB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-p2gzncfFVpNKbELB .icon-shape p,#mermaid-svg-p2gzncfFVpNKbELB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-p2gzncfFVpNKbELB .icon-shape .label rect,#mermaid-svg-p2gzncfFVpNKbELB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-p2gzncfFVpNKbELB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-p2gzncfFVpNKbELB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-p2gzncfFVpNKbELB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-p2gzncfFVpNKbELB .xmlStyle>*{fill:#f5f5f5!important;stroke:#616161!important;}#mermaid-svg-p2gzncfFVpNKbELB .xmlStyle span{fill:#f5f5f5!important;stroke:#616161!important;}#mermaid-svg-p2gzncfFVpNKbELB .directStyle>*{fill:#fff3e0!important;stroke:#e65100!important;}#mermaid-svg-p2gzncfFVpNKbELB .directStyle span{fill:#fff3e0!important;stroke:#e65100!important;}#mermaid-svg-p2gzncfFVpNKbELB .pcmStyle>*{fill:#e3f2fd!important;stroke:#1565c0!important;}#mermaid-svg-p2gzncfFVpNKbELB .pcmStyle span{fill:#e3f2fd!important;stroke:#1565c0!important;}#mermaid-svg-p2gzncfFVpNKbELB .ioStyle>*{fill:#f3e5f5!important;stroke:#6a1b9a!important;}#mermaid-svg-p2gzncfFVpNKbELB .ioStyle span{fill:#f3e5f5!important;stroke:#6a1b9a!important;} PCM 混音路径
Direct 直出路径
静态配置 XML
audio_policy_configuration.xml
IOProfile (AUDIO_PORT_TYPE_MIX)
openDirectOutput()
getProfileForOutput()
getCompatibilityScore()
areAllDevicesSupported()
getFlagsCompatibleScore()
checkExactAudioProfile()
Output 已 open (SwAudioOutputDescriptor)
desc->mProfile 即 mixPort
getOutputsForDevices 按 device 过滤
selectOutput 按 flags 打分
5、时序视角(APM 内部自调用)
selectOutput getOutputsForDevices IOProfile getProfileForOutput openDirectOutput getOutputForDevices AudioEngine PolicyMixes getOutputForAttrInt getOutputForAttr selectOutput getOutputsForDevices IOProfile getProfileForOutput openDirectOutput getOutputForDevices AudioEngine PolicyMixes getOutputForAttrInt getOutputForAttr #mermaid-svg-XHCJrsmgJ9SVSToE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XHCJrsmgJ9SVSToE .error-icon{fill:#552222;}#mermaid-svg-XHCJrsmgJ9SVSToE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XHCJrsmgJ9SVSToE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XHCJrsmgJ9SVSToE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XHCJrsmgJ9SVSToE .marker.cross{stroke:#333333;}#mermaid-svg-XHCJrsmgJ9SVSToE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XHCJrsmgJ9SVSToE p{margin:0;}#mermaid-svg-XHCJrsmgJ9SVSToE .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XHCJrsmgJ9SVSToE text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XHCJrsmgJ9SVSToE .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-XHCJrsmgJ9SVSToE .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-XHCJrsmgJ9SVSToE #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-XHCJrsmgJ9SVSToE .sequenceNumber{fill:white;}#mermaid-svg-XHCJrsmgJ9SVSToE #sequencenumber{fill:#333;}#mermaid-svg-XHCJrsmgJ9SVSToE #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-XHCJrsmgJ9SVSToE .messageText{fill:#333;stroke:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XHCJrsmgJ9SVSToE .labelText,#mermaid-svg-XHCJrsmgJ9SVSToE .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .loopText,#mermaid-svg-XHCJrsmgJ9SVSToE .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-XHCJrsmgJ9SVSToE .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-XHCJrsmgJ9SVSToE .noteText,#mermaid-svg-XHCJrsmgJ9SVSToE .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-XHCJrsmgJ9SVSToE .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XHCJrsmgJ9SVSToE .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XHCJrsmgJ9SVSToE .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-XHCJrsmgJ9SVSToE .actorPopupMenu{position:absolute;}#mermaid-svg-XHCJrsmgJ9SVSToE .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-XHCJrsmgJ9SVSToE .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-XHCJrsmgJ9SVSToE .actor-man circle,#mermaid-svg-XHCJrsmgJ9SVSToE line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-XHCJrsmgJ9SVSToE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个candidate altdirect 成功fallback PCM altPolicyMix 命中 primary常规路径 路由决策getOutputForAttr()primaryMix 且 usePrimary=true可选 openDirectOutputoutputId未命中 或 secondary onlygetOutputDevicesForAttributes()DeviceVectordevices + flags + config调整 flags尝试 directgetProfileForOutput()getCompatibilityScore()outputId过滤已 open outputsSortedVectorflags/format/channels/rate8 维打分比较best outputIdoutputIdoutputId + selectedDeviceIdsportId = getNextUniqueId()addClient(TrackClientDescriptor)
函数对照表
| 函数 | 输入 | 输出 | 核心作用 |
|---|---|---|---|
getOutputForAttrInt |
attr, flags, config, devices | outputId, flags(改), devices | 总调度:PolicyMix → Engine → getOutputForDevices |
getOutputForDevices |
DeviceVector, flags, config | outputId, flags(改) | 调 flags、试 direct、否则 PCM 混音 |
getProfileForOutput |
devices, format, rate, flags | sp<IOProfile> |
在 XML mixPort 里找兼容 profile |
getOutputsForDevices |
devices, 已 open outputs | output 列表 | 按设备能力过滤 |
selectOutput |
output 列表 + flags + config | 最优 outputId | 8 维打分选 mixPort 对应 thread |
getOutputForAttr |
同上 | + portId, volume | 包装:分配 portId、注册 client |
AudioRecord 调用UML
RecordThread (AF) AudioPolicyManager AudioPolicyService AudioSystem AudioFlinger AudioRecord.cpp (Native) android_media_AudioRecord.cpp AudioRecord.java App (MediaRecorder/AAudio/Camera/...) RecordThread (AF) AudioPolicyManager AudioPolicyService AudioSystem AudioFlinger AudioRecord.cpp (Native) android_media_AudioRecord.cpp AudioRecord.java App (MediaRecorder/AAudio/Camera/...) #mermaid-svg-2VUkEytypYh0ittI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2VUkEytypYh0ittI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2VUkEytypYh0ittI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2VUkEytypYh0ittI .error-icon{fill:#552222;}#mermaid-svg-2VUkEytypYh0ittI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2VUkEytypYh0ittI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2VUkEytypYh0ittI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2VUkEytypYh0ittI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2VUkEytypYh0ittI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2VUkEytypYh0ittI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2VUkEytypYh0ittI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2VUkEytypYh0ittI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2VUkEytypYh0ittI .marker.cross{stroke:#333333;}#mermaid-svg-2VUkEytypYh0ittI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2VUkEytypYh0ittI p{margin:0;}#mermaid-svg-2VUkEytypYh0ittI .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2VUkEytypYh0ittI text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2VUkEytypYh0ittI .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2VUkEytypYh0ittI .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-2VUkEytypYh0ittI .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2VUkEytypYh0ittI .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2VUkEytypYh0ittI #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2VUkEytypYh0ittI .sequenceNumber{fill:white;}#mermaid-svg-2VUkEytypYh0ittI #sequencenumber{fill:#333;}#mermaid-svg-2VUkEytypYh0ittI #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2VUkEytypYh0ittI .messageText{fill:#333;stroke:none;}#mermaid-svg-2VUkEytypYh0ittI .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2VUkEytypYh0ittI .labelText,#mermaid-svg-2VUkEytypYh0ittI .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2VUkEytypYh0ittI .loopText,#mermaid-svg-2VUkEytypYh0ittI .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2VUkEytypYh0ittI .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2VUkEytypYh0ittI .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2VUkEytypYh0ittI .noteText,#mermaid-svg-2VUkEytypYh0ittI .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2VUkEytypYh0ittI .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2VUkEytypYh0ittI .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2VUkEytypYh0ittI .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2VUkEytypYh0ittI .actorPopupMenu{position:absolute;}#mermaid-svg-2VUkEytypYh0ittI .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2VUkEytypYh0ittI .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2VUkEytypYh0ittI .actor-man circle,#mermaid-svg-2VUkEytypYh0ittI line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2VUkEytypYh0ittI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 参数校验、resolveSessionIdAttributionSource 封装 android_media_AudioRecord_setup() 保存 attr/format/flags/sessionId 等 校验参数、创建 RecordingActivityTracker创建 AudioRecordThread(若有 callback) 1. 校验 FAST flag 前置条件2. 组装 CreateRecordInput 进程内封装,转 AIDL/Binder AudioPolicyInterfaceImpl.cpp权限校验 + 调用 APM 核心路由决策 getInputForAttr altMMAP_NOIRQ 复用已有 inputREMOTE_SUBMIX (带 addr tag)常规路由 getInputProfile → 复用已有 input或 inputDesc->>open() 新建 HAL input 创建 RecordTrack、校验 FAST/DIRECT分配共享内存 cblk + buffer FAST 被拒绝时 retry(去掉 FAST 重走 getInputForAttr) 后续 startRecording() 时才会真正开始采集 inputDesc->>start()setClientActive + setInputDevice通知 SoundTrigger / remote submix new AudioRecord(attr/format/bufferSize/sessionId/...)1native_setup(attr, format, bufferSize, session, ...)2new AudioRecord() + set(...)3AudioRecord::set()4createRecord_l()5IAudioFlinger::createRecord() Binder IPC6AudioSystem::getInputForAttr()7IAudioPolicyService::getInputForAttr()8AudioPolicyManager::getInputForAttr()9① source 默认化 (DEFAULT → MIC)10校验 session / UID,复用 requestedInput11mPolicyMixes.getInputMixForAttr()12② mEngine->>getInputDeviceForAttributes()或 explicitRoutingDevice13③ checkPermissionForInput()14④ getInputForDevice()15⑤ 分配 portId,注册 RecordClientDescriptor16inputId, portId, selectedDeviceId, config...17GetInputForAttrResponse18inputId + portId + ...19checkRecordThread_l(inputId)20RecordThread::createRecordTrack_l()21sp<IAfRecordTrack>, frameCount, flags...22CreateRecordResponse (IAudioRecord + cblk)23保存 mAudioRecord/mPortId/mFlags/mCblk24acquireAudioSessionId()25set() 成功26native_setup 返回27AudioRecord 对象就绪 (STATE_INITIALIZED)28startRecording()29native_start()30AudioRecord::start()31IAudioRecord::start() Binder IPC32RecordThread::start()33AudioSystem::startInput(portId)34IAudioPolicyService::startInput()35AudioPolicyManager::startInput()36
AudioTrack 和 AudioRecord 关键差异
| 维度 | AudioTrack (播放) | AudioRecord (采集) |
|---|---|---|
| Policy 接口 | getOutputForAttr() |
getInputForAttr() |
| AF 接口 | createTrack() |
createRecord() |
| AF 线程 | PlaybackThread |
RecordThread |
| Track 创建 | createTrack_l() |
createRecordTrack_l() |
| 客户端描述符 | TrackClientDescriptor |
RecordClientDescriptor |
| 真正激活时机 | play() → startOutput(portId) |
startRecording() → startInput(portId) |
| 路由引擎 | getOutputDevicesForAttributes() |
getInputDeviceForAttributes() |
| 特殊路径 | Direct / Offload / MMAP | MMAP_NOIRQ 复用 / REMOTE_SUBMIX / HOTWORD |
3、FW核心组件及核心方法
3.1、AudioManager
用于管理音频输出、音量控制、音频焦点(Audio Focus)以及音频设备路由的核心系统服务。
核心api与功能分类
3.1.1、音频焦点管理 (Audio Focus)
作用:控制多应用(如导航、音乐、电话)并发发声时的优先级与混音/抢占策略。
requestAudioFocus(AudioFocusRequest focusRequest)- 作用 :向系统申请音频焦点。需配合
AudioFocusRequest.Builder构建,支持设置延迟获取(Delayed Focus)和自动降低音量(Duck)。
- 作用 :向系统申请音频焦点。需配合
abandonAudioFocusRequest(AudioFocusRequest focusRequest)- 作用:释放音频焦点,系统将自动恢复先前被抢占焦点的应用。
AudioManager.OnAudioFocusChangeListener- 接口作用:监听音频焦点变化的抽象回调接口。
- 核心方法 :
onAudioFocusChange(int focusChange)AUDIOFOCUS_GAIN:获得/恢复长期焦点(恢复正常播放)。AUDIOFOCUS_LOSS:永久失去焦点(停止播放并释放资源)。AUDIOFOCUS_LOSS_TRANSIENT:暂时失去焦点(如来电,需暂停)。AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:短暂失去焦点(允许降低音量继续播放)
3.1.2、音频设备路由与外设感知 (Device & Routing)
作用:感知蓝牙、USB、有线耳机、车载总线等设备的连接状态,并动态指定声音通道。
getDevices(int flags)- 作用:获取系统当前连接的所有音频设备。
- 常用 Flag :
GET_DEVICES_OUTPUTS(输出设备)、GET_DEVICES_INPUTS(输入设备)。
registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler)- 作用:注册音频设备热插拔监听器。
AudioDeviceCallback回调 :onAudioDevicesAdded(AudioDeviceInfo[] addedDevices):新设备接入(如插入 USB 声卡)。onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices):设备断开(用于实现"拔出耳机自动暂停")。
setCommunicationDevice(AudioDeviceInfo device)/clearCommunicationDevice()- 作用 :指定 VoIP/通话 场景下的声音路由(替代已废弃的
setSpeakerphoneOn)。
- 作用 :指定 VoIP/通话 场景下的声音路由(替代已废弃的
getCommunicationDevice()- 作用:获取当前通信流绑定的目标音频设备。
3.1.3、空间音频与硬解码直通 (Spatial Audio & Direct Audio)
作用:针对高级耳机、车载多扬声器及高保真音频流的控制。
getSpatializer()- 作用 :获取系统的
Spatializer(空间音频管理器)实例。 Spatializer关键接口 :isAvailable():判断当前硬件与连接设备是否支持空间音频。isEnabled():用户是否在系统设置中开启了空间音频。addOnSpatializerStateChangedListener(...):监听空间音频可用状态的变化。
- 作用 :获取系统的
isDirectPlaybackSupported(AudioFormat format, AudioAttributes attributes)- 作用:查询系统硬件是否支持对指定格式(如 Dolby Atmos、DSD、高采样率 PCM)进行硬解码直通,避免经过 AudioFlinger 重采样。
3.1.4、音量、响铃与场景模式 (Volume & Modes)
作用:管理系统各音频流的音量等级与场景模式。
setStreamVolume(int streamType, int index, int flags)/getStreamVolume(int streamType)- 作用 :设置或获取特定音频流的音量大小(如
STREAM_MUSIC、STREAM_RING、STREAM_VOICE_CALL)。
- 作用 :设置或获取特定音频流的音量大小(如
adjustStreamVolume(int streamType, int direction, int flags)- 作用 :相对调整音量(如
ADJUST_RAISE调大、ADJUST_LOWER调小、ADJUST_TOGGLE_MUTE静音)。
- 作用 :相对调整音量(如
setMode(int mode)/getMode()- 作用:设置音频系统的全局场景模式。
- 常见模式 :
MODE_NORMAL:普通媒体播放。MODE_IN_COMMUNICATION:VoIP 音视频通话(微信/会议软件必须切换至此模式以启用 AEC 回音消除和降噪)。MODE_IN_CALL:蜂窝电话通话。
setRingerMode(int ringerMode)/getRingerMode()- 作用 :设置/获取响铃模式(
RINGER_MODE_NORMAL响铃、RINGER_MODE_VIBRATE震动、RINGER_MODE_SILENT静音)。
- 作用 :设置/获取响铃模式(
3.1.5、全局音频监控 (Playback & Recording Tracking)
作用:系统级应用用于监控全局音频活动。
registerAudioPlaybackCallback(AudioManager.AudioPlaybackCallback cb, Handler handler)- 作用:监听系统中所有应用的音频播放状态变化。
registerAudioRecordingCallback(AudioManager.AudioRecordingCallback cb, Handler handler)- 作用:监听系统中所有应用的录音状态变化(如麦克风设备抢占、采样率变化等)。
3.2、AudioTrack
用于向系统 AudioFlinger 推送原始 PCM 音频字节流或未解码压缩流,实现低延迟原生音频播放的核心系统组件。在 Android 16 中,进一步强化了对动态设备路由、高解析度直通(Direct/Offload)以及 Spatializer(空间音频)的适配支持。
3.2.1、播放状态与生命周期控制 (Playback State & Lifecycle)
作用:控制音频播放的启动、暂停、停止以及 Native 层句柄与内存缓冲区的分配与释放。
-
play():启动或恢复音频播放。底层 AudioFlinger 开始从共享环形缓冲区(Ring Buffer)中拉取数据并送往音频 HAL。 -
pause():暂停播放。当前排队未播放完的数据仍保留在缓冲区中,再次调用play()时可顺畅接续播放。 -
stop():停止播放。停止消耗缓冲区数据,并重置播放位置标记。 -
flush():清空排队等待播放的 PCM 数据(必须在pause()或stop()后调用,常用于 Seek 跳转或切歌场景)。 -
release():销毁 Native 层的 C++AudioTrack句柄,释放与 AudioFlinger 共享的内存块和系统资源。 -
getState():获取AudioTrack对象的初始化状态。- 返回值 :
STATE_INITIALIZED(初始化成功)、STATE_UNINITIALIZED(初始化失败/参数不受硬件支持)。
- 返回值 :
-
getPlayState():获取当前播放器的实时运行状态。- 返回值 :
PLAYSTATE_PLAYING(播放中)、PLAYSTATE_PAUSED(暂停)、PLAYSTATE_STOPPED(已停止)。
- 返回值 :
3.2.2、PCM 数据写入与传输 (Data Writing & Transfer)
作用:应用层通过写模式向内核环形缓冲区注入原始 PCM 数据或压缩流。
write(byte[] audioData, int offsetInBytes, int sizeInBytes, int writeMode)
- 作用:向播放缓冲区写入 PCM 字节数组。
- 常用传输模式 (
writeMode) :WRITE_BLOCKING:阻塞式写入。若缓冲区空间不足,调用线程挂起等待(默认模式)。WRITE_NON_BLOCKING:非阻塞式写入。立即返回当前实际成功写入的字节数(常用于高帧率游戏或低延迟 Native 引擎)。
write(short[] audioData, int offsetInShorts, int sizeInShorts, int writeMode)
- 作用:写入 16-bit PCM 采样数据(以 short 为单位)。
write(ByteBuffer audioData, int sizeInBytes, int writeMode)
- 作用 :将 PCM 数据直接从 Native 层的
ByteBuffer(Direct Buffer)推入缓冲区,避免 Java 与 C/C++ JNI 层之间的数据拷贝,常用于 FFmpeg 等 C++ 解码引擎。
3.2.3、工作模式与属性配置 (Data Transfer Modes & Attributes)
作用:在构造时通过 AudioTrack.Builder 指定数据传输类型、音频用途(Attributes)与硬解码参数。
setTransferMode(int mode)
- 作用:指定数据传输机制。
- 工作模式类型 :
MODE_STREAM:流式传输。应用层通过循环不断向AudioTrack写入 PCM 数据,适用于长音频、视频播放、VoIP 等场景。MODE_STATIC:静态一次性加载。播放前将所有 PCM 数据一次性写入内部缓冲区,适用于 UI 点击音效、游戏枪械声等对延迟极度敏感且短促的音效。
setAudioAttributes(AudioAttributes attributes)
- 作用:指定音频的用途(Usage)和内容类型(ContentType),以便系统 AudioManager 做出正确的音频焦点(Audio Focus)与混音策略。
- 常见 Usage :
USAGE_MEDIA(媒体播放)、USAGE_VOICE_COMMUNICATION(通话/VoIP)、USAGE_GAME(游戏音频)、USAGE_ALARM(闹钟)。
setOffloadDelayPadding(int delayInFrames, int paddingInFrames)
- 作用:在 Offload(硬件直通/DSP 硬件解码)播放模式下,指定音频流的延迟与补零帧数,实现无缝衔接播放(Gapless Playback)。
3.2.4、动态设备路由与空间音频 (Device Routing & Spatializer)
Android 13+ / 16 架构增强能力
作用:显式控制声音输出设备(如蓝牙 A2DP、USB DAC、车载总线),并查询/绑定空间音频与硬件直通。
setPreferredDevice(AudioDeviceInfo device) / getPreferredDevice()
- 作用:强行指定音频输出的外设通道(如将特定音频绑定至蓝牙耳机或指定的 USB 声卡播放)。
getRoutedDevice()
- 作用:获取当前 AudioFlinger 实际用于输出该轨道声音的硬件设备对象。
isDirectPlaybackSupported(AudioFormat format, AudioAttributes attributes) (静态 API)
- 作用:查询系统硬件是否支持对指定格式(如 Dolby Atmos、DSD、高采样率 PCM)进行硬解码直通(Direct Playback),避开 AudioFlinger 的重采样(SRC)。
3.2.5、音量、速率与位置监控 (Volume, Speed & Position Tracking)
- Android 16 兼容 PlaybackParams *
作用:动态调节播放效果并监控底层硬件播放进度。
setVolume(float gain)
- 作用:设置当前轨道的线性音量增益(范围 0.0f 至 1.0f)。
setPlaybackParams(PlaybackParams params)
- 作用:动态调节播放倍速(Speed,如 0.5x~2.0x)与调性/音高(Pitch),系统底层自动进行变调不变音算法处理。
getPlaybackHeadPosition()
- 作用:获取当前硬件已实际播放的采样帧数(Frames),用于精准计算音画同步(AV Sync)时间戳。
setNotificationMarkerPosition(int markerInFrames) / setPositionNotificationPeriod(int periodInFrames)
- 作用 :设置播放进度标记点或周期性通知帧数。配合
OnPlaybackPositionUpdateListener实现播放进度回调。
3.3、AudioRecord
用于从底层硬件麦克风、系统内录通道或外接音频设备中采集原始 PCM 音频数据的核心系统组件。
3.3.1、采集状态与生命周期控制 (Recording State & Lifecycle)
作用:控制硬件麦克风采集的启动、停止以及 Native 层资源的分配与释放。
startRecording():启动音频采集,底层 AudioFlinger 开始向共享环形缓冲区(Ring Buffer)填充 PCM 数据。
stop():停止音频采集,暂停向缓冲区写入新数据。
release():销毁 Native 层的 C++ AudioRecord 句柄,释放底层的硬件麦克风句柄及系统 AudioFlinger 客户端资源。
getState():获取 AudioRecord 对象的初始化状态。
- 返回值 :
STATE_INITIALIZED(初始化成功)、STATE_UNINITIALIZED(参数错误或硬件被占用导致初始化失败)。
getRecordingState():获取当前录音采集的实时运行状态。
- 返回值 :
RECORDSTATE_RECORDING(正在采集)、RECORDSTATE_STOPPED(已停止)。
3.3.2、PCM 数据拉取与读取 (Data Reading)
作用:应用层通过推拉模型(Pull-based)从内核环形缓冲区中提取原始 PCM 字节流。
read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode)
- 作用:从缓冲区读取 PCM 字节数组数据。
- 常用模式 (
readMode) :READ_BLOCKING:阻塞式读取。若缓冲区数据不足指定长度,调用线程挂起等待(默认模式)。READ_NON_BLOCKING:非阻塞式读取。立即返回当前缓冲区已有的实际字节数。
read(short[] audioData, int offsetInShorts, int sizeInShorts)
- 作用:直接读取 16-bit PCM 采样数据(以 short 为单位),方便进行波形分析或 DSP 算法处理。
read(ByteBuffer audioData, int sizeInBytes)
- 作用 :将 PCM 数据直接写入 Native 层的
ByteBuffer(Direct Buffer),避免 Java 与 C/C++ JNI 层之间的数据拷贝,常用于 JNI/NDK 音视频编码。
3.3.3、音频源配置与算法预处理 (Audio Source & Pre-processing)
作用:在构造时指定硬件采样来源,并触发系统硬件级或算法级的回音消除 (AEC)、降噪 (ANS) 和自动增益 (AGC)。
AudioRecord.Builder.setAudioSource(int audioSource)
- 作用:指定采集的音频硬件通道与预处理策略。
- 常见音频源 (
audioSource) :MIC:普通麦克风原始输入(无额外算法干预)。VOICE_COMMUNICATION:VoIP / 实时通话场景专用。系统会自动启用硬件/系统级回音消除 (AEC)、自动增益 (AGC) 和噪声抑制 (ANS)。VOICE_RECOGNITION:语音识别 (ASR) 场景专用。优化语音清晰度,关闭可能抹平语音细节的激进降噪算法。CAMCORDER:摄像机麦克风(自动绑定与当前摄像头同向的麦克风阵列)。REMOTE_SUBMIX:系统内录通道(用于屏幕录制或音频流重定向)。
3.3.4、动态设备路由与定向拾音 (Device Routing & Beamforming)
Android 10+ / 16 硬件感知能力
作用:感知与绑定指定的输入设备(如 USB 麦克风、蓝牙耳机),并控制麦克风阵列的波束拾音方向。
setPreferredDevice(AudioDeviceInfo device) / getPreferredDevice()
- 作用:显式指定强行的音频输入设备(如指定从特定的 USB 声卡或蓝牙 SCO 设备录音)。
getRoutedDevice()
- 作用:获取当前实际生效的音频采集硬件设备对象。
setPreferredMicrophoneDirection(int direction)
- 作用:控制多麦克风阵列的拾音指向性。
- 方向类型 :
MIC_DIRECTION_TOWARDS_USER(朝向用户/前置)、MIC_DIRECTION_AWAY_FROM_USER(背向用户/后置)、MIC_DIRECTION_EXTERNAL(外接麦克风)。
setPreferredMicrophoneFieldDimension(float zoom)
- 作用:结合相机 Zoom 变焦,动态缩放麦克风阵列的拾音波束宽度(Beamforming)。
3.3.5、采集监控与位置通知 (Tracking & Position Notification)
作用:监听录音配置的动态变化以及周期性采集进度回调。
registerAudioRecordingCallback(AudioRecordingCallback cb, Handler handler)
- 作用:监听当前录制流的实时配置变化。
- 回调感知:麦克风被更高优先级应用抢占、采样率发生强制重采样、或录音被系统隐私策略静音。
setPositionNotificationPeriod(int periodInFrames)
- 作用:设置周期性帧数回调的间隔。
setRecordPositionUpdateListener(OnRecordPositionUpdateListener listener)
- 作用 :注册录制位置监听器。配合
setPositionNotificationPeriod使用,常用于实时计算录制时长或绘制波形图。
3.4、MediaPlayer
用于提供高级、开箱即用的音视频播放与控制功能的核心系统框架组件。内置解封装(Demuxer)与音视频解码引擎(MediaCodec/NuPlayer),支持加载并播放本地及网络主流格式的媒体流,自动处理缓冲区管理、音视频同步与状态机生命周期流转,并将解码后的音频无缝交付底层 AudioTrack 推送渲染。
3.4.1、声明周期管理
MediaPlayer 内部维护着严格的状态机(State Machine)。绝大多数 API 的调用都严格依赖特定状态 ,在错误的状态下调用方法会抛出 IllegalStateException 或触发 OnErrorListener。
reset():重置播放器到 Idle(空闲)状态 。清除先前的数据源和配置,便于复用MediaPlayer对象,避免频繁创建销毁 Native 资源。prepare():同步 准备解码器与缓冲区(进入 Prepared 状态 )。会阻塞当前线程,仅建议用于极小的本地文件,切勿在主线程/UI 线程中调用。prepareAsync():异步 准备解码器与缓冲区(进入 Preparing 状态 )。耗时操作(如网络 IO、解析文件头)均在后台线程完成,准备完成后触发OnPreparedListener回调。stop():停止播放(进入 Stopped 状态 )。停止解码与音频输出,如需重新播放,必须重新调用prepare()/prepareAsync()。release():释放 Native 层的解码器、硬件 DSP 及系统资源(进入 End 状态 )。对象销毁或页面退出时必须显式调用。
3.4.2、数据源配置与初始化
指定播放器需要加载并解析的媒体内容来源(支持本地文件、content:// 媒体库 URI、HTTP/HTTPS 网络流、RTSP 流等)。
3.4.3、数据源指定 API
setDataSource(String path):通过文件路径或网络 URL 指定数据源(必须在 Idle 状态 下调用)。setDataSource(Context context, Uri uri):通过 Android 系统Uri(如 ContentProvider 提供的content://)设置数据源。setDataSource(FileDescriptor fd, long offset, long length):通过文件描述符(FD)读取数据,常用于读取 APK 内的assets/raw资源或经过加密的临时文件。
3.4.4、播放控制和进度定位
控制音频/视频流的播放、暂停、停止以及指定时间戳跳转。
start():启动或恢复播放(进入 Started 状态)。pause():暂停播放(进入 Paused 状态 )。内部维持当前解码和播放位置,调用start()可无缝接续。seekTo(long msec, int mode):精准或快速跳转到指定毫秒时间戳。- 常用 Mode :
SEEK_PREVIOUS_SYNC/SEEK_NEXT_SYNC:定位到目标时间戳的前/后一个关键帧(I帧),速度快。SEEK_CLOSEST:精准定位到最近的帧(开销稍大)。
- 常用 Mode :
isPlaying():查询当前是否处于正在播放状态。getCurrentPosition()/getDuration():分别获取当前已播放的毫秒数与媒体文件的总时长(毫秒)。
3.4.5、音频策略与效果 API
setAudioAttributes(AudioAttributes attributes):绑定音频的用途(Usage)与内容类型(ContentType)。例如将用途设为USAGE_MEDIA(媒体播放)或USAGE_ALARM(闹钟),系统AudioManager会据此做音量通道映射与音频焦点抢占。setVolume(float leftVolume, float rightVolume):设置左右声道的相对音量增益(范围0.0f到1.0f)。setLooping(boolean looping)/isLooping():设置或查询是否开启无缝循环播放。setPlaybackParams(PlaybackParams params):动态调节播放倍速(Speed,如0.5x~2.0x)与调性(Pitch),系统底层自动处理变调不变音算法。attachAuxEffect(int effectId)/setAuxEffectSendLevel(float level):将当前音频流附加到系统的全局辅助音效(如环绕声、重低音或混响效果器)。
3.4.6、事件监听与异步回调
捕获播放器内部的异步事件(准备完成、播放结束、网络缓冲、错误异常等)。
MediaPlayer.OnPreparedListener- 核心回调
onPrepared(MediaPlayer mp):异步准备(prepareAsync)完成时触发,在此处调用mp.start()才是安全的启动时机。
- 核心回调
MediaPlayer.OnCompletionListener- 核心回调
onCompletion(MediaPlayer mp):媒体文件播放到末尾(且未开启Looping)时触发。
- 核心回调
MediaPlayer.OnErrorListener- 核心回调
onError(MediaPlayer mp, int what, int extra):底层解析、解码或网络传输发生异常时触发。 - 返回值说明 :返回
true表示应用已自行处理错误,可防止系统再次触发OnCompletionListener。
- 核心回调
MediaPlayer.OnBufferingUpdateListener- 核心回调
onBufferingUpdate(MediaPlayer mp, int percent):网络流媒体播放时,回调当前缓冲进度百分比(0~100)。
- 核心回调
MediaPlayer.OnSeekCompleteListener- 核心回调
onSeekComplete(MediaPlayer mp):seekTo()操作完成、解码器精准定位到新帧时触发。
- 核心回调
3.4.7、Code Example
java
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.media.PlaybackParams;
import android.util.Log;
import java.io.IOException;
public class AppAudioPlayer implements
MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnBufferingUpdateListener {
private static final String TAG = "AppAudioPlayer";
private MediaPlayer mediaPlayer;
private boolean isPrepared = false;
private int currentBufferPercent = 0;
/**
* 加载并播放指定 URL 或本地路径的音频
*/
public void play(String pathOrUrl) {
try {
// 1. 复用或新建 MediaPlayer
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
} else {
mediaPlayer.reset(); // 重置回 Idle 状态
}
isPrepared = false;
// 2. 配置音频属性(配合系统 AudioManager 焦点管理)
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA) // 媒体播放场景
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // 内容类型:音乐
.build();
mediaPlayer.setAudioAttributes(audioAttributes);
// 3. 绑定监听回调
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setOnBufferingUpdateListener(this);
// 4. 设置数据源并开启后台异步准备
mediaPlayer.setDataSource(pathOrUrl);
mediaPlayer.prepareAsync(); // 进入 Preparing 状态
} catch (IOException e) {
Log.e(TAG, "设置数据源失败: " + pathOrUrl, e);
release();
} catch (IllegalStateException e) {
Log.e(TAG, "MediaPlayer 状态机调用异常", e);
release();
}
}
@Override
public void onPrepared(MediaPlayer mp) {
isPrepared = true;
Log.d(TAG, "音频准备完毕,总时长: " + mp.getDuration() + "ms");
// 安全启动播放
mp.start();
}
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "音频播放结束");
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(TAG, "播放器内部错误 -> what: " + what + ", extra: " + extra);
release(); // 发生错误时释放底层资源
return true; // 返回 true 阻止触发 onCompletion
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
this.currentBufferPercent = percent;
}
/**
* 设置播放倍速 (0.5x ~ 2.0x)
*/
public void setSpeed(float speed) {
if (mediaPlayer != null && isPrepared) {
PlaybackParams params = mediaPlayer.getPlaybackParams();
params.setSpeed(speed);
mediaPlayer.setPlaybackParams(params);
}
}
/**
* 暂停播放
*/
public void pause() {
if (mediaPlayer != null && isPrepared && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
/**
* 恢复播放
*/
public void resume() {
if (mediaPlayer != null && isPrepared && !mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
/**
* 安全释放内存与 Native 解码器资源
*/
public void release() {
if (mediaPlayer != null) {
try {
mediaPlayer.stop();
} catch (Exception ignored) {}
mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer = null;
isPrepared = false;
Log.d(TAG, "MediaPlayer 资源已完全安全释放");
}
}
}
3.5、AudioTrack 与 AudioRecord 区别全方位对比
AudioTrack 与 AudioRecord 是 Android Framework 层 Native 音频框架(AudioFlinger)提供给 Java/Kotlin 层的两大原生低级 API。两者的本质区别在于数据的流向相反 :一个负责声音的输出与播放 ,另一个负责声音的输入与采集。
3.5.1、对比表
| 对比维度 | AudioTrack (音频播放组件) | AudioRecord (音频采集组件) |
|---|---|---|
| 数据流向 | 应用层 ➔ AudioFlinger ➔ 硬件扬声器 (输出) | 硬件麦克风 ➔ AudioFlinger ➔ 应用层 (输入) |
| 数据类型 | 解码后的原始 PCM 字节流(或特定格式压缩流) | 未经编码的原始 PCM 采样字节流 |
| 数据传输模型 | 推模型 (Push-based) :应用主动写 write() |
拉模型 (Pull-based) :应用主动读 read() |
| 核心操作 API | play(), write(), pause(), stop(), flush() |
startRecording(), read(), stop() |
| 工作模式 (Mode) | 支持 MODE_STREAM (流式) 与 MODE_STATIC (静态) |
仅支持流式不断读取 (无 STATIC 概念) |
| 硬件策略配置 | AudioAttributes (Usage/ContentType,如媒体/游戏) |
AudioSource (如 MIC / VOICE_COMMUNICATION) |
| 系统预处理 | 变调/变速 (PlaybackParams)、空间音频 (Spatializer) |
硬件/系统级回音消除 (AEC)、降噪 (ANS)、增益 (AGC) |
| 核心应用场景 | 自研音视频播放器 (FFmpeg)、游戏音频引擎、VoIP 对讲 | 实时语音通话、语音识别 (ASR)、声纹分析、录音机 |
3.5.2、数据流向与架构差异
xml
【AudioTrack 播放流向】
[应用层 PCM 数据] ──(write)──> [AudioTrack 缓冲区] ──> [AudioFlinger 混音器] ──> [扬声器/耳机]
【AudioRecord 采集流向】
[麦克风/外设] ──> [HAL/AudioFlinger 驱动] ──> [AudioRecord 缓冲区] ──(read)──> [应用层 PCM 数据]
AudioTrack:生产者是应用层,消费者是底层硬件。应用层解码完音频后,将 PCM 字节推入(Push)系统缓冲区,底层硬件按采样率定时消耗并播发出声音。AudioRecord:生产者是底层硬件,消费者是应用层。硬件麦克风将采集到的声波转换为数字信号推入内核环形缓冲区,应用层开启线程主动拉取(Pull)数据进行编码或算法处理。
3.5.3、策略与预处理配置差异
AudioTrack 构造时关注的是声音要发给谁、在什么场景下播放:
- 通过
AudioAttributes.setUsage(...)指定用途,如USAGE_MEDIA(媒体音乐)、USAGE_VOICE_COMMUNICATION(通话)、USAGE_GAME(游戏音效)。 - 系统
AudioManager会根据 Usage 决定音频焦点(Audio Focus)抢占、音量通道(Stream Volume)控制以及输出路由(如切至蓝牙 A2DP 或扬声器)。
AudioRecord 构造时关注的是声音从哪里来、需要什么算法处理:
- 通过
setAudioSource(...)指定采集源,如MIC(原始麦克风)、VOICE_COMMUNICATION(通话源)、VOICE_RECOGNITION(语音识别)。 - 若指定为
VOICE_COMMUNICATION,系统底层(硬件 DSP 或 AudioFlinger)会自动挂载回音消除(AEC)、噪声抑制(ANS)和自动增益(AGC)算法;若指定为VOICE_RECOGNITION,系统会关闭激进的降噪,避免抹平语音细节。
3.5.4、协同工作场景 (如 VoIP / 实时音视频通话)
在实际复杂开发(如微信语音通话、RTC 会议软件、实时 K 歌系统)中,AudioTrack 与 AudioRecord 通常是配对协同工作的:

- 上行链路 (采集) :
AudioRecord以VOICE_COMMUNICATION源采集远端说话人的声音,PCM 数据经过 AAC/Opus 编码后打包发送至网络。 - 下行链路 (播放) :应用从网络接收到对方的语音包,解码为 PCM 数据后,通过
AudioTrack写入并由扬声器/耳机播放。
3.5.5、Codo Example
1、AudioTrack 播放示例(MODE_STREAM)
java
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
public class PcmPlayer {
private AudioTrack audioTrack;
private Thread playThread;
private volatile boolean isPlaying = false;
/**
* 初始化并启动 AudioTrack 播放
*
* @param sampleRate 采样率(单位:Hz),例如 44100Hz(CD音质)或 48000Hz(Android 系统默认硬件采样率)
* - 代表每秒钟在连续声波信号上采集多少个点。
* @param channelConfig 声道配置,例如:
* - AudioFormat.CHANNEL_OUT_MONO (单声道)
* - AudioFormat.CHANNEL_OUT_STEREO (双声道立体声:左/右)
* @param audioFormat 采样数据格式(位深),例如:
* - AudioFormat.ENCODING_PCM_16BIT (常用的 16 位线性 PCM,每个采样点占 2 字节)
* - AudioFormat.ENCODING_PCM_8BIT (8 位 PCM)
* - AudioFormat.ENCODING_PCM_FLOAT (单精度浮点 PCM,动态范围更高)
*/
public void initAndPlay(int sampleRate, int channelConfig, int audioFormat) {
// 1. 获取硬件底层要求的最小内部缓冲区大小 (MinBufferSize)
// 作用:根据传入的采样率、声道数和编码位深,计算出 AudioFlinger 和硬件 DMA 传输时所需的最小字节数。
// 注意:小于此大小会导致 AudioTrack 初始化失败或播放时出现卡顿、爆音(Underrun)。
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
// 2. 使用 Builder 构建现代 AudioTrack 实例
audioTrack = new AudioTrack.Builder()
// 设置音频属性 (AudioAttributes):告知系统这个声音是干什么的,供系统的 AudioManager 决定策略
.setAudioAttributes(new AudioAttributes.Builder()
// setUsage: 音频的使用场景
// USAGE_MEDIA 代表普通媒体播放(音乐、视频)。如果是打电话应用需设为 USAGE_VOICE_COMMUNICATION
.setUsage(AudioAttributes.USAGE_MEDIA)
// setContentType: 内容类型
// CONTENT_TYPE_MUSIC 代表音乐。其他如 CONTENT_TYPE_SPEECH(语音/朗读)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
// 设置音频数据格式 (AudioFormat)
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(audioFormat) // 设置 PCM 编码格式(如 PCM_16BIT)
.setSampleRate(sampleRate) // 设置采样率(如 44100Hz)
.setChannelMask(channelConfig) // 设置声道遮罩(如 CHANNEL_OUT_STEREO)
.build())
// 设置内部共享环形缓冲区大小
// 推荐:设置为 minBufferSize 的 2 到 4 倍,以防止应用层写入延迟导致硬件吃空缓冲区发生卡顿
.setBufferSizeInBytes(minBufferSize * 2)
// 设置传输模式 (TransferMode)
// - MODE_STREAM: 流式传输。适用于长音频,通过 write() 循环不断向缓冲区塞数据
// - MODE_STATIC: 静态加载。一次性把全部 PCM 存入缓冲区,适用于短促音效(如按键音、枪声)
.setTransferMode(AudioTrack.MODE_STREAM)
.build();
// 3. 启动播放引擎(将底层状态从 STATE_STOPPED 切换为 PLAYSTATE_PLAYING)
// 此时 AudioFlinger 开始准备拉取数据,即使缓冲区尚无数据也不会报错,但会处于等待状态
audioTrack.play();
isPlaying = true;
// 4. 开启独立的后台线程进行阻塞式 PCM 数据写入(严谨在主线程/UI线程中调用 write 方法)
playThread = new Thread(() -> {
// 定义一次写入的字节数组缓冲区,通常取 minBufferSize 大小
byte[] buffer = new byte[minBufferSize];
while (isPlaying) {
/*
* 伪代码:从文件、解码器或网络流中读取原始 PCM 数据装入 buffer
* int bytesRead = pcmInputStream.read(buffer, 0, buffer.length);
*/
int bytesRead = 0; // 假设此处获得了实际读到的字节数
if (bytesRead > 0) {
/*
* write 方法参数解析:
* @param audioData 要写入播放器的原始 PCM 字节数组
* @param offsetInBytes 数据在数组中的起始偏移量(一般为 0)
* @param sizeInBytes 本次要写入的字节长度
* @param writeMode 写入模式:
* - AudioTrack.WRITE_BLOCKING: 阻塞式。若内部缓冲区满,线程挂起等待直到空间释放
* - AudioTrack.WRITE_NON_BLOCKING: 非阻塞式。立即返回实际成功写入的字节数
*/
int writtenResult = audioTrack.write(buffer, 0, bytesRead, AudioTrack.WRITE_BLOCKING);
if (writtenResult < 0) {
// 返回值为负数代表错误码,如 ERROR_INVALID_OPERATION 或 ERROR_DEAD_OBJECT (Native 层服务挂掉)
break;
}
}
}
}, "AudioTrack-Play-Thread");
playThread.start();
}
/**
* 停止播放并释放系统 Native 层资源
*/
public void stopAndRelease() {
isPlaying = false; // 退出线程循环
if (playThread != null) {
try {
playThread.join(); // 等待数据写入线程彻底结束
} catch (InterruptedException ignored) {}
}
if (audioTrack != null) {
// 如果正在播放,先调用 stop()
if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
audioTrack.stop(); // 停止消费缓冲区数据
}
audioTrack.flush(); // 清空环形缓冲区内排队未播完的数据
audioTrack.release(); // 销毁 Native 层 C++ 句柄,释放与 AudioFlinger 共享的内存
audioTrack = null;
}
}
}
2、AudioRecord 采集示例
java
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Process;
public class PcmRecorder {
private AudioRecord audioRecord;
private Thread recordThread;
private volatile boolean isRecording = false;
/**
* 初始化并启动 AudioRecord 麦克风采集
*
* @param sampleRate 采样率(单位:Hz),例如 16000Hz(语音识别常用)、44100Hz 或 48000Hz
* @param channelConfig 采集声道配置,例如:
* - AudioFormat.CHANNEL_IN_MONO (单声道,麦克风采集最常用)
* - AudioFormat.CHANNEL_IN_STEREO (双声道/双麦克风立体声采集)
* @param audioFormat 采样数据格式(位深),例如:
* - AudioFormat.ENCODING_PCM_16BIT (16 位 PCM,最通用的位深格式)
*/
public void startRecording(int sampleRate, int channelConfig, int audioFormat) {
// 1. 获取硬件底层采集要求的最小内部缓冲区大小
// 作用:计算麦克风驱动与内核 HAL 传递 PCM 数据时所需的最小内存空间
int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
// 2. 使用 Builder 构建现代 AudioRecord 实例
audioRecord = new AudioRecord.Builder()
// setAudioSource: 指定音频输入源(非常重要!)
// 不同的 Source 会触发系统挂载不同的硬件/算法预处理模块:
// - MediaRecorder.AudioSource.MIC: 原始麦克风输入(无降噪/回音消除算法干预)
// - MediaRecorder.AudioSource.VOICE_COMMUNICATION: 通话/VoIP场景专用。
// 【核心特性】:系统会自动挂载硬件/DSP级的回音消除 (AEC)、降噪 (ANS) 和自动增益 (AGC)!
// - MediaRecorder.AudioSource.VOICE_RECOGNITION: 语音识别专用(会优化语音清晰度,关闭会抹平细节的激进降噪)
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
// setAudioFormat: 设置采集参数
.setAudioFormat(new AudioFormat.Builder()
.setEncoding(audioFormat) // 位深(如 PCM_16BIT)
.setSampleRate(sampleRate) // 采样率(如 16000Hz)
.setChannelMask(channelConfig) // 声道(如 CHANNEL_IN_MONO)
.build())
// 设置应用层的接收缓冲区大小,推荐设为最小值的 2 倍防止读取不及时造成溢出丢帧
.setBufferSizeInBytes(minBufferSize * 2)
.build();
// 3. 启动硬件麦克风采集(将状态切换为 RECORDSTATE_RECORDING)
// 底层硬件和 AudioFlinger 开始向内核环形缓冲区源源不断地塞入采集到的 PCM 字节
audioRecord.startRecording();
isRecording = true;
// 4. 开启独立的后台高优先级线程不断拉取(Pull)PCM 数据
recordThread = new Thread(() -> {
// 【关键设置】:提高录音线程的系统调度优先级为 URGENT_AUDIO(极高音频优先级)
// 作用:防止 CPU 在繁忙时抢占当前线程,导致录音数据读取不及时而造成丢帧或卡顿
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
byte[] buffer = new byte[minBufferSize];
while (isRecording) {
/*
* read 方法参数解析:
* @param audioData 用于接收 PCM 数据的字节数组
* @param offsetInBytes 写入数组的起始偏移量(一般为 0)
* @param sizeInBytes 本次期望读取的最大字节长度
* @param readMode 读取模式:
* - AudioRecord.READ_BLOCKING: 阻塞式读取。若内部缓冲区数据不够 sizeInBytes,线程挂起等待直到填满
* - AudioRecord.READ_NON_BLOCKING: 非阻塞式读取。立即返回当前缓冲区现有的实际数据量
* @return 返回实际成功读到的字节数。若为负数表示错误码(如 ERROR_INVALID_OPERATION)
*/
int readBytes = audioRecord.read(buffer, 0, buffer.length, AudioRecord.READ_BLOCKING);
if (readBytes > 0) {
/*
* 此时 buffer 中就是刚录好的原始 PCM 数据!
* 业务操作:
* 1. 送入 AAC / Opus 编码器压缩
* 2. 通过 Socket 发送到网络 (实时语音通话)
* 3. 写入文件保存为 .pcm 或 .wav 文件
*/
} else if (readBytes < 0) {
// 读取发生异常,退出采集循环
break;
}
}
}, "AudioRecord-Capture-Thread");
recordThread.start();
}
/**
* 停止采集并释放麦克风设备与 Native 层资源
*/
public void stopAndRelease() {
isRecording = false; // 标志位置为 false,终止线程循环
if (recordThread != null) {
try {
recordThread.join(); // 等待采集线程彻底安全退出
} catch (InterruptedException ignored) {}
}
if (audioRecord != null) {
// 如果正在采集,先停止采集
if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
audioRecord.stop(); // 停止硬件麦克风数据推送
}
audioRecord.release(); // 销毁 Native 层的 AudioRecord 实例,交还麦克风控制权
audioRecord = null;
}
}
}