在嵌入式音频开发中,一套灵活、可扩展的驱动架构是实现高效音频处理的基础。openvela 音频驱动凭借分层设计、标准化接口和硬件解耦特性,为音频播放、录制等功能提供了稳定的底层支持。本文将深入解析其架构设计、核心模块及实现细节。

一、概述:分层架构的核心设计

openvela 音频驱动采用分层架构,通过抽象统一的接口实现硬件与软件的解耦,核心分为两部分:

  • 上半部分驱动Upper Half Driver):定义标准化外部接口和通用代码,提供默认的 ioctl 命令实现,负责与上层应用交互。
  • 下半部分驱动Lower Half Driver):专注于硬件交互逻辑(如音频设备控制、数据传输),由芯片厂商根据具体硬件实现,具备平台特异性。

这种设计的优势在于:提高代码复用性、降低硬件适配复杂度,开发者可通过扩展下半部分驱动快速适配新硬件,而上层应用无需修改。

二、软件层次与工具支持

openvela 音频驱动的软件层次从下到上分为:

  • 音频驱动(Audio Driver):封装硬件细节,为上层提供接口;
  • 音频应用(Audio Applications):基于驱动接口开发的多媒体应用(如音乐播放器)。

在这里插入图片描述

为方便调试,openvela 提供三个内置工具:

  • nxplayer:音频播放测试工具;
  • nxrecorder:音频录制测试工具;
  • nxlooper:辅助验证音频功能的工具。

三、代码目录与核心文件

音频驱动代码位于 nuttx/audio 目录,核心文件包括:

├── audio.c          # 核心驱动实现
├── audio_comp.c     # 复合音频设备支持
├── Kconfig          # 配置文件
├── Make.dep         # 依赖文件
├── Makefile         # 构建文件
└── README.txt       # 说明文档

该目录包含驱动的核心逻辑、配置脚本和构建规则,支持开发者根据需求扩展功能。

四、对外接口:标准化的交互方式

openvela 通过 ioctl 系统调用提供标准化接口,涵盖设备配置、状态控制、缓冲区管理等功能。以下结合代码实例说明核心接口的使用:

1. 基础操作流程(打开 - 配置 - 关闭)

// 打开音频设备(播放设备pcm0p)
int dev_fd = open("/dev/audio/pcm0p", O_RDWR | O_CLOEXEC);
if (dev_fd < 0) {
    // 错误处理
}

// 配置设备参数(如采样率、声道数)
struct audio_caps_s cap_desc = {
    .ac_type = AUDIO_TYPE_OUTPUT,
    .ac_controls.hw[0] = 44100,  // 采样率44.1kHz
    .ac_channels = 2             // 双声道
};
int ret = ioctl(dev_fd, AUDIOIOC_CONFIGURE, (unsigned long)&cap_desc);
if (ret != 0) {
    // 配置失败处理
}

// 关闭设备
close(dev_fd);

2. 核心接口说明

接口名称功能描述
AUDIOIOC_CONFIGURE配置音频设备参数(如采样率、声道数)
AUDIOIOC_START / AUDIOIOC_STOP启动 / 停止音频播放 / 录制
AUDIOIOC_PAUSE / AUDIOIOC_RESUME暂停 / 恢复音频操作
AUDIOIOC_ALLOCBUFFER / AUDIOIOC_FREEBUFFER分配 / 释放音频缓冲区
AUDIOIOC_ENQUEUEBUFFER向驱动提交音频数据缓冲区(播放)或空缓冲区(录音)

五、设备节点注册与代码实例

设备节点注册是上下层驱动建立关联的过程,核心通过 audio_registeraudio_comp_initialize 实现。

1. 单一设备注册(audio_register)

int audio_register(FAR const char *name, FAR struct audio_lowerhalf_s *dev)
{
  upper = (FAR struct audio_upperhalf_s *)kmm_zalloc(sizeof(struct audio_upperhalf_s));
  nxmutex_init(&upper->lock);
  upper->dev = dev;  // 关联下半部分驱动
  dev->upper = audio_callback;  // 设置上半部分回调
  dev->priv = upper;
  return register_driver(path, &g_audioops, 0666, upper);
}

示例:模拟平台注册 pcm0p 节点

// 原代码中sim_alsa.c的注册示例
audio_register("pcm0p", sim_audio_initialize(true, false));

2. 复合设备注册(audio_comp_initialize)

适用于多硬件组合场景(如 I2S+PA):

FAR struct audio_lowerhalf_s *audio_comp_initialize(FAR const char *name, ...)
{
  priv = kmm_zalloc(sizeof(struct audio_comp_priv_s));
  priv->export.ops = &g_audio_comp_ops;
  // 遍历不定长参数,组合多个下半部分驱动
  va_start(ap, name);
  while (va_arg(ap, FAR struct audio_lowerhalf_s *)) {
    priv->count++;
  }
  // 注册复合设备
  ret = audio_register(name, &priv->export);
  return &priv->export;
}

六、下半部分驱动:硬件交互的核心

下半部分驱动需实现 audio_ops_s 接口集,以下为三个典型实例:

  1. audio_dma:基于 DMA 的高效传输

    • 功能:基于 DMA 实现高效音频数据传输,避免额外数据拷贝。
    • 核心接口:实现 audio_ops_s 中的控制接口(如 start、stop)和数据接口(如 enqueuebuffer),直接调用 DMA 模块接口(如 DMA_START_CYCLIC)。
// audio_dma的操作接口实现
static const struct audio_ops_s g_audio_dma_ops = {
    .getcaps = audio_dma_getcaps,
    .configure = audio_dma_configure,
    .start = audio_dma_start,
    .stop = audio_dma_stop,
    .enqueuebuffer = audio_dma_enqueuebuffer,
    // 其他接口...
};

// 启动DMA传输
static int audio_dma_enqueuebuffer(struct audio_lowerhalf_s *dev,
                                   struct ap_buffer_s *apb)
{
  flags = enter_critical_section();
  dq_addlast(&apb->dq_entry, &audio_dma->pendq);  // 加入待处理队列
  leave_critical_section(flags);
  return OK;
}
  1. audio_i2s:I2S 设备支持

    • 功能:支持 I2S 接口的音频设备,负责 PCM 数据传输。
    • 核心接口:通过 i2s_ops_s 实现通道配置、采样率设置、数据发送 / 接收等,适配不同 I2S 硬件。
// I2S数据发送实现
static const struct audio_ops_s g_audio_i2s_ops =
{
  audio_i2s_getcaps,
  audio_i2s_configure,
  audio_i2s_shutdown,
  audio_i2s_start,
  audio_i2s_stop,
  // 其他接口...
};

//数据传输通过 i2s_send/i2s_receive 实现:
static int audio_i2s_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
                                   FAR struct ap_buffer_s *apb)
{
  if (audio_i2s->playback) {
    return I2S_SEND(i2s, apb, audio_i2s_callback, audio_i2s, 0);
  } else {
    return I2S_RECEIVE(i2s, apb, audio_i2s_callback, audio_i2s, 0);
  }
}
  1. sim_alsa:模拟平台适配

    • 功能:在模拟平台(sim)上桥接 openvela 驱动与主机 ALSA 系统,支持音频播放和录制。
    • 实现:借助主机 libmad(MP3 解码)和 lame(MP3 编码)库,支持压缩音频处理。
// 模拟平台播放实现(桥接ALSA)
static const struct audio_ops_s g_sim_audio_ops =
{
  .getcaps       = sim_audio_getcaps,
  .configure     = sim_audio_configure,
  .start         = sim_audio_start,
  .stop          = sim_audio_stop,
  .enqueuebuffer = sim_audio_enqueuebuffer,
  // 其他接口...
};

七、Compress 能力:压缩音频的处理

openvela 音频驱动也可以抽象出类似的 compress 节点,用于实现音频数据的解码和输出功能:

例如,在某些平台上,如果存在独立的 DSP(Digital Signal Processor)用于音频编解码,那么在适配 openvela 时,可以通过抽象 compress 节点来实现以下功能:

  • 应用程序通过 ENQUEUE 接口将压缩音频数据传递给 compress 节点。
  • 驱动内部通过 RPC 与 DSP 通信,完成音频数据的解码和播放。

总结

openvela 音频驱动通过分层架构实现了硬件解耦与功能扩展,其标准化接口和丰富的驱动实例为开发者提供了清晰的实现参考。无论是基于 DMA 的高效传输、I2S 设备适配,还是模拟平台的 ALSA 桥接,都能通过灵活的接口设计满足多样化的音频需求。结合代码实例理解这些原理,有助于快速上手驱动开发与硬件适配。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐