ESPHome  2024.9.0
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/i2s.h>
6 
8 #include "esphome/core/hal.h"
9 #include "esphome/core/log.h"
10 
11 namespace esphome {
12 namespace i2s_audio {
13 
14 static const size_t BUFFER_COUNT = 20;
15 
16 static const char *const TAG = "i2s_audio.speaker";
17 
19  ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
20 
21  this->buffer_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(DataEvent));
22  if (this->buffer_queue_ == nullptr) {
23  ESP_LOGE(TAG, "Failed to create buffer queue");
24  this->mark_failed();
25  return;
26  }
27 
28  this->event_queue_ = xQueueCreate(BUFFER_COUNT, sizeof(TaskEvent));
29  if (this->event_queue_ == nullptr) {
30  ESP_LOGE(TAG, "Failed to create event queue");
31  this->mark_failed();
32  return;
33  }
34 }
35 
37  if (this->is_failed()) {
38  ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
39  return;
40  }
41  if (this->task_created_) {
42  ESP_LOGW(TAG, "Called start while task has been already created.");
43  return;
44  }
46 }
48  if (this->task_created_) {
49  return;
50  }
51  if (!this->parent_->try_lock()) {
52  return; // Waiting for another i2s component to return lock
53  }
54 
55  xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_);
56  this->task_created_ = true;
57 }
58 
59 template<typename a, typename b> const uint8_t *convert_data_format(const a *from, b *to, size_t &bytes, bool repeat) {
60  if (sizeof(a) == sizeof(b) && !repeat) {
61  return reinterpret_cast<const uint8_t *>(from);
62  }
63  const b *result = to;
64  for (size_t i = 0; i < bytes; i += sizeof(a)) {
65  b value = static_cast<b>(*from++) << (sizeof(b) - sizeof(a)) * 8;
66  *to++ = value;
67  if (repeat)
68  *to++ = value;
69  }
70  bytes *= (sizeof(b) / sizeof(a)) * (repeat ? 2 : 1); // NOLINT
71  return reinterpret_cast<const uint8_t *>(result);
72 }
73 
74 void I2SAudioSpeaker::player_task(void *params) {
75  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
76 
77  TaskEvent event;
79  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
80 
81  i2s_driver_config_t config = {
82  .mode = (i2s_mode_t) (this_speaker->i2s_mode_ | I2S_MODE_TX),
83  .sample_rate = this_speaker->sample_rate_,
84  .bits_per_sample = this_speaker->bits_per_sample_,
85  .channel_format = this_speaker->channel_,
86  .communication_format = I2S_COMM_FORMAT_STAND_I2S,
87  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
88  .dma_buf_count = 8,
89  .dma_buf_len = 256,
90  .use_apll = this_speaker->use_apll_,
91  .tx_desc_auto_clear = true,
92  .fixed_mclk = 0,
93  .mclk_multiple = I2S_MCLK_MULTIPLE_256,
94  .bits_per_chan = this_speaker->bits_per_channel_,
95  };
96 #if SOC_I2S_SUPPORTS_DAC
97  if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
98  config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
99  }
100 #endif
101 
102  esp_err_t err = i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr);
103  if (err != ESP_OK) {
104  event.type = TaskEventType::WARNING;
105  event.err = err;
106  xQueueSend(this_speaker->event_queue_, &event, 0);
107  event.type = TaskEventType::STOPPED;
108  xQueueSend(this_speaker->event_queue_, &event, 0);
109  while (true) {
110  delay(10);
111  }
112  }
113 
114 #if SOC_I2S_SUPPORTS_DAC
115  if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
116 #endif
117  i2s_pin_config_t pin_config = this_speaker->parent_->get_pin_config();
118  pin_config.data_out_num = this_speaker->dout_pin_;
119 
120  i2s_set_pin(this_speaker->parent_->get_port(), &pin_config);
121 #if SOC_I2S_SUPPORTS_DAC
122  } else {
123  i2s_set_dac_mode(this_speaker->internal_dac_mode_);
124  }
125 #endif
126 
127  DataEvent data_event;
128 
129  event.type = TaskEventType::STARTED;
130  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
131 
132  int32_t buffer[BUFFER_SIZE];
133 
134  while (true) {
135  if (xQueueReceive(this_speaker->buffer_queue_, &data_event, this_speaker->timeout_ / portTICK_PERIOD_MS) !=
136  pdTRUE) {
137  break; // End of audio from main thread
138  }
139  if (data_event.stop) {
140  // Stop signal from main thread
141  xQueueReset(this_speaker->buffer_queue_); // Flush queue
142  break;
143  }
144 
145  const uint8_t *data = data_event.data;
146  size_t remaining = data_event.len;
147  switch (this_speaker->bits_per_sample_) {
148  case I2S_BITS_PER_SAMPLE_8BIT:
149  case I2S_BITS_PER_SAMPLE_16BIT: {
150  data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int16_t *>(buffer),
151  remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
152  break;
153  }
154  case I2S_BITS_PER_SAMPLE_24BIT:
155  case I2S_BITS_PER_SAMPLE_32BIT: {
156  data = convert_data_format(reinterpret_cast<const int16_t *>(data), reinterpret_cast<int32_t *>(buffer),
157  remaining, this_speaker->channel_ == I2S_CHANNEL_FMT_ALL_LEFT);
158  break;
159  }
160  }
161 
162  while (remaining != 0) {
163  size_t bytes_written;
164  esp_err_t err =
165  i2s_write(this_speaker->parent_->get_port(), data, remaining, &bytes_written, (32 / portTICK_PERIOD_MS));
166  if (err != ESP_OK) {
167  event = {.type = TaskEventType::WARNING, .err = err};
168  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
169  ESP_LOGW(TAG, "Failed to send WARNING event");
170  }
171  continue;
172  }
173  data += bytes_written;
174  remaining -= bytes_written;
175  }
176  }
177 
178  event.type = TaskEventType::STOPPING;
179  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
180  ESP_LOGW(TAG, "Failed to send STOPPING event");
181  }
182 
183  i2s_zero_dma_buffer(this_speaker->parent_->get_port());
184 
185  i2s_driver_uninstall(this_speaker->parent_->get_port());
186 
187  event.type = TaskEventType::STOPPED;
188  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
189  ESP_LOGW(TAG, "Failed to send STOPPED event");
190  }
191 
192  while (true) {
193  delay(10);
194  }
195 }
196 
197 void I2SAudioSpeaker::stop() { this->stop_(false); }
198 
199 void I2SAudioSpeaker::finish() { this->stop_(true); }
200 
201 void I2SAudioSpeaker::stop_(bool wait_on_empty) {
202  if (this->is_failed())
203  return;
204  if (this->state_ == speaker::STATE_STOPPED)
205  return;
206  if (this->state_ == speaker::STATE_STARTING) {
208  return;
209  }
211  DataEvent data;
212  data.stop = true;
213  if (wait_on_empty) {
214  xQueueSend(this->buffer_queue_, &data, portMAX_DELAY);
215  } else {
216  xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
217  }
218 }
219 
221  TaskEvent event;
222  if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
223  switch (event.type) {
225  ESP_LOGD(TAG, "Starting I2S Audio Speaker");
226  break;
228  ESP_LOGD(TAG, "Started I2S Audio Speaker");
230  this->status_clear_warning();
231  break;
233  ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
234  break;
237  vTaskDelete(this->player_task_handle_);
238  this->task_created_ = false;
239  this->player_task_handle_ = nullptr;
240  this->parent_->unlock();
241  xQueueReset(this->buffer_queue_);
242  ESP_LOGD(TAG, "Stopped I2S Audio Speaker");
243  break;
245  ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err));
246  this->status_set_warning();
247  break;
248  }
249  }
250 }
251 
253  switch (this->state_) {
255  this->start_();
256  [[fallthrough]];
259  this->watch_();
260  break;
262  break;
263  }
264 }
265 
266 size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) {
267  if (this->is_failed()) {
268  ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup");
269  return 0;
270  }
271  if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
272  this->start();
273  }
274  size_t remaining = length;
275  size_t index = 0;
276  while (remaining > 0) {
277  DataEvent event;
278  event.stop = false;
279  size_t to_send_length = std::min(remaining, BUFFER_SIZE);
280  event.len = to_send_length;
281  memcpy(event.data, data + index, to_send_length);
282  if (xQueueSend(this->buffer_queue_, &event, 0) != pdTRUE) {
283  return index;
284  }
285  remaining -= to_send_length;
286  index += to_send_length;
287  }
288  return index;
289 }
290 
291 bool I2SAudioSpeaker::has_buffered_data() const { return uxQueueMessagesWaiting(this->buffer_queue_) > 0; }
292 
293 } // namespace i2s_audio
294 } // namespace esphome
295 
296 #endif // USE_ESP32
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
bool is_failed() const
Definition: component.cpp:143
static void player_task(void *params)
i2s_channel_fmt_t channel_
Definition: i2s_audio.h:25
void status_clear_warning()
Definition: component.cpp:166
const uint8_t * convert_data_format(const a *from, b *to, size_t &bytes, bool repeat)
size_t play(const uint8_t *data, size_t length) override
i2s_bits_per_sample_t bits_per_sample_
Definition: i2s_audio.h:27
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
uint16_t length
Definition: tt21100.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
std::vector< uint8_t > bytes
Definition: sml_parser.h:12
i2s_bits_per_chan_t bits_per_channel_
Definition: i2s_audio.h:28
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26