24#include <driver/i2s_std.h>
26#include "WrappedAudioOutputBase.h"
35#if SOC_I2S_HW_VERSION_2
36#undef I2S_STD_CLK_DEFAULT_CONFIG
37#define I2S_STD_CLK_DEFAULT_CONFIG(rate) \
38 { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, }
54 ESP32I2SAudio(int8_t bclk = 0, int8_t ws = 1, int8_t dout = 2, int8_t mclk = -1) {
79 void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t mclk = -1) {
108 bool setBuffers(
size_t buffers,
size_t bufferWords, int32_t silenceSample = 0)
override {
111 _bufferWords = bufferWords;
115 while (_bufferWords > 1023) {
119 _silenceSample = silenceSample;
132 if (!_running && bps == 16) {
146 if (_running && (_sampleRate != freq)) {
147 i2s_std_clk_config_t clk_cfg;
148 clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG((uint32_t)freq);
149 i2s_channel_disable(_tx_handle);
150 i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg);
151 i2s_channel_enable(_tx_handle);
184 i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
185 chan_cfg.dma_desc_num = _buffers;
186 chan_cfg.dma_frame_num = _bufferWords;
187 assert(ESP_OK == i2s_new_channel(&chan_cfg, &_tx_handle,
nullptr));
189 i2s_std_config_t std_cfg = {
190 .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sampleRate),
191 .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
193 .mclk = _mclk < 0 ? I2S_GPIO_UNUSED : (gpio_num_t)_mclk,
194 .bclk = (gpio_num_t)_bclk,
195 .ws = (gpio_num_t)_ws,
196 .dout = (gpio_num_t)_dout,
197 .din = I2S_GPIO_UNUSED,
199 .mclk_inv = _mclkInv,
200 .bclk_inv = _bclkInv,
205 assert(ESP_OK == i2s_channel_init_std_mode(_tx_handle, &std_cfg));
207 i2s_chan_info_t _info;
208 i2s_channel_get_info(_tx_handle, &_info);
210 assert(_info.total_dma_buf_size == _buffers * _bufferWords * 4);
211 _totalAvailable = _info.total_dma_buf_size;
214 int16_t a[2] = {0, 0};
217 i2s_channel_preload_data(_tx_handle, (
void*)a,
sizeof(a), &written);
221 i2s_event_callbacks_t _cbs = {
223 .on_recv_q_ovf =
nullptr,
225 .on_send_q_ovf =
nullptr
227 assert(ESP_OK == i2s_channel_register_event_callback(_tx_handle, &_cbs, (
void *)
this));
228 xTaskCreate(
_taskShim,
"BackgroundAudioI2S", 8192, (
void*)
this, 2, &_taskHandle);
229 _running = ESP_OK == i2s_channel_enable(_tx_handle);
238 static IRAM_ATTR
bool _onSent(i2s_chan_handle_t handle, i2s_event_data_t *event,
void *user_ctx) {
239 return ((
ESP32I2SAudio *)user_ctx)->_onSentCB(handle, event);
255 uint32_t ulNotifiedValue;
256 xTaskNotifyWait(0, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY);
261 _saturating_add_available((uint32_t)ulNotifiedValue);
269 inline void _saturating_add_available(uint32_t add) {
270 uint32_t cur = _available.load(std::memory_order_relaxed);
272 uint64_t sum = (uint64_t)cur + add;
273 uint32_t capped = sum;
274 if (sum > _totalAvailable) {
275 capped = (uint32_t) _totalAvailable;
276 _underflowed.store(
true, std::memory_order_release);
277 _underflows.fetch_add(1, std::memory_order_relaxed);
280 if (_available.compare_exchange_weak(cur, capped, std::memory_order_release, std::memory_order_relaxed)) {
287 inline void _saturating_sub_available(uint32_t sub) {
288 uint32_t cur = _available.load(std::memory_order_relaxed);
290 uint32_t next = (cur > sub) ? (cur - sub) : 0u;
291 if (_available.compare_exchange_weak(cur, next, std::memory_order_release, std::memory_order_relaxed)) {
322 return _underflows.load(std::memory_order_acquire);
330 IRAM_ATTR
bool _onSentCB(i2s_chan_handle_t handle, i2s_event_data_t *event) {
331 BaseType_t xHigherPriorityTaskWoken;
332 xHigherPriorityTaskWoken = pdFALSE;
335 xTaskNotifyFromISR(_taskHandle, event->size, eSetValueWithoutOverwrite, &xHigherPriorityTaskWoken);
337 if (xHigherPriorityTaskWoken) {
338 portYIELD_FROM_ISR();
340 return (
bool)xHigherPriorityTaskWoken;
351 i2s_channel_disable(_tx_handle);
352 i2s_del_channel(_tx_handle);
353 vTaskDelete(_taskHandle);
366 return _underflowed.exchange(
false, std::memory_order_acq_rel);
390 size_t write(
const uint8_t *buffer,
size_t size)
override {
393 size_t cumWritten = 0;
396 i2s_channel_write(_tx_handle, buffer, size, &written, 100);
397 _saturating_sub_available((uint32_t)written);
400 cumWritten += written;
424 return (
int)_available.load(std::memory_order_acquire);
433 bool _bclkInv =
false;
435 bool _mclkInv =
false;
436 std::atomic<bool> _underflowed{
false};
440 uint32_t _silenceSample;
441 TaskHandle_t _taskHandle = 0;
445 i2s_chan_handle_t _tx_handle;
446 size_t _totalAvailable = 0;
447 std::atomic<uint32_t> _available{0};
449 uint32_t _frames = 0;
450 std::atomic<uint32_t> _underflows{0};
I2S object with IRQ-based callbacks to a FreeRTOS task, for use with BackgroundAudio.
Definition ESP32I2SAudio.h:44
ESP32I2SAudio(int8_t bclk=0, int8_t ws=1, int8_t dout=2, int8_t mclk=-1)
Construct ESP32-based I2S object with IRQ-based callbacks to a FreeRTOS task, for use with Background...
Definition ESP32I2SAudio.h:54
static void _taskShim(void *pvParameters)
C-language shim to start the real object's task.
Definition ESP32I2SAudio.h:245
void _backgroundTask()
Background I2S DMA buffer notification task. Tracks number of bytes available to be written.
Definition ESP32I2SAudio.h:252
uint32_t irqs()
Get the number of input data shifts processed by decoder since begin
Definition ESP32I2SAudio.h:312
bool end() override
Stop the I2S device.
Definition ESP32I2SAudio.h:349
uint32_t frames()
Get number of DMA frames(buffers) processed.
Definition ESP32I2SAudio.h:303
bool setStereo(bool stereo=true) override
Set mono or stereo mode. Only stereo supported.
Definition ESP32I2SAudio.h:164
bool setFrequency(int freq) override
Set the sample rate (LRCLK/WS) of the I2S interface. Can be called while running.
Definition ESP32I2SAudio.h:145
uint32_t underflows()
Get the number of times the MP3 decoder has underflowed waiting on raw data since begin
Definition ESP32I2SAudio.h:321
IRAM_ATTR bool _onSentCB(i2s_chan_handle_t handle, i2s_event_data_t *event)
Object-based callback for I2S Sent notification.
Definition ESP32I2SAudio.h:330
void setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t mclk=-1)
Set the I2S GPIO pins before calling begin
Definition ESP32I2SAudio.h:79
bool getUnderflow() override
Determine if there was an underflow since the last time this was called. Cleared on read.
Definition ESP32I2SAudio.h:364
int availableForWrite() override
Determine the number of frames we can write to the DMA buffers at this instant.
Definition ESP32I2SAudio.h:423
static IRAM_ATTR bool _onSent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
C-language wrapper for I2S Sent event.
Definition ESP32I2SAudio.h:238
size_t write(uint8_t d) override
Write single byte to I2S buffers. Not supported.
Definition ESP32I2SAudio.h:414
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample=0) override
Set the size and number of the I2S buffers before begin
Definition ESP32I2SAudio.h:108
bool setBitsPerSample(int bps) override
Set the bits per sample for the I2S output. Only 16-bit supported.
Definition ESP32I2SAudio.h:131
void onTransmit(void(*cb)(void *), void *cbData) override
Set the callback function to be called every DMA buffer completion.
Definition ESP32I2SAudio.h:375
bool begin() override
Start the I2S interface.
Definition ESP32I2SAudio.h:178
size_t write(const uint8_t *buffer, size_t size) override
Write data to the I2S interface. Not legal from IRQ context. Will not block and may write less than r...
Definition ESP32I2SAudio.h:390
void setInverted(bool bclk, bool ws, bool mclk=false)
Set the I2S GPIO inversions before calling begin
Definition ESP32I2SAudio.h:93