我将描述使用 ESP32 微控制器对音频进行采样的三种方法。
1. 直接(顺序)读出
2. 中断驱动读出
3. I2S 驱动读出
但首先,让我告诉你一些关于采样和奈奎斯特定理的信息
用品
ESP32 板,如 Devkit
当您想使用微控制器对音频信号进行采样时,重要的是要知道您将如何处理该数字化信息。您会使用它将音频发送到另一台设备,还是会使用它来录制音频、进行 FFT 分析或完全不同的事情?此信息对于在选择参数时进行写入选择非常重要。稍后会详细介绍。
假设我们有一个 10Khz 的音频信号,我想将其数字化。该信号连接到微控制器的音频引脚,目前,我们假设该信号仅位于正域中。(它不会低于零)。
出于宗派目的,我们将使用以下参数对音频信号进行数字化:
- 罪恶浪潮
- Vtt = 1V
- 频率为 10 Khz
- 我们将以 9 Khz 的频率采集 10 个样本
一切都在图片中得到了证明。垂直线代表 9 个采样点。每个正弦波上的点代表存储到内存中的实际样本。
图中第一个正弦波的频率为2Khz。如果我们将采样的点与一条直线(黄色)连接起来,我们可以使用这些样本重新创建信号。
第二个正弦波的频率为 5 Khz,正如我们所看到的,当点连接起来时,我们可以再次从存储的样本中重新创建或发出信号。
现在,第三个正弦波的频率为 10 Khz,现在,正如您所看到的,当我们连接红点时,我们看到的只是一条直线,因此无法再通过使用音频样本来重新创建信号。
这正是我们应用奈奎斯特定理的原因。奈奎斯特表示,为了重新创建音频信号,采样频率必须至少是音频信号中最高频率的两倍。如果我们将其应用于音频信号,则意味着我们的 10 Khz 信号必须以 20Khz 或更高的频率进行采样才能获得良好的结果。
在 3 个正弦波信号的示例中,您会看到我们用样本再现的信号确实具有与原始正弦波相同的漂亮形状。现在,这完全取决于我们每秒采集的样本数量。如您所知,这取决于采样频率。如果我们使用 44.1Khz 的采样频率对 5 Khz 的音频信号进行采样,采样将以 22 uS 的间隔进行。( t=1/f)Sinds 5Khz 信号的周期时间为 200uS,我们将每个周期采集近 10 个样本。
为了对音频信号进行采样,您始终必须获取并存储该信号的一部分,然后在转到下一个卡盘之前对其进行处理。处理可以像存储或发送一样简单,但也可以进行 FFT 分析。
假设我们将以 8Khz 的采样频率采集 10 个样本。
顺序采样
这是对音频信号进行采样的最简单方法,但也是最耗时的方法。
基本上,您要做的是取第一个样本并将其放入缓冲区中。然后,只要需要等待,直到需要采集下一个样本,您就采集该样本并再次等待。重复此过程,直到缓冲区已满。当它已满时,您处理您的缓冲区。处理完成后,您将重新开始。
这非常耗时,因为您必须等到采集所有样品,并且必须等到所有处理完成,直到您可以处理下一个样品。在“等待”期间,您的音频信号会丢失,因此您将丢失一堆数据。如果您进行 FFT 分析的目的是可视化数据(VU 仪表的频谱分析仪),这可能根本不是问题,而且这种方法可能效果很好。
但是,如果您计划采集超过 8 个样本,它就会变得越来越重要。例如,256,512 个中的 1024 个...然后“等待”将很长,缓冲区数据的处理时间将非常长,以至于在可视化效果中会很明显。
I2S 采样
I2S 采样背后的原理类似于中断驱动采样的原理。
但是,您无需担心时间和中断。I2s 可以直接访问内存,因此能够在没有内核帮助的情况下直接写入内存。这使得它比使用中断驱动采样原理要快得多。
I2S 有 2 个或多个由 I2S 总线/协议处理的缓冲区。你不需要担心,一切都会得到照顾。每当缓冲区已满时,它就会移动到下一个缓冲区。您无需担心要读取哪个缓冲区,因为 I2s 协议会自动为您执行此作。您需要做的就是设置采样频率、缓冲液数量,并且您需要决定每个缓冲液中适合多少样本。
在所有提到的方法中,这是最快的。






顺序采样项目代码
#define NumberofSamples 8
#define audio_in_Pin 32
int Samplefrequency=44100;
int sampling_period = round(1000000*(1.0/Samplefrequency));
int ADCValue[8];
void setup(){
Serial.begin(115200);
}
void loop(){
for (int i=0 ; i < NumberofSamples; i++) {
unsigned long newTime= micros();
ADCValue[i]=analogRead(audio_in_Pin);
while( micros() < newTime+ sampling_period ){ // do nothing}
}
// now the buffer ADCValue is filled with 8 samples of ADC
// data that was sampled at 10 Khz
// now you can process your audio samples
for(int i=0;i<NumberofSamples;i++){
Serial.printf("%7d ",ADCValue[i]);
}
Serial.printf("\n");
// FFT processing
// Displaying of data
// etc.
}
}
I2S 驱动采样项目代码
#include <arduinoFFT.h>
#include <driver/i2s.h>
#define ADC_INPUT ADC1_CHANNEL_0 //pin 32
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
const int numBands =8;
const double samplingFrequency = 10000;
const int SAMPLEBLOCK = 8;
const i2s_port_t I2S_PORT = I2S_NUM_0;
uint16_t offset = (int)ADC_INPUT * 0x1000 + 0xFFF;
uint16_t samples[SAMPLEBLOCK];
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Setting up Audio Input I2S");
setupI2S();
Serial.println("Audio input setup completed");
delay(1000);
}
void loop() {
size_t bytesRead = 0;
i2s_read(I2S_PORT,
(void*)samples,
sizeof(samples),
&bytesRead,
portMAX_DELAY); // no timeout
if (bytesRead != sizeof(samples))
{
Serial.printf("Could only read %u bytes of %u in FillBufferI2S()\n", bytesRead, sizeof(samples));
// return;
}
for (uint16_t i = 0; i < ARRAYSIZE(samples); i++) {
// samples[i] &FFF
Serial.printf("%7d,",offset-samples[i]);
}
Serial.printf("\n");
}
void setupI2S() {
Serial.println("Configuring I2S...");
esp_err_t err;
// The I2S config as per the example
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
.sample_rate = samplingFrequency,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // could only get it to work with 32bits
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // although the SEL config should be left, it seems to transmit on right
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // Interrupt level 1
.dma_buf_count = 4, // number of buffers
.dma_buf_len = SAMPLEBLOCK, // samples per buffer
.use_apll = false//,
// .tx_desc_auto_clear = false,
// .fixed_mclk = 1
};
err = adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_0); //step 1
if (err != ESP_OK) {
Serial.printf("Failed setting up adc channel: %d\n", err);
while (true);
}
err = i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); //step 2
if (err != ESP_OK) {
Serial.printf("Failed installing driver: %d\n", err);
while (true);
}
err = i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT);
if (err != ESP_OK) {
Serial.printf("Failed setting up adc mode: %d\n", err);
while (true);
}
Serial.println("I2S driver installed.");
}
【Arduino 动手做】使用 ESP32 采样音频的最佳方法
项目链接:https://www.instructables.com/The-Best-Way-for-Sampling-Audio-With-ESP32/
项目作者:emdee401
项目视频:
https://www.youtube.com/watch?v=ca2GKE4xkvM
https://www.youtube.com/watch?v=Mgh2WblO5_c
项目代码:
https://www.judoles.nl/Projecten/I2S/I2S_Sample.ino
https://content.instructables.com/F7I/TG1I/KOH6QVWJ/F7ITG1IKOH6QVWJ.ino
https://content.instructables.com/FW4/CKXV/KOLH2HGY/FW4CKXVKOLH2HGY.ino

评论