fit the memory usage for esp32c3

This commit is contained in:
Terrence
2024-11-29 11:06:05 +08:00
parent ff28586c35
commit 436ff2b906
35 changed files with 754 additions and 360 deletions

View File

@@ -9,34 +9,48 @@
#define TAG "AudioCodec"
AudioCodec::AudioCodec() {
audio_event_group_ = xEventGroupCreate();
}
AudioCodec::~AudioCodec() {
if (audio_input_task_ != nullptr) {
vTaskDelete(audio_input_task_);
}
if (audio_output_task_ != nullptr) {
vTaskDelete(audio_output_task_);
}
if (audio_event_group_ != nullptr) {
vEventGroupDelete(audio_event_group_);
}
}
void AudioCodec::OnInputData(std::function<void(std::vector<int16_t>&& data)> callback) {
on_input_data_ = callback;
void AudioCodec::OnInputReady(std::function<bool()> callback) {
on_input_ready_ = callback;
}
void AudioCodec::OnOutputReady(std::function<bool()> callback) {
on_output_ready_ = callback;
}
void AudioCodec::OutputData(std::vector<int16_t>& data) {
std::lock_guard<std::mutex> lock(audio_output_queue_mutex_);
audio_output_queue_.emplace_back(std::move(data));
audio_output_queue_cv_.notify_one();
Write(data.data(), data.size());
}
bool AudioCodec::InputData(std::vector<int16_t>& data) {
int duration = 30;
int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_;
data.resize(input_frame_size);
int samples = Read(data.data(), data.size());
if (samples > 0) {
return true;
}
return false;
}
IRAM_ATTR bool AudioCodec::on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
auto audio_codec = (AudioCodec*)user_ctx;
xEventGroupSetBits(audio_codec->audio_event_group_, AUDIO_EVENT_OUTPUT_DONE);
if (audio_codec->output_enabled_ && audio_codec->on_output_ready_) {
return audio_codec->on_output_ready_();
}
return false;
}
IRAM_ATTR bool AudioCodec::on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
auto audio_codec = (AudioCodec*)user_ctx;
if (audio_codec->input_enabled_ && audio_codec->on_input_ready_) {
return audio_codec->on_input_ready_();
}
return false;
}
@@ -44,85 +58,20 @@ void AudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
// 注册音频输出回调
i2s_event_callbacks_t callbacks = {};
callbacks.on_sent = on_sent;
i2s_channel_register_event_callback(tx_handle_, &callbacks, this);
// 注册音频数据回调
i2s_event_callbacks_t rx_callbacks = {};
rx_callbacks.on_recv = on_recv;
i2s_channel_register_event_callback(rx_handle_, &rx_callbacks, this);
i2s_event_callbacks_t tx_callbacks = {};
tx_callbacks.on_sent = on_sent;
i2s_channel_register_event_callback(tx_handle_, &tx_callbacks, this);
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
EnableInput(true);
EnableOutput(true);
// 创建音频输入任务
if (audio_input_task_ == nullptr) {
xTaskCreate([](void* arg) {
auto audio_device = (AudioCodec*)arg;
audio_device->InputTask();
}, "audio_input", 4096 * 2, this, 3, &audio_input_task_);
}
// 创建音频输出任务
if (audio_output_task_ == nullptr) {
xTaskCreate([](void* arg) {
auto audio_device = (AudioCodec*)arg;
audio_device->OutputTask();
}, "audio_output", 4096 * 2, this, 3, &audio_output_task_);
}
}
void AudioCodec::InputTask() {
int duration = 30;
int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_;
while (true) {
std::vector<int16_t> input_data(input_frame_size);
int samples = Read(input_data.data(), input_data.size());
if (samples > 0) {
if (on_input_data_) {
on_input_data_(std::move(input_data));
}
}
}
}
void AudioCodec::OutputTask() {
while (true) {
std::unique_lock<std::mutex> lock(audio_output_queue_mutex_);
if (!audio_output_queue_cv_.wait_for(lock, std::chrono::seconds(30), [this]() {
return !audio_output_queue_.empty();
})) {
// If timeout, disable output
EnableOutput(false);
continue;
}
auto data = std::move(audio_output_queue_.front());
audio_output_queue_.pop_front();
lock.unlock();
if (!output_enabled_) {
EnableOutput(true);
}
xEventGroupClearBits(audio_event_group_, AUDIO_EVENT_OUTPUT_DONE);
Write(data.data(), data.size());
audio_output_queue_cv_.notify_all();
}
}
void AudioCodec::WaitForOutputDone() {
// Wait for the output queue to be empty and the output is done
std::unique_lock<std::mutex> lock(audio_output_queue_mutex_);
audio_output_queue_cv_.wait(lock, [this]() {
return audio_output_queue_.empty();
});
lock.unlock();
xEventGroupWaitBits(audio_event_group_, AUDIO_EVENT_OUTPUT_DONE, pdFALSE, pdFALSE, portMAX_DELAY);
}
void AudioCodec::ClearOutputQueue() {
std::lock_guard<std::mutex> lock(audio_output_queue_mutex_);
audio_output_queue_.clear();
}
void AudioCodec::SetOutputVolume(int volume) {

View File

@@ -2,21 +2,15 @@
#define _AUDIO_CODEC_H
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <driver/i2s_std.h>
#include <vector>
#include <string>
#include <functional>
#include <list>
#include <mutex>
#include <condition_variable>
#include "board.h"
#define AUDIO_EVENT_OUTPUT_DONE (1 << 0)
class AudioCodec {
public:
AudioCodec();
@@ -27,10 +21,10 @@ public:
virtual void EnableOutput(bool enable);
void Start();
void OnInputData(std::function<void(std::vector<int16_t>&& data)> callback);
void OutputData(std::vector<int16_t>& data);
void WaitForOutputDone();
void ClearOutputQueue();
bool InputData(std::vector<int16_t>& data);
void OnOutputReady(std::function<bool()> callback);
void OnInputReady(std::function<bool()> callback);
inline bool duplex() const { return duplex_; }
inline bool input_reference() const { return input_reference_; }
@@ -41,18 +35,12 @@ public:
inline int output_volume() const { return output_volume_; }
private:
TaskHandle_t audio_input_task_ = nullptr;
TaskHandle_t audio_output_task_ = nullptr;
std::function<void(std::vector<int16_t>&& data)> on_input_data_;
std::list<std::vector<int16_t>> audio_output_queue_;
std::mutex audio_output_queue_mutex_;
std::condition_variable audio_output_queue_cv_;
EventGroupHandle_t audio_event_group_ = nullptr;
std::function<bool()> on_input_ready_;
std::function<bool()> on_output_ready_;
IRAM_ATTR static bool on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
IRAM_ATTR static bool on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx);
void InputTask();
void OutputTask();
protected:
i2s_chan_handle_t tx_handle_ = nullptr;
i2s_chan_handle_t rx_handle_ = nullptr;

View File

@@ -28,7 +28,7 @@ BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int
// Output
audio_codec_i2c_cfg_t i2c_cfg = {
.port = I2C_NUM_1,
.port = (i2c_port_t)1,
.addr = es8311_addr,
.bus_handle = i2c_master_handle,
};
@@ -96,8 +96,8 @@ void BoxAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.dma_desc_num = 2,
.dma_frame_num = 240 * 3,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,

View File

@@ -0,0 +1,186 @@
#include "es8311_audio_codec.h"
#include <esp_log.h>
static const char TAG[] = "Es8311AudioCodec";
Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr) {
duplex_ = true; // 是否双工
input_reference_ = false; // 是否使用参考输入,实现回声消除
input_channels_ = 1; // 输入通道数
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
CreateDuplexChannels(mclk, bclk, ws, dout, din);
// Do initialize of related interface: data_if, ctrl_if and gpio_if
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = rx_handle_,
.tx_handle = tx_handle_,
};
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
assert(data_if_ != NULL);
// Output
audio_codec_i2c_cfg_t i2c_cfg = {
.port = i2c_port,
.addr = es8311_addr,
.bus_handle = i2c_master_handle,
};
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
assert(ctrl_if_ != NULL);
gpio_if_ = audio_codec_new_gpio();
assert(gpio_if_ != NULL);
es8311_codec_cfg_t es8311_cfg = {};
es8311_cfg.ctrl_if = ctrl_if_;
es8311_cfg.gpio_if = gpio_if_;
es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
es8311_cfg.pa_pin = pa_pin;
es8311_cfg.use_mclk = true;
es8311_cfg.hw_gain.pa_voltage = 5.0;
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
codec_if_ = es8311_codec_new(&es8311_cfg);
assert(codec_if_ != NULL);
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
.codec_if = codec_if_,
.data_if = data_if_,
};
output_dev_ = esp_codec_dev_new(&dev_cfg);
assert(output_dev_ != NULL);
dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
input_dev_ = esp_codec_dev_new(&dev_cfg);
assert(input_dev_ != NULL);
ESP_LOGI(TAG, "Es8311AudioCodec initialized");
}
Es8311AudioCodec::~Es8311AudioCodec() {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
audio_codec_delete_codec_if(codec_if_);
audio_codec_delete_ctrl_if(ctrl_if_);
audio_codec_delete_gpio_if(gpio_if_);
audio_codec_delete_data_if(data_if_);
}
void Es8311AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
assert(input_sample_rate_ == output_sample_rate_);
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 2,
.dma_frame_num = 240 * 3,
.auto_clear_after_cb = true,
.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_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true,
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = mclk,
.bclk = bclk,
.ws = ws,
.dout = dout,
.din = din,
.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 Es8311AudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void Es8311AudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void Es8311AudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioCodec::EnableOutput(enable);
}
int Es8311AudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int Es8311AudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
}
return samples;
}

View File

@@ -0,0 +1,36 @@
#ifndef _ES8311_AUDIO_CODEC_H
#define _ES8311_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
class Es8311AudioCodec : public AudioCodec {
private:
const audio_codec_data_if_t* data_if_ = nullptr;
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
const audio_codec_if_t* codec_if_ = nullptr;
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
public:
Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr);
virtual ~Es8311AudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
};
#endif // _ES8311_AUDIO_CODEC_H

View File

@@ -23,8 +23,8 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.dma_desc_num = 2,
.dma_frame_num = 240 * 3,
.auto_clear_after_cb = false,
.auto_clear_before_cb = false,
.intr_priority = 0,
@@ -75,7 +75,7 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n
// Create a new channel for speaker
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.id = (i2s_port_t)0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
@@ -120,7 +120,7 @@ NoAudioCodec::NoAudioCodec(int input_sample_rate, int output_sample_rate, gpio_n
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
// Create a new channel for MIC
chan_cfg.id = I2S_NUM_1;
chan_cfg.id = (i2s_port_t)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 = mic_sck;