ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_audio.cpp
Go to the documentation of this file.
1#include "usb_audio.h"
2
3#ifdef USE_USB_AUDIO
4
5#include <algorithm>
6#include <cinttypes>
7#include <cmath>
8
10#include "esphome/core/hal.h"
11#include "esphome/core/log.h"
12
13extern "C" {
14#include "esp_err.h"
15}
16
17namespace esphome::usb_audio {
18
19ESPHOME_LOG_TAG(TAG, "usb_audio");
20
21// The driver task completes and resubmits the speaker's transfers, with only CONFIG_UAC_NUM_ISOC_URBS of them
22// queued on the bus, so it runs above the network stack like the I2S speaker task; each wakeup is short.
23static constexpr UBaseType_t UAC_TASK_PRIORITY = 19;
24static constexpr uint32_t UAC_TASK_STACK_SIZE = 4096;
25// Below the driver's task, so its transfers are not held up by control requests.
26static constexpr UBaseType_t WORKER_TASK_PRIORITY = 5;
27static constexpr uint32_t WORKER_TASK_STACK_SIZE = 4096;
28// The worker retries volume and mute up to this many times, this far apart.
29static constexpr uint8_t MAX_CONTROL_ATTEMPTS = 5;
30static constexpr uint32_t CONTROL_RETRY_MS = 100;
31// A failed close is retried up to this many times, this far apart, before the handle is given up.
32static constexpr uint8_t MAX_CLOSE_ATTEMPTS = 5;
33static constexpr uint32_t CLOSE_RETRY_MS = 500;
34
35static const LogString *endpoint_name(Endpoint endpoint) {
36 return endpoint == Endpoint::SPEAKER ? LOG_STR("Speaker") : LOG_STR("Microphone");
37}
38
39static uint8_t discrete_rate_count(const uac_host_dev_alt_param_t &alt) {
40 return std::min<uint8_t>(alt.sample_freq_type, UAC_FREQ_NUM_MAX);
41}
42
43static bool alt_supports_rate(const uac_host_dev_alt_param_t &alt, uint32_t sample_rate) {
44 if (alt.sample_freq_type == 0) {
45 return sample_rate >= alt.sample_freq_lower && sample_rate <= alt.sample_freq_upper;
46 }
47 const uint8_t count = discrete_rate_count(alt);
48 return std::find(alt.sample_freq, alt.sample_freq + count, sample_rate) != alt.sample_freq + count;
49}
50
52 this->event_queue_ = xQueueCreateStatic(EVENT_QUEUE_LENGTH, sizeof(USBAudioEvent), this->event_queue_storage_.data(),
53 &this->event_queue_struct_);
54 this->op_queue_ =
55 xQueueCreateStatic(OP_QUEUE_LENGTH, sizeof(Op), this->op_queue_storage_.data(), &this->op_queue_struct_);
56
57 uac_host_driver_config_t uac_cfg = {
58 .create_background_task = true,
59 .task_priority = UAC_TASK_PRIORITY,
60 .stack_size = UAC_TASK_STACK_SIZE,
61 .core_id = 0,
63 .callback_arg = this,
64 };
65
66 esp_err_t err = uac_host_install(&uac_cfg);
67 if (err != ESP_OK) {
68 ESP_LOGE(TAG, "uac_host_install failed: %s", esp_err_to_name(err));
69 this->mark_failed();
70 return;
71 }
72 this->uac_installed_ = true;
73
74 // Opening, starting, suspending and closing streams and setting volume are control transfers that can block for
75 // seconds, so they run here instead of on the main loop.
76 if (!this->worker_.create(&USBAudioComponent::worker_task, "usb_audio", WORKER_TASK_STACK_SIZE, this,
77 WORKER_TASK_PRIORITY, false)) {
78 this->mark_failed(LOG_STR("Failed to create worker task"));
79 return;
80 }
81 // The USB callbacks and the worker wake the loop when there is work.
82 this->disable_loop();
83}
84
87
88 if (const uint32_t errors = this->transfer_errors_.exchange(0); errors != 0) {
89 ESP_LOGW(TAG, "%" PRIu32 " USB transfer errors", errors);
90 }
91
92 // Stay awake while a lost device still waits to be closed.
93 if (std::any_of(this->endpoints_.begin(), this->endpoints_.end(),
94 [](const EndpointState &ep) { return ep.closing != nullptr; })) {
95 return;
96 }
97 this->disable_loop();
98}
99
101 this->finish_closes_(now);
102
103 USBAudioEvent event;
104 // Only the events already queued: one posted again while this runs waits for the next loop().
105 for (UBaseType_t queued = uxQueueMessagesWaiting(this->event_queue_);
106 queued > 0 && xQueueReceive(this->event_queue_, &event, 0) == pdTRUE; queued--) {
107 this->process_event_(event, now);
108 }
109}
110
112 const auto &speaker = this->endpoint_(Endpoint::SPEAKER);
113 const auto &microphone = this->endpoint_(Endpoint::MICROPHONE);
114 ESP_LOGCONFIG(TAG,
115 "USB Audio:\n"
116 " Device connected: %s\n"
117 " Speaker registered: %s\n"
118 " Microphone registered: %s",
119 YESNO(speaker.handle.load() != nullptr || microphone.handle.load() != nullptr),
120 YESNO(speaker.platform != nullptr), YESNO(microphone.platform != nullptr));
121}
122
124 if (this->event_queue_ == nullptr) {
125 return true;
126 }
127 // Stop the platforms first, then let the worker close each device once its platform is idle.
130 // The loop time does not advance while components tear down.
131 const uint32_t now = millis();
132 this->process_events_(now);
133 if (!this->finish_closes_(now)) {
134 return false;
135 }
136 // Wait for the worker to finish whatever it was still running.
137 if (uxQueueMessagesWaiting(this->op_queue_) != 0 || this->driver_busy_) {
138 return false;
139 }
140 xQueueReset(this->event_queue_);
141
142 if (this->uac_installed_) {
143 esp_err_t err = uac_host_uninstall();
144 if (err == ESP_ERR_INVALID_STATE) {
145 return false;
146 }
147 if (err != ESP_OK) {
148 ESP_LOGW(TAG, "uac_host_uninstall returned %s", esp_err_to_name(err));
149 }
150 this->uac_installed_ = false;
151 }
152 return true;
153}
154
156 auto &ep = this->endpoint_(endpoint);
157 ep.active = true;
158 ep.request_seq++;
159 const uac_host_device_handle_t handle = ep.handle;
160 if (handle == nullptr) {
161 if (ep.open_failed && !ep.open_pending) {
162 // The device is still plugged in but its last open failed; try again for this start.
163 ep.open_failed = false;
164 this->open_stream_(endpoint, ep.failed_addr, ep.failed_iface);
165 return;
166 }
167 ESP_LOGD(TAG, "%s interface not connected yet", LOG_STR_ARG(endpoint_name(endpoint)));
168 return;
169 }
170 if (ep.start_pending) {
171 // The pending start reports back to the platform, and checks `active` first.
172 return;
173 }
174 if (!ep.stream_started) {
175 if (ep.start_rejected) {
176 // Already probed and logged for this device; a retrying caller must not flood the log.
177 this->stream_failed_(endpoint);
178 return;
179 }
180 this->start_stream_(endpoint);
181 return;
182 }
183 // Queued even while an earlier resume or suspend is pending: the worker runs them in order, so the driver ends in
184 // the latest requested state, and only this request's RESUMED reports back.
185 Op op;
187 op.endpoint = endpoint;
188 op.handle = handle;
189 op.seq = ep.request_seq;
190 if (!this->queue_op_(op)) {
191 this->stream_failed_(endpoint);
192 }
193}
194
196 auto &ep = this->endpoint_(endpoint);
197 ep.active = false;
198 ep.request_seq++;
199 const uac_host_device_handle_t handle = ep.handle;
200 // A start still running on the worker is suspended once its STARTED arrives.
201 if (!ep.stream_started || handle == nullptr) {
202 return;
203 }
204 Op op;
206 op.endpoint = endpoint;
207 op.handle = handle;
208 this->queue_op_(op);
209}
210
212 const uint32_t start = millis();
213 while (true) {
214 // Counted before the busy check, so the worker waits for this call to leave the driver before it takes it.
215 this->driver_users_.fetch_add(1);
216 if (!this->driver_busy_) {
217 const uint32_t waited = millis() - start;
218 timeout_ms = waited < timeout_ms ? timeout_ms - waited : 0;
219 return true;
220 }
221 this->driver_users_.fetch_sub(1);
222 // A control request takes a few milliseconds, so go on as soon as the worker is done with it.
223 if (millis() - start >= timeout_ms) {
224 return false;
225 }
226 vTaskDelay(1);
227 }
228}
229
230esp_err_t USBAudioComponent::read_microphone(uint8_t *buffer, size_t size, size_t *bytes_read, uint32_t timeout_ms) {
231 *bytes_read = 0;
232 if (!this->enter_driver_(timeout_ms)) {
233 return ESP_ERR_TIMEOUT;
234 }
235 uac_host_device_handle_t handle = this->endpoint_(Endpoint::MICROPHONE).handle;
236 if (handle == nullptr) {
237 this->driver_users_.fetch_sub(1);
238 return ESP_ERR_INVALID_STATE;
239 }
240 // The driver collects up to `size` bytes for the whole timeout and returns what it got, or ESP_ERR_TIMEOUT when
241 // nothing arrived.
242 uint32_t actual = 0;
243 esp_err_t err = uac_host_device_read(handle, buffer, static_cast<uint32_t>(size), &actual, pdMS_TO_TICKS(timeout_ms));
244 this->driver_users_.fetch_sub(1);
245 *bytes_read = actual;
246 return err;
247}
248
249esp_err_t USBAudioComponent::write_speaker(const uint8_t *data, size_t length, uint32_t timeout_ms) {
250 if (!this->enter_driver_(timeout_ms)) {
251 // A non-blocking caller retries later.
252 return ESP_ERR_TIMEOUT;
253 }
254 uac_host_device_handle_t handle = this->endpoint_(Endpoint::SPEAKER).handle;
255 if (handle == nullptr) {
256 this->driver_users_.fetch_sub(1);
257 return ESP_ERR_INVALID_STATE;
258 }
259 esp_err_t err = uac_host_device_write(handle, const_cast<uint8_t *>(data), static_cast<uint32_t>(length),
260 pdMS_TO_TICKS(timeout_ms));
261 this->driver_users_.fetch_sub(1);
262 if (err == ESP_FAIL || err == ESP_ERR_INVALID_STATE) {
263 // Ring buffer full or stream inactive; report as timeout so callers back off and retry.
264 err = ESP_ERR_TIMEOUT;
265 }
266 return err;
267}
268
270 switch (event.type) {
272 auto &ep = this->endpoint_(event.endpoint);
273 if (ep.closing != nullptr) {
274 // A device that came back waits until its old handle is closed; the queue keeps draining meanwhile, so the
275 // worker's CLOSED can arrive.
276 ep.connect_deferred = true;
277 ep.deferred_addr = event.addr;
278 ep.deferred_iface = event.iface_num;
279 break;
280 }
281 this->open_stream_(event.endpoint, event.addr, event.iface_num);
282 break;
283 }
285 this->handle_disconnect_(event.handle);
286 break;
288 this->on_opened_(event);
289 break;
291 this->on_started_(event);
292 break;
294 this->on_resumed_(event);
295 break;
297 this->on_closed_(event, now);
298 break;
299 }
300}
301
302void USBAudioComponent::open_stream_(Endpoint endpoint, uint8_t addr, uint8_t iface_num) {
303 auto &ep = this->endpoint_(endpoint);
304 const LogString *name = endpoint_name(endpoint);
305 if (ep.platform == nullptr) {
306 ESP_LOGD(TAG, "%s interface connected but not configured", LOG_STR_ARG(name));
307 return;
308 }
309 if (ep.open_pending) {
310 // Either a duplicate, or a replacement for a device that went away during the pending open; on_opened_() tells
311 // them apart and replays a replacement.
312 ep.connect_deferred = true;
313 ep.deferred_addr = addr;
314 ep.deferred_iface = iface_num;
315 return;
316 }
317 if (ep.handle.load() != nullptr) {
318 ESP_LOGW(TAG, "%s interface already open, ignoring duplicate event", LOG_STR_ARG(name));
319 return;
320 }
321 Op op;
322 op.type = Op::Type::OPEN;
323 op.endpoint = endpoint;
324 op.addr = addr;
325 op.iface_num = iface_num;
326 if (!this->queue_op_(op)) {
327 // The event is already off the queue, so post it again for a later loop() instead of losing the device.
328 USBAudioEvent event;
330 event.endpoint = endpoint;
331 event.addr = addr;
332 event.iface_num = iface_num;
333 this->post_event_(event);
334 return;
335 }
336 ep.open_pending = true;
337}
338
340 auto &ep = this->endpoint_(event.endpoint);
341 ep.open_pending = false;
342 bool lost_meanwhile = false;
343 for (auto &lost : this->lost_while_opening_) {
344 if (event.ok && lost == event.handle) {
345 lost = nullptr;
346 lost_meanwhile = true;
347 }
348 }
349 if (std::none_of(this->endpoints_.begin(), this->endpoints_.end(),
350 [](const EndpointState &other) { return other.open_pending; })) {
351 // Only a pending open can still claim an entry; a failed one reports no handle, so its entry would stay behind
352 // and could match a later device that reuses the freed handle.
353 this->lost_while_opening_.fill(nullptr);
354 }
355 if (!event.ok) {
356 if (ep.connect_deferred) {
357 // A newer connect arrived during the failed open; try that one.
358 ep.connect_deferred = false;
359 this->open_stream_(event.endpoint, ep.deferred_addr, ep.deferred_iface);
360 return;
361 }
362 // Keep where the device is, so the next start() retries the open without a replug.
363 ep.open_failed = true;
364 ep.failed_addr = event.addr;
365 ep.failed_iface = event.iface_num;
366 if (ep.active) {
367 this->stream_failed_(event.endpoint);
368 }
369 return;
370 }
371 ep.open_failed = false;
372 ep.handle = event.handle;
373 ep.iface = event.iface_num;
374 ESP_LOGI(TAG, "%s interface %u connected (addr=%u)", LOG_STR_ARG(endpoint_name(event.endpoint)), ep.iface,
375 event.addr);
376 if (lost_meanwhile) {
377 // It went away while the worker was opening it; a connect deferred meanwhile is opened once it is closed.
378 this->handle_disconnect_(event.handle);
379 return;
380 }
381 // The device that was being opened is still here, so a connect deferred meanwhile was a duplicate.
382 ep.connect_deferred = false;
383 if (ep.active) {
384 this->start_stream_(event.endpoint);
385 }
386}
387
389 auto &ep = this->endpoint_(endpoint);
390 Op op;
392 op.endpoint = endpoint;
393 op.handle = ep.handle;
394 if (!this->queue_op_(op)) {
395 this->stream_failed_(endpoint);
396 return;
397 }
398 ep.start_pending = true;
399}
400
402 auto &ep = this->endpoint_(event.endpoint);
403 ep.start_pending = false;
404 if (event.handle != ep.handle.load()) {
405 // The device went away meanwhile; the platform was already told.
406 return;
407 }
408 if (!event.ok) {
409 ep.start_rejected = event.rejected;
410 if (ep.active) {
411 this->stream_failed_(event.endpoint);
412 }
413 return;
414 }
415 ep.stream_started = true;
416 if (event.endpoint == Endpoint::SPEAKER) {
417 // Send the current volume and mute to the newly started stream.
418 this->volume_.pending = true;
419 this->mute_.pending = true;
420 this->queue_controls_();
421 }
422 if (!ep.active) {
423 // The platform stopped while the stream was starting.
424 this->suspend(event.endpoint);
425 return;
426 }
427 ep.platform->on_stream_state(StreamEvent::RUNNING);
428}
429
431 auto &ep = this->endpoint_(event.endpoint);
432 if (event.handle != ep.handle.load() || event.seq != ep.request_seq || !ep.active) {
433 // The device went away, or a later suspend or resume is queued behind this one and decides the state.
434 return;
435 }
436 if (!event.ok) {
437 this->stream_failed_(event.endpoint);
438 return;
439 }
440 ep.platform->on_stream_state(StreamEvent::RUNNING);
441}
442
444 auto &ep = this->endpoint_(endpoint);
445 // The platform goes back to stopped, so a reconnect must not restart the stream; its next start() retries.
446 ep.active = false;
447 ep.platform->on_stream_state(StreamEvent::FAILED);
448}
449
451 this->speaker_volume_ = static_cast<uint8_t>(std::lround(std::clamp(volume, 0.0f, 1.0f) * 100.0f));
452 this->volume_.pending = true;
453 this->queue_controls_();
454}
455
457 this->speaker_muted_ = mute_state;
458 this->mute_.pending = true;
459 this->queue_controls_();
460}
461
463 if (this->op_queue_ == nullptr) {
464 return;
465 }
466 if (this->controls_queued_.exchange(true)) {
467 // The queued op sends the latest values, so lifecycle ops keep their queue slots.
468 return;
469 }
470 Op op;
472 if (xQueueSend(this->op_queue_, &op, 0) != pdTRUE) {
473 // The controls stay pending and the next start or change queues them again.
474 this->controls_queued_ = false;
475 }
476}
477
478void USBAudioComponent::handle_disconnect_(uac_host_device_handle_t handle) {
479 for (size_t i = 0; i < this->endpoints_.size(); i++) {
480 auto &ep = this->endpoints_[i];
481 if (handle != nullptr && ep.handle.load() == handle) {
482 const auto endpoint = static_cast<Endpoint>(i);
483 ESP_LOGI(TAG, "%s interface %u disconnected", LOG_STR_ARG(endpoint_name(endpoint)), ep.iface);
484 this->begin_close_(endpoint);
485 return;
486 }
487 }
488 if (std::any_of(this->endpoints_.begin(), this->endpoints_.end(),
489 [](const EndpointState &ep) { return ep.open_pending; })) {
490 // Its open is still running on the worker; close it once it reports back.
491 for (auto &lost : this->lost_while_opening_) {
492 if (lost == nullptr || lost == handle) {
493 lost = handle;
494 return;
495 }
496 }
497 // Both endpoints are already waiting on a lost open; the oldest one is the least likely to still report back.
498 this->lost_while_opening_[0] = handle;
499 return;
500 }
501 ESP_LOGD(TAG, "Disconnect event for an interface with no active stream");
502}
503
505 auto &ep = this->endpoint_(endpoint);
506 // Platform reads and writes see nullptr from here on and return at once.
507 const uac_host_device_handle_t handle = ep.handle.exchange(nullptr);
508 if (handle == nullptr) {
509 return;
510 }
511 ep.closing = handle;
512 ep.close_queued = false;
513 // `active` is kept, so the stream restarts when the device returns.
514 ep.platform->on_stream_state(StreamEvent::LOST);
515}
516
518 bool done = true;
519 for (size_t i = 0; i < this->endpoints_.size(); i++) {
520 auto &ep = this->endpoints_[i];
521 if (ep.closing == nullptr) {
522 continue;
523 }
524 done = false;
525 // Closing frees the driver's interface, so wait until no platform task can still be using the handle.
526 if (ep.close_queued || !ep.platform->stream_idle()) {
527 continue;
528 }
529 if (ep.close_attempts > 0 && static_cast<int32_t>(now - ep.close_retry_ms) < 0) {
530 // A failed close waits a moment before it is retried.
531 continue;
532 }
533 Op op;
535 op.endpoint = static_cast<Endpoint>(i);
536 op.handle = ep.closing;
537 op.stop = ep.stream_started;
538 ep.close_queued = this->queue_op_(op);
539 }
540 return done;
541}
542
544 auto &ep = this->endpoint_(event.endpoint);
545 ep.close_queued = false;
546 if (!event.ok && ++ep.close_attempts < MAX_CLOSE_ATTEMPTS) {
547 // Keep the handle, so finish_closes_() retries the close.
548 ep.close_retry_ms = now + CLOSE_RETRY_MS;
549 return;
550 }
551 if (!event.ok) {
552 ESP_LOGE(TAG, "Giving up closing the %s interface after %u attempts", LOG_STR_ARG(endpoint_name(event.endpoint)),
553 ep.close_attempts);
554 }
555 ep.close_attempts = 0;
556 ep.closing = nullptr;
557 ep.stream_started = false;
558 ep.start_rejected = false;
559 ep.start_pending = false;
560 if (ep.connect_deferred) {
561 // The device came back while its old handle was closing.
562 ep.connect_deferred = false;
563 this->open_stream_(event.endpoint, ep.deferred_addr, ep.deferred_iface);
564 }
565}
566
568 if (xQueueSend(this->op_queue_, &op, 0) != pdTRUE) {
569 ESP_LOGW(TAG, "USB audio work queue full, dropping %s request", LOG_STR_ARG(endpoint_name(op.endpoint)));
570 return false;
571 }
572 return true;
573}
574
576 if (xQueueSend(this->event_queue_, &event, 0) != pdTRUE) {
577 ESP_LOGW(TAG, "USB audio event queue full, dropping event");
578 }
580}
581
583 auto *self = static_cast<USBAudioComponent *>(param);
584 Op op;
585 while (true) {
586 if (xQueueReceive(self->op_queue_, &op, portMAX_DELAY) != pdTRUE) {
587 continue;
588 }
589 self->lock_driver_();
590 self->run_op_(op);
591 self->unlock_driver_();
592 }
593}
594
596 this->driver_busy_ = true;
597 // Reads and writes give up within their timeout; wait for those already inside the driver.
598 while (this->driver_users_ != 0) {
599 vTaskDelay(1);
600 }
601}
602
604
606 const LogString *name = endpoint_name(op.endpoint);
607 USBAudioEvent result;
608 result.endpoint = op.endpoint;
609 result.handle = op.handle;
610
611 switch (op.type) {
612 case Op::Type::OPEN: {
613 uint32_t buffer_size = this->endpoint_(op.endpoint).platform->get_buffer_size();
614 uint32_t threshold = std::max<uint32_t>(buffer_size / 4, 1U);
615 if (op.endpoint == Endpoint::SPEAKER) {
616 // The driver raises TX_DONE after taking a transfer only while the ring buffer holds at most the threshold,
617 // and it needs the threshold below the size. The speaker never fills past its buffer_size, so it hears about
618 // every transfer even when a waiting write lands right after one.
619 threshold = buffer_size;
620 buffer_size += SPEAKER_RING_SLACK;
621 }
622 uac_host_device_config_t dev_cfg = {
623 .addr = op.addr,
624 .iface_num = op.iface_num,
625 .buffer_size = buffer_size,
626 .buffer_threshold = threshold,
628 .callback_arg = this,
629 };
630 uac_host_device_handle_t handle{nullptr};
631 esp_err_t err = uac_host_device_open(&dev_cfg, &handle);
632 if (err != ESP_OK) {
633 ESP_LOGE(TAG, "Failed to open %s interface: %s", LOG_STR_ARG(name), esp_err_to_name(err));
634 }
636 result.ok = err == ESP_OK;
637 result.handle = handle;
638 result.addr = op.addr;
639 result.iface_num = op.iface_num;
640 this->post_event_(result);
641 return;
642 }
643 case Op::Type::START: {
645 uac_host_stream_config_t stm_cfg{};
646 if (this->populate_stream_config_(op.endpoint, op.handle, stm_cfg, result.rejected)) {
647 esp_err_t err = uac_host_device_start(op.handle, &stm_cfg);
648 // Already started or active is ESP_OK; ESP_ERR_INVALID_STATE means the interface is not ready.
649 result.ok = err == ESP_OK;
650 // The host cannot allocate the endpoint's pipe, for example when its max packet size exceeds the USB FIFO.
651 result.rejected = err == ESP_ERR_NOT_SUPPORTED;
652 if (!result.ok) {
653 ESP_LOGW(TAG, "Failed to start %s stream: %s", LOG_STR_ARG(name), esp_err_to_name(err));
654 }
655 }
656 this->post_event_(result);
657 return;
658 }
659 case Op::Type::RESUME: {
660 esp_err_t err = uac_host_device_resume(op.handle);
662 result.seq = op.seq;
663 // Already started or active is ESP_OK; ESP_ERR_INVALID_STATE means the interface is not ready.
664 result.ok = err == ESP_OK;
665 if (!result.ok) {
666 ESP_LOGW(TAG, "Failed to resume %s stream: %s", LOG_STR_ARG(name), esp_err_to_name(err));
667 }
668 this->post_event_(result);
669 return;
670 }
671 case Op::Type::SUSPEND: {
672 esp_err_t err = uac_host_device_suspend(op.handle);
673 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
674 ESP_LOGW(TAG, "Failed to suspend %s stream: %s", LOG_STR_ARG(name), esp_err_to_name(err));
675 }
676 return;
677 }
678 case Op::Type::CLOSE: {
679 if (op.stop) {
680 esp_err_t err = uac_host_device_stop(op.handle);
681 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
682 ESP_LOGW(TAG, "Failed to stop %s stream: %s", LOG_STR_ARG(name), esp_err_to_name(err));
683 }
684 }
685 esp_err_t err = uac_host_device_close(op.handle);
687 result.ok = err == ESP_OK;
688 if (!result.ok) {
689 // The interface may still be held, so the main loop keeps the handle and retries.
690 ESP_LOGW(TAG, "uac_host_device_close failed: %s", esp_err_to_name(err));
691 this->post_event_(result);
692 return;
693 }
694 if (op.endpoint == Endpoint::SPEAKER) {
695 // A reconnected device may support different controls.
696 for (ControlState *control : {&this->volume_, &this->mute_}) {
697 control->supported = true;
698 control->attempts = 0;
699 }
700 }
701 this->post_event_(result);
702 return;
703 }
704 case Op::Type::CONTROLS: {
705 // Cleared first, so a change made during the transfer queues another op.
706 this->controls_queued_ = false;
708 // Give the device a moment before retrying; the queue keeps any other work in order.
709 this->unlock_driver_();
710 vTaskDelay(pdMS_TO_TICKS(CONTROL_RETRY_MS));
711 this->lock_driver_();
712 this->queue_controls_();
713 }
714 return;
715 }
716 }
717}
718
719bool USBAudioComponent::populate_stream_config_(Endpoint endpoint, uac_host_device_handle_t handle,
720 uac_host_stream_config_t &stream_config, bool &rejected) {
721 const LogString *name = endpoint_name(endpoint);
722 const audio::AudioStreamInfo info = this->endpoint_(endpoint).platform->get_usb_stream_info();
723 const uint8_t channels = info.get_channels();
724 const uint8_t bits = info.get_bits_per_sample();
725 const uint32_t sample_rate = info.get_sample_rate();
726
727 uac_host_dev_info_t dev_info{};
728 esp_err_t err = uac_host_get_device_info(handle, &dev_info);
729 if (err != ESP_OK) {
730 ESP_LOGW(TAG, "Failed to query %s interface info: %s", LOG_STR_ARG(name), esp_err_to_name(err));
731 return false;
732 }
733
734 // uac_host_device_start() picks the alternate setting itself from channels, bit depth and rate: the last match with
735 // discrete rates, or the first match with a rate range. Find the one it will pick, so its subframe size is checked.
736 uint8_t selected_alt = 0;
737 uint8_t selected_subframe = 0;
738 for (uint8_t alt = 1; alt <= dev_info.iface_alt_num; alt++) {
739 uac_host_dev_alt_param_t alt_param{};
740 if (uac_host_get_device_alt_param(handle, alt, &alt_param) != ESP_OK || alt_param.channels != channels ||
741 alt_param.bit_resolution != bits || !alt_supports_rate(alt_param, sample_rate)) {
742 continue;
743 }
744 selected_alt = alt;
745 selected_subframe = alt_param.subframe_size;
746 if (alt_param.sample_freq_type == 0) {
747 break;
748 }
749 }
750
751 // ESPHome's samples are packed, so a device that pads them (e.g. 24-bit samples in 4-byte subframes) cannot be
752 // streamed unchanged.
753 if (selected_alt != 0 && selected_subframe * 8 == bits) {
754 stream_config.channels = channels;
755 stream_config.bit_resolution = bits;
756 stream_config.sample_freq = sample_rate;
757 stream_config.flags = 0;
758 return true;
759 }
760
761 rejected = true;
762 if (selected_alt != 0) {
763 ESP_LOGW(TAG, "%s interface %u would stream alt %u with %u-bit samples padded to %u bytes, which is not supported",
764 LOG_STR_ARG(name), dev_info.iface_num, selected_alt, bits, selected_subframe);
765 }
766 ESP_LOGW(TAG, "%s interface %u does not support requested format (channels=%u bits=%u sample_rate=%" PRIu32 " Hz)",
767 LOG_STR_ARG(name), dev_info.iface_num, channels, bits, sample_rate);
768 for (uint8_t alt = 1; alt <= dev_info.iface_alt_num; alt++) {
769 uac_host_dev_alt_param_t alt_param{};
770 if (uac_host_get_device_alt_param(handle, alt, &alt_param) != ESP_OK) {
771 continue;
772 }
773 if (alt_param.sample_freq_type == 0) {
774 ESP_LOGI(TAG, " alt %u: channels=%u bits=%u subframe=%u bytes sample rate range=%" PRIu32 "-%" PRIu32 " Hz", alt,
775 alt_param.channels, alt_param.bit_resolution, alt_param.subframe_size, alt_param.sample_freq_lower,
776 alt_param.sample_freq_upper);
777 continue;
778 }
779 ESP_LOGI(TAG, " alt %u: channels=%u bits=%u subframe=%u bytes sample rates:", alt, alt_param.channels,
780 alt_param.bit_resolution, alt_param.subframe_size);
781 const uint8_t count = discrete_rate_count(alt_param);
782 for (uint8_t i = 0; i < count; i++) {
783 // Some devices list a rate more than once.
784 if (std::find(alt_param.sample_freq, alt_param.sample_freq + i, alt_param.sample_freq[i]) ==
785 alt_param.sample_freq + i) {
786 ESP_LOGI(TAG, " %" PRIu32 " Hz", alt_param.sample_freq[i]);
787 }
788 }
789 }
790 return false;
791}
792
794 const uac_host_device_handle_t handle = this->endpoint_(Endpoint::SPEAKER).handle;
795 if (handle == nullptr) {
796 // Sent once the stream starts.
797 return false;
798 }
799 bool retry = false;
800 if (this->volume_.pending.exchange(false) && this->volume_.supported) {
801 retry |= this->apply_control_(this->volume_, uac_host_device_set_volume(handle, this->speaker_volume_),
802 LOG_STR("volume"));
803 }
804 if (this->mute_.pending.exchange(false) && this->mute_.supported) {
805 retry |= this->apply_control_(this->mute_, uac_host_device_set_mute(handle, this->speaker_muted_), LOG_STR("mute"));
806 }
807 return retry;
808}
809
810bool USBAudioComponent::apply_control_(ControlState &control, esp_err_t err, const LogString *name) {
811 if (err == ESP_OK) {
812 control.attempts = 0;
813 return false;
814 }
815 if (err == ESP_ERR_NOT_SUPPORTED) {
816 ESP_LOGW(TAG, "Speaker %s control not supported by device", LOG_STR_ARG(name));
817 control.supported = false;
818 return false;
819 }
820 if (err == ESP_ERR_INVALID_STATE) {
821 // The stream is not started yet; starting it queues the controls again.
822 control.pending = true;
823 return false;
824 }
825 if (++control.attempts >= MAX_CONTROL_ATTEMPTS) {
826 ESP_LOGW(TAG, "Failed to set speaker %s: %s", LOG_STR_ARG(name), esp_err_to_name(err));
827 control.attempts = 0;
828 return false;
829 }
830 control.pending = true;
831 return true;
832}
833
834void USBAudioComponent::driver_event_stub(uint8_t addr, uint8_t iface_num, const uac_host_driver_event_t event,
835 void *user_data) {
836 auto *self = static_cast<USBAudioComponent *>(user_data);
837 USBAudioEvent evt;
838 switch (event) {
839 case UAC_HOST_DRIVER_EVENT_TX_CONNECTED:
841 break;
842 case UAC_HOST_DRIVER_EVENT_RX_CONNECTED:
844 break;
845 default:
846 return;
847 }
849 evt.addr = addr;
850 evt.iface_num = iface_num;
851 self->post_event_(evt);
852}
853
854void USBAudioComponent::device_event_stub(uac_host_device_handle_t handle, const uac_host_device_event_t event,
855 void *user_data) {
856 auto *self = static_cast<USBAudioComponent *>(user_data);
857 if (event == UAC_HOST_DEVICE_EVENT_TX_DONE) {
858 auto &speaker = self->endpoint_(Endpoint::SPEAKER);
859 // Only the speaker's own interface; the handle is cleared first when the device goes away.
860 if (speaker.platform != nullptr && handle == speaker.handle.load()) {
861 speaker.platform->on_transfer_taken();
862 }
863 } else if (event == UAC_HOST_DEVICE_EVENT_TRANSFER_ERROR) {
864 self->transfer_errors_.fetch_add(1, std::memory_order_relaxed);
865 self->enable_loop_soon_any_context();
866 } else if (event == UAC_HOST_DRIVER_EVENT_DISCONNECTED) {
867 USBAudioEvent evt;
869 evt.handle = handle;
870 self->post_event_(evt);
871 }
872}
873
874} // namespace esphome::usb_audio
875
876#endif
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
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.
uint8_t get_bits_per_sample() const
Definition audio.h:28
uint8_t get_channels() const
Definition audio.h:29
uint32_t get_sample_rate() const
Definition audio.h:30
void lock_driver_()
Keep platform reads and writes out of the driver while the worker uses it.
static void worker_task(void *param)
void on_started_(const USBAudioEvent &event)
void on_closed_(const USBAudioEvent &event, uint32_t now)
bool populate_stream_config_(Endpoint endpoint, uac_host_device_handle_t handle, uac_host_stream_config_t &stream_config, bool &rejected)
void begin_close_(Endpoint endpoint)
void set_speaker_mute_state(bool mute_state)
bool apply_control_(ControlState &control, esp_err_t err, const LogString *name)
void stream_failed_(Endpoint endpoint)
void open_stream_(Endpoint endpoint, uint8_t addr, uint8_t iface_num)
static constexpr size_t EVENT_QUEUE_LENGTH
Definition usb_audio.h:109
bool apply_pending_speaker_controls_()
Send pending volume and mute; returns true when one should be retried later.
static void device_event_stub(uac_host_device_handle_t handle, uac_host_device_event_t event, void *user_data)
std::array< EndpointState, 2 > endpoints_
Definition usb_audio.h:215
std::array< uac_host_device_handle_t, 2 > lost_while_opening_
Definition usb_audio.h:218
void start_stream_(Endpoint endpoint)
void handle_disconnect_(uac_host_device_handle_t handle)
std::atomic< bool > controls_queued_
Definition usb_audio.h:230
static constexpr uint32_t SPEAKER_RING_SLACK
Definition usb_audio.h:112
EndpointState & endpoint_(Endpoint endpoint)
Definition usb_audio.h:174
std::atomic< uint32_t > transfer_errors_
Definition usb_audio.h:220
void process_event_(const USBAudioEvent &event, uint32_t now)
std::array< uint8_t, EVENT_QUEUE_LENGTH *sizeof(USBAudioEvent)> event_queue_storage_
Definition usb_audio.h:223
std::array< uint8_t, OP_QUEUE_LENGTH *sizeof(Op)> op_queue_storage_
Definition usb_audio.h:224
void post_event_(const USBAudioEvent &event)
void suspend(Endpoint endpoint)
void on_resumed_(const USBAudioEvent &event)
esp_err_t read_microphone(uint8_t *buffer, size_t size, size_t *bytes_read, uint32_t timeout_ms)
void on_opened_(const USBAudioEvent &event)
void resume(Endpoint endpoint)
Start streaming as soon as the device is connected; the platform hears back through on_stream_state()...
static constexpr size_t OP_QUEUE_LENGTH
Definition usb_audio.h:110
static void driver_event_stub(uint8_t addr, uint8_t iface_num, uac_host_driver_event_t event, void *user_data)
esp_err_t write_speaker(const uint8_t *data, size_t length, uint32_t timeout_ms)
bool enter_driver_(uint32_t &timeout_ms)
Platform side: wait, within timeout_ms, until the worker is not using the driver, and count the calle...
std::atomic< uint8_t > driver_users_
Definition usb_audio.h:233
void set_speaker_volume_level(float volume)
Safe from any task; the worker sends the values to the device.
std::atomic< uint8_t > speaker_volume_
Definition usb_audio.h:227
virtual audio::AudioStreamInfo get_usb_stream_info()=0
The fixed format the hub opens the stream with.
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "usb_audio.mic")
uint16_t size
Definition helpers.cpp:34
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
std::atomic< uac_host_device_handle_t > handle
Definition usb_audio.h:116
A blocking driver call, run on the worker.
Definition usb_audio.h:153
Posted to the main loop, by the USB driver task (CONNECTED, DISCONNECTED) or the hub's worker (the re...
Definition usb_audio.h:59
uac_host_device_handle_t handle
Definition usb_audio.h:70
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle