ESPHome 2026.9.0
Loading...
Searching...
No Matches
i2s_audio_speaker.cpp
Go to the documentation of this file.
1#include "i2s_audio_speaker.h"
2
3#ifdef USE_ESP32
4
5#include <driver/gpio.h>
6#include <driver/i2s_std.h>
7
10
12#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14
15#include "esp_timer.h"
16
17// esp-audio-libs
18#include <gain.h>
19
20namespace esphome::i2s_audio {
21
22static const char *const TAG = "i2s_audio.speaker";
23
24// Software volume control maps the user-facing [0.0, 1.0] range to a Q31 scale factor.
25// Volumes in (0.0, 1.0) map linearly to a dB reduction in [-49.0, 0.0] dB.
26static constexpr float SOFTWARE_VOLUME_MIN_DB = -49.0f;
27
29 this->event_group_ = xEventGroupCreate();
30
31 if (this->event_group_ == nullptr) {
32 ESP_LOGE(TAG, "Event group creation failed");
33 this->mark_failed();
34 return;
35 }
36
37 // Initialize volume control. When audio_dac is configured, this sets the DAC volume.
38 // When no audio_dac is configured, this initializes software volume control.
39 this->set_volume(this->volume_);
40}
41
43 ESP_LOGCONFIG(TAG,
44 "Speaker:\n"
45 " Pin: %d\n"
46 " Buffer duration: %" PRIu32,
47 static_cast<int8_t>(this->dout_pin_), this->buffer_duration_ms_);
48 if (this->timeout_.has_value()) {
49 ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 " ms", this->timeout_.value());
50 }
51}
52
54 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
55
56 // A stop that arrives while stopped cancels any start that has not been processed yet
58 if ((event_group_bits & stop_bits) && (this->state_ == speaker::STATE_STOPPED)) {
59 xEventGroupClearBits(this->event_group_, stop_bits | SpeakerEventGroupBits::COMMAND_START);
60 event_group_bits &= ~(stop_bits | SpeakerEventGroupBits::COMMAND_START);
61 }
62
63 if ((event_group_bits & SpeakerEventGroupBits::COMMAND_START) && (this->state_ == speaker::STATE_STOPPED)) {
65 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
66 }
67
68 // Handle the task's state
69 if (event_group_bits & SpeakerEventGroupBits::TASK_STARTING) {
70 ESP_LOGD(TAG, "Starting");
71 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::TASK_STARTING);
72 }
73 if (event_group_bits & SpeakerEventGroupBits::TASK_RUNNING) {
74 ESP_LOGV(TAG, "Started");
75 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::TASK_RUNNING);
77 }
78 if (event_group_bits & SpeakerEventGroupBits::TASK_STOPPING) {
79 ESP_LOGV(TAG, "Stopping");
80 // Lockstep-breaking error bits are latched by the task and cleared along with all other bits
81 // when TASK_STOPPED is processed; log them here, exactly once, as the task winds down.
82 if (event_group_bits & SpeakerEventGroupBits::ERR_DROPPED_EVENT) {
83 ESP_LOGE(TAG, "ISR event queue overflow, restarting speaker task to recover timestamp sync");
84 }
85 if (event_group_bits & SpeakerEventGroupBits::ERR_PARTIAL_WRITE) {
86 ESP_LOGE(TAG, "Partial DMA write broke buffer alignment, restarting speaker task");
87 }
88 if (event_group_bits & SpeakerEventGroupBits::ERR_LOCKSTEP_DESYNC) {
89 ESP_LOGE(TAG, "Event/record queues desynced, restarting speaker task");
90 }
91 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::TASK_STOPPING);
93 }
94 if (event_group_bits & SpeakerEventGroupBits::TASK_STOPPED) {
95 ESP_LOGD(TAG, "Stopped");
96
97 vTaskDelete(this->speaker_task_handle_);
98 this->speaker_task_handle_ = nullptr;
99
100 this->stop_i2s_driver_();
101 // ALL_BITS includes COMMAND_START. Take the bits from the clear itself, not from the snapshot at
102 // the top of loop(): the audio source's task can raise a start at any point above, including
103 // during stop_i2s_driver_(), and nothing would ever re-issue it.
104 const EventBits_t bits_before_clear = xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS);
105 if (bits_before_clear & SpeakerEventGroupBits::COMMAND_START) {
106 ESP_LOGD(TAG, "Start requested while stopping; keeping the request");
107 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
108 }
109 this->status_clear_error();
110
111 this->on_task_stopped();
112
114 }
115
116 if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NO_MEM) {
117 ESP_LOGE(TAG, "Speaker task setup failed (allocation, preload, or channel enable)");
118 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
119 }
120
121 // Handle the speaker's state
122 switch (this->state_) {
124 if (this->status_has_error()) {
125 break;
126 }
127
128 // Still starting up or winding down from a previous run
129 if ((this->tx_handle_ != nullptr) || (this->speaker_task_handle_ != nullptr)) {
130 break;
131 }
132
133 if (this->start_i2s_driver(this->audio_stream_info_) != ESP_OK) {
134 ESP_LOGE(TAG, "Driver failed to start; retrying in 1 second");
135 this->status_momentary_error("driver-failure", 1000);
136 break;
137 }
138
139 xTaskCreate(I2SAudioSpeakerBase::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
140 &this->speaker_task_handle_);
141
142 if (this->speaker_task_handle_ == nullptr) {
143 ESP_LOGE(TAG, "Task failed to start, retrying in 1 second");
144 this->status_momentary_error("task-failure", 1000);
145 this->stop_i2s_driver_(); // Stops the driver to return the lock; will be reloaded in next attempt
146 }
147 break;
148 case speaker::STATE_RUNNING: // Intentional fallthrough
149 case speaker::STATE_STOPPING: // Intentional fallthrough
151 break;
152 }
153}
154
155void I2SAudioSpeakerBase::set_volume(float volume) {
156 this->volume_ = volume;
157#ifdef USE_AUDIO_DAC
158 if (this->audio_dac_ != nullptr) {
159 if (volume > 0.0f) {
160 this->audio_dac_->set_mute_off();
161 }
162 this->audio_dac_->set_volume(volume);
163 } else
164#endif // USE_AUDIO_DAC
165 {
166 // Fallback to software volume control by using a Q31 fixed point scaling factor.
167 // At maximum volume (1.0), set to INT32_MAX to bypass volume processing entirely
168 // and avoid any floating-point precision issues that could cause slight volume reduction.
169 if (volume >= 1.0f) {
170 this->q31_volume_factor_ = INT32_MAX;
171 } else if (volume <= 0.0f) {
172 this->q31_volume_factor_ = 0;
173 } else {
174 this->q31_volume_factor_ =
175 esp_audio_libs::gain::db_to_q31(remap<float, float>(volume, 0.0f, 1.0f, SOFTWARE_VOLUME_MIN_DB, 0.0f));
176 }
177 }
178}
179
180void I2SAudioSpeakerBase::set_mute_state(bool mute_state) {
181 this->mute_state_ = mute_state;
182#ifdef USE_AUDIO_DAC
183 if (this->audio_dac_) {
184 if (mute_state) {
185 this->audio_dac_->set_mute_on();
186 } else {
187 this->audio_dac_->set_mute_off();
188 }
189 } else
190#endif // USE_AUDIO_DAC
191 {
192 if (mute_state) {
193 // Fallback to software volume control and scale by 0
194 this->q31_volume_factor_ = 0;
195 } else {
196 // Revert to previous volume when unmuting
197 this->set_volume(this->volume_);
198 }
199 }
200}
201
202size_t I2SAudioSpeakerBase::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
203 if (this->is_failed()) {
204 ESP_LOGE(TAG, "Setup failed; cannot play audio");
205 return 0;
206 }
207
209 this->start();
210 }
211
212 if (this->state_ != speaker::STATE_RUNNING) {
213 // Unable to write data to a running speaker, so delay the max amount of time so it can get ready
214 vTaskDelay(ticks_to_wait);
215 ticks_to_wait = 0;
216 }
217
218 size_t bytes_written = 0;
219 if (this->state_ == speaker::STATE_RUNNING) {
220 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
221 if (temp_ring_buffer != nullptr) {
222 // The weak_ptr locks successfully only while the speaker task owns the ring buffer, so it is safe to write
223 bytes_written = temp_ring_buffer->write_without_replacement((void *) data, length, ticks_to_wait);
224 }
225 }
226
227 return bytes_written;
228}
229
231 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
232 if (temp_ring_buffer != nullptr) {
233 return temp_ring_buffer->available() > 0;
234 }
235 return false;
236}
237
238void I2SAudioSpeakerBase::speaker_task(void *params) {
239 I2SAudioSpeakerBase *this_speaker = (I2SAudioSpeakerBase *) params;
240 this_speaker->run_speaker_task();
241}
242
244 if (!this->is_ready() || this->is_failed() || this->status_has_error())
245 return;
246 if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING))
247 return;
248
249 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
250}
251
252void I2SAudioSpeakerBase::stop() { this->stop_(false); }
253
254void I2SAudioSpeakerBase::finish() { this->stop_(true); }
255
256void I2SAudioSpeakerBase::stop_(bool wait_on_empty) {
257 if (!this->is_ready() || this->is_failed())
258 return;
259
260 // Always set the bit, even when stopped, so loop() can cancel a start that is still pending
261 if (wait_on_empty) {
263 } else {
264 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_STOP);
265 }
266}
267
268esp_err_t I2SAudioSpeakerBase::init_i2s_channel_(const i2s_chan_config_t &chan_cfg, const i2s_std_config_t &std_cfg,
269 size_t event_queue_size) {
270 esp_err_t err = i2s_new_channel(&chan_cfg, &this->tx_handle_, NULL);
271 if (err != ESP_OK) {
272 ESP_LOGE(TAG, "I2S channel allocation failed: %s", esp_err_to_name(err));
273 this->parent_->unlock();
274 return err;
275 }
276
277 err = i2s_channel_init_std_mode(this->tx_handle_, &std_cfg);
278 if (err != ESP_OK) {
279 ESP_LOGE(TAG, "Failed to initialize I2S channel");
280 i2s_del_channel(this->tx_handle_);
281 this->tx_handle_ = nullptr;
282 this->parent_->unlock();
283 return err;
284 }
285
286 if (this->i2s_event_queue_ == nullptr) {
287 this->i2s_event_queue_ = xQueueCreate(event_queue_size, sizeof(int64_t));
288 } else {
289 // Reset queue to clear any stale events from previous task
290 xQueueReset(this->i2s_event_queue_);
291 }
292
293 // Lockstep records queue. One record per in-flight DMA buffer; sized to match the I2S event queue
294 // so a fully-saturated DMA pipeline cannot overflow either side before drain.
295 if (this->write_records_queue_ == nullptr) {
296 this->write_records_queue_ = xQueueCreate(event_queue_size, sizeof(uint32_t));
297 } else {
298 xQueueReset(this->write_records_queue_);
299 }
300
301 if (this->i2s_event_queue_ == nullptr || this->write_records_queue_ == nullptr) {
302 ESP_LOGE(TAG, "Failed to allocate I2S event queue(s)");
303 i2s_del_channel(this->tx_handle_);
304 this->tx_handle_ = nullptr;
305 this->parent_->unlock();
306 return ESP_ERR_NO_MEM;
307 }
308
309 return ESP_OK;
310}
311
313 if (this->tx_handle_ != nullptr) {
314 i2s_channel_disable(this->tx_handle_);
315 i2s_del_channel(this->tx_handle_);
316 this->tx_handle_ = nullptr;
317
318 // i2s_del_channel() leaves dout wired to this port's data-out signal in the GPIO matrix: it only
319 // clears an internal reservation mask, never the esp_rom_gpio_connect_out_signal() routing that
320 // setup installed. If another speaker reuses this port (shared bus), its audio still reaches our
321 // dout. Detach the pin and drive it low so a stale output stops driving downstream hardware: a
322 // SPDIF optical transmitter would otherwise stay lit, and an analog DAC would emit noise.
323 gpio_reset_pin(this->dout_pin_);
324 gpio_set_direction(this->dout_pin_, GPIO_MODE_OUTPUT);
325 gpio_set_level(this->dout_pin_, 0);
326 }
327 this->parent_->unlock();
328}
329
330bool IRAM_ATTR I2SAudioSpeakerBase::i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) {
331 int64_t now = esp_timer_get_time();
332
333 BaseType_t need_yield1 = pdFALSE;
334 BaseType_t need_yield2 = pdFALSE;
335 BaseType_t need_yield3 = pdFALSE;
336
337 I2SAudioSpeakerBase *this_speaker = (I2SAudioSpeakerBase *) user_ctx;
338
339 if (xQueueIsQueueFullFromISR(this_speaker->i2s_event_queue_)) {
340 // Queue is full, so discard the oldest event. Once we drop a completion event, ``i2s_event_queue_``
341 // and any per-buffer record queue maintained by the task are permanently desynced, so the task
342 // must restart to recover. Set both ERR_DROPPED_EVENT (so loop() can log it) and COMMAND_STOP
343 // (so the task bails immediately, closing the race where loop() could clear the error bit
344 // before the task observes it).
345 int64_t dummy;
346 xQueueReceiveFromISR(this_speaker->i2s_event_queue_, &dummy, &need_yield1);
347 xEventGroupSetBitsFromISR(this_speaker->event_group_,
349 &need_yield2);
350 }
351
352 xQueueSendToBackFromISR(this_speaker->i2s_event_queue_, &now, &need_yield3);
353
354 return need_yield1 | need_yield2 | need_yield3;
355}
356
357void I2SAudioSpeakerBase::apply_software_volume_(uint8_t *data, size_t bytes_read) {
358 if (this->q31_volume_factor_ == INT32_MAX) {
359 return; // Max volume, no processing needed
360 }
361
362 const size_t bytes_per_sample = this->current_stream_info_.samples_to_bytes(1);
363 const uint32_t len = bytes_read / bytes_per_sample;
364
365 esp_audio_libs::gain::apply(data, data, this->q31_volume_factor_, len, bytes_per_sample);
366}
367
368void I2SAudioSpeakerBase::swap_esp32_mono_samples_(uint8_t *data, size_t bytes_read) {
369#ifdef USE_ESP32_VARIANT_ESP32
370 // For ESP32 16-bit mono mode, adjacent samples need to be swapped.
371 if (this->output_stream_info_.get_channels() == 1 && this->output_stream_info_.get_bits_per_sample() == 16) {
372 int16_t *samples = reinterpret_cast<int16_t *>(data);
373 size_t sample_count = bytes_read / sizeof(int16_t);
374 for (size_t i = 0; i + 1 < sample_count; i += 2) {
375 int16_t tmp = samples[i];
376 samples[i] = samples[i + 1];
377 samples[i + 1] = tmp;
378 }
379 }
380#endif // USE_ESP32_VARIANT_ESP32
381}
382
383} // namespace esphome::i2s_audio
384
385#endif // USE_ESP32
void mark_failed()
Mark this component as failed.
void status_momentary_error(const char *name, uint32_t length=5000)
Set error status flag and automatically clear it after a timeout.
bool is_failed() const
Definition component.h:272
void status_clear_error()
Definition component.h:295
bool is_ready() const
bool status_has_error() const
Definition component.h:280
size_t samples_to_bytes(uint32_t samples) const
Converts samples to bytes.
Definition audio.h:58
uint8_t get_channels() const
Definition audio.h:29
virtual bool set_mute_off()=0
virtual bool set_volume(float volume)=0
virtual bool set_mute_on()=0
Abstract base class for I2S audio speaker implementations.
static bool i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
Callback function used to send playback timestamps to the speaker task.
void stop_i2s_driver_()
Stops the I2S driver and unlocks the I2S port.
void apply_software_volume_(uint8_t *data, size_t bytes_read)
Apply software volume control using Q15 fixed-point scaling.
std::weak_ptr< ring_buffer::RingBuffer > audio_ring_buffer_
void swap_esp32_mono_samples_(uint8_t *data, size_t bytes_read)
Swap adjacent 16-bit mono samples for ESP32 (non-variant) hardware quirk.
virtual esp_err_t start_i2s_driver(audio::AudioStreamInfo &audio_stream_info)=0
Starts the ESP32 I2S driver.
esp_err_t init_i2s_channel_(const i2s_chan_config_t &chan_cfg, const i2s_std_config_t &std_cfg, size_t event_queue_size)
Shared I2S channel allocation, initialization, and event queue setup.
virtual void on_task_stopped()
Called in loop() when the task has stopped. Override for mode-specific cleanup.
void stop_(bool wait_on_empty)
Plays the provided audio data.
virtual size_t play(const uint8_t *data, size_t length)=0
Plays the provided audio data.
virtual void set_volume(float volume)
Definition speaker.h:70
audio_dac::AudioDac * audio_dac_
Definition speaker.h:119
virtual void set_mute_state(bool mute_state)
Definition speaker.h:80
virtual bool has_buffered_data() const =0
audio::AudioStreamInfo audio_stream_info_
Definition speaker.h:114
const void size_t len
Definition hal.h:64
T remap(U value, U min, U max, T min_out, T max_out)
Remap value from the range (min, max) to (min_out, max_out).
Definition helpers.h:821
int64_t esp_timer_get_time(void)
static void uint32_t
uint16_t length
Definition tt21100.cpp:0
spi_device_handle_t handle