ESP32-S3 ADF音频播放器player应用开发学习记录
使用的开发板是 ESP32-S3-Korvo-2 V3.0,项目主要应用 播放SD卡音频 功能,所以开发板上其他组件功能未使用,主要学习开发创建播放器player播放音频功能。
开发板上使用的音频解码芯片是 ES8311和 音频功率放大器 NS4150。
组件 | 介绍 |
---|---|
音频编解码芯片 (Audio Codec Chip) | 音频编解码器芯片 ES8311 是一种低功耗单声道音频编解码器,包含单通道 ADC、单通道 DAC、低噪声前置放大器、耳机驱动器、数字音效、模拟混音和增益功能。它通过 I2S 和 I2C 总线与 ESP32-S3-WROOM-1 模组连接,以提供独立于音频应用程序的硬件音频处理。 |
音频功率放大器 (Audio PA Chip) | NS4150 是一款低 EMI、3 W 单声道 D 类音频功率放大器,用于放大来自音频编解码芯片的音频信号,以驱动扬声器。 |
硬件部分
开发板使用引脚如下
管脚名称 | ES8311 | NS4150 |
---|---|---|
IO16 | I2S0_MCLK | |
IO17 | I2C_SDA | |
IO18 | I2C_CLK | |
IO8 | I2S0_DSDIN | |
IO9 | I2S0_SCLK | |
IO45 | I2S0_LRCK | |
IO48 | PA_CTRL |
开发板硬件原理图如下
程序部分
ESP32提供了几个简单的高级 API。它旨在基于标准化音频元素的典型互连快速实现音频应用。所以此次开发使用了高等级的接口 API (esp_audio_
) 来构建 player。
可以参考例程: /examples/advanced_examples/esp_dispatcher_dueros/main/audio_setup.c
创建播放器
创建播放器具体分为六步:
Ⅰ 初始化音频解码芯片
Ⅱ 创建esp_audio实例
Ⅲ 启动编解码驱动程序
Ⅳ 添加音频输入流到特定的esp_audio实例
Ⅴ 添加解码器和编码器到esp_audio实例
Ⅵ 添加音频输出流到特定的esp_audio实例
void Setup_player(void)
{
esp_audio_cfg_t cfg = DEFAULT_ESP_AUDIO_CONFIG();
//硬件编解码器初始化
board_handle = audio_board_init();
cfg.vol_handle = board_handle->audio_hal;
cfg.vol_set = (audio_volume_set)audio_hal_set_volume;
cfg.vol_get = (audio_volume_get)audio_hal_get_volume;
//esp_audio适用于sepcific类型
//ESP_AUDIO_PREFER_MEM 在新管道启动之前停止了先前链接的元素,但out流元素除外
cfg.prefer_type = ESP_AUDIO_PREFER_MEM;
//目标采样率
cfg.resample_rate = 48000;
//根据 cfg 参数创建 esp_audio实例
player = esp_audio_create(&cfg);
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);//启动/停止编解码驱动程序
//添加音频输入流到特定的esp_audio实例
fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
fatfs_cfg.type = AUDIO_STREAM_READER;
audio_element_handle_t fatfs_stream_reader = fatfs_stream_init(&fatfs_cfg);
esp_audio_input_stream_add(player, fatfs_stream_reader);
//添加一个新的编解码器库,可以解码或编码音乐
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
audio_element_handle_t mp3_decoder = mp3_decoder_init(&mp3_cfg);
esp_audio_codec_lib_add(player, AUDIO_CODEC_TYPE_DECODER, mp3_decoder);
//添加音频输出流到特定的esp_audio实例
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_config.sample_rate = 48000;//和编解码的采样率保持一致
i2s_cfg.type = AUDIO_STREAM_WRITER;
audio_element_handle_t i2s_stream_writer = i2s_stream_init(&i2s_cfg);
esp_audio_output_stream_add(player, i2s_stream_writer);
}
Ⅰ.初始化音频解码芯片
audio_board_handle_t board_handle = audio_board_init();
具体初始化流程
\esp-adf\components\audio_board\esp32_s3_korvo2_v3\board.c
audio_board_init --> audio_board_codec_init(音频芯片初始化) --> es8311_codec_init
--> audio_board_adc_init(音频ADC初始化)
audio_board_handle_t audio_board_init(void)
{
if (board_handle) {
ESP_LOGW(TAG, "The board has already been initialized!");
return board_handle;
}
board_handle = (audio_board_handle_t) audio_calloc(1, sizeof(struct audio_board_handle));
AUDIO_MEM_CHECK(TAG, board_handle, return NULL);
board_handle->audio_hal = audio_board_codec_init();
board_handle->adc_hal = audio_board_adc_init();
return board_handle;
}
audio_hal_handle_t audio_board_codec_init(void)
{
audio_hal_codec_config_t audio_codec_cfg = AUDIO_CODEC_DEFAULT_CONFIG();
audio_hal_handle_t codec_hal = audio_hal_init(&audio_codec_cfg, &AUDIO_CODEC_ES8311_DEFAULT_HANDLE);
AUDIO_NULL_CHECK(TAG, codec_hal, return NULL);
return codec_hal;
}
//初始化音频解码芯片的结构体
struct {
audio_hal_adc_input_t adc_input;/ * !<设置adc通道*/
audio_hal_dac_output_t dac_output;/ * !<设置dac通道*/
audio_hal_codec_mode_t codec_mode;/ * !<选择编解码器模式:adc, dac或两者*/
audio_hal_codec_i2s_iface_t i2s_iface;/ * !< set I2S接口配置*/
} audio_hal_codec_config_t;
//编解码器E8311操作功能
AUDIO_CODEC_ES8311_DEFAULT_HANDLE = {
.audio_codec_initialize = es8311_codec_init //ES8311初始化
.audio_codec_deinitialize = es8311_codec_deinit,//去初始化
.audio_codec_ctrl = es8311_codec_ctrl_state,//控制状态
.audio_codec_config_iface = es8311_codec_config_i2s,//i2s配置
.audio_codec_set_mute = es8311_set_voice_mute,//设置静音
.audio_codec_set_volume = es8311_codec_set_voice_volume,//设置音量大小
.audio_codec_get_volume = es8311_codec_get_voice_volume,//获取音量大小
.audio_hal_lock = NULL
.handle = NULL;
};
ES8311初始化
\esp-adf\components\audio_hal\driver\es8311\es8311.c
esp_err_t es8311_codec_init(audio_hal_codec_config_t *codec_cfg)
{
uint8_t datmp, regv;
int coeff;
esp_err_t ret = ESP_OK;
i2c_init(); // ESP32 in master mode ESP32主模式
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, 0x30);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, 0x00);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG03, 0x10);
ret |= es8311_write_reg(ES8311_ADC_REG16, 0x24);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG04, 0x10);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG05, 0x00);
ret |= es8311_write_reg(ES8311_SYSTEM_REG0B, 0x00);
ret |= es8311_write_reg(ES8311_SYSTEM_REG0C, 0x00);
ret |= es8311_write_reg(ES8311_SYSTEM_REG10, 0x1F);
ret |= es8311_write_reg(ES8311_SYSTEM_REG11, 0x7F);
ret |= es8311_write_reg(ES8311_RESET_REG00, 0x80);
/*
* Set Codec into Master or Slave mode 设置编解码器为主或从模式
*/
regv = es8311_read_reg(ES8311_RESET_REG00);
/*
* Set master/slave audio interface 设置主\从音频接口
*/
audio_hal_codec_i2s_iface_t *i2s_cfg = &(codec_cfg->i2s_iface);
switch (i2s_cfg->mode) {
case AUDIO_HAL_MODE_MASTER: /* MASTER MODE 主模式*/
ESP_LOGI(TAG, "ES8311 in Master mode");
regv |= 0x40;
break;
case AUDIO_HAL_MODE_SLAVE: /* SLAVE MODE 从模式*/
ESP_LOGI(TAG, "ES8311 in Slave mode");
regv &= 0xBF;
break;
default:
regv &= 0xBF;
}
ret |= es8311_write_reg(ES8311_RESET_REG00, regv);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, 0x3F);
/*
* Select clock source for internal mclk 为内部时钟选择时钟源
*/
switch (get_es8311_mclk_src()) {
case FROM_MCLK_PIN:
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG01);
regv &= 0x7F;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
case FROM_SCLK_PIN:
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG01);
regv |= 0x80;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
default:
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG01);
regv &= 0x7F;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
}
int sample_fre = 0;
int mclk_fre = 0;
switch (i2s_cfg->samples) {
case AUDIO_HAL_08K_SAMPLES:
sample_fre = 8000;
break;
case AUDIO_HAL_11K_SAMPLES:
sample_fre = 11025;
break;
case AUDIO_HAL_16K_SAMPLES:
sample_fre = 16000;
break;
case AUDIO_HAL_22K_SAMPLES:
sample_fre = 22050;
break;
case AUDIO_HAL_24K_SAMPLES:
sample_fre = 24000;
break;
case AUDIO_HAL_32K_SAMPLES:
sample_fre = 32000;
break;
case AUDIO_HAL_44K_SAMPLES:
sample_fre = 44100;
break;
case AUDIO_HAL_48K_SAMPLES:
sample_fre = 48000;
break;
default:
ESP_LOGE(TAG, "Unable to configure sample rate %dHz", sample_fre);
break;
}
mclk_fre = sample_fre * MCLK_DIV_FRE;
coeff = get_coeff(mclk_fre, sample_fre);
if (coeff < 0) {
ESP_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK", sample_fre, mclk_fre);
return ESP_FAIL;
}
/*
* Set clock parammeters 设置时钟参数
*/
if (coeff >= 0) {
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG02) & 0x07;
regv |= (coeff_div[coeff].pre_div - 1) << 5;
datmp = 0;
switch (coeff_div[coeff].pre_multi) {
case 1:
datmp = 0;
break;
case 2:
datmp = 1;
break;
case 4:
datmp = 2;
break;
case 8:
datmp = 3;
break;
default:
break;
}
if (get_es8311_mclk_src() == FROM_SCLK_PIN) {
datmp = 3; /* DIG_MCLK = LRCK * 256 = BCLK * 8 */
}
regv |= (datmp) << 3;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG02, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG05) & 0x00;
regv |= (coeff_div[coeff].adc_div - 1) << 4;
regv |= (coeff_div[coeff].dac_div - 1) << 0;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG05, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG03) & 0x80;
regv |= coeff_div[coeff].fs_mode << 6;
regv |= coeff_div[coeff].adc_osr << 0;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG03, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG04) & 0x80;
regv |= coeff_div[coeff].dac_osr << 0;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG04, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG07) & 0xC0;
regv |= coeff_div[coeff].lrck_h << 0;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG07, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG08) & 0x00;
regv |= coeff_div[coeff].lrck_l << 0;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG08, regv);
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG06) & 0xE0;
if (coeff_div[coeff].bclk_div < 19) {
regv |= (coeff_div[coeff].bclk_div - 1) << 0;
} else {
regv |= (coeff_div[coeff].bclk_div) << 0;
}
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG06, regv);
}
/*
* mclk inverted or not MCLK是否倒置
*/
if (INVERT_MCLK) {
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG01);
regv |= 0x40;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, regv);
} else {
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG01);
regv &= ~(0x40);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG01, regv);
}
/*
* sclk inverted or not SCLK是否倒置
*/
if (INVERT_SCLK) {
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG06);
regv |= 0x20;
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG06, regv);
} else {
regv = es8311_read_reg(ES8311_CLK_MANAGER_REG06);
regv &= ~(0x20);
ret |= es8311_write_reg(ES8311_CLK_MANAGER_REG06, regv);
}
ret |= es8311_write_reg(ES8311_SYSTEM_REG13, 0x10);
ret |= es8311_write_reg(ES8311_ADC_REG1B, 0x0A);
ret |= es8311_write_reg(ES8311_ADC_REG1C, 0x6A);
/* pa power gpio init 初始化功放引脚*/
gpio_config_t io_conf;
memset(&io_conf, 0, sizeof(io_conf));
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT64(get_pa_enable_gpio());
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
/* enable pa power 使能功放电源*/
es8311_pa_power(true);
codec_dac_volume_config_t vol_cfg = ES8311_DAC_VOL_CFG_DEFAULT();
dac_vol_handle = audio_codec_volume_init(&vol_cfg);
return ESP_OK;
}
#define PA_ENABLE_GPIO GPIO_NUM_48
int8_t get_pa_enable_gpio(void)
{
return PA_ENABLE_GPIO;
}
void es8311_pa_power(bool enable)
{
if (enable) {
gpio_set_level(get_pa_enable_gpio(), 1);
} else {
gpio_set_level(get_pa_enable_gpio(), 0);
}
}
Ⅱ.创建esp_audio实例
// esp_audio配置参数
struct {
int in_stream_buf_size;/// * !<输入缓冲区大小*/
int out_stream_buf_size;// * !<输出缓冲区大小*/
//目标采样率,0:禁用重新采样;其他:支持44.1K、48K、32K、16K、8K
//它应该确保相同的I2S流' sample_rate '
int resample_rate;
QueueHandle_t evt_que;// * !<对于收到的esp_audio事件(可选)*/
esp_audio_event_callback cb_func;// * !< esp_audio events callback(可选)*/
void * cb_ctx;// * !< esp_audio回调上下文(可选)*/
// esp_audio适用于特定类型,首选默认内存。
//ESP_AUDIO_PREFER_MEM '模式在新管道启动之前停止先前链接的元素,除了out stream元素。
//ESP_AUDIO_PREFER_SPEED '模式在新管道启动之前保留先前链接的元素,除了out stream元素。
esp_audio_prefer_t prefer_type;
void * vol_handle;// * !< 音量变化实例 */
audio_volume_set vol_set;// * !<设置音量回调*/
audio_volume_get vol_get;// * !<获取音量回调*/
int task_prio;// * !< esp_audio任务优先级*/
int task_stack;// * !< esp_audio任务栈大小*/
} esp_audio_cfg_t;
cfg.vol_handle = board_handle->audio_hal;
cfg.vol_set = (audio_volume_set)audio_hal_set_volume;
cfg.vol_get = (audio_volume_get)audio_hal_get_volume;
cfg.prefer_type = ESP_AUDIO_PREFER_MEM;
cfg.resample_rate = 48000;
esp_audio_handle_t player = esp_audio_create(&cfg);
Ⅲ.启动编解码驱动程序
//audio_hal_ctrl_codec() Start/stop codec driver启动/停止编解码驱动程序
//audio_hal 所选音频编解码器的参考函数指针
//mode 选择编解码模式 编码/解码/或两者都
//audio_hal_ctrl 选择特定模式的启动和停止状态
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);//启动/停止编解码驱动程序
Ⅳ.添加音频输入流到特定的esp_audio实例
此次开发使用的是读取SD卡的音频文件,所以添加了用来处理来自 sdcard 文件操作系统中的 fatfs stream
fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
fatfs_cfg.type = AUDIO_STREAM_READER;
audio_element_handle_t fatfs_stream_reader = fatfs_stream_init(&fatfs_cfg);
esp_audio_input_stream_add(player, fatfs_stream_reader);//添加音频输入流到特定的esp_audio实例
也可以添加多个音频输入流
// Create readers and add to esp_audio
fatfs_stream_cfg_t fs_reader = FATFS_STREAM_CFG_DEFAULT();
fs_reader.type = AUDIO_STREAM_READER;
esp_audio_input_stream_add(handle, fatfs_stream_init(&fs_reader));//faths stream
http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
http_cfg.event_handle = _http_stream_event_handle;
http_cfg.type = AUDIO_STREAM_READER;
http_cfg.enable_playlist_parser = true;
audio_element_handle_t http_stream_reader = http_stream_init(&http_cfg);
esp_audio_input_stream_add(handle, http_stream_reader);//http stream
Ⅴ.添加解码器和编码器到esp_audio实例
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
audio_element_handle_t mp3_decoder = mp3_decoder_init(&mp3_cfg);
esp_audio_codec_lib_add(player, AUDIO_CODEC_TYPE_DECODER, mp3_decoder);
也可以同时添加其他的编解码器
// Add decoders and encoders to esp_audio
wav_decoder_cfg_t wav_dec_cfg = DEFAULT_WAV_DECODER_CONFIG();
mp3_decoder_cfg_t mp3_dec_cfg = DEFAULT_MP3_DECODER_CONFIG();
mp3_dec_cfg.task_core = 1;
aac_decoder_cfg_t aac_cfg = DEFAULT_AAC_DECODER_CONFIG();
aac_cfg.task_core = 1;
esp_audio_codec_lib_add(handle, AUDIO_CODEC_TYPE_DECODER, aac_decoder_init(&aac_cfg));
esp_audio_codec_lib_add(handle, AUDIO_CODEC_TYPE_DECODER, wav_decoder_init(&wav_dec_cfg));
esp_audio_codec_lib_add(handle, AUDIO_CODEC_TYPE_DECODER, mp3_decoder_init(&mp3_dec_cfg));
Ⅵ.添加音频输出流到特定的esp_audio实例
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.i2s_config.sample_rate = 48000;//和编解码的采样率保持一致
i2s_cfg.type = AUDIO_STREAM_WRITER;
audio_element_handle_t i2s_stream_writer = i2s_stream_init(&i2s_cfg);
esp_audio_output_stream_add(player, i2s_stream_writer);
参考
① ESP Audio — ADF 音频应用开发框架 文档 (readthedocs-hosted.com)
音频播放
//播放给定的uri
//esp_audio_play有跟随活动,设置输入流,输出流和编解码器的uri,启动它们。
//esp_audio将根据URI字段选择输入流,编解码器和输出流。
//@paramhandle esp_audio_handle_t实例
//@param uri 播放音频文件的uri,如果设置为NULL,将使用esp_audio_setup设置的uri
//@param type 特定句柄类型的解码器或编码器
//@param pos 指定的起始位置(以字节为单位)
*
//@return
// - ESP_ERR_AUDIO_NO_ERROR: on success
// - ESP_ERR_AUDIO_TIMEOUT:暂停播放活动
// - ESP_ERR_AUDIO_NOT_SUPPORT:当前状态为AUDIO_STATUS_RUNNING
// - ESP_ERR_AUDIO_INVALID_URI: URI不合法
// - ESP_ERR_AUDIO_INVALID_PARAMETER:无效参数
// - ESP_ERR_AUDIO_STOP_BY_USER:由于esp_audio_stop已被调用而退出。
audio_err_t esp_audio_play(esp_audio_handle_t handle, audio_codec_type_t type, const char *uri, int pos);
// @brief 播放给定的uri,直到音乐结束或发生错误
//@param handleesp_audio_handle_t实例
//@param uri
//@param pos指定的起始位置(以字节为单位)
*
//@return
//- ESP_ERR_AUDIO_NO_ERROR: on success
//- ESP_ERR_AUDIO_TIMEOUT:暂停播放活动
//- ESP_ERR_AUDIO_NOT_SUPPORT:当前状态为AUDIO_STATUS_RUNNING
//- ESP_ERR_AUDIO_INVALID_URI: URI不合法
//- ESP_ERR_AUDIO_INVALID_PARAMETER:无效参数
audio_err_t esp_audio_sync_play(esp_audio_handle_t handle, const char *uri, int pos);
开发中实例问题
- 自己画的板子和开发板中使用的引脚不同,更改ES8311 I2S 引脚
在 esp-adf\esp-adf\components\audio_board\esp32_s3_korvo2_v3\board_pins_config.c
更改引脚
esp_err_t get_i2s_pins(i2s_port_t port, i2s_pin_config_t *i2s_config)
{
AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL);
if (port == I2S_NUM_0) {
i2s_config->bck_io_num = GPIO_NUM_9;
i2s_config->ws_io_num = GPIO_NUM_45;
i2s_config->data_out_num = GPIO_NUM_8;
i2s_config->data_in_num = GPIO_NUM_10;
// i2s_config->mck_io_num = GPIO_NUM_16;
i2s_config->mck_io_num = GPIO_NUM_21;
} else if (port == I2S_NUM_1) {
i2s_config->bck_io_num = -1;
i2s_config->ws_io_num = -1;
i2s_config->data_out_num = -1;
i2s_config->data_in_num = -1;
i2s_config->mck_io_num = -1;
} else {
memset(i2s_config, -1, sizeof(i2s_pin_config_t));
ESP_LOGE(TAG, "i2s port %d is not supported", port);
return ESP_FAIL;
}
return ESP_OK;
}