From 79abd773a2c402b65f0637b3d18267f383e51074 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 May 2023 15:50:44 +1200 Subject: [PATCH] Allow i2s microphone bits per sample to be configured (#4884) --- .../i2s_audio/microphone/__init__.py | 14 +++++++- .../microphone/i2s_audio_microphone.cpp | 35 ++++++++++++++++--- .../microphone/i2s_audio_microphone.h | 4 ++- esphome/components/microphone/__init__.py | 2 +- esphome/components/microphone/automation.h | 4 +-- esphome/components/microphone/microphone.h | 4 +-- .../voice_assistant/voice_assistant.cpp | 5 +-- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index 089e796ae0..07f5158188 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -20,6 +20,7 @@ DEPENDENCIES = ["i2s_audio"] CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" +CONF_BITS_PER_SAMPLE = "bits_per_sample" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component @@ -30,10 +31,17 @@ CHANNELS = { "left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, "right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, } +i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") +BITS_PER_SAMPLE = { + 16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, + 32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, +} INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] +_validate_bits = cv.float_with_unit("bits", "bit") + def validate_esp32_variant(config): variant = esp32.get_esp32_variant() @@ -54,6 +62,9 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(BITS_PER_SAMPLE) + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -93,6 +104,7 @@ async def to_code(config): cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_pdm(config[CONF_PDM])) - cg.add(var.set_channel(CHANNELS[config[CONF_CHANNEL]])) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) await microphone.register_microphone(var, config) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 9452762e94..9c661c3ac2 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -16,7 +16,13 @@ static const char *const TAG = "i2s_audio.microphone"; void I2SAudioMicrophone::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); - this->buffer_.resize(BUFFER_SIZE); + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_ = allocator.allocate(BUFFER_SIZE); + if (this->buffer_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate buffer!"); + this->mark_failed(); + return; + } #if SOC_I2S_SUPPORTS_ADC if (this->adc_) { @@ -48,7 +54,7 @@ void I2SAudioMicrophone::start_() { i2s_driver_config_t config = { .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = 16000, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .bits_per_sample = this->bits_per_sample_, .channel_format = this->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, @@ -107,16 +113,35 @@ void I2SAudioMicrophone::stop_() { void I2SAudioMicrophone::read_() { size_t bytes_read = 0; esp_err_t err = - i2s_read(this->parent_->get_port(), this->buffer_.data(), BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); + i2s_read(this->parent_->get_port(), this->buffer_, BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); if (err != ESP_OK) { ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); this->status_set_warning(); return; } - this->status_clear_warning(); - this->data_callbacks_.call(this->buffer_); + std::vector samples; + size_t samples_read = 0; + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + samples_read = bytes_read / sizeof(int16_t); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + samples_read = bytes_read / sizeof(int32_t); + } else { + ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); + return; + } + samples.resize(samples_read); + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + memcpy(samples.data(), this->buffer_, bytes_read); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + for (size_t i = 0; i < samples_read; i++) { + int32_t temp = reinterpret_cast(this->buffer_)[i] >> 14; + samples[i] = clamp(temp, INT16_MIN, INT16_MAX); + } + } + + this->data_callbacks_.call(samples); } void I2SAudioMicrophone::loop() { diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index acc7d2b45a..0cb87d42fd 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -29,6 +29,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } + void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } protected: void start_(); @@ -41,8 +42,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub bool adc_{false}; #endif bool pdm_{false}; - std::vector buffer_; + uint8_t *buffer_; i2s_channel_fmt_t channel_; + i2s_bits_per_sample_t bits_per_sample_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index ff1f7aa963..d99500bbed 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -41,7 +41,7 @@ async def setup_microphone_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( trigger, - [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")], + [(cg.std_vector.template(cg.int16).operator("ref").operator("const"), "x")], conf, ) diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5f404b8d74..5313f07f72 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -16,10 +16,10 @@ template class StopCaptureAction : public Action, public void play(Ts... x) override { this->parent_->stop(); } }; -class DataTrigger : public Trigger &> { +class DataTrigger : public Trigger &> { public: explicit DataTrigger(Microphone *mic) { - mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); + mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); } }; diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index b725f66ad7..5b16a67c00 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -17,7 +17,7 @@ class Microphone { public: virtual void start() = 0; virtual void stop() = 0; - void add_data_callback(std::function &)> &&data_callback) { + void add_data_callback(std::function &)> &&data_callback) { this->data_callbacks_.add(std::move(data_callback)); } @@ -26,7 +26,7 @@ class Microphone { protected: State state_{STATE_STOPPED}; - CallbackManager &)> data_callbacks_{}; + CallbackManager &)> data_callbacks_{}; }; } // namespace microphone diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index fb96d484d4..4245578711 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -58,11 +58,12 @@ void VoiceAssistant::setup() { } #endif - this->mic_->add_data_callback([this](const std::vector &data) { + this->mic_->add_data_callback([this](const std::vector &data) { if (!this->running_) { return; } - this->socket_->sendto(data.data(), data.size(), 0, (struct sockaddr *) &this->dest_addr_, sizeof(this->dest_addr_)); + this->socket_->sendto(data.data(), data.size() * sizeof(int16_t), 0, (struct sockaddr *) &this->dest_addr_, + sizeof(this->dest_addr_)); }); }