arduino-audio-tools
I2SESP32V1.h
1 #pragma once
2 
3 #include "AudioConfig.h"
4 #if defined(ESP32) && defined(USE_I2S) && \
5  ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) || \
6  defined(DOXYGEN)
7 
8 #include "AudioTools/CoreAudio/AudioI2S/I2SConfig.h"
9 #include "driver/i2s_pdm.h"
10 #include "driver/i2s_std.h"
11 #include "driver/i2s_tdm.h"
12 #include "esp_system.h"
13 
14 #define IS_I2S_IMPLEMENTED
15 
16 namespace audio_tools {
17 
26  public:
29  I2SConfigESP32V1 c(mode);
30  return c;
31  }
33  bool setAudioInfo(AudioInfo info) {
34  // nothing to do
35  if (is_started) {
36  if (info.equals(cfg)) return true;
37  if (info.equalsExSampleRate(cfg)) {
38  cfg.sample_rate = info.sample_rate;
39  LOGI("i2s_set_sample_rates: %d", (int)info.sample_rate);
40  return getDriver(cfg).changeSampleRate(cfg, rx_chan, tx_chan);
41  }
42  } else {
43  LOGE("not started");
44  }
45  return false;
46  }
47 
49  bool begin(RxTxMode mode) { return begin(defaultConfig(mode)); }
50 
53  bool begin() { return (!is_started) ? begin(cfg) : true; }
54 
56  bool begin(I2SConfigESP32V1 cfg) {
57  TRACED();
58  this->cfg = cfg;
59 
60  // stop if it is already open
61  if (is_started) end();
62 
63  switch (cfg.rx_tx_mode) {
64  case TX_MODE:
65  return begin(cfg, cfg.pin_data, I2S_GPIO_UNUSED);
66  case RX_MODE:
67  // usually we expet cfg.pin_data but if the used assinged rx we might
68  // consider this one
69  return begin(cfg, I2S_GPIO_UNUSED,
70  cfg.pin_data_rx != I2S_GPIO_UNUSED ? cfg.pin_data_rx
71  : cfg.pin_data);
72  default:
73  return begin(cfg, cfg.pin_data, cfg.pin_data_rx);
74  }
75  LOGE("Did not expect go get here");
76  }
77 
79  int available() { return I2S_BUFFER_COUNT * I2S_BUFFER_SIZE; }
80 
82  int availableForWrite() { return I2S_BUFFER_COUNT * I2S_BUFFER_SIZE; }
83 
85  void end() {
86  TRACED();
87  if (rx_chan != nullptr) {
88  i2s_channel_disable(rx_chan);
89  i2s_del_channel(rx_chan);
90  rx_chan = nullptr;
91  }
92  if (tx_chan != nullptr) {
93  i2s_channel_disable(tx_chan);
94  i2s_del_channel(tx_chan);
95  tx_chan = nullptr;
96  }
97 
98  is_started = false;
99  }
100 
102  I2SConfigESP32V1 config() { return cfg; }
103 
105  size_t writeBytes(const void *src, size_t size_bytes) {
106  TRACED();
107  size_t result;
108  assert(tx_chan != nullptr);
109  if (i2s_channel_write(tx_chan, src, size_bytes, &result,
110  ticks_to_wait_write) != ESP_OK) {
111  TRACEE();
112  }
113  return result;
114  }
115 
116  size_t readBytes(void *dest, size_t size_bytes) {
117  size_t result = 0;
118  if (i2s_channel_read(rx_chan, dest, size_bytes, &result,
119  ticks_to_wait_read) != ESP_OK) {
120  TRACEE();
121  }
122  return result;
123  }
124 
125  void setWaitTimeReadMs(TickType_t ms) {
126  ticks_to_wait_read = pdMS_TO_TICKS(ms);
127  }
128  void setWaitTimeWriteMs(TickType_t ms) {
129  ticks_to_wait_write = pdMS_TO_TICKS(ms);
130  }
131 
132  protected:
133  I2SConfigESP32V1 cfg = defaultConfig(RXTX_MODE);
134  i2s_std_config_t i2s_config;
135  i2s_chan_handle_t tx_chan = nullptr; // I2S tx channel handler
136  i2s_chan_handle_t rx_chan = nullptr; // I2S rx channel handler
137  bool is_started = false;
138  TickType_t ticks_to_wait_read = portMAX_DELAY;
139  TickType_t ticks_to_wait_write = portMAX_DELAY;
140 
141  struct DriverCommon {
142  virtual bool startChannels(I2SConfigESP32V1 &cfg,
143  i2s_chan_handle_t &tx_chan,
144  i2s_chan_handle_t &rx_chan, int txPin,
145  int rxPin) = 0;
146 
147  virtual i2s_chan_config_t getChannelConfig(I2SConfigESP32V1 &cfg) = 0;
148  // changes the sample rate
149  virtual bool changeSampleRate(I2SConfigESP32V1 &cfg,
150  i2s_chan_handle_t &tx_chan,
151  i2s_chan_handle_t &rx_chan) {
152  return false;
153  }
154 
155  protected:
157  int get_bits_eff(int bits) { return ( bits == 24 )? 32 : bits; }
158 
159  };
160 
161  struct DriverI2S : public DriverCommon {
162  bool startChannels(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &tx_chan,
163  i2s_chan_handle_t &rx_chan, int txPin, int rxPin) {
164  TRACED();
165  LOGI("tx: %d, rx: %d", txPin, rxPin);
166  i2s_std_config_t std_cfg = {
167  .clk_cfg = getClockConfig(cfg),
168  .slot_cfg = getSlotConfig(cfg),
169  .gpio_cfg =
170  {
171  .mclk = (gpio_num_t)cfg.pin_mck,
172  .bclk = (gpio_num_t)cfg.pin_bck,
173  .ws = (gpio_num_t)cfg.pin_ws,
174  .dout = (gpio_num_t)txPin,
175  .din = (gpio_num_t)rxPin,
176  .invert_flags =
177  {
178  .mclk_inv = false,
179  .bclk_inv = false,
180  .ws_inv = false,
181  },
182  },
183  };
184 
185  if (cfg.rx_tx_mode == RXTX_MODE || cfg.rx_tx_mode == TX_MODE) {
186  if (i2s_channel_init_std_mode(tx_chan, &std_cfg) != ESP_OK) {
187  LOGE("i2s_channel_init_std_mode %s", "tx");
188  return false;
189  }
190  if (i2s_channel_enable(tx_chan) != ESP_OK) {
191  LOGE("i2s_channel_enable %s", "tx");
192  return false;
193  }
194  }
195 
196  if (cfg.rx_tx_mode == RXTX_MODE || cfg.rx_tx_mode == RX_MODE) {
197  if (i2s_channel_init_std_mode(rx_chan, &std_cfg) != ESP_OK) {
198  LOGE("i2s_channel_init_std_mode %s", "rx");
199  return false;
200  }
201  if (i2s_channel_enable(rx_chan) != ESP_OK) {
202  LOGE("i2s_channel_enable %s", "rx");
203  return false;
204  }
205  }
206 
207  LOGD("%s - %s", __func__, "started");
208  return true;
209  }
210 
211  protected:
212  i2s_std_slot_config_t getSlotConfig(I2SConfigESP32V1 &cfg) {
213  TRACED();
214  i2s_std_slot_config_t result;
215  switch (cfg.i2s_format) {
216  case I2S_LEFT_JUSTIFIED_FORMAT:
217  case I2S_MSB_FORMAT:
218  result = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(
219  (i2s_data_bit_width_t)cfg.bits_per_sample,
220  (i2s_slot_mode_t)cfg.channels);
221  break;
222  case I2S_PCM:
223  result = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(
224  (i2s_data_bit_width_t)cfg.bits_per_sample,
225  (i2s_slot_mode_t)cfg.channels);
226  break;
227  default:
228  result = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(
229  (i2s_data_bit_width_t)cfg.bits_per_sample,
230  (i2s_slot_mode_t)cfg.channels);
231  }
232 
233  // Update slot_mask if only one channel
234  if (cfg.channels == 1) {
235  switch (cfg.channel_format) {
236  case I2SChannelSelect::Left:
237  result.slot_mask = I2S_STD_SLOT_LEFT;
238  break;
239  case I2SChannelSelect::Right:
240  result.slot_mask = I2S_STD_SLOT_RIGHT;
241  break;
242  default:
243  LOGW("Using channel_format: I2SChannelSelect::Left for mono");
244  result.slot_mask = I2S_STD_SLOT_LEFT;
245  break;
246  }
247  }
248 
249  return result;
250  }
251 
252  i2s_chan_config_t getChannelConfig(I2SConfigESP32V1 &cfg) {
253  TRACED();
254  i2s_chan_config_t result = I2S_CHANNEL_DEFAULT_CONFIG(
255  (i2s_port_t)cfg.port_no,
256  cfg.is_master ? I2S_ROLE_MASTER : I2S_ROLE_SLAVE);
257  // use the legicy size parameters for frame num
258  int size = cfg.buffer_size * cfg.buffer_count;
259  int frame_size = get_bits_eff(cfg.bits_per_sample) * cfg.channels / 8;
260  if (size > 0) result.dma_frame_num = size / frame_size;
261  LOGI("dma_frame_num: %d", (int)result.dma_frame_num);
262  result.auto_clear = cfg.auto_clear;
263  return result;
264  }
265 
266  i2s_std_clk_config_t getClockConfig(I2SConfigESP32V1 &cfg) {
267  TRACED();
268  i2s_std_clk_config_t clk_cfg =
269  I2S_STD_CLK_DEFAULT_CONFIG((uint32_t)cfg.sample_rate);
270  if (cfg.mclk_multiple > 0) {
271  clk_cfg.mclk_multiple = (i2s_mclk_multiple_t)cfg.mclk_multiple;
272  } else {
273  if (cfg.bits_per_sample == 24) {
274  // mclk_multiple' should be the multiple of 3 while using 24-bit
275  clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_384;
276  LOGI("mclk_multiple=384");
277  }
278  }
279  return clk_cfg;
280  }
281 
282  bool changeSampleRate(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &tx_chan,
283  i2s_chan_handle_t &rx_chan) override {
284  bool rc = false;
285  auto clock_cfg = getClockConfig(cfg);
286  if (tx_chan != nullptr) {
287  i2s_channel_disable(tx_chan);
288  rc = i2s_channel_reconfig_std_clock(tx_chan, &clock_cfg) == ESP_OK;
289  i2s_channel_enable(tx_chan);
290  }
291  if (rx_chan != nullptr) {
292  i2s_channel_disable(rx_chan);
293  rc = i2s_channel_reconfig_std_clock(rx_chan, &clock_cfg) == ESP_OK;
294  i2s_channel_enable(rx_chan);
295  }
296  return rc;
297  }
298 
299  } i2s;
300 
301 #ifdef USE_PDM
302 
303  struct DriverPDM : public DriverCommon {
304  bool startChannels(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &tx_chan,
305  i2s_chan_handle_t &rx_chan, int txPin, int rxPin) {
306  if (cfg.rx_tx_mode == TX_MODE) {
307  return startTX(cfg, tx_chan, txPin);
308  } else if (cfg.rx_tx_mode == RX_MODE) {
309  return startRX(cfg, rx_chan, rxPin);
310  }
311  LOGE("Only RX and TX is supported for PDM")
312  return false;
313  }
314 
315  protected:
316  i2s_pdm_tx_slot_config_t getTxSlotConfig(I2SConfigESP32V1 &cfg) {
317  return I2S_PDM_TX_SLOT_DEFAULT_CONFIG(
318  (i2s_data_bit_width_t)cfg.bits_per_sample,
319  (i2s_slot_mode_t)cfg.channels);
320  }
321 
322  i2s_chan_config_t getChannelConfig(I2SConfigESP32V1 &cfg) {
323  return I2S_CHANNEL_DEFAULT_CONFIG(
324  (i2s_port_t)cfg.port_no,
325  cfg.is_master ? I2S_ROLE_MASTER : I2S_ROLE_SLAVE);
326  }
327 
328  i2s_pdm_tx_clk_config_t getTxClockConfig(I2SConfigESP32V1 &cfg) {
329  return I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)cfg.sample_rate);
330  }
331 
332  bool startTX(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &tx_chan, int txPin) {
333  i2s_pdm_tx_config_t pdm_tx_cfg = {
334  .clk_cfg = getTxClockConfig(cfg),
335  .slot_cfg = getTxSlotConfig(cfg),
336  .gpio_cfg =
337  {
338  .clk = (gpio_num_t)cfg.pin_bck,
339  .dout = (gpio_num_t)txPin,
340  .invert_flags =
341  {
342  .clk_inv = false,
343  },
344  },
345  };
346 
347  if (i2s_channel_init_pdm_tx_mode(tx_chan, &pdm_tx_cfg) != ESP_OK) {
348  LOGE("i2s_channel_init_pdm_tx_mode %s", "tx");
349  return false;
350  }
351  if (i2s_channel_enable(tx_chan) != ESP_OK) {
352  LOGE("i2s_channel_enable %s", "tx");
353  return false;
354  }
355  return true;
356  }
357 
358 #if defined(USE_PDM_RX)
359  i2s_pdm_rx_slot_config_t getRxSlotConfig(I2SConfigESP32V1 &cfg) {
360  return I2S_PDM_RX_SLOT_DEFAULT_CONFIG(
361  (i2s_data_bit_width_t)cfg.bits_per_sample,
362  (i2s_slot_mode_t)cfg.channels);
363  }
364  i2s_pdm_rx_clk_config_t getRxClockConfig(I2SConfigESP32V1 &cfg) {
365  return I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)cfg.sample_rate);
366  }
367  bool startRX(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &rx_chan, int rxPin) {
368  i2s_pdm_rx_config_t pdm_rx_cfg = {
369  .clk_cfg = getRxClockConfig(cfg),
370  .slot_cfg = getRxSlotConfig(cfg),
371  .gpio_cfg =
372  {
373  .clk = (gpio_num_t)cfg.pin_bck,
374  .din = (gpio_num_t)rxPin,
375  .invert_flags =
376  {
377  .clk_inv = false,
378  },
379  },
380  };
381 
382  if (i2s_channel_init_pdm_rx_mode(rx_chan, &pdm_rx_cfg) != ESP_OK) {
383  LOGE("i2s_channel_init_pdm_rx_mode %s", "rx");
384  return false;
385  }
386  if (i2s_channel_enable(rx_chan) != ESP_OK) {
387  LOGE("i2s_channel_enable %s", "tx");
388  return false;
389  }
390  return true;
391  }
392 #else
393  bool startRX(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &rx_chan, int rxPin) {
394  LOGE("PDM RX not supported");
395  return false;
396  }
397 #endif
398  } pdm;
399 
400 #endif
401 
402 #ifdef USE_TDM
403  // example at
404  // https://github.com/espressif/esp-idf/blob/v5.3-dev/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/i2s_tdm_example_main.c
405  struct DriverTDM : public DriverCommon {
406  bool startChannels(I2SConfigESP32V1 &cfg, i2s_chan_handle_t &tx_chan,
407  i2s_chan_handle_t &rx_chan, int txPin, int rxPin) {
408  i2s_tdm_config_t tdm_cfg = {
409  .clk_cfg = getClockConfig(cfg),
410  .slot_cfg = getSlotConfig(cfg),
411  .gpio_cfg =
412  {
413  .mclk = (gpio_num_t)cfg.pin_mck,
414  .bclk = (gpio_num_t)cfg.pin_bck,
415  .ws = (gpio_num_t)cfg.pin_ws,
416  .dout = (gpio_num_t)txPin,
417  .din = (gpio_num_t)rxPin,
418  .invert_flags =
419  {
420  .mclk_inv = false,
421  .bclk_inv = false,
422  .ws_inv = false,
423  },
424  },
425  };
426 
427  if (cfg.rx_tx_mode == TX_MODE || cfg.rx_tx_mode == RXTX_MODE) {
428  if (i2s_channel_init_tdm_mode(tx_chan, &tdm_cfg) != ESP_OK) {
429  LOGE("i2s_channel_init_tdm_tx_mode %s", "tx");
430  return false;
431  }
432  }
433  if (cfg.rx_tx_mode == RX_MODE || cfg.rx_tx_mode == RXTX_MODE) {
434  if (i2s_channel_init_tdm_mode(rx_chan, &tdm_cfg) != ESP_OK) {
435  LOGE("i2s_channel_init_tdm_tx_mode %s", "rx");
436  return false;
437  }
438  }
439  return true;
440  }
441 
442  protected:
443  i2s_tdm_slot_config_t getSlotConfig(I2SConfigESP32V1 &cfg) {
444  int slots = 0;
445  for (int j = 0; j < cfg.channels; j++) {
446  slots |= 1 << j;
447  }
448  // setup default format
449  i2s_tdm_slot_config_t slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
450  (i2s_data_bit_width_t)cfg.bits_per_sample, I2S_SLOT_MODE_STEREO,
451  (i2s_tdm_slot_mask_t)slots);
452 
453  switch (cfg.i2s_format) {
454  case I2S_RIGHT_JUSTIFIED_FORMAT:
455  case I2S_LSB_FORMAT:
456  case I2S_PHILIPS_FORMAT:
457  case I2S_STD_FORMAT:
458  slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
459  (i2s_data_bit_width_t)cfg.bits_per_sample, I2S_SLOT_MODE_STEREO,
460  (i2s_tdm_slot_mask_t)slots);
461  break;
462  case I2S_LEFT_JUSTIFIED_FORMAT:
463  case I2S_MSB_FORMAT:
464  slot_cfg = I2S_TDM_MSB_SLOT_DEFAULT_CONFIG(
465  (i2s_data_bit_width_t)cfg.bits_per_sample, I2S_SLOT_MODE_STEREO,
466  (i2s_tdm_slot_mask_t)slots);
467  break;
468  case I2S_PCM:
469  slot_cfg = I2S_TDM_PCM_LONG_SLOT_DEFAULT_CONFIG(
470  (i2s_data_bit_width_t)cfg.bits_per_sample, I2S_SLOT_MODE_STEREO,
471  (i2s_tdm_slot_mask_t)slots);
472  break;
473  default:
474  LOGE("TDM: Unsupported format");
475  }
476 
477  return slot_cfg;
478  }
479 
480  i2s_chan_config_t getChannelConfig(I2SConfigESP32V1 &cfg) {
481  return I2S_CHANNEL_DEFAULT_CONFIG(
482  (i2s_port_t)cfg.port_no,
483  cfg.is_master ? I2S_ROLE_MASTER : I2S_ROLE_SLAVE);
484  }
485 
486  i2s_tdm_clk_config_t getClockConfig(I2SConfigESP32V1 &cfg) {
487  return I2S_TDM_CLK_DEFAULT_CONFIG((uint32_t)cfg.sample_rate);
488  }
489 
490  } tdm;
491 
492 #endif
493 
495 
497  bool begin(I2SConfigESP32V1 cfg, int txPin, int rxPin) {
498  TRACED();
499  cfg.logInfo();
500  this->cfg = cfg;
501  if (cfg.channels <= 0 || cfg.channels > 2) {
502  LOGE("invalid channels: %d", cfg.channels);
503  return false;
504  }
505 
506  DriverCommon &driver = getDriver(cfg);
507  if (!newChannels(cfg, driver)) {
508  end();
509  return false;
510  }
511 
512  is_started = driver.startChannels(cfg, tx_chan, rx_chan, txPin, rxPin);
513  if (!is_started) {
514  end();
515  LOGE("Channels not started");
516  }
517  return is_started;
518  }
519 
520  bool newChannels(I2SConfigESP32V1 &cfg, DriverCommon &driver) {
521  i2s_chan_config_t chan_cfg = driver.getChannelConfig(cfg);
522  switch (cfg.rx_tx_mode) {
523  case RX_MODE:
524  if (i2s_new_channel(&chan_cfg, NULL, &rx_chan) != ESP_OK) {
525  LOGE("i2s_channel");
526  return false;
527  }
528  break;
529  case TX_MODE:
530  if (i2s_new_channel(&chan_cfg, &tx_chan, NULL) != ESP_OK) {
531  LOGE("i2s_channel");
532  return false;
533  }
534  break;
535  default:
536  if (i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan) != ESP_OK) {
537  LOGE("i2s_channel");
538  return false;
539  }
540  }
541  return true;
542  }
543 
544  DriverCommon &getDriver(I2SConfigESP32V1 &cfg) {
545  switch (cfg.signal_type) {
546  case Digital:
547  return i2s;
548 #ifdef USE_PDM
549  case Analog:
550  case PDM:
551  return pdm;
552 #endif
553 #ifdef USE_TDM
554  case TDM:
555  return tdm;
556 #endif
557  default:
558  break;
559  }
560  LOGE("Unsupported singal_type");
561  return i2s;
562  }
563 };
564 
565 using I2SDriver = I2SDriverESP32V1;
566 
567 } // namespace audio_tools
568 
569 #endif
Configuration for ESP32 i2s for IDF > 5.0.
Definition: I2SConfigESP32V1.h:22
int buffer_count
not used any more
Definition: I2SConfigESP32V1.h:66
RxTxMode rx_tx_mode
public settings
Definition: I2SConfigESP32V1.h:55
int mclk_multiple
masterclock multiple (-1 = use default)
Definition: I2SConfigESP32V1.h:74
I2SChannelSelect channel_format
Select left or right channel when channels == 1.
Definition: I2SConfigESP32V1.h:72
int buffer_size
not used any more
Definition: I2SConfigESP32V1.h:68
Basic I2S API for the ESP32 (using the new API). https://docs.espressif.com/projects/esp-idf/en/v5....
Definition: I2SESP32V1.h:25
I2SConfigESP32V1 defaultConfig(RxTxMode mode)
Provides the default configuration.
Definition: I2SESP32V1.h:28
bool begin(I2SConfigESP32V1 cfg)
starts the DAC
Definition: I2SESP32V1.h:56
int available()
we assume the data is already available in the buffer
Definition: I2SESP32V1.h:79
bool begin()
Definition: I2SESP32V1.h:53
bool setAudioInfo(AudioInfo info)
Potentially updates the sample rate (if supported)
Definition: I2SESP32V1.h:33
int availableForWrite()
We limit the write size to the buffer size.
Definition: I2SESP32V1.h:82
I2SConfigESP32V1 config()
provides the actual configuration
Definition: I2SESP32V1.h:102
void end()
stops the I2C and unistalls the driver
Definition: I2SESP32V1.h:85
bool begin(RxTxMode mode)
starts the DAC with the default config
Definition: I2SESP32V1.h:49
bool begin(I2SConfigESP32V1 cfg, int txPin, int rxPin)
-> protected methods from I2SDriverESP32V1
Definition: I2SESP32V1.h:497
size_t writeBytes(const void *src, size_t size_bytes)
writes the data to the I2S interface
Definition: I2SESP32V1.h:105
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:55
bool equals(AudioInfo alt)
Returns true if alt values are the same like the current values.
Definition: AudioTypes.h:85
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:57
bool equalsExSampleRate(AudioInfo alt)
Checks if only the sample rate is different.
Definition: AudioTypes.h:90
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:59
int get_bits_eff(int bits)
24 bits are stored in a 32 bit integer
Definition: I2SESP32V1.h:157
Definition: I2SESP32V1.h:161