mirror of
https://github.com/esphome/esphome.git
synced 2025-07-29 06:36:45 +00:00
[i2s_audio] Correct a microphone with a DC offset signal (#8751)
This commit is contained in:
parent
11dcaf7383
commit
71e88fe9b2
@ -30,6 +30,7 @@ DEPENDENCIES = ["i2s_audio"]
|
|||||||
|
|
||||||
CONF_ADC_PIN = "adc_pin"
|
CONF_ADC_PIN = "adc_pin"
|
||||||
CONF_ADC_TYPE = "adc_type"
|
CONF_ADC_TYPE = "adc_type"
|
||||||
|
CONF_CORRECT_DC_OFFSET = "correct_dc_offset"
|
||||||
CONF_PDM = "pdm"
|
CONF_PDM = "pdm"
|
||||||
|
|
||||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||||
@ -88,10 +89,13 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
|||||||
default_sample_rate=16000,
|
default_sample_rate=16000,
|
||||||
default_channel=CONF_RIGHT,
|
default_channel=CONF_RIGHT,
|
||||||
default_bits_per_sample="32bit",
|
default_bits_per_sample="32bit",
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_CORRECT_DC_OFFSET, default=False): cv.boolean,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.typed_schema(
|
cv.typed_schema(
|
||||||
{
|
{
|
||||||
@ -140,3 +144,5 @@ async def to_code(config):
|
|||||||
else:
|
else:
|
||||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||||
|
|
||||||
|
cg.add(var.set_correct_dc_offset(config[CONF_CORRECT_DC_OFFSET]))
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "esphome/components/audio/audio.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace i2s_audio {
|
namespace i2s_audio {
|
||||||
|
|
||||||
@ -22,6 +24,9 @@ static const uint32_t READ_DURATION_MS = 16;
|
|||||||
static const size_t TASK_STACK_SIZE = 4096;
|
static const size_t TASK_STACK_SIZE = 4096;
|
||||||
static const ssize_t TASK_PRIORITY = 23;
|
static const ssize_t TASK_PRIORITY = 23;
|
||||||
|
|
||||||
|
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
|
||||||
|
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
|
||||||
|
|
||||||
static const char *const TAG = "i2s_audio.microphone";
|
static const char *const TAG = "i2s_audio.microphone";
|
||||||
|
|
||||||
enum MicrophoneEventGroupBits : uint32_t {
|
enum MicrophoneEventGroupBits : uint32_t {
|
||||||
@ -70,21 +75,11 @@ void I2SAudioMicrophone::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->configure_stream_settings_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void I2SAudioMicrophone::start() {
|
void I2SAudioMicrophone::configure_stream_settings_() {
|
||||||
if (this->is_failed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
xSemaphoreTake(this->active_listeners_semaphore_, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool I2SAudioMicrophone::start_driver_() {
|
|
||||||
if (!this->parent_->try_lock()) {
|
|
||||||
return false; // Waiting for another i2s to return lock
|
|
||||||
}
|
|
||||||
esp_err_t err;
|
|
||||||
|
|
||||||
uint8_t channel_count = 1;
|
uint8_t channel_count = 1;
|
||||||
#ifdef USE_I2S_LEGACY
|
#ifdef USE_I2S_LEGACY
|
||||||
uint8_t bits_per_sample = this->bits_per_sample_;
|
uint8_t bits_per_sample = this->bits_per_sample_;
|
||||||
@ -93,10 +88,10 @@ bool I2SAudioMicrophone::start_driver_() {
|
|||||||
channel_count = 2;
|
channel_count = 2;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_AUTO) {
|
uint8_t bits_per_sample = 16;
|
||||||
this->slot_bit_width_ = I2S_SLOT_BIT_WIDTH_16BIT;
|
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||||
|
bits_per_sample = this->slot_bit_width_;
|
||||||
}
|
}
|
||||||
uint8_t bits_per_sample = this->slot_bit_width_;
|
|
||||||
|
|
||||||
if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
|
if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
|
||||||
channel_count = 2;
|
channel_count = 2;
|
||||||
@ -114,6 +109,26 @@ bool I2SAudioMicrophone::start_driver_() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (this->pdm_) {
|
||||||
|
bits_per_sample = 16; // PDM mics are always 16 bits per sample
|
||||||
|
}
|
||||||
|
|
||||||
|
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void I2SAudioMicrophone::start() {
|
||||||
|
if (this->is_failed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
xSemaphoreTake(this->active_listeners_semaphore_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool I2SAudioMicrophone::start_driver_() {
|
||||||
|
if (!this->parent_->try_lock()) {
|
||||||
|
return false; // Waiting for another i2s to return lock
|
||||||
|
}
|
||||||
|
esp_err_t err;
|
||||||
|
|
||||||
#ifdef USE_I2S_LEGACY
|
#ifdef USE_I2S_LEGACY
|
||||||
i2s_driver_config_t config = {
|
i2s_driver_config_t config = {
|
||||||
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
|
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
|
||||||
@ -202,8 +217,6 @@ bool I2SAudioMicrophone::start_driver_() {
|
|||||||
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
|
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
|
||||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||||
if (this->pdm_) {
|
if (this->pdm_) {
|
||||||
bits_per_sample = 16; // PDM mics are always 16 bits per sample with the IDF 5 driver
|
|
||||||
|
|
||||||
i2s_pdm_rx_clk_config_t clk_cfg = {
|
i2s_pdm_rx_clk_config_t clk_cfg = {
|
||||||
.sample_rate_hz = this->sample_rate_,
|
.sample_rate_hz = this->sample_rate_,
|
||||||
.clk_src = clk_src,
|
.clk_src = clk_src,
|
||||||
@ -277,10 +290,8 @@ bool I2SAudioMicrophone::start_driver_() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
|
|
||||||
|
|
||||||
this->status_clear_error();
|
this->status_clear_error();
|
||||||
|
this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,9 +372,12 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
|||||||
samples.resize(bytes_to_read);
|
samples.resize(bytes_to_read);
|
||||||
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
|
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
|
||||||
samples.resize(bytes_read);
|
samples.resize(bytes_read);
|
||||||
|
if (this_microphone->correct_dc_offset_) {
|
||||||
|
this_microphone->fix_dc_offset_(samples);
|
||||||
|
}
|
||||||
this_microphone->data_callbacks_.call(samples);
|
this_microphone->data_callbacks_.call(samples);
|
||||||
} else {
|
} else {
|
||||||
delay(READ_DURATION_MS);
|
vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,11 +387,34 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
|||||||
|
|
||||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
|
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
|
||||||
while (true) {
|
while (true) {
|
||||||
// Continuously delay until the loop method delete the task
|
// Continuously delay until the loop method deletes the task
|
||||||
delay(10);
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
||||||
|
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
||||||
|
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
||||||
|
|
||||||
|
if (total_samples == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t offset_accumulator = 0;
|
||||||
|
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
||||||
|
const uint32_t byte_index = sample_index * bytes_per_sample;
|
||||||
|
int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
||||||
|
offset_accumulator += sample;
|
||||||
|
sample -= this->dc_offset_;
|
||||||
|
audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32_t new_offset = offset_accumulator / total_samples;
|
||||||
|
this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
|
||||||
|
(DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
|
||||||
|
DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
|
||||||
|
}
|
||||||
|
|
||||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||||
size_t bytes_read = 0;
|
size_t bytes_read = 0;
|
||||||
#ifdef USE_I2S_LEGACY
|
#ifdef USE_I2S_LEGACY
|
||||||
|
@ -7,8 +7,10 @@
|
|||||||
#include "esphome/components/microphone/microphone.h"
|
#include "esphome/components/microphone/microphone.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/event_groups.h>
|
#include <freertos/event_groups.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace i2s_audio {
|
namespace i2s_audio {
|
||||||
@ -20,6 +22,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
void set_correct_dc_offset(bool correct_dc_offset) { this->correct_dc_offset_ = correct_dc_offset; }
|
||||||
|
|
||||||
#ifdef USE_I2S_LEGACY
|
#ifdef USE_I2S_LEGACY
|
||||||
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
||||||
#else
|
#else
|
||||||
@ -41,8 +46,16 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||||||
bool start_driver_();
|
bool start_driver_();
|
||||||
void stop_driver_();
|
void stop_driver_();
|
||||||
|
|
||||||
|
/// @brief Attempts to correct a microphone DC offset; e.g., a microphones silent level is offset from 0. Applies a
|
||||||
|
/// correction offset that is updated using an exponential moving average for all samples away from 0.
|
||||||
|
/// @param data
|
||||||
|
void fix_dc_offset_(std::vector<uint8_t> &data);
|
||||||
|
|
||||||
size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait);
|
size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait);
|
||||||
|
|
||||||
|
/// @brief Sets the Microphone ``audio_stream_info_`` member variable to the configured I2S settings.
|
||||||
|
void configure_stream_settings_();
|
||||||
|
|
||||||
static void mic_task(void *params);
|
static void mic_task(void *params);
|
||||||
|
|
||||||
SemaphoreHandle_t active_listeners_semaphore_{nullptr};
|
SemaphoreHandle_t active_listeners_semaphore_{nullptr};
|
||||||
@ -61,6 +74,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
|||||||
i2s_chan_handle_t rx_handle_;
|
i2s_chan_handle_t rx_handle_;
|
||||||
#endif
|
#endif
|
||||||
bool pdm_{false};
|
bool pdm_{false};
|
||||||
|
|
||||||
|
bool correct_dc_offset_;
|
||||||
|
int32_t dc_offset_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace i2s_audio
|
} // namespace i2s_audio
|
||||||
|
@ -10,6 +10,7 @@ microphone:
|
|||||||
adc_type: external
|
adc_type: external
|
||||||
pdm: false
|
pdm: false
|
||||||
mclk_multiple: 384
|
mclk_multiple: 384
|
||||||
|
correct_dc_offset: true
|
||||||
on_data:
|
on_data:
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user