【ESP32 在线语音】音频接收的缓存机制和网络发送分包机制
首先是初始化 I2S 设备中,可能用到了缓存
//初始化 I2S 设备 INMP441
Serial.println("Setup I2S ...");
i2s_install();
i2s_setpin();
esp_err_t err = i2s_start(I2S_PORT_0);
其中的 i2s_install() 配置了 i2s 的相关设置,函数具体内容如下:
/**
* @brief 配置 i2s 参数
*
*/
void i2s_install()
{
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = i2s_bits_per_sample_t(16),
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S),
.intr_alloc_flags = 0, // default interrupt priority
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll = false};
esp_err_t err = i2s_driver_install(I2S_PORT_0, &i2s_config, 0, NULL);
if (err != ESP_OK)
{
Serial.printf("I2S driver install failed (I2S_PORT_0): %d\n", err);
while (true)
;
}
else
{
Serial.printf("I2S driver install OK\r\n");
}
}
其中,与缓存有关的包括下面的结构体成员和 i2s_driver_install() 函数:
.dma_buf_count = 8,
.dma_buf_len = 1024,
int dma_buf_count; /**< The total number of DMA buffers to receive/transmit data.
* A descriptor includes some information such as buffer address,
* the address of the next descriptor, and the buffer length.
* Since one descriptor points to one buffer, therefore, 'dma_desc_num' can be interpreted as the total number of DMA buffers used to store data from DMA interrupt.
* Notice that these buffers are internal to'i2s_read' and descriptors are created automatically inside of the I2S driver.
* Users only need to set the buffer number while the length is derived from the parameter described below.
*/
int dma_buf_len; /**< Number of frames in a DMA buffer.
* A frame means the data of all channels in a WS cycle.
* The real_dma_buf_size = dma_buf_len * chan_num * bits_per_chan / 8.
* For example, if two channels in stereo mode (i.e., 'channel_format' is set to 'I2S_CHANNEL_FMT_RIGHT_LEFT') are active,
* and each channel transfers 32 bits (i.e., 'bits_per_sample' is set to 'I2S_BITS_PER_CHAN_32BIT'),
* then the total number of bytes of a frame is 'channel_format' * 'bits_per_sample' = 2 * 32 / 8 = 8 bytes.
* We assume that the current 'dma_buf_len' is 100, then the real length of the DMA buffer is 8 * 100 = 800 bytes.
* Note that the length of an internal real DMA buffer shouldn't be greater than 4092.
*/
从注释(Notice that these buffers are internal to'i2s_read)可以得到 , DMA功能的使用,指定即可,而不需要自己实现——内部会自动完成相关的配置。
i2s_driver_install() 函数定义如下
/**
* @brief Install and start I2S driver.
*
* @param i2s_num I2S port number
*
* @param i2s_config I2S configurations - see i2s_config_t struct
*
* @param queue_size I2S event queue size/depth.
*i2s 队列尺寸 I2S事件队列尺寸
* @param i2s_queue I2S event queue handle, if set NULL, driver will not use an event queue.
* /*i2s 队列 I2S事件队列句柄
* This function must be called before any I2S driver read/write operations.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM Out of memory
* - ESP_ERR_INVALID_STATE Current I2S port is in use
*/
esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void *i2s_queue);
实际代码中没有使用,暂时也不知道所谓的 I2S 时间是什么?(莫非可能自动检测VAD端点检测?)
I2S读取真正的过程如下:
/**
* @brief
* recordingSize:采样得到的字节数的1/2 或者说uint16_t 的个数(每次采样,深度为16bit,得到2字节)
* recordTimeSeconds:此处为3s
* SAMPLE_RATE:此处为16k
* recordTimeSeconds * SAMPLE_RATE 表示i2s采集,经过3s所得到的 uint16_t 的个数
*/
while (recordingSize < recordTimeSeconds * SAMPLE_RATE)
{
// 开始循环录音,将录制结果保存在pcm_data中
esp_err_t result = i2s_read(I2S_PORT_0, audioData, sizeof(audioData), &bytes_read, portMAX_DELAY);
memcpy(pcm_data + recordingSize/*pcm_data 是 uint16_t 类型的指针,因此recordingSize不需要乘2*/,
audioData,
bytes_read);
recordingSize += bytes_read / 2; //此处除以2,很有意思
}
这段代码一直读取 i2s 数据到 audioData 中,并将读取到的字节数赋值给 bytes_read, 直到读取到3s的音频为止(前提:在 i2s 初始化中,已经将采样率设置为了 16k)
其中主要用到了 i2s_read() 函数。
/**
* @brief Read data from I2S DMA receive buffer
*
* @param i2s_num I2S port number
*
* @param dest Destination address to read into
* 数据的目的地
* @param size Size of data in bytes
* 数据目的地的容量
* @param[out] bytes_read Number of bytes read, if timeout, bytes read will be less than the size passed in.
* 实际读出数据
* @param ticks_to_wait RX buffer wait timeout in RTOS ticks. If this many ticks pass without bytes becoming available in the DMA receive buffer, then the function will return (note that if data is read from the DMA buffer in pieces, the overall operation may still take longer than this timeout.) Pass portMAX_DELAY for no timeout.
*
* @note If the built-in ADC mode is enabled, we should call i2s_adc_enable and i2s_adc_disable around the whole reading process,
* to prevent the data getting corrupted.
*
* @return
* - ESP_OK Success
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t i2s_read(i2s_port_t i2s_num, void *dest, size_t size, size_t *bytes_read, TickType_t ticks_to_wait);
代码展望:
-
阻塞式读取的风险:使用
portMAX_DELAY意味着无限期等待数据,在实时系统中可能造成线程阻塞,缺乏超时保护机制。 -
内存拷贝的性能损耗:每次读取都执行
memcpy到全局缓冲区,在高速音频流处理中可能成为性能瓶颈,直接DMA传输会是更优选择。
以下是讯飞星火语音听写的接口调用流程:从下面可以看到,上传音频的时候还需要特殊的处理 https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B

由于采集到的是PCM格式的数据,因此设置每次传输 1280 字节数据(实测:一次发送1280x8字节数据是可行的,而且反应速度更快)
另外:需要科普一下"帧"的概念:从一个问题引出——16k 采样率 单声道,16bit深度的PCM音频,其一帧大小是多少
重要澄清:帧 vs. 数据包
在实际编程和处理中(例如使用WebRTC、Opus编码器等),人们常说的“帧”可能指的是一个时间块,比如20ms的音频数据。这更像一个数据包。
我们来计算一下一个 20ms的数据包 有多大:
-
计算20ms内的样本数量:
采样率 × 时间 = 16000 样本/秒 × 0.020 秒 = 320 个样本 -
计算这个数据包的总大小:
样本数量 × 每样本字节数 × 声道数 = 320 × 2 字节 × 1 = 640 字节
所以,虽然技术上一帧是2字节,但在讨论网络传输或编码时,一个包含320个样本(即320帧)、大小为640字节的数据块,也经常被称作“一帧音频”或“一个音频包”。
(对于科大讯飞星火模型而言,其时间块长度应该为40ms,所以文档中写为 1280 字节)
简短回答
对于 16kHz, 单声道, 16bit 的PCM音频,一帧的大小是 2 字节。
详细解释
要理解这个答案,我们需要先明确几个概念:
-
采样率:每秒采集(或播放)多少个样本。16kHz 表示每秒 16,000 个样本。
-
声道:单声道 表示只有1个声音通道。
-
位深度:每个样本用多少比特来表示。16bit 表示每个样本占用 16 个比特。
-
帧:在PCM音频中,一帧等于一个采样点在所有声道上的数据总和。
计算步骤
对于单声道音频,计算非常简单:
一帧大小(字节) = (位深度 / 8) × 声道数
-
位深度(字节):
16 bit / 8 = 2 字节(因为1字节=8比特,这是每个样本的大小) -
声道数:
1
所以:
一帧大小 = 2 字节/样本 × 1 声道 = 2 字节
总结
-
从纯PCM格式定义上讲:一帧 = 2 字节。
-
这是音频处理底层(如直接操作PCM数组)时最精确的概念。
-
-
从实时音频传输/编码的上下文上讲:一帧(或一个包)可能指 640 字节(对应20ms时长)。
-
这是在高层次应用(如WebRTC, VoIP)中更常见的说法。
-
在与人沟通或编写代码时,请务必根据上下文确认“帧”的具体含义。如果您是在处理原始的PCM字节流,那么 2字节 是正确的答案。

浙公网安备 33010602011771号