ESPHome 2025.5.0
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
8#include "esphome/core/log.h"
9
10#include <algorithm>
11#include <cstring>
12
13namespace esphome {
14namespace resampler {
15
16static const UBaseType_t RESAMPLER_TASK_PRIORITY = 1;
17
18static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
19
20static const uint32_t TASK_DELAY_MS = 20;
21static const uint32_t TASK_STACK_SIZE = 3072;
22
23static const char *const TAG = "resampler_speaker";
24
25enum ResamplingEventGroupBits : uint32_t {
26 COMMAND_STOP = (1 << 0), // stops the resampler task
27 STATE_STARTING = (1 << 10),
28 STATE_RUNNING = (1 << 11),
29 STATE_STOPPING = (1 << 12),
30 STATE_STOPPED = (1 << 13),
31 ERR_ESP_NO_MEM = (1 << 19),
33 ERR_ESP_FAIL = (1 << 21),
34 ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
35};
36
38 this->event_group_ = xEventGroupCreate();
39
40 if (this->event_group_ == nullptr) {
41 ESP_LOGE(TAG, "Failed to create event group");
42 this->mark_failed();
43 return;
44 }
45
46 this->output_speaker_->add_audio_output_callback([this](uint32_t new_frames, int64_t write_timestamp) {
47 if (this->audio_stream_info_.get_sample_rate() != this->target_stream_info_.get_sample_rate()) {
48 // Convert the number of frames from the target sample rate to the source sample rate. Track the remainder to
49 // avoid losing frames from integer division truncation.
50 const uint64_t numerator = new_frames * this->audio_stream_info_.get_sample_rate() + this->callback_remainder_;
51 const uint64_t denominator = this->target_stream_info_.get_sample_rate();
52 this->callback_remainder_ = numerator % denominator;
53 this->audio_output_callback_(numerator / denominator, write_timestamp);
54 } else {
55 this->audio_output_callback_(new_frames, write_timestamp);
56 }
57 });
58}
59
61 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
62
63 if (event_group_bits & ResamplingEventGroupBits::STATE_STARTING) {
64 ESP_LOGD(TAG, "Starting resampler task");
65 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_STARTING);
66 }
67
68 if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_NO_MEM) {
69 this->status_set_error("Resampler task failed to allocate the internal buffers");
70 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
72 }
74 this->status_set_error("Cannot resample due to an unsupported audio stream");
75 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
77 }
78 if (event_group_bits & ResamplingEventGroupBits::ERR_ESP_FAIL) {
79 this->status_set_error("Resampler task failed");
80 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
82 }
83
84 if (event_group_bits & ResamplingEventGroupBits::STATE_RUNNING) {
85 ESP_LOGD(TAG, "Started resampler task");
86 this->status_clear_error();
87 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_RUNNING);
88 }
89 if (event_group_bits & ResamplingEventGroupBits::STATE_STOPPING) {
90 ESP_LOGD(TAG, "Stopping resampler task");
91 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::STATE_STOPPING);
92 }
93 if (event_group_bits & ResamplingEventGroupBits::STATE_STOPPED) {
94 if (this->delete_task_() == ESP_OK) {
95 ESP_LOGD(TAG, "Stopped resampler task");
96 xEventGroupClearBits(this->event_group_, ResamplingEventGroupBits::ALL_BITS);
97 }
98 }
99
100 switch (this->state_) {
102 esp_err_t err = this->start_();
103 if (err == ESP_OK) {
104 this->status_clear_error();
106 } else {
107 switch (err) {
108 case ESP_ERR_INVALID_STATE:
109 this->status_set_error("Failed to start resampler: resampler task failed to start");
110 break;
111 case ESP_ERR_NO_MEM:
112 this->status_set_error("Failed to start resampler: not enough memory for task stack");
113 default:
114 this->status_set_error("Failed to start resampler");
115 break;
116 }
117
119 }
120 break;
121 }
123 if (this->output_speaker_->is_stopped()) {
125 }
126
127 break;
129 this->stop_();
131 break;
133 break;
134 }
135}
136
137size_t ResamplerSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
138 if (this->is_stopped()) {
139 this->start();
140 }
141
142 size_t bytes_written = 0;
143 if ((this->output_speaker_->is_running()) && (!this->requires_resampling_())) {
144 bytes_written = this->output_speaker_->play(data, length, ticks_to_wait);
145 } else {
146 if (this->ring_buffer_.use_count() == 1) {
147 std::shared_ptr<RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
148 bytes_written = temp_ring_buffer->write_without_replacement(data, length, ticks_to_wait);
149 }
150 }
151
152 return bytes_written;
153}
154
156
159 this->target_bits_per_sample_, this->audio_stream_info_.get_channels(), this->target_sample_rate_);
160
162 this->output_speaker_->start();
163
164 if (this->requires_resampling_()) {
165 // Start the resampler task to handle converting sample rates
166 return this->start_task_();
167 }
168
169 return ESP_OK;
170}
171
173 if (this->task_stack_buffer_ == nullptr) {
174 if (this->task_stack_in_psram_) {
176 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
177 } else {
179 this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
180 }
181 }
182
183 if (this->task_stack_buffer_ == nullptr) {
184 return ESP_ERR_NO_MEM;
185 }
186
187 if (this->task_handle_ == nullptr) {
188 this->task_handle_ = xTaskCreateStatic(resample_task, "sample", TASK_STACK_SIZE, (void *) this,
189 RESAMPLER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
190 }
191
192 if (this->task_handle_ == nullptr) {
193 return ESP_ERR_INVALID_STATE;
194 }
195
196 return ESP_OK;
197}
198
200
202 if (this->task_handle_ != nullptr) {
203 xEventGroupSetBits(this->event_group_, ResamplingEventGroupBits::COMMAND_STOP);
204 }
205 this->output_speaker_->stop();
206}
207
209 if (!this->task_created_) {
210 this->task_handle_ = nullptr;
211
212 if (this->task_stack_buffer_ != nullptr) {
213 if (this->task_stack_in_psram_) {
215 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
216 } else {
218 stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
219 }
220
221 this->task_stack_buffer_ = nullptr;
222 }
223
224 return ESP_OK;
225 }
226
227 return ESP_ERR_INVALID_STATE;
228}
229
231
233 bool has_ring_buffer_data = false;
234 if (this->requires_resampling_() && (this->ring_buffer_.use_count() > 0)) {
235 has_ring_buffer_data = (this->ring_buffer_.lock()->available() > 0);
236 }
237 return (has_ring_buffer_data || this->output_speaker_->has_buffered_data());
238}
239
240void ResamplerSpeaker::set_mute_state(bool mute_state) {
241 this->mute_state_ = mute_state;
242 this->output_speaker_->set_mute_state(mute_state);
243}
244
246 this->volume_ = volume;
247 this->output_speaker_->set_volume(volume);
248}
249
251 return (this->audio_stream_info_.get_sample_rate() != this->target_sample_rate_) ||
253}
254
256 ResamplerSpeaker *this_resampler = (ResamplerSpeaker *) params;
257
258 this_resampler->task_created_ = true;
259 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STARTING);
260
261 std::unique_ptr<audio::AudioResampler> resampler =
262 make_unique<audio::AudioResampler>(this_resampler->audio_stream_info_.ms_to_bytes(TRANSFER_BUFFER_DURATION_MS),
263 this_resampler->target_stream_info_.ms_to_bytes(TRANSFER_BUFFER_DURATION_MS));
264
265 esp_err_t err = resampler->start(this_resampler->audio_stream_info_, this_resampler->target_stream_info_,
266 this_resampler->taps_, this_resampler->filters_);
267
268 if (err == ESP_OK) {
269 std::shared_ptr<RingBuffer> temp_ring_buffer =
271
272 if (temp_ring_buffer.use_count() == 0) {
273 err = ESP_ERR_NO_MEM;
274 } else {
275 this_resampler->ring_buffer_ = temp_ring_buffer;
276 resampler->add_source(this_resampler->ring_buffer_);
277
278 this_resampler->output_speaker_->set_audio_stream_info(this_resampler->target_stream_info_);
279 resampler->add_sink(this_resampler->output_speaker_);
280 }
281 }
282
283 if (err == ESP_OK) {
284 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_RUNNING);
285 } else if (err == ESP_ERR_NO_MEM) {
286 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_NO_MEM);
287 } else if (err == ESP_ERR_NOT_SUPPORTED) {
288 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_NOT_SUPPORTED);
289 }
290
291 while (err == ESP_OK) {
292 uint32_t event_bits = xEventGroupGetBits(this_resampler->event_group_);
293
295 break;
296 }
297
298 // Stop gracefully if the decoder is done
299 int32_t ms_differential = 0;
300 audio::AudioResamplerState resampler_state = resampler->resample(false, &ms_differential);
301
302 if (resampler_state == audio::AudioResamplerState::FINISHED) {
303 break;
304 } else if (resampler_state == audio::AudioResamplerState::FAILED) {
305 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::ERR_ESP_FAIL);
306 break;
307 }
308 }
309
310 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STOPPING);
311 resampler.reset();
312 xEventGroupSetBits(this_resampler->event_group_, ResamplingEventGroupBits::STATE_STOPPED);
313 this_resampler->task_created_ = false;
314 vTaskDelete(nullptr);
315}
316
317} // namespace resampler
318} // namespace esphome
319
320#endif
virtual void mark_failed()
Mark this component as failed.
void status_set_error(const char *message="unspecified")
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:683
void deallocate(T *p, size_t n)
Definition helpers.h:741
T * allocate(size_t n)
Definition helpers.h:703
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_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
esp_err_t delete_task_()
Deallocates the task stack and resets the pointers.
esp_err_t start_task_()
Starts the resampler task after allocating the task stack.
void stop_()
Stops the output speaker. If the resampling task is running, it sends the stop command.
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
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
std::unique_ptr< T > make_unique(Args &&...args)
Definition helpers.h:85
uint16_t length
Definition tt21100.cpp:0