ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_audio_microphone.cpp
Go to the documentation of this file.
2
3#ifdef USE_USB_AUDIO
4
5#include "esphome/core/log.h"
6
7#include <algorithm>
8#include <cinttypes>
9
10extern "C" {
11#include "esp_err.h"
12}
13
15
16ESPHOME_LOG_TAG(TAG, "usb_audio.mic");
17
19 this->frame_.reserve(this->audio_stream_info_.ms_to_bytes(2 * READ_TIMEOUT_MS));
21 false)) {
22 this->mark_failed(LOG_STR("Failed to create microphone task"));
23 return;
24 }
25 // start() and stop() wake the loop.
26 this->disable_loop();
27}
28
30 ESP_LOGCONFIG(TAG,
31 "USB Microphone:\n"
32 " Sample rate: %" PRIu32 " Hz\n"
33 " Bits per sample: %u\n"
34 " Channels: %u\n"
35 " Buffer size: %" PRIu32,
36 this->audio_stream_info_.get_sample_rate(), this->audio_stream_info_.get_bits_per_sample(),
37 this->audio_stream_info_.get_channels(), this->buffer_size_);
38}
39
41 const uint8_t commands = this->commands_.exchange(0);
42 if (commands & COMMAND_STOP) {
43 // Also when a failed start left the microphone STOPPED, so the next session's retries start from the minimum.
46 }
47 if ((commands & COMMAND_STOP) &&
49 this->parent_->suspend(Endpoint::MICROPHONE);
50 // The task notices within one read timeout; the stop finishes below once it is idle.
51 this->running_ = false;
53 }
54 if (this->state_ == microphone::STATE_STOPPING && this->task_idle_) {
56 }
57 // A retry can race the last stop(), so only start while someone is still listening.
58 if ((commands & COMMAND_START) && this->listeners_ > 0) {
59 if (this->state_ == microphone::STATE_STOPPED) {
61 // Answers through on_stream_state() now or once the device connects.
62 this->parent_->resume(Endpoint::MICROPHONE);
63 } else if (this->state_ == microphone::STATE_STOPPING) {
64 // Retry once the task is idle.
65 this->commands_.fetch_or(COMMAND_START);
66 }
67 }
69 this->disable_loop();
70 }
71}
72
73void USBAudioMicrophone::request_(uint8_t set, uint8_t clear) {
74 this->commands_.fetch_and(static_cast<uint8_t>(~clear));
75 this->commands_.fetch_or(set);
77}
78
79// Every MicrophoneSource forwards its own start() and stop(), so capture runs while any of them is listening, like
80// i2s_audio's listener count.
82 if (this->is_failed()) {
83 return;
84 }
85 uint8_t listeners = this->listeners_.load();
86 do {
87 if (listeners >= MAX_LISTENERS) {
88 return;
89 }
90 } while (!this->listeners_.compare_exchange_weak(listeners, listeners + 1));
91 if (listeners == 0) {
93 }
94}
95
97 uint8_t listeners = this->listeners_.load();
98 do {
99 if (listeners == 0) {
100 return;
101 }
102 } while (!this->listeners_.compare_exchange_weak(listeners, listeners - 1));
103 if (listeners == 1) {
105 }
106}
107
109 switch (event) {
111 if (this->state_ == microphone::STATE_STARTING) {
112 this->task_idle_ = false;
113 this->running_ = true;
114 xTaskNotifyGive(this->mic_task_.get_handle());
117 }
118 break;
120 // The hub restarts the stream on reconnect, since the microphone still wants it, so keep waiting in STARTING;
121 // this also covers a disconnect while the stream was still starting.
122 if (this->state_ == microphone::STATE_RUNNING) {
123 ESP_LOGW(TAG, "USB device disconnected, waiting for it to return");
124 this->running_ = false;
126 }
127 break;
129 // Only a start or resume fails, so the task is idle.
130 if (this->state_ == microphone::STATE_STARTING) {
132 // A MicrophoneSource calls start() once and then waits for data, and start() only asks for the stream when
133 // the first listener arrives, so retry here for the listeners still held.
134 if (this->listeners_ > 0) {
135 this->set_timeout(RETRY_START_ID, this->retry_delay_ms_, [this]() {
136 if (this->listeners_ > 0) {
137 this->request_(COMMAND_START, 0);
138 }
139 });
140 this->retry_delay_ms_ = std::min(this->retry_delay_ms_ * 2, RETRY_MAX_MS);
141 }
142 }
143 break;
144 }
145}
146
147void USBAudioMicrophone::mic_task(void *param) { static_cast<USBAudioMicrophone *>(param)->mic_task_loop_(); }
148
150 while (true) {
151 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
152
153 while (this->running_) {
154 size_t bytes_read = 0;
155 this->frame_.resize(this->frame_.capacity());
156 esp_err_t err =
157 this->parent_->read_microphone(this->frame_.data(), this->frame_.size(), &bytes_read, READ_TIMEOUT_MS);
158 if (!this->running_) {
159 break;
160 }
161
162 if (err == ESP_OK && bytes_read > 0) {
163 this->frame_.resize(bytes_read);
164 this->data_callbacks_.call(this->frame_);
165 } else if (err == ESP_ERR_TIMEOUT) {
166 // The driver already waited READ_TIMEOUT_MS for data; read again.
167 } else if (err == ESP_ERR_INVALID_STATE) {
168 // The stream is starting or suspended.
169 vTaskDelay(pdMS_TO_TICKS(READ_TIMEOUT_MS));
170 } else if (err != ESP_OK) {
171 ESP_LOGW(TAG, "Read error: %s", esp_err_to_name(err));
172 vTaskDelay(pdMS_TO_TICKS(READ_TIMEOUT_MS));
173 }
174 }
175
176 this->task_idle_ = true;
177 }
178}
179
180} // namespace esphome::usb_audio
181
182#endif
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void disable_loop()
Disable this component's loop.
TaskHandle_t get_handle() const
Get the FreeRTOS task handle.
Definition static_task.h:31
bool create(TaskFunction_t fn, const char *name, uint32_t stack_size, void *param, UBaseType_t priority, bool use_psram, BaseType_t core_id=tskNO_AFFINITY)
Allocate the TCB (always internal RAM) and the stack, then create the task.
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:75
uint32_t get_sample_rate() const
Definition audio.h:30
audio::AudioStreamInfo audio_stream_info_
Definition microphone.h:45
CallbackManager< void(const std::vector< uint8_t > &)> data_callbacks_
Definition microphone.h:47
void on_stream_state(StreamEvent event) override
static constexpr uint32_t MIC_TASK_STACK_SIZE
void request_(uint8_t set, uint8_t clear)
static constexpr UBaseType_t MIC_TASK_PRIORITY
ESPHOME_LOG_TAG(TAG, "usb_audio.mic")
StreamEvent
What the hub reports to a platform about its stream.
Definition usb_audio.h:29