Android Audio-FW

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)。
  • 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:精准定位到最近的帧(开销稍大)。
  • 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 通常是配对协同工作的:

  1. 上行链路 (采集) :AudioRecord 以 VOICE_COMMUNICATION 源采集远端说话人的声音,PCM 数据经过 AAC/Opus 编码后打包发送至网络。
  2. 下行链路 (播放) :应用从网络接收到对方的语音包,解码为 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;
        }
    }
}
相关推荐
三少爷的鞋13 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)17 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码19 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程19 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓1 天前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer