BackgroundAudio 1.4.1
Loading...
Searching...
No Matches
ESP32PDMAudio.h
1/*
2 BackgroundAudio
3 Plays an audio file using IRQ driven decompression. Main loop() writes
4 data to the buffer but isn't blocked while playing
5
6 Copyright (c) 2025 Earle F. Philhower, III <earlephilhower@yahoo.com>
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#pragma once
23
24#include <driver/i2s_pdm.h>
25#include "ESP32I2SAudio.h"
26#include "WrappedAudioOutputBase.h"
27
32public:
38 ESP32PDMAudio(int8_t dout = 0) {
39 _dout = dout;
40 _running = false;
41 _sampleRate = 44100;
42 _buffers = 5;
43 _bufferWords = 512;
44 _silenceSample = 0;
45 _cb = nullptr;
46 _underflowed = false;
47 }
48
49 virtual ~ESP32PDMAudio() {
50 }
51
57 void setPin(int8_t dout) {
58 _dout = dout;
59 }
60
68 bool setFrequency(int freq) override {
69
70 // TODO - There is some fixed off-by-ratio 1/1.25 in the PDM output clock vs. the PCM input data at IDF 5.5
71 freq *= 8;
72 freq /= 10;
73 if (_running && (_sampleRate != freq)) {
74 i2s_pdm_tx_clk_config_t clk_cfg;
75 clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)freq);
76 clk_cfg.up_sample_fp = 960;
77 clk_cfg.up_sample_fs = 480;
78 i2s_channel_disable(_tx_handle);
79 i2s_channel_reconfig_pdm_tx_clock(_tx_handle, &clk_cfg);
80 i2s_channel_enable(_tx_handle);
81 _sampleRate = freq;
82 }
83 return true;
84 }
85
96 bool begin() override {
97 if (_running) {
98 return false;
99 }
100
101 // Make a new channel of the requested buffers (which may be ignored by the IDF!)
102 i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
103 chan_cfg.dma_desc_num = _buffers;
104 chan_cfg.dma_frame_num = _bufferWords;
105 assert(ESP_OK == i2s_new_channel(&chan_cfg, &_tx_handle, nullptr));
106
107 i2s_pdm_tx_config_t pdm_cfg = {
108 .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sampleRate),
109 .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
110 .gpio_cfg = {
111 .clk = I2S_GPIO_UNUSED,
112 .dout = (gpio_num_t)_dout,
113 .dout2 = I2S_GPIO_UNUSED,
114 .invert_flags = {
115 .clk_inv = false,
116 },
117 },
118 };
119 pdm_cfg.slot_cfg.data_fmt = I2S_PDM_DATA_FMT_PCM;
120 pdm_cfg.clk_cfg.up_sample_fp = 960;
121 pdm_cfg.clk_cfg.up_sample_fs = 480;
122 assert(ESP_OK == i2s_channel_init_pdm_tx_mode(_tx_handle, &pdm_cfg));
123
124 i2s_chan_info_t _info;
125 i2s_channel_get_info(_tx_handle, &_info);
126 // If the IDF has changed our buffer size or count then we can't work
127 assert(_info.total_dma_buf_size == _buffers * _bufferWords * 4);
128 _totalAvailable = _info.total_dma_buf_size;
129
130 // Prefill silence and calculate how bug we really have
131 int16_t a[2] = {0, 0};
132 size_t written = 0;
133 do {
134 i2s_channel_preload_data(_tx_handle, (void*)a, sizeof(a), &written);
135 } while (written);
136
137 // The IRQ callbacks which will just trigger the playback task
138 i2s_event_callbacks_t _cbs = {
139 .on_recv = nullptr,
140 .on_recv_q_ovf = nullptr,
141 .on_sent = _onSent,
142 .on_send_q_ovf = nullptr
143 };
144 assert(ESP_OK == i2s_channel_register_event_callback(_tx_handle, &_cbs, (void *)this));
145 xTaskCreate(_taskShim, "BackgroundAudioI2S", 8192, (void*)this, 2, &_taskHandle);
146 _running = ESP_OK == i2s_channel_enable(_tx_handle);
147 return _running;
148 }
149
150};
I2S object with IRQ-based callbacks to a FreeRTOS task, for use with BackgroundAudio.
Definition ESP32I2SAudio.h:44
static void _taskShim(void *pvParameters)
C-language shim to start the real object's task.
Definition ESP32I2SAudio.h:245
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
PDM object with IRQ-based callbacks to a FreeRTOS task, for use with BackgroundAudio.
Definition ESP32PDMAudio.h:31
ESP32PDMAudio(int8_t dout=0)
Construct ESP32-based PDM object with IRQ-based callbacks to a FreeRTOS task, for use with Background...
Definition ESP32PDMAudio.h:38
bool setFrequency(int freq) override
Set the sample rate of the PDM interface. Can be called while running.
Definition ESP32PDMAudio.h:68
void setPin(int8_t dout)
Set the PDM GPIO pin before calling begin
Definition ESP32PDMAudio.h:57
bool begin() override
Start the PDM interface.
Definition ESP32PDMAudio.h:96