ESPHome  2024.12.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 
10 #include "esphome/core/hal.h"
11 #include "esphome/core/log.h"
12 
13 namespace esphome {
14 namespace i2s_audio {
15 
16 static const uint8_t DMA_BUFFER_DURATION_MS = 15;
17 static const size_t DMA_BUFFERS_COUNT = 4;
18 
19 static const size_t TASK_DELAY_MS = DMA_BUFFER_DURATION_MS * DMA_BUFFERS_COUNT / 2;
20 
21 static const size_t TASK_STACK_SIZE = 4096;
22 static const ssize_t TASK_PRIORITY = 23;
23 
24 static const size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT + 1;
25 
26 static const char *const TAG = "i2s_audio.speaker";
27 
28 enum SpeakerEventGroupBits : uint32_t {
29  COMMAND_START = (1 << 0), // starts the speaker task
30  COMMAND_STOP = (1 << 1), // stops the speaker task
31  COMMAND_STOP_GRACEFULLY = (1 << 2), // Stops the speaker task once all data has been written
32  STATE_STARTING = (1 << 10),
33  STATE_RUNNING = (1 << 11),
34  STATE_STOPPING = (1 << 12),
35  STATE_STOPPED = (1 << 13),
37  ERR_ESP_INVALID_STATE = (1 << 15),
38  ERR_ESP_NOT_SUPPORTED = (1 << 16),
39  ERR_ESP_INVALID_ARG = (1 << 17),
40  ERR_ESP_INVALID_SIZE = (1 << 18),
41  ERR_ESP_NO_MEM = (1 << 19),
42  ERR_ESP_FAIL = (1 << 20),
45  ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
46 };
47 
48 // Translates a SpeakerEventGroupBits ERR_ESP bit to the coressponding esp_err_t
49 static esp_err_t err_bit_to_esp_err(uint32_t bit) {
50  switch (bit) {
52  return ESP_ERR_INVALID_STATE;
54  return ESP_ERR_INVALID_ARG;
56  return ESP_ERR_INVALID_SIZE;
58  return ESP_ERR_NO_MEM;
60  return ESP_ERR_NOT_SUPPORTED;
61  default:
62  return ESP_FAIL;
63  }
64 }
65 
75 static void q15_multiplication(const int16_t *input, int16_t *output, size_t len, int16_t c) {
76  for (int i = 0; i < len; i++) {
77  int32_t acc = (int32_t) input[i] * (int32_t) c;
78  output[i] = (int16_t) (acc >> 15);
79  }
80 }
81 
82 // Lists the Q15 fixed point scaling factor for volume reduction.
83 // Has 100 values representing silence and a reduction [49, 48.5, ... 0.5, 0] dB.
84 // dB to PCM scaling factor formula: floating_point_scale_factor = 2^(-db/6.014)
85 // float to Q15 fixed point formula: q15_scale_factor = floating_point_scale_factor * 2^(15)
86 static const std::vector<int16_t> Q15_VOLUME_SCALING_FACTORS = {
87  0, 116, 122, 130, 137, 146, 154, 163, 173, 183, 194, 206, 218, 231, 244,
88  259, 274, 291, 308, 326, 345, 366, 388, 411, 435, 461, 488, 517, 548, 580,
89  615, 651, 690, 731, 774, 820, 868, 920, 974, 1032, 1094, 1158, 1227, 1300, 1377,
90  1459, 1545, 1637, 1734, 1837, 1946, 2061, 2184, 2313, 2450, 2596, 2750, 2913, 3085, 3269,
91  3462, 3668, 3885, 4116, 4360, 4619, 4893, 5183, 5490, 5816, 6161, 6527, 6914, 7324, 7758,
92  8218, 8706, 9222, 9770, 10349, 10963, 11613, 12302, 13032, 13805, 14624, 15491, 16410, 17384, 18415,
93  19508, 20665, 21891, 23189, 24565, 26022, 27566, 29201, 30933, 32767};
94 
96  ESP_LOGCONFIG(TAG, "Setting up I2S Audio Speaker...");
97 
98  this->event_group_ = xEventGroupCreate();
99 
100  if (this->event_group_ == nullptr) {
101  ESP_LOGE(TAG, "Failed to create event group");
102  this->mark_failed();
103  return;
104  }
105 }
106 
108  uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
109 
110  if (event_group_bits & SpeakerEventGroupBits::STATE_STARTING) {
111  ESP_LOGD(TAG, "Starting Speaker");
113  xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STARTING);
114  }
115  if (event_group_bits & SpeakerEventGroupBits::STATE_RUNNING) {
116  ESP_LOGD(TAG, "Started Speaker");
118  xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
119  this->status_clear_warning();
120  this->status_clear_error();
121  }
122  if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPING) {
123  ESP_LOGD(TAG, "Stopping Speaker");
125  xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
126  }
127  if (event_group_bits & SpeakerEventGroupBits::STATE_STOPPED) {
128  if (!this->task_created_) {
129  ESP_LOGD(TAG, "Stopped Speaker");
131  xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ALL_BITS);
132  this->speaker_task_handle_ = nullptr;
133  }
134  }
135 
136  if (event_group_bits & SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START) {
137  this->status_set_error("Failed to start speaker task");
138  xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
139  }
140 
141  if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
142  uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
143  ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
144  this->status_set_warning();
145  }
146 
147  if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
148  this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
149  ESP_LOGE(TAG,
150  "Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
151  this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels,
152  this->audio_stream_info_.bits_per_sample);
153  }
154 }
155 
156 void I2SAudioSpeaker::set_volume(float volume) {
157  this->volume_ = volume;
158 #ifdef USE_AUDIO_DAC
159  if (this->audio_dac_ != nullptr) {
160  if (volume > 0.0) {
161  this->audio_dac_->set_mute_off();
162  }
163  this->audio_dac_->set_volume(volume);
164  } else
165 #endif
166  {
167  // Fallback to software volume control by using a Q15 fixed point scaling factor
168  ssize_t decibel_index = remap<ssize_t, float>(volume, 0.0f, 1.0f, 0, Q15_VOLUME_SCALING_FACTORS.size() - 1);
169  this->q15_volume_factor_ = Q15_VOLUME_SCALING_FACTORS[decibel_index];
170  }
171 }
172 
173 void I2SAudioSpeaker::set_mute_state(bool mute_state) {
174  this->mute_state_ = mute_state;
175 #ifdef USE_AUDIO_DAC
176  if (this->audio_dac_) {
177  if (mute_state) {
178  this->audio_dac_->set_mute_on();
179  } else {
180  this->audio_dac_->set_mute_off();
181  }
182  } else
183 #endif
184  {
185  if (mute_state) {
186  // Fallback to software volume control and scale by 0
187  this->q15_volume_factor_ = 0;
188  } else {
189  // Revert to previous volume when unmuting
190  this->set_volume(this->volume_);
191  }
192  }
193 }
194 
195 size_t I2SAudioSpeaker::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
196  if (this->is_failed()) {
197  ESP_LOGE(TAG, "Cannot play audio, speaker failed to setup");
198  return 0;
199  }
200  if (this->state_ != speaker::STATE_RUNNING && this->state_ != speaker::STATE_STARTING) {
201  this->start();
202  }
203 
204  size_t bytes_written = 0;
205  if ((this->state_ == speaker::STATE_RUNNING) && (this->audio_ring_buffer_.use_count() == 1)) {
206  // Only one owner of the ring buffer (the speaker task), so the ring buffer is allocated and no other components are
207  // attempting to write to it.
208 
209  // Temporarily share ownership of the ring buffer so it won't be deallocated while writing
210  std::shared_ptr<RingBuffer> temp_ring_buffer = this->audio_ring_buffer_;
211  bytes_written = temp_ring_buffer->write_without_replacement((void *) data, length, ticks_to_wait);
212  }
213 
214  return bytes_written;
215 }
216 
218  if (this->audio_ring_buffer_ != nullptr) {
219  return this->audio_ring_buffer_->available() > 0;
220  }
221  return false;
222 }
223 
224 void I2SAudioSpeaker::speaker_task(void *params) {
225  I2SAudioSpeaker *this_speaker = (I2SAudioSpeaker *) params;
226  uint32_t event_group_bits =
227  xEventGroupWaitBits(this_speaker->event_group_,
229  SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY, // Bit message to read
230  pdTRUE, // Clear the bits on exit
231  pdFALSE, // Don't wait for all the bits,
232  portMAX_DELAY); // Block indefinitely until a bit is set
233 
235  // Received a stop signal before the task was requested to start
236  this_speaker->delete_task_(0);
237  }
238 
239  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
240 
241  audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_;
242 
243  const uint32_t bytes_per_ms =
244  audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000;
245 
246  const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms;
247 
248  // Ensure ring buffer is at least as large as the total size of the DMA buffers
249  const size_t ring_buffer_size =
250  std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
251 
252  if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
253  // Failed to allocate buffers
254  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
255  this_speaker->delete_task_(dma_buffers_size);
256  }
257 
258  if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
259  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
260 
261  bool stop_gracefully = false;
262  uint32_t last_data_received_time = millis();
263  bool tx_dma_underflow = false;
264 
265  while (!this_speaker->timeout_.has_value() ||
266  (millis() - last_data_received_time) <= this_speaker->timeout_.value()) {
267  event_group_bits = xEventGroupGetBits(this_speaker->event_group_);
268 
269  if (event_group_bits & SpeakerEventGroupBits::COMMAND_STOP) {
270  break;
271  }
272  if (event_group_bits & SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY) {
273  stop_gracefully = true;
274  }
275 
276  if (this_speaker->audio_stream_info_ != audio_stream_info) {
277  // Audio stream info has changed, stop the speaker task so it will restart with the proper settings.
278 
279  break;
280  }
281 
282  i2s_event_t i2s_event;
283  while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
284  if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
285  tx_dma_underflow = true;
286  }
287  }
288 
289  size_t bytes_to_read = dma_buffers_size;
290  size_t bytes_read = this_speaker->audio_ring_buffer_->read((void *) this_speaker->data_buffer_, bytes_to_read,
291  pdMS_TO_TICKS(TASK_DELAY_MS));
292 
293  if (bytes_read > 0) {
294  size_t bytes_written = 0;
295 
296  if ((audio_stream_info.bits_per_sample == 16) && (this_speaker->q15_volume_factor_ < INT16_MAX)) {
297  // Scale samples by the volume factor in place
298  q15_multiplication((int16_t *) this_speaker->data_buffer_, (int16_t *) this_speaker->data_buffer_,
299  bytes_read / sizeof(int16_t), this_speaker->q15_volume_factor_);
300  }
301 
302  if (audio_stream_info.bits_per_sample == (uint8_t) this_speaker->bits_per_sample_) {
303  i2s_write(this_speaker->parent_->get_port(), this_speaker->data_buffer_, bytes_read, &bytes_written,
304  portMAX_DELAY);
305  } else if (audio_stream_info.bits_per_sample < (uint8_t) this_speaker->bits_per_sample_) {
306  i2s_write_expand(this_speaker->parent_->get_port(), this_speaker->data_buffer_, bytes_read,
307  audio_stream_info.bits_per_sample, this_speaker->bits_per_sample_, &bytes_written,
308  portMAX_DELAY);
309  }
310 
311  if (bytes_written != bytes_read) {
312  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_ESP_INVALID_SIZE);
313  }
314  tx_dma_underflow = false;
315  last_data_received_time = millis();
316  } else {
317  // No data received
318  if (stop_gracefully && tx_dma_underflow) {
319  break;
320  }
321  }
322  }
323 
324  xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
325 
326  i2s_driver_uninstall(this_speaker->parent_->get_port());
327 
328  this_speaker->parent_->unlock();
329  }
330 
331  this_speaker->delete_task_(dma_buffers_size);
332 }
333 
335  if (!this->is_ready() || this->is_failed() || this->status_has_error())
336  return;
337  if ((this->state_ == speaker::STATE_STARTING) || (this->state_ == speaker::STATE_RUNNING))
338  return;
339 
340  if (this->speaker_task_handle_ == nullptr) {
341  xTaskCreate(I2SAudioSpeaker::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY,
342  &this->speaker_task_handle_);
343  }
344 
345  if (this->speaker_task_handle_ != nullptr) {
346  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_START);
347  this->task_created_ = true;
348  } else {
350  }
351 }
352 
353 void I2SAudioSpeaker::stop() { this->stop_(false); }
354 
355 void I2SAudioSpeaker::finish() { this->stop_(true); }
356 
357 void I2SAudioSpeaker::stop_(bool wait_on_empty) {
358  if (this->is_failed())
359  return;
360  if (this->state_ == speaker::STATE_STOPPED)
361  return;
362 
363  if (wait_on_empty) {
365  } else {
366  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::COMMAND_STOP);
367  }
368 }
369 
371  switch (err) {
372  case ESP_OK:
373  return false;
374  case ESP_ERR_INVALID_STATE:
376  return true;
377  case ESP_ERR_INVALID_ARG:
378  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_INVALID_ARG);
379  return true;
380  case ESP_ERR_INVALID_SIZE:
382  return true;
383  case ESP_ERR_NO_MEM:
384  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
385  return true;
386  case ESP_ERR_NOT_SUPPORTED:
388  return true;
389  default:
390  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
391  return true;
392  }
393 }
394 
395 esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size) {
396  if (this->data_buffer_ == nullptr) {
397  // Allocate data buffer for temporarily storing audio from the ring buffer before writing to the I2S bus
399  this->data_buffer_ = allocator.allocate(data_buffer_size);
400  }
401 
402  if (this->data_buffer_ == nullptr) {
403  return ESP_ERR_NO_MEM;
404  }
405 
406  if (this->audio_ring_buffer_.use_count() == 0) {
407  // Allocate ring buffer. Uses a shared_ptr to ensure it isn't improperly deallocated.
408  this->audio_ring_buffer_ = RingBuffer::create(ring_buffer_size);
409  }
410 
411  if (this->audio_ring_buffer_ == nullptr) {
412  return ESP_ERR_NO_MEM;
413  }
414 
415  return ESP_OK;
416 }
417 
419  if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT
420  // Can't reconfigure I2S bus, so the sample rate must match the configured value
421  return ESP_ERR_NOT_SUPPORTED;
422  }
423 
424  if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
425  // Currently can't handle the case when the incoming audio has more bits per sample than the configured value
426  return ESP_ERR_NOT_SUPPORTED;
427  }
428 
429  if (!this->parent_->try_lock()) {
430  return ESP_ERR_INVALID_STATE;
431  }
432 
433  i2s_channel_fmt_t channel = this->channel_;
434 
435  if (audio_stream_info.channels == 1) {
436  if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) {
437  channel = I2S_CHANNEL_FMT_ONLY_LEFT;
438  } else {
439  channel = I2S_CHANNEL_FMT_ONLY_RIGHT;
440  }
441  } else if (audio_stream_info.channels == 2) {
442  channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
443  }
444 
445  int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
446 
447  i2s_driver_config_t config = {
448  .mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
449  .sample_rate = audio_stream_info.sample_rate,
450  .bits_per_sample = this->bits_per_sample_,
451  .channel_format = channel,
452  .communication_format = this->i2s_comm_fmt_,
453  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
454  .dma_buf_count = DMA_BUFFERS_COUNT,
455  .dma_buf_len = dma_buffer_length,
456  .use_apll = this->use_apll_,
457  .tx_desc_auto_clear = true,
458  .fixed_mclk = I2S_PIN_NO_CHANGE,
459  .mclk_multiple = I2S_MCLK_MULTIPLE_256,
460  .bits_per_chan = this->bits_per_channel_,
461 #if SOC_I2S_SUPPORTS_TDM
462  .chan_mask = (i2s_channel_t) (I2S_TDM_ACTIVE_CH0 | I2S_TDM_ACTIVE_CH1),
463  .total_chan = 2,
464  .left_align = false,
465  .big_edin = false,
466  .bit_order_msb = false,
467  .skip_msk = false,
468 #endif
469  };
470 #if SOC_I2S_SUPPORTS_DAC
471  if (this->internal_dac_mode_ != I2S_DAC_CHANNEL_DISABLE) {
472  config.mode = (i2s_mode_t) (config.mode | I2S_MODE_DAC_BUILT_IN);
473  }
474 #endif
475 
476  esp_err_t err =
477  i2s_driver_install(this->parent_->get_port(), &config, I2S_EVENT_QUEUE_COUNT, &this->i2s_event_queue_);
478  if (err != ESP_OK) {
479  // Failed to install the driver, so unlock the I2S port
480  this->parent_->unlock();
481  return err;
482  }
483 
484 #if SOC_I2S_SUPPORTS_DAC
485  if (this->internal_dac_mode_ == I2S_DAC_CHANNEL_DISABLE) {
486 #endif
487  i2s_pin_config_t pin_config = this->parent_->get_pin_config();
488  pin_config.data_out_num = this->dout_pin_;
489 
490  err = i2s_set_pin(this->parent_->get_port(), &pin_config);
491 #if SOC_I2S_SUPPORTS_DAC
492  } else {
493  i2s_set_dac_mode(this->internal_dac_mode_);
494  }
495 #endif
496 
497  if (err != ESP_OK) {
498  // Failed to set the data out pin, so uninstall the driver and unlock the I2S port
499  i2s_driver_uninstall(this->parent_->get_port());
500  this->parent_->unlock();
501  }
502 
503  return err;
504 }
505 
506 void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
507  this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr
508 
509  if (this->data_buffer_ != nullptr) {
511  allocator.deallocate(this->data_buffer_, buffer_size);
512  this->data_buffer_ = nullptr;
513  }
514 
515  xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::STATE_STOPPED);
516 
517  this->task_created_ = false;
518  vTaskDelete(nullptr);
519 }
520 
521 } // namespace i2s_audio
522 } // namespace esphome
523 
524 #endif // USE_ESP32
value_type const & value() const
Definition: optional.h:89
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
Plays the provided audio data.
void stop_(bool wait_on_empty)
Sends a stop command to the speaker task via event_group_.
bool send_esp_err_to_event_group_(esp_err_t err)
Sets the corresponding ERR_ESP event group bits.
esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size)
Allocates the data buffer and ring buffer.
virtual bool set_mute_off()=0
virtual bool set_mute_on()=0
static void speaker_task(void *params)
Function for the FreeRTOS task handling audio output.
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
bool is_failed() const
Definition: component.cpp:143
void set_volume(float volume) override
Sets the volume of the speaker.
T * allocate(size_t n)
Definition: helpers.h:690
bool has_value() const
Definition: optional.h:87
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
bool is_ready() const
Definition: component.cpp:144
bool status_has_error() const
Definition: component.cpp:150
void status_set_error(const char *message="unspecified")
Definition: component.cpp:159
i2s_channel_fmt_t channel_
Definition: i2s_audio.h:25
esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info)
Starts the ESP32 I2S driver.
void status_clear_warning()
Definition: component.cpp:166
void set_mute_state(bool mute_state) override
Mutes or unmute the speaker.
void deallocate(T *p, size_t n)
Definition: helpers.h:709
virtual bool set_volume(float volume)=0
void status_clear_error()
Definition: component.cpp:172
i2s_bits_per_sample_t bits_per_sample_
Definition: i2s_audio.h:27
std::string size_t len
Definition: helpers.h:293
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
audio_dac::AudioDac * audio_dac_
Definition: speaker.h:105
void delete_task_(size_t buffer_size)
Deletes the speaker&#39;s task.
std::shared_ptr< RingBuffer > audio_ring_buffer_
uint16_t length
Definition: tt21100.cpp:12
size_t get_bytes_per_sample() const
Definition: audio.h:14
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
i2s_bits_per_chan_t bits_per_channel_
Definition: i2s_audio.h:28
An STL allocator that uses SPI or internal RAM.
Definition: helpers.h:675
static std::unique_ptr< RingBuffer > create(size_t len)
Definition: ring_buffer.cpp:22
audio::AudioStreamInfo audio_stream_info_
Definition: speaker.h:100