Audio学习9(AudioFlinger/HAL)

发布时间:2026/11/4 23:06:30
Audio学习9(AudioFlinger/HAL)

1 环境

这次主要是看看audio在安卓平台的实现,用是瑞芯微RK3568开发板。

本来是想还是用之前的WM8960模块,但是很无语发现它板子上的I2S接口并没有引出来。引出的口只有I2C,SPI,还有UART。

和之前用的WM8960比较一下:

参数

RK809-5

WM8960

芯片定位

PMIC + Audio Codec

独立 Audio Codec

ADC

24-bit 立体声

24-bit 立体声

DAC

24-bit 立体声

24-bit 立体声

最高采样率

192kHz

48kHz

I2S

支持

支持

PLL

内置

内置

麦克风输入

单端/差分

多路输入,支持伪差分

MICBIAS

支持麦克风输入,具体偏置配置看电路

集成低噪声 MICBIAS

ALC

支持

支持

Noise Gate

支持

支持

耳机功放

Class-AB,Real Ground

Class-AB 耳机驱动

扬声器功放

Class-D,最高约1.3W

双声道 Class-D,每声道1W(8Ω、5V)

控制接口

I2C

I2C

封装

QFN68,7×7mm

QFN,5×5mm

看来功能基本上都差不多,RK809甚至采样率还更好。不过WM8960 是专门设计的音频 Codec,在模拟输入、音频路由、增益控制和音频应用灵活性方面可能更好点。

算了,将就平台本来的audio去看吧,不折腾了。

2 整体架构

关于安卓的整体音频架构,之前写过一篇:Android音频系统_android uac-CSDN博客,不过之前没有实际用版本验证。下面这个图是流传很广的安卓音频架构的图。

不过用AI跑了一下,这个图估计已经是上古时代的了。整体应该还是有不少的变化。其实新的架构如下:

App (Java/Native)->AudioFlinger (系统服务,audioserver)->Binder IPC->android.hardware.audio.service(供应商 HAL 进程)->ALSA 驱动 (/dev/snd/*)->硬件音频 Codec / DAC / 喇叭

改动是:

原图这块板的实际情况
AudioFlinger、AudioPolicyService 放在Media Server中已拆分到audioserver;板上mediaserver仍存在,但不再包办这些音频服务
HAL 画成简单的一层调用板上有独立的android.hardware.audio.service进程,对外提供HIDL 7.0接口
内核写成 ALSA/OSS/Custom Driver本板模拟音频走ALSA+ASoC,包含 I2S 控制器驱动、RK809 Codec 驱动及声卡连接配置
框中的源码路径有些已经变化,例如 AudioTrack、AudioRecord 等客户端代码主要在frameworks/av/media/libaudioclient

新的核心就是两个,AudioServer和android.hardware.audio.service。一个是上层服务,一个是HAL。

3 底层

3.1 驱动

这次还是用板子实际来看吧。先用AI把这个板子上的音频链路跑了一遍。目前在电路上实际接了一个喇叭,一个麦克风,一个耳机插座。

RK3568 与 RK809之间的接口:

信号作用
MCLK音频主时钟
SCLK/BCLK音频位时钟
LRCK左右声道/帧同步时钟
RK3568 SDO0 → RK809 SDI播放数据
RK809 SDO → RK3568 SDI0录音数据

此外,RK3568 通过 I²C0、地址 0x20 控制 RK809,设置供电、音频通路、增益和静音等。I²C 负责配置,I2S 负责传声音数据。

目前在系统中已经有了三个底层声卡:

声卡编号声卡实际用途
0rockchip,rk809-codec板载麦克风、耳机、喇叭;支持录音和播放
1rockchip,btI2S3 接蓝牙模块的 SCO/PCM 语音链路
2rockchip,hdmiHDMI 音频输出

3.2 tinyalsa

Android这边底层和一般Linux基本没太大区别。不过ALSA的界面是阉割了,给了一个青春版tinyalsa,很多功能(混音、重采样、设备选择和音频路由等功能)收到自己的上层去处理了。

功能alsa-lib 可以提供的机制Android 通常由谁负责
多个应用同时播放、混音dmix等插件AudioFlinger
格式、采样率转换plug、`rate` 等插件音频框架及相应处理模块
选择输出设备设备名、配置及插件机制AudioPolicy+HAL
读写 PCM、操作 MixerALSA 接口TinyALSA

TinyALSA常见命令:

工具全称 / 作用对标 amixer核心用途参数要点
tinymixtiny mixer 控制amixer查看 / 设置 mixer 控件(路由、增益、开关)-D声卡号;-a列出枚举全部值;get看当前值;直接带名字和值 = 设置
tinyplaytiny player播放 wav放音测试,验证播放通路-D声卡;-pperiod;-nbuffer;只支持 wav
tinycaptiny capturearecord录音采集,验证录音通路-D;-r采样率;-c声道;-b位深;-pperiod;-T时长 (秒)
tinypcminfotiny pcm info—查看声卡 PCM 支持能力(格式 / 采样率)-D;-p查 playback;-c查 capture

常用命令组合:

场景命令序列备注
✅ 板载麦录音(推荐模板)

tinymix "Playback Path" OFF

tinymix "Capture MIC Path" "MAIN MIC"

tinycap /sdcard/main_mic.wav -T 8

关闭播放通路避免回授啸叫;录制 8 秒
✅ 带麦耳机录音(4 段耳机)

tinymix "Playback Path" HP

tinymix "Capture MIC Path" "HP MIC"

tinycap /sdcard/hp_mic.wav -T 8

耳机听 + 耳机麦收音
✅ 喇叭外放播放

tinymix "Playback Path" SPK

tinyplay /sdcard/test.wav

喇叭出声
✅ 无麦耳机播放(3 节耳机)

tinymix "Playback Path" HP_NO_MIC

tinyplay /sdcard/test.wav

只有听,没有麦克风
✅ 带麦耳机播放(4 段耳机)

tinymix "Playback Path" HP

tinyplay /sdcard/test.wav

耳机播放,后续可以切 HP MIC 录音
✅ 关闭全部音频通路

tinymix "Playback Path" OFF

tinymix "Capture MIC Path" "MIC OFF"

调试收尾,切断通路
✅ 自定义参数录音模板

tinymix "Capture MIC Path" "MAIN MIC"

tinycap /sdcard/rec.wav -D 0 -r 48000 -c 2 -b 16 -p 1024 -T 10

48k、立体声、16bit、period=1024 帧,录 10s

这些命令可以直接操作底层硬件,但是需要root权限,一般的app不能直接访问。需要上层接口。

4 android.hardware.audio.service

android.hardware.audio.service 是 Android 系统中负责音频硬件抽象层(Audio HAL)的核心服务进程。它是 Android 高层软件栈(如 AudioFlinger、Java 音频 API)与底层 Linux 内核音频驱动(ALSA / SoC 声音驱动)之间的“桥梁和翻译官”。

作用是:

  • 音频设备的控制与路由: 管理扬声器(Speaker)、听筒(Earpiece)、麦克风、蓝牙耳机、USB 声卡等硬件的开关、音量调节、音频路径切换(Routing)。

  • 数据流管理(Stream Management): 负责创建和维护音频输入流(IStreamIn,录音)和输出流(IStreamOut,播放)。通过共享内存(Shared Memory / FMQ 快速消息队列)高效接收来自AudioFlinger的 PCM 音频数据包。

  • 对接底层 ALSA 驱动: HAL 进程内部最终会调用 Linux 标准的alsa-lib(如snd_pcm_writei),将 PCM 数据写入内核的声卡驱动缓冲区,完成物理发声。

工作HAL 实际要处理的内容
选设备Android 要从耳机输出,具体应该打开哪张声卡、哪个 PCM 设备?
配置音频流使用什么采样率、位深、声道数?硬件是否支持?
配置缓冲区每次传多少帧、准备几块缓冲区,兼顾延迟和稳定性
打开硬件通路设置 Mixer,打开 PCM;处理打开失败的情况
持续传输数据将上层送来的 PCM 写入声卡;录音时反向读取
必要的数据转换例如你这份 RK 实现中的部分重采样处理
处理状态变化暂停后进入待机、恢复时重新打开;切换输出时关闭或重建相应通路
向上报告状态缓冲区大小、延迟、播放位置等,让 Android 安排后续数据并维持音画同步

4.1 Android定义

代码位置在/data/rk3568/android12_sdk/hardware/interfaces/audio/common/all-versions/default/service/

hardware/interfaces/应该所有的HAL接口定义都在这边。

服务的定义service.cpp如下:

/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "audiohalservice" #include <string> #include <vector> #include <binder/ProcessState.h> #include <cutils/properties.h> #include <hidl/HidlTransportSupport.h> #include <hidl/LegacySupport.h> #include <hwbinder/ProcessState.h> using namespace android::hardware; using android::OK; using InterfacesList = std::vector<std::string>; /** Try to register the provided factories in the provided order. * If any registers successfully, do not register any other and return true. * If all fail, return false. */ template <class Iter> static bool registerPassthroughServiceImplementations(Iter first, Iter last) { for (; first != last; ++first) { if (registerPassthroughServiceImplementation(*first) == OK) { return true; } } return false; } int main(int /* argc */, char* /* argv */ []) { ::android::ProcessState::initWithDriver("/dev/vndbinder"); // start a threadpool for vndbinder interactions ::android::ProcessState::self()->startThreadPool(); const int32_t defaultValue = -1; int32_t value = property_get_int32("persist.vendor.audio.service.hwbinder.size_kbyte", defaultValue); if (value != defaultValue) { ALOGD("Configuring hwbinder with mmap size %d KBytes", value); ProcessState::initWithMmapSize(static_cast<size_t>(value) * 1024); } configureRpcThreadpool(16, true /*callerWillJoin*/); // Automatic formatting tries to compact the lines, making them less readable // clang-format off const std::vector<InterfacesList> mandatoryInterfaces = { { "Audio Core API", "android.hardware.audio@7.0::IDevicesFactory", "android.hardware.audio@6.0::IDevicesFactory", "android.hardware.audio@5.0::IDevicesFactory", "android.hardware.audio@4.0::IDevicesFactory", }, { "Audio Effect API", "android.hardware.audio.effect@7.0::IEffectsFactory", "android.hardware.audio.effect@6.0::IEffectsFactory", "android.hardware.audio.effect@5.0::IEffectsFactory", "android.hardware.audio.effect@4.0::IEffectsFactory", } }; const std::vector<InterfacesList> optionalInterfaces = { { "Soundtrigger API", "android.hardware.soundtrigger@2.3::ISoundTriggerHw", "android.hardware.soundtrigger@2.2::ISoundTriggerHw", "android.hardware.soundtrigger@2.1::ISoundTriggerHw", "android.hardware.soundtrigger@2.0::ISoundTriggerHw", }, { "Bluetooth Audio API", "android.hardware.bluetooth.audio@2.1::IBluetoothAudioProvidersFactory", "android.hardware.bluetooth.audio@2.0::IBluetoothAudioProvidersFactory", }, // remove the old HIDL when Bluetooth Audio Hal V2 has offloading supported { "Bluetooth Audio Offload API", "android.hardware.bluetooth.a2dp@1.0::IBluetoothAudioOffload" } }; // clang-format on for (const auto& listIter : mandatoryInterfaces) { auto iter = listIter.begin(); const std::string& interfaceFamilyName = *iter++; LOG_ALWAYS_FATAL_IF(!registerPassthroughServiceImplementations(iter, listIter.end()), "Could not register %s", interfaceFamilyName.c_str()); } for (const auto& listIter : optionalInterfaces) { auto iter = listIter.begin(); const std::string& interfaceFamilyName = *iter++; ALOGW_IF(!registerPassthroughServiceImplementations(iter, listIter.end()), "Could not register %s", interfaceFamilyName.c_str()); } joinRpcThreadpool(); }

服务启动配置android.hardware.audio.service.rc:

service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service class hal user audioserver # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub capabilities BLOCK_SUSPEND ioprio rt 4 task_profiles ProcessCapacityHigh HighPerformance onrestart restart audioserver

当前这块板向 Android 音频框架提供的是 HIDL 7.0,主要接口如下:

接口主要职责代表方法
IDevicesFactory服务入口,打开音频设备openDevice()、`openPrimaryDevice()`
IDevice设备级控制、创建录放音流及连接通路openOutputStream()、`openInputStream()`、`setMicMute()`、`createAudioPatch()`、`setConnectedState()`
IPrimaryDevice继承IDevice,增加主音频设备、通话及蓝牙相关控制setMode()、`setVoiceVolume()`、`setBtScoWidebandEnabled()`
IStream输入、输出流共用的配置和生命周期接口getAudioProperties()、`setAudioProperties()`、`standby()`、`close()`
IStreamOut播放流、输出延迟、播放进度及暂停等控制prepareForWriting()、`getLatency()`、`getPresentationPosition()`、`pause()`、`drain()`
IStreamIn录音流、采集进度及麦克风信息prepareForReading()、`getCapturePosition()`、`getActiveMicrophones()`
IStreamOutCallback播放流异步通知onWriteReady()、`onDrainReady()`、`onError()`
IStreamOutEventCallback输出流事件通知onCodecFormatChanged()
IEffectsFactory枚举、查询和创建音效实例getAllDescriptors()、`getDescriptor()`、`createEffect()`
IEffect配置和运行具体音效setConfig()、`enable()`、`disable()`、`setParameter()`、`prepareForProcessing()`

4.2 RK适配

所有的适配代码在hardware/rockchip/

音频代码在:

主要的工作是:

/data/rk3568/android12_sdk/hardware/rockchip/audio/tinyalsa_hal/
├── audio_hw.c 声卡识别、HDMI/蓝牙/采集特殊处理
├── alsa_route.c 应用通路配置
└── codec_config/ 不同 Codec 的控件配置表

5 AudioServer

audioserver是 Android 音频的集中管理进程,主要负责音频流、混音、设备路由和硬件访问。它里面最核心的是 AudioFlinger 和 AudioPolicyService。

整体功能上和才写过的Linux的PipeWire差不多。

5.1 功能和实现

功能如下:

功能主要负责者具体作用
创建播放、录音通道AudioFlinger为 App 的 AudioTrack/AudioRecord 创建服务端音轨和录音通道
多路混音AudioFlinger将音乐、通知、导航等多个音轨混合输出
音量、静音及必要的格式转换AudioFlinger应用各音轨音量,按输出要求进行必要的重采样、声道等处理
音效处理AudioFlinger 及音效实现组织音效链,例如均衡器、录音预处理;实际支持取决于设备
选择设备和通路AudioPolicyService/AudioPolicyManager决定使用耳机、喇叭、蓝牙或 HDMI,以及使用哪个输入输出流
协调并发使用音频策略+AudioFlinger根据策略处理多个应用、录放音及特殊音频模式的资源使用
管理流的运行状态AudioFlinger启动、待机、关闭和资源回收,管理播放/采集线程与缓冲区
访问 Audio HAL经libaudiohal打开硬件音频流、读写数据、设置参数、查询延迟和播放位置

理论上其实app可以直接访问HAL。但 谷歌把它集中到audioserver,是为了让多个应用安全、有序地共享音频硬件。正常第三方 App 也没有直接访问音频 HAL 的权限。

场景如果各 App 直接操作 HALaudioserver提供的协调
音乐和导航同时发声争用硬件流,或各自重复实现混音AudioFlinger 统一混音
不同格式、采样率的声音每个 App 自己匹配硬件要求统一处理必要的转换
耳机插入或拔出多个 App 各自切换,容易冲突根据音频策略协调路由
一个 App 卡住或退出可能留下占用的资源统一管理和回收音频流
通话、录音、媒体同时工作各 App 难以协调全局状态配合系统策略管理并发和权限

代码在/frameworks/av/media/audioserver/

具体实现在其它地方:

# AudioFlinger:音频流、混音、录放音线程
frameworks/av/services/audioflinger/AudioFlinger.cpp

# 音频策略服务:对外提供策略接口
frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp

# 默认策略实现:设备选择、路由等决策
frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

一般情况下,audioserver使用AOSP原生的即可,不用怎么修改。

5.2 App调用

使用AudioTrack这套库,就可以直接调用audioserver。用的是JNI/Binder。

例子:

import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioTrack; private void playTone() { new Thread(() -> { int sampleRate = 48000; short[] pcm = new short[sampleRate]; // 单声道,1 秒 for (int i = 0; i < pcm.length; i++) { pcm[i] = (short) ( Math.sin(2 * Math.PI * 440 * i / sampleRate) * 2000 ); } AudioTrack track = new AudioTrack.Builder() .setAudioAttributes(new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()) .setAudioFormat(new AudioFormat.Builder() .setSampleRate(sampleRate) .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) .build()) .setTransferMode(AudioTrack.MODE_STATIC) .setBufferSizeInBytes(pcm.length * 2) .build(); try { track.write(pcm, 0, pcm.length); track.play(); while (track.getPlaybackHeadPosition() < pcm.length) { Thread.sleep(10); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { track.release(); } }).start(); }