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_TYPE = "adc_type"
|
||||
CONF_CORRECT_DC_OFFSET = "correct_dc_offset"
|
||||
CONF_PDM = "pdm"
|
||||
|
||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||
@ -88,10 +89,13 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
||||
default_sample_rate=16000,
|
||||
default_channel=CONF_RIGHT,
|
||||
default_bits_per_sample="32bit",
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_CORRECT_DC_OFFSET, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
@ -140,3 +144,5 @@ async def to_code(config):
|
||||
else:
|
||||
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
|
||||
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/log.h"
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
|
||||
namespace esphome {
|
||||
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 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";
|
||||
|
||||
enum MicrophoneEventGroupBits : uint32_t {
|
||||
@ -70,21 +75,11 @@ void I2SAudioMicrophone::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->configure_stream_settings_();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
void I2SAudioMicrophone::configure_stream_settings_() {
|
||||
uint8_t channel_count = 1;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
uint8_t bits_per_sample = this->bits_per_sample_;
|
||||
@ -93,10 +88,10 @@ bool I2SAudioMicrophone::start_driver_() {
|
||||
channel_count = 2;
|
||||
}
|
||||
#else
|
||||
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_AUTO) {
|
||||
this->slot_bit_width_ = I2S_SLOT_BIT_WIDTH_16BIT;
|
||||
uint8_t bits_per_sample = 16;
|
||||
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) {
|
||||
channel_count = 2;
|
||||
@ -114,6 +109,26 @@ bool I2SAudioMicrophone::start_driver_() {
|
||||
}
|
||||
#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
|
||||
i2s_driver_config_t config = {
|
||||
.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();
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
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 = {
|
||||
.sample_rate_hz = this->sample_rate_,
|
||||
.clk_src = clk_src,
|
||||
@ -277,10 +290,8 @@ bool I2SAudioMicrophone::start_driver_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
|
||||
|
||||
this->status_clear_error();
|
||||
|
||||
this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -361,9 +372,12 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
||||
samples.resize(bytes_to_read);
|
||||
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
|
||||
samples.resize(bytes_read);
|
||||
if (this_microphone->correct_dc_offset_) {
|
||||
this_microphone->fix_dc_offset_(samples);
|
||||
}
|
||||
this_microphone->data_callbacks_.call(samples);
|
||||
} 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);
|
||||
while (true) {
|
||||
// Continuously delay until the loop method delete the task
|
||||
delay(10);
|
||||
// Continuously delay until the loop method deletes the task
|
||||
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 bytes_read = 0;
|
||||
#ifdef USE_I2S_LEGACY
|
||||
|
@ -7,8 +7,10 @@
|
||||
#include "esphome/components/microphone/microphone.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2s_audio {
|
||||
@ -20,6 +22,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
void stop() override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void set_correct_dc_offset(bool correct_dc_offset) { this->correct_dc_offset_ = correct_dc_offset; }
|
||||
|
||||
#ifdef USE_I2S_LEGACY
|
||||
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
|
||||
#else
|
||||
@ -41,8 +46,16 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
bool start_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);
|
||||
|
||||
/// @brief Sets the Microphone ``audio_stream_info_`` member variable to the configured I2S settings.
|
||||
void configure_stream_settings_();
|
||||
|
||||
static void mic_task(void *params);
|
||||
|
||||
SemaphoreHandle_t active_listeners_semaphore_{nullptr};
|
||||
@ -61,6 +74,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
i2s_chan_handle_t rx_handle_;
|
||||
#endif
|
||||
bool pdm_{false};
|
||||
|
||||
bool correct_dc_offset_;
|
||||
int32_t dc_offset_{0};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
@ -10,6 +10,7 @@ microphone:
|
||||
adc_type: external
|
||||
pdm: false
|
||||
mclk_multiple: 384
|
||||
correct_dc_offset: true
|
||||
on_data:
|
||||
- if:
|
||||
condition:
|
||||
|
Loading…
x
Reference in New Issue
Block a user