ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_audio.h
Go to the documentation of this file.
1#pragma once
2
4
5#ifdef USE_USB_AUDIO
6
7#include <array>
8#include <atomic>
9
13
14extern "C" {
15#include "freertos/FreeRTOS.h"
16#include "freertos/queue.h"
17#include "freertos/task.h"
18#include "usb/uac_host.h"
19}
20
21namespace esphome::usb_audio {
22
23enum class Endpoint : uint8_t {
24 SPEAKER,
26};
27
29enum class StreamEvent : uint8_t {
30 // The stream runs.
31 RUNNING,
32 // The device went away; the hub restarts the stream when it returns while the platform still wants it.
33 LOST,
34 // The stream failed to start; the platform goes back to stopped and its next start() retries.
35 FAILED,
36};
37
40 public:
41 explicit USBAudioStream(uint32_t buffer_size) : buffer_size_(buffer_size) {}
42
43 uint32_t get_buffer_size() const { return this->buffer_size_; }
46 virtual void on_stream_state(StreamEvent event) = 0;
48 virtual bool stream_idle() const = 0;
52 virtual void on_transfer_taken() {}
53
54 protected:
56};
57
60 enum class Type : uint8_t {
62 // The driver reports disconnects through the device callback, not the driver callback.
64 OPENED,
65 STARTED,
66 RESUMED,
67 CLOSED,
68 };
69
70 uac_host_device_handle_t handle{nullptr};
71 uint8_t addr{0};
72 uint8_t iface_num{0};
73 // RESUMED: the request it answers; only the latest request's result is acted on.
74 uint8_t seq{0};
77 bool ok{false};
78 // STARTED: the start will fail the same way until the device reconnects: the device does not support the configured
79 // format, or the host cannot allocate its pipe.
80 bool rejected{false};
81};
82
84 public:
85 void setup() override;
86 void dump_config() override;
87 void loop() override;
88 // Just after usb_host, which installs the USB host library that uac_host_install needs.
89 float get_setup_priority() const override { return setup_priority::BUS - 1.0f; }
90 bool teardown() override;
91
94
96 void resume(Endpoint endpoint);
97 void suspend(Endpoint endpoint);
98
100 void set_speaker_volume_level(float volume);
101 void set_speaker_mute_state(bool mute_state);
102
103 // Safe from any task. While the worker holds the driver for a control transfer, these wait for it within timeout_ms
104 // and return ESP_ERR_TIMEOUT if it is still busy, instead of blocking on the driver's interface lock for seconds.
105 esp_err_t read_microphone(uint8_t *buffer, size_t size, size_t *bytes_read, uint32_t timeout_ms);
106 esp_err_t write_speaker(const uint8_t *data, size_t length, uint32_t timeout_ms);
107
108 protected:
109 static constexpr size_t EVENT_QUEUE_LENGTH = 16;
110 static constexpr size_t OP_QUEUE_LENGTH = 8;
111 // Extra driver ring buffer space above the speaker's buffer_size, see Op::Type::OPEN.
112 static constexpr uint32_t SPEAKER_RING_SLACK = 4;
113
115 // Read by the platform tasks and the worker; cleared first when the device goes away.
116 std::atomic<uac_host_device_handle_t> handle{nullptr};
117 // A lost device, closed by the worker once the platform reports stream_idle().
118 uac_host_device_handle_t closing{nullptr};
120 uint8_t iface{0};
121 bool stream_started{false};
122 // The platform asked for the stream and has not stopped it.
123 bool active{false};
124 // Starting the connected device failed for good (see USBAudioEvent::rejected); cleared when it is closed.
125 bool start_rejected{false};
126 // An open or start is queued on the worker; its result event clears it.
127 bool open_pending{false};
128 bool start_pending{false};
129 bool close_queued{false};
130 // Counts resume() and suspend() calls, so a RESUMED that a later suspend overtook is ignored.
131 uint8_t request_seq{0};
132 // A connect that arrived while the old handle was still opening or closing; opened once that finishes.
133 bool connect_deferred{false};
134 uint8_t deferred_addr{0};
135 uint8_t deferred_iface{0};
136 // The last open failed; the next resume() retries it at this address and interface.
137 bool open_failed{false};
138 uint8_t failed_addr{0};
139 uint8_t failed_iface{0};
140 // A failed close keeps `closing` and is retried after this time, a limited number of times.
141 uint8_t close_attempts{0};
143 };
144
146 std::atomic<bool> pending{false};
147 // Only touched by the worker, and reset while the speaker is closed.
148 bool supported{true};
149 uint8_t attempts{0};
150 };
151
153 struct Op {
154 enum class Type : uint8_t {
155 OPEN,
156 START,
157 RESUME,
158 SUSPEND,
159 CLOSE,
160 CONTROLS,
161 };
162
163 uac_host_device_handle_t handle{nullptr};
164 uint8_t addr{0};
165 uint8_t iface_num{0};
166 // RESUME: copied to the result, see EndpointState::request_seq.
167 uint8_t seq{0};
170 // CLOSE: stop the stream before closing.
171 bool stop{false};
172 };
173
174 EndpointState &endpoint_(Endpoint endpoint) { return this->endpoints_[static_cast<size_t>(endpoint)]; }
175 const EndpointState &endpoint_(Endpoint endpoint) const { return this->endpoints_[static_cast<size_t>(endpoint)]; }
176 void process_events_(uint32_t now);
177 void process_event_(const USBAudioEvent &event, uint32_t now);
178 void open_stream_(Endpoint endpoint, uint8_t addr, uint8_t iface_num);
179 void on_opened_(const USBAudioEvent &event);
180 void on_started_(const USBAudioEvent &event);
181 void on_resumed_(const USBAudioEvent &event);
182 void on_closed_(const USBAudioEvent &event, uint32_t now);
183 void start_stream_(Endpoint endpoint);
184 void stream_failed_(Endpoint endpoint);
185 void handle_disconnect_(uac_host_device_handle_t handle);
186 void begin_close_(Endpoint endpoint);
187 bool finish_closes_(uint32_t now);
188 bool queue_op_(const Op &op);
189 void queue_controls_();
190 void post_event_(const USBAudioEvent &event);
191
192 // Worker side: the only place that makes blocking driver calls after setup().
193 static void worker_task(void *param);
194 void run_op_(const Op &op);
196 void lock_driver_();
199 bool enter_driver_(uint32_t &timeout_ms);
200 void unlock_driver_();
201 bool populate_stream_config_(Endpoint endpoint, uac_host_device_handle_t handle,
202 uac_host_stream_config_t &stream_config, bool &rejected);
205 bool apply_control_(ControlState &control, esp_err_t err, const LogString *name);
206
207 static void driver_event_stub(uint8_t addr, uint8_t iface_num, uac_host_driver_event_t event, void *user_data);
208 static void device_event_stub(uac_host_device_handle_t handle, uac_host_device_event_t event, void *user_data);
209
211 // Filled from the USB driver task and the worker, drained on the main loop.
212 QueueHandle_t event_queue_{nullptr};
213 // Filled on the main loop (and by volume/mute changes from any task), drained by the worker.
214 QueueHandle_t op_queue_{nullptr};
215 std::array<EndpointState, 2> endpoints_{};
216 // Disconnects for devices whose open is still running on the worker, one per endpoint since a speakerphone opens
217 // two interfaces; each is closed once its OPENED arrives.
218 std::array<uac_host_device_handle_t, 2> lost_while_opening_{};
219 // Counted on the USB driver task, logged from loop().
220 std::atomic<uint32_t> transfer_errors_{0};
221 StaticQueue_t event_queue_struct_{};
222 StaticQueue_t op_queue_struct_{};
223 std::array<uint8_t, EVENT_QUEUE_LENGTH * sizeof(USBAudioEvent)> event_queue_storage_{};
224 std::array<uint8_t, OP_QUEUE_LENGTH * sizeof(Op)> op_queue_storage_{};
227 std::atomic<uint8_t> speaker_volume_{100};
228 std::atomic<bool> speaker_muted_{false};
229 // A CONTROLS op is queued; the pending flags carry the latest values, so one is enough.
230 std::atomic<bool> controls_queued_{false};
231 // Set while the worker runs a driver call; platform reads and writes counted in driver_users_ back off.
232 std::atomic<bool> driver_busy_{false};
233 std::atomic<uint8_t> driver_users_{0};
234 bool uac_installed_{false};
235};
236
237} // namespace esphome::usb_audio
238
239#endif
Helper for FreeRTOS static task management.
Definition static_task.h:18
const EndpointState & endpoint_(Endpoint endpoint) const
Definition usb_audio.h:175
void lock_driver_()
Keep platform reads and writes out of the driver while the worker uses it.
void register_microphone(USBAudioStream *microphone)
Definition usb_audio.h:93
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 register_speaker(USBAudioStream *speaker)
Definition usb_audio.h:92
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()...
float get_setup_priority() const override
Definition usb_audio.h:89
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
A speaker or microphone platform that streams through the hub. The hub calls it on the main loop only...
Definition usb_audio.h:39
virtual bool stream_idle() const =0
No platform task is inside a read or write call on the device; the hub closes a lost device only then...
virtual void on_stream_state(StreamEvent event)=0
virtual audio::AudioStreamInfo get_usb_stream_info()=0
The fixed format the hub opens the stream with.
virtual void on_transfer_taken()
Speaker only: the driver tried to take its next transfer from the ring buffer, which it does each tim...
Definition usb_audio.h:52
USBAudioStream(uint32_t buffer_size)
Definition usb_audio.h:41
uint32_t buffer
Definition gif.cpp:50
constexpr float BUS
For communication buses like i2c/spi.
Definition component.h:39
StreamEvent
What the hub reports to a platform about its stream.
Definition usb_audio.h:29
uint16_t size
Definition helpers.cpp:34
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
spi_device_handle_t handle