5.21 Audio设备
2026/7/20大约 8 分钟设备驱动驱动Audio音频I2S播放器
5.21 Audio设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、音频基础知识、5.1 设备驱动框架
🎯 学习目标
- 理解 OneOS Audio 设备驱动的三层架构(应用层 → 播放器框架 → I2S 硬件层)
- 掌握 Audio 设备的核心数据结构(
os_audio_device、os_audio_ops、os_audio_caps) - 掌握音频播放器的内存池缓冲队列机制与 TX 写完回调
- 理解 I2S 硬件抽象层的操作接口
- 掌握音频参数配置(采样率、声道数、采样位宽、音量)
一、概述
Audio 音频设备是 OneOS 提供的多媒体音频驱动框架,支持音频播放(Playback)和录音(Record)功能。驱动源码位于 drivers/audio/ 目录下,核心头文件为 audio.h,实现文件为 audio_player.c。I2S 硬件抽象层位于 drivers/audio/i2s.h。
Audio 设备框架的特点:
- 三层架构:应用层调用
os_device_write()写入 PCM 数据 → 播放器框架管理内存池缓冲队列 → I2S 硬件层驱动 DMA 传输 - 内存池缓冲队列:使用
os_mempool管理音频数据块,通过tx_count/tx_size实现背压流控 - TX 完成回调:通过
OS_DEVICE_CB_TYPE_TX回调自动归还已播放的缓冲区,形成生产者-消费者模型 - I2S 硬件抽象:
os_i2s_device将 I2S 外设抽象为独立设备,支持 TX/RX 使能、频率/声道/位宽配置 - 统一控制接口:通过
os_device_control()配置音频参数(采样率、声道、音量等)
二、核心数据结构
2.1 Audio 设备结构体 struct os_audio_device
struct os_audio_device
{
struct os_device parent; /* 继承自 os_device_t */
const struct os_audio_ops *ops; /* Audio 操作函数表 */
struct os_audio_replay *replay; /* 播放器控制块 */
struct os_audio_record *record; /* 录音控制块 */
};Audio 设备继承自 os_device_t,设备类型注册为 OS_DEVICE_TYPE_SOUND。通过 replay 和 record 分别管理播放和录音功能。
2.2 Audio 操作函数表 struct os_audio_ops
struct os_audio_ops
{
os_err_t (*getcaps)(struct os_audio_device *audio, struct os_audio_caps *caps);
os_err_t (*configure)(struct os_audio_device *audio, struct os_audio_caps *caps);
os_err_t (*init)(struct os_audio_device *audio);
os_err_t (*deinit)(struct os_audio_device *audio);
os_err_t (*start)(struct os_audio_device *audio);
os_err_t (*stop)(struct os_audio_device *audio);
os_size_t (*transmit)(struct os_audio_device *audio, const void *writeBuf, os_size_t size);
os_size_t (*receive)(struct os_audio_device *audio, void *readBuf, os_size_t size);
};| 函数指针 | 说明 |
|---|---|
getcaps | 获取音频设备能力(支持的采样率/声道/位宽等) |
configure | 配置音频参数(采样率、声道、位宽等) |
init | 初始化音频硬件(I2S/DMA 配置) |
deinit | 去初始化音频硬件 |
start | 启动音频播放/录音 |
stop | 停止音频播放/录音 |
transmit | 硬件发送一帧音频数据(由 HAL 层实现) |
receive | 硬件接收一帧音频数据(由 HAL 层实现) |
2.3 音频配置结构体 struct os_audio_configure
struct os_audio_configure
{
uint32_t samplerate; /* 采样率,如 44100 */
uint16_t channels; /* 声道数,1=单声道,2=立体声 */
uint16_t samplebits; /* 采样位宽,如 16bit */
uint32_t recorde_fifo_size; /* 录音 FIFO 大小 */
};2.4 音频能力结构体 struct os_audio_caps
struct os_audio_caps
{
int config_type; /* 配置类型:AUDIO_PARAM_CMD(0) 或 AUDIO_VOLUME_CMD(1) */
union
{
int value; /* 音量值 */
struct os_audio_configure config; /* 音频参数配置 */
} udata;
};配置类型:
| 宏定义 | 值 | 说明 |
|---|---|---|
AUDIO_PARAM_CMD | 0 | 设置全部参数(采样率+声道+位宽) |
AUDIO_VOLUME_CMD | 1 | 仅设置音量 |
2.5 音频播放器控制块 struct os_audio_replay
struct os_audio_replay
{
os_mempool_dummy_t mp_dummy; /* 内存池虚拟控制块 */
os_mempool_id mp; /* 内存池句柄 */
uint8_t *write_data; /* 当前写入数据指针 */
void *start_addr; /* 内存池起始地址 */
os_bool_t activated; /* 是否已激活播放 */
};播放器使用 内存池(mempool) 作为音频数据缓冲队列:
- 内存池块大小由
OS_AUDIO_REPLAY_MP_BLOCK_SIZE定义 - 块数量由
OS_AUDIO_REPLAY_MP_BLOCK_COUNT定义 activated标志避免重复调用start
2.6 音频缓冲信息 struct os_audio_buf_info
struct os_audio_buf_info
{
uint8_t *buffer; /* 缓冲区指针 */
uint16_t block_size; /* 每个数据块大小 */
uint16_t block_count; /* 数据块数量 */
uint32_t total_size; /* 总缓冲区大小 */
};2.7 采样率宏定义
| 宏定义 | 说明 |
|---|---|
AUDIO_SAMP_RATE_8K | 8 kHz |
AUDIO_SAMP_RATE_11K | 11.025 kHz |
AUDIO_SAMP_RATE_16K | 16 kHz |
AUDIO_SAMP_RATE_22K | 22.05 kHz |
AUDIO_SAMP_RATE_32K | 32 kHz |
AUDIO_SAMP_RATE_44K | 44.1 kHz |
AUDIO_SAMP_RATE_48K | 48 kHz |
AUDIO_SAMP_RATE_96K | 96 kHz |
AUDIO_SAMP_RATE_128K | 128 kHz |
AUDIO_SAMP_RATE_160K | 160 kHz |
AUDIO_SAMP_RATE_172K | 172 kHz |
AUDIO_SAMP_RATE_192K | 192 kHz |
三、I2S 硬件抽象层
3.1 I2S 设备结构体 struct os_i2s_device
struct os_i2s_device
{
os_device_t parent; /* 继承自 os_device_t */
const struct os_i2s_ops *ops; /* I2S 操作函数表 */
};3.2 I2S 操作函数表 struct os_i2s_ops
struct os_i2s_ops
{
os_err_t (*init)(struct os_i2s_device *i2s);
os_err_t (*transimit)(struct os_i2s_device *i2s, uint8_t *pData, uint32_t Size);
os_err_t (*receive)(struct os_i2s_device *i2s, uint8_t *pData, uint32_t Size);
os_err_t (*enable_tx)(struct os_i2s_device *i2s, os_bool_t enable);
os_err_t (*enable_rx)(struct os_i2s_device *i2s, os_bool_t enable);
os_err_t (*set_frq)(struct os_i2s_device *i2s, uint32_t frequency);
os_err_t (*set_channel)(struct os_i2s_device *i2s, uint8_t channels);
os_err_t (*set_bits)(struct os_i2s_device *i2s, uint8_t bits);
};| 函数指针 | 说明 |
|---|---|
init | 初始化 I2S 外设 |
transimit | 启动 DMA 发送 |
receive | 启动 DMA 接收 |
enable_tx | 使能/禁用 TX |
enable_rx | 使能/禁用 RX |
set_frq | 设置采样频率 |
set_channel | 设置声道数 |
set_bits | 设置采样位宽 |
3.3 I2S 控制命令
#define OS_AUDIO_CMD_TX_ENABLE IOC_SOUND(0x00) /* 使能 TX */
#define OS_AUDIO_CMD_RX_ENABLE IOC_SOUND(0x01) /* 使能 RX */
#define OS_AUDIO_CMD_SET_FRQ IOC_SOUND(0x02) /* 设置频率 */
#define OS_AUDIO_CMD_SET_CHANNEL IOC_SOUND(0x03) /* 设置声道 */
#define OS_AUDIO_CMD_SET_BITS IOC_SOUND(0x04) /* 设置位宽 */
#define OS_AUDIO_CMD_TX_DISABLE IOC_SOUND(0x05) /* 禁用 TX */
#define OS_AUDIO_CMD_RX_DISABLE IOC_SOUND(0x06) /* 禁用 RX */四、关键 API
4.1 音频播放器注册
os_err_t os_audio_player_register(struct os_audio_device *audio, const char *name, void *data);- audio: Audio 设备控制块指针
- name: 设备名称(如
"sound0") - data: 用户私有数据
- 返回:
OS_SUCCESS注册成功
注册后设备类型为 OS_DEVICE_TYPE_SOUND,可通过 os_device_open("sound0") 打开使用。
4.2 音频录音器注册
os_err_t os_audio_recorder_register(struct os_audio_device *audio, const char *name, void *data);4.3 TX 发送完成通知
os_err_t os_audio_tx_complete(struct os_audio_device *audio);由 HAL 层在 DMA 发送完成中断中调用,通知框架一个数据块已播放完毕。
4.4 RX 接收完成通知
void os_audio_rx_done(struct os_audio_device *audio, uint8_t *pbuf, os_size_t len);由 HAL 层在 DMA 接收完成中断中调用,通知框架已接收到音频数据。
4.5 I2S 设备注册
os_err_t os_i2s_register(struct os_i2s_device *i2s, const char *name, void *data);4.6 I2S 中断处理
void os_hw_i2s_isr(struct os_i2s_device *i2s, struct os_device_cb_info *info);I2S 中断服务例程,由 HAL 层在 I2S DMA 中断中调用。
五、使用示例
5.1 音频播放器注册示例
#include <audio.h>
#include <device.h>
/* 音频播放器 HAL 层实现 */
static os_err_t my_audio_init(struct os_audio_device *audio)
{
os_kprintf("audio hardware init\r\n");
return OS_SUCCESS;
}
static os_err_t my_audio_start(struct os_audio_device *audio)
{
os_kprintf("audio playback start\r\n");
return OS_SUCCESS;
}
static os_err_t my_audio_stop(struct os_audio_device *audio)
{
os_kprintf("audio playback stop\r\n");
return OS_SUCCESS;
}
static os_size_t my_audio_transmit(struct os_audio_device *audio, const void *buf, os_size_t size)
{
/* 启动 DMA 传输一帧音频数据 */
/* 在 DMA 完成中断中调用 os_audio_tx_complete(audio) */
return size;
}
static const struct os_audio_ops my_audio_ops =
{
.init = my_audio_init,
.start = my_audio_start,
.stop = my_audio_stop,
.transmit = my_audio_transmit,
};
/* 注册音频播放器 */
void audio_player_init(void)
{
static struct os_audio_device audio_dev;
audio_dev.ops = &my_audio_ops;
os_audio_player_register(&audio_dev, "sound0", OS_NULL);
}5.2 播放 PCM 音频数据
#include <audio.h>
#include <device.h>
#include <os_task.h>
#define AUDIO_BUF_SIZE (4096)
void audio_playback_task(void *arg)
{
os_device_t *audio_dev;
uint8_t *pcm_data;
os_size_t len;
/* 1. 打开音频设备 */
audio_dev = os_device_open("sound0");
if (audio_dev == OS_NULL)
{
os_kprintf("open sound0 failed!\r\n");
return;
}
/* 2. 配置音频参数:44.1kHz, 立体声, 16bit */
struct os_audio_caps caps = {
.config_type = AUDIO_PARAM_CMD,
.udata.config.samplerate = 44100,
.udata.config.channels = 2,
.udata.config.samplebits = 16,
};
os_device_control(audio_dev, AUDIO_CTL_CONFIGURE, &caps);
/* 3. 循环播放 PCM 数据 */
while (1)
{
/* 从文件或网络获取 PCM 数据 */
pcm_data = get_pcm_data(&len);
if (pcm_data == OS_NULL)
break;
/* 写入音频数据(阻塞直到有可用缓冲区) */
os_device_write(audio_dev, 0, pcm_data, len);
}
}5.3 音量控制示例
void audio_set_volume(os_device_t *audio_dev, int volume)
{
struct os_audio_caps caps;
/* 限制音量范围 */
if (volume < AUDIO_VOLUME_MIN)
volume = AUDIO_VOLUME_MIN;
if (volume > AUDIO_VOLUME_MAX)
volume = AUDIO_VOLUME_MAX;
caps.config_type = AUDIO_VOLUME_CMD;
caps.udata.value = volume;
os_device_control(audio_dev, AUDIO_CTL_CONFIGURE, &caps);
os_kprintf("audio volume set to %d\r\n", volume);
}5.4 I2S 设备注册示例
#include <audio/i2s.h>
#include <device.h>
/* I2S HAL 层实现 */
static os_err_t my_i2s_init(struct os_i2s_device *i2s)
{
/* 初始化 I2S 外设、DMA、GPIO 等 */
return OS_SUCCESS;
}
static os_err_t my_i2s_transmit(struct os_i2s_device *i2s, uint8_t *pData, uint32_t Size)
{
/* 启动 DMA 发送 */
return OS_SUCCESS;
}
static const struct os_i2s_ops my_i2s_ops =
{
.init = my_i2s_init,
.transimit = my_i2s_transmit,
};
void i2s_device_init(void)
{
static struct os_i2s_device i2s_dev;
i2s_dev.ops = &my_i2s_ops;
os_i2s_register(&i2s_dev, "i2s0", OS_NULL);
}六、内部实现机制
6.1 内存池缓冲队列(生产者-消费者模型)
音频播放器使用内存池实现生产者-消费者模型:
应用层(生产者) HAL 层(消费者)
┌──────────────┐ ┌──────────────┐
│ os_device_ │ 写入数据块 │ DMA 传输 │
│ write() │ ──────────────→ │ 完成中断 │
│ │ │ │
│ 从 mempool │ │ 归还到 │
│ 分配空闲块 │ │ mempool │
└──────────────┘ └──────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────┐
│ os_mempool 缓冲池 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ blk0 │ │ blk1 │ │ blk2 │ │ ... │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ 空闲块 ← 分配 → 已用块 ← 归还 → 空闲块 │
└─────────────────────────────────────────────┘背压控制:通过 tx_count 和 tx_size 实现流控:
tx_count:当前已占用缓冲区数量tx_size:总缓冲区数量(=OS_AUDIO_REPLAY_MP_BLOCK_COUNT)- 当
tx_count >= tx_size时,os_device_write()返回已写入字节数,应用层需等待回调归还缓冲区
6.2 TX 完成回调流程
1. 应用层调用 os_device_write() 写入 PCM 数据
2. 框架从 mempool 分配空闲块,拷贝数据
3. 调用 ops->transmit() 启动 DMA 传输
4. DMA 传输完成 → 硬件中断 → HAL 层调用 os_audio_tx_complete()
5. 框架通过 TX 回调 _audio_player_callback() 归还缓冲区到 mempool
6. tx_count--,等待中的 os_device_write() 可继续写入6.3 音频播放器初始化流程
os_audio_player_register()
│
├── 设置 device->type = OS_DEVICE_TYPE_SOUND
├── 设置 device->ops = &audio_ops
├── os_device_register()
│
└── 用户首次 open() 时触发 _audio_player_init():
├── 分配 os_audio_replay 控制块
├── 创建 mempool(块大小 = OS_AUDIO_REPLAY_MP_BLOCK_SIZE)
├── 设置 tx_count = 0, tx_size = BLOCK_COUNT
├── 调用 ops->init() 初始化硬件
└── 注册 OS_DEVICE_CB_TYPE_TX 回调📝 本节小结
本节介绍了 OneOS 中 Audio 音频设备驱动的使用方法,包括:
- 三层架构:应用层(PCM 数据写入)→ 播放器框架(内存池队列管理)→ I2S 硬件层(DMA 传输)
- 核心数据结构:
os_audio_device继承自os_device_t,通过os_audio_ops操作函数表实现音频操作 - 内存池缓冲队列:使用
os_mempool实现生产者-消费者模型,通过tx_count/tx_size实现背压流控 - I2S 硬件抽象:
os_i2s_device独立管理 I2S 外设,支持 TX/RX 使能、频率/声道/位宽配置 - 控制接口:通过
os_device_control()+AUDIO_CTL_CONFIGURE配置采样率、声道、音量等参数
Audio 设备驱动框架非常适合需要音频播放或录音的嵌入式 IoT 设备,如智能音箱、语音助手、对讲设备等。