Hello, Xiaozhi.

This commit is contained in:
Terrence
2024-08-31 18:00:23 +08:00
parent 142653087f
commit 5da7d1755f
28 changed files with 2259 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
tmp/
components/
managed_components/
build/
.vscode/
.devcontainer/
sdkconfig.old
sdkconfig
dependencies.lock

8
CMakeLists.txt Executable file
View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(xiaozhi)

6
README.md Executable file
View File

@ -0,0 +1,6 @@
# 你好,小智
【ESP32+SenseVoice+Qwen72B打造你的AI聊天伴侣
https://www.bilibili.com/video/BV11msTenEH3/?share_source=copy_web&vd_source=ee1aafe19d6e60cf22e60a93881faeba

430
main/Application.cc Normal file
View File

@ -0,0 +1,430 @@
#include "Application.h"
#include <cstring>
#include "esp_log.h"
#include "model_path.h"
#include "SystemInfo.h"
#include "cJSON.h"
#include "silk_resampler.h"
#define TAG "application"
#define INPUT_SAMPLE_RATE 16000
#define DECODE_SAMPLE_RATE 24000
#define OUTPUT_SAMPLE_RATE 24000
Application::Application() {
event_group_ = xEventGroupCreate();
audio_encode_queue_ = xQueueCreate(100, sizeof(AudioEncoderData));
audio_decode_queue_ = xQueueCreate(100, sizeof(AudioPacket*));
srmodel_list_t *models = esp_srmodel_init("model");
for (int i = 0; i < models->num; i++) {
ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]);
if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL) {
wakenet_model_ = models->model_name[i];
} else if (strstr(models->model_name[i], ESP_NSNET_PREFIX) != NULL) {
nsnet_model_ = models->model_name[i];
}
}
opus_encoder_.Configure(INPUT_SAMPLE_RATE, 1);
opus_decoder_ = opus_decoder_create(DECODE_SAMPLE_RATE, 1, NULL);
if (DECODE_SAMPLE_RATE != OUTPUT_SAMPLE_RATE) {
assert(0 == silk_resampler_init(&resampler_state_, DECODE_SAMPLE_RATE, OUTPUT_SAMPLE_RATE, 1));
}
}
Application::~Application() {
if (afe_detection_data_ != nullptr) {
esp_afe_sr_v1.destroy(afe_detection_data_);
}
if (afe_communication_data_ != nullptr) {
esp_afe_vc_v1.destroy(afe_communication_data_);
}
if (opus_decoder_ != nullptr) {
opus_decoder_destroy(opus_decoder_);
}
if (audio_encode_task_stack_ != nullptr) {
free(audio_encode_task_stack_);
}
if (audio_decode_task_stack_ != nullptr) {
free(audio_decode_task_stack_);
}
vQueueDelete(audio_decode_queue_);
vQueueDelete(audio_encode_queue_);
vEventGroupDelete(event_group_);
}
void Application::Start() {
audio_device_.Start(INPUT_SAMPLE_RATE, OUTPUT_SAMPLE_RATE);
audio_device_.OnStateChanged([this]() {
if (audio_device_.playing()) {
SetChatState(kChatStateSpeaking);
} else {
// Check if communication is still running
if (xEventGroupGetBits(event_group_) & COMMUNICATION_RUNNING) {
SetChatState(kChatStateListening);
} else {
SetChatState(kChatStateIdle);
}
}
});
// OPUS encoder / decoder use a lot of stack memory
audio_encode_task_stack_ = (StackType_t*)malloc(4096 * 8);
xTaskCreateStatic([](void* arg) {
Application* app = (Application*)arg;
app->AudioEncodeTask();
}, "opus_encode", 4096 * 8, this, 1, audio_encode_task_stack_, &audio_encode_task_buffer_);
audio_decode_task_stack_ = (StackType_t*)malloc(4096 * 8);
xTaskCreateStatic([](void* arg) {
Application* app = (Application*)arg;
app->AudioDecodeTask();
}, "opus_decode", 4096 * 8, this, 1, audio_decode_task_stack_, &audio_decode_task_buffer_);
wifi_station_.Start();
StartCommunication();
StartDetection();
xEventGroupSetBits(event_group_, DETECTION_RUNNING);
}
void Application::SetChatState(ChatState state) {
chat_state_ = state;
switch (chat_state_) {
case kChatStateIdle:
ESP_LOGI(TAG, "Chat state: idle");
builtin_led_.TurnOff();
break;
case kChatStateConnecting:
ESP_LOGI(TAG, "Chat state: connecting");
builtin_led_.SetBlue();
builtin_led_.TurnOn();
break;
case kChatStateListening:
ESP_LOGI(TAG, "Chat state: listening");
builtin_led_.SetRed();
builtin_led_.TurnOn();
break;
case kChatStateSpeaking:
ESP_LOGI(TAG, "Chat state: speaking");
builtin_led_.SetGreen();
builtin_led_.TurnOn();
break;
}
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (ws_client_ && ws_client_->IsConnected()) {
cJSON* root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "type", "state");
cJSON_AddStringToObject(root, "state", chat_state_ == kChatStateListening ? "listening" : "speaking");
char* json = cJSON_PrintUnformatted(root);
ws_client_->Send(json);
cJSON_Delete(root);
free(json);
}
}
void Application::StartCommunication() {
afe_config_t afe_config = {
.aec_init = false,
.se_init = true,
.vad_init = false,
.wakenet_init = false,
.voice_communication_init = true,
.voice_communication_agc_init = true,
.voice_communication_agc_gain = 10,
.vad_mode = VAD_MODE_3,
.wakenet_model_name = NULL,
.wakenet_model_name_2 = NULL,
.wakenet_mode = DET_MODE_90,
.afe_mode = SR_MODE_HIGH_PERF,
.afe_perferred_core = 0,
.afe_perferred_priority = 5,
.afe_ringbuf_size = 50,
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
.afe_linear_gain = 1.0,
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
.pcm_config = {
.total_ch_num = 1,
.mic_num = 1,
.ref_num = 0,
.sample_rate = INPUT_SAMPLE_RATE
},
.debug_init = false,
.debug_hook = {{ AFE_DEBUG_HOOK_MASE_TASK_IN, NULL }, { AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL }},
.afe_ns_mode = NS_MODE_SSP,
.afe_ns_model_name = NULL,
.fixed_first_channel = true,
};
afe_communication_data_ = esp_afe_vc_v1.create_from_config(&afe_config);
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->AudioCommunicationTask();
}, "audio_communication", 4096 * 2, this, 5, NULL);
}
void Application::StartDetection() {
afe_config_t afe_config = {
.aec_init = false,
.se_init = true,
.vad_init = false,
.wakenet_init = true,
.voice_communication_init = false,
.voice_communication_agc_init = false,
.voice_communication_agc_gain = 10,
.vad_mode = VAD_MODE_3,
.wakenet_model_name = wakenet_model_,
.wakenet_model_name_2 = NULL,
.wakenet_mode = DET_MODE_90,
.afe_mode = SR_MODE_HIGH_PERF,
.afe_perferred_core = 0,
.afe_perferred_priority = 5,
.afe_ringbuf_size = 50,
.memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM,
.afe_linear_gain = 1.0,
.agc_mode = AFE_MN_PEAK_AGC_MODE_2,
.pcm_config = {
.total_ch_num = 1,
.mic_num = 1,
.ref_num = 0,
.sample_rate = INPUT_SAMPLE_RATE
},
.debug_init = false,
.debug_hook = {{ AFE_DEBUG_HOOK_MASE_TASK_IN, NULL }, { AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL }},
.afe_ns_mode = NS_MODE_SSP,
.afe_ns_model_name = NULL,
.fixed_first_channel = true,
};
afe_detection_data_ = esp_afe_sr_v1.create_from_config(&afe_config);
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->AudioFeedTask();
}, "audio_feed", 4096 * 2, this, 5, NULL);
xTaskCreate([](void* arg) {
Application* app = (Application*)arg;
app->AudioDetectionTask();
}, "audio_detection", 4096 * 2, this, 5, NULL);
}
void Application::AudioFeedTask() {
int chunk_size = esp_afe_vc_v1.get_feed_chunksize(afe_detection_data_);
int16_t buffer[chunk_size];
ESP_LOGI(TAG, "Audio feed task started, chunk size: %d", chunk_size);
while (true) {
audio_device_.Read(buffer, chunk_size);
auto event_bits = xEventGroupGetBits(event_group_);
if (event_bits & DETECTION_RUNNING) {
esp_afe_sr_v1.feed(afe_detection_data_, buffer);
} else if (event_bits & COMMUNICATION_RUNNING) {
esp_afe_vc_v1.feed(afe_communication_data_, buffer);
}
}
vTaskDelete(NULL);
}
void Application::AudioDetectionTask() {
auto chunk_size = esp_afe_sr_v1.get_fetch_chunksize(afe_detection_data_);
ESP_LOGI(TAG, "Audio detection task started, chunk size: %d", chunk_size);
while (true) {
xEventGroupWaitBits(event_group_, DETECTION_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = esp_afe_sr_v1.fetch(afe_detection_data_);
if (res == nullptr || res->ret_value == ESP_FAIL) {
ESP_LOGE(TAG, "Error in fetch");
if (res != nullptr) {
ESP_LOGI(TAG, "Error code: %d", res->ret_value);
}
continue;;
}
if (res->wakeup_state == WAKENET_DETECTED) {
ESP_LOGI(TAG, "Wakenet detected");
xEventGroupClearBits(event_group_, DETECTION_RUNNING);
SetChatState(kChatStateConnecting);
StartWebSocketClient();
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (ws_client_ && ws_client_->IsConnected()) {
// If connected, the hello message is already sent, so we can start communication
xEventGroupSetBits(event_group_, COMMUNICATION_RUNNING);
} else {
SetChatState(kChatStateIdle);
xEventGroupSetBits(event_group_, DETECTION_RUNNING);
}
}
}
}
void Application::AudioCommunicationTask() {
int chunk_size = esp_afe_vc_v1.get_fetch_chunksize(afe_communication_data_);
ESP_LOGI(TAG, "Audio communication task started, chunk size: %d", chunk_size);
while (true) {
xEventGroupWaitBits(event_group_, COMMUNICATION_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
auto res = esp_afe_vc_v1.fetch(afe_communication_data_);
if (res == nullptr || res->ret_value == ESP_FAIL) {
ESP_LOGE(TAG, "Error in fetch");
if (res != nullptr) {
ESP_LOGI(TAG, "Error code: %d", res->ret_value);
}
continue;
}
// Check if the websocket client is disconnected by the server
{
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (ws_client_ == nullptr || !ws_client_->IsConnected()) {
if (ws_client_ != nullptr) {
delete ws_client_;
ws_client_ = nullptr;
}
if (audio_device_.playing()) {
audio_device_.Break();
}
SetChatState(kChatStateIdle);
xEventGroupSetBits(event_group_, DETECTION_RUNNING);
xEventGroupClearBits(event_group_, COMMUNICATION_RUNNING);
continue;
}
}
if (chat_state_ == kChatStateListening) {
// Send audio data to server
AudioEncoderData data;
data.size = res->data_size;
data.data = malloc(data.size);
memcpy((void*)data.data, res->data, data.size);
xQueueSend(audio_encode_queue_, &data, portMAX_DELAY);
}
}
}
void Application::AudioEncodeTask() {
ESP_LOGI(TAG, "Audio encode task started");
while (true) {
AudioEncoderData data;
xQueueReceive(audio_encode_queue_, &data, portMAX_DELAY);
// Encode audio data
opus_encoder_.Encode(data.data, data.size, [this](const void* data, size_t size) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (ws_client_ && ws_client_->IsConnected()) {
ws_client_->Send((const char*)data, size, true);
}
});
free((void*)data.data);
}
}
void Application::AudioDecodeTask() {
int frame_size = DECODE_SAMPLE_RATE / 1000 * opus_duration_ms_;
while (true) {
AudioPacket* packet;
xQueueReceive(audio_decode_queue_, &packet, portMAX_DELAY);
packet->pcm.resize(frame_size);
int ret = opus_decode(opus_decoder_, packet->opus.data(), packet->opus.size(), packet->pcm.data(), frame_size, 0);
if (ret < 0) {
ESP_LOGE(TAG, "Failed to decode audio, error code: %d", ret);
free(packet);
continue;
}
if (DECODE_SAMPLE_RATE != OUTPUT_SAMPLE_RATE) {
int target_size = frame_size * OUTPUT_SAMPLE_RATE / DECODE_SAMPLE_RATE;
std::vector<int16_t> resampled(target_size);
assert(0 == silk_resampler(&resampler_state_, resampled.data(), packet->pcm.data(), frame_size));
packet->pcm = std::move(resampled);
}
audio_device_.QueueAudioPacket(packet);
}
}
void Application::StartWebSocketClient() {
if (ws_client_ != nullptr) {
delete ws_client_;
}
std::string token = "Bearer " + std::string(CONFIG_WEBSOCKET_ACCESS_TOKEN);
ws_client_ = new WebSocketClient();
ws_client_->SetHeader("Authorization", token.c_str());
ws_client_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
ws_client_->OnConnected([this]() {
ESP_LOGI(TAG, "Websocket connected");
// Send hello message to describe the client
// keys: message type, version, wakeup_model, audio_params (format, sample_rate, channels)
std::string message = "{";
message += "\"type\":\"hello\", \"version\":\"1.0\",";
message += "\"wakeup_model\":\"" + std::string(wakenet_model_) + "\",";
message += "\"audio_params\":{";
message += "\"format\":\"opus\", \"sample_rate\":" + std::to_string(INPUT_SAMPLE_RATE) + ", \"channels\":1";
message += "}}";
ws_client_->Send(message);
});
ws_client_->OnData([this](const char* data, size_t len, bool binary) {
auto packet = new AudioPacket();
if (binary) {
auto header = (AudioDataHeader*)data;
packet->type = kAudioPacketTypeData;
packet->timestamp = ntohl(header->timestamp);
auto payload_size = ntohl(header->payload_size);
packet->opus.resize(payload_size);
memcpy(packet->opus.data(), data + sizeof(AudioDataHeader), payload_size);
} else {
// Parse JSON data
auto root = cJSON_Parse(data);
auto type = cJSON_GetObjectItem(root, "type");
if (type != NULL) {
if (strcmp(type->valuestring, "tts") == 0) {
auto state = cJSON_GetObjectItem(root, "state");
if (strcmp(state->valuestring, "start") == 0) {
packet->type = kAudioPacketTypeStart;
} else if (strcmp(state->valuestring, "stop") == 0) {
packet->type = kAudioPacketTypeStop;
} else if (strcmp(state->valuestring, "sentence_end") == 0) {
packet->type = kAudioPacketTypeSentenceEnd;
} else if (strcmp(state->valuestring, "sentence_start") == 0) {
packet->type = kAudioPacketTypeSentenceStart;
packet->text = cJSON_GetObjectItem(root, "text")->valuestring;
}
}
}
cJSON_Delete(root);
}
xQueueSend(audio_decode_queue_, &packet, portMAX_DELAY);
});
ws_client_->OnError([this](int error) {
ESP_LOGE(TAG, "Websocket error: %d", error);
});
ws_client_->OnClosed([this]() {
ESP_LOGI(TAG, "Websocket closed");
});
if (!ws_client_->Connect(CONFIG_WEBSOCKET_URL)) {
ESP_LOGE(TAG, "Failed to connect to websocket server");
return;
}
}

83
main/Application.h Normal file
View File

@ -0,0 +1,83 @@
#ifndef _APPLICATION_H_
#define _APPLICATION_H_
#include "WifiStation.h"
#include "AudioDevice.h"
#include "OpusEncoder.h"
#include "WebSocketClient.h"
#include "BuiltinLed.h"
#include "opus.h"
#include "resampler_structs.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_afe_sr_models.h"
#include "esp_nsn_models.h"
#include <mutex>
#define DETECTION_RUNNING 1
#define COMMUNICATION_RUNNING 2
struct AudioEncoderData {
const void* data;
size_t size;
};
enum ChatState {
kChatStateIdle,
kChatStateConnecting,
kChatStateListening,
kChatStateSpeaking,
};
class Application {
public:
Application();
~Application();
void Start();
private:
WifiStation wifi_station_;
AudioDevice audio_device_;
BuiltinLed builtin_led_;
std::recursive_mutex mutex_;
WebSocketClient* ws_client_ = nullptr;
esp_afe_sr_data_t* afe_detection_data_ = nullptr;
esp_afe_sr_data_t* afe_communication_data_ = nullptr;
EventGroupHandle_t event_group_;
char* wakenet_model_ = NULL;
char* nsnet_model_ = NULL;
volatile ChatState chat_state_ = kChatStateIdle;
// Audio encode / decode
TaskHandle_t audio_feed_task_ = nullptr;
StaticTask_t audio_encode_task_buffer_;
StackType_t* audio_encode_task_stack_ = nullptr;
QueueHandle_t audio_encode_queue_ = nullptr;
TaskHandle_t audio_decode_task_ = nullptr;
StaticTask_t audio_decode_task_buffer_;
StackType_t* audio_decode_task_stack_ = nullptr;
QueueHandle_t audio_decode_queue_ = nullptr;
OpusEncoder opus_encoder_;
OpusDecoder* opus_decoder_ = nullptr;
int opus_duration_ms_ = 60;
silk_resampler_state_struct resampler_state_;
void SetChatState(ChatState state);
void StartDetection();
void StartCommunication();
void StartWebSocketClient();
void AudioFeedTask();
void AudioDetectionTask();
void AudioCommunicationTask();
void AudioEncodeTask();
void AudioDecodeTask();
};
#endif // _APPLICATION_H_

235
main/AudioDevice.cc Normal file
View File

@ -0,0 +1,235 @@
#include "AudioDevice.h"
#include "esp_log.h"
#include <cstring>
#define TAG "AudioDevice"
#define SPEAKING BIT0
AudioDevice::AudioDevice() {
audio_play_queue_ = xQueueCreate(100, sizeof(AudioPacket*));
}
AudioDevice::~AudioDevice() {
vQueueDelete(audio_play_queue_);
if (audio_play_task_ != nullptr) {
vTaskDelete(audio_play_task_);
}
if (rx_handle_ != nullptr) {
ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
}
if (tx_handle_ != nullptr) {
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
}
}
void AudioDevice::Start(int input_sample_rate, int output_sample_rate) {
assert(input_sample_rate == 16000);
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
if (output_sample_rate == 16000) {
CreateDuplexChannels();
} else {
CreateSimplexChannels();
}
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
xTaskCreate([](void* arg) {
auto audio_device = (AudioDevice*)arg;
audio_device->AudioPlayTask();
}, "audio_play", 4096 * 4, this, 5, &audio_play_task_);
}
void AudioDevice::CreateDuplexChannels() {
duplex_ = true;
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.auto_clear_after_cb = false,
.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_32BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_MONO,
.slot_mask = I2S_STD_SLOT_LEFT,
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
.ws_pol = false,
.bit_shift = true,
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = GPIO_NUM_5,
.ws = GPIO_NUM_4,
.dout = GPIO_NUM_6,
.din = GPIO_NUM_3,
.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_std_mode(rx_handle_, &std_cfg));
ESP_LOGI(TAG, "Duplex channels created");
}
void AudioDevice::CreateSimplexChannels() {
// Create a new channel for speaker
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.auto_clear_after_cb = false,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, nullptr));
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_32BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_MONO,
.slot_mask = I2S_STD_SLOT_LEFT,
.ws_width = I2S_DATA_BIT_WIDTH_32BIT,
.ws_pol = false,
.bit_shift = true,
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = GPIO_NUM_5,
.ws = GPIO_NUM_4,
.dout = GPIO_NUM_6,
.din = I2S_GPIO_UNUSED,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
// Create a new channel for MIC
chan_cfg.id = I2S_NUM_1;
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_));
std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_;
std_cfg.gpio_cfg.bclk = GPIO_NUM_11;
std_cfg.gpio_cfg.ws = GPIO_NUM_10;
std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
std_cfg.gpio_cfg.din = GPIO_NUM_3;
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
ESP_LOGI(TAG, "Simplex channels created");
}
void AudioDevice::Write(const int16_t* data, int samples) {
int32_t buffer[samples];
for (int i = 0; i < samples; i++) {
buffer[i] = int32_t(data[i]) << 15;
}
size_t bytes_written;
ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer, samples * sizeof(int32_t), &bytes_written, portMAX_DELAY));
}
int AudioDevice::Read(int16_t* dest, int samples) {
size_t bytes_read;
int32_t bit32_buffer_[samples];
if (i2s_channel_read(rx_handle_, bit32_buffer_, samples * sizeof(int32_t), &bytes_read, portMAX_DELAY) != ESP_OK) {
ESP_LOGE(TAG, "Read Failed!");
return 0;
}
samples = bytes_read / sizeof(int32_t);
for (int i = 0; i < samples; i++) {
int32_t value = bit32_buffer_[i] >> 12;
dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value;
}
return samples;
}
void AudioDevice::QueueAudioPacket(AudioPacket* packet) {
xQueueSend(audio_play_queue_, &packet, portMAX_DELAY);
}
void AudioDevice::AudioPlayTask() {
while (true) {
AudioPacket* packet;
xQueueReceive(audio_play_queue_, &packet, portMAX_DELAY);
switch (packet->type)
{
case kAudioPacketTypeStart:
playing_ = true;
breaked_ = false;
if (on_state_changed_) {
on_state_changed_();
}
break;
case kAudioPacketTypeStop:
playing_ = false;
if (on_state_changed_) {
on_state_changed_();
}
break;
case kAudioPacketTypeSentenceStart:
ESP_LOGI(TAG, "Playing sentence: %s", packet->text.c_str());
break;
case kAudioPacketTypeSentenceEnd:
if (breaked_) { // Clear the queue
AudioPacket* p;
while (xQueueReceive(audio_play_queue_, &p, 0) == pdTRUE) {
delete p;
}
breaked_ = false;
}
break;
case kAudioPacketTypeData:
Write(packet->pcm.data(), packet->pcm.size());
break;
default:
ESP_LOGE(TAG, "Unknown audio packet type: %d", packet->type);
}
delete packet;
}
}
void AudioDevice::OnStateChanged(std::function<void()> callback) {
on_state_changed_ = callback;
}
void AudioDevice::Break() {
breaked_ = true;
}

76
main/AudioDevice.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef _AUDIO_DEVICE_H
#define _AUDIO_DEVICE_H
#include "opus.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "driver/i2s_std.h"
#include <vector>
#include <string>
#include <functional>
enum AudioPacketType {
kAudioPacketTypeUnkonwn = 0,
kAudioPacketTypeStart,
kAudioPacketTypeStop,
kAudioPacketTypeData,
kAudioPacketTypeSentenceStart,
kAudioPacketTypeSentenceEnd
};
struct AudioPacket {
AudioPacketType type = kAudioPacketTypeUnkonwn;
std::string text;
std::vector<uint8_t> opus;
std::vector<int16_t> pcm;
uint32_t timestamp;
};
struct AudioDataHeader {
uint32_t version;
uint32_t reserved;
uint32_t timestamp;
uint32_t payload_size;
} __attribute__((packed));
class AudioDevice {
public:
AudioDevice();
~AudioDevice();
void Start(int input_sample_rate, int output_sample_rate);
int Read(int16_t* dest, int samples);
void Write(const int16_t* data, int samples);
void QueueAudioPacket(AudioPacket* packet);
void OnStateChanged(std::function<void()> callback);
void Break();
int input_sample_rate() const { return input_sample_rate_; }
int output_sample_rate() const { return output_sample_rate_; }
bool duplex() const { return duplex_; }
bool playing() const { return playing_; }
private:
bool playing_ = false;
bool breaked_ = false;
bool duplex_ = false;
int input_sample_rate_ = 0;
int output_sample_rate_ = 0;
i2s_chan_handle_t tx_handle_ = nullptr;
i2s_chan_handle_t rx_handle_ = nullptr;
QueueHandle_t audio_play_queue_ = nullptr;
TaskHandle_t audio_play_task_ = nullptr;
EventGroupHandle_t event_group_;
std::function<void()> on_state_changed_;
void CreateDuplexChannels();
void CreateSimplexChannels();
void AudioPlayTask();
};
#endif // _AUDIO_DEVICE_H

89
main/BuiltinLed.cc Normal file
View File

@ -0,0 +1,89 @@
#include "BuiltinLed.h"
#include <cstring>
#include "driver/gpio.h"
#include "esp_log.h"
#define TAG "builtin_led"
BuiltinLed::BuiltinLed() {
mutex_ = xSemaphoreCreateMutex();
Configure();
SetGreen();
}
BuiltinLed::~BuiltinLed() {
if (blink_task_ != nullptr) {
vTaskDelete(blink_task_);
}
if (led_strip_ != nullptr) {
led_strip_del(led_strip_);
}
vSemaphoreDelete(mutex_);
}
void BuiltinLed::Configure() {
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config;
bzero(&strip_config, sizeof(strip_config));
strip_config.strip_gpio_num = CONFIG_BUILTIN_LED_GPIO;
strip_config.max_leds = 1;
led_strip_rmt_config_t rmt_config;
bzero(&rmt_config, sizeof(rmt_config));
rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_));
/* Set all LED off to clear all pixels */
led_strip_clear(led_strip_);
}
void BuiltinLed::SetColor(uint8_t r, uint8_t g, uint8_t b) {
r_ = r;
g_ = g;
b_ = b;
}
void BuiltinLed::TurnOn() {
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_set_pixel(led_strip_, 0, r_, g_, b_);
led_strip_refresh(led_strip_);
xSemaphoreGive(mutex_);
}
void BuiltinLed::TurnOff() {
xSemaphoreTake(mutex_, portMAX_DELAY);
led_strip_clear(led_strip_);
xSemaphoreGive(mutex_);
}
void BuiltinLed::BlinkOnce() {
Blink(1, 100);
}
void BuiltinLed::Blink(int times, int interval_ms) {
xSemaphoreTake(mutex_, portMAX_DELAY);
struct BlinkTaskArgs {
BuiltinLed* self;
int times;
int interval_ms;
};
auto args = new BlinkTaskArgs {this, times, interval_ms};
xTaskCreate([](void* obj) {
auto args = (BlinkTaskArgs*) obj;
auto this_ = args->self;
for (int i = 0; i < args->times; i++) {
this_->TurnOn();
vTaskDelay(args->interval_ms / portTICK_PERIOD_MS);
this_->TurnOff();
vTaskDelay(args->interval_ms / portTICK_PERIOD_MS);
}
delete args;
this_->blink_task_ = nullptr;
vTaskDelete(NULL);
}, "blink", 4096, args, tskIDLE_PRIORITY, &blink_task_);
xSemaphoreGive(mutex_);
}

33
main/BuiltinLed.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef _BUILTIN_LED_H_
#define _BUILTIN_LED_H_
#include "led_strip.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
class BuiltinLed {
public:
BuiltinLed();
~BuiltinLed();
void BlinkOnce();
void Blink(int times, int interval_ms);
void TurnOn();
void TurnOff();
void SetColor(uint8_t r, uint8_t g, uint8_t b);
void SetWhite() { SetColor(128, 128, 128); }
void SetGrey() { SetColor(32, 32, 32); }
void SetRed() { SetColor(128, 0, 0); }
void SetGreen() { SetColor(0, 128, 0); }
void SetBlue() { SetColor(0, 0, 128); }
private:
SemaphoreHandle_t mutex_;
TaskHandle_t blink_task_ = nullptr;
led_strip_handle_t led_strip_ = nullptr;
uint8_t r_ = 0, g_ = 0, b_ = 0;
void Configure();
};
#endif // _BUILTIN_LED_H_

15
main/CMakeLists.txt Executable file
View File

@ -0,0 +1,15 @@
set(SOURCES "AudioDevice.cc"
"SystemInfo.cc"
"WebSocketClient.cc"
"OpusEncoder.cc"
"BuiltinLed.cc"
"Application.cc"
"WifiConfigurationAp.cc"
"main.cc"
"WifiStation.cc"
)
idf_component_register(SRCS ${SOURCES}
INCLUDE_DIRS "."
EMBED_TXTFILES "assets/wifi_configuration_ap.html"
)

22
main/Kconfig.projbuild Normal file
View File

@ -0,0 +1,22 @@
menu "Xiaozhi Assistant"
config WEBSOCKET_URL
string "Websocket URL"
default "ws://"
help
Communication with the server through websocket after wake up.
config WEBSOCKET_ACCESS_TOKEN
string "Websocket Access Token"
default ""
help
Access token for websocket communication.
config BUILTIN_LED_GPIO
int "Builtin LED GPIO"
default 48
help
GPIO number of the builtin LED.
endmenu

69
main/OpusEncoder.cc Normal file
View File

@ -0,0 +1,69 @@
#include "OpusEncoder.h"
#include "esp_err.h"
#include "esp_log.h"
#define TAG "OpusEncoder"
OpusEncoder::OpusEncoder() {
}
OpusEncoder::~OpusEncoder() {
if (out_buffer_ != nullptr) {
free(out_buffer_);
}
if (audio_enc_ != nullptr) {
opus_encoder_destroy(audio_enc_);
}
}
void OpusEncoder::Configure(int sample_rate, int channels, int duration_ms) {
if (audio_enc_ != nullptr) {
opus_encoder_destroy(audio_enc_);
audio_enc_ = nullptr;
}
if (out_buffer_ != nullptr) {
free(out_buffer_);
out_buffer_ = nullptr;
}
int error;
audio_enc_ = opus_encoder_create(sample_rate, channels, OPUS_APPLICATION_VOIP, &error);
if (audio_enc_ == nullptr) {
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", error);
return;
}
// Set DTX
opus_encoder_ctl(audio_enc_, OPUS_SET_DTX(1));
// Set complexity to 5
opus_encoder_ctl(audio_enc_, OPUS_SET_COMPLEXITY(5));
frame_size_ = sample_rate / 1000 * duration_ms;
out_size_ = sample_rate * channels * sizeof(int16_t);
out_buffer_ = (uint8_t*)malloc(out_size_);
assert(out_buffer_ != nullptr);
}
void OpusEncoder::Encode(const void* pcm, size_t pcm_len, std::function<void(const void*, size_t)> handler) {
if (audio_enc_ == nullptr) {
ESP_LOGE(TAG, "Audio encoder is not configured");
return;
}
in_buffer_.append((const char*)pcm, pcm_len);
while (in_buffer_.size() >= frame_size_ * sizeof(int16_t)) {
auto ret = opus_encode(audio_enc_, (const opus_int16*)in_buffer_.data(), frame_size_, out_buffer_, out_size_);
if (ret < 0) {
ESP_LOGE(TAG, "Failed to encode audio, error code: %ld", ret);
return;
}
if (handler != nullptr) {
handler(out_buffer_, ret);
}
in_buffer_.erase(0, frame_size_ * sizeof(int16_t));
}
}

25
main/OpusEncoder.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _OPUS_ENCODER_H_
#define _OPUS_ENCODER_H_
#include <functional>
#include <string>
#include "opus.h"
class OpusEncoder {
public:
OpusEncoder();
~OpusEncoder();
void Configure(int sample_rate, int channels, int duration_ms = 60);
void Encode(const void* pcm, size_t pcm_len, std::function<void(const void*, size_t)> handler);
bool IsBufferEmpty() const { return in_buffer_.empty(); }
private:
struct OpusEncoder* audio_enc_ = nullptr;
int frame_size_;
int out_size_;
uint8_t* out_buffer_ = nullptr;
std::string in_buffer_;
};
#endif // _OPUS_ENCODER_H_

211
main/SystemInfo.cc Normal file
View File

@ -0,0 +1,211 @@
#include "SystemInfo.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_flash.h"
#include "esp_mac.h"
#include "esp_chip_info.h"
#include "esp_system.h"
#include "esp_partition.h"
#include "esp_app_desc.h"
#include "esp_psram.h"
#define TAG "SystemInfo"
size_t SystemInfo::GetFlashSize() {
uint32_t flash_size;
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
ESP_LOGE(TAG, "Failed to get flash size");
return 0;
}
return (size_t)flash_size;
}
size_t SystemInfo::GetMinimumFreeHeapSize() {
return esp_get_minimum_free_heap_size();
}
size_t SystemInfo::GetFreeHeapSize() {
return esp_get_free_heap_size();
}
std::string SystemInfo::GetMacAddress() {
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char mac_str[18];
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return std::string(mac_str);
}
std::string SystemInfo::GetChipModelName() {
return std::string(CONFIG_IDF_TARGET);
}
std::string SystemInfo::GetJsonString() {
/*
{
"flash_size": 4194304,
"psram_size": 0,
"minimum_free_heap_size": 123456,
"mac_address": "00:00:00:00:00:00",
"chip_model_name": "esp32s3",
"chip_info": {
"model": 1,
"cores": 2,
"revision": 0,
"features": 0
},
"application": {
"name": "my-app",
"version": "1.0.0",
"compile_time": "2021-01-01T00:00:00Z"
"idf_version": "4.2-dev"
"elf_sha256": ""
},
"partition_table": {
"app": {
"label": "app",
"type": 1,
"subtype": 2,
"address": 0x10000,
"size": 0x100000
},
}
}
*/
std::string json = "{";
json += "\"flash_size\":" + std::to_string(GetFlashSize()) + ",";
json += "\"psram_size\":" + std::to_string(esp_psram_get_size()) + ",";
json += "\"minimum_free_heap_size\":" + std::to_string(GetMinimumFreeHeapSize()) + ",";
json += "\"mac_address\":\"" + GetMacAddress() + "\",";
json += "\"chip_model_name\":\"" + GetChipModelName() + "\",";
json += "\"chip_info\":{";
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
json += "\"model\":" + std::to_string(chip_info.model) + ",";
json += "\"cores\":" + std::to_string(chip_info.cores) + ",";
json += "\"revision\":" + std::to_string(chip_info.revision) + ",";
json += "\"features\":" + std::to_string(chip_info.features);
json += "},";
json += "\"application\":{";
auto app_desc = esp_app_get_description();
json += "\"name\":\"" + std::string(app_desc->project_name) + "\",";
json += "\"version\":\"" + std::string(app_desc->version) + "\",";
json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\",";
json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\",";
char sha256_str[65];
for (int i = 0; i < 32; i++) {
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
}
json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\"";
json += "},";
json += "\"partition_table\": [";
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
while (it) {
const esp_partition_t *partition = esp_partition_get(it);
json += "{";
json += "\"label\":\"" + std::string(partition->label) + "\",";
json += "\"type\":" + std::to_string(partition->type) + ",";
json += "\"subtype\":" + std::to_string(partition->subtype) + ",";
json += "\"address\":" + std::to_string(partition->address) + ",";
json += "\"size\":" + std::to_string(partition->size);
json += "},";
it = esp_partition_next(it);
}
json.pop_back(); // Remove the last comma
json += "]";
// Close the JSON object
json += "}";
return json;
}
esp_err_t SystemInfo::PrintRealTimeStats(TickType_t xTicksToWait) {
#define ARRAY_SIZE_OFFSET 5
TaskStatus_t *start_array = NULL, *end_array = NULL;
UBaseType_t start_array_size, end_array_size;
configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time;
esp_err_t ret;
uint32_t total_elapsed_time;
//Allocate array to store current task states
start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size);
if (start_array == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
//Get current task states
start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
if (start_array_size == 0) {
ret = ESP_ERR_INVALID_SIZE;
goto exit;
}
vTaskDelay(xTicksToWait);
//Allocate array to store tasks states post delay
end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size);
if (end_array == NULL) {
ret = ESP_ERR_NO_MEM;
goto exit;
}
//Get post delay task states
end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
if (end_array_size == 0) {
ret = ESP_ERR_INVALID_SIZE;
goto exit;
}
//Calculate total_elapsed_time in units of run time stats clock period.
total_elapsed_time = (end_run_time - start_run_time);
if (total_elapsed_time == 0) {
ret = ESP_ERR_INVALID_STATE;
goto exit;
}
printf("| Task | Run Time | Percentage\n");
//Match each task in start_array to those in the end_array
for (int i = 0; i < start_array_size; i++) {
int k = -1;
for (int j = 0; j < end_array_size; j++) {
if (start_array[i].xHandle == end_array[j].xHandle) {
k = j;
//Mark that task have been matched by overwriting their handles
start_array[i].xHandle = NULL;
end_array[j].xHandle = NULL;
break;
}
}
//Check if matching task found
if (k >= 0) {
uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES);
printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time);
}
}
//Print unmatched tasks
for (int i = 0; i < start_array_size; i++) {
if (start_array[i].xHandle != NULL) {
printf("| %s | Deleted\n", start_array[i].pcTaskName);
}
}
for (int i = 0; i < end_array_size; i++) {
if (end_array[i].xHandle != NULL) {
printf("| %s | Created\n", end_array[i].pcTaskName);
}
}
ret = ESP_OK;
exit: //Common return path
free(start_array);
free(end_array);
return ret;
}

20
main/SystemInfo.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef _SYSTEM_INFO_H_
#define _SYSTEM_INFO_H_
#include <string>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
class SystemInfo {
public:
static size_t GetFlashSize();
static size_t GetMinimumFreeHeapSize();
static size_t GetFreeHeapSize();
static std::string GetMacAddress();
static std::string GetChipModelName();
static std::string GetJsonString();
static esp_err_t PrintRealTimeStats(TickType_t xTicksToWait);
};
#endif // _SYSTEM_INFO_H_

130
main/WebSocketClient.cc Normal file
View File

@ -0,0 +1,130 @@
#include "WebSocketClient.h"
#include <cstring>
#include "freertos/task.h"
#include "esp_log.h"
#define TAG "WebSocket"
#define TIMEOUT_TICKS pdMS_TO_TICKS(3000)
WebSocketClient::WebSocketClient(bool auto_reconnect) {
event_group_ = xEventGroupCreate();
esp_websocket_client_config_t config = {};
config.task_prio = 1;
config.disable_auto_reconnect = !auto_reconnect;
client_ = esp_websocket_client_init(&config);
assert(client_ != NULL);
esp_websocket_register_events(client_, WEBSOCKET_EVENT_ANY, [](void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
WebSocketClient* ws = (WebSocketClient*)arg;
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id)
{
case WEBSOCKET_EVENT_BEFORE_CONNECT:
break;
case WEBSOCKET_EVENT_CONNECTED:
if (ws->on_connected_) {
ws->on_connected_();
}
xEventGroupSetBits(ws->event_group_, WEBSOCKET_CONNECTED_BIT);
break;
case WEBSOCKET_EVENT_DISCONNECTED:
xEventGroupSetBits(ws->event_group_, WEBSOCKET_DISCONNECTED_BIT);
break;
case WEBSOCKET_EVENT_DATA:
if (data->data_len != data->payload_len) {
ESP_LOGE(TAG, "Payload segmentating is not supported, data_len: %d, payload_len: %d", data->data_len, data->payload_len);
break;
}
if (data->op_code == 8) { // Websocket close
ESP_LOGI(TAG, "Websocket closed");
if (ws->on_closed_) {
ws->on_closed_();
}
} else if (data->op_code == 9) {
// Websocket ping
} else if (data->op_code == 10) {
// Websocket pong
} else if (data->op_code == 1) {
// Websocket text
if (ws->on_data_) {
ws->on_data_(data->data_ptr, data->data_len, false);
}
} else if (data->op_code == 2) {
// Websocket binary
if (ws->on_data_) {
ws->on_data_(data->data_ptr, data->data_len, true);
}
} else {
ESP_LOGI(TAG, "Unknown opcode: %d", data->op_code);
}
break;
case WEBSOCKET_EVENT_ERROR:
if (ws->on_error_) {
ws->on_error_(data->error_handle.error_type);
}
xEventGroupSetBits(ws->event_group_, WEBSOCKET_ERROR_BIT);
break;
case WEBSOCKET_EVENT_CLOSED:
break;
default:
ESP_LOGI(TAG, "Event %ld", event_id);
}
}, this);
}
WebSocketClient::~WebSocketClient() {
esp_websocket_client_close(client_, TIMEOUT_TICKS);
ESP_LOGI(TAG, "Destroying websocket client");
esp_websocket_client_destroy(client_);
}
void WebSocketClient::SetHeader(const char* key, const char* value) {
esp_websocket_client_append_header(client_, key, value);
}
bool WebSocketClient::Connect(const char* uri) {
esp_websocket_client_set_uri(client_, uri);
esp_websocket_client_start(client_);
// Wait for the connection to be established or an error
EventBits_t bits = xEventGroupWaitBits(event_group_, WEBSOCKET_CONNECTED_BIT | WEBSOCKET_ERROR_BIT, pdFALSE, pdFALSE, TIMEOUT_TICKS);
return bits & WEBSOCKET_CONNECTED_BIT;
}
void WebSocketClient::Send(const char* data, size_t len, bool binary) {
if (binary) {
esp_websocket_client_send_bin(client_, data, len, portMAX_DELAY);
} else {
esp_websocket_client_send_text(client_, data, len, portMAX_DELAY);
}
}
void WebSocketClient::Send(const std::string& data) {
Send(data.c_str(), data.size(), false);
}
void WebSocketClient::OnClosed(std::function<void()> callback) {
on_closed_ = callback;
}
void WebSocketClient::OnData(std::function<void(const char*, size_t, bool binary)> callback) {
on_data_ = callback;
}
void WebSocketClient::OnError(std::function<void(int)> callback) {
on_error_ = callback;
}
void WebSocketClient::OnConnected(std::function<void()> callback) {
on_connected_ = callback;
}
void WebSocketClient::OnDisconnected(std::function<void()> callback) {
on_disconnected_ = callback;
}
bool WebSocketClient::IsConnected() const {
return esp_websocket_client_is_connected(client_);
}

40
main/WebSocketClient.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef _WEBSOCKET_CLIENT_H_
#define _WEBSOCKET_CLIENT_H_
#include <functional>
#include <string>
#include "esp_websocket_client.h"
#include "freertos/event_groups.h"
#define WEBSOCKET_CONNECTED_BIT BIT0
#define WEBSOCKET_DISCONNECTED_BIT BIT1
#define WEBSOCKET_ERROR_BIT BIT2
class WebSocketClient {
public:
WebSocketClient(bool auto_reconnect = false);
~WebSocketClient();
void SetHeader(const char* key, const char* value);
bool IsConnected() const;
bool Connect(const char* uri);
void Send(const std::string& data);
void Send(const char* data, size_t len, bool binary = false);
void OnConnected(std::function<void()> callback);
void OnDisconnected(std::function<void()> callback);
void OnData(std::function<void(const char*, size_t, bool binary)> callback);
void OnError(std::function<void(int)> callback);
void OnClosed(std::function<void()> callback);
private:
esp_websocket_client_handle_t client_ = NULL;
EventGroupHandle_t event_group_;
std::function<void(const char*, size_t, bool binary)> on_data_;
std::function<void(int)> on_error_;
std::function<void()> on_closed_;
std::function<void()> on_connected_;
std::function<void()> on_disconnected_;
};
#endif // _WEBSOCKET_CLIENT_H_

259
main/WifiConfigurationAp.cc Normal file
View File

@ -0,0 +1,259 @@
#include "WifiConfigurationAp.h"
#include <cstdio>
#include "esp_err.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "lwip/ip_addr.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/task.h"
#define TAG "WifiConfigurationAp"
extern const char index_html_start[] asm("_binary_wifi_configuration_ap_html_start");
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
WifiConfigurationAp::WifiConfigurationAp()
{
event_group_ = xEventGroupCreate();
}
std::string WifiConfigurationAp::GetSsid()
{
// Get MAC and use it to generate a unique SSID
uint8_t mac[6];
ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP));
char ssid[32];
snprintf(ssid, sizeof(ssid), "ESP32-%02X%02X%02X", mac[3], mac[4], mac[5]);
return std::string(ssid);
}
void WifiConfigurationAp::StartAccessPoint()
{
// Get the SSID
std::string ssid = GetSsid();
// Register the WiFi event handler
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
[](void *ctx, esp_event_base_t event_base, int32_t event_id, void *event_data) {
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
ESP_LOGI(TAG, "Station connected: " MACSTR, MAC2STR(event->mac));
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
ESP_LOGI(TAG, "Station disconnected: " MACSTR, MAC2STR(event->mac));
} else if (event_id == WIFI_EVENT_STA_CONNECTED) {
xEventGroupSetBits(static_cast<WifiConfigurationAp *>(ctx)->event_group_, WIFI_CONNECTED_BIT);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupSetBits(static_cast<WifiConfigurationAp *>(ctx)->event_group_, WIFI_FAIL_BIT);
}
}, this));
// Initialize the TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
// Create the default event loop
auto netif = esp_netif_create_default_wifi_ap();
// Set the router IP address to 192.168.4.1
esp_netif_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, 192, 168, 4, 1);
IP4_ADDR(&ip_info.gw, 192, 168, 4, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
esp_netif_dhcps_stop(netif);
esp_netif_set_ip_info(netif, &ip_info);
esp_netif_dhcps_start(netif);
// Initialize the WiFi stack in Access Point mode
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Set the WiFi configuration
wifi_config_t wifi_config = {};
strcpy((char *)wifi_config.ap.ssid, ssid.c_str());
wifi_config.ap.ssid_len = ssid.length();
wifi_config.ap.max_connection = 4;
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
// Start the WiFi Access Point
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Access Point started with SSID %s", ssid.c_str());
}
void WifiConfigurationAp::StartWebServer()
{
// Start the web server
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_ERROR_CHECK(httpd_start(&server_, &config));
// Register the index.html file
httpd_uri_t index_html = {
.uri = "/",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
httpd_resp_send(req, index_html_start, strlen(index_html_start));
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &index_html));
// Register the /scan URI
httpd_uri_t scan = {
.uri = "/scan",
.method = HTTP_GET,
.handler = [](httpd_req_t *req) -> esp_err_t {
esp_wifi_scan_start(nullptr, true);
uint16_t ap_num = 0;
esp_wifi_scan_get_ap_num(&ap_num);
wifi_ap_record_t *ap_records = (wifi_ap_record_t *)malloc(ap_num * sizeof(wifi_ap_record_t));
esp_wifi_scan_get_ap_records(&ap_num, ap_records);
// Send the scan results as JSON
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr_chunk(req, "[");
for (int i = 0; i < ap_num; i++) {
ESP_LOGI(TAG, "SSID: %s, RSSI: %d, Authmode: %d",
(char *)ap_records[i].ssid, ap_records[i].rssi, ap_records[i].authmode);
char buf[128];
snprintf(buf, sizeof(buf), "{\"ssid\":\"%s\",\"rssi\":%d,\"authmode\":%d}",
(char *)ap_records[i].ssid, ap_records[i].rssi, ap_records[i].authmode);
httpd_resp_sendstr_chunk(req, buf);
if (i < ap_num - 1) {
httpd_resp_sendstr_chunk(req, ",");
}
}
httpd_resp_sendstr_chunk(req, "]");
httpd_resp_sendstr_chunk(req, NULL);
free(ap_records);
return ESP_OK;
},
.user_ctx = NULL
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &scan));
// Register the form submission
httpd_uri_t form_submit = {
.uri = "/submit",
.method = HTTP_POST,
.handler = [](httpd_req_t *req) -> esp_err_t {
char buf[128];
int ret = httpd_req_recv(req, buf, sizeof(buf));
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
buf[ret] = '\0';
ESP_LOGI(TAG, "Received form data: %s", buf);
// Parse the form data
char ssid[32], password[64];
if (sscanf(buf, "ssid=%32[^&]&password=%64s", ssid, password) != 2) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid form data");
return ESP_FAIL;
}
// Get this object from the user context
auto *this_ = static_cast<WifiConfigurationAp *>(req->user_ctx);
if (!this_->ConnectToWifi(ssid, password)) {
char error[] = "Failed to connect to WiFi";
char location[128];
snprintf(location, sizeof(location), "/?error=%s&ssid=%s", error, ssid);
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", location);
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
// Set HTML response
httpd_resp_set_status(req, "200 OK");
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, "<h1>Done!</h1>", -1);
this_->Save(ssid, password);
return ESP_OK;
},
.user_ctx = this
};
ESP_ERROR_CHECK(httpd_register_uri_handler(server_, &form_submit));
ESP_LOGI(TAG, "Web server started");
}
void WifiConfigurationAp::Start()
{
builtin_led_.SetBlue();
builtin_led_.Blink(1000, 500);
StartAccessPoint();
StartWebServer();
}
bool WifiConfigurationAp::ConnectToWifi(const std::string &ssid, const std::string &password)
{
// auto esp_netif = esp_netif_create_default_wifi_sta();
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config));
strcpy((char *)wifi_config.sta.ssid, ssid.c_str());
strcpy((char *)wifi_config.sta.password, password.c_str());
wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN;
wifi_config.sta.failure_retry_cnt = 1;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
auto ret = esp_wifi_connect();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect to WiFi: %d", ret);
return false;
}
ESP_LOGI(TAG, "Connecting to WiFi %s", ssid.c_str());
// Wait for the connection to complete for 5 seconds
EventBits_t bits = xEventGroupWaitBits(event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to WiFi %s", ssid.c_str());
return true;
} else {
ESP_LOGE(TAG, "Failed to connect to WiFi %s", ssid.c_str());
return false;
}
}
void WifiConfigurationAp::Save(const std::string &ssid, const std::string &password)
{
// Open the NVS flash
nvs_handle_t nvs_handle;
ESP_ERROR_CHECK(nvs_open("wifi", NVS_READWRITE, &nvs_handle));
// Write the SSID and password to the NVS flash
ESP_ERROR_CHECK(nvs_set_str(nvs_handle, "ssid", ssid.c_str()));
ESP_ERROR_CHECK(nvs_set_str(nvs_handle, "password", password.c_str()));
// Commit the changes
ESP_ERROR_CHECK(nvs_commit(nvs_handle));
// Close the NVS flash
nvs_close(nvs_handle);
ESP_LOGI(TAG, "WiFi configuration saved");
// Use xTaskCreate to create a new task that restarts the ESP32
xTaskCreate([](void *ctx) {
ESP_LOGI(TAG, "Restarting the ESP32 in 3 second");
vTaskDelay(pdMS_TO_TICKS(3000));
esp_restart();
}, "restart_task", 4096, NULL, 5, NULL);
}

View File

@ -0,0 +1,25 @@
#ifndef _WIFI_CONFIGURATION_AP_H_
#define _WIFI_CONFIGURATION_AP_H_
#include <string>
#include "esp_http_server.h"
#include "BuiltinLed.h"
class WifiConfigurationAp {
public:
WifiConfigurationAp();
void Start();
private:
BuiltinLed builtin_led_;
httpd_handle_t server_ = NULL;
EventGroupHandle_t event_group_;
std::string GetSsid();
void StartAccessPoint();
void StartWebServer();
bool ConnectToWifi(const std::string &ssid, const std::string &password);
void Save(const std::string &ssid, const std::string &password);
};
#endif // _WIFI_CONFIGURATION_AP_H_

100
main/WifiStation.cc Normal file
View File

@ -0,0 +1,100 @@
#include "WifiStation.h"
#include <cstring>
#include "esp_log.h"
#include "esp_wifi.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_system.h"
#define TAG "wifi"
#define WIFI_EVENT_CONNECTED BIT0
#define WIFI_EVENT_FAILED BIT1
#define MAX_RECONNECT_COUNT 5
WifiStation::WifiStation() {
// Get ssid and password from NVS
nvs_handle_t nvs_handle;
ESP_ERROR_CHECK(nvs_open("wifi", NVS_READONLY, &nvs_handle));
char ssid[32], password[64];
size_t length = sizeof(ssid);
ESP_ERROR_CHECK(nvs_get_str(nvs_handle, "ssid", ssid, &length));
length = sizeof(password);
ESP_ERROR_CHECK(nvs_get_str(nvs_handle, "password", password, &length));
nvs_close(nvs_handle);
ssid_ = std::string(ssid);
password_ = std::string(password);
// Create the event group
event_group_ = xEventGroupCreate();
// Register event handler
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
[](void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto this_ = static_cast<WifiStation*>(event_handler_arg);
if (event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
xEventGroupClearBits(this_->event_group_, WIFI_EVENT_CONNECTED);
if (this_->reconnect_count_ < MAX_RECONNECT_COUNT) {
esp_wifi_connect();
this_->reconnect_count_++;
ESP_LOGI(TAG, "Reconnecting to WiFi (attempt %d)", this_->reconnect_count_);
} else {
xEventGroupSetBits(this_->event_group_, WIFI_EVENT_FAILED);
ESP_LOGI(TAG, "Failed to connect to WiFi");
}
}
}, this));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
[](void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
auto this_ = static_cast<WifiStation*>(event_handler_arg);
auto event = static_cast<ip_event_got_ip_t*>(event_data);
char ip_address[16];
esp_ip4addr_ntoa(&event->ip_info.ip, ip_address, sizeof(ip_address));
this_->ip_address_ = ip_address;
ESP_LOGI(TAG, "Got IP: %s", this_->ip_address_.c_str());
xEventGroupSetBits(this_->event_group_, WIFI_EVENT_CONNECTED);
}, this));
}
void WifiStation::Start() {
// Initialize the TCP/IP stack
ESP_ERROR_CHECK(esp_netif_init());
// Create the default event loop
esp_netif_create_default_wifi_sta();
// Initialize the WiFi stack in station mode
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_LOGI(TAG, "Connecting to WiFi ssid=%s password=%s", ssid_.c_str(), password_.c_str());
wifi_config_t wifi_config;
bzero(&wifi_config, sizeof(wifi_config));
strcpy((char *)wifi_config.sta.ssid, ssid_.c_str());
strcpy((char *)wifi_config.sta.password, password_.c_str());
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
// Start the WiFi stack
ESP_ERROR_CHECK(esp_wifi_start());
// Wait for the WiFi stack to start
auto bits = xEventGroupWaitBits(event_group_, WIFI_EVENT_CONNECTED | WIFI_EVENT_FAILED, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_EVENT_FAILED) {
ESP_LOGE(TAG, "WifiStation start failed");
} else {
ESP_LOGI(TAG, "WifiStation started");
}
}
bool WifiStation::IsConnected() {
return xEventGroupGetBits(event_group_) & WIFI_EVENT_CONNECTED;
}

23
main/WifiStation.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef _WIFI_STATION_H_
#define _WIFI_STATION_H_
#include <string>
#include "esp_event.h"
class WifiStation {
public:
WifiStation();
void Start();
bool IsConnected();
std::string ssid() { return ssid_; }
std::string ip_address() { return ip_address_; }
private:
EventGroupHandle_t event_group_;
std::string ssid_;
std::string password_;
std::string ip_address_;
int reconnect_count_ = 0;
};
#endif // _WIFI_STATION_H_

View File

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html>
<head>
<title>WiFi Configuration</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style type="text/css">
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
h1 {
text-align: center;
margin-top: 50px;
}
form {
width: 300px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 5px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 3px;
}
input[type="submit"] {
background-color: #007bff;
color: #fff;
border: none;
border-radius: 3px;
padding: 10px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
input[type="submit"]:disabled {
background-color: #ccc;
cursor: not-allowed;
}
#ap_list {
margin-top: 20px;
border-top: 1px solid #ccc;
padding-top: 10px;
}
#ap_list a {
display: block;
margin-top: 5px;
color: #007bff;
text-decoration: none;
}
#ap_list a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<h1>WiFi Configuration</h1>
<form action="/submit" method="post" onsubmit="button.disabled = true;">
<p class="error" style="color: red; text-align: center;" id="error">
</p>
<p>
<label for="ssid">SSID:</label>
<input type="text" id="ssid" name="ssid" required>
</p>
<p>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</p>
<p style="text-align: center;">
<input type="submit" value="Submit" id="button">
</p>
<p id="ap_list">
</p>
</form>
<script type="text/javascript">
const button = document.getElementById('button');
const error = document.getElementById('error');
const ssid = document.getElementById('ssid');
const params = new URLSearchParams(window.location.search);
if (params.has('error')) {
error.textContent = params.get('error');
}
if (params.has('ssid')) {
ssid.value = params.get('ssid');
}
// Load AP list from /scan
function loadAPList() {
if (button.disabled) {
return;
}
fetch('/scan')
.then(response => response.json())
.then(data => {
const apList = document.getElementById('ap_list');
apList.innerHTML = '<p>Select an AP from the list below: </p>';
data.forEach(ap => {
// Create a link for each AP
const link = document.createElement('a');
link.href = '#';
link.textContent = ap.ssid + ' (' + ap.rssi + ' dBm)';
if (ap.authmode === 0) {
link.textContent += ' 🌐';
} else {
link.textContent += ' 🔒';
}
link.addEventListener('click', () => {
ssid.value = ap.ssid;
});
apList.appendChild(link);
});
setTimeout(loadAPList, 5000);
})
.catch(error => {
console.error('Error:', error);
});
}
loadAPList();
</script>
</body>
</html>

20
main/idf_component.yml Normal file
View File

@ -0,0 +1,20 @@
## IDF Component Manager Manifest File
dependencies:
78/esp-opus: "*"
espressif/esp_websocket_client: "^1.2.3"
espressif/led_strip: "*"
espressif/esp-sr: "^1.9.0"
## Required IDF version
idf:
version: ">=4.1.0"
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true

66
main/main.cc Executable file
View File

@ -0,0 +1,66 @@
#include <cstdio>
#include "esp_log.h"
#include "esp_err.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "WifiConfigurationAp.h"
#include "Application.h"
#include "SystemInfo.h"
#define TAG "main"
#define STATS_TICKS pdMS_TO_TICKS(1000)
extern "C" void app_main(void)
{
// Initialize the default event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Configure GPIO1 as INPUT, reset NVS flash if the button is pressed
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = 1ULL << 1;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
if (gpio_get_level(GPIO_NUM_1) == 0) {
ESP_LOGI(TAG, "Button is pressed, reset NVS flash");
nvs_flash_erase();
}
// Initialize NVS flash for WiFi configuration
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Get the WiFi configuration
nvs_handle_t nvs_handle;
ret = nvs_open("wifi", NVS_READONLY, &nvs_handle);
// If the WiFi configuration is not found, launch the WiFi configuration AP
if (ret != ESP_OK) {
auto app = new WifiConfigurationAp();
app->Start();
return;
}
nvs_close(nvs_handle);
// Otherwise, launch the application
auto app = new Application();
app->Start();
// Dump CPU usage every 1 second
while (true) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
SystemInfo::PrintRealTimeStats(STATS_TICKS);
int free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
ESP_LOGI(TAG, "Free heap size: %u minimal internal: %u", SystemInfo::GetFreeHeapSize(), free_sram);
}
}

60
main/resampler_structs.h Normal file
View File

@ -0,0 +1,60 @@
/***********************************************************************
Copyright (c) 2006-2011, Skype Limited. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***********************************************************************/
#ifndef SILK_RESAMPLER_STRUCTS_H
#define SILK_RESAMPLER_STRUCTS_H
#ifdef __cplusplus
extern "C" {
#endif
#define SILK_RESAMPLER_MAX_FIR_ORDER 36
#define SILK_RESAMPLER_MAX_IIR_ORDER 6
typedef struct _silk_resampler_state_struct{
opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */
union{
opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ];
opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ];
} sFIR;
opus_int16 delayBuf[ 48 ];
opus_int resampler_function;
opus_int batchSize;
opus_int32 invRatio_Q16;
opus_int FIR_Order;
opus_int FIR_Fracs;
opus_int Fs_in_kHz;
opus_int Fs_out_kHz;
opus_int inputDelay;
const opus_int16 *Coefs;
} silk_resampler_state_struct;
#ifdef __cplusplus
}
#endif
#endif /* SILK_RESAMPLER_STRUCTS_H */

25
main/silk_resampler.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _SILK_RESAMPLER_H_
#define _SILK_RESAMPLER_H_
/*!
* Initialize/reset the resampler state for a given pair of input/output sampling rates
*/
extern "C" opus_int silk_resampler_init(
silk_resampler_state_struct *S, /* I/O Resampler state */
opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */
opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */
opus_int forEnc /* I If 1: encoder; if 0: decoder */
);
/*!
* Resampler: convert from one sampling rate to another
*/
extern "C" opus_int silk_resampler(
silk_resampler_state_struct *S, /* I/O Resampler state */
opus_int16 out[], /* O Output signal */
const opus_int16 in[], /* I Input signal */
opus_int32 inLen /* I Number of input samples */
);
#endif // _SILK_RESAMPLER_H_

9
partitions.csv Normal file
View File

@ -0,0 +1,9 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 2M,
ota_0, app, ota_0, , 2M,
ota_1, app, ota_1, , 2M,
model, data, spiffs, , 1M,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 factory, app, factory, 0x10000, 2M,
7 ota_0, app, ota_0, , 2M,
8 ota_1, app, ota_1, , 2M,
9 model, data, spiffs, , 1M,

34
sdkconfig.defaults Normal file
View File

@ -0,0 +1,34 @@
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
CONFIG_ESP32S3_DATA_CACHE_64KB=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_FLASHMODE_QIO=y
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=16384
CONFIG_SPIRAM_MEMTEST=n
CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
CONFIG_HTTPD_MAX_URI_LEN=2048
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_MODEL_IN_SPIFFS=y
CONFIG_USE_WAKENET=y
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
CONFIG_USE_MULTINET=n
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y