ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_audio_speaker.cpp
Go to the documentation of this file.
1#include "usb_audio_speaker.h"
2
3#ifdef USE_USB_AUDIO
4
5#include <algorithm>
6#include <cinttypes>
7
8#include "esphome/core/hal.h"
9#include "esphome/core/log.h"
10#include "esp_err.h"
11#include "esp_timer.h"
12#include "sdkconfig.h"
13
14namespace esphome::usb_audio {
15
16ESPHOME_LOG_TAG(TAG, "usb_audio.spk");
17
18static constexpr size_t MAX_TIMEOUT_RETRIES = 4;
19// Zeros are silence for the signed PCM the driver streams; 240 is a whole number of frames for every format.
20static constexpr uint8_t SILENCE[240] = {};
21
23 const auto &info = this->usb_stream_info_;
24 this->write_chunk_ = info.frames_to_bytes(std::max<uint32_t>(info.bytes_to_frames(this->buffer_size_ / 4), 1));
25 // The driver sends 1 ms packets, grouped into transfers it keeps in flight.
26 this->frame_bytes_ = info.frames_to_bytes(1);
27 this->packet_bytes_ = info.frames_to_bytes(info.get_sample_rate() / 1000);
28 this->packet_frac_ = info.get_sample_rate() % 1000;
29 this->transfer_bytes_ = info.frames_to_bytes((info.get_sample_rate() + 999) / 1000 * CONFIG_UAC_NUM_PACKETS_PER_URB);
30 this->transfer_us_ = CONFIG_UAC_NUM_PACKETS_PER_URB * 1000;
31 this->in_flight_us_ = CONFIG_UAC_NUM_ISOC_URBS * this->transfer_us_;
32 // About 10 ms per report, like the I2S speaker's DMA buffers.
33 this->report_frames_ = std::max<uint32_t>(info.get_sample_rate() / 100, 1);
34 // start(), stop() and finish() wake the loop.
35 this->disable_loop();
36}
37
39 ESP_LOGCONFIG(TAG,
40 "USB Speaker:\n"
41 " Sample rate: %" PRIu32 " Hz\n"
42 " Bits per sample: %u\n"
43 " Channels: %u\n"
44 " Buffer size: %" PRIu32 "\n"
45 " Write timeout: %" PRIu32 " ms",
46 this->usb_stream_info_.get_sample_rate(), this->usb_stream_info_.get_bits_per_sample(),
47 this->usb_stream_info_.get_channels(), this->buffer_size_, this->write_timeout_ms_);
48}
49
51 this->deliver_reports_();
52 const uint8_t commands = this->commands_.exchange(0);
53 if (commands & COMMAND_STOP) {
54 this->stop_();
55 }
56 if ((commands & COMMAND_START) && this->state_ == speaker::STATE_STOPPED) {
57 // Suspending flushed the driver's ring buffer, and resuming restarts its packet sizing.
58 portENTER_CRITICAL(&this->taken_lock_);
59 this->ring_bytes_ = 0;
60 this->ring_padding_ = 0;
61 // Resuming resets the driver's accumulator, then prepares every transfer, which advances it by their packets.
62 this->packet_frac_accum_ = CONFIG_UAC_NUM_ISOC_URBS * CONFIG_UAC_NUM_PACKETS_PER_URB * this->packet_frac_ % 1000;
63 this->unreported_frames_ = 0;
64 this->report_frames_pending_ = 0;
65 this->last_finish_us_ = 0;
66 portEXIT_CRITICAL(&this->taken_lock_);
68 // Answers through on_stream_state() now or once the device connects.
69 this->parent_->resume(Endpoint::SPEAKER);
70 }
71
72 if (commands & COMMAND_FINISH) {
73 if (this->state_ == speaker::STATE_RUNNING) {
74 this->begin_finishing_();
75 } else if (this->state_ == speaker::STATE_STARTING) {
76 // The stream is still starting; on_stream_state() finishes it once it runs.
77 this->finish_after_start_ = true;
78 }
79 }
80 if (this->finishing_ && this->pad_remaining_ > 0) {
81 this->pad_with_silence_();
82 }
83 // has_buffered_data_() turns false once the driver has taken the last whole transfer and it has had time to play.
84 if (this->finishing_ && this->pad_remaining_ == 0 && !this->has_buffered_data_(esp_timer_get_time())) {
85 this->stop_();
86 }
87 if (!this->finishing_) {
88 this->disable_loop();
89 }
90}
91
92void USBAudioSpeaker::request_(uint8_t set, uint8_t clear) {
93 this->commands_.fetch_and(static_cast<uint8_t>(~clear));
94 this->commands_.fetch_or(set);
96}
97
100 return;
101 }
103}
104
106
108
110 if (this->finishing_) {
111 return;
112 }
113 this->finishing_ = true;
114 this->pad_remaining_ = this->transfer_bytes_;
115 // loop() pads the last transfer and stops once the audio has played.
116 this->enable_loop();
117}
118
120 this->finishing_ = false;
121 this->finish_after_start_ = false;
122 this->pad_remaining_ = 0;
123 if (this->state_ == speaker::STATE_STOPPED) {
124 return;
125 }
126 this->parent_->suspend(Endpoint::SPEAKER);
128}
129
131 if (event == StreamEvent::RUNNING) {
132 if (this->state_ == speaker::STATE_STARTING) {
134 if (this->finish_after_start_) {
135 this->finish_after_start_ = false;
136 this->begin_finishing_();
137 }
138 }
139 return;
140 }
141 // Playback stops on a lost device as well as a failed start; the next start() opens it again.
142 if (event == StreamEvent::LOST && this->state_ == speaker::STATE_RUNNING) {
143 ESP_LOGW(TAG, "USB device disconnected, stopping playback");
144 }
145 this->stop_();
146}
147
148size_t USBAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
149 // 0 ticks is one non-blocking pass; portMAX_DELAY is capped by the configured write timeout.
150 const uint32_t budget_ms = ticks_to_wait == portMAX_DELAY ? this->write_timeout_ms_ : pdTICKS_TO_MS(ticks_to_wait);
151 return this->play_internal_(data, length, budget_ms);
152}
153
154// Callers such as voice_assistant and rtttl call this from loop(), so it never blocks.
155size_t USBAudioSpeaker::play(const uint8_t *data, size_t length) { return this->play(data, length, 0); }
156
157size_t USBAudioSpeaker::play_internal_(const uint8_t *data, size_t length, uint32_t budget_ms) {
158 if (this->audio_stream_info_ != this->usb_stream_info_) {
159 // Playing it through the configured stream would change the speed and channel layout.
160 if (!this->format_mismatch_.exchange(true)) {
161 const auto &info = this->audio_stream_info_;
162 ESP_LOGE(TAG,
163 "Audio format (%" PRIu32 " Hz, %u bits, %u channels) does not match the USB stream; use a resampler "
164 "speaker",
165 info.get_sample_rate(), info.get_bits_per_sample(), info.get_channels());
166 }
167 if (budget_ms > 0) {
168 vTaskDelay(pdMS_TO_TICKS(budget_ms));
169 }
170 return 0;
171 }
172 this->format_mismatch_ = false;
173
174 if (this->state_ != speaker::STATE_RUNNING) {
175 this->start();
176 // Wait out the budget so a pipeline task retrying play() does not spin.
177 if (budget_ms > 0) {
178 vTaskDelay(pdMS_TO_TICKS(budget_ms));
179 }
180 return 0;
181 }
182
183 // Counted before the hub's handle is read, so the hub never closes the device under this call.
184 this->writes_in_flight_.fetch_add(1);
185
186 // Runs on the caller's task, so the cached loop time does not apply.
187 const uint32_t start_ms = millis();
188 uint32_t now = start_ms;
189 size_t written = 0;
190 size_t timeout_retries = 0;
191 while (written < length) {
192 const uint32_t elapsed = now - start_ms;
193 if (budget_ms > 0 && elapsed >= budget_ms) {
194 break;
195 }
196 const size_t chunk = std::min<size_t>({length - written, this->write_chunk_, this->ring_room_()});
197 if (chunk == 0) {
198 // Back-pressure: wait for the driver to take a transfer.
199 if (budget_ms == 0) {
200 break;
201 }
202 vTaskDelay(1);
203 now = millis();
204 continue;
205 }
206 const uint32_t timeout_ms = budget_ms > 0 ? budget_ms - elapsed : 0;
207 const esp_err_t err = this->write_(data + written, chunk, timeout_ms, false);
208 now = millis();
209 if (err == ESP_OK) {
210 written += chunk;
211 timeout_retries = 0;
212 } else if (err == ESP_ERR_INVALID_STATE) {
213 // The device went away.
214 break;
215 } else if (err != ESP_ERR_TIMEOUT) {
216 ESP_LOGW(TAG, "Write error: %s", esp_err_to_name(err));
217 break;
218 } else if (budget_ms == 0 || ++timeout_retries >= MAX_TIMEOUT_RETRIES) {
219 // A full driver buffer is back-pressure; the caller retries.
220 break;
221 }
222 }
223 this->writes_in_flight_.fetch_sub(1);
224 return written;
225}
226
228 // Whole frames up to buffer_size_. ring_bytes_ is never below the real fill, so a write of this much does not wait.
229 const uint32_t ring = this->ring_bytes_;
230 const uint32_t room = ring < this->buffer_size_ ? this->buffer_size_ - ring : 0;
231 return room - room % this->frame_bytes_;
232}
233
234esp_err_t USBAudioSpeaker::write_(const uint8_t *data, size_t length, uint32_t timeout_ms, bool padding) {
235 // Counted first: the driver submits transfers inside the write, and on_transfer_taken() must see these bytes then.
236 // A failed write stores nothing, since the driver only writes whole chunks.
237 this->ring_bytes_.fetch_add(length);
238 if (padding) {
239 this->ring_padding_.fetch_add(length);
240 }
241 const esp_err_t err = this->parent_->write_speaker(data, length, timeout_ms);
242 if (err != ESP_OK) {
243 this->ring_bytes_.fetch_sub(length);
244 if (padding) {
245 this->ring_padding_.fetch_sub(length);
246 }
247 }
248 return err;
249}
250
252 if (this->state_ != speaker::STATE_RUNNING) {
253 this->pad_remaining_ = 0;
254 return;
255 }
256 // Counted before the hub's handle is read, so the hub never closes the device under this call.
257 this->writes_in_flight_.fetch_add(1);
258 const size_t piece = sizeof(SILENCE) - sizeof(SILENCE) % this->usb_stream_info_.frames_to_bytes(1);
259 while (this->pad_remaining_ > 0) {
260 const size_t chunk = std::min<size_t>({this->pad_remaining_, piece, this->ring_room_()});
261 if (chunk == 0 || this->write_(SILENCE, chunk, 0, true) != ESP_OK) {
262 // A full buffer; the rest is written on a later loop().
263 break;
264 }
265 this->pad_remaining_ -= chunk;
266 }
267 this->writes_in_flight_.fetch_sub(1);
268}
269
271 bool report = false;
272 portENTER_CRITICAL(&this->taken_lock_);
273 // Mirror the driver: the next transfer is CONFIG_UAC_NUM_PACKETS_PER_URB packets, and the driver takes it only when
274 // the ring buffer holds all of it; otherwise it parks the transfer until the next write.
275 uint32_t accum = this->packet_frac_accum_;
276 uint32_t size = 0;
277 for (int i = 0; i < CONFIG_UAC_NUM_PACKETS_PER_URB; i++) {
278 accum += this->packet_frac_;
279 size += this->packet_bytes_;
280 if (accum >= 1000) {
281 size += this->frame_bytes_;
282 accum -= 1000;
283 }
284 }
285 const uint32_t ring = this->ring_bytes_;
286 if (ring >= size) {
287 this->packet_frac_accum_ = accum;
288 this->ring_bytes_.fetch_sub(size);
289 // Padding sits at the end of the ring buffer and is not audio from the caller, so it is not reported.
290 const uint32_t padding = std::min<uint32_t>(this->ring_padding_, ring);
291 const uint32_t audio = std::min(size, ring - padding);
292 if (audio < size) {
293 this->ring_padding_.fetch_sub(std::min(size - audio, padding));
294 }
295 const int64_t now_us = esp_timer_get_time();
296 this->last_taken_us_ = static_cast<uint32_t>(now_us);
297 // The bus plays one packet per millisecond, so while it streams a transfer finishes one transfer after the one
298 // taken before it, however late this callback runs. It cannot finish later than now plus the transfers in flight,
299 // so a callback that runs on time pulls back any lateness the chain picked up. When the bus is idle, after a start
300 // (the driver submits nothing until the first write) or once it ran dry, the transfer plays right away.
301 const int64_t finish_us = this->last_finish_us_ < now_us
302 ? now_us + this->transfer_us_
303 : std::min(this->last_finish_us_ + this->transfer_us_, now_us + this->in_flight_us_);
304 this->last_finish_us_ = finish_us;
306 // Batched like the I2S speaker's DMA buffers: about every 10 ms, and once the caller's audio runs out.
307 if (this->unreported_frames_ > 0 && (this->unreported_frames_ >= this->report_frames_ || audio == ring - padding)) {
309 this->report_finish_us_ = finish_us;
310 this->unreported_frames_ = 0;
311 report = true;
312 }
313 }
314 portEXIT_CRITICAL(&this->taken_lock_);
315 if (report) {
316 // The listeners can block, so loop() calls them; the frames and their finish time travel together, so the delay
317 // does not change what they compute.
319 }
320}
321
323 portENTER_CRITICAL(&this->taken_lock_);
324 const uint32_t frames = this->report_frames_pending_;
325 const int64_t finish_us = this->report_finish_us_;
326 this->report_frames_pending_ = 0;
327 portEXIT_CRITICAL(&this->taken_lock_);
328 if (frames > 0) {
329 this->audio_output_callback_(frames, finish_us);
330 }
331}
332
333bool USBAudioSpeaker::has_buffered_data_(int64_t now_us) const {
334 if (this->state_ != speaker::STATE_RUNNING) {
335 return false;
336 }
337 // The driver still holds a whole transfer to take, or one it took has not finished playing. Less than a transfer
338 // stays in the ring buffer until the next write.
339 return this->ring_bytes_ >= this->transfer_bytes_ ||
340 static_cast<int32_t>(static_cast<uint32_t>(now_us) - this->last_taken_us_) <
341 static_cast<int32_t>(this->in_flight_us_);
342}
343
345
346// The hub's worker sends volume and mute to the device.
347void USBAudioSpeaker::set_volume(float volume) {
349 this->parent_->set_speaker_volume_level(volume);
350}
351
352void USBAudioSpeaker::set_mute_state(bool mute_state) {
354 this->parent_->set_speaker_mute_state(mute_state);
355}
356
357} // namespace esphome::usb_audio
358
359#endif
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
Definition audio.h:53
uint32_t bytes_to_frames(size_t bytes) const
Convert bytes to frames.
Definition audio.h:43
uint32_t get_sample_rate() const
Definition audio.h:30
virtual void set_volume(float volume)
Definition speaker.h:74
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
Definition speaker.h:138
virtual void set_mute_state(bool mute_state)
Definition speaker.h:85
audio::AudioStreamInfo audio_stream_info_
Definition speaker.h:131
void request_(uint8_t set, uint8_t clear)
void set_mute_state(bool mute_state) override
static constexpr uint8_t COMMAND_START
static constexpr uint8_t COMMAND_STOP
std::atomic< uint32_t > ring_padding_
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
void pad_with_silence_()
Write silence until a whole USB transfer is padded, so the driver sends a final partial transfer.
size_t play_internal_(const uint8_t *data, size_t length, uint32_t budget_ms)
static constexpr uint8_t COMMAND_FINISH
void set_volume(float volume) override
const audio::AudioStreamInfo usb_stream_info_
void on_stream_state(StreamEvent event) override
bool has_buffered_data_(int64_t now_us) const
esp_err_t write_(const uint8_t *data, size_t length, uint32_t timeout_ms, bool padding)
Write to the driver, counting the bytes in ring_bytes_ (and in ring_padding_ for silence padding).
std::atomic< uint32_t > last_taken_us_
size_t ring_room_() const
Bytes that can be written without filling the driver's ring buffer past buffer_size_.
ESPHOME_LOG_TAG(TAG, "usb_audio.mic")
StreamEvent
What the hub reports to a platform about its stream.
Definition usb_audio.h:29
uint16_t size
Definition helpers.cpp:34
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
int written
Definition helpers.h:1262
int64_t esp_timer_get_time(void)
static void uint32_t
uint16_t length
Definition tt21100.cpp:0