【AI小智硬件程序(七)】
enabled_input/enabled_output:记录音频输入 / 输出功能的 “启用状态”(false表示默认未启用),后续调用enable_input()/enable_output()时会把这些变量设为true,方便判断当前功能是否处于激活状态;input_ref:记录音频输入的 “参考模式” 配置(构造函数传入的参数),用于后续 ES7210 芯片的硬件参数初始化。
AI小智硬件程序(七)
音频代码重构
一、代码结构更改
1.目前代码结构


音频输入和音频输出作为两个独立的组件,既然音频输入和输出在很多场合都是同时出现的,
我们可以将这两个组件封装起来成为一个音频组件,乐鑫已经将这件事做好了
那就是 esp_codec_dev 组件
2.使用esp_codec_dev 组件框架
https://github.com/espressif/esp-adf/blob/60054d26e02b358de2ac3db0d2b631a46ae240b8/components/esp_codec_dev/README_CN.md
网站上有各种介绍
我们的结构简洁来说就是
3.整体框架

留个印象,后面写代码具体理解。
二、代码整体框架
1、创建音频硬件抽象层类

audio_hal.h
#pragma once
#include <vector>
#include <cstdint>
class AudioHAL
{
private:
public:
AudioHAL();
~AudioHAL();
virtual void enable_input();
virtual void enable_output();
virtual void disable_input();
virtual void disable_output();
virtual void write(std::vector<int16_t>& data);
virtual int read(std::vector<int16_t>& data,int samples);
};
audio_hal.cpp
#include "audio_hal.h"
AudioHAL::AudioHAL()
{
}
AudioHAL::~AudioHAL()
{
}
这段代码定义了一个音频硬件抽象层(HAL)的基类 AudioHAL,本质是对「音频输入 / 输出」的通用操作(启用 / 禁用、读写音频数据)做了标准化抽象,目的是屏蔽不同音频硬件(比如 ES7210、ES8311)的底层差异,让上层代码能统一调用音频功能。
1.本质是音频硬件的通用抽象层基类,定义标准化接口;
2.屏蔽底层硬件差异,让上层代码统一调用音频输入 / 输出功能;
3.所有方法为虚函数,需子类实现具体硬件的操作逻辑。
2、创建硬件层文件

audio_es8311_es7210.h
#pragma once
#include "audio_hal.h"
#include <gpio_num.h>
class AudioEs8311Es7210: public AudioHAL
{
private:
public:
virtual void enable_input()override;
virtual void enable_output()override;
virtual void disable_input()override;
virtual void disable_output()override;
virtual void write(std::vector<int16_t>& data)override;
virtual int read(std::vector<int16_t>& data,int samples)override;
AudioEs8311Es7210(void* i2c_bus_handle, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk_pin, gpio_num_t bclk_pin, gpio_num_t ws_pin, gpio_num_t dout_pin, gpio_num_t din_pin,
gpio_num_t pa_pin,bool pa_reverted, uint8_t es8311_i2c_addr, uint8_t es7210_i2c_addr, bool input_ref);
~AudioEs8311Es7210();
};
audio_es8311_es7210.cpp
#include "audio_es8311_es7210.h"
AudioEs8311Es7210::AudioEs8311Es7210(void* i2c_bus_handle, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk_pin, gpio_num_t bclk_pin, gpio_num_t ws_pin, gpio_num_t dout_pin, gpio_num_t din_pin,
gpio_num_t pa_pin,bool pa_reverted, uint8_t es8311_i2c_addr, uint8_t es7210_i2c_addr, bool input_ref)
{
}
AudioEs8311Es7210::~AudioEs8311Es7210()
{
}
void AudioEs8311Es7210::enable_input()
{
}
void AudioEs8311Es7210::enable_output()
{
}
void AudioEs8311Es7210::disable_input()
{
}
void AudioEs8311Es7210::disable_output()
{
}
int AudioEs8311Es7210::read(std::vector<int16_t>& data,int samples)
{
return 0;
}
void AudioEs8311Es7210::write(std::vector<int16_t>& data)
{
}
定义了适配 ES8311(输出)+ ES7210(输入)音频芯片的 AudioEs8311Es7210 子类(继承通用音频抽象层 AudioHAL),仅声明并空实现了所有音频操作接口—— 包含芯片初始化的构造函数(接收引脚、I2C、采样率等硬件参数),以及输入输出启用 / 禁用、音频数据读写的方法,但暂未实现具体的芯片控制和数据传输逻辑。
1.继承通用音频抽象层,绑定 ES8311/ES7210 硬件配置参数;
2.覆写所有音频操作接口,但仅为空实现(无实际功能);
3.是后续填充 ES8311/ES7210 芯片具体驱动逻辑的 “骨架代码”。
三、代码具体实现(一)
1、安装依赖

idf.py add-dependency "espressif/esp_codec_dev^1.3.4"
idf.py reconfigure

2、创建类成员变量

#include <driver/i2s_types.h>
#include <audio_codec_data_if.h>
#include <audio_codec_ctrl_if.h>
#include <audio_codec_if.h>
#include <audio_codec_gpio_if.h>
#include <esp_codec_dev.h>
protected:
i2s_chan_handle_t tx_handle = NULL;
i2s_chan_handle_t rx_handle = NULL;
int output_sample_rate=0;
int input_sample_rate=0;
const audio_codec_data_if_t* data_if = nullptr;
const audio_codec_ctrl_if_t* out_ctrl_if = nullptr;
const audio_codec_ctrl_if_t* in_ctrl_if = nullptr;
const audio_codec_if_t* out_codec_if = nullptr;
const audio_codec_if_t* in_codec_if = nullptr;
const audio_codec_gpio_if_t* gpio_if = nullptr;
esp_codec_dev_handle_t audio_output_dev = nullptr;
esp_codec_dev_handle_t audio_input_dev = nullptr;
新添加的变量是 ESP32 音频编解码的核心资源句柄 / 参数,涵盖 I2S 通道、编解码器接口、设备句柄和采样率。统一封装在基类中,实现音频资源的标准化管理,降低子类开发成本。
PS:
这些成员变量是音频框架的公共资源(比如 I2S 句柄、编解码器接口):
若把它们写在每个硬件子类(如AudioEs8311Es7210)里,会导致每个子类都重复定义相同变量,代码冗余;
用protected把它们放在基类AudioHAL中,既能让子类直接访问(避免冗余),又能阻止外部类随意修改(保证封装性)。
3、声明私有成员函数

#include <hal/i2c_types.h>
/**
* @brief 初始化i2s通道
* @param mclk_pin mclk引脚
* @param bclk_pin bclk引脚
* @param ws_pin ws引脚
* @param dout_pin dout引脚
* @param din_pin din引脚
*/
void init_i2s_channel(gpio_num_t mclk_pin,gpio_num_t bclk_pin,gpio_num_t ws_pin, gpio_num_t dout_pin,gpio_num_t din_pin);
// 初始化es8311
/**
* @brief 初始化es8311
* @param i2c_bus_handle i2c总线句柄
* @param i2c_port i2c总线端口
* @param es8311_i2c_addr es8311 i2c地址
* @param pa_pin 功放使能引脚
* @param pa_reverted
* pa_reverted = false表示非反转逻辑:当 pa_pin 引脚输出 1(高电平) 时,功放(PA)被使能;输出 0(低电平) 时,功放被禁用。
* pa_reverted = true 表示反转逻辑:当 pa_pin 引脚输出 0(低电平) 时,功放被使能;输出 1(高电平) 时,功放被禁用。
*/
void init_es8311(void* i2c_bus_handle,i2c_port_t i2c_port,uint8_t es8311_i2c_addr,gpio_num_t pa_pin,bool pa_reverted);
/**
* @brief 初始化es7210
* @param i2c_bus_handle i2c总线句柄
* @param i2c_port i2c总线端口
* @param es7210_i2c_addr es7210 i2c地址
*/
void init_es7210(void* i2c_bus_handle,i2c_port_t i2c_port,uint8_t es7210_i2c_addr);
这里的三个函数对应的就是之前框架图里的三部分
功能写好了,具体还没调用,data的部分后面会写,写好了调用就是完整的流程
4、实现私有成员函数

#include <driver/i2s_common.h>
#include <driver/i2s_std.h>
#include <driver/i2s_tdm.h>
#include <esp_codec_dev_defaults.h>
void AudioEs8311Es7210::init_i2s_channel(gpio_num_t mclk_pin, gpio_num_t bclk_pin, gpio_num_t ws_pin, gpio_num_t dout_pin, gpio_num_t din_pin)
{
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER, //i2s的主从模式
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,//中断优先级,数值越小优先级越高
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)output_sample_rate,
.clk_src = I2S_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true,
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = mclk_pin,
.bclk = bclk_pin,
.ws = ws_pin,
.dout = dout_pin,
.din = GPIO_NUM_NC,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
i2s_tdm_config_t tdm_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)input_sample_rate,
.clk_src = I2S_CLK_SRC_DEFAULT,
.ext_clk_freq_hz = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
.bclk_div = 8,
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2),
.ws_width = I2S_TDM_AUTO_WS_WIDTH,
.ws_pol = false,
.bit_shift = true,
.left_align = false,
.big_endian = false,
.bit_order_lsb = false,
.skip_mask = false,
.total_slot = I2S_TDM_AUTO_SLOT_NUM
},
.gpio_cfg = {
.mclk = mclk_pin,
.bclk = bclk_pin,
.ws = ws_pin,
.dout = GPIO_NUM_NC,
.din = din_pin,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle, &tdm_cfg));
}
void AudioEs8311Es7210::init_es8311(void* i2c_bus_handle,i2c_port_t i2c_port,uint8_t es8311_i2c_addr,gpio_num_t pa_pin,bool pa_reverted)
{
// Output
audio_codec_i2c_cfg_t i2c_cfg = {
.port = i2c_port,
.addr = es8311_i2c_addr,
.bus_handle = i2c_bus_handle,
};
out_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
gpio_if = audio_codec_new_gpio();
es8311_codec_cfg_t es8311_cfg = {};
es8311_cfg.ctrl_if = out_ctrl_if;
es8311_cfg.gpio_if = gpio_if;
es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC;
es8311_cfg.pa_pin = pa_pin;
es8311_cfg.pa_reverted = pa_reverted;
es8311_cfg.use_mclk = true;
es8311_cfg.hw_gain.pa_voltage = 5.0;
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
out_codec_if = es8311_codec_new(&es8311_cfg);
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
.codec_if = out_codec_if,
.data_if = data_if,
};
audio_output_dev = esp_codec_dev_new(&dev_cfg);
}
void AudioEs8311Es7210::init_es7210(void* i2c_bus_handle,i2c_port_t i2c_port,uint8_t es7210_i2c_addr)
{
audio_codec_i2c_cfg_t i2c_cfg = {
.port = i2c_port,
.addr = es7210_i2c_addr,
.bus_handle = i2c_bus_handle,
};
in_ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
es7210_codec_cfg_t es7210_cfg = {};
es7210_cfg.ctrl_if = in_ctrl_if;
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4;
in_codec_if = es7210_codec_new(&es7210_cfg);
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.codec_if = in_codec_if,
.data_if = data_if,
};
audio_input_dev = esp_codec_dev_new(&dev_cfg);
}
- init_i2s_channel:初始化 I2S 总线通道
先创建 I2S 主通道(生成 tx/rx 句柄);
分别配置输出(std 模式)、输入(tdm 模式)的 I2S 参数(采样率、引脚、数据格式等);
初始化 tx 通道为 std 模式(适配 ES8311 输出)、rx 通道为 tdm 模式(适配 ES7210 输入),完成音频数据传输链路搭建。- init_es8311:初始化 ES8311 输出芯片
配置 I2C 控制参数,创建输出控制接口;
初始化 GPIO 接口,配置 ES8311 专属参数(功放引脚、工作模式为 DAC 等);
创建 ES8311 核心接口,封装成标准音频输出设备句柄(audio_output_dev)。- init_es7210:初始化 ES7210 输入芯片
配置 I2C 控制参数,创建输入控制接口;
配置 ES7210 专属参数(启用 4 路麦克风),创建 ES7210 核心接口;
封装成标准音频输入设备句柄(audio_input_dev)。
5、编写构造函数

```csharp
AudioEs8311Es7210::AudioEs8311Es7210(void* i2c_bus_handle,i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk_pin, gpio_num_t bclk_pin, gpio_num_t ws_pin, gpio_num_t dout_pin, gpio_num_t din_pin,
gpio_num_t pa_pin,bool pa_reverted, uint8_t es8311_i2c_addr, uint8_t es7210_i2c_addr, bool input_ref)
{
this->input_sample_rate = input_sample_rate;
this->output_sample_rate = output_sample_rate;
init_i2s_channel(mclk_pin, bclk_pin, ws_pin, dout_pin, din_pin);
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = rx_handle,
.tx_handle = tx_handle,
};
data_if = audio_codec_new_i2s_data(&i2s_cfg);
init_es8311(i2c_bus_handle, i2c_port, es8311_i2c_addr, pa_pin, pa_reverted);
init_es7210(i2c_bus_handle, i2c_port, es7210_i2c_addr);
}
其中这个部分对应

四、代码具体实现(二)
1、定义成员变量

bool enabled_input = false;
bool enabled_output = false;
bool input_ref=false;
enabled_input/enabled_output:记录音频输入 / 输出功能的 “启用状态”(false表示默认未启用),后续调用enable_input()/enable_output()时会把这些变量设为true,方便判断当前功能是否处于激活状态;
input_ref:记录音频输入的 “参考模式” 配置(构造函数传入的参数),用于后续 ES7210 芯片的硬件参数初始化。
2、获取输入参考参数

它的核心作用是告诉 ES7210 芯片:音频输入信号的参考基准是什么
3、实现代码
AudioEs8311Es7210::~AudioEs8311Es7210()
{
esp_codec_dev_close(audio_input_dev);
esp_codec_dev_close(audio_output_dev);
esp_codec_dev_delete(audio_output_dev);
esp_codec_dev_delete(audio_input_dev);
audio_codec_delete_codec_if(in_codec_if);
audio_codec_delete_ctrl_if(in_ctrl_if);
audio_codec_delete_codec_if(out_codec_if);
audio_codec_delete_ctrl_if(out_ctrl_if);
audio_codec_delete_gpio_if(gpio_if);
audio_codec_delete_data_if(data_if);
}
void AudioEs8311Es7210::enable_input()
{
if (enabled_input) {
return;
}
esp_codec_dev_sample_info_t codec_dev = {
.bits_per_sample = 16,
.channel = 4,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)input_sample_rate,
.mclk_multiple = 0,
};
if (input_ref) {
codec_dev.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
}
ESP_ERROR_CHECK(esp_codec_dev_open(audio_input_dev, &codec_dev));
ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(audio_input_dev, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
enabled_input= true;
}
void AudioEs8311Es7210::enable_output()
{
if (enabled_output) {
return;
}
esp_codec_dev_sample_info_t codec_dev = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(audio_output_dev, &codec_dev));
enabled_output = true;
}
void AudioEs8311Es7210::disable_input()
{
ESP_ERROR_CHECK(esp_codec_dev_close(audio_input_dev));
enabled_input = false;
}
void AudioEs8311Es7210::disable_output()
{
ESP_ERROR_CHECK(esp_codec_dev_close(audio_output_dev));
enabled_output = false;
}
int AudioEs8311Es7210::read(int16_t* data,int samples)
{
if (enabled_input) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(audio_input_dev, (void*)data , samples * sizeof(int16_t)));
}
return samples * sizeof(int16_t);
}
void AudioEs8311Es7210::write(const int16_t* data)
{
if (enabled_output) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(audio_output_dev, (void*)data, sizeof(data) * sizeof(int16_t)));
}
}
AudioEs8311Es7210 核心函数功能说明
| 函数 | 核心操作 |
|---|---|
析构函数 ~AudioEs8311Es7210 |
释放所有音频资源:关闭/删除设备句柄、销毁编解码器接口(控制/数据/GPIO),避免内存泄漏 |
enable_input |
启用录音功能: 1. 检查状态避免重复启用; 2. 配置 ES7210 采样参数(16 位、4 通道、采样率); 3. 根据 input_ref调整通道掩码;4. 打开输入设备并设置麦克风增益; 5. 标记输入为启用状态 |
enable_output |
启用播放功能: 1. 检查状态避免重复启用; 2. 配置 ES8311 采样参数(16 位、单通道、采样率); 3. 打开输出设备; 4. 标记输出为启用状态 |
disable_input/disable_output |
禁用录音 / 播放功能:关闭对应设备,重置启用状态 |
read |
读取录音数据:仅当输入启用时,从音频输入设备读取指定数量的 16 位音频数据 |
write |
写入播放数据:仅当输出启用时,向音频输出设备写入 16 位音频数据(注:sizeof(data) 是笔误,应为数据长度) |
4、格式调整
适配之前的函数实现
virtual void write(const int16_t* data)override;
virtual int read(int16_t* data,int samples)override;

virtual void write(const int16_t* data);
virtual int read(int16_t* dest,int samples);
更多推荐



所有评论(0)