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_MUSICSPEECH 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)
    • 作用:获取系统当前连接的所有音频设备。
    • 常用 FlagGET_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_MUSICSTREAM_RINGSTREAM_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)与混音策略。
  • 常见 UsageUSAGE_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.0f1.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 区别全方位对比

AudioTrackAudioRecord 是 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 歌系统)中,AudioTrackAudioRecord 通常是配对协同工作的:

  1. 上行链路 (采集)AudioRecordVOICE_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;
        }
    }
}
相关推荐
kayyoo2 小时前
CoordinatorLayout解析(二): 结合CollapsingToolbarLayout实现Toolbar折叠效果
android
数据知道4 小时前
移动端渗透测试流程:Android APK 逆向与动态调试
android·web安全·网络安全
我命由我123455 小时前
人脸识别 - 勒克斯(光线照射到物体表面上的明亮程度)
android·java·学习·java-ee·人脸识别·学习方法·android runtime
聚美智数6 小时前
护照OCR识别-护照图像识别-护照OCR文字识别-护照识别OCR-护照信息识别-护照OCR
android·ocr
码农coding7 小时前
android12 点击桌面应用图标启动Activity流程
android
johnsong8 小时前
深度拆解:150M循环模型如何用循环推理击败大Transformer
android·深度学习·transformer
CedarQR9 小时前
云卓 G20 遥控器 Android 开发实录:从 USB 设备到读取摇杆/按键通道值
android·linux·无人机
pengyu10 小时前
【Kotlin 协程修仙录 · 炼虚境 · 初阶】 | 虚空造物:Channel 基础与协程间通信的管道艺术
android·前端·kotlin
小强闯江湖10 小时前
ViewCompose:让原生 Android View 进入声明式时代
android·开源·kotlin