4. 音频HAL HIDL接口解析与实现

任务说明

本案例实现音频HAL层输出流的打开与关闭操作,对应Android音频HAL接口中的open_output_stream和close_output_stream。核心目标是:当上层AudioFlinger请求打开输出流时,HAL能够正确创建并初始化音频输出流句柄;当上层关闭输出流时,HAL能够正确释放资源并置空句柄。实现范围限定为输出流管理,不涉及音频数据读写与路由策略。

核心数据结构

// audio_hal_local.h
#ifndef AUDIO_HAL_LOCAL_H
#define AUDIO_HAL_LOCAL_H

#include <hardware/audio.h>
#include <system/audio.h>
#include <cutils/log.h>
#include <stdlib.h>
#include <string.h>

// 自定义输出流对象,嵌入audio_stream_out作为首个成员
struct local_stream_out {
    struct audio_stream_out stream;  // HAL标准接口结构体,必须位于首位
    audio_devices_t devices;         // 输出设备类型
    uint32_t sample_rate;            // 采样率
    audio_format_t format;           // 音频格式
    audio_channel_mask_t channel_mask; // 通道掩码
    int handle;                      // 模拟硬件句柄,实际驱动中为文件描述符或DMA通道号
};

// HAL模块结构体
struct local_audio_device {
    struct audio_hw_device device;   // 标准HAL设备结构体
    struct local_stream_out *out_stream; // 当前活动的输出流
};

// 前向声明
static int adev_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);

static void adev_close_output_stream(struct audio_hw_device *dev,
                                     struct audio_stream_out *stream);

#endif // AUDIO_HAL_LOCAL_H

实现代码

// audio_hal_output.c
#include "audio_hal_local.h"

// 输出流标准接口函数集(仅实现必要的控制函数)
static uint32_t out_get_sample_rate(const struct audio_stream *stream)
{
    const struct local_stream_out *out = (const struct local_stream_out *)stream;
    return out->sample_rate;
}

static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
{
    struct local_stream_out *out = (struct local_stream_out *)stream;
    out->sample_rate = rate;
    return 0;
}

static size_t out_get_buffer_size(const struct audio_stream *stream)
{
    // 以16bit双声道为例:采样率/1000 * 帧大小(4字节) * 20ms
    const struct local_stream_out *out = (const struct local_stream_out *)stream;
    return (out->sample_rate / 1000) * 4 * 20;
}

static audio_channel_mask_t out_get_channels(const struct audio_stream *stream)
{
    const struct local_stream_out *out = (const struct local_stream_out *)stream;
    return out->channel_mask;
}

static audio_format_t out_get_format(const struct audio_stream *stream)
{
    const struct local_stream_out *out = (const struct local_stream_out *)stream;
    return out->format;
}

static int out_set_format(struct audio_stream *stream, audio_format_t format)
{
    struct local_stream_out *out = (struct local_stream_out *)stream;
    out->format = format;
    return 0;
}

static int out_standby(struct audio_stream *stream)
{
    // 模拟进入待机,实际驱动中在此关闭DMA或降低功耗
    (void)stream;
    return 0;
}

static int out_dump(const struct audio_stream *stream, int fd)
{
    const struct local_stream_out *out = (const struct local_stream_out *)stream;
    dprintf(fd, "local_stream_out: handle=%d rate=%u format=0x%x channels=0x%x\n",
            out->handle, out->sample_rate, out->format, out->channel_mask);
    return 0;
}

static int out_set_parameters(struct audio_stream *stream, const char *kvpairs)
{
    // 本案例不处理参数设置,仅返回成功
    (void)stream;
    (void)kvpairs;
    return 0;
}

static char *out_get_parameters(const struct audio_stream *stream, const char *keys)
{
    // 返回空字符串表示无参数
    (void)stream;
    (void)keys;
    return strdup("");
}

static uint32_t out_get_latency(const struct audio_stream_out *stream)
{
    // 模拟延迟:20ms
    (void)stream;
    return 20;
}

static int out_set_volume(struct audio_stream_out *stream, float left, float right)
{
    // 本案例不实现音量控制
    (void)stream;
    (void)left;
    (void)right;
    return -ENOSYS;
}

static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, size_t bytes)
{
    // 本案例仅管理流生命周期,不实现实际写入
    (void)stream;
    (void)buffer;
    (void)bytes;
    return -ENOSYS;
}

// 绑定输出流函数表
static void init_stream_out_ops(struct audio_stream_out *stream_out)
{
    struct audio_stream *stream = &stream_out->common;

    stream_out->get_latency = out_get_latency;
    stream_out->set_volume = out_set_volume;
    stream_out->write = out_write;

    stream->get_sample_rate = out_get_sample_rate;
    stream->set_sample_rate = out_set_sample_rate;
    stream->get_buffer_size = out_get_buffer_size;
    stream->get_channels = out_get_channels;
    stream->get_format = out_get_format;
    stream->set_format = out_set_format;
    stream->standby = out_standby;
    stream->dump = out_dump;
    stream->set_parameters = out_set_parameters;
    stream->get_parameters = out_get_parameters;
}

// 打开输出流
static int adev_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)
{
    struct local_audio_device *ladev = (struct local_audio_device *)dev;
    struct local_stream_out *out;
    int ret = 0;

    // 检查是否已有输出流打开(本案例仅支持单输出流)
    if (ladev->out_stream != NULL) {
        ALOGE("Output stream already open");
        return -EBUSY;
    }

    // 分配输出流对象
    out = (struct local_stream_out *)calloc(1, sizeof(struct local_stream_out));
    if (out == NULL) {
        return -ENOMEM;
    }

    // 初始化标准接口
    init_stream_out_ops(&out->stream);

    // 保存配置参数
    out->devices = devices;
    out->handle = (int)handle;

    // 若调用方提供了config则使用,否则使用默认值
    if (config != NULL) {
        out->sample_rate = config->sample_rate;
        out->format = config->format;
        out->channel_mask = config->channel_mask;
    } else {
        out->sample_rate = 48000;
        out->format = AUDIO_FORMAT_PCM_16_BIT;
        out->channel_mask = AUDIO_CHANNEL_OUT_STEREO;
    }

    // 将配置回填给调用方
    if (config != NULL) {
        config->sample_rate = out->sample_rate;
        config->format = out->format;
        config->channel_mask = out->channel_mask;
    }

    // 记录到设备结构体
    ladev->out_stream = out;
    *stream_out = &out->stream;

    ALOGI("Output stream opened: handle=%d rate=%u format=0x%x channels=0x%x",
          out->handle, out->sample_rate, out->format, out->channel_mask);

    return ret;
}

// 关闭输出流
static void adev_close_output_stream(struct audio_hw_device *dev,
                                     struct audio_stream_out *stream)
{
    struct local_audio_device *ladev = (struct local_audio_device *)dev;
    struct local_stream_out *out = (struct local_stream_out *)stream;

    // 校验是否为当前活动的输出流
    if (ladev->out_stream != out) {
        ALOGE("Attempting to close non-active stream");
        return;
    }

    // 释放资源
    ladev->out_stream = NULL;
    free(out);

    ALOGI("Output stream closed");
}

// HAL模块方法表
static int adev_close(hw_device_t *device)
{
    struct local_audio_device *ladev = (struct local_audio_device *)device;

    // 若仍有输出流未关闭,则强制关闭
    if (ladev->out_stream != NULL) {
        adev_close_output_stream(&ladev->device, &ladev->out_stream->stream);
    }

    free(ladev);
    return 0;
}

static int adev_open(const hw_module_t *module, const char *name,
                     hw_device_t **device)
{
    struct local_audio_device *ladev;

    if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) {
        return -EINVAL;
    }

    ladev = (struct local_audio_device *)calloc(1, sizeof(struct local_audio_device));
    if (ladev == NULL) {
        return -ENOMEM;
    }

    ladev->device.common.tag = HARDWARE_DEVICE_TAG;
    ladev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
    ladev->device.common.module = (struct hw_module_t *)module;
    ladev->device.common.close = adev_close;

    ladev->device.open_output_stream = adev_open_output_stream;
    ladev->device.close_output_stream = adev_close_output_stream;
    // 其他函数指针置NULL,表示不支持

    *device = &ladev->device.common;
    return 0;
}

static struct hw_module_methods_t hal_module_methods = {
    .open = adev_open,
};

struct audio_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "Local Audio HAL",
        .author = "Android HAL Course",
        .methods = &hal_module_methods,
    },
};

关键逻辑说明

打开输出流时,adev_open_output_stream首先检查设备结构体中是否已有活动输出流,若存在则返回-EBUSY,保证单输出流约束。随后分配local_stream_out内存,初始化标准接口函数表,将调用方传入的audio_config参数(采样率、格式、通道掩码)保存到流对象中。若调用方未提供配置,则使用48000Hz/16bit/立体声作为默认值,并将配置回填给调用方。最后将新流对象记录到设备结构体,通过输出参数返回标准接口指针。

关闭输出流时,adev_close_output_stream校验传入的流指针是否为当前活动流,防止误关。确认后释放内存并将设备结构体中的流指针置空。设备关闭函数adev_close会检查是否有未关闭的流并强制清理,避免资源泄漏。

SVG:输出流生命周期状态图

IDLE out_stream = NULL 无活动输出流 OPENING calloc + 初始化ops 保存config参数 ACTIVE out_stream != NULL 可进行write操作 CLOSING 校验流指针 free + 置NULL ERROR 返回 -EBUSY 或 -ENOMEM adev_open_output_stream() 成功返回0 adev_close_output_stream() free + 置NULL calloc失败 资源管理:local_stream_out(handle, sample_rate, format, channel_mask) | 设备结构体持有out_stream指针 | 单输出流互斥

编译与验证

# 编译(假设Android源码环境已就绪)
# 将audio_hal_local.h和audio_hal_output.c放入hardware/libhardware/modules/audio/目录
# 在Android.bp中添加编译规则:
# cc_library_shared {
#     name: "audio.primary.local",
#     srcs: ["audio_hal_output.c"],
#     shared_libs: ["libhardware", "libcutils", "liblog"],
# }

# 单元验证方法(使用C测试程序模拟调用)
# 编写测试程序test_hal.c:
# 1. hw_get_module(AUDIO_HARDWARE_MODULE_ID, &module) 加载模块
# 2. audio_hw_device_open(module, &dev) 打开设备
# 3. 构造audio_config config = {.sample_rate=44100, .format=AUDIO_FORMAT_PCM_16_BIT, .channel_mask=AUDIO_CHANNEL_OUT_MONO}
# 4. 调用dev->open_output_stream(dev, 1, AUDIO_DEVICE_OUT_SPEAKER, AUDIO_OUTPUT_FLAG_NONE, &config, &stream)
# 5. 断言stream != NULL,且config.sample_rate == 44100
# 6. 调用dev->close_output_stream(dev, stream)
# 7. 再次调用open_output_stream应成功(前一次已关闭)
# 8. 连续两次open_output_stream第二次应返回- EBUSY

# 判定标准:
# - open_output_stream返回0且stream指针非空
# - config参数正确回填
# - close_output_stream后再次open成功
# - 重复open返回- EBUSY
# - 使用dumpsys media.audio_flinger或logcat观察ALOGI输出

验证要点

  • 单流互斥:连续两次调用open_output_stream,第二次必须返回-EBUSY,且第一次返回的流指针仍然有效。
  • 参数回填:传入非默认audio_config(如44100Hz单声道),打开后检查config结构体中的值是否与传入一致。
  • 关闭后重开:关闭输出流后再次打开,应成功返回0,证明资源已正确释放。
  • 空指针保护:关闭一个非活动流指针时,函数应打印错误日志但不崩溃。