5#include <driver/gpio.h>
6#include <driver/i2s_std.h>
22static const char *
const TAG =
"i2s_audio.speaker";
26static constexpr float SOFTWARE_VOLUME_MIN_DB = -49.0f;
32 ESP_LOGE(TAG,
"Event group creation failed");
46 " Buffer duration: %" PRIu32,
49 ESP_LOGCONFIG(TAG,
" Timeout: %" PRIu32
" ms", this->
timeout_.value());
58 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::COMMAND_START);
63 ESP_LOGD(TAG,
"Starting");
64 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_STARTING);
67 ESP_LOGV(TAG,
"Started");
68 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_RUNNING);
72 ESP_LOGV(TAG,
"Stopping");
76 ESP_LOGE(TAG,
"ISR event queue overflow, restarting speaker task to recover timestamp sync");
79 ESP_LOGE(TAG,
"Partial DMA write broke buffer alignment, restarting speaker task");
82 ESP_LOGE(TAG,
"Event/record queues desynced, restarting speaker task");
84 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_STOPPING);
88 ESP_LOGD(TAG,
"Stopped");
103 ESP_LOGE(TAG,
"Speaker task setup failed (allocation, preload, or channel enable)");
104 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
115 ESP_LOGE(TAG,
"Driver failed to start; retrying in 1 second");
121 xTaskCreate(I2SAudioSpeakerBase::speaker_task,
"speaker_task", TASK_STACK_SIZE, (
void *)
this, TASK_PRIORITY,
125 ESP_LOGE(TAG,
"Task failed to start, retrying in 1 second");
152 if (volume >= 1.0f) {
154 }
else if (volume <= 0.0f) {
158 esp_audio_libs::gain::db_to_q31(
remap<float, float>(volume, 0.0f, 1.0f, SOFTWARE_VOLUME_MIN_DB, 0.0f));
187 ESP_LOGE(TAG,
"Setup failed; cannot play audio");
197 vTaskDelay(ticks_to_wait);
201 size_t bytes_written = 0;
203 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
audio_ring_buffer_.lock();
204 if (temp_ring_buffer !=
nullptr) {
206 bytes_written = temp_ring_buffer->write_without_replacement((
void *) data,
length, ticks_to_wait);
210 return bytes_written;
215 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
audio_ring_buffer_.lock();
216 return temp_ring_buffer->available() > 0;
221void I2SAudioSpeakerBase::speaker_task(
void *params) {
222 I2SAudioSpeakerBase *this_speaker = (I2SAudioSpeakerBase *) params;
223 this_speaker->run_speaker_task();
255 size_t event_queue_size) {
256 esp_err_t err = i2s_new_channel(&chan_cfg, &this->
tx_handle_, NULL);
258 ESP_LOGE(TAG,
"I2S channel allocation failed: %s", esp_err_to_name(err));
263 err = i2s_channel_init_std_mode(this->
tx_handle_, &std_cfg);
265 ESP_LOGE(TAG,
"Failed to initialize I2S channel");
288 ESP_LOGE(TAG,
"Failed to allocate I2S event queue(s)");
292 return ESP_ERR_NO_MEM;
310 gpio_set_direction(this->
dout_pin_, GPIO_MODE_OUTPUT);
319 BaseType_t need_yield1 = pdFALSE;
320 BaseType_t need_yield2 = pdFALSE;
321 BaseType_t need_yield3 = pdFALSE;
338 xQueueSendToBackFromISR(this_speaker->
i2s_event_queue_, &now, &need_yield3);
340 return need_yield1 | need_yield2 | need_yield3;
349 const uint32_t len = bytes_read / bytes_per_sample;
351 esp_audio_libs::gain::apply(data, data, this->
q31_volume_factor_, len, bytes_per_sample);
355#ifdef USE_ESP32_VARIANT_ESP32
358 int16_t *samples =
reinterpret_cast<int16_t *
>(data);
359 size_t sample_count = bytes_read /
sizeof(int16_t);
360 for (
size_t i = 0; i + 1 < sample_count; i += 2) {
361 int16_t tmp = samples[i];
362 samples[i] = samples[i + 1];
363 samples[i + 1] = tmp;
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.
void status_clear_error()
bool status_has_error() const
I2SAudioComponent * parent_
size_t samples_to_bytes(uint32_t samples) const
Converts samples to bytes.
uint8_t get_channels() const
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.
uint32_t buffer_duration_ms_
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.
i2s_chan_handle_t tx_handle_
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_
TaskHandle_t speaker_task_handle_
void swap_esp32_mono_samples_(uint8_t *data, size_t bytes_read)
Swap adjacent 16-bit mono samples for ESP32 (non-variant) hardware quirk.
audio::AudioStreamInfo current_stream_info_
void dump_config() override
optional< uint32_t > timeout_
int32_t q31_volume_factor_
QueueHandle_t write_records_queue_
virtual esp_err_t start_i2s_driver(audio::AudioStreamInfo &audio_stream_info)=0
Starts the ESP32 I2S driver.
QueueHandle_t i2s_event_queue_
EventGroupHandle_t event_group_
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)
audio_dac::AudioDac * audio_dac_
virtual void set_mute_state(bool mute_state)
virtual bool has_buffered_data() const =0
audio::AudioStreamInfo audio_stream_info_
@ COMMAND_STOP_GRACEFULLY
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).
int64_t esp_timer_get_time(void)
spi_device_handle_t handle