5#include <driver/gpio.h>
6#include <driver/i2s_std.h>
21static const char *
const TAG =
"i2s_audio.speaker";
25static constexpr float SOFTWARE_VOLUME_MIN_DB = -49.0f;
28static constexpr uint32_t GAIN_RAMP_MS_PER_DB = 1;
34 ESP_LOGE(TAG,
"Event group creation failed");
46 this->park_dout_pin_();
49void I2SAudioSpeakerBase::park_dout_pin_() {
51 gpio_set_direction(this->
dout_pin_, GPIO_MODE_OUTPUT);
59 " Buffer duration: %" PRIu32,
62 ESP_LOGCONFIG(TAG,
" Timeout: %" PRIu32
" ms", this->
timeout_.value());
78 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::COMMAND_START);
83 ESP_LOGD(TAG,
"Starting");
84 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_STARTING);
87 ESP_LOGV(TAG,
"Started");
88 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_RUNNING);
98 ESP_LOGV(TAG,
"Stopping");
99 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::TASK_STOPPING);
103 ESP_LOGD(TAG,
"Stopped");
108 this->stop_i2s_driver_();
114 ESP_LOGD(TAG,
"Start requested while stopping; keeping the request");
115 xEventGroupSetBits(this->
event_group_, SpeakerEventGroupBits::COMMAND_START);
119 this->on_task_stopped();
125 ESP_LOGE(TAG,
"Speaker task setup failed (allocation, preload, or channel enable)");
126 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
142 ESP_LOGE(TAG,
"Driver failed to start; retrying in 1 second");
150 this->post_software_gain_(0);
151 xTaskCreate(I2SAudioSpeakerBase::speaker_task,
"speaker_task", TASK_STACK_SIZE, (
void *)
this, TASK_PRIORITY,
155 ESP_LOGE(TAG,
"Task failed to start, retrying in 1 second");
157 this->stop_i2s_driver_();
177void I2SAudioSpeakerBase::post_software_gain_(
uint32_t rate_samples) {
186 target_db = -INFINITY;
187 }
else if (this->
volume_ >= 1.0f) {
192 this->
gain_ramp_.set_target_db_at_rate(target_db, rate_samples);
197 ESP_LOGE(TAG,
"Setup failed; cannot play audio");
207 vTaskDelay(ticks_to_wait);
211 size_t bytes_written = 0;
213 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
audio_ring_buffer_.lock();
214 if (temp_ring_buffer !=
nullptr) {
216 bytes_written = temp_ring_buffer->write_without_replacement((
void *) data,
length, ticks_to_wait);
220 return bytes_written;
224 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
audio_ring_buffer_.lock();
225 if (temp_ring_buffer !=
nullptr) {
226 return temp_ring_buffer->available() > 0;
231void I2SAudioSpeakerBase::speaker_task(
void *params) {
232 I2SAudioSpeakerBase *this_speaker = (I2SAudioSpeakerBase *) params;
233 this_speaker->run_speaker_task();
249void I2SAudioSpeakerBase::stop_(
bool wait_on_empty) {
261esp_err_t I2SAudioSpeakerBase::init_i2s_channel_(
const i2s_chan_config_t &chan_cfg,
const i2s_std_config_t &std_cfg,
262 size_t event_queue_size) {
263 esp_err_t err = i2s_new_channel(&chan_cfg, &this->
tx_handle_, NULL);
265 ESP_LOGE(TAG,
"I2S channel allocation failed: %s", esp_err_to_name(err));
270 err = i2s_channel_init_std_mode(this->
tx_handle_, &std_cfg);
272 ESP_LOGE(TAG,
"Failed to initialize I2S channel");
295 ESP_LOGE(TAG,
"Failed to allocate I2S event queue(s)");
299 return ESP_ERR_NO_MEM;
305void I2SAudioSpeakerBase::stop_i2s_driver_() {
316 this->park_dout_pin_();
321bool IRAM_ATTR I2SAudioSpeakerBase::i2s_on_sent_cb(i2s_chan_handle_t
handle, i2s_event_data_t *event,
void *user_ctx) {
324 BaseType_t need_yield1 = pdFALSE;
325 BaseType_t need_yield2 = pdFALSE;
326 BaseType_t need_yield3 = pdFALSE;
328 I2SAudioSpeakerBase *this_speaker = (I2SAudioSpeakerBase *) user_ctx;
330 if (xQueueIsQueueFullFromISR(this_speaker->i2s_event_queue_)) {
333 xQueueReceiveFromISR(this_speaker->i2s_event_queue_, &dummy, &need_yield1);
337 xQueueSendToBackFromISR(this_speaker->i2s_event_queue_, &now, &need_yield3);
339 return need_yield1 | need_yield2 | need_yield3;
350 frames += record_frames;
355 ESP_LOGV(TAG,
"Crediting %" PRIu32
" dropped frames as played", frames);
367 this->
gain_ramp_.process(data,
static_cast<uint8_t
>(bytes_per_sample),
372#ifdef USE_ESP32_VARIANT_ESP32
375 int16_t *samples =
reinterpret_cast<int16_t *
>(data);
376 size_t sample_count = bytes_read /
sizeof(int16_t);
377 for (
size_t i = 0; i + 1 < sample_count; i += 2) {
378 int16_t tmp = samples[i];
379 samples[i] = samples[i + 1];
380 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_
uint32_t ms_to_samples(uint32_t ms) const
Converts duration to samples.
size_t samples_to_bytes(uint32_t samples) const
Converts samples to bytes.
uint8_t get_channels() const
uint32_t bytes_to_samples(size_t bytes) const
Convert bytes to samples.
virtual void on_audio_started()
Called from the main loop by the speaker driving this DAC once its audio clocks are running.
uint32_t buffer_duration_ms_
void drain_lockstep_(uint32_t extra_frames)
Plays the provided audio data.
esp_audio_libs::gain::GainRamp gain_ramp_
audio::AudioStreamInfo output_stream_info_
i2s_chan_handle_t tx_handle_
void apply_software_volume_(uint8_t *data, size_t bytes_read)
Apply software volume control by running the samples through the gain ramp.
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_
QueueHandle_t write_records_queue_
QueueHandle_t i2s_event_queue_
EventGroupHandle_t event_group_
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_
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
virtual void set_mute_state(bool mute_state)
virtual bool has_buffered_data() const =0
audio::AudioStreamInfo audio_stream_info_
bool is_silent_() const
Whether the output should be silent: muted, or the volume is effectively zero.
@ 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