Android 15 音频子系统(八):Audio HAL 与硬件接口——音频数据的最后一公里

引言:最后一公里的旅程

如果把 Android 音频系统比作一条物流网络,那么 AudioFlinger 是"中央分拣中心",AudioPolicy 是"路由规划师",而 Audio HAL(Hardware Abstraction Layer)就是最终把包裹送到用户手里的"快递员"。

前几篇我们聊了 AudioFlinger 的混音魔法(第一篇)、AudioTrack 的数据传输(第二篇)、录音链路(第三篇)、音效系统(第四篇)、AudioPolicy 的路由决策(第五篇)、音频焦点(第六篇)和音量控制(第七篇)。今天我们来到整个系列的终点------Audio HAL 与硬件接口

这是音频数据真正"触地"的地方:PCM 数据从 AudioFlinger 的内存缓冲区,经过 HAL 接口、TinyALSA,最终通过 DMA 搬运到音频编解码器,再经 I2S 总线送到扬声器振膜,化为你耳中听到的声音。

整个过程涉及三个关键问题:

  1. HAL 是什么:framework 与 vendor 驱动之间的标准接口契约
  2. HAL 怎么进化:从 Legacy .so 直接加载到 HIDL 跨进程调用,再到 AIDL 的现代化设计
  3. HAL 怎么工作:openOutput → write → pcm_write → DMA 的完整数据链路

让我们一层一层揭开这"最后一公里"的神秘面纱。

一、Audio HAL 的历史演进:三代接口的进化史

1.1 Legacy HAL(Android 2.3 ~ 7.x):简单粗暴的直接加载

在 Project Treble 之前,Audio HAL 是最"朴素"的实现方式------一个 .so 动态库,由 audioserver 进程用 dlopen() 直接加载。

bash 复制代码
audioserver 进程
    │
    ├── dlopen("audio.primary.default.so")
    │       ↓
    │   audio_hw.c / audio_hw.cpp
    │       ↓
    │   TinyALSA / ALSA ioctl
    │       ↓
    │   /dev/snd/pcmC0D0p

Legacy HAL 的接口定义在 hardware/libhardware/include/hardware/audio.h

c 复制代码
// hardware/libhardware/include/hardware/audio.h
struct audio_hw_device {
    struct hw_device_t common;

    int (*open_output_stream)(struct audio_hw_device *dev,
                              audio_io_handle_t handle,
                              audio_devices_t devices,
                              audio_output_flags_t flags,
                              struct audio_config *config,
                              struct audio_stream_out **stream_out,
                              const char *address);

    int (*open_input_stream)(struct audio_hw_device *dev,
                             audio_io_handle_t handle,
                             audio_devices_t devices,
                             struct audio_config *config,
                             struct audio_stream_in **stream_in,
                             audio_input_flags_t flags,
                             const char *address,
                             audio_source_t source);
    // ...
};

struct audio_stream_out {
    struct audio_stream common;
    ssize_t (*write)(struct audio_stream_out* stream,
                     const void* buffer, size_t bytes);
    int (*get_render_position)(const struct audio_stream_out *stream,
                               uint32_t *dsp_frames);
    // ...
};

这种方式的问题很明显:system 分区和 vendor 分区紧耦合。每次 Android 大版本升级,OEM 厂商都需要重新编译整个 HAL 库,导致碎片化严重,OTA 更新成本极高。这就是为什么 Google 在 Android 8 推出了 Project Treble。

1.2 HIDL HAL(Android 8.0 ~ 12.x):Treble 的第一步

Project Treble 的核心目标是把 Android framework(system 分区)与 vendor 实现(vendor 分区)彻底分离。Audio HAL 迁移到 HIDL(Hardware Interface Definition Language)接口:

less 复制代码
audioserver (system 进程)          vendor HAL 进程 (android.hardware.audio@X.Y-service)
    │                                      │
    │   HwBinder IPC                       │
    │ ◄─────────────────────────────────► │
    │                                      │
IDevicesFactory                    audio.primary.so (vendor 实现)
    │                                      │
    └── openDevice()                       └── TinyALSA

主要接口层次(以 HIDL 7.0 为例):

接口 职责
IDevicesFactory 工厂,负责创建 IDevice 实例
IDevice 代表一个音频硬件设备,管理 stream
IStreamOut 播放输出流,提供 write() 方法
IStreamIn 录音输入流,提供 read() 方法
IStream IStreamOut / IStreamIn 的基接口
hidl 复制代码
// hardware/interfaces/audio/7.0/IDevice.hal
interface IDevice {
    openOutputStream(
        AudioIoHandle ioHandle,
        DeviceAddress device,
        AudioConfig config,
        bitfield<AudioOutputFlag> flags,
        SourceMetadata sourceMetadata
    ) generates (
        Result retval,
        IStreamOut outStream,
        AudioConfig suggestedConfig
    );

    openInputStream(
        AudioIoHandle ioHandle,
        DeviceAddress device,
        AudioConfig config,
        bitfield<AudioInputFlag> flags,
        SinkMetadata sinkMetadata
    ) generates (
        Result retval,
        IStreamIn inStream,
        AudioConfig suggestedConfig
    );
};

HIDL 的 HwBinder 与普通 Binder 相比,针对大数据传输有优化,但本质上仍是跨进程调用,每次 write() 都要经历一次进程间通信,带来额外延迟。

1.3 AIDL HAL(Android 13+ / Android 15 默认):现代化重设计

Android 13 引入了基于 AIDL(Android Interface Definition Language)的新版 Audio HAL,Android 15 中所有 GRF(Google Reference Framework)设备必须支持。AIDL HAL 相比 HIDL 有以下优势:

  1. 统一的 Binder:使用标准 Binder 而非 HwBinder,工具链统一
  2. 更简洁的接口:移除了历史包袱,重新设计了 API
  3. Stable AIDL:接口稳定性由 ABI 保证,支持向前兼容
  4. 更好的性能:减少了不必要的数据拷贝
对比项 Legacy HIDL AIDL
接口语言 C 头文件 .hal .aidl
通信方式 dlopen 直接调用 HwBinder IPC Binder IPC
进程隔离 无(同进程)
Android 版本 ≤ 7.x 8.0 ~ 12.x 13+
稳定性保证 HIDL ABI Stable AIDL

二、Treble 架构:system 与 vendor 的护城河

理解 Audio HAL 的现代实现,必须先理解 Treble 的架构边界。

bash 复制代码
┌─────────────────────────────────────────────────────────┐
│                    system 分区                           │
│                                                          │
│   audioserver (audioflinger + audiopolicy)               │
│   frameworks/av/services/audioflinger/                   │
│                                                          │
│               ↕ Binder IPC                               │
├─────────────────────────────────────────────────────────┤
│                  Stable AIDL 接口边界                    │
│   android.hardware.audio.core.IModule                    │
│   android.hardware.audio.core.IStreamOut                 │
├─────────────────────────────────────────────────────────┤
│                    vendor 分区                           │
│                                                          │
│   android.hardware.audio.service (vendor HAL 进程)       │
│   vendor/qcom/audio/ 或 vendor/mediatek/audio/           │
│                                                          │
│   audio.primary.so → TinyALSA → /dev/snd/               │
└─────────────────────────────────────────────────────────┘

关键特性

  • audioserver 运行在 system 进程中,属于 system 分区
  • android.hardware.audio.service 运行在独立的 vendor 进程中
  • 两者通过稳定的 AIDL 接口通信,接口变更受到严格版本控制
  • vendor 进程崩溃不会影响 audioserver,增强了系统稳定性

2.1 HAL 服务注册与发现

HAL 服务通过 servicemanager 注册,audioserver 通过 AIDL 代理获取:

cpp 复制代码
// frameworks/av/services/audioflinger/AudioFlinger.cpp
// AudioFlinger 初始化时获取 HAL 工厂

auto service = IServiceManager::getService("android.hardware.audio.core.IConfig/default");
// 或者对于 AIDL:
sp<IAudioHalService> halService;
auto status = AServiceManager_getService(
    "android.hardware.audio.service", &halService);

vendor HAL 进程在启动时注册自身:

cpp 复制代码
// vendor/xxx/audio/service/main.cpp
int main() {
    ABinderProcess_setThreadPoolMaxThreadCount(16);

    auto service = ndk::SharedRefBase::make<AudioHalService>();
    auto binder = service->asBinder();

    const std::string instance =
        std::string(IConfig::descriptor) + "/default";
    AServiceManager_addService(binder.get(), instance.c_str());

    ABinderProcess_joinThreadPool();
    return EXIT_FAILURE;
}

三、AIDL HAL 接口设计(Android 13+/15)

Android 15 的 Audio HAL AIDL 接口位于 hardware/interfaces/audio/aidl/,核心接口如下:

3.1 IModule:硬件模块抽象

IModule 是 AIDL HAL 的顶层接口,对应 HIDL 中的 IDevice,代表一个音频硬件模块:

aidl 复制代码
// hardware/interfaces/audio/aidl/android/hardware/audio/core/IModule.aidl
@VintfStability
interface IModule {
    // 打开输出流
    IStreamOut openOutputStream(in OpenOutputStreamArguments args,
                                out OpenOutputStreamReturn ret);

    // 打开输入流
    IStreamIn openInputStream(in OpenInputStreamArguments args,
                              out OpenInputStreamReturn ret);

    // 设置/获取音频端口配置
    AudioPortConfig[] getAudioPortConfigs();
    void setAudioPortConfig(in AudioPortConfig portConfig);

    // 连接/断开外部设备(如耳机插拔)
    void connectExternalDevice(in AudioPort templateIdAndAdditionalData);
    void disconnectExternalDevice(int portId);

    // 获取支持的音频格式
    AudioPort[] getAudioPorts();
    AudioRoute[] getAudioRoutes();

    // Master 音量/静音
    void setMasterVolume(float volume);
    float getMasterVolume();
    void setMasterMute(boolean mute);
    boolean getMasterMute();
}

OpenOutputStreamArguments 封装了所有打开输出流所需的参数:

aidl 复制代码
parcelable OpenOutputStreamArguments {
    // 流的唯一标识,对应 AudioFlinger 的 io_handle
    int portConfigId;
    // 源元数据(描述将要播放的内容)
    SourceMetadata sourceMetadata;
    // 音频配置(采样率、格式、声道数)
    @nullable AudioOffloadInfo offloadInfo;
    // Buffer 大小(帧数)
    long bufferSizeFrames;
    // 输出标志
    AudioOutputFlags[] flags;
}

3.2 IStreamOut:输出流接口

aidl 复制代码
// hardware/interfaces/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
@VintfStability
interface IStreamOut {
    // 写入 PCM 数据(核心方法)
    void write(in byte[] buffer);

    // 获取当前播放位置(帧数)
    long getPlaybackRateParameters();

    // 暂停/恢复/冲刷/关闭
    void pause();
    void resume();
    void flush();
    void drain(AudioDrain type);

    // Android 15 新增:精确的呈现位置
    // 用于 A/V 同步
    void updateOffloadPresentationPosition(in PresentationPosition position);

    // 获取延迟(毫秒)
    int getLatency();

    // 音量控制(用于 direct/offload stream)
    void setVolume(float left, float right);
}

3.3 IStreamIn:输入流接口

aidl 复制代码
@VintfStability
interface IStreamIn {
    // 读取 PCM 数据
    void read(inout byte[] buffer);

    // 获取输入增益(麦克风增益)
    float getInputFramesLost();

    // 获取捕获位置
    MmapPosition getCapturePosition();

    // Android 15 新增:多通道麦克风支持
    MicrophoneInfo[] getActiveMicrophones();
}

3.4 Android 15 AIDL HAL 的新特性

Android 15(AIDL version 3)引入了几个值得关注的新特性:

1. Bluetooth A2DP/LE Audio HAL 统一

Android 15 将蓝牙音频 HAL 从独立的 IBluetoothAudioProvider 整合进标准 Audio HAL 框架:

aidl 复制代码
// 新增 BluetoothA2dpCapabilities
parcelable BluetoothA2dpCapabilities {
    BluetoothA2dpCodec[] supportedCodecs;
    boolean supportsNonOffloaded;
    boolean supportsOffloaded;
}

2. Spatial Audio HAL 支持

aidl 复制代码
// IModule 新增空间音频接口
SpatializerCapabilities getSpatializerCapabilities();
void setSpatializerHeadTrackingMode(HeadTrackingMode mode);

3. Ultra-Low Latency (ULL) 路径优化

aidl 复制代码
// 新增 MMAP 零拷贝模式标志
const int AUDIO_OUTPUT_FLAG_MMAP_NOIRQ = 0x2000;

四、HAL 调用流程:音频数据的传递之旅

下图展示了从 AudioFlinger 到硬件的完整调用链路:

4.1 openOutput:建立输出通道

当 AudioFlinger 需要打开一个新的播放通道时,会调用 HAL 的 openOutputStream()

cpp 复制代码
// frameworks/av/services/audioflinger/AudioFlinger.cpp
// AudioFlinger::openOutput() 关键流程(简化)

status_t AudioFlinger::openOutput(
        const media::OpenOutputRequest& request,
        media::OpenOutputResponse* response)
{
    // 1. 找到对应的 HAL 模块
    sp<DeviceHalInterface> device = mAudioHWDevs[hwDevId]->hwDevice();

    // 2. 设置音频配置
    AudioConfig config;
    config.sample_rate = halConfig.sampleRate;
    config.channel_mask = halConfig.channelMask;
    config.format = halConfig.format;

    // 3. 调用 HAL 打开输出流(关键调用)
    sp<StreamOutHalInterface> outStream;
    status_t status = device->openOutputStream(
            *output,
            devices,
            flags,
            &config,
            address.c_str(),
            &outStream);

    // 4. 创建 PlaybackThread,绑定 outStream
    sp<PlaybackThread> thread = new MixerThread(this, outStream, *output, devices);
    mPlaybackThreads.add(*output, thread);

    return NO_ERROR;
}

在 AIDL HAL 的 vendor 实现中,openOutputStream() 会:

cpp 复制代码
// vendor/qcom/audio/aidl/AudioDevice.cpp(高通示例,简化)

ndk::ScopedAStatus AudioDevice::openOutputStream(
        const OpenOutputStreamArguments& args,
        OpenOutputStreamReturn* ret) {

    // 1. 解析音频参数
    struct pcm_config config;
    config.rate = args.portConfig.sampleRate.value;
    config.channels = audioChannelCountFromOutMask(args.portConfig.channelMask);
    config.format = tinyAlsaFormatFromAidl(args.portConfig.format.value);
    config.period_size = args.bufferSizeFrames;
    config.period_count = PLAYBACK_PERIOD_COUNT;  // 通常为 4

    // 2. 打开 ALSA PCM 设备
    struct pcm* pcm = pcm_open(SOUND_CARD, PLAYBACK_DEVICE,
                                PCM_OUT, &config);
    if (!pcm_is_ready(pcm)) {
        LOG(ERROR) << "pcm_open failed: " << pcm_get_error(pcm);
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }

    // 3. 创建输出流对象并返回
    auto stream = ndk::SharedRefBase::make<StreamOut>(pcm, config);
    ret->stream = stream;
    return ndk::ScopedAStatus::ok();
}

4.2 混音循环:AudioFlinger 的心跳

MixerThread::threadLoop() 是 AudioFlinger 的核心循环,以固定的时间间隔(通常 5~20ms)运行:

cpp 复制代码
// frameworks/av/services/audioflinger/Threads.cpp (简化)

bool AudioFlinger::MixerThread::threadLoop() {
    while (!exitPending()) {
        // 1. 等待新数据或超时
        waitForNextBuffer();

        // 2. 混音:将所有活跃 Track 的 PCM 数据混合
        mAudioMixer->process(pts);

        // 3. 对混音结果做音效处理
        processEffectChains_l(effectBuffer, frameCount);

        // 4. 写入 HAL(关键调用)
        ssize_t framesWritten = mOutput->write(
            mMixBuffer,
            mNormalFrameCount * mFrameSize);

        // 5. 更新时间戳
        updateTimestamp();
    }
    return false;
}

mOutput->write() 经由 AIDL Binder 调用到 vendor 进程的 StreamOut::write()

cpp 复制代码
// vendor HAL: StreamOut::write() 实现
ndk::ScopedAStatus StreamOut::write(
        const std::vector<uint8_t>& buffer,
        int64_t* framesWritten) {

    // 直接调用 TinyALSA 写入
    int ret = pcm_write(mPcm,
                        buffer.data(),
                        buffer.size());
    if (ret != 0) {
        LOG(ERROR) << "pcm_write error: " << pcm_get_error(mPcm);
        *framesWritten = 0;
        return ndk::ScopedAStatus::fromExceptionCode(EX_IO);
    }

    *framesWritten = buffer.size() / mFrameSize;
    return ndk::ScopedAStatus::ok();
}

4.3 时序分析:每一毫秒都很重要

对于音乐播放(44100Hz,stereo,16bit),典型的 timing 如下:

less 复制代码
AudioTrack.write() [App 进程]
    │ (Binder: ~0.1ms)
    ▼
AudioFlinger SharedBuffer 写入
    │ (等待混音周期)
    ▼
MixerThread::threadLoop() [每 ~5ms 触发一次]
    │  ├── 混音处理: ~0.5ms
    │  ├── 音效处理: ~0.2ms
    │  └── stream->write(): ~0.1ms (Binder 到 HAL)
    │
    ▼
StreamOut::write() [vendor HAL 进程]
    │
    ▼
pcm_write() → /dev/snd/pcmC0D0p [TinyALSA]
    │  ALSA ring buffer 还有 3~4 个周期的缓冲
    │
    ▼
DMA 引擎 [内核]
    │  自动搬运数据到 Audio Codec
    │
    ▼
I2S 总线 → Audio Codec → 扬声器

典型延迟(低延迟模式)

  • AudioTrack → AudioFlinger: ~20ms(默认双缓冲)
  • AudioFlinger → HAL write: ~5ms(一个混音周期)
  • HAL → 扬声器(DMA 缓冲): ~10ms
  • 总端到端延迟:3550ms(普通音乐播放)

对于游戏/专业音频,Android 提供了 MMAP 零拷贝模式,可将延迟降低到 ~10ms。

五、TinyALSA:内核音频接口

TinyALSA 是 Android 对标准 ALSA(Advanced Linux Sound Architecture)的轻量级封装,位于 external/tinyalsa/

5.1 核心数据结构

c 复制代码
// external/tinyalsa/include/tinyalsa/asoundlib.h

struct pcm_config {
    unsigned int channels;      // 声道数(1=mono, 2=stereo)
    unsigned int rate;          // 采样率(Hz,如 44100, 48000)
    unsigned int period_size;   // 每个 period 的帧数(如 960 帧 @ 48kHz = 20ms)
    unsigned int period_count;  // period 数量(ring buffer 大小 = period_size × period_count)
    enum pcm_format format;     // PCM_FORMAT_S16_LE 等
    unsigned int start_threshold;  // 触发 DMA 启动的阈值
    unsigned int stop_threshold;   // 触发停止的阈值
    unsigned int silence_threshold;
    int silence_size;
};

5.2 关键 API

c 复制代码
// 打开 PCM 设备
// card: 声卡编号(通常为 0)
// device: 设备编号(通常为 0,即 pcmC0D0)
// flags: PCM_OUT(播放)或 PCM_IN(录音)
struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, const struct pcm_config *config);

// 检查是否就绪
int pcm_is_ready(const struct pcm *pcm);

// 写入 PCM 数据(播放)
// 阻塞调用,直到数据被写入 ring buffer
int pcm_write(struct pcm *pcm, const void *data, unsigned int count);

// 读取 PCM 数据(录音)
int pcm_read(struct pcm *pcm, void *data, unsigned int count);

// 获取 ring buffer 中已有数据量
unsigned int pcm_avail_update(struct pcm *pcm);

// 关闭 PCM 设备
int pcm_close(struct pcm *pcm);

5.3 ALSA Ring Buffer 原理

TinyALSA 与内核 ALSA 之间通过 mmap 的共享内存实现零拷贝:

arduino 复制代码
AudioFlinger                  TinyALSA                 内核 ALSA driver
    │                             │                          │
    │  pcm_write(buf, bytes)      │                          │
    │ ─────────────────────────► │                          │
    │                             │  mmap 写入 ring buffer   │
    │                             │ ───────────────────────► │
    │                             │                          │ DMA 搬运
    │                             │                          │ ──────────────►
    │                             │                          │  Audio Codec
    │                             │                          │   ↓
    │                             │                          │  I2S 总线
    │                             │                          │   ↓
    │                             │                          │  扬声器

pcm_write() 的实际实现是通过 ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES) 通知内核有新数据可以搬运,然后 DMA 引擎会自动(中断驱动)将 ring buffer 中的数据搬运到音频编解码器的寄存器/FIFO。

5.4 /dev/snd 节点

bash 复制代码
# 查看声卡和设备节点
$ ls /dev/snd/
controlC0   # 控制设备(mixer controls)
pcmC0D0c    # PCM 录音设备(Card 0, Device 0, Capture)
pcmC0D0p    # PCM 播放设备(Card 0, Device 0, Playback)
pcmC0D1p    # Deep Buffer 播放设备(Card 0, Device 1)
timer       # ALSA timer 设备

# 查看声卡信息
$ cat /proc/asound/cards
 0 [msmnile        ]: msm-tavita-snd - msm-tavita-snd
                      msm-tavita-snd

# 查看 PCM 设备列表
$ cat /proc/asound/pcm
00-00: MultiMedia1 (*) : playback 1 : capture 1
00-01: MultiMedia2 (*) : playback 1
00-06: MultiMedia7 (*) : playback 1 : capture 1

六、Audio HAL 实现要点

下图展示了 Audio HAL 完整的架构层次:

6.1 vendor HAL 实现结构

一个典型的 vendor Audio HAL 实现(以类 QCOM 架构为例):

bash 复制代码
vendor/qcom/opensource/audio-hal/
├── primary-hal/
│   ├── hal/
│   │   ├── AudioDevice.cpp        # IModule 实现
│   │   ├── AudioStream.cpp        # IStreamOut / IStreamIn 实现
│   │   └── AudioEffects.cpp       # Effects HAL
│   ├── audio_extn/
│   │   ├── AudioExtn.cpp          # 扩展功能
│   │   └── ...
│   └── configs/
│       ├── audio_policy_configuration.xml  # 音频路由配置
│       └── mixer_paths.xml                 # ALSA mixer 路径配置
└── service/
    └── main.cpp                   # HAL 进程入口

6.2 mixer_paths.xml:ALSA mixer 控制

mixer_paths.xml 是 vendor HAL 的核心配置文件,定义了不同音频路径(路由)对应的 ALSA mixer control 设置:

xml 复制代码
<!-- vendor/qcom/audio/configs/msmnile/mixer_paths.xml 示例 -->
<mixer>
    <!-- 定义不同的音频路径 -->
    <path name="speaker">
        <ctl name="RX_CDC_DMA_RX_0 Audio Mixer MultiMedia1" value="1"/>
        <ctl name="RX INT0_1 MIX1 INP0" value="RX0"/>
        <ctl name="RDAC_CLK_CTL" value="1"/>
        <ctl name="WSA_SPK_PA_GAIN" value="6 dB"/>
    </path>

    <path name="headphones">
        <ctl name="RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1" value="1"/>
        <ctl name="RX INT1_1 MIX1 INP0" value="RX1"/>
        <ctl name="RX INT2_1 MIX1 INP0" value="RX2"/>
    </path>

    <path name="speaker-and-headphones">
        <path name="speaker"/>
        <path name="headphones"/>
    </path>

    <!-- 麦克风路径 -->
    <path name="handset-mic">
        <ctl name="TX_CDC_DMA_TX_3 Channels" value="One"/>
        <ctl name="TX_DEC0 MUX" value="SWR_MIC"/>
        <ctl name="ADC MUX0" value="DMIC"/>
        <ctl name="DMIC MUX0" value="DMIC0"/>
    </path>
</mixer>

vendor HAL 在路由切换时调用 mixer_ctl_set_value() 应用这些配置:

c 复制代码
// 路由切换示例(简化)
void AudioDevice::setOutputDevices(audio_devices_t devices) {
    struct mixer *mixer = mixer_open(SOUND_CARD);

    if (devices & AUDIO_DEVICE_OUT_SPEAKER) {
        applyMixerPath(mixer, "speaker");
    } else if (devices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
        applyMixerPath(mixer, "headphones");
    }

    mixer_close(mixer);
}

6.3 典型 out_write 实现

c 复制代码
// 完整的 out_write 实现(Legacy HAL 风格,也适用于 AIDL HAL 底层)
static ssize_t out_write(struct audio_stream_out *stream,
                         const void *buffer, size_t bytes)
{
    struct stream_out *out = (struct stream_out *)stream;
    struct audio_device *adev = out->dev;
    int ret = 0;

    pthread_mutex_lock(&out->lock);

    // 1. 如果流未打开,先打开 PCM 设备
    if (!out->pcm) {
        out->pcm = pcm_open(adev->card_id,
                            out->pcm_device_id,
                            PCM_OUT,
                            &out->config);
        if (!pcm_is_ready(out->pcm)) {
            ALOGE("pcm_open failed: %s", pcm_get_error(out->pcm));
            pcm_close(out->pcm);
            out->pcm = NULL;
            pthread_mutex_unlock(&out->lock);
            return -ENODEV;
        }
    }

    // 2. 写入数据
    ret = pcm_write(out->pcm, buffer, bytes);
    if (ret != 0) {
        ALOGE("pcm_write error: %s", pcm_get_error(out->pcm));
        // 尝试恢复
        pcm_close(out->pcm);
        out->pcm = NULL;
    }

    // 3. 更新写入帧数(用于时间戳计算)
    out->written += bytes / audio_stream_out_frame_size(stream);

    pthread_mutex_unlock(&out->lock);
    return (ret == 0) ? (ssize_t)bytes : ret;
}

6.4 Hardware Effects / DSP Offload

现代 SoC(如高通 Snapdragon、联发科 Dimensity)通常集成了独立的 DSP 协处理器,可以卸载音效处理任务,降低 AP 功耗。

scss 复制代码
AudioFlinger (AP 侧)
    │
    │  写入原始 PCM 数据
    ▼
Audio HAL (AP 侧)
    │
    │  offload 到 DSP
    ▼
DSP 处理 (独立核心)
    ├── Equalizer(EQ 均衡器)
    ├── Reverb(混响)
    ├── DRC(动态范围控制)
    └── 噪声消除 / 回声消除 (AEC)
    │
    ▼
Audio Codec → 扬声器/耳机

AIDL HAL 中的 Effects 接口:

aidl 复制代码
// hardware/interfaces/audio/aidl/android/hardware/audio/effect/IEffect.aidl
@VintfStability
interface IEffect {
    void open(in Parameter.Common common,
              in @nullable Parameter.Specific specific,
              out OpenEffectReturn ret);
    void close();
    void command(in CommandId commandId);
    Status process(MQDescriptor<PCM, SynchronizedReadWrite> inBuffer,
                   MQDescriptor<PCM, SynchronizedReadWrite> outBuffer);
}

七、调试技巧:当声音出问题时

7.1 dumpsys 诊断

bash 复制代码
# 查看 AudioFlinger HAL 状态
$ adb shell dumpsys media.audio_flinger

# 输出示例(关键部分):
AudioHwDevice 0x7f8b2c0 primary:
  hw_ptr framesRead:1234567890 timeNs:1736581515285000
  mHALFrameCount:960
  mSamplingRate:48000 mFormat:0x1 mChannelMask:0x3
  Audio hardware state: NORMAL
  Output stream is at:0x7f8b380
  Output stream HAL latency: 22 ms

# 查看 ALSA 状态
$ adb shell cat /proc/asound/card0/pcm0p/sub0/status
state: RUNNING
owner_pid   : 456    (audioserver)
trigger_time: 12345.678901234
tstamp      : 12345.689012345
delay       : 960    (frames)
avail       : 7680
avail_max   : 8640

7.2 tinyplay / tinycap 工具

bash 复制代码
# 播放 WAV 文件(直接测试 HAL)
$ adb shell tinyplay /sdcard/test.wav -D 0 -d 0 -r 48000 -c 2

# 录音测试
$ adb shell tinycap /sdcard/capture.wav -D 0 -d 0 -r 48000 -c 2 -b 16

# 查看 ALSA mixer controls
$ adb shell tinymix --card 0

# 设置特定 mixer control
$ adb shell tinymix --card 0 "RX_CDC_DMA_RX_0 Audio Mixer MultiMedia1" 1

tinyplay 绕过整个 Android 音频框架,直接测试 HAL 底层 ,是排查 codec 驱动问题的利器。如果 tinyplay 能播放但 Android 应用不能,问题在 AudioFlinger 及以上层;如果 tinyplay 也失败,问题在 HAL/驱动层。

7.3 logcat 过滤

bash 复制代码
# 过滤 AudioFlinger 相关日志
$ adb logcat -s AudioFlinger:V AudioHAL:V r_submix:E

# 关键日志关键字
# "openOutput" → HAL openOutputStream 调用
# "pcm_open" → TinyALSA 打开设备
# "pcm_write error" → 写入失败
# "underrun" → 播放缓冲区不足(可能导致爆音)
# "overrun" → 录音缓冲区溢出

7.4 常见问题排查

问题1:播放无声音

排查路径:

bash 复制代码
检查 mixer_paths.xml 对应路径是否正确
    → tinymix 查看 mixer controls 是否已设置
        → 检查 pcm_open 是否成功
            → 检查 /dev/snd/pcmC0D0p 权限

问题2:爆音/杂音

通常是 underrun(欠载)导致:

bash 复制代码
$ adb shell cat /proc/asound/card0/pcm0p/sub0/status | grep "avail\|delay"
delay : 240   # 缓冲中的帧数(应大于 period_size)
avail : 7440  # 可写入的帧数

# 如果 delay 持续接近 0,说明 AudioFlinger 写入太慢 → underrun

调优方案:增大 period_count 或减少 period_size,平衡延迟和稳定性:

c 复制代码
// 低延迟配置(可能有更多 underrun 风险)
config.period_size = 240;     // 5ms @ 48kHz
config.period_count = 2;      // 2 × 5ms = 10ms 缓冲

// 稳定配置(更大缓冲,延迟更高)
config.period_size = 960;     // 20ms @ 48kHz
config.period_count = 4;      // 4 × 20ms = 80ms 缓冲

问题3:录音有回声

AEC(Acoustic Echo Cancellation)配置问题:

bash 复制代码
# 检查是否启用了 AEC 音效
$ adb shell dumpsys media.audio_flinger | grep -i "echo\|aec"

# 检查麦克风 mixer 路径
$ adb shell tinymix | grep -i "mic\|ADC"

7.5 性能分析:Perfetto/Systrace

bash 复制代码
# 使用 Systrace 捕获音频延迟
$ adb shell atrace --async_start -b 32768 audio hal binder

# 捕获 10 秒
$ sleep 10

$ adb shell atrace --async_stop > audio_trace.html

# 在 Perfetto UI 中分析:
# - MixerThread::threadLoop() 执行时间
# - write() 到 HAL 的 Binder 延迟
# - DMA 触发间隔

八、系列总结:八篇之旅的终点与新起点

至此,我们完成了 Android 15 音频子系统系列的全部 8 篇内容。让我们用一张"全景图"回顾整个旅程:

scss 复制代码
用户视角                    Android 音频栈                    硬件视角
─────────────────────────────────────────────────────────────────────

[用户点击播放]

  App 调用 MediaPlayer
  或 AudioTrack.write()           ← 第2篇:AudioTrack 与播放链路
        │
        ▼ Binder IPC
  AudioFlinger (audioserver)
  ├── MixerThread 混音            ← 第1篇:AudioFlinger 架构基础
  ├── EffectChain 音效            ← 第4篇:音效系统与 AudioEffect
  └── 音量控制(Q4.12)           ← 第7篇:音量控制系统
        │
        ▼ Binder IPC
  AudioPolicyService              ← 第5篇:AudioPolicy 路由决策
  ├── Engine 路由决策
  └── 设备选择(喇叭/耳机/BT)
        │
        ▼(同进程通知)
  AudioService (system_server)
  ├── 音量分级管理                ← 第7篇(续)
  └── 音频焦点仲裁                ← 第6篇:音频焦点管理
        │
        ▼ AIDL Binder IPC
  vendor HAL 进程                  ← 第8篇(本篇):Audio HAL
  ├── IModule / IStreamOut
  ├── TinyALSA pcm_write()
  └── ALSA ring buffer
        │
        ▼ DMA
  Audio Codec (硬件)
        │
        ▼ I2S 总线
  扬声器/耳机 → 声音✓

[同时,麦克风拾音]             ← 第3篇:音频录制系统
  AudioRecord → AudioFlinger

八篇文章串起的核心知识点

篇章 主题 核心知识
第1篇 AudioFlinger 架构 Binder IPC、线程模型、SharedBuffer
第2篇 AudioTrack 播放 双缓冲、同步机制、低延迟路径
第3篇 音频录制 AudioRecord、MMAP 模式、AEC
第4篇 音效系统 AudioEffect、插入/辅助音效、链式处理
第5篇 AudioPolicy 路由决策、策略引擎、XML 配置
第6篇 音频焦点 焦点栈、GAIN 类型、自动闪避
第7篇 音量控制 三层架构、对数曲线、Q4.12 定点
第8篇 Audio HAL AIDL接口、TinyALSA、DMA链路

写给最后的你:音频系统是 Android 中少有的"全栈穿透"系统------从 Java API 到 Native C++,从 Binder IPC 到内核驱动,从软件混音到 DMA 硬件操作,每一层都有其独特的设计哲学和工程取舍。

当你下次听到从 Android 设备中流出的音乐时,不妨想一想:这段旋律走过了多少层代码、穿越了多少道 IPC 边界、经历了多少次格式转换,才最终化为空气中的振动,传入你的耳朵。

这就是 Android 音频系统的魅力------它把复杂的底层细节优雅地隐藏在一个简单的 audioTrack.play() 调用之后,而理解这些细节,正是成为优秀 Android 系统工程师的必经之路。

系列完结:如果这个系列对你有帮助,欢迎关注后续的 Android 视频子系统、Binder IPC 深度解析等系列文章。技术的旅程永无止境,我们下个系列见!

相关推荐
黄林晴5 小时前
Compose Multiplatform 1.10 发布:里程碑式更新!
android
流星白龙5 小时前
【MySQL】19.MySQL用户管理
android·mysql·adb
匆忙拥挤repeat5 小时前
Android Compose 可组合项的生命周期、副作用API
android
hnlgzb6 小时前
目前编写安卓app的话有哪几种设计模式?
android·设计模式·kotlin·android jetpack·compose
studyForMokey7 小时前
【Android面试】Fragment生命周期专题
android·microsoft·面试
Android系统攻城狮8 小时前
Android tinyalsa深度解析之pcm_plugin_open调用流程与实战(一百七十四)
android·pcm·tinyalsa·音频进阶手册
用户622386252179 小时前
Android 列表控件实战:从 ListView 到 RecyclerView,仿今日头条 HeadLine 项目全解析
android
呦呼4579 小时前
Android 仿今日头条项目分析
android
Android系统攻城狮9 小时前
Android tinyalsa深度解析之pcm_params_set_max调用流程与实战(一百七十)
android·pcm·tinyalsa·android音频进阶