Android 音频系统深度解析:从 App 到内核的完整链路

张开发
2026/4/12 4:56:05 15 分钟阅读

分享文章

Android 音频系统深度解析:从 App 到内核的完整链路
文章目录一、引言二、Android 音频架构分层概览三、应用层 API音频播放的入口3.1 API 选型指南3.2 AudioTrackPCM 播放的核心四、Java 框架层JNI 与 AudioSystem五、Native 核心层5.1 AudioFlinger音频中枢5.2 PlaybackThread音频输出线程5.3 AudioPolicyService策略决策者5.4 数据流核心共享内存通信六、HAL 层硬件抽象6.1 HAL 的作用6.2 从 HIDL 到 AIDL 的演进七、驱动层从 TinyALSA 到 ALSA7.1 TinyALSAAndroid 的轻量级 ALSA7.2 从 TinyALSA 到内核驱动的完整路径7.3 硬件数据传输八、完整链路串联一次音频播放的全流程九、总结参考资料一、引言从应用程序写入一段音频数据到声音从扬声器或耳机发出这段看似简单的旅程在 Android 系统中要经过应用进程 → Binder → AudioFlinger → HAL → Kernel → 硬件多个关卡期间经历共享内存传递、采样率转换、多路混音、格式重排等一系列处理。本文将沿着音频播放这条主线自上而下地梳理 Android 音频系统的完整链路从应用层 API 一路深入到内核驱动帮助读者建立起对整个音频栈的系统性认知。二、Android 音频架构分层概览Android 音频系统采用分层设计从上到下可划分为以下五个层次┌───────────────────────────────┐ │ 应用层 API │ ← Java/Kotlin 层MediaPlayer, SoundPool, AudioTrack ├───────────────────────────────┤ │ Framework 层 (Java) │ ← AudioManager, AudioSystem ├───────────────────────────────┤ │ Native 核心层 │ ← AudioFlinger, AudioPolicyService (libaudioflinger.so) ├───────────────────────────────┤ │ HAL 层 │ ← 厂商实现audio.primary.xxx.so ├───────────────────────────────┤ │ 驱动层 (Kernel) │ ← ALSA/TinyALSA控制音频 Codec └───────────────────────────────┘无论上层使用哪种 API 进行播放最终音频数据都会通过 AudioFlinger并由 Audio HAL 与底层硬件驱动交互完成音频输出。Audio 系统在 Android 中主要负责音频数据流传输和音频设备管理不涉及解码功能——解码由 OpenCore 或 StageFright 完成解码后才调用音频系统接口进行播放。三、应用层 API音频播放的入口3.1 API 选型指南Android 为开发者提供了多套音频 API各有不同的定位和适用场景API数据源延迟适用场景MediaPlayer压缩音频文件MP3/AAC 等较高100-300ms背景音乐、视频播放SoundPool预加载 PCM 短音效极低按键音、提示音AudioTrack原始 PCM 数据低10-50ms实时音频、VOIP、自定义播放AAudio/OboePCM超低10ms专业音频、高版本设备OpenSL ESPCM中等旧设备兼容、底层控制3.2 AudioTrackPCM 播放的核心MediaPlayer 与 AudioTrack 的本质区别MediaPlayer 在 framework 层最终也会创建 AudioTrack把解码后的 PCM 数据流传递给 AudioTrack。MediaPlayer 适合播放现成的音频文件而 AudioTrack 则面向实时处理场景需要开发者自行处理 PCM 数据的生成、缓冲与传输。共享缓冲区机制AudioTrack 的低延迟核心源于共享缓冲区机制应用层初始化 AudioTrack 时与 AudioFlinger 协商创建一块共享内存缓冲区应用层通过write()方法将 PCM 数据写入共享缓冲区AudioFlinger 从共享缓冲区中“拉取”数据经过混音、格式转换后发送给 HALHAL 驱动音频硬件播放数据这种“应用层写入 → 系统层拉取”的模型避免了数据的多次拷贝显著降低延迟。两种播放模式STREAM 模式共享缓冲区是环形缓冲区适合持续播放大体积音频如音乐、通话STATIC 模式播放前一次性加载全部 PCM 数据适合短音效如提示音延迟极低四、Java 框架层JNI 与 AudioSystemJava 层的 AudioTrack、AudioManager 等 API 通过 JNI 调用 Native 层的 libmedia.so。JNI 部分代码位于frameworks/base/core/jni生成libandroid_runtime.so。AudioSystem 是 Java 框架层和 Native 层之间的桥梁通过 Binder 机制与 AudioFlinger 进行跨进程通信。AudioSystem 负责音量调节、音频设备选择、响铃模式选择等管理功能而 AudioTrack 负责音频流的数据传输。五、Native 核心层5.1 AudioFlinger音频中枢AudioFlinger 是 Android 音频系统的核心服务运行在 audioserver 进程中Android 8.0 之前运行在 mediaserver 进程中。它是所有音频流的最终归宿控制着音频的输出流程。核心功能多音频流混音将多个独立的音频流按优先级和音量规则混合成单一音频流。优先级规则为语音通话流 通知流 音乐流音频路由根据 AudioPolicyService 的决策将音频流导向正确的输出设备扬声器、耳机、蓝牙等音频格式转换统一不同应用输出的采样率、位深和声道数使其与硬件能力匹配与 Audio HAL 交互通过 HAL 接口将数据发送至驱动层主要组件AudioFlinger 主要包括 Mixer合并多个音频流、Track代表应用程序的音频流、Output输出到物理设备和 Effect音频效果处理四大模块。其核心实现位于frameworks/av/services/audioflinger/AudioFlinger.cpp。5.2 PlaybackThread音频输出线程每个输出设备对应一个 PlaybackThread 实例核心方法是threadLoop()。根据场景不同AudioFlinger 会选择不同类型的播放线程线程类型特点适用场景MixerThread支持混音、重采样、音量调节大多数普通播放场景DirectOutputThread绕过混音器要求格式与 HAL 严格匹配低延迟场景OffloadThread压缩音频硬件直通MP3/AAC 等格式的硬件解码DuplicatingThread一份输入复制到多个输出同时扬声器蓝牙播放以 MixerThread 为例其线程循环主要包括三个阶段prepareTracks_l()收集有数据的 Track 并计算混音权重 →threadLoop_mix()从各 Track 共享内存取出数据、重采样并累加到混音缓冲区 →threadLoop_write()将混音缓冲区写入 HAL。5.3 AudioPolicyService策略决策者AudioPolicyService 是策略的制定者比如决定什么时候打开音频接口设备、某种 Stream 类型的音频对应什么设备而 AudioFlinger 则是策略的执行者负责与音频设备通信、混音等具体操作。与 AudioFlinger 的职责分离维度AudioPolicyServiceAudioFlinger角色决策者指挥官执行者士兵职责路由策略、设备管理、音量策略、音频焦点混音、数据传输、资源管理典型协作场景当用户插入耳机时AudioPolicyService 检测设备状态变化决定新的路由策略然后通过 AudioFlinger 执行具体的硬件切换操作。音频策略优先级AudioPolicyManager 通过优先级系统解决多音频流竞争问题通话最高 通知音 媒体播放 辅助功能最低。5.4 数据流核心共享内存通信App 与 AudioFlinger 之间通过匿名共享内存进行数据传递实现零拷贝通信App 创建 AudioTrack 时通过IAudioFlinger::createTrack()发起 Binder 调用AudioFlinger 分配 Track 对象并通过 MemoryDealer 分配一块匿名共享内存共享内存头部包含audio_track_cblk_t控制块包含 user/server 读写指针、缓冲区大小、等待标志等AudioFlinger 将共享内存文件描述符通过 Binder 传回 App 进程App 端通过 mmap 将共享内存映射到自己的进程空间App 和 AudioFlinger 通过原子操作更新读写指针实现无锁通信写入流程App 调用AudioTrack::write()或obtainBuffer()releaseBuffer()将 PCM 数据拷贝到共享内存的环形缓冲区。写入位置由 user 指针指示AudioFlinger 侧的读取位置由 server 指针指示。当缓冲区满时App 进入等待由 Cblk 中的 futex 机制唤醒。六、HAL 层硬件抽象6.1 HAL 的作用Android 的音频硬件抽象层HAL将android.media中较高层级的音频专用框架 API 连接到底层的音频驱动程序和硬件。音频 HAL 定义了音频服务会调用的标准接口必须实现音频 HAL 才能使音频硬件正常运行。HAL 层的代码由各 SoC 厂商实现被编译成动态库如audio.primary.xxx.so供 AudioFlinger 通过dlopen动态加载。6.2 从 HIDL 到 AIDL 的演进版本接口定义语言说明Android 8.x - 13HIDLProject Treble 引入分离 Framework 和 HALAndroid 14 及以上AIDL统一使用 AIDL配置规范移至 HAL 内从 Android 14 开始使用 AIDL 定义音频 HAL 接口。AIDL 音频 HAL 包含 Core HAL播放和控制主 API、Effects HAL音频效果控制和 Common HAL共用数据结构三大模块。AIDL Core HAL 关键接口IModule.aidlAPI 入口点IStreamOut.aidl/IStreamIn.aidl音频流收发ITelephony.aidl电话功能控件IBluetooth.aidlBT SCO 和 HFP 控件七、驱动层从 TinyALSA 到 ALSA7.1 TinyALSAAndroid 的轻量级 ALSAAndroid 使用 TinyALSA 替代标准 ALSA-lib这是一个为嵌入式移动设备设计的轻量级用户空间库。其设计理念是“去插件化”——移除所有运行时插件机制音频格式转换等操作由上层处理精简 API 集合仅保留 PCM 和 Control 两类核心接口从 87 个 API 缩减到 23 个。TinyALSA 代码位于external/tinyalsa目录下主要提供三个用户空间工具tinyplay播放音频tinycap录制音频tinymix控制混音器参数7.2 从 TinyALSA 到内核驱动的完整路径以pcm_open为例分析 TinyALSA 如何调用到内核驱动第一步TinyALSA 打开设备节点// external/tinyalsa/pcm_hw.cstaticintpcm_hw_open(unsignedintcard,unsignedintdevice,unsignedintflags,void**data,void*node){snprintf(fn,sizeof(fn),/dev/snd/pcmC%uD%u%c,card,device,flagsPCM_IN?c:p);fdopen(fn,O_RDWR|O_NONBLOCK);}在 Linux 设备中音频节点的主设备号都是 116。例如/dev/snd/pcmC0D0p的主设备号为 116次设备号为 2。第二步内核中的设备注册在 Linux Kernel 中主设备号为 116 的设备通过alsa_sound_init()注册// sound/core/sound.cstaticint__initalsa_sound_init(void){if(register_chrdev(major,alsa,snd_fops)){// 主设备号 major CONFIG_SND_MAJOR 116}}注册的snd_fops文件操作结构体是所有 ALSA 音频设备的统一入口。第三步ASoC 驱动框架Android 内核中的音频驱动基于 ASoCALSA System on Chip框架分为三大组件Platform对应 SoC 的 DMA 引擎和 I2S 控制器Codec音频编解码芯片驱动Machine连接 Platform 和 Codec定义硬件板级的音频通路7.3 硬件数据传输内核声卡驱动使用 DMA 通道将样本从内存传输到声卡的硬件缓冲区。对于播放场景应用程序通过 DMA 将自己的缓冲区数据传送到声卡的硬件环形缓冲区中。具体数据路径HAL 模块的out_write()将用户空间的音频数据拷贝到内核 ALSA DMA 缓冲区DMA 控制器将 DMA 缓冲区中的数据拷贝到 SoC 的 I2S Tx FIFO通过 I2S 等数字音频接口将数据送往音频 CodecCodec 完成数模转换DAC后驱动扬声器或耳机发声Codec 与 SoC 之间通过 I2C 总线用于寄存器配置和 DAI数字音频接口用于音频数据传输进行通信。八、完整链路串联一次音频播放的全流程将以上所有层次串联起来一次音频播放的完整链路如下App 调用应用调用AudioTrack.write(PCM_data)写入共享内存PCM 数据被拷贝到 App 与 AudioFlinger 之间的匿名共享内存环形缓冲区Binder 通知App 通过 Binder 通知 AudioFlinger 有新数据可用AudioFlinger 拉取AudioFlinger 的 PlaybackThread 从共享内存读取数据混音处理MixerThread 将多个 Track 的数据混音并进行重采样、格式转换AudioPolicyService 决策根据设备状态耳机/扬声器/蓝牙决定输出路由HAL 调用AudioFlinger 调用 HAL 层的out_write()接口TinyALSAHAL 通过 TinyALSA 调用内核驱动接口ioctl或直接操作/dev/snd/*ALSA 驱动内核 ALSA 驱动接收数据放入 DMA 缓冲区DMA 传输DMA 控制器将数据通过 I2S 总线传输给 Codec硬件播放Codec 完成 DAC 转换驱动扬声器或耳机发声九、总结Android 音频系统是一个层次清晰、职责分明的复杂系统。从上层的应用 API 到底层的内核驱动每一层都承担着特定的职责应用层提供多样化的 APIMediaPlayer、AudioTrack、AAudio 等满足不同场景的音频播放需求框架层通过 JNI 连接 Java 和 NativeAudioSystem 提供系统级音频管理Native 核心层AudioFlinger 负责音频数据的混音、格式转换和路由执行AudioPolicyService 负责策略决策和设备管理两者通过共享内存实现高效的零拷贝数据传输HAL 层定义标准硬件接口从 HIDL 演进到 AIDL隔离厂商实现与系统框架驱动层TinyALSA 提供轻量级用户空间接口内核 ALSA/ASoC 框架完成真正的硬件控制DMA I2S Codec 实现最终的声音输出理解这一整套链路不仅有助于写出高性能、低延迟的音频程序也是进行音频系统定制和调试的必备基础。希望本文能为读者在 Android 音频开发之路上提供一份清晰的路线图。参考资料Android 音频架构全解析从 AudioTrack 到 AudioFlinger一文了解 Android 中的 AudioFlingerAndroid Audio 架构分析AIDL 和 HIDL 的 AudioHal 对比TinyALSA 全解析从 TinyALSA 到底层音频驱动的全流程分析Android 音频数据流笔记Android 音频架构核心深入解析 AudioPolicyServiceAndroid 音频设备切换背后的秘密AOSP 源码Android 14/15

更多文章