ESPHome 2026.2.3
Loading...
Searching...
No Matches
resampler_speaker.cpp
Go to the documentation of this file.
1#include "resampler_speaker.h"
2
3#ifdef USE_ESP32
4
6
10#include "esphome/core/log.h"
11
12#include <algorithm>
13#include <cstring>
14
15namespace esphome {
16namespace resampler {
17
18static const UBaseType_t RESAMPLER_TASK_PRIORITY = 1;
19
20static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
21
22static const uint32_t TASK_STACK_SIZE = 3072;
23
24static const uint32_t STATE_TRANSITION_TIMEOUT_MS = 5000;
25
26static const char *const TAG = "resampler_speaker";
27
28enum ResamplingEventGroupBits : uint32_t {
29 COMMAND_STOP = (1 << 0), // signals stop request
30 COMMAND_START = (1 << 1), // signals start request
31 COMMAND_FINISH = (1 << 2), // signals finish request (graceful stop)
32 TASK_COMMAND_STOP = (1 << 5), // signals the task to stop
33 STATE_STARTING = (1 << 10),
34 STATE_RUNNING = (1 << 11),
35 STATE_STOPPING = (1 << 12),
36 STATE_STOPPED = (1 << 13),
37 ERR_ESP_NO_MEM = (1 << 19),
39 ERR_ESP_FAIL = (1 << 21),
40 ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
41};
42
44 ESP_LOGCONFIG(TAG,
45 "Resampler Speaker:\n"
46 " Target Bits Per Sample: %u\n"
47 " Target Sample Rate: %" PRIu32 " Hz",
49}
50
52 this->event_group_ = xEventGroupCreate();
53 if (this->event_group_ == nullptr) {
54 ESP_LOGE(TAG, "Failed to create event group");
55 this->mark_failed();
56 return;
57 }
58
59 this->output_speaker_->add_audio_output_callback([this](uint32_t new_frames, int64_t write_timestamp) {
60 if (this->audio_stream_info_.get_sample_rate() != this->target_stream_info_.get_sample_rate()) {
61 // Convert the number of frames from the target sample rate to the source sample rate. Track the remainder to
62 // avoid losing frames from integer division truncation.
63 const uint64_t numerator = new_frames * this->audio_stream_info_.get_sample_rate() + this->callback_remainder_;
64 const uint64_t denominator = this->target_stream_info_.get_sample_rate();
65 this->callback_remainder_ = numerator % denominator;
66 this->audio_output_callback_(numerator / denominator, write_timestamp);
67 } else {
68 this->audio_output_callback_(new_frames, write_timestamp);
69 }
70 });
71
72 // Start with loop disabled since no task is running and no commands are pending
73 this->disable_loop();
74}
75
77 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
78
79 // Process commands with priority: STOP > FINISH > START
80 // This ensures stop commands take precedence over conflicting start commands
81 if (event_group_bits & ResamplingEventGroupBits::COMMAND_STOP) {
83 // Clear STOP, START, and FINISH bits - stop takes precedence
84 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_STOP |
87 this->waiting_for_output_ = false;
89 } else if (this->state_ == speaker::STATE_STOPPED) {
90 // Already stopped, just clear the command bits
91 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_STOP |
94 }
95 // Leave bits set if STATE_STOPPING - will be processed once stopped
96 } else if (event_group_bits & ResamplingEventGroupBits::COMMAND_FINISH) {
97 if (this->state_ == speaker::STATE_RUNNING) {
98 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_FINISH);
99 this->output_speaker_->finish();
100 } else if (this->state_ == speaker::STATE_STOPPED) {
101 // Already stopped, just clear the command bit
102 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_FINISH);
103 }
104 // Leave bit set if transitioning states - will be processed once state allows
105 } else if (event_group_bits & ResamplingEventGroupBits::COMMAND_START) {
106 if (this->state_ == speaker::STATE_STOPPED) {
107 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_START);
109 } else if (this->state_ == speaker::STATE_RUNNING) {
110 // Already running, just clear the command bit
111 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::COMMAND_START);
112 }
113 // Leave bit set if transitioning states - will be processed once state allows
114 }
115
116 // Re-read bits after command processing (enter_stopping_state_ may have set task bits)
117 event_group_bits = xEventGroupGetBits(this->event_group_);
118
119 if (event_group_bits & ResamplingEventGroupBits::STATE_STARTING) {
120 ESP_LOGD(TAG, "Starting");
121 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_STARTING);
122 }
123
124 if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NO_MEM) {
125 this->status_set_error(LOG_STR("Not enough memory"));
126 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
127 this->enter_stopping_state_();
128 }
130 this->status_set_error(LOG_STR("Unsupported stream"));
131 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
132 this->enter_stopping_state_();
133 }
134 if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_FAIL) {
135 this->status_set_error(LOG_STR("Resampler failure"));
136 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
137 this->enter_stopping_state_();
138 }
139
140 if (event_group_bits & ResamplingEventGroupBits::STATE_RUNNING) {
141 ESP_LOGV(TAG, "Started");
142 this->status_clear_error();
143 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_RUNNING);
144 }
145 if (event_group_bits & ResamplingEventGroupBits::STATE_STOPPING) {
146 ESP_LOGV(TAG, "Stopping");
147 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_STOPPING);
148 }
149 if (event_group_bits & ResamplingEventGroupBits::STATE_STOPPED) {
150 this->delete_task_();
151 ESP_LOGD(TAG, "Stopped");
152 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ALL_BITS);
153 }
154
155 switch (this->state_) {
157 if (!this->waiting_for_output_) {
158 esp_err_t err = this->start_();
159 if (err == ESP_OK) {
160 this->callback_remainder_ = 0; // reset callback remainder
161 this->status_clear_error();
162 this->waiting_for_output_ = true;
164 } else {
165 this->set_start_error_(err);
166 this->waiting_for_output_ = false;
167 this->enter_stopping_state_();
168 }
169 } else {
170 if (this->output_speaker_->is_running()) {
172 this->waiting_for_output_ = false;
173 } else if ((App.get_loop_component_start_time() - this->state_start_ms_) > STATE_TRANSITION_TIMEOUT_MS) {
174 // Timed out waiting for the output speaker to start
175 this->waiting_for_output_ = false;
176 this->enter_stopping_state_();
177 }
178 }
179 break;
180 }
182 if (this->output_speaker_->is_stopped()) {
183 this->enter_stopping_state_();
184 }
185 break;
187 if ((this->output_speaker_->get_pause_state()) ||
188 ((App.get_loop_component_start_time() - this->state_start_ms_) > STATE_TRANSITION_TIMEOUT_MS)) {
189 // If output speaker is paused or stopping timeout exceeded, force stop
190 this->output_speaker_->stop();
191 }
192
193 if (this->output_speaker_->is_stopped() && (this->task_handle_ == nullptr)) {
194 // Only transition to stopped state once the output speaker and resampler task are fully stopped
195 this->waiting_for_output_ = false;
197 }
198 break;
199 }
201 event_group_bits = xEventGroupGetBits(this->event_group_);
202 if (event_group_bits == 0) {
203 // No pending events, disable loop to save CPU cycles
204 this->disable_loop();
205 }
206 break;
207 }
208}
209
211 switch (err) {
212 case ESP_ERR_INVALID_STATE:
213 this->status_set_error(LOG_STR("Task failed to start"));
214 break;
215 case ESP_ERR_NO_MEM:
216 this->status_set_error(LOG_STR("Not enough memory"));
217 break;
218 default:
219 this->status_set_error(LOG_STR("Failed to start"));
220 break;
221 }
222}
223
224size_t ResamplerSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
225 if (this->is_stopped()) {
226 this->start();
227 }
228
229 size_t bytes_written = 0;
230 if ((this->output_speaker_->is_running()) && (!this->requires_resampling_())) {
231 bytes_written = this->output_speaker_->play(data, length, ticks_to_wait);
232 } else {
233 std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
234 if (temp_ring_buffer) {
235 // Only write to the ring buffer if the reference is valid
236 bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait);
237 } else {
238 // Delay to avoid repeatedly hammering while waiting for the speaker to start
239 vTaskDelay(ticks_to_wait);
240 }
241 }
242
243 return bytes_written;
244}
245
246void ResamplerSpeaker::send_command_(uint32_t command_bit, bool wake_loop) {
248 uint32_t event_bits = xEventGroupGetBits(this->event_group_);
249 if (!(event_bits & command_bit)) {
250 xEventGroupSetBits(this->event_group_, command_bit);
251#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
252 if (wake_loop) {
254 }
255#endif
256 }
257}
258
260
263 this->target_bits_per_sample_, this->audio_stream_info_.get_channels(), this->target_sample_rate_);
264
266 this->output_speaker_->start();
267
268 if (this->requires_resampling_()) {
269 // Start the resampler task to handle converting sample rates
270 return this->start_task_();
271 }
272
273 return ESP_OK;
274}
275
277 if (this->task_stack_buffer_ == nullptr) {
278 if (this->task_stack_in_psram_) {
280 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
281 } else {
283 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
284 }
285 }
286
287 if (this->task_stack_buffer_ == nullptr) {
288 return ESP_ERR_NO_MEM;
289 }
290
291 if (this->task_handle_ == nullptr) {
292 this->task_handle_ = xTaskCreateStatic(resample_task, "resampler", TASK_STACK_SIZE, (void *) this,
293 RESAMPLER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
294 }
295
296 if (this->task_handle_ == nullptr) {
297 return ESP_ERR_INVALID_STATE;
298 }
299
300 return ESP_OK;
301}
302
304
313
315 if (this->task_handle_ != nullptr) {
316 // Delete the suspended task
317 vTaskDelete(this->task_handle_);
318 this->task_handle_ = nullptr;
319 }
320
321 if (this->task_stack_buffer_ != nullptr) {
322 // Deallocate the task stack buffer
323 if (this->task_stack_in_psram_) {
325 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
326 } else {
328 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
329 }
330
331 this->task_stack_buffer_ = nullptr;
332 }
333}
334
336
338 bool has_ring_buffer_data = false;
339 if (this->requires_resampling_()) {
340 std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
341 if (temp_ring_buffer) {
342 has_ring_buffer_data = (temp_ring_buffer->available() > 0);
343 }
344 }
345 return (has_ring_buffer_data || this->output_speaker_->has_buffered_data());
346}
347
348void ResamplerSpeaker::set_mute_state(bool mute_state) {
349 this->mute_state_ = mute_state;
350 this->output_speaker_->set_mute_state(mute_state);
351}
352
354 this->volume_ = volume;
355 this->output_speaker_->set_volume(volume);
356}
357
359 return (this->audio_stream_info_.get_sample_rate() != this->target_sample_rate_) ||
361}
362
364 ResamplerSpeaker *this_resampler = static_cast<ResamplerSpeaker *>(params);
365
366 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STARTING);
367
368 std::unique_ptr<audio::AudioResampler> resampler =
369 make_unique<audio::AudioResampler>(this_resampler->audio_stream_info_.ms_to_bytes(TRANSFER_BUFFER_DURATION_MS),
370 this_resampler->target_stream_info_.ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
371
372 esp_err_t err = resampler->start(this_resampler->audio_stream_info_, this_resampler->target_stream_info_,
373 this_resampler->taps_, this_resampler->filters_);
374
375 if (err == ESP_OK) {
376 std::shared_ptr<RingBuffer> temp_ring_buffer =
378
379 if (!temp_ring_buffer) {
380 err = ESP_ERR_NO_MEM;
381 } else {
382 this_resampler->ring_buffer_ = temp_ring_buffer;
383 resampler->add_source(this_resampler->ring_buffer_);
384
385 this_resampler->output_speaker_->set_audio_stream_info(this_resampler->target_stream_info_);
386 resampler->add_sink(this_resampler->output_speaker_);
387 }
388 }
389
390 if (err == ESP_OK) {
391 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_RUNNING);
392 } else if (err == ESP_ERR_NO_MEM) {
393 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
394 } else if (err == ESP_ERR_NOT_SUPPORTED) {
395 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
396 }
397
398 while (err == ESP_OK) {
399 uint32_t event_bits = xEventGroupGetBits(this_resampler->event_group_);
400
402 break;
403 }
404
405 // Stop gracefully if the decoder is done
406 int32_t ms_differential = 0;
407 audio::AudioResamplerState resampler_state = resampler->resample(false, &ms_differential);
408
409 if (resampler_state == audio::AudioResamplerState::FINISHED) {
410 break;
411 } else if (resampler_state == audio::AudioResamplerState::FAILED) {
412 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
413 break;
414 }
415 }
416
417 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STOPPING);
418 resampler.reset();
419 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STOPPED);
420
421 vTaskSuspend(nullptr); // Suspend this task indefinitely until the loop method deletes it
422}
423
424} // namespace resampler
425} // namespace esphome
426
427#endif
void wake_loop_threadsafe()
Wake the main event loop from a FreeRTOS task Thread-safe, can be called from task context to immedia...
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.
virtual 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.
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:1647
void deallocate(T *p, size_t n)
Definition helpers.h:1705
T * allocate(size_t n)
Definition helpers.h:1667
static std::unique_ptr< RingBuffer > create(size_t len)
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:73
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 set_start_error_(esp_err_t err)
Sets the appropriate status error based on the start failure reason.
void set_volume(float volume) override
Volume state changes are passed to the parent's output speaker.
std::weak_ptr< RingBuffer > ring_buffer_
audio::AudioStreamInfo target_stream_info_
esp_err_t start_()
Starts the output speaker after setting the resampled stream info.
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
void enter_stopping_state_()
Transitions to STATE_STOPPING, records the stopping timestamp, sends the task stop command if the tas...
void delete_task_()
Deletes the resampler task if suspended, deallocates the task stack, and resets the related pointers.
void send_command_(uint32_t command_bit, bool wake_loop=false)
Sends a command via event group bits, enables the loop, and optionally wakes the main loop.
esp_err_t start_task_()
Starts the resampler task after allocating the task stack.
void set_mute_state(bool mute_state) override
Mute state changes are passed to the parent's output speaker.
virtual size_t play(const uint8_t *data, size_t length)=0
Plays the provided audio data.
bool is_running() const
Definition speaker.h:66
virtual void set_volume(float volume)
Definition speaker.h:71
void add_audio_output_callback(std::function< void(uint32_t, int64_t)> &&callback)
Callback function for sending the duration of the audio written to the speaker since the last callbac...
Definition speaker.h:109
virtual bool get_pause_state() const
Definition speaker.h:62
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
Definition speaker.h:123
void set_audio_stream_info(const audio::AudioStreamInfo &audio_stream_info)
Definition speaker.h:99
virtual void set_mute_state(bool mute_state)
Definition speaker.h:81
virtual bool has_buffered_data() const =0
audio::AudioStreamInfo audio_stream_info_
Definition speaker.h:115
virtual void start()=0
virtual void finish()
Definition speaker.h:58
bool is_stopped() const
Definition speaker.h:67
virtual void stop()=0
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t length
Definition tt21100.cpp:0