ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

esp-adf codec_dev_test 测试应用:用模拟 Codec 与真实硬件双维度验证 esp_codec_dev 播放录音接口

esp-adf codec_dev_test 测试应用:用模拟 Codec 与真实硬件双维度验证 esp_codec_dev 播放录音接口
  • 音视频
  • 音频处理
  • 嵌入式
  • 物联网
  • 语音

【免费下载链接】esp-adf

Espressif Advanced Development Framework for Multimedia Applications

项目地址:https://gitcode.com/gh_mirrors/es/esp-adf
点击查看免费下载

本文基于 esp-adf 仓库中的 codec_dev_test 测试应用,系统讲解esp_codec_dev组件配套的单元测试是如何组织的:一套基于完全自定义(模拟)codec 接口对全部上层 API 做纯软件验证,另一套基于 ESP32-S3 KORVO2_V3 等真实开发板验证 ES8311/ES7210 等 codec 芯片的播放、录音、混响镜像、TDM 多通道与重叠场景。读完本文,你可以掌握该测试应用的两类用例结构、换板适配方法(修改 test_board.h 与 test_board.c),以及构建、交互运行与 CI 批量执行的方式。

1. 应用定位与支持的芯片

codec_dev_test是 esp_codec_dev 组件的测试应用(位于 test_apps 目录),官方 README 明确了应用内包含两组测试:

  1. Using customized codec to test all related API:使用一套完全自定义的 codec 实现(仓库中的 my_codec.c / my_codec.h)驱动esp_codec_dev的全部相关 API,属于纯软件层面的单元测试;
  2. Test codec device on special board:在特定开发板上测试真实 codec 设备。默认测试板是ESP32S3_KORVO2_V3(板上 ES8311 播放 + ES7210 录音),如果使用其他开发板,需要修改 test_board.h 中的引脚定义以及 test_board.c 中的实现代码。

README 给出的支持目标为:

Supported TargetsESP32ESP32-C2ESP32-C3ESP32-C6ESP32-S2ESP32-S3ESP32-P4
codec_dev_testYYYYYYY

需要注意:虽然目标表覆盖了多系列芯片,但真实硬件用例(如 S3 板用例、TDM 用例)依赖具体板子与芯片外设能力(SOC_I2S_SUPPORTS_TDM、SOC_I2S_SUPPORTS_XTAL等编译宏),CI 中实际仅对 ESP32-S3 执行(见 pytest_esp_codec_dev_test.py)。

2. 测试框架入口与内存泄漏检查

应用入口非常薄,见 test_app_main.c:

#define TEST_MEMORY_LEAK_THRESHOLD (400) void setUp(void) { unity_utils_record_free_mem(); } void tearDown(void) { unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); } void app_main(void) { unity_run_menu(); }

这里使用 ESP-IDFunity组件的交互式菜单模式(unity_run_menu()):设备启动后进入菜单,按下对应数字键即可单独运行某个TEST_CASE。每个用例执行前后通过unity_utils_record_free_mem()/unity_utils_evaluate_leaks_direct()记录并校验空闲内存,泄漏阈值设为400 字节——对驱动类代码而言,这要求esp_codec_dev_new/delete、各audio_codec_*_if的创建/删除必须严格配对。

main/CMakeLists.txt 声明了依赖组件:unity、driver、esp_driver_i2c、esp_driver_i2s、esp_pm、esp_codec_dev,并启用WHOLE_ARCHIVE TRUE(保证 Unity 用例符号不被裁剪)。main/idf_component.yml 中将esp_codec_dev依赖版本约束为>=1.1,并用override_path: ../../../指向仓库内本组件源码,确保测试的正是当前仓库代码而非组件注册表版本。

3. 第一组测试:自定义 codec(my_codec)驱动全部 API

3.1 模拟接口的设计

my_codec.c 按esp_codec_dev的分层架构实现了四套接口,全部用内存模拟,不依赖任何真实外设:

接口实现内容作用
my_codec_ctrl_new()一个uint8_t reg[MY_CODEC_REG_MAX]数组模拟寄存器验证read_reg/write_reg控制通路
my_codec_data_new()read输出递增字节序列、write累计write_idx验证数据通路读/写行为可断言
my_codec_new()GPIO 复位时序(RESET 拉低 10ms 再拉高)、enable中“先恢复 suspend 再开 PA / 先 mute 再关 PA 再 suspend”的防破音时序验证 codec 接口 open/enable/set_fs/mute/set_vol/set_mic_gain 等
my_codec_vol_new()按db / 6.0206换算位移量对 16bit 数据做移位验证软件音量 handler(audio_codec_vol_if_t)

其中my_codec_set_vol()体现了硬件增益补偿逻辑:

static int my_codec_set_vol(const audio_codec_if_t *h, float db) { my_codec_t *codec = (my_codec_t *) h; // Need minus hw_gain to avoid hardware saturation db -= codec->hw_gain; if (db > 0) { db = 0; } uint8_t data = esp_codec_dev_vol_calc_reg(&vol_range, db); return codec->ctrl_if->write_reg(codec->ctrl_if, MY_CODEC_REG_VOL, 1, &data, 1); }

hw_gain由esp_codec_dev_col_calc_hw_gain(&codec_cfg->hw_gain)根据 PA 电压、codec DAC 电压和 PA 增益计算(测试中配置pa_voltage=3.3, codec_dac_voltage=3.3, pa_gain=10.0,见 test_my_codec.c),这与 esp_codec_dev README 中“硬件增益会影响模拟放大效率,需配置esp_codec_dev_hw_gain_t保证不同平台响度一致”的说明一一对应。

3.2 API 全覆盖用例

"esp codec dev API test"(test_my_codec.c#L25-L182)覆盖了核心 API 矩阵:

  • 设备生命周期:esp_codec_dev_new→esp_codec_dev_open(48kHz / 2ch / 16bit)→esp_codec_dev_close→esp_codec_dev_delete,并验证 close 之后 read/write 必须失败(ret != ESP_CODEC_DEV_OK);

  • 音量曲线:通过esp_codec_dev_set_vol_curve注入自定义曲线

    static esp_codec_dev_vol_map_t volume_maps[] = { {.vol = 1, .db_value = -49.5}, {.vol = 33, .db_value = -33.5}, {.vol = 66, .db_value = -17.0}, {.vol = 100, .db_value = 0.0}, };

    然后断言esp_codec_dev_set_out_vol(dev, 33.0)后模拟寄存器reg[MY_CODEC_REG_VOL] == 43、设 66 时寄存器为 27,即验证了“线性插值换算 dB → 再经esp_codec_dev_vol_calc_reg映射到寄存器值”的完整计算链;

  • mute / mic gain / mic mute:分别对esp_codec_dev_set_out_mute、esp_codec_dev_set_in_gain(20dB/40dB 直接映射到模拟寄存器)、esp_codec_dev_set_in_mute做逐值断言;

  • 读写数据:esp_codec_dev_read两次 256 字节后逐字节断言递增序列(验证 fake data_if 的透传),esp_codec_dev_write后断言write_idx == 512;

  • 软件音量 handler:通过esp_codec_dev_set_vol_handler(dev, vol_if)挂入my_codec_vol_new(),验证esp_codec_dev_set_out_vol(dev, 40.0)时 handler 收到曲线插值出的-30.0 dB,open/close时 handler 的is_open状态翻转,且write 512字节后process_len == 512。

3.3 错误路径与能力边界用例

  • "esp codec dev wrong argument test"(L271-L354):验证esp_codec_dev_new(NULL)、data_if=NULL、open(dev, NULL)、缺vol_map的曲线、set_out_vol(NULL, 50)、read/write 传 NULL 指针等所有非法输入均返回失败;
  • "esp codec dev feature should not support"(L356-L442):验证能力边界——ESP_CODEC_DEV_TYPE_IN设备调用set_vol_curve/set_out_vol/write失败,ESP_CODEC_DEV_TYPE_OUT设备调用set_in_gain/set_in_mute/read失败,且对纯输出设备调用esp_codec_dev_mirror_cfg/mirror_read精确返回ESP_CODEC_DEV_NOT_SUPPORT。

3.4 输入镜像(mirror)用例

"input mirror - fake input keeps latest data"(L184-L269)专门验证esp_codec_dev_mirror_cfg()/esp_codec_dev_mirror_read()的语义:

  • 未读入数据时mirror_read超时返回ESP_CODEC_DEV_TIMEOUT且bytes_read == 0;
  • esp_codec_dev_mirror_cfg(dev, 0)返回ESP_CODEC_DEV_INVALID_ARG,重复配置同尺寸幂等;
  • mirror_read的 NULL 指针、0 长度、NULLbytes_read均返回ESP_CODEC_DEV_INVALID_ARG;
  • 读入 600 字节后再mirror_read(dev, cache_data, 512, 0, &bytes_read)应拿到最近512 字节(即read_data[88..],体现“保留最新数据”的环形缓存语义);
  • close之后mirror_read返回ESP_CODEC_DEV_WRONG_STATE。

该 API 在真实板用例中被用于“边录边播”的镜像回听链路(见下文 4.3 节)。

4. 第二组测试:真实开发板上的 codec 验证

4.1 默认板引脚定义与换板方法

默认板(ESP32-S3 KORVO2_V3)的全部引脚在 test_board.h 中集中定义:

宏GPIO说明
TEST_BOARD_I2C_SDA_PIN17I2C 数据脚(codec 控制总线)
TEST_BOARD_I2C_SCL_PIN18I2C 时钟脚
TEST_BOARD_I2S_BCK_PIN9I2S BCLK
TEST_BOARD_I2S_MCK_PIN16I2S MCLK
TEST_BOARD_I2S_DATA_IN_PIN10I2S DIN(录音数据)
TEST_BOARD_I2S_DATA_OUT_PIN8I2S DOUT(播放数据)
TEST_BOARD_I2S_DATA_WS_PIN45I2S WS/LRCLK
TEST_BOARD_PA_PIN48功放(PA)控制脚

换板时需要同时修改两处:test_board.h的引脚宏,以及test_board.c中与板级强相关的初始化实现(I2C/I2S 安装、codec 型号与 I2C 地址等)。test_board.c 中的ut_i2c_init()/ut_i2s_init()都支持传入自定义引脚结构体(codec_i2c_pin_t/codec_i2s_pin_t),不传则回落到test_board.h默认宏,因此“多 codec 实例”用例可以直接传不同引脚复用同一安装函数(见 L1044-L1060)。

4.2 硬件用例的通用初始化链

以"esp codec dev test using S3 board"(test_board.c#L394-L539)为例,初始化链与 esp_codec_dev README 的 Usage 步骤完全一致:

  1. 安装底层驱动:ut_i2c_init(0)、ut_i2s_init(0)。I2S 使用 IDF 5.x 通道 API,默认I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(16, I2S_SLOT_MODE_STEREO)、16kHz 时钟(TDM 模式则配置 4 slot 的i2s_tdm_config_t);I2C 在CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n时走新的i2c_new_master_bus,内部上拉、glitch_ignore_cnt=7;

  2. 创建默认接口:

    const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg); const audio_codec_ctrl_if_t *out_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); /* addr = ES8311_CODEC_DEFAULT_ADDR, 400kHz */ i2c_cfg.addr = ES7210_CODEC_DEFAULT_ADDR; const audio_codec_ctrl_if_t *in_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg); const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio();
  3. 创建芯片级 codec 接口:ES8311 配codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC、pa_pin = TEST_BOARD_PA_PIN、use_mclk = true后经es8311_codec_new()创建;ES7210 配mic_selected = ES7210_SEL_MIC1|MIC2|MIC3后经es7210_codec_new()创建;

  4. 分别创建ESP_CODEC_DEV_TYPE_OUT的play_dev与ESP_CODEC_DEV_TYPE_IN的record_dev,设置esp_codec_dev_set_out_vol(play_dev, 60.0)、esp_codec_dev_set_in_gain(record_dev, 30.0);

  5. 以 48kHz / 2ch / 16bit 打开双设备后持续读录 10 秒数据(limit_size = 10 * sample_rate * channel * (bits>>3)),每 512 字节用codec_max_sample()检查max > min,确认录音数据“非常量”(即麦克风确实有信号进来);

  6. 结束后按esp_codec_dev_close→esp_codec_dev_delete→ 各audio_codec_delete_*_if→ut_i2c_deinit/ut_i2s_deinit的严格逆序释放。

该链中用到的接口配置结构体定义可参考 esp_codec_dev_defaults.h(audio_codec_i2c_cfg_t的clock_speed_hz为 0 时默认 100kHz、audio_codec_i2s_cfg_t的clk_src为 0 时使用默认时钟源等默认值注释)。

4.3 板级用例总览

用例名(Unity tag:[esp_codec_dev])验证重点默认板/条件
esp codec dev test using S3 board基础播放 + 录音 + 镜像回听(esp_codec_dev_mirror_cfg512B,独立任务mirror_task以esp_codec_dev_mirror_read循环读取并回写播放,校验total_read > 0且出现过活跃数据)KORVO2_V3
esp codec dev test using S3 board with XTALI2S 时钟源切到 XTAL 且esp_pm_configure({40MHz, 40MHz})限制 CPU 频率,验证低主频下音频时钟稳定SOC_I2S_SUPPORTS_XTAL && CONFIG_PM_ENABLE
Record play overlap test双向重叠:①播放持续、录音 5 轮 open/close 打断;②录音持续、播放 5 轮 open/close 打断,验证设备状态机在交叉开关下不崩溃KORVO2_V3
Multiple es8311 codec test use ESP32_S3_KORVO_2L/... reuse data_if test双 ES8311 实例:全部接口独立 vs 共享data_if两种拓扑;执行“只录 → 回放中关录 → 回放中开录 → 录关中播”的交叉开关序列KORVO_2L
Playing while recording use TDM modeRX 走 TDM 4 通道(ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0) | MASK(3)选通道 0/3),TX 走 STD,实现边录 4ch 边播 2chSOC_I2S_SUPPORTS_TDM
Play and record use separate data_if with 2ch 32bitsES7210 以 2ch 32bit +mclk_multiple=256+channel_mask=BIT(0)录 4ch 16bit(顺序 ch3 ch1 ch2 ch4),ES8311 用独立 data_if 以 16bit 播放,回放时跳过 2 字节对齐IDF ≥ 5.0
esp codec dev ADC MIC record and PDM speaker play test (default use ESP32_C3_LYRA)无外部 codec 芯片的板型:audio_codec_new_adc_data(ADC_UNIT_1,12bit,16kHz)录 ADC 麦克风 + I2S PDM TX 驱动喇叭 +dummy_codec_new绑定pa_pin=1,循环 300 轮 read 校验数据非常量后 writeESP32-C3-Lyra,CONFIG_CODEC_DATA_ADC_SUPPORT && SOC_ADC_SUPPORTED && SOC_I2S_SUPPORTS_PDM_TX

其中镜像回听任务(test_board.c#L345-L381)的错误处理值得注意:ESP_CODEC_DEV_TIMEOUT时继续循环、ESP_CODEC_DEV_WRONG_STATE且已收到 stop 时正常退出、其余错误码写入ctx->ret由主线程断言,展示了mirror_read超时语义在真实场景中的正确用法。

TDM 用例中 TDM 配置细节见 L1169-L1289:ut_set_i2s_mode(I2S_COMM_MODE_STD, I2S_COMM_MODE_TDM)先切换 RX 模式再ut_i2s_init,录音侧.channel = 4并配channel_mask,测试结束后ut_clr_i2s_mode()复位模式。

5. 构建、运行与 CI 执行

5.1 默认配置

sdkconfig.defaults 中的关键项:

# Enable FreeRTOS trace CONFIG_FREERTOS_HZ=1000 # Use new I2C master CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n CONFIG_ESP_TASK_WDT_EN=n CONFIG_ESP_INT_WDT=n CONFIG_PM_ENABLE=y
  • CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n:强制使用 IDF 5.3+ 的新 I2C master 总线 API(对应test_board.c中USE_IDF_I2C_MASTER分支,控制接口以 400kHz 运行);
  • CONFIG_PM_ENABLE=y:使能功耗管理,配合SOC_I2S_SUPPORTS_XTAL编译出 XTAL 时钟源用例;
  • 关闭任务/中断 WDT 是因为录音回放为长时间忙等待测试。

5.2 本地构建与交互式运行

# 在测试应用目录下构建(默认目标按 CI 使用 esp32s3) idf.py -D IDF_TARGET=esp32s3 build flash monitor

烧录进入 monitor 后,unity_run_menu()会打印用例菜单,按数字键即可逐个运行;每个用例结束都会执行 400 字节阈值的泄漏检查。真实板用例需要在对应硬件上执行,纯my_codec用例(API test、mirror、wrong argument、feature should not support)在任意支持目标上都能跑通。

5.3 CI 批量执行

pytest_esp_codec_dev_test.py 通过pytest-embedded在esp32s3目标上运行以下固定清单(超时 3600 秒):

case_list = ['esp codec dev test using S3 board', 'Record play overlap test', 'Playing while recording use TDM mode', 'Play and record use separate data_if with 2ch 32bits', 'esp codec dev test using S3 board with XTAL', 'esp codec dev API test', 'esp codec dev wrong argument test', 'esp codec dev feature should not support', 'input mirror - fake input keeps latest data'] dut.run_all_single_board_cases(name=case_list, timeout=3600)

可见 CI 清单覆盖了 8 个 S3 板用例 + 4 个模拟接口用例;而依赖 ESP32-C3-Lyra(ADC MIC + PDM)和 ESP32-S3-KORVO_2L(双 ES8311)的用例不在通用 CI 清单内,属于特定硬件条件下的补充验证,这与各用例源码中的编译条件宏一致。在仓库根目录可用以下命令复现 CI 行为(需安装pytest-embedded-idf环境):

pytest components/esp_codec_dev/test_apps/codec_dev_test/pytest_esp_codec_dev_test.py

6. 小结:从该测试应用能获得什么

  • API 契约参考:test_my_codec.c 对返回值(ESP_CODEC_DEV_OK/INVALID_ARG/WRONG_STATE/NOT_SUPPORT/TIMEOUT)、能力边界(IN/OUT 设备不可互换调用)、关闭后行为均有明确断言,是调用esp_codec_dev时的行为契约手册;
  • 自定义 codec 模板:my_codec.c 给出了实现audio_codec_ctrl_if_t/audio_codec_data_if_t/audio_codec_if_t/audio_codec_vol_if_t四类接口、防破音 enable 时序与硬件增益补偿的完整骨架,移植新 codec 芯片时可作为起点;
  • 真实板适配模板:test_board.c 展示了 I2C/I2S 双驱动安装、多 codec 实例共享/独立接口、TDM 通道选择、XTAL 时钟源与低主频 PM 配置等场景的完整初始化/释放序列;
  • 可复制的 CI 模式:sdkconfig.defaults+ Unity 菜单 +pytest-embedded用例清单的组合,可作为其他 ADF 组件测试应用的参考结构。
  • 音视频
  • 音频处理
  • 嵌入式
  • 物联网
  • 语音

【免费下载链接】esp-adf

Espressif Advanced Development Framework for Multimedia Applications

项目地址:https://gitcode.com/gh_mirrors/es/esp-adf
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进