2026/11/4 23:05:05

Audio学习9(AudioFlinger/HAL)

Audio学习9(AudioFlinger/HAL) 1 环境这次主要是看看audio在安卓平台的实现用是瑞芯微RK3568开发板。本来是想还是用之前的WM8960模块但是很无语发现它板子上的I2S接口并没有引出来。引出的口只有I2CSPI还有UART。和之前用的WM8960比较一下参数RK809-5WM8960芯片定位PMIC Audio Codec独立 Audio CodecADC24-bit 立体声24-bit 立体声DAC24-bit 立体声24-bit 立体声最高采样率192kHz48kHzI2S支持支持PLL内置内置麦克风输入单端/差分多路输入支持伪差分MICBIAS支持麦克风输入具体偏置配置看电路集成低噪声 MICBIASALC支持支持Noise Gate支持支持耳机功放Class-ABReal GroundClass-AB 耳机驱动扬声器功放Class-D最高约1.3W双声道 Class-D每声道1W8Ω、5V控制接口I2CI2C封装QFN687×7mmQFN5×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接口内核写成 ALSAOSSCustom Driver本板模拟音频走ALSAASoC包含 I2S 控制器驱动、RK809 Codec 驱动及声卡连接配置框中的源码路径有些已经变化例如 AudioTrack、AudioRecord 等客户端代码主要在frameworks/av/media/libaudioclient新的核心就是两个AudioServer和android.hardware.audio.service。一个是上层服务一个是HAL。3 底层3.1 驱动这次还是用板子实际来看吧。先用AI把这个板子上的音频链路跑了一遍。目前在电路上实际接了一个喇叭一个麦克风一个耳机插座。RK3568 与 RK809之间的接口信号作用MCLK音频主时钟SCLKBCLK音频位时钟LRCK左右声道帧同步时钟RK3568 SDO0 → RK809 SDI播放数据RK809 SDO → RK3568 SDI0录音数据此外RK3568 通过 I²C0、地址 0x20 控制 RK809设置供电、音频通路、增益和静音等。I²C 负责配置I2S 负责传声音数据。目前在系统中已经有了三个底层声卡声卡编号声卡实际用途0rockchip,rk809-codec板载麦克风、耳机、喇叭支持录音和播放1rockchip,btI2S3 接蓝牙模块的 SCOPCM 语音链路2rockchip,hdmiHDMI 音频输出3.2 tinyalsaAndroid这边底层和一般Linux基本没太大区别。不过ALSA的界面是阉割了给了一个青春版tinyalsa很多功能混音、重采样、设备选择和音频路由等功能收到自己的上层去处理了。功能alsa-lib 可以提供的机制Android 通常由谁负责多个应用同时播放、混音dmix等插件AudioFlinger格式、采样率转换plug、rate 等插件音频框架及相应处理模块选择输出设备设备名、配置及插件机制AudioPolicyHAL读写 PCM、操作 MixerALSA 接口TinyALSATinyALSA常见命令工具全称 / 作用对标 amixer核心用途参数要点tinymixtiny mixer 控制amixer查看 / 设置 mixer 控件路由、增益、开关-D声卡号-a列出枚举全部值get看当前值直接带名字和值 设置tinyplaytiny player播放 wav放音测试验证播放通路-D声卡-pperiod-nbuffer只支持 wavtinycaptiny capturearecord录音采集验证录音通路-D-r采样率-c声道-b位深-pperiod-T时长 (秒)tinypcminfotiny pcm info—查看声卡 PCM 支持能力格式 / 采样率-D-p查 playback-c查 capture常用命令组合场景命令序列备注✅ 板载麦录音推荐模板tinymix Playback Path OFFtinymix Capture MIC Path MAIN MICtinycap /sdcard/main_mic.wav -T 8关闭播放通路避免回授啸叫录制 8 秒✅ 带麦耳机录音4 段耳机tinymix Playback Path HPtinymix Capture MIC Path HP MICtinycap /sdcard/hp_mic.wav -T 8耳机听 耳机麦收音✅ 喇叭外放播放tinymix Playback Path SPKtinyplay /sdcard/test.wav喇叭出声✅ 无麦耳机播放3 节耳机tinymix Playback Path HP_NO_MICtinyplay /sdcard/test.wav只有听没有麦克风✅ 带麦耳机播放4 段耳机tinymix Playback Path HPtinyplay /sdcard/test.wav耳机播放后续可以切 HP MIC 录音✅ 关闭全部音频通路tinymix Playback Path OFFtinymix Capture MIC Path MIC OFF调试收尾切断通路✅ 自定义参数录音模板tinymix Capture MIC Path MAIN MICtinycap /sdcard/rec.wav -D 0 -r 48000 -c 2 -b 16 -p 1024 -T 1048k、立体声、16bit、period1024 帧录 10s这些命令可以直接操作底层硬件但是需要root权限一般的app不能直接访问。需要上层接口。4 android.hardware.audio.serviceandroid.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::vectorstd::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_castsize_t(value) * 1024); } configureRpcThreadpool(16, true /*callerWillJoin*/); // Automatic formatting tries to compact the lines, making them less readable // clang-format off const std::vectorInterfacesList mandatoryInterfaces { { Audio Core API, android.hardware.audio7.0::IDevicesFactory, android.hardware.audio6.0::IDevicesFactory, android.hardware.audio5.0::IDevicesFactory, android.hardware.audio4.0::IDevicesFactory, }, { Audio Effect API, android.hardware.audio.effect7.0::IEffectsFactory, android.hardware.audio.effect6.0::IEffectsFactory, android.hardware.audio.effect5.0::IEffectsFactory, android.hardware.audio.effect4.0::IEffectsFactory, } }; const std::vectorInterfacesList optionalInterfaces { { Soundtrigger API, android.hardware.soundtrigger2.3::ISoundTriggerHw, android.hardware.soundtrigger2.2::ISoundTriggerHw, android.hardware.soundtrigger2.1::ISoundTriggerHw, android.hardware.soundtrigger2.0::ISoundTriggerHw, }, { Bluetooth Audio API, android.hardware.bluetooth.audio2.1::IBluetoothAudioProvidersFactory, android.hardware.bluetooth.audio2.0::IBluetoothAudioProvidersFactory, }, // remove the old HIDL when Bluetooth Audio Hal V2 has offloading supported { Bluetooth Audio Offload API, android.hardware.bluetooth.a2dp1.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.rcservice 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 AudioServeraudioserver是 Android 音频的集中管理进程主要负责音频流、混音、设备路由和硬件访问。它里面最核心的是 AudioFlinger 和 AudioPolicyService。整体功能上和才写过的Linux的PipeWire差不多。5.1 功能和实现功能如下功能主要负责者具体作用创建播放、录音通道AudioFlinger为 App 的 AudioTrackAudioRecord 创建服务端音轨和录音通道多路混音AudioFlinger将音乐、通知、导航等多个音轨混合输出音量、静音及必要的格式转换AudioFlinger应用各音轨音量按输出要求进行必要的重采样、声道等处理音效处理AudioFlinger 及音效实现组织音效链例如均衡器、录音预处理实际支持取决于设备选择设备和通路AudioPolicyServiceAudioPolicyManager决定使用耳机、喇叭、蓝牙或 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(); }