ESPHome 2025.5.0
Loading...
Searching...
No Matches
i2s_audio_microphone.cpp
Go to the documentation of this file.
2
3#ifdef USE_ESP32
4
5#ifdef USE_I2S_LEGACY
6#include <driver/i2s.h>
7#else
8#include <driver/i2s_std.h>
9#include <driver/i2s_pdm.h>
10#endif
11
12#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14
16
17namespace esphome {
18namespace i2s_audio {
19
20static const UBaseType_t MAX_LISTENERS = 16;
21
22static const uint32_t READ_DURATION_MS = 16;
23
24static const size_t TASK_STACK_SIZE = 4096;
25static const ssize_t TASK_PRIORITY = 23;
26
27// Use an exponential moving average to correct a DC offset with weight factor 1/1000
28static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
29
30static const char *const TAG = "i2s_audio.microphone";
31
32enum MicrophoneEventGroupBits : uint32_t {
33 COMMAND_STOP = (1 << 0), // stops the microphone task
34 TASK_STARTING = (1 << 10),
35 TASK_RUNNING = (1 << 11),
36 TASK_STOPPING = (1 << 12),
37 TASK_STOPPED = (1 << 13),
38
39 ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
40};
41
43 ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone...");
44#ifdef USE_I2S_LEGACY
45#if SOC_I2S_SUPPORTS_ADC
46 if (this->adc_) {
47 if (this->parent_->get_port() != I2S_NUM_0) {
48 ESP_LOGE(TAG, "Internal ADC only works on I2S0!");
49 this->mark_failed();
50 return;
51 }
52 } else
53#endif
54#endif
55 {
56 if (this->pdm_) {
57 if (this->parent_->get_port() != I2S_NUM_0) {
58 ESP_LOGE(TAG, "PDM only works on I2S0!");
59 this->mark_failed();
60 return;
61 }
62 }
63 }
64
65 this->active_listeners_semaphore_ = xSemaphoreCreateCounting(MAX_LISTENERS, MAX_LISTENERS);
66 if (this->active_listeners_semaphore_ == nullptr) {
67 ESP_LOGE(TAG, "Failed to create semaphore");
68 this->mark_failed();
69 return;
70 }
71
72 this->event_group_ = xEventGroupCreate();
73 if (this->event_group_ == nullptr) {
74 ESP_LOGE(TAG, "Failed to create event group");
75 this->mark_failed();
76 return;
77 }
78
80}
81
83 uint8_t channel_count = 1;
84#ifdef USE_I2S_LEGACY
85 uint8_t bits_per_sample = this->bits_per_sample_;
86
87 if (this->channel_ == I2S_CHANNEL_FMT_RIGHT_LEFT) {
88 channel_count = 2;
89 }
90#else
91 uint8_t bits_per_sample = 16;
92 if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
93 bits_per_sample = this->slot_bit_width_;
94 }
95
96 if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
97 channel_count = 2;
98 }
99#endif
100
101#ifdef USE_ESP32_VARIANT_ESP32
102 // ESP32 reads audio aligned to a multiple of 2 bytes. For example, if configured for 24 bits per sample, then it will
103 // produce 32 bits per sample, where the actual data is in the most significant bits. Other ESP32 variants produce 24
104 // bits per sample in this situation.
105 if (bits_per_sample < 16) {
106 bits_per_sample = 16;
107 } else if ((bits_per_sample > 16) && (bits_per_sample <= 32)) {
108 bits_per_sample = 32;
109 }
110#endif
111
112 if (this->pdm_) {
113 bits_per_sample = 16; // PDM mics are always 16 bits per sample
114 }
115
116 this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
117}
118
120 if (this->is_failed())
121 return;
122
123 xSemaphoreTake(this->active_listeners_semaphore_, 0);
124}
125
127 if (!this->parent_->try_lock()) {
128 return false; // Waiting for another i2s to return lock
129 }
130 esp_err_t err;
131
132#ifdef USE_I2S_LEGACY
133 i2s_driver_config_t config = {
134 .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
135 .sample_rate = this->sample_rate_,
136 .bits_per_sample = this->bits_per_sample_,
137 .channel_format = this->channel_,
138 .communication_format = I2S_COMM_FORMAT_STAND_I2S,
139 .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
140 .dma_buf_count = 4,
141 .dma_buf_len = 240, // Must be divisible by 3 to support 24 bits per sample on old driver and newer variants
142 .use_apll = this->use_apll_,
143 .tx_desc_auto_clear = false,
144 .fixed_mclk = 0,
145 .mclk_multiple = this->mclk_multiple_,
146 .bits_per_chan = this->bits_per_channel_,
147 };
148
149#if SOC_I2S_SUPPORTS_ADC
150 if (this->adc_) {
151 config.mode = (i2s_mode_t) (config.mode | I2S_MODE_ADC_BUILT_IN);
152 err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
153 if (err != ESP_OK) {
154 ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
155 this->status_set_error();
156 return false;
157 }
158
159 err = i2s_set_adc_mode(ADC_UNIT_1, this->adc_channel_);
160 if (err != ESP_OK) {
161 ESP_LOGW(TAG, "Error setting ADC mode: %s", esp_err_to_name(err));
162 this->status_set_error();
163 return false;
164 }
165 err = i2s_adc_enable(this->parent_->get_port());
166 if (err != ESP_OK) {
167 ESP_LOGW(TAG, "Error enabling ADC: %s", esp_err_to_name(err));
168 this->status_set_error();
169 return false;
170 }
171
172 } else
173#endif
174 {
175 if (this->pdm_)
176 config.mode = (i2s_mode_t) (config.mode | I2S_MODE_PDM);
177
178 err = i2s_driver_install(this->parent_->get_port(), &config, 0, nullptr);
179 if (err != ESP_OK) {
180 ESP_LOGW(TAG, "Error installing I2S driver: %s", esp_err_to_name(err));
181 this->status_set_error();
182 return false;
183 }
184
185 i2s_pin_config_t pin_config = this->parent_->get_pin_config();
186 pin_config.data_in_num = this->din_pin_;
187
188 err = i2s_set_pin(this->parent_->get_port(), &pin_config);
189 if (err != ESP_OK) {
190 ESP_LOGW(TAG, "Error setting I2S pin: %s", esp_err_to_name(err));
191 this->status_set_error();
192 return false;
193 }
194 }
195#else
196 i2s_chan_config_t chan_cfg = {
197 .id = this->parent_->get_port(),
198 .role = this->i2s_role_,
199 .dma_desc_num = 4,
200 .dma_frame_num = 256,
201 .auto_clear = false,
202 };
203 /* Allocate a new RX channel and get the handle of this channel */
204 err = i2s_new_channel(&chan_cfg, NULL, &this->rx_handle_);
205 if (err != ESP_OK) {
206 ESP_LOGW(TAG, "Error creating new I2S channel: %s", esp_err_to_name(err));
207 this->status_set_error();
208 return false;
209 }
210
211 i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
212#ifdef I2S_CLK_SRC_APLL
213 if (this->use_apll_) {
214 clk_src = I2S_CLK_SRC_APLL;
215 }
216#endif
217 i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
218#if SOC_I2S_SUPPORTS_PDM_RX
219 if (this->pdm_) {
220 i2s_pdm_rx_clk_config_t clk_cfg = {
221 .sample_rate_hz = this->sample_rate_,
222 .clk_src = clk_src,
223 .mclk_multiple = this->mclk_multiple_,
224 .dn_sample_mode = I2S_PDM_DSR_8S,
225 };
226
227 i2s_pdm_rx_slot_config_t slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, this->slot_mode_);
228 switch (this->std_slot_mask_) {
229 case I2S_STD_SLOT_LEFT:
230 slot_cfg.slot_mask = I2S_PDM_SLOT_LEFT;
231 break;
232 case I2S_STD_SLOT_RIGHT:
233 slot_cfg.slot_mask = I2S_PDM_SLOT_RIGHT;
234 break;
235 case I2S_STD_SLOT_BOTH:
236 slot_cfg.slot_mask = I2S_PDM_SLOT_BOTH;
237 break;
238 }
239
240 /* Init the channel into PDM RX mode */
241 i2s_pdm_rx_config_t pdm_rx_cfg = {
242 .clk_cfg = clk_cfg,
243 .slot_cfg = slot_cfg,
244 .gpio_cfg =
245 {
246 .clk = pin_config.ws,
247 .din = this->din_pin_,
248 .invert_flags =
249 {
250 .clk_inv = pin_config.invert_flags.ws_inv,
251 },
252 },
253 };
254 err = i2s_channel_init_pdm_rx_mode(this->rx_handle_, &pdm_rx_cfg);
255 } else
256#endif
257 {
258 i2s_std_clk_config_t clk_cfg = {
259 .sample_rate_hz = this->sample_rate_,
260 .clk_src = clk_src,
261 .mclk_multiple = this->mclk_multiple_,
262 };
263 i2s_std_slot_config_t std_slot_cfg =
264 I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) this->slot_bit_width_, this->slot_mode_);
265 std_slot_cfg.slot_bit_width = this->slot_bit_width_;
266 std_slot_cfg.slot_mask = this->std_slot_mask_;
267
268 pin_config.din = this->din_pin_;
269
270 i2s_std_config_t std_cfg = {
271 .clk_cfg = clk_cfg,
272 .slot_cfg = std_slot_cfg,
273 .gpio_cfg = pin_config,
274 };
275 /* Initialize the channel */
276 err = i2s_channel_init_std_mode(this->rx_handle_, &std_cfg);
277 }
278 if (err != ESP_OK) {
279 ESP_LOGW(TAG, "Error initializing I2S channel: %s", esp_err_to_name(err));
280 this->status_set_error();
281 return false;
282 }
283
284 /* Before reading data, start the RX channel first */
285 i2s_channel_enable(this->rx_handle_);
286 if (err != ESP_OK) {
287 ESP_LOGW(TAG, "Error enabling I2S Microphone: %s", esp_err_to_name(err));
288 this->status_set_error();
289 return false;
290 }
291#endif
292
293 this->status_clear_error();
294 this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
295 return true;
296}
297
299 if (this->state_ == microphone::STATE_STOPPED || this->is_failed())
300 return;
301
302 xSemaphoreGive(this->active_listeners_semaphore_);
303}
304
306 esp_err_t err;
307#ifdef USE_I2S_LEGACY
308#if SOC_I2S_SUPPORTS_ADC
309 if (this->adc_) {
310 err = i2s_adc_disable(this->parent_->get_port());
311 if (err != ESP_OK) {
312 ESP_LOGW(TAG, "Error disabling ADC: %s", esp_err_to_name(err));
313 this->status_set_error();
314 return;
315 }
316 }
317#endif
318 err = i2s_stop(this->parent_->get_port());
319 if (err != ESP_OK) {
320 ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
321 this->status_set_error();
322 return;
323 }
324 err = i2s_driver_uninstall(this->parent_->get_port());
325 if (err != ESP_OK) {
326 ESP_LOGW(TAG, "Error uninstalling I2S driver: %s", esp_err_to_name(err));
327 this->status_set_error();
328 return;
329 }
330#else
331 /* Have to stop the channel before deleting it */
332 err = i2s_channel_disable(this->rx_handle_);
333 if (err != ESP_OK) {
334 ESP_LOGW(TAG, "Error stopping I2S microphone: %s", esp_err_to_name(err));
335 this->status_set_error();
336 return;
337 }
338 /* If the handle is not needed any more, delete it to release the channel resources */
339 err = i2s_del_channel(this->rx_handle_);
340 if (err != ESP_OK) {
341 ESP_LOGW(TAG, "Error deleting I2S channel: %s", esp_err_to_name(err));
342 this->status_set_error();
343 return;
344 }
345#endif
346 this->parent_->unlock();
347 this->status_clear_error();
348}
349
351 I2SAudioMicrophone *this_microphone = (I2SAudioMicrophone *) params;
352
353 xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
354
355 uint8_t start_counter = 0;
356 bool started = this_microphone->start_driver_();
357 while (!started && start_counter < 10) {
358 // Attempt to load the driver again in 100 ms. Doesn't slow down main loop since its in a task.
359 vTaskDelay(pdMS_TO_TICKS(100));
360 ++start_counter;
361 started = this_microphone->start_driver_();
362 }
363
364 if (started) {
365 xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
366 const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
367 std::vector<uint8_t> samples;
368 samples.reserve(bytes_to_read);
369
370 while (!(xEventGroupGetBits(this_microphone->event_group_) & COMMAND_STOP)) {
371 if (this_microphone->data_callbacks_.size() > 0) {
372 samples.resize(bytes_to_read);
373 size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
374 samples.resize(bytes_read);
375 if (this_microphone->correct_dc_offset_) {
376 this_microphone->fix_dc_offset_(samples);
377 }
378 this_microphone->data_callbacks_.call(samples);
379 } else {
380 vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
381 }
382 }
383 }
384
385 xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
386 this_microphone->stop_driver_();
387
388 xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
389 while (true) {
390 // Continuously delay until the loop method deletes the task
391 vTaskDelay(pdMS_TO_TICKS(10));
392 }
393}
394
395void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
396 const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
397 const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
398
399 if (total_samples == 0) {
400 return;
401 }
402
403 int64_t offset_accumulator = 0;
404 for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
405 const uint32_t byte_index = sample_index * bytes_per_sample;
406 int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
407 offset_accumulator += sample;
408 sample -= this->dc_offset_;
409 audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
410 }
411
412 const int32_t new_offset = offset_accumulator / total_samples;
413 this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
414 (DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
415 DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
416}
417
418size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
419 size_t bytes_read = 0;
420#ifdef USE_I2S_LEGACY
421 esp_err_t err = i2s_read(this->parent_->get_port(), buf, len, &bytes_read, ticks_to_wait);
422#else
423 // i2s_channel_read expects the timeout value in ms, not ticks
424 esp_err_t err = i2s_channel_read(this->rx_handle_, buf, len, &bytes_read, pdTICKS_TO_MS(ticks_to_wait));
425#endif
426 if ((err != ESP_OK) && ((err != ESP_ERR_TIMEOUT) || (ticks_to_wait != 0))) {
427 // Ignore ESP_ERR_TIMEOUT if ticks_to_wait = 0, as it will read the data on the next call
428 ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err));
429 this->status_set_warning();
430 return 0;
431 }
432 if ((bytes_read == 0) && (ticks_to_wait > 0)) {
433 this->status_set_warning();
434 return 0;
435 }
436 this->status_clear_warning();
437#if defined(USE_ESP32_VARIANT_ESP32) and not defined(USE_I2S_LEGACY)
438 // For ESP32 8/16 bit standard mono mode samples need to be switched.
439 if (this->slot_mode_ == I2S_SLOT_MODE_MONO && this->slot_bit_width_ <= 16 && !this->pdm_) {
440 size_t samples_read = bytes_read / sizeof(int16_t);
441 for (int i = 0; i < samples_read; i += 2) {
442 int16_t tmp = buf[i];
443 buf[i] = buf[i + 1];
444 buf[i + 1] = tmp;
445 }
446 }
447#endif
448 return bytes_read;
449}
450
452 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
453
454 if (event_group_bits & MicrophoneEventGroupBits::TASK_STARTING) {
455 ESP_LOGD(TAG, "Task has started, attempting to setup I2S audio driver");
456 xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STARTING);
457 }
458
459 if (event_group_bits & MicrophoneEventGroupBits::TASK_RUNNING) {
460 ESP_LOGD(TAG, "Task is running and reading data");
461
462 xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
464 }
465
466 if (event_group_bits & MicrophoneEventGroupBits::TASK_STOPPING) {
467 ESP_LOGD(TAG, "Task is stopping, attempting to unload the I2S audio driver");
468 xEventGroupClearBits(this->event_group_, MicrophoneEventGroupBits::TASK_STOPPING);
469 }
470
471 if ((event_group_bits & MicrophoneEventGroupBits::TASK_STOPPED)) {
472 ESP_LOGD(TAG, "Task is finished, freeing resources");
473 vTaskDelete(this->task_handle_);
474 this->task_handle_ = nullptr;
475 xEventGroupClearBits(this->event_group_, ALL_BITS);
477 }
478
479 if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) < MAX_LISTENERS) &&
482 }
483 if ((uxSemaphoreGetCount(this->active_listeners_semaphore_) == MAX_LISTENERS) &&
486 }
487
488 switch (this->state_) {
490 if ((this->task_handle_ == nullptr) && !this->status_has_error()) {
491 xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
492 &this->task_handle_);
493
494 if (this->task_handle_ == nullptr) {
495 this->status_momentary_error("Task failed to start, attempting again in 1 second", 1000);
496 }
497 }
498 break;
500 break;
502 xEventGroupSetBits(this->event_group_, MicrophoneEventGroupBits::COMMAND_STOP);
503 break;
505 break;
506 }
507}
508
509} // namespace i2s_audio
510} // namespace esphome
511
512#endif // USE_ESP32
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_momentary_error(const std::string &name, uint32_t length=5000)
bool status_has_error() const
void status_set_warning(const char *message="unspecified")
void status_set_error(const char *message="unspecified")
void status_clear_warning()
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:73
size_t samples_to_bytes(uint32_t samples) const
Converts samples to bytes.
Definition audio.h:58
uint32_t bytes_to_samples(size_t bytes) const
Convert bytes to samples.
Definition audio.h:48
i2s_std_slot_mask_t std_slot_mask_
Definition i2s_audio.h:45
i2s_slot_bit_width_t slot_bit_width_
Definition i2s_audio.h:46
i2s_bits_per_chan_t bits_per_channel_
Definition i2s_audio.h:41
i2s_mclk_multiple_t mclk_multiple_
Definition i2s_audio.h:50
i2s_bits_per_sample_t bits_per_sample_
Definition i2s_audio.h:40
void fix_dc_offset_(std::vector< uint8_t > &data)
Attempts to correct a microphone DC offset; e.g., a microphones silent level is offset from 0.
size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait)
void configure_stream_settings_()
Sets the Microphone audio_stream_info_ member variable to the configured I2S settings.
audio::AudioStreamInfo audio_stream_info_
Definition microphone.h:41
CallbackManager< void(const std::vector< uint8_t > &)> data_callbacks_
Definition microphone.h:43
__int64 ssize_t
Definition httplib.h:175
int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_sample)
Unpacks a quantized audio sample into a Q31 fixed-point number.
Definition audio.h:142
void pack_q31_as_audio_sample(int32_t sample, uint8_t *data, size_t bytes_per_sample)
Packs a Q31 fixed-point number as an audio sample with the specified number of bytes per sample.
Definition audio.h:168
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:301