Add recorder support.

This commit is contained in:
Colin 2025-12-19 23:27:13 +08:00
parent 36558e2252
commit 5c5dcb40fa
8 changed files with 305 additions and 3175 deletions

View File

@ -1,4 +1,4 @@
idf_component_register(SRCS "img_bilibili120.c" "app_sr.c" "esp32_s3_szp.c" "main.c" "app_ui.c"
idf_component_register(SRCS "app_sr.c" "esp32_s3_szp.c" "main.c" "app_ui.c"
INCLUDE_DIRS ".")
spiffs_create_partition_image(storage ../spiffs FLASH_IN_PROJECT)

View File

@ -91,7 +91,6 @@ void detect_Task(void* arg) {
// play_voice = -1;
afe_handle->disable_wakenet(afe_data); // 关闭唤醒词识别
detect_flag = 1; // 标记已检测到唤醒词
ai_gui_in(); // AI人出现
printf("AFE_FETCH_CHANNEL_VERIFIED, channel index: %d\n",
res->trigger_channel_id);
}
@ -148,7 +147,6 @@ void detect_Task(void* arg) {
afe_handle->enable_wakenet(afe_data); // 重新打开唤醒词识别
detect_flag = 0; // 清除标记
printf("\n-----------awaits to be waken up-----------\n");
ai_gui_out(); // AI人退出
continue;
}
}

View File

@ -123,7 +123,8 @@ void mp3_player_init(void) {
player_config.coreID = 1;
// ESP_ERROR_CHECK(audio_player_new(player_config));
// ESP_ERROR_CHECK(audio_player_callback_register(_audio_player_callback, NULL));
// ESP_ERROR_CHECK(audio_player_callback_register(_audio_player_callback,
// NULL));
// 显示界面
music_ui();
@ -396,27 +397,8 @@ void music_ui(void) {
lvgl_port_unlock();
}
// AI人动画
LV_IMG_DECLARE(img_bilibili120);
lv_obj_t* gif_start;
// AI人出现在屏幕
void ai_gui_in(void) {
lvgl_port_lock(0);
gif_start = lv_gif_create(lv_scr_act());
lv_gif_set_src(gif_start, &img_bilibili120);
lv_obj_align(gif_start, LV_ALIGN_CENTER, 0, 0);
lvgl_port_unlock();
}
// AI人退出屏幕
void ai_gui_out(void) {
lvgl_port_lock(0);
lv_obj_del(gif_start);
lvgl_port_unlock();
}
// AI播放音乐
void ai_play(void) {
int index = file_iterator_get_index(file_iterator);

View File

@ -3,8 +3,6 @@
void mp3_player_init(void);
void music_ui(void);
void ai_gui_in(void);
void ai_gui_out(void);
void ai_play(void);
void ai_pause(void);

78
main/format_wav.h Normal file
View File

@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Header structure for WAV file with only one data chunk
*
* @note See this for reference: http://soundfile.sapp.org/doc/WaveFormat/
*
* @note Assignment to variables in this struct directly is only possible for
* little endian architectures (including Xtensa & RISC-V)
*/
typedef struct {
struct {
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
uint32_t chunk_size; /*!< This is the size of the rest of the chunk
following this number */
char chunk_format[4]; /*!< Contains the letters "WAVE" */
} descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< This is the size of the rest of the Subchunk
which follows this number */
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form
of compression */
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< 8000, 44100, etc. */
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
} fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
struct {
char subchunk_id[4]; /*!< Contains the letters "data" */
uint32_t
subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
int16_t data[0]; /*!< Holds raw audio data */
} data_chunk; /*!< The "data" subchunk contains the size of the data and the
actual sound */
} wav_header_t;
/**
* @brief Default header for PCM format WAV files
*
*/
#define WAV_HEADER_PCM_DEFAULT(wav_sample_size, wav_sample_bits, \
wav_sample_rate, wav_channel_num) \
{ \
.descriptor_chunk = {.chunk_id = {'R', 'I', 'F', 'F'}, \
.chunk_size = \
(wav_sample_size) + sizeof(wav_header_t) - 8, \
.chunk_format = {'W', 'A', 'V', 'E'}}, \
.fmt_chunk = {.subchunk_id = {'f', 'm', 't', ' '}, \
.subchunk_size = 16, /* 16 for PCM */ \
.audio_format = 1, /* 1 for PCM */ \
.num_of_channels = (wav_channel_num), \
.sample_rate = (wav_sample_rate), \
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * \
(wav_channel_num) / 8, \
.block_align = (wav_sample_bits) * (wav_channel_num) / 8, \
.bits_per_sample = (wav_sample_bits)}, \
.data_chunk = { \
.subchunk_id = {'d', 'a', 't', 'a'}, \
.subchunk_size = (wav_sample_size) \
} \
}
#ifdef __cplusplus
}
#endif

View File

@ -1,5 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/es7210: "^1.0.0"
# espressif/esp32-camera: "^2.0.10" # 摄像头驱动
lvgl/lvgl: "~8.3.0"
espressif/esp_lvgl_port: "~1.4.0" # LVGL接口
@ -9,3 +10,6 @@ dependencies:
espressif/esp_codec_dev: "~1.3.0" # 音频驱动
espressif/esp-sr: "~1.6.0" # 语音识别
idf:
version: ">=5.4.0"

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,208 @@
#include <stdio.h>
#include <string.h>
#include "app_sr.h"
#include "app_ui.h"
#include "driver/i2c.h"
#include "driver/i2s_std.h"
#include "driver/i2s_tdm.h"
#include "driver/sdmmc_host.h"
#include "es7210.h"
#include "esp32_s3_szp.h"
#include "esp_check.h"
#include "esp_vfs_fat.h"
#include "format_wav.h"
#include "sdkconfig.h"
#include "sdmmc_cmd.h"
/* I2C port and GPIOs */
#define EXAMPLE_I2C_NUM (0)
#define EXAMPLE_I2C_SDA_IO (1)
#define EXAMPLE_I2C_SCL_IO (2)
/* I2S port and GPIOs */
#define EXAMPLE_I2S_NUM (0)
#define EXAMPLE_I2S_MCK_IO (38)
#define EXAMPLE_I2S_BCK_IO (14)
#define EXAMPLE_I2S_WS_IO (13)
#define EXAMPLE_I2S_DI_IO (12)
/* SD card GPIOs */
#define EXAMPLE_SD_CMD_IO (48)
#define EXAMPLE_SD_CLK_IO (47)
#define EXAMPLE_SD_DAT0_IO (21)
/* I2S configurations */
#define EXAMPLE_I2S_TDM_FORMAT (ES7210_I2S_FMT_I2S)
#define EXAMPLE_I2S_CHAN_NUM (2)
#define EXAMPLE_I2S_SAMPLE_RATE (48000)
#define EXAMPLE_I2S_MCLK_MULTIPLE (I2S_MCLK_MULTIPLE_256)
#define EXAMPLE_I2S_SAMPLE_BITS (I2S_DATA_BIT_WIDTH_16BIT)
#define EXAMPLE_I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)
/* ES7210 configurations */
#define EXAMPLE_ES7210_I2C_ADDR (0x41)
#define EXAMPLE_ES7210_I2C_CLK (100000)
#define EXAMPLE_ES7210_MIC_GAIN (ES7210_MIC_GAIN_30DB)
#define EXAMPLE_ES7210_MIC_BIAS (ES7210_MIC_BIAS_2V87)
#define EXAMPLE_ES7210_ADC_VOLUME (0)
/* SD card & recording configurations */
#define EXAMPLE_RECORD_TIME_SEC (10)
#define EXAMPLE_SD_MOUNT_POINT "/sdcard"
#define EXAMPLE_RECORD_FILE_PATH "/RECORD.WAV"
static const char* TAG = "example";
static i2s_chan_handle_t es7210_i2s_init(void) {
i2s_chan_handle_t i2s_rx_chan = NULL; // 定义接收通道句柄
ESP_LOGI(TAG, "Create I2S receive channel");
i2s_chan_config_t i2s_rx_conf = I2S_CHANNEL_DEFAULT_CONFIG(
I2S_NUM_AUTO, I2S_ROLE_MASTER); // 配置接收通道
ESP_ERROR_CHECK(
i2s_new_channel(&i2s_rx_conf, NULL, &i2s_rx_chan)); // 创建i2s通道
ESP_LOGI(TAG, "Configure I2S receive channel to TDM mode");
// 定义接收通道为I2S TDM模式 并配置
i2s_tdm_config_t i2s_tdm_rx_conf = {
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
EXAMPLE_I2S_SAMPLE_BITS, I2S_SLOT_MODE_STEREO,
EXAMPLE_I2S_TDM_SLOT_MASK),
.clk_cfg = {.clk_src = I2S_CLK_SRC_DEFAULT,
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.mclk_multiple = EXAMPLE_I2S_MCLK_MULTIPLE},
.gpio_cfg = {.mclk = EXAMPLE_I2S_MCK_IO,
.bclk = EXAMPLE_I2S_BCK_IO,
.ws = EXAMPLE_I2S_WS_IO,
.dout = -1, // ES7210 only has ADC capability
.din = EXAMPLE_I2S_DI_IO},
};
ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(
i2s_rx_chan, &i2s_tdm_rx_conf)); // 初始化I2S通道为TDM模式
return i2s_rx_chan;
}
sdmmc_card_t* mount_sdcard(void) {
sdmmc_card_t* sdmmc_card = NULL;
ESP_LOGI(TAG, "Mounting SD card");
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 2,
.allocation_unit_size = 8 * 1024};
ESP_LOGI(TAG, "Initializing SD card");
ESP_LOGI(TAG, "Using SDMMC peripheral");
sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
sdmmc_slot_config_t slot_config =
SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
slot_config.width = 1; // 设置为1线SD模式
slot_config.clk = EXAMPLE_SD_CLK_IO;
slot_config.cmd = EXAMPLE_SD_CMD_IO;
slot_config.d0 = EXAMPLE_SD_DAT0_IO;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻
ESP_LOGI(TAG, "Mounting filesystem");
esp_err_t ret;
while (1) {
ret = esp_vfs_fat_sdmmc_mount(EXAMPLE_SD_MOUNT_POINT, &sdmmc_host,
&slot_config, &mount_config, &sdmmc_card);
if (ret == ESP_OK) {
break;
} else if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). ",
esp_err_to_name(ret));
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
ESP_LOGI(
TAG, "Card size: %lluMB, speed: %dMHz",
(((uint64_t)sdmmc_card->csd.capacity) * sdmmc_card->csd.sector_size) >>
20,
sdmmc_card->max_freq_khz / 1000);
return sdmmc_card;
}
static void es7210_codec_init(void) {
// 创建es7210器件句柄
es7210_dev_handle_t es7210_handle = NULL;
es7210_i2c_config_t es7210_i2c_conf = {.i2c_port = EXAMPLE_I2C_NUM,
.i2c_addr = EXAMPLE_ES7210_I2C_ADDR};
ESP_ERROR_CHECK(es7210_new_codec(&es7210_i2c_conf, &es7210_handle));
// 初始化es7210芯片
ESP_LOGI(TAG, "Configure ES7210 codec parameters");
es7210_codec_config_t codec_conf = {
.i2s_format = EXAMPLE_I2S_TDM_FORMAT,
.mclk_ratio = EXAMPLE_I2S_MCLK_MULTIPLE,
.sample_rate_hz = EXAMPLE_I2S_SAMPLE_RATE,
.bit_width = (es7210_i2s_bits_t)EXAMPLE_I2S_SAMPLE_BITS,
.mic_bias = EXAMPLE_ES7210_MIC_BIAS,
.mic_gain = EXAMPLE_ES7210_MIC_GAIN,
.flags.tdm_enable = true};
ESP_ERROR_CHECK(es7210_config_codec(es7210_handle, &codec_conf));
ESP_ERROR_CHECK(
es7210_config_volume(es7210_handle, EXAMPLE_ES7210_ADC_VOLUME));
}
static esp_err_t record_wav(i2s_chan_handle_t i2s_rx_chan) {
ESP_RETURN_ON_FALSE(i2s_rx_chan, ESP_FAIL, TAG,
"invalid i2s channel handle pointer");
esp_err_t ret = ESP_OK;
uint32_t byte_rate = EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_CHAN_NUM *
EXAMPLE_I2S_SAMPLE_BITS / 8;
uint32_t wav_size = byte_rate * EXAMPLE_RECORD_TIME_SEC;
const wav_header_t wav_header =
WAV_HEADER_PCM_DEFAULT(wav_size, EXAMPLE_I2S_SAMPLE_BITS,
EXAMPLE_I2S_SAMPLE_RATE, EXAMPLE_I2S_CHAN_NUM);
ESP_LOGI(TAG, "Opening file %s", EXAMPLE_RECORD_FILE_PATH);
FILE* f = fopen(EXAMPLE_SD_MOUNT_POINT EXAMPLE_RECORD_FILE_PATH, "w");
ESP_RETURN_ON_FALSE(f, ESP_FAIL, TAG, "error while opening wav file");
/* Write wav header */
ESP_GOTO_ON_FALSE(fwrite(&wav_header, sizeof(wav_header_t), 1, f), ESP_FAIL,
err, TAG, "error while writing wav header");
/* Start recording */
size_t wav_written = 0;
static int16_t i2s_readraw_buff[4096];
ESP_GOTO_ON_ERROR(i2s_channel_enable(i2s_rx_chan), err, TAG,
"error while starting i2s rx channel");
while (wav_written < wav_size) {
if (wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
ESP_LOGI(TAG, "Recording: %" PRIu32 "/%ds", wav_written / byte_rate + 1,
EXAMPLE_RECORD_TIME_SEC);
}
size_t bytes_read = 0;
/* Read RAW samples from ES7210 */
ESP_GOTO_ON_ERROR(i2s_channel_read(i2s_rx_chan, i2s_readraw_buff,
sizeof(i2s_readraw_buff), &bytes_read,
pdMS_TO_TICKS(1000)),
err, TAG, "error while reading samples from i2s");
/* Write the samples to the WAV file */
ESP_GOTO_ON_FALSE(fwrite(i2s_readraw_buff, bytes_read, 1, f), ESP_FAIL, err,
TAG, "error while writing samples to wav file");
wav_written += bytes_read;
}
err:
i2s_channel_disable(i2s_rx_chan);
ESP_LOGI(TAG, "Recording done! Flushing file buffer");
fclose(f);
return ret;
}
void app_main(void) {
bsp_i2c_init(); // I2C初始化
@ -14,4 +214,24 @@ void app_main(void) {
mp3_player_init(); // MP3播放器初始化
app_sr_init(); // 语音识别初始化
/* 初始化I2S接口 */
i2s_chan_handle_t i2s_rx_chan = es7210_i2s_init();
/* 初始化es7210芯片 */
es7210_codec_init();
/* 挂载SD卡 */
sdmmc_card_t* sdmmc_card = mount_sdcard();
/* 录音 */
esp_err_t err = record_wav(i2s_rx_chan);
/* 弹出SD卡 */
esp_vfs_fat_sdcard_unmount(EXAMPLE_SD_MOUNT_POINT, sdmmc_card);
if (err == ESP_OK) {
ESP_LOGI(TAG,
"Audio was successfully recorded into " EXAMPLE_RECORD_FILE_PATH
". You can now remove the SD card safely");
} else {
ESP_LOGE(TAG, "Record failed, " EXAMPLE_RECORD_FILE_PATH
" on SD card may not be playable.");
}
}