ESPHome  2024.7.2
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 void I2SAudioSpeaker::player_task(void *params) {
60  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
61 
62  TaskEvent event;
64  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
65 
66  i2s_driver_config_t config = {
67  .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_TX),
68  .sample_rate = 16000,
69  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
70  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
71  .communication_format = I2S_COMM_FORMAT_STAND_I2S,
72  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
73  .dma_buf_count = 8,
74  .dma_buf_len = 128,
75  .use_apll = false,
76  .tx_desc_auto_clear = true,
77  .fixed_mclk = I2S_PIN_NO_CHANGE,
78  .mclk_multiple = I2S_MCLK_MULTIPLE_256,
79  .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
80  };
81 #if SOC_I2S_SUPPORTS_DAC
82  if (this_speaker->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
83  config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
84  }
85 #endif
86 
87  esp_err_t err = i2s_driver_install(this_speaker->parent_->get_port(), &config, 0, nullptr);
88  if (err != ESP_OK) {
89  event.type = TaskEventType::WARNING;
90  event.err = err;
91  xQueueSend(this_speaker->event_queue_, &event, 0);
92  event.type = TaskEventType::STOPPED;
93  xQueueSend(this_speaker->event_queue_, &event, 0);
94  while (true) {
95  delay(10);
96  }
97  }
98 
99 #if SOC_I2S_SUPPORTS_DAC
100  if (this_speaker->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
101 #endif
102  i2s_pin_config_t pin_config = this_speaker->parent_->get_pin_config();
103  pin_config.data_out_num = this_speaker->dout_pin_;
104 
105  i2s_set_pin(this_speaker->parent_->get_port(), &pin_config);
106 #if SOC_I2S_SUPPORTS_DAC
107  } else {
108  i2s_set_dac_mode(this_speaker->internal_dac_mode_);
109  }
110 #endif
111 
112  DataEvent data_event;
113 
114  event.type = TaskEventType::STARTED;
115  xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
116 
117  int16_t buffer[BUFFER_SIZE / 2];
118 
119  while (true) {
120  if (xQueueReceive(this_speaker->buffer_queue_, &data_event, 100 / portTICK_PERIOD_MS) != pdTRUE) {
121  break; // End of audio from main thread
122  }
123  if (data_event.stop) {
124  // Stop signal from main thread
125  xQueueReset(this_speaker->buffer_queue_); // Flush queue
126  break;
127  }
128  size_t bytes_written;
129 
130  memmove(buffer, data_event.data, data_event.len);
131  size_t remaining = data_event.len / 2;
132  size_t current = 0;
133 
134  while (remaining > 0) {
135  uint32_t sample = (buffer[current] << 16) | (buffer[current] & 0xFFFF);
136 
137  esp_err_t err = i2s_write(this_speaker->parent_->get_port(), &sample, sizeof(sample), &bytes_written,
138  (10 / portTICK_PERIOD_MS));
139  if (err != ESP_OK) {
140  event = {.type = TaskEventType::WARNING, .err = err};
141  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
142  ESP_LOGW(TAG, "Failed to send WARNING event");
143  }
144  continue;
145  }
146  if (bytes_written != sizeof(sample)) {
147  event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
148  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
149  ESP_LOGW(TAG, "Failed to send WARNING event");
150  }
151  continue;
152  }
153  remaining--;
154  current++;
155  }
156 
157  event.type = TaskEventType::PLAYING;
158  event.err = current;
159  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
160  ESP_LOGW(TAG, "Failed to send PLAYING event");
161  }
162  }
163 
164  event.type = TaskEventType::STOPPING;
165  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
166  ESP_LOGW(TAG, "Failed to send STOPPING event");
167  }
168 
169  i2s_zero_dma_buffer(this_speaker->parent_->get_port());
170 
171  i2s_driver_uninstall(this_speaker->parent_->get_port());
172 
173  event.type = TaskEventType::STOPPED;
174  if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
175  ESP_LOGW(TAG, "Failed to send STOPPED event");
176  }
177 
178  while (true) {
179  delay(10);
180  }
181 }
182 
184  if (this->is_failed())
185  return;
186  if (this->state_ == speaker::STATE_STOPPED)
187  return;
188  if (this->state_ == speaker::STATE_STARTING) {
190  return;
191  }
193  DataEvent data;
194  data.stop = true;
195  xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
196 }
197 
199  TaskEvent event;
200  if (xQueueReceive(this->event_queue_, &event, 0) == pdTRUE) {
201  switch (event.type) {
203  ESP_LOGD(TAG, "Starting I2S Audio Speaker");
204  break;
206  ESP_LOGD(TAG, "Started I2S Audio Speaker");
208  break;
210  ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
211  break;
213  this->status_clear_warning();
214  break;
217  vTaskDelete(this->player_task_handle_);
218  this->task_created_ = false;
219  this->player_task_handle_ = nullptr;
220  this->parent_->unlock();
221  xQueueReset(this->buffer_queue_);
222  ESP_LOGD(TAG, "Stopped I2S Audio Speaker");
223  break;
225  ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(event.err));
226  this->status_set_warning();
227  break;
228  }
229  }
230 }
231 
233  switch (this->state_) {
235  this->start_();
238  this->watch_();
239  break;
241  break;
242  }
243 }
244 
245 size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length) {
246  if (this->is_failed()) {
247  ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup");
248  return 0;
249  }
250  if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
251  this->start();
252  }
253  size_t remaining = length;
254  size_t index = 0;
255  while (remaining > 0) {
256  DataEvent event;
257  event.stop = false;
258  size_t to_send_length = std::min(remaining, BUFFER_SIZE);
259  event.len = to_send_length;
260  memcpy(event.data, data + index, to_send_length);
261  if (xQueueSend(this->buffer_queue_, &event, 0) != pdTRUE) {
262  return index;
263  }
264  remaining -= to_send_length;
265  index += to_send_length;
266  }
267  return index;
268 }
269 
270 bool I2SAudioSpeaker::has_buffered_data() const { return uxQueueMessagesWaiting(this->buffer_queue_) > 0; }
271 
272 } // namespace i2s_audio
273 } // namespace esphome
274 
275 #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)
void status_clear_warning()
Definition: component.cpp:166
size_t play(const uint8_t *data, size_t length) override
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
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26