简介
在前两篇文章中微雪ESP32-P4开发板评测【三】ES8311原理图分析和麦克风回环音乐播放 和 微雪ESP32-P4开发板评测【四】解码MP3实现MP3音频播放 我们已经实现了MP3的音频播放,接下来让我们来看下在ESP-ADF下该如何播放MP3文件(在ADF下就不需要我们来自己加入音频的解码器了)

首先安装好ESP-ADF并且创建,Play_mp3_control的项目

项目的默认是按键控制播放左侧的三个音频文件的。我们并不需要关注play_mp3_control_example.c 中的代码。
最主要我们需要关注的左侧组件中的my_board 和 codec的驱动。myboard中主要是控制了板载信息的初始化,比如说I2C和I2S。而,My_codeC中则是音频芯片的驱动。由于我们使用的是ES8311,可以直接删除它的驱动来换成我们自己的。
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/i2c.h"
#include "board.h"
#include "es8311_codec.h"
#include "es8311.h"
/* Fallbacks for older ADF/IDF where these macros may be missing */
#ifndef AUDIO_HAL_CTRL_RESUME
#define AUDIO_HAL_CTRL_RESUME AUDIO_HAL_CTRL_START
#endif
#ifndef AUDIO_HAL_CTRL_PAUSE
#define AUDIO_HAL_CTRL_PAUSE AUDIO_HAL_CTRL_STOP
#endif
/* Fallbacks for ES8311 symbols if the header is absent in the include path */
#ifndef ES8311_RESOLUTION_16
typedef enum
{
ES8311_RESOLUTION_16 = 16,
ES8311_RESOLUTION_18 = 18,
ES8311_RESOLUTION_20 = 20,
ES8311_RESOLUTION_24 = 24,
ES8311_RESOLUTION_32 = 32,
} es8311_resolution_t;
#endif
#ifndef ES8311_CLOCK_CONFIG_FWD
#define ES8311_CLOCK_CONFIG_FWD
typedef struct es8311_clock_config_t
{
bool mclk_inverted;
bool sclk_inverted;
bool mclk_from_mclk_pin;
int mclk_frequency;
int sample_frequency;
} es8311_clock_config_t;
#endif
#ifndef ES8311_ADDRRES_0
#define ES8311_ADDRRES_0 0x18u
#endif
#ifndef es8311_handle_t
typedef void *es8311_handle_t;
#endif
#ifndef ES8311_API_FWD
#define ES8311_API_FWD
es8311_handle_t es8311_create(const i2c_port_t port, const uint16_t dev_addr);
void es8311_delete(es8311_handle_t dev);
esp_err_t es8311_init(es8311_handle_t dev, const es8311_clock_config_t *const clk_cfg,
const es8311_resolution_t res_in, const es8311_resolution_t res_out);
esp_err_t es8311_sample_frequency_config(es8311_handle_t dev, int mclk_frequency, int sample_frequency);
esp_err_t es8311_microphone_config(es8311_handle_t dev, bool digital_mic);
esp_err_t es8311_voice_volume_set(es8311_handle_t dev, int volume, int *volume_set);
#endif
static const char *TAG = "es8311_board_codec";
/* Keep the ES8311 wiring compatible with the example I2S playback */
#define ES8311_I2C_PORT I2C_NUM_0
#define ES8311_I2C_CLK_HZ (100000) /* Standard mode is enough */
#define ES8311_MCLK_MULTIPLE (256)
static es8311_handle_t s_es8311 = NULL;
static int s_volume = 60;
static bool s_muted = false;
static bool s_i2c_started = false;
static int hal_samples_to_rate(audio_hal_iface_samples_t samples)
{
switch (samples)
{
case AUDIO_HAL_08K_SAMPLES:
return 8000;
case AUDIO_HAL_11K_SAMPLES:
return 11025;
case AUDIO_HAL_16K_SAMPLES:
return 16000;
case AUDIO_HAL_22K_SAMPLES:
return 22050;
case AUDIO_HAL_24K_SAMPLES:
return 24000;
case AUDIO_HAL_32K_SAMPLES:
return 32000;
case AUDIO_HAL_44K_SAMPLES:
return 44100;
case AUDIO_HAL_48K_SAMPLES:
return 48000;
default:
return 44100;
}
}
static es8311_resolution_t hal_bits_to_resolution(audio_hal_iface_bits_t bits)
{
switch (bits)
{
case AUDIO_HAL_BIT_LENGTH_24BITS:
return ES8311_RESOLUTION_24;
case AUDIO_HAL_BIT_LENGTH_32BITS:
return ES8311_RESOLUTION_32;
case AUDIO_HAL_BIT_LENGTH_16BITS:
default:
return ES8311_RESOLUTION_16;
}
}
static esp_err_t es8311_setup_clock(int sample_rate, es8311_resolution_t res)
{
const es8311_clock_config_t clk_cfg = {
.mclk_inverted = false,
.sclk_inverted = false,
.mclk_from_mclk_pin = true,
.mclk_frequency = sample_rate * ES8311_MCLK_MULTIPLE,
.sample_frequency = sample_rate,
};
ESP_RETURN_ON_ERROR(es8311_init(s_es8311, &clk_cfg, res, res), TAG, "es8311 init failed");
return es8311_sample_frequency_config(s_es8311, clk_cfg.mclk_frequency, sample_rate);
}
static esp_err_t es8311_i2c_bus_init(void)
{
if (s_i2c_started)
{
return ESP_OK;
}
i2c_config_t i2c_cfg = {
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = ES8311_I2C_CLK_HZ,
};
ESP_RETURN_ON_ERROR(get_i2c_pins(ES8311_I2C_PORT, &i2c_cfg), TAG, "get i2c pins failed");
ESP_RETURN_ON_ERROR(i2c_param_config(ES8311_I2C_PORT, &i2c_cfg), TAG, "i2c param config failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(ES8311_I2C_PORT, I2C_MODE_MASTER, 0, 0, 0), TAG, "i2c driver install failed");
s_i2c_started = true;
return ESP_OK;
}
audio_hal_func_t AUDIO_NEW_CODEC_DEFAULT_HANDLE = {
.audio_codec_initialize = new_codec_init,
.audio_codec_deinitialize = new_codec_deinit,
.audio_codec_ctrl = new_codec_ctrl_state,
.audio_codec_config_iface = new_codec_config_i2s,
.audio_codec_set_mute = new_codec_set_voice_mute,
.audio_codec_set_volume = new_codec_set_voice_volume,
.audio_codec_get_volume = new_codec_get_voice_volume,
};
bool new_codec_initialized()
{
return s_es8311 != NULL;
}
esp_err_t new_codec_init(audio_hal_codec_config_t *cfg)
{
ESP_LOGI(TAG, "Initializing ES8311");
ESP_RETURN_ON_ERROR(es8311_i2c_bus_init(), TAG, "i2c init failed");
if (!s_es8311)
{
s_es8311 = es8311_create(ES8311_I2C_PORT, ES8311_ADDRRES_0);
ESP_RETURN_ON_FALSE(s_es8311, ESP_FAIL, TAG, "es8311 handle create failed");
}
const int sample_rate = hal_samples_to_rate(cfg->i2s_iface.samples);
const es8311_resolution_t res = hal_bits_to_resolution(cfg->i2s_iface.bits);
ESP_RETURN_ON_ERROR(es8311_setup_clock(sample_rate, res), TAG, "clock setup failed");
/* Configure microphone path off for playback-only use case */
ESP_RETURN_ON_ERROR(es8311_microphone_config(s_es8311, false), TAG, "mic config failed");
/* Set an initial volume */
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(s_es8311, s_volume, NULL), TAG, "volume set failed");
s_muted = false;
ESP_LOGI(TAG, "ES8311 ready: %d Hz, %d bits", sample_rate, cfg->i2s_iface.bits);
return ESP_OK;
}
esp_err_t new_codec_deinit(void)
{
if (s_es8311)
{
es8311_delete(s_es8311);
s_es8311 = NULL;
}
if (s_i2c_started)
{
i2c_driver_delete(ES8311_I2C_PORT);
s_i2c_started = false;
}
return ESP_OK;
}
esp_err_t new_codec_ctrl_state(audio_hal_codec_mode_t mode, audio_hal_ctrl_t ctrl_state)
{
if (!s_es8311)
{
return ESP_ERR_INVALID_STATE;
}
if (ctrl_state == AUDIO_HAL_CTRL_START || ctrl_state == AUDIO_HAL_CTRL_RESUME)
{
return new_codec_set_voice_mute(false);
}
if (ctrl_state == AUDIO_HAL_CTRL_STOP || ctrl_state == AUDIO_HAL_CTRL_PAUSE)
{
return new_codec_set_voice_mute(true);
}
return ESP_OK;
}
esp_err_t new_codec_config_i2s(audio_hal_codec_mode_t mode, audio_hal_codec_i2s_iface_t *iface)
{
if (!s_es8311 || !iface)
{
return ESP_ERR_INVALID_ARG;
}
const int sample_rate = hal_samples_to_rate(iface->samples);
const es8311_resolution_t res = hal_bits_to_resolution(iface->bits);
return es8311_setup_clock(sample_rate, res);
}
esp_err_t new_codec_set_voice_mute(bool mute)
{
s_muted = mute;
if (!s_es8311)
{
return ESP_OK;
}
const int target = mute ? 0 : s_volume;
return es8311_voice_volume_set(s_es8311, target, NULL);
}
esp_err_t new_codec_set_voice_volume(int volume)
{
if (volume < 0)
{
volume = 0;
}
else if (volume > 100)
{
volume = 100;
}
s_volume = volume;
if (s_muted || !s_es8311)
{
return ESP_OK;
}
return es8311_voice_volume_set(s_es8311, volume, NULL);
}
esp_err_t new_codec_get_voice_volume(int *volume)
{
if (!volume)
{
return ESP_ERR_INVALID_ARG;
}
*volume = s_muted ? 0 : s_volume;
return ESP_OK;
}函数名一定要和原本的保持一致,实际上还是调用ES8311的驱动库来播放音频。实际上是封装了一层。然后修改board_pins_config.c 修改I2S和I2C的初始化。
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD>
*
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
* it is free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
#include "esp_log.h"
#include "driver/gpio.h"
#include <string.h>
#include "sdkconfig.h"
#include "board.h"
#include "audio_error.h"
#include "audio_mem.h"
#include "soc/soc_caps.h"
static const char *TAG = "MY_BOARD_V1_0";
esp_err_t get_i2c_pins(i2c_port_t port, i2c_config_t *i2c_config)
{
AUDIO_NULL_CHECK(TAG, i2c_config, return ESP_FAIL);
if (port == I2C_NUM_0 || port == I2C_NUM_1)
{
/* Select pins per target */
#if CONFIG_IDF_TARGET_ESP32P4
i2c_config->sda_io_num = 7;
i2c_config->scl_io_num = 8;
#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
i2c_config->sda_io_num = 15;
i2c_config->scl_io_num = 14;
#elif CONFIG_IDF_TARGET_ESP32H2
i2c_config->sda_io_num = 9;
i2c_config->scl_io_num = 8;
#else
i2c_config->sda_io_num = 7;
i2c_config->scl_io_num = 6;
#endif
}
else
{
i2c_config->sda_io_num = -1;
i2c_config->scl_io_num = -1;
ESP_LOGE(TAG, "i2c port %d is not supported", port);
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t get_i2s_pins(int port, board_i2s_pin_t *i2s_config)
{
AUDIO_NULL_CHECK(TAG, i2s_config, return ESP_FAIL);
if (port == 0)
{
/* Default to ESP32-P4 wiring; fall back for other targets */
#if CONFIG_IDF_TARGET_ESP32P4
i2s_config->mck_io_num = GPIO_NUM_13;
i2s_config->bck_io_num = GPIO_NUM_12;
i2s_config->ws_io_num = GPIO_NUM_10;
i2s_config->data_out_num = GPIO_NUM_9;
i2s_config->data_in_num = GPIO_NUM_11;
#else
i2s_config->mck_io_num = GPIO_NUM_16;
i2s_config->bck_io_num = GPIO_NUM_9;
i2s_config->ws_io_num = GPIO_NUM_45;
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
i2s_config->data_out_num = GPIO_NUM_8;
i2s_config->data_in_num = GPIO_NUM_10;
#else
i2s_config->data_out_num = GPIO_NUM_2;
i2s_config->data_in_num = GPIO_NUM_3;
#endif
#endif
}
else if (port == 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;
}
else
{
memset(i2s_config, -1, sizeof(board_i2s_pin_t));
ESP_LOGE(TAG, "i2s port %d is not supported", port);
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t get_spi_pins(spi_bus_config_t *spi_config, spi_device_interface_config_t *spi_device_interface_config)
{
AUDIO_NULL_CHECK(TAG, spi_config, return ESP_FAIL);
AUDIO_NULL_CHECK(TAG, spi_device_interface_config, return ESP_FAIL);
spi_config->mosi_io_num = -1;
spi_config->miso_io_num = -1;
spi_config->sclk_io_num = -1;
spi_config->quadwp_io_num = -1;
spi_config->quadhd_io_num = -1;
spi_device_interface_config->spics_io_num = -1;
ESP_LOGW(TAG, "SPI interface is not supported");
return ESP_OK;
}
// sdcard detect gpio
int8_t get_sdcard_intr_gpio(void)
{
return SDCARD_INTR_GPIO;
}
// max number of sdcard open file
int8_t get_sdcard_open_file_num_max(void)
{
return SDCARD_OPEN_FILE_NUM_MAX;
}
// volume up button
int8_t get_input_volup_id(void)
{
return BUTTON_VOLUP_ID;
}
// volume down button
int8_t get_input_voldown_id(void)
{
return BUTTON_VOLDOWN_ID;
}
// pa enable
int8_t get_pa_enable_gpio(void)
{
return PA_ENABLE_GPIO;
}
// mode button
int8_t get_input_mode_id(void)
{
return BUTTON_MODE_ID;
}
// set button
int8_t get_input_set_id(void)
{
return BUTTON_SET_ID;
}
// play button
int8_t get_input_play_id(void)
{
return BUTTON_PLAY_ID;
}
// mute button
int8_t get_input_mute_id(void)
{
return BUTTON_MUTE_ID;
}由于我们的功放芯片需要开关,所以需要额外集成一个功放芯片的开关
static void board_enable_pa(bool enable)
{
if (PA_ENABLE_GPIO < 0)
{
return;
}
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << PA_ENABLE_GPIO,
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(PA_ENABLE_GPIO, enable ? 1 : 0);
}把原本代码main.c (程序入口,demo代码不叫main.c 我修改为了main.c) 内的多余控制代码都删掉,只简单播放测试
/* Play mp3 file by audio pipeline
with possibility to start, stop, pause and resume playback
as well as adjust volume
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_mem.h"
#include "audio_common.h"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "board.h"
static const char *TAG = "PLAY_FLASH_MP3_CONTROL";
static struct marker
{
int pos;
const uint8_t *start;
const uint8_t *end;
} file_marker;
extern const uint8_t mp3_file_start[] asm("_binary_1_mp3_start");
extern const uint8_t mp3_file_end[] asm("_binary_1_mp3_end");
static int mp3_music_read_cb_impl(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
int read_size = file_marker.end - file_marker.start - file_marker.pos;
if (read_size == 0)
{
return AEL_IO_DONE;
}
else if (len < read_size)
{
read_size = len;
}
memcpy(buf, file_marker.start + file_marker.pos, read_size);
file_marker.pos += read_size;
return read_size;
}
int mp3_music_read_cb(audio_element_handle_t el, char *buf, int len, TickType_t wait_time, void *ctx)
{
return mp3_music_read_cb_impl(el, buf, len, wait_time, ctx);
}
void app_main(void)
{
audio_pipeline_handle_t pipeline;
audio_element_handle_t i2s_stream_writer, mp3_decoder;
esp_log_level_set("*", ESP_LOG_WARN);
esp_log_level_set(TAG, ESP_LOG_INFO);
ESP_LOGI(TAG, "[ 1 ] Start audio codec chip");
audio_board_handle_t board_handle = audio_board_init();
audio_hal_ctrl_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);
audio_hal_set_volume(board_handle->audio_hal, 80);
ESP_LOGI(TAG, "[ 2 ] Create audio pipeline, add all elements to pipeline");
audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
pipeline = audio_pipeline_init(&pipeline_cfg);
mem_assert(pipeline);
ESP_LOGI(TAG, "[2.1] Create mp3 decoder to decode mp3 file and set custom read callback");
mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
mp3_decoder = mp3_decoder_init(&mp3_cfg);
audio_element_set_read_cb(mp3_decoder, mp3_music_read_cb, NULL);
ESP_LOGI(TAG, "[2.2] Create i2s stream to write data to codec chip");
#if defined CONFIG_ESP32_C3_LYRA_V2_BOARD
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_PDM_TX_CFG_DEFAULT();
#else
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
#endif
i2s_cfg.type = AUDIO_STREAM_WRITER;
i2s_stream_writer = i2s_stream_init(&i2s_cfg);
ESP_LOGI(TAG, "[2.3] Register all elements to audio pipeline");
audio_pipeline_register(pipeline, mp3_decoder, "mp3");
audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
ESP_LOGI(TAG, "[2.4] Link it together [mp3_music_read_cb]-->mp3_decoder-->i2s_stream-->[codec_chip]");
const char *link_tag[2] = {"mp3", "i2s"};
audio_pipeline_link(pipeline, &link_tag[0], 2);
ESP_LOGI(TAG, "[ 3 ] Start audio_pipeline");
file_marker.start = mp3_file_start;
file_marker.end = mp3_file_end;
file_marker.pos = 0;
audio_pipeline_run(pipeline);
ESP_LOGI(TAG, "[ 4 ] Playing 1.mp3 (no button control)");
vTaskDelay(portMAX_DELAY); // Keep playing indefinitely
}然后把Board.c 中 audio_board_key_init中的 ADC1_CHANNEL_0 修改成 ADC_CHANNEL_0 。 按照上文中的代码修改Flash大小和分区表。 然后将程序编译并且烧录到单片机中。

视频效果演示
代码如下
play_mp3_control.zip