API GUIDE / ICODEC / MIDDLEWARE

ICODEC Middleware 接口

audio mid api

1. 文档范围

本文档定义 G32S10 SDK Audio middleware 的正式接口,面向使用 Audio Core 完成录音、 放音和循环采集的应用、BSP 和测试代码。

本文档以当前 SDK 源码为准:

类型 路径
应用公开接口 code/middleware/audio/include/audio/audio.h
可选零拷贝接口 code/middleware/audio/include/audio/audio_ext.h
公共类型 code/middleware/audio/include/audio/audio_types.h
公共音频格式与 DAI 类型 code/middleware/audio/include/im_audio_dai_types.h
板级设备描述类型 code/middleware/audio/include/im_audio_device.h
PCM buffer 描述 code/middleware/audio/include/im_audio_buffer_types.h
Audio Core 实现 code/middleware/audio/core/
Basic Stream 实现 code/middleware/audio/audio_stream/
Controller adapter code/middleware/audio/audio_adapter/
构建配置 code/middleware/audio/ 下的各级 Kconfig
示例 code/middleware/audio/example/

补充说明:

  • 普通应用只需要包含 audio/audio.h。需要直接消费 DMA ring 的算法可以额外包含 audio/audio_ext.h。
  • im_audio_controller_*、im_audio_dma_transport_*、im_aic_adapter_*、 im_pwma_adapter_* 和 im_audio_stream_* 是 BSP、controller adapter 和 transport 的集成接口,不是普通应用的业务接口。
  • Audio Core 使用调用方提供的 PCM ring,不负责分配或释放 PCM 数据内存。
  • 板级音频设备表由 BSP 提供。Audio Core 只写设备表中的 state、configured 和 active 运行字段,不修改板级静态连接关系。

目录

2. 分层关系与依赖

Application / Test
       |
       | audio/audio.h
       | audio/audio_ext.h
       v
   Audio Core
       |
       v
  Basic Audio Stream
       |
       +----------------------+
       |                      |
       v                      v
Controller Adapter      DMA/Transport
       |                      |
       +----------+-----------+
                  |
                  v
          HAL / LL Drivers
                  |
                  v
       AIC / PWMA / Codec Hardware

Audio Core 的职责:

  • 维护全局初始化状态和固定大小的 stream 对象池。
  • 根据 BSP 设备表初始化 controller、codec 和 transport。
  • 管理一条 device 对应的一条方向明确的 stream。
  • 提供应用可移植的 open/start/read/write/wait/stop/close 接口。
  • 将 controller 周期完成事件转换为可等待的 Audio Core 事件。
  • 提供时间戳、播放延迟、增益、静音和零拷贝访问。

Audio Core 不负责:

  • 自动创建业务线程。
  • 自动进行 PCM 文件解析、重采样或格式转换。
  • 为应用分配 PCM ring。
  • 在后台自动填满播放 ring。
  • 在 XRUN 后自动重启业务流。

主要依赖如下:

依赖 用途
MIDDLEWARE_AUDIO_CORE Audio Core 注册表、stream 池和公开 API
MIDDLEWARE_AUDIO_STREAM 固定 period 的基本音频流
MIDDLEWARE_AUDIO_ADAPTER controller 通用 adapter 接口
MIDDLEWARE_AUDIO_ADAPTER_AIC G32S10 AIC 硬件路径
MIDDLEWARE_AUDIO_ADAPTER_PWMA G32S10 PWMA 硬件路径
MIDDLEWARE_AUDIO_TRANSPORT_DMA DMA transport 边界
MIDDLEWARE_AUDIO_BACKEND_G32S10_PDMA G32S10 PDMA backend;按 period 使用软件分段 cyclic 搬运
MIDDLEWARE_DMA descriptor、channel 和 PDMA IRQ 管理
Codec device ES8311、ICODEC 等具体 codec
BSP audio board 提供 controller、codec、DAI 和 transport 的设备表

3. 构建配置与文件组织

3.1 Kconfig

选项 默认值 说明
MIDDLEWARE_AUDIO_CORE n 编译 Audio Core;自动选择 MIDDLEWARE_AUDIO_STREAM 和 OS_API。
MIDDLEWARE_AUDIO_CORE_MAX_STREAMS 4 静态 stream 对象池大小,范围 1 到 16。
MIDDLEWARE_AUDIO_STREAM n 编译基本音频流和固定 period ring 管理。
MIDDLEWARE_AUDIO_TRANSPORT_DMA n 编译通用 DMA transport。
MIDDLEWARE_AUDIO_BACKEND_G32S10_PDMA n 编译 G32S10 PDMA backend。
MIDDLEWARE_AUDIO_ADAPTER n 编译 controller adapter 公共层。
MIDDLEWARE_AUDIO_ADAPTER_AIC n 编译 AIC adapter,并选择 DRIVER_AIC。
MIDDLEWARE_AUDIO_ADAPTER_PWMA n 编译 PWMA adapter,并选择 DRIVER_PWMA。
MIDDLEWARE_AUDIO_BOARD BSP 决定 audio_init() 自动调用当前 BSP 的 board_audio_register()。

最小软件配置:

CONFIG_OS_API=y
CONFIG_MIDDLEWARE_AUDIO_CORE=y
CONFIG_MIDDLEWARE_AUDIO_BOARD=y

G32S10 芯片板使用 AIC、PWMA 和 PDMA 时通常由 BSP Kconfig 自动选择:

CONFIG_MIDDLEWARE_AUDIO_ADAPTER_AIC=y
CONFIG_MIDDLEWARE_AUDIO_ADAPTER_PWMA=y
CONFIG_MIDDLEWARE_AUDIO_TRANSPORT_DMA=y
CONFIG_MIDDLEWARE_AUDIO_BACKEND_G32S10_PDMA=y

3.2 文件职责

文件或目录 说明
include/audio/audio.h Audio Core 应用 API
include/audio/audio_ext.h 可选零拷贝 API
include/audio/audio_types.h stream 配置、状态、事件、时间戳和延迟类型
include/im_audio_dai_types.h 采样格式、DAI 格式和时钟角色
include/im_audio_device.h 板级设备表和 audio_device_t
include/im_audio_buffer_types.h PCM buffer 描述与 placement flags
core/audio.c 全局初始化、board 注册和 deinit
core/audio_stream.c stream 池、生命周期、读写、事件和扩展 API
audio_stream/im_audio_stream.c ring 几何、position 和基本流状态机
audio_stream/transport/ DMA transport 和 G32S10 PDMA backend
audio_adapter/ controller、AIC 和 PWMA adapter
example/main/main.c AIC + ES8311 录音、ES8311 回放和 PWMA 回放示例

应用包含方式:

#include "audio/audio.h"

需要零拷贝时:

#include "audio/audio_ext.h"

4. 资源模型与公开宏

4.1 stream 池

Audio Core 在静态内存中预留 CONFIG_MIDDLEWARE_AUDIO_CORE_MAX_STREAMS 个 stream 对象。该对象池只保存控制结构, 不包含 PCM ring 或 DMA descriptor。

同一个 audio_device_t 同一时刻最多只能打开一条 stream。重复打开同一 device 返回 IM_ERR_BUSY。

调用方关闭 stream 后,slot 立即回到 AUDIO_STREAM_STATE_CLOSED,可以再次使用。

4.2 PCM ring 所有权

PCM ring 由调用方分配并持有,audio_stream_open() 只保存 buffer 描述。ring 必须从 audio_stream_open() 成功返回后保持有效,直到 audio_stream_close() 返回。

最小 ring 大小:

memory_sample_bytes = 8-bit: 1
                      16-bit: 2
                      18/20/24/32-bit: 4

frame_bytes  = memory_sample_bytes * channels
period_bytes = frame_bytes * period_frames
ring_bytes   = period_bytes * period_count

data_bytes 必须不小于计算出的 ring_bytes。实际使用的 ring 只占 buffer 起始位置 的前 ring_bytes 字节,多余的尾部空间不会使用。

当前 G32S10 PDMA 路径还要求:

  1. buffer.data 按 64 byte cache line 对齐。
  2. period_bytes 为 64 byte 的整数倍。
  3. period_count 不小于 2。
  4. buffer 对 CPU 和 DMA 均可见。

4.3 公开宏

宏 当前值 说明
AUDIO_WAIT_FOREVER_MS UINT32_MAX audio_stream_wait() 无限等待
AUDIO_CLOCK_DOMAIN_LOCAL_MTIME 0 本 SoC 音频时间戳使用的 CLINT mtime 域
AUDIO_DIRECTION_PLAYBACK 1 << 0 放音方向
AUDIO_DIRECTION_CAPTURE 1 << 1 录音方向
AUDIO_DIRECTION_LOOP 1 << 2 controller 本地循环采集方向
AUDIO_EVENT_PERIOD_DONE 1 << 0 一个或多个 period 已完成
AUDIO_EVENT_XRUN 1 << 1 播放欠载或录音溢出
AUDIO_EVENT_STATE_CHANGED 1 << 2 XRUN/ERROR 导致的 stream 状态变化
AUDIO_EVENT_ERROR 1 << 3 transport 或硬件错误

4.4 buffer flags

flag 说明
IM_AUDIO_BUFFER_FLAG_DMA_ACCESSIBLE 调用方声明 buffer 可被 DMA 访问。
IM_AUDIO_BUFFER_FLAG_CACHEABLE 调用方声明 buffer 使用 cacheable 内存。
IM_AUDIO_BUFFER_FLAG_PSRAM 调用方声明 buffer 位于 PSRAM。

这些 flags 是调用方的 placement 提示,最终可访问性和 cache 一致性仍由 transport backend 校验。即使填写了 DMA_ACCESSIBLE,地址或 period 对齐不合法时 open 仍会失败。

5. 公开类型

5.1 audio_stream_t

typedef struct audio_stream audio_stream_t;

不透明 stream 句柄。对象由 Audio Core 静态池持有,调用方不得定义、复制或修改对象内容。 所有 API 中的 audio_stream_t * 必须来自成功的 audio_stream_open()。

5.2 audio_stream_state_t

typedef enum {
    AUDIO_STREAM_STATE_CLOSED = 0U,
    AUDIO_STREAM_STATE_OPEN,
    AUDIO_STREAM_STATE_CONFIGURED,
    AUDIO_STREAM_STATE_PREPARED,
    AUDIO_STREAM_STATE_RUNNING,
    AUDIO_STREAM_STATE_XRUN,
    AUDIO_STREAM_STATE_ERROR,
} audio_stream_state_t;
状态 说明
CLOSED slot 空闲,句柄无效。
OPEN stream 已分配但仍在完成配置。
CONFIGURED 格式和 ring 已配置。
PREPARED transport 和 DMA 资源已准备好,可以 start。
RUNNING 硬件流正在运行。
XRUN 播放欠载或录音溢出。
ERROR 不可继续运行,需要 stop/close 或重新初始化。

audio_stream_open() 成功返回的稳定状态是 PREPARED。

5.3 audio_stop_mode_t

typedef enum {
    AUDIO_STOP_MODE_DROP = 0U,
} audio_stop_mode_t;

当前只支持立即停止并丢弃尚未播放的数据。需要听到最后一个样本时,应先等待播放位置 完成或根据 audio_stream_delay_get() 判断 ring 已排空,再调用 stop。

5.4 im_audio_stream_format_t

typedef struct {
    im_audio_sample_rate_t sample_rate;
    im_audio_sample_width_t sample_width;
    uint8_t channels;
} im_audio_stream_format_t;

公共 Basic Stream 层接受 1 或 2 个通道。实际硬件是否支持目标采样率、位宽、通道数 和 DAI 格式由 controller 与 codec 决定。

支持的采样率枚举:

5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000,
44100, 48000, 64000, 88200, 96000, 176400, 192000 Hz

支持的样本位宽:

位宽 内存容器
8 bit 1 byte
16 bit 2 byte
18 bit 4 byte
20 bit 4 byte
24 bit 4 byte
32 bit 4 byte

5.5 audio_stream_config_t

typedef struct {
    im_audio_stream_format_t format;
    uint32_t period_frames;
    uint32_t period_count;
    uint32_t min_start_frames;
} audio_stream_config_t;
字段 约束
format 采样率必须是公开枚举值,通道数必须为 1 或 2。
period_frames 必须大于 0;G32S10 PDMA 下 period byte 数必须为 64 的倍数。
period_count 必须大于等于 2。
min_start_frames 播放启动前最低 queued frames;0 表示一个 period,且不得超过 ring 总帧数。

5.6 audio_stream_open_config_t

typedef struct {
    audio_stream_config_t stream;
    im_audio_stream_buffer_t buffer;
} audio_stream_open_config_t;

buffer 必须是调用方持有的连续 PCM ring:

typedef struct {
    void *data;
    uint32_t data_bytes;
    uint32_t flags;
} im_audio_stream_buffer_t;

5.7 audio_timestamp_t

typedef struct {
    uint64_t monotonic_us;
    uint64_t frame_index;
    im_audio_sample_rate_t sample_rate;
    uint32_t period_frames;
    uint32_t sequence;
    uint32_t clock_domain_id;
    uint32_t flags;
} audio_timestamp_t;
字段 说明
monotonic_us 最近完成 period 的时间戳,单位 us。
frame_index 硬件完成帧位置。
sample_rate stream 采样率。
period_frames 一个 period 的帧数。
sequence 已完成 period 序号。
clock_domain_id 时间戳所属时钟域。
flags discontinuity、estimated、XRUN 标志。

5.8 audio_delay_info_t

typedef struct {
    uint32_t queued_frames;
    uint32_t backlog_frames;
    uint32_t buffer_level_frames;
    uint32_t delay_us;
    uint32_t flags;
} audio_delay_info_t;

当前三个帧数字段都表示软件已写入但硬件尚未完成的帧数。delay_us 使用 stream 的 采样率换算。该快照只对采样瞬间有效。

5.9 增益类型

typedef enum {
    IM_AUDIO_GAIN_ANALOG = 0U,
    IM_AUDIO_GAIN_DIGITAL,
} im_audio_gain_domain_t;

typedef enum {
    IM_AUDIO_GAIN_UNIT_RAW = 0U,
    IM_AUDIO_GAIN_UNIT_CENTI_DB,
} im_audio_gain_unit_t;

RAW 是 codec 寄存器码值,不是 dB 值或音量百分比。CENTI_DB 只有在 audio_stream_gain_capability_get() 返回 db_supported == true 时才可用。

6. 生命周期与状态机

closed slot
    |
    | audio_stream_open()
    v
OPEN -> CONFIGURED -> PREPARED
                         |
                         | audio_stream_start()
                         v
                      RUNNING
                         |
                         +---- period events -> RUNNING
                         |
                         +---- underrun/overflow -> XRUN
                         |
                         +---- fatal transport error -> ERROR

audio_stream_stop() from RUNNING/XRUN/ERROR -> PREPARED
audio_stream_close() from any open state -> CLOSED
audio_deinit() requires CLOSED for every stream

audio_init() 是幂等的。重复调用已初始化 Audio Core 返回 IM_OK。

audio_deinit() 只有在所有 stream 都已 close 时才允许执行。存在任何 open stream 时 返回 IM_ERR_BUSY。

7. Audio Core 应用接口

7.1 audio_init

error_code_t audio_init(void);

初始化 Audio Core 和静态 stream 池。

当 CONFIG_MIDDLEWARE_AUDIO_BOARD=y 时,本函数调用当前 BSP 的 board_audio_register(),由 BSP 注册 AIC/PWMA controller、codec 和 transport。

返回:

返回值 条件
IM_OK 初始化成功或已经初始化。
BSP 注册错误 board adapter、codec、transport 或 GPIO 初始化失败。

BSP 注册失败时 Audio Core 会回滚初始化标志和 stream 池,调用方可以在修复失败原因后 重试。

7.2 audio_deinit

error_code_t audio_deinit(void);

反初始化所有已注册音频 device,并清空 stream 池。

返回:

返回值 条件
IM_OK 已反初始化,或当前本来就未初始化。
IM_ERR_BUSY 仍有 stream 未关闭。
BSP deinit 错误 controller、codec 或 transport 释放失败。

deinit 失败时 Audio Core 保持已初始化状态,允许调用方修复后再次调用。

7.3 audio_is_initialized

bool audio_is_initialized(void);

返回 Audio Core 当前是否处于初始化完成状态。本接口不访问硬件。

7.4 audio_device_get

error_code_t audio_device_get(const char *name,
                              audio_device_t **device_out);

按板级设备名查找设备。name 必须与 audio_device_t.name 完全一致。

返回值 条件
IM_OK 找到设备,device_out 指向板级静态记录。
IM_ERR_NOT_READY audio_init() 尚未完成。
IM_ERR_NOT_FOUND 当前板级设备表中没有该名称。
IM_ERR_ALREADY_EXISTS 当前板级设备表存在重复名称。
IM_ERR_INVALID_PARAM 参数为空或设备名非法。

返回指针由 BSP 持有,应用不能释放或复制该设备记录。

7.5 audio_stream_open

error_code_t audio_stream_open(audio_device_t *device,
                               const audio_stream_open_config_t *config,
                               audio_stream_t **stream_out);

打开一个板级 device 对应的单向 stream。

参数:

参数 说明
device board_audio_config_get() 设备表中的一项,不能复制或修改。
config PCM 格式、period、ring 大小和启动预填充配置。
stream_out 成功时返回 Audio Core 静态池中的 stream 句柄。

行为:

  1. 校验 Audio Core 已初始化。
  2. 校验 device 状态以及该 device 是否已有打开的 stream。
  3. 第一次打开 device 时初始化 controller、codec 和 transport。
  4. 配置硬件格式、DAI、PCM ring 和 DMA transport。
  5. 成功返回 AUDIO_STREAM_STATE_PREPARED。

返回:

返回值 条件
IM_OK stream 已准备好。
IM_ERR_NOT_READY Audio Core 未初始化或 device 状态不允许打开。
IM_ERR_INVALID_PARAM device、config、格式、ring 或周期参数非法。
IM_ERR_BUSY 同一 device 已有 stream,或硬件资源被占用。
IM_ERR_NO_MEMORY stream 池已满或 ring 小于所需空间。
IM_ERR_UNSUPPORTED controller/codec 不支持该格式或 DAI 组合。
HAL/transport 错误 硬件初始化、DMA 配置或中断资源配置失败。

正常情况下失败后 *stream_out == NULL。如果失败后的清理本身也失败,函数可能返回 一个 ERROR stream;调用方必须继续调用 audio_stream_close() 重试清理。

7.6 audio_stream_close

error_code_t audio_stream_close(audio_stream_t *stream);

停止并释放 stream 的 ring、transport 和硬件路径配置,使 slot 回到 CLOSED。

调用方应在 close 后把本地句柄置 NULL,不要再次使用旧句柄。

返回:

返回值 条件
IM_OK stream 已关闭。
IM_ERR_BUSY 同一 controller 的 LOOP stream 仍引用该 playback。
HAL/transport 错误 停止或资源释放失败,stream 保留用于重试 close。

7.7 audio_stream_start

error_code_t audio_stream_start(audio_stream_t *stream);

启动一个 PREPARED stream。

播放流要求在调用前已经写入至少 min_start_frames 个 PCM 帧。录音流没有预填充要求。 LOOP stream 要求它引用的 playback stream 已经完成配置并处于 PREPARED 或 RUNNING。允许先启动 LOOP,再启动 playback。

返回:

返回值 条件
IM_OK transport 和 controller 已启动。
IM_ERR_NOT_READY stream 不是 PREPARED,或播放 ring 未达到启动水位。
HAL/controller 错误 codec enable、controller start 或 transport start 失败。

7.8 audio_stream_stop

error_code_t audio_stream_stop(audio_stream_t *stream,
                               audio_stop_mode_t mode);

立即停止 stream,丢弃尚未播放的数据,并保留 stream 为 PREPARED,允许再次 start。

当前 mode 必须为 AUDIO_STOP_MODE_DROP。

如果停止的是 playback 且同一 controller 上存在 LOOP stream,Audio Core 先停止 LOOP。

7.9 audio_stream_read

error_code_t audio_stream_read(audio_stream_t *stream,
                               void *dst,
                               uint32_t max_frames,
                               uint32_t *frames_out,
                               audio_timestamp_t *timestamp_out);

从 capture 或 loop stream 的 ring 读取 PCM。

参数:

参数 说明
dst 调用方目标缓冲区。
max_frames 本次最多读取帧数。
frames_out 实际读取帧数。
timestamp_out 可选,返回第一个读取帧的元数据。

返回:

返回值 条件
IM_OK 至少读取了一帧。
IM_ERR_NOT_READY 当前没有可读帧,或 stream 已进入 XRUN。
IM_ERR_UNSUPPORTED 对 playback stream 调用。
IM_ERR_INVALID_PARAM stream、目标地址或输出参数非法。

7.10 audio_stream_write

error_code_t audio_stream_write(audio_stream_t *stream,
                                const void *src,
                                uint32_t max_frames,
                                uint32_t *frames_out);

把 PCM 写入 playback ring 的空闲空间。

返回:

返回值 条件
IM_OK 至少写入了一帧。
IM_ERR_NOT_READY ring 已满。
IM_ERR_UNSUPPORTED 对 capture 或 loop stream 调用。
IM_ERR_INVALID_PARAM stream、源地址或输出参数非法。

调用方应使用 frames_out 推进输入位置,不能假定一次写入了 max_frames 帧。

7.11 audio_stream_state_get

error_code_t audio_stream_state_get(const audio_stream_t *stream,
                                    audio_stream_state_t *state_out);

同步 Basic Stream 和 transport 的最新状态,再返回公开生命周期状态。

本接口适合在等待失败、读写返回异常或 stop 前确认 stream 状态。它不替代事件等待。

7.12 audio_stream_position_get

error_code_t audio_stream_position_get(const audio_stream_t *stream,
                                       audio_timestamp_t *timestamp_out);

原子复制最近一次 hardware period 完成位置。

事件是 pending bit,可能被合并。需要准确知道跨过多少个 period 时,应比较相邻两次 读取的 sequence 和 frame_index,不要只统计 wait 返回次数。

7.13 audio_stream_gain_capability_get

error_code_t audio_stream_gain_capability_get(
    const audio_stream_t *stream,
    im_audio_gain_domain_t domain,
    im_audio_gain_capability_t *capability_out);

查询当前 endpoint 支持的增益范围。若设备带 codec,Audio Core 查询 codec 能力; 若 controller 提供本地增益路径,则查询 controller adapter 的能力。

返回:

返回值 条件
IM_OK capability 已填充。
IM_ERR_UNSUPPORTED codec 和 controller 均未实现该方向/增益域。
IM_ERR_INVALID_PARAM stream 或输出参数非法。

raw_supported 和各 raw code 描述当前 endpoint 的原始硬件码值。只有 db_supported == true 时才能使用 centi-dB。

7.14 audio_stream_gain_set

error_code_t audio_stream_gain_set(audio_stream_t *stream,
                                   im_audio_gain_domain_t domain,
                                   im_audio_gain_unit_t unit,
                                   int32_t gain_value);

设置模拟或数字增益。

stream 已运行时,codec 应立即应用新值;stream 尚未运行时,driver 可以缓存该值并在 enable 阶段恢复。

返回 IM_ERR_UNSUPPORTED 表示 codec 不支持该 path/domain/unit,超出 capability 范围时通常返回 IM_ERR_INVALID_PARAM。

ES8311 播放流可以在 AUDIO_STREAM_STATE_RUNNING 状态下动态调节音量,不需要先 stop、close 或重新打开 stream:

ret = audio_stream_gain_set(
    stream,
    IM_AUDIO_GAIN_DIGITAL,
    IM_AUDIO_GAIN_UNIT_RAW,
    gain_code);

ES8311 当前只声明 RAW 增益,gain_code 是 DAC 音量寄存器 0x32 的原始码值, 范围为 0..255,不是音量百分比。stream 正在运行时,接口直接更新 codec 寄存器; stream 尚未运行时,driver 缓存新值并在 codec enable 时应用。

ES8311 的录音流使用 IM_AUDIO_GAIN_ANALOG + IM_AUDIO_GAIN_UNIT_RAW。 需要平滑音量变化时,应用应分多步修改 RAW code 并留出间隔,避免一次跨越过多码值 产生可闻的突变。

PWMA 没有 codec,但 controller adapter 提供 controller-local 动态增益路径。当前支持:

stream:  IM_AUDIO_STREAM_PLAYBACK
domain:  IM_AUDIO_GAIN_DIGITAL
unit:    IM_AUDIO_GAIN_UNIT_RAW
range:   0x000..0x1FF

RAW 是完整的 9 位 AUDIO_VOLUME 寄存器码,方向位和增益字段都包含在内:

RAW             = (VOLUME_DOWN << 8) | VOLUME_GAIN[7:0]
VOLUME_DOWN     = RAW[8]
VOLUME_GAIN     = RAW[7:0]

因此可以在同一条播放流运行期间切换方向:

0x000..0x00A -> VOLUME_DOWN=0,放大方向
0x100..0x1FF -> VOLUME_DOWN=1,衰减方向

放大方向仅允许 VOLUME_GAIN=0..10;传入 0x00B..0x0FF 时 audio_stream_gain_set() 返回 IM_ERR_INVALID_PARAM,避免高增益造成严重削波。 例如 0x000 为放大方向、VOLUME_GAIN=0;0x100 为衰减方向、 VOLUME_GAIN=0;0x10F 为衰减方向、VOLUME_GAIN=15。由于 VOLUME_GAIN 同时包含 coarse 和 fine 字段,dB 步进并不均匀,不能把整个 RAW 值当作线性音量百分比。

播放中调用:

ret = audio_stream_gain_set(
    pwma_stream,
    IM_AUDIO_GAIN_DIGITAL,
    IM_AUDIO_GAIN_UNIT_RAW,
    gain_code);

adapter 写入 AUDIO_VOLUME shadow 后立即执行 AUDIO_UPDATE,正在运行的 DMA 波形 会应用新音量和方向。初始板级 volume_down 只决定设备初始化时的音量方向;运行时 gain_set 以传入 RAW 的方向位为准。

PWMA 的持久音量仍使用 BSP 的 board_audio_set_pwma_param(),并要求 PWMA 处于 RESET。动态接口只修改当前硬件音量,PWMA deinit/reinit 后会恢复板级持久配置。

7.15 audio_stream_mute_set

error_code_t audio_stream_mute_set(audio_stream_t *stream,
                                   bool mute);

设置当前 stream 对应方向的静音状态。并非所有 codec 都支持 capture mute;不支持时 返回 IM_ERR_UNSUPPORTED。

7.16 audio_stream_delay_get

error_code_t audio_stream_delay_get(const audio_stream_t *stream,
                                    audio_delay_info_t *delay_out);

返回 playback ring 的当前排队帧数和预计延迟。

返回:

返回值 条件
IM_OK snapshot 已填充。
IM_ERR_UNSUPPORTED stream 不是 playback。
IM_ERR_NOT_READY stream 尚未进入 PREPARED 或 RUNNING。
IM_ERR_INVALID_PARAM stream 或输出参数非法。

本接口可用于播放结束前等待 ring 排空,避免最后一个 period 被 DROP。

7.17 audio_stream_wait

error_code_t audio_stream_wait(audio_stream_t *stream,
                               uint32_t event_mask,
                               uint32_t timeout_ms,
                               uint32_t *events_out);

等待一个或多个 Audio Core 事件。

参数:

参数 说明
event_mask AUDIO_EVENT_* 的按位组合,不能为 0。
timeout_ms 超时时间;AUDIO_WAIT_FOREVER_MS 表示永久等待。
events_out 返回并消费本次命中的 pending events。

返回:

返回值 条件
IM_OK 至少一个请求事件已经发生。
IM_ERR_TIMEOUT 超时或 timeout_ms == 0 时没有 pending event。
IM_ERR_BUSY 另一个线程正在等待同一 stream。
IM_ERR_NOT_READY 当前上下文不能取得 SDK thread handle。
IM_ERR_UNSUPPORTED 裸机模式请求永久等待。
IM_ERR_INVALID_PARAM event mask 或输出参数非法。

本接口只能在 thread context 调用。同一 stream 同一时刻只允许一个等待线程。

8. 零拷贝扩展接口

零拷贝接口直接返回 ring 中一段连续 view,适合算法原地处理数据。view 跨越 ring 回绕点时会自动截断到当前连续段。

8.1 audio_stream_read_view_t

typedef struct {
    const void *data;
    uint32_t frame_count;
    uint32_t byte_count;
    audio_timestamp_t timestamp;
} audio_stream_read_view_t;

只读 capture view。data 在 release 前有效。

8.2 audio_stream_write_view_t

typedef struct {
    void *data;
    uint32_t frame_count;
    uint32_t byte_count;
    audio_timestamp_t timestamp;
} audio_stream_write_view_t;

可写 playback view。调用方必须填满整个 view 后 commit,不能部分提交。

8.3 audio_stream_read_acquire

error_code_t audio_stream_read_acquire(audio_stream_t *stream,
                                      uint32_t max_frames,
                                      audio_stream_read_view_t *view_out);

借出一段连续的 capture/loop ring。

约束:

  1. 每个 stream 同时最多一个未释放 view。
  2. 获取前缓存访问已经同步。
  3. 下一次 acquire、stop 或 close 之前必须调用 release。
  4. 不得保存 view 指针并在 release 后继续访问。

返回 IM_ERR_BUSY 表示已有未释放 view,IM_ERR_NOT_READY 表示当前没有数据。

8.4 audio_stream_read_release

error_code_t audio_stream_read_release(audio_stream_t *stream);

释放此前 acquire 的整个 capture view,并推进软件读取位置。

8.5 audio_stream_write_acquire

error_code_t audio_stream_write_acquire(audio_stream_t *stream,
                                       uint32_t max_frames,
                                       audio_stream_write_view_t *view_out);

借出 playback ring 的一段连续空闲区域。

返回的 timestamp 是 view 第一帧预计被硬件输出的时间,并带有 TIMESTAMP_ESTIMATED 标志。

8.6 audio_stream_write_commit

error_code_t audio_stream_write_commit(audio_stream_t *stream);

提交此前 acquire 的完整 playback view。调用方应先把整个 byte_count 填满。

commit 前执行必要的 cache clean,使 DMA 能观察到新 PCM 数据。

9. 事件、位置与错误恢复

9.1 pending events

AUDIO_EVENT_* 是 pending bit,不是计数。若应用处理较慢,多个 period 和 XRUN 事件 可能在一次 wait 中合并。

当一个 event 同时包含:

AUDIO_EVENT_PERIOD_DONE | AUDIO_EVENT_XRUN

说明至少一个 period 完成,但播放或录音已经越界。业务应立即停止继续依赖 ring, 读取 audio_stream_position_get() 和 audio_stream_state_get() 后执行恢复。

9.2 timestamp flags

flag 说明
TIMESTAMP_ESTIMATED 时间由最近的硬件完成点投影,不是真实 IRQ 时间。
DISCONTINUITY transport 检测到时间或 position 不连续。
XRUN position 所属流发生 XRUN。

9.3 XRUN 恢复

推荐步骤:

  1. 调用 audio_stream_state_get() 确认状态。
  2. 调用 audio_stream_stop(stream, AUDIO_STOP_MODE_DROP)。
  3. 重新填充播放 ring,至少达到 min_start_frames。
  4. 再次调用 audio_stream_start()。

Audio Core 不会自动补齐缺失 PCM。录音路径发生 overflow 后,缺失帧无法恢复,只能 丢弃当前一段并重新开始采集。

9.4 正常播放结束

audio_stream_stop() 使用 DROP 语义。若必须完整播出最后一个 period,应:

  1. 写完全部 PCM。
  2. 使用 audio_stream_delay_get() 等待 queued_frames 降到 0,或等待 position 到达预期总帧数。
  3. 再调用 stop 和 close。

10. 并发、所有权与实时约束

10.1 上下文约束

接口类别 可调用上下文
audio_init() / audio_deinit() task/thread context
open/close/start/stop task/thread context
read/write/acquire/release/commit task/thread context
gain/mute/delay/position/state task/thread context;position/state只做短临界区访问
audio_stream_wait() SDK OS thread context
transport 中断回调 ISR/IRQ context

所有 PCM copy、DMA 配置、codec I2C 访问和错误日志都不应在 ISR 中由应用执行。

10.2 并发所有权

同一 stream 的 close、stop、read/write 和零拷贝 acquire 必须由业务串行化,不应由 多个线程同时操作。

不同 device 的 stream 可以由不同线程并发处理,但 controller/codec 的共享资源仍由 Audio Core 和 adapter 管理。应用不得绕过 Audio Core 直接修改设备表中的运行字段。

PCM ring 在 stream 生命周期内由调用方持有。close 返回后 Audio Core 不再访问该 ring。

10.3 音频回调

im_audio_stream_notify_t 是 Basic Stream/transport 集成层的 IRQ-safe 回调。该回调 不能阻塞、不能复制 PCM、不能执行算法,也不能改变 stream 或 controller 状态。

普通应用应优先使用 audio_stream_wait(),不建议直接注册 Basic Stream notifier。

11. BSP 与移植集成接口

11.1 音频对象分层与接口归属

层级/前缀 公开头文件 主要职责 普通应用是否调用
Audio Core,audio_* audio/audio.h、audio/audio_ext.h stream 生命周期、PCM 读写、事件、增益和零拷贝 是
Shared types,audio_*_t、im_audio_*_t audio/audio_types.h、im_audio_dai_types.h 格式、DAI、device、buffer、事件和时间戳 是,仅使用类型
Controller adapter,im_audio_controller_* im_audio_controller_adapter.h 统一 controller 的 init/configure/start/stop 能力 否
AIC adapter,im_aic_adapter_* audio_adapter/im_aic_adapter.h 把 HAL AIC 映射成 controller 对象,管理 AIC 状态 否,BSP/集成使用
PWMA adapter,im_pwma_adapter_* audio_adapter/im_pwma_adapter.h 把 HAL PWMA 映射成 controller 对象,管理 PWMA 参数 否,BSP/集成使用
Basic Stream,im_audio_stream_* im_audio_stream.h 固定 period ring、software position、read/write 和状态机 否
Transport provider,im_audio_stream_transport_t im_audio_transport.h 提供 attach/detach 和传输模式描述 否
DMA transport,im_audio_dma_transport_* audio_stream/transport/im_audio_dma_transport.h 连接 controller endpoint、Basic Stream 和平台 DMA backend 否
G32S10 PDMA backend audio_stream/transport/im_g32s10_pdma_backend.h 按 period 启动普通 DMA transfer,完成后启动下一 period 否
BSP audio board,board_audio_* 当前 board 的 board_audio.h 注册 controller、codec、transport 并提供静态 device table 仅获取设备表并设置板级策略

数据路径可以概括为:

audio_stream_read/write()
        |
        v
im_audio_stream_* 固定 period ring
        |
        v
im_audio_dma_transport_*
        |
        v
im_audio_dma_backend_ops_t
        |
        v
im_g32s10_pdma_backend_t
        |
        v
im_dma_t -> G32S10 PDMA -> AIC/PWMA FIFO

Controller adapter 位于 DMA endpoint 一侧,负责提供 FIFO 地址、request ID、帧格式和 controller 生命周期;DMA transport 负责 PCM ring 与 FIFO 之间的周期搬运。二者通过 im_audio_controller_dma_endpoint_get() 对接。

11.2 板级设备表

BSP 必须提供:

error_code_t board_audio_register(void);
const board_audio_config_t *board_audio_config_get(void);

board_audio_register() 在 audio_init() 中调用,负责注册:

  • AIC 和/或 PWMA controller adapter。
  • ES8311、ICODEC 或其他 codec device。
  • 每个方向对应的 DMA transport。
  • 外部功放、codec reset 或 GPIO 使能资源。

board_audio_config_get() 返回静态 audio_device_t 数组。

board_audio_set_aic_mode()

G32S107 SK full BSP 提供:

error_code_t board_audio_set_aic_mode(
    board_audio_aic_mode_t mode);

该接口在 audio_init() 使用的 AIC connection policy 和 device table 之间切换:

mode AIC policy device table
BOARD_AUDIO_AIC_MODE_ES8311_PLAYBACK AIC TX/RX 均为 master,外部 ES8311 为 slave ES8311 playback、ES8311 capture、PWMA playback
BOARD_AUDIO_AIC_MODE_ICODEC_CAPTURE AIC TX master、AIC RX slave,内部 ICOD 使用 AIC 提供的共享时钟 ICOD capture、AIC LOOP reference、PWMA playback

前置条件:

  1. 必须在 audio_init() 之前,或在前一次 audio_deinit() 成功之后调用。
  2. AIC 必须处于 RESET,不能有 active stream。
  3. AIC 已初始化或仍有打开 stream 时返回 IM_ERR_BUSY。
  4. BOARD_AUDIO_AIC_MODE_ICODEC_CAPTURE 需要 BSP 配置 CONFIG_BOARD_G32S107_SK_FULL_8910_ICODEC_CAPTURE=y;未启用时返回 IM_ERR_UNSUPPORTED。

该接口是板级策略接口,不属于通用 Audio Core API。调用后 device 索引可能改变, 应用必须重新调用 board_audio_config_get() 并按 direction/codec 选择设备。

11.3 audio_device_t

typedef struct audio_device {
    const char *name;
    im_audio_controller_t *controller;
    audio_codec_device_t *codec;
    audio_direction_t direction;
    const im_audio_dai_bus_config_t *dai_bus;
    const im_audio_stream_transport_t *transport;
    uint32_t clock_domain_id;
    audio_device_state_t state;
    bool configured;
    bool active;
} audio_device_t;
字段 归属
name BSP 静态配置;表内必须非空且唯一。
controller BSP 静态资源
codec BSP 静态资源;controller-only device 为 NULL
direction BSP 静态配置
dai_bus BSP 静态配置;PWMA 等 controller-only path 为 NULL
transport BSP 静态资源
clock_domain_id BSP 静态配置
state Audio Core 管理
configured Audio Core 管理
active Audio Core 管理

应用可以只读遍历设备表,根据 direction 和 codec 选择设备。应用不得复制活动 device 或修改 state、configured、active。

应用也可以按设备名查找:

audio_device_t *capture_device;
error_code_t ret;

ret = audio_device_get("es8311-capture", &capture_device);

audio_device_get() 返回板级表中的借用指针。找不到名称时返回 IM_ERR_NOT_FOUND; 板级表存在重复名称时返回 IM_ERR_ALREADY_EXISTS。

11.4 DAI 配置

im_audio_dai_bus_config_t 描述:

  • I2S、left justified、right justified、PCM A 或 PCM B。
  • codec 侧 BCLK/LRCK 是 master 还是 slave。
  • 时钟极性。
  • 左右声道顺序。
  • codec clock 是否来源于 SoC 音频时钟域。

该配置属于 board policy,不是单次 open 的可变参数。应用中需要切换 DAI 格式时,应 使用 BSP 提供的专用接口,并严格在 AIC 未运行、未配置状态下操作。

11.5 Controller adapter

im_audio_controller_* 是 controller 通用对象接口:

接口 用途
im_audio_controller_init() 初始化 controller,进入 READY。
im_audio_controller_deinit() 释放 controller。
im_audio_controller_stream_configure() 配置无 DAI 的 controller stream。
im_audio_controller_dai_configure() 配置带串行 DAI 的 stream。
im_audio_controller_stream_start() 启动指定 stream。
im_audio_controller_stream_stop() 停止指定 stream。
im_audio_controller_stream_post_stop() transport 停止后执行 controller 清理。
im_audio_controller_dma_endpoint_get() 取得 transport 使用的 FIFO DMA endpoint。
im_audio_controller_fifo_threshold_configure() 配置 FIFO service threshold。

这些接口由 Audio Core、BSP 和 adapter 实现使用,应用不应直接调用。

11.6 DMA transport

im_audio_dma_transport_* 把一个 controller DMA endpoint 绑定到 Basic Stream:

接口 用途
im_audio_dma_transport_register() 创建 controller stream 对应的持久 transport。
im_audio_dma_transport_attach() 把 transport 附加到一次 open 的 stream。
im_audio_dma_transport_detach() 在 stream 释放后解绑 transport。

transport backend 的 prepare/start/stop/release 管理 DMA transfer 和 channel。 read_sync 在 CPU 读取 capture ring 前使 cache 一致,write_sync 在 DMA 读取 playback ring 前 clean cache。

im_audio_stream_transport_t 由 BSP 静态持有,包含:

字段 说明
ops transport provider 的 attach/detach 回调
context transport 或 DMA backend 的上下文
transfer_mode 当前音频路径使用的传输模式

Audio Core 在 open 时调用 ops->attach(),close 时调用 ops->detach()。transport 对象不能由多个同时打开的 stream 共用。

11.7 Basic Stream

im_audio_stream_* 提供 Audio Core 以下的固定 period ring 和状态机。普通应用不直接 使用,原因是该层要求调用方自行编排 controller、transport、buffer、notifier 和错误 恢复。

主要接口:

接口 用途
im_audio_stream_layout_calculate() 在不修改 stream 的情况下计算 frame/period/ring 大小。
im_audio_stream_attach() 将调用方对象绑定到方向和一个硬件上下文。
im_audio_stream_configure() 设置 PCM 格式、period 和启动水位。
im_audio_stream_buffer_attach() 绑定调用方 PCM ring。
im_audio_stream_prepare() 配置 transport 并进入 PREPARED。
im_audio_stream_start() 检查播放预填充并启动 transport。
im_audio_stream_stop() 停止 transport 并回到 PREPARED。
im_audio_stream_read/write() 在 ring 与调用方内存之间复制 PCM。
im_audio_stream_read_acquire() / im_audio_stream_write_acquire() 借出 ring 连续 view。
im_audio_stream_read_release() / im_audio_stream_write_commit() 归还 capture view 或提交 playback view。
im_audio_stream_unprepare() 释放 transport,回到 CONFIGURED。
im_audio_stream_transport_period_complete() transport 向 stream 提交周期完成事件。
im_audio_stream_transport_error() transport 向 stream 提交致命错误。

BSP 或新 controller 集成可以复用该层,但必须遵守:

  1. period_count >= 2。
  2. PCM buffer 至少覆盖计算出的 ring 大小。
  3. 在 prepare 前配置 controller 和 DAI。
  4. transport 回调只报告周期进度和错误。
  5. stop 后由 transport 和 controller 按顺序释放资源。

11.8 G32S10 PDMA backend

im_g32s10_pdma_backend_t 是 im_audio_dma_backend_ops_t 的 G32S10 平台实现。 BSP 持有 backend 对象,在注册 transport 前调用:

error_code_t im_g32s10_pdma_backend_init(
    im_g32s10_pdma_backend_t *backend);

backend 的公开状态对象包含:

字段 说明
dma 本次 stream 使用的 im_dma_t 实例。
period_cb / error_cb 周期完成和终端错误回调。
event_context 回调使用的 transport 上下文。
buffer / buffer_bytes 调用方 PCM ring。
period_bytes / period_count 单个 period 大小和 ring 内 period 数量。
segment_index 当前搬运的 period。
peripheral_address controller FIFO 的 DMA 地址。
direction memory-to-peripheral 或 peripheral-to-memory。
request_id AIC/PWMA DMA request ID。
initialized/opened/running backend 生命周期状态。

backend 执行:

  1. 校验 controller endpoint、direction、request unit 和 PCM buffer。
  2. 通过 im_dma_request() 申请对应 AIC/PWMA request。
  3. AIC 和 PWMA 每次调用 im_dma_start() 搬运一个 period。
  4. transfer complete 后切换 segment_index,立即启动下一 period,并调用 period_cb。
  5. DMA 或重新启动失败时调用 error_cb。
  6. 在 CPU 读取 capture/loop ring 前执行 cache invalidate,在 DMA 读取 playback ring 前 执行 cache clean。
  7. stop 和 release 时停止 DMA、回收资源并释放 request。

im_g32s10_pdma_backend_t 不能由多个 transport 或 stream 同时复用。PDMA IRQ 由 im_dma 统一注册和分发,backend 不直接注册 PDMA IRQ。

Audio backend 的 software cyclic 完成条件是一次普通 DMA transfer 完成。backend 使用 segment_index 表示当前 period,通过 period_cb 上报完成索引和时间戳,再启动 ring 中的下一段。generic transport 将每次 callback 作为一个 period 处理,Basic Stream 固定前进一个 period。该路径不使用 DIP.CDOA 推导 period index。

LOOP stream 的 peripheral_address 指向 AICLR。AIC controller 打开 LOOP FIFO request 后,backend 使用与 capture 相同的 peripheral-to-memory 方式读取 reference PCM。

12. 典型用法

12.1 播放

#include "audio/audio.h"

#define PERIOD_FRAMES 160U
#define PERIOD_COUNT  4U

static int16_t s_play_ring[PERIOD_FRAMES * PERIOD_COUNT]
    __attribute__((aligned(64)));

static error_code_t play_pcm(const audio_device_t *device,
                             const int16_t *pcm,
                             uint32_t frames)
{
    audio_stream_open_config_t open_config = {
        .stream = {
            .format = {
                .sample_rate = IM_AUDIO_SAMPLE_RATE_16000_HZ,
                .sample_width = IM_AUDIO_SAMPLE_WIDTH_16_BITS,
                .channels = 1U,
            },
            .period_frames = PERIOD_FRAMES,
            .period_count = PERIOD_COUNT,
            .min_start_frames = PERIOD_FRAMES * 2U,
        },
        .buffer = {
            .data = s_play_ring,
            .data_bytes = sizeof(s_play_ring),
            .flags = IM_AUDIO_BUFFER_FLAG_DMA_ACCESSIBLE |
                     IM_AUDIO_BUFFER_FLAG_CACHEABLE,
        },
    };
    audio_stream_t *stream = NULL;
    uint32_t written = 0U;
    error_code_t ret;

    ret = audio_stream_open((audio_device_t *)device,
                            &open_config,
                            &stream);
    if (ret != IM_OK) {
        return ret;
    }
    if (frames < open_config.stream.min_start_frames) {
        ret = IM_ERR_INVALID_PARAM;
        goto out;
    }

    while (written < open_config.stream.min_start_frames) {
        uint32_t count = 0U;

        ret = audio_stream_write(stream,
                                 &pcm[written],
                                 open_config.stream.min_start_frames - written,
                                 &count);
        if ((ret != IM_OK) || (count == 0U)) {
            goto out;
        }
        written += count;
    }

    ret = audio_stream_start(stream);
    if (ret != IM_OK) {
        goto out;
    }

    while (written < frames) {
        uint32_t events = 0U;
        uint32_t count = 0U;

        ret = audio_stream_wait(stream, AUDIO_EVENT_PERIOD_DONE, 1000U,
                                &events);
        if (ret != IM_OK) {
            break;
        }
        ret = audio_stream_write(stream, &pcm[written],
                                 frames - written, &count);
        if (ret == IM_ERR_NOT_READY) {
            ret = IM_OK;
            continue;
        }
        if ((ret != IM_OK) || (count == 0U)) {
            break;
        }
        written += count;
    }

out:
    (void)audio_stream_stop(stream, AUDIO_STOP_MODE_DROP);
    (void)audio_stream_close(stream);
    return ret;
}

12.2 录音

static error_code_t capture_pcm(const audio_device_t *device,
                                int16_t *pcm,
                                uint32_t total_frames)
{
    audio_stream_open_config_t open_config = {
        .stream = {
            .format = {
                .sample_rate = IM_AUDIO_SAMPLE_RATE_16000_HZ,
                .sample_width = IM_AUDIO_SAMPLE_WIDTH_16_BITS,
                .channels = 1U,
            },
            .period_frames = PERIOD_FRAMES,
            .period_count = PERIOD_COUNT,
        },
        .buffer = {
            .data = s_capture_ring,
            .data_bytes = sizeof(s_capture_ring),
            .flags = IM_AUDIO_BUFFER_FLAG_DMA_ACCESSIBLE |
                     IM_AUDIO_BUFFER_FLAG_CACHEABLE,
        },
    };
    audio_stream_t *stream = NULL;
    uint32_t captured = 0U;
    error_code_t ret;

    ret = audio_stream_open((audio_device_t *)device,
                            &open_config,
                            &stream);
    if (ret != IM_OK) {
        return ret;
    }
    ret = audio_stream_start(stream);
    if (ret != IM_OK) {
        goto out;
    }

    while (captured < total_frames) {
        uint32_t events = 0U;
        uint32_t count = 0U;

        ret = audio_stream_wait(stream, AUDIO_EVENT_PERIOD_DONE, 1000U,
                                &events);
        if (ret != IM_OK) {
            break;
        }
        ret = audio_stream_read(stream, &pcm[captured],
                                total_frames - captured, &count, NULL);
        if (ret == IM_ERR_NOT_READY) {
            ret = IM_OK;
            continue;
        }
        if ((ret != IM_OK) || (count == 0U)) {
            break;
        }
        captured += count;
    }

out:
    (void)audio_stream_stop(stream, AUDIO_STOP_MODE_DROP);
    (void)audio_stream_close(stream);
    return ret;
}

12.3 查找设备

板级设备名使用字符串:

"es8311-playback"
"es8311-capture"
"pwma-playback"
"aic-loop"

应用按名称取得设备:

audio_device_t *capture;
audio_device_t *playback;

ret = audio_device_get("es8311-capture", &capture);
if (ret != IM_OK) {
    goto fail;
}

ret = audio_device_get("es8311-playback", &playback);
if (ret != IM_OK) {
    goto fail;
}

设备名在板级设备表内必须唯一。应用仍可直接遍历 board_audio_config_get();设备 索引属于板级实现细节,不应作为通用应用接口。

12.4 LOOP reference 与 AEC

LOOP 是一路 AUDIO_DIRECTION_LOOP stream。它的数据来自播放 TX FIFO 的内部参考路径, 通过 AICLR 读取,不是麦克风录音。

使用顺序:

audio_stream_open(playback_device, &playback_cfg, &playback);
audio_stream_open(loop_device, &loop_cfg, &loop);
audio_stream_open(capture_device, &capture_cfg, &capture);

audio_stream_write(playback, pcm, frames, &written);

audio_stream_start(loop);
audio_stream_start(playback);
audio_stream_start(capture);

audio_stream_read(loop, reference_pcm, period_frames,
                  &reference_frames, &reference_ts);
audio_stream_read(capture, microphone_pcm, period_frames,
                  &microphone_frames, &microphone_ts);

约束:

  1. playback、loop 和 capture 使用独立的 PCM ring。
  2. loop 的采样率、位宽和声道数必须与 playback 相同。
  3. audio_stream_start(loop) 调用前,playback 必须已经处于 PREPARED 或 RUNNING。
  4. AEC 使用 reference_pcm 和 microphone_pcm,并按时间戳或 frame index 对齐。
  5. reference_pcm 是播放数字参考,microphone_pcm 包含环境声和喇叭回声。

停止顺序:

audio_stream_stop(capture, AUDIO_STOP_MODE_DROP);
audio_stream_stop(loop, AUDIO_STOP_MODE_DROP);
audio_stream_stop(playback, AUDIO_STOP_MODE_DROP);

audio_stream_close(capture);
audio_stream_close(loop);
audio_stream_close(playback);

13. 示例程序与构建

Audio 示例位于:

code/middleware/audio/example/

当前示例演示:

  1. audio_init()。
  2. AIC + ES8311 录音 20 秒。
  3. 等待 2 秒。
  4. AIC + ES8311 回放录音。
  5. 等待 2 秒。
  6. PWMA 回放同一段录音。
  7. audio_deinit()。

构建命令:

tools/imde/bin/imde use-defconfig code/middleware/audio/example/audio_example_defconfig
tools/imde/bin/imde --no-preset -D SDK_APP_DIR=code/middleware/audio/example configure --fresh
tools/imde/bin/imde --no-preset build

示例使用固定采样率、period 和 ring,应用于新产品时应根据 codec/controller capability 和目标延迟重新选择 period frames 与 period count。

14. 常见错误码

错误码 常见原因 处理方式
IM_ERR_INVALID_PARAM 格式、period、buffer、event mask 或指针非法 检查参数和 buffer 几何
IM_ERR_NOT_READY Audio Core 未初始化、ring 无数据、播放未达到启动水位 检查生命周期和预填数据
IM_ERR_BUSY device 已打开、stream 有等待线程或仍有 LOOP 引用 先关闭/停止相关 stream
IM_ERR_NO_MEMORY stream 池已满或 ring 小于计算需求 增大配置或释放其他 stream
IM_ERR_UNSUPPORTED codec/controller 不支持该格式、方向、gain 或操作 查询 capability 或板级设备表
IM_ERR_TIMEOUT 等待 period 或事件超时 检查时钟、DMA、IRQ、codec 和采样率
IM_ERR_IO PCM 数据搬运或底层 I/O 失败 检查 buffer 和 transport
IM_ERR_HARDWARE transport 报硬件错误 停止并检查 controller/codec 寄存器

播放欠载和录音溢出通过 AUDIO_EVENT_XRUN 通知,不是独立的 return code。

Audio Core 的 return code 使用统一 error_code_t。错误发生时,应先读取 audio_stream_state_get() 和 audio_stream_position_get(),再根据状态决定 stop、 close 或完整重新执行 audio_deinit() / audio_init()。