wangrong
2026-08-24 beef6536e7c8dd3f5cbe8f73585611479910bea7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#include "adc_pdm_audio_codec.h"
 
#include <esp_log.h>
#include <esp_timer.h>
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#include "driver/i2s_pdm.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "hal/rtc_io_hal.h"
#include "hal/gpio_ll.h"
#include "settings.h"
#include "config.h"
 
static const char TAG[] = "AdcPdmAudioCodec";
 
#define BSP_I2S_GPIO_CFG(_dout)       \
    {                          \
        .clk = GPIO_NUM_NC,    \
        .dout = _dout,  \
        .invert_flags = {      \
            .clk_inv = false, \
        },                     \
    }
 
/**
 * @brief Mono Duplex I2S configuration structure
 *
 * This configuration is used by default in bsp_audio_init()
 */
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout)                                                         \
    {                                                                                                 \
        .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate),                                          \
        .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
        .gpio_cfg = BSP_I2S_GPIO_CFG(_dout),                                                                 \
    }
 
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
    uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
 
    input_reference_ = false;
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
 
    audio_codec_adc_cfg_t cfg = {};
    cfg.handle = NULL;
    cfg.continuous_cfg.max_store_buf_size = 1024 * 2;
    cfg.continuous_cfg.conv_frame_size = 1024;
    cfg.continuous_cfg.sample_freq_hz = (uint32_t)input_sample_rate;
    cfg.continuous_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1;
    cfg.continuous_cfg.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2;
    cfg.continuous_cfg.pattern_num = 1;
    cfg.continuous_cfg.cfg_mode = AUDIO_CODEC_ADC_CFG_MODE_SINGLE_UNIT;
    cfg.continuous_cfg.cfg.single_unit.unit_id = ADC_UNIT_1;
    cfg.continuous_cfg.cfg.single_unit.atten = ADC_ATTEN_DB_12;
    cfg.continuous_cfg.cfg.single_unit.bit_width = ADC_BITWIDTH_12;
    cfg.continuous_cfg.cfg.single_unit.channel_id[0] = (uint8_t)adc_mic_channel;
    const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
 
    esp_codec_dev_cfg_t codec_dev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_IN,
        .data_if = adc_if,
    };
    input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
    if (!input_dev_) {
        ESP_LOGE(TAG, "Failed to create codec device");
        return;
    }
 
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
 
    i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
    pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
    pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
    pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
    pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
    pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
    const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
 
    ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
 
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = NULL,
        .tx_handle = tx_handle_,
    };
 
    const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
 
    codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
    codec_dev_cfg.codec_if = NULL;
    codec_dev_cfg.data_if = i2s_data_if;
    output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
 
    output_volume_ = 100;
    if(pa_ctl != GPIO_NUM_NC) {
        pa_ctrl_pin_ = pa_ctl;
        gpio_config_t io_conf = {};
        io_conf.intr_type = GPIO_INTR_DISABLE;
        io_conf.mode = GPIO_MODE_OUTPUT;
        io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
        io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
        io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
        gpio_config(&io_conf);
    }
    gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
 
    if(pdm_speak_n != GPIO_NUM_NC){
        PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO);
        gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
        esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
        gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
    }
 
    // 初始化输出定时器
    esp_timer_create_args_t output_timer_args = {
        .callback = &AdcPdmAudioCodec::OutputTimerCallback,
        .arg = this,
        .dispatch_method = ESP_TIMER_TASK,
        .name = "output_timer"
    };
    ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
 
    ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
}
 
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
    // 删除定时器
    if (output_timer_) {
        esp_timer_stop(output_timer_);
        esp_timer_delete(output_timer_);
        output_timer_ = nullptr;
    }
 
    ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    esp_codec_dev_delete(output_dev_);
    ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    esp_codec_dev_delete(input_dev_);
}
 
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
    ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
    AudioCodec::SetOutputVolume(volume);
}
 
void AdcPdmAudioCodec::EnableInput(bool enable) {
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
            .sample_rate = (uint32_t)input_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    }
    AudioCodec::EnableInput(enable);
}
 
void AdcPdmAudioCodec::EnableOutput(bool enable) {
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        // Play 16bit 1 channel
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
 
        // 强制按板卡配置重配PDM TX时钟,覆盖第三方库在set_fmt中的默认up_sample_fs
        // 若通道已启用,先禁用再重配,最后再启用
        ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
        i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
        clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
        ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
        ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
        if(pa_ctrl_pin_ != GPIO_NUM_NC){
            gpio_set_level(pa_ctrl_pin_, 1);
        }
        // 启用输出时启动定时器
        if (output_timer_) {
            esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
        }
 
    } else {
        // 禁用输出时停止定时器
        if (output_timer_) {
            esp_timer_stop(output_timer_);
        }
        if(pa_ctrl_pin_ != GPIO_NUM_NC){
            gpio_set_level(pa_ctrl_pin_, 0);
        }
        ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    }
    AudioCodec::EnableOutput(enable);
}
 
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
        // 重置输出定时器
        if (output_timer_) {
            esp_timer_stop(output_timer_);
            esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
        }
    }
    return samples;
}
 
void AdcPdmAudioCodec::Start() {
    Settings settings("audio", false);
    output_volume_ = settings.GetInt("output_volume", output_volume_);
    if (output_volume_ <= 0) {
        ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
        output_volume_ = 10;
    }
 
    EnableInput(true);
    EnableOutput(true);
    ESP_LOGI(TAG, "Audio codec started");
}
 
// 定时器回调函数实现
void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
    AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
    if (codec && codec->output_enabled_) {
        codec->EnableOutput(false);
    }
}