简介
对于大模型的接入这里有很多种方式, 如果是仅仅想搭建一个类似于chatgpt的网站页面的话只需要在esp32-s3box上搭建一个http_server 然后当用户访问本机的IP地址的话, 解析用户的请求,然后直接将文本信息发送到后台的大模型的服务器上即可实现一个仿chatgpt的页面. 但是对于chatgpt_demo或者小智来说其最基本的方案就是. 首先麦克风检测音频输入, 然后将音频直接发送或者转码后发送到ASR服务, 获取到语音转换后的文字内容. 再将文字内容发送到大模型. 当拿到响应之后便可以调用TTS服务,将文本内容再转换成音频格式,通过HTTP或者HTTPS协议将音频的数据下载到ESP32-S3BOX. 然后使用扬声器播放. 简要的流程如下所示.
经过我的短暂摸索实现了基本的语音录制和播放. 有了这两点之后我们便可以将数据发送到下面的服务中. demo的参考仓库是ESP-BSP
这个仓库为乐鑫的开发板提供了板级支持包,其中包括esp32-s3-box.
我们需要使用的是其中example下的display_audio_photo demo. 经过我的删减移除了原本的功能从而保留了最基本的使用麦克风录音,然后给录音的文件增加WAV头信息. 保存到文件系统中,然后再调用播放.
#include "audio_play.h" static esp_codec_dev_handle_t mic_codec_dev = NULL; static bool play_file_stop = false; static esp_codec_dev_handle_t spk_codec_dev = NULL; void app_audio_init(void) { spk_codec_dev = bsp_audio_codec_speaker_init(); assert(spk_codec_dev); esp_codec_dev_set_out_vol(spk_codec_dev, DEFAULT_VOLUME); } void play_file(const char *path) { FILE *file = NULL; int16_t *wav_bytes = malloc(BUFFER_SIZE); if (!wav_bytes) { return; } file = fopen(path, "rb"); if (!file) { free(wav_bytes); return; } struct { uint8_t header[22]; uint16_t num_channels; uint32_t sample_rate; uint8_t other[6]; uint16_t bits_per_sample; uint8_t extra[4]; uint32_t data_size; } wav_header; if (fread(&wav_header, 1, sizeof(wav_header), file) != sizeof(wav_header)) { fclose(file); free(wav_bytes); return; } esp_codec_dev_sample_info_t fs = { .sample_rate = wav_header.sample_rate, .channel = wav_header.num_channels, .bits_per_sample = wav_header.bits_per_sample, .mclk_multiple = I2S_MCLK_MULTIPLE_384, }; esp_codec_dev_open(spk_codec_dev, &fs); while (!play_file_stop) { size_t bytes_read = fread(wav_bytes, 1, BUFFER_SIZE, file); if (bytes_read == 0) break; esp_codec_dev_write(spk_codec_dev, wav_bytes, bytes_read); } fclose(file); free(wav_bytes); esp_codec_dev_close(spk_codec_dev); } void stop_playback(void) { play_file_stop = true; } void app_mic_init(void) { mic_codec_dev = bsp_audio_codec_microphone_init(); assert(mic_codec_dev); esp_codec_dev_set_in_gain(mic_codec_dev, 50.0); } void record_file(const char *path) { FILE *record_file = fopen(path, "wb"); if (!record_file) { ESP_LOGE(TAG, "%s file does not exist!", path); return; } struct { uint8_t header[22]; uint16_t num_channels; uint32_t sample_rate; uint8_t other[6]; uint16_t bits_per_sample; uint8_t extra[4]; uint32_t data_size; } recording_header = { .num_channels = 1, .sample_rate = SAMPLE_RATE, .bits_per_sample = 16, .data_size = RECORDING_LENGTH * BUFFER_SIZE}; fwrite(&recording_header, 1, sizeof(recording_header), record_file); esp_codec_dev_sample_info_t fs = { .sample_rate = SAMPLE_RATE, .channel = 1, .bits_per_sample = 16, .mclk_multiple = I2S_MCLK_MULTIPLE_384, }; esp_codec_dev_open(mic_codec_dev, &fs); int16_t *recording_buffer = malloc(BUFFER_SIZE); for (int i = 0; i < RECORDING_LENGTH; i++) { esp_codec_dev_read(mic_codec_dev, recording_buffer, BUFFER_SIZE); fwrite(recording_buffer, 1, BUFFER_SIZE, record_file); } free(recording_buffer); fclose(record_file); esp_codec_dev_close(mic_codec_dev); }
这里对它原本的代码进行了删减, 仅仅保留了核心的功能.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "esp_log.h" #include "esp_codec_dev.h" #include "bsp/esp-bsp.h" #define BUFFER_SIZE (1024) #define DEFAULT_VOLUME (100) #define SAMPLE_RATE (22050) #define DEFAULT_VOLUME (70) #define RECORDING_LENGTH (160) #define REC_FILENAME "/spiffs/recording.wav" static const char *TAG = "AUDIO"; void app_audio_init(void); void play_file(const char *path); void stop_playback(void); void app_mic_init(void); void record_file(const char *path);
之后我们便可以在主程序中调用我们抽取出来的函数. 如下所示.
#include "esp_log.h" #include "bsp/esp-bsp.h" #include "audio_play.h" #define REC_FILENAME "/spiffs/recording.wav" void app_main(void) { /* Initialize and mount SPIFFS */ bsp_spiffs_mount(); /* Initialize I2C (for touch and audio) */ bsp_i2c_init(); /* Initialize audio */ app_audio_init(); app_mic_init(); record_file("/spiffs/recording.wav"); vTaskDelay(50); ESP_LOGI("MAIN", "Finished recording"); play_file("/spiffs/recording.wav"); while (1) { /* code */ } }
使用BSP可以避免编写复杂的驱动程序,直接实现音频的录制和播放。虽然BSP的使用相对简单,但理解其底层代码仍有一定难度。乐鑫提供了丰富的示例代码,例如在录制音频后自动添加WAV文件头的功能,这大大减少了开发时间。
工程文件如下: