我结合了GitHub上esp-idf repo中的一些i2 s-adc和sd卡示例,制作了一个程序,对模拟麦克风进行采样,并将录音作为WAV文件存储在SD卡中。问题是,我录制的音频总是以设置为采样的频率的一半播放。
如下面的代码所示,I2S_SAMPLE_RATE
被设置为44100 Hz,但如果我播放,听起来好像是在88200 Hz附近采样的。
-I2 s驱动程序以较高的采样率工作。
我已经试过用i2s_set_clk
对频率进行编码,但没有运气。
-录制时深度较低,因此会出现一些失真。
我知道esp 32有一个12位ADC,我将BIT_SAMPLE
设置为16,但这是否足以导致我所描述的失真类型?
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_system.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "driver/gpio.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "sdkconfig.h"
#include "esp_adc_cal.h"
#include "driver/adc.h"
#define V_REF 1100
#define I2S_COMM_MODE 0 // ADC/DAC Mode
#define I2S_SAMPLE_RATE 44100
#define I2S_SAMPLE_BITS 16
#define I2S_BUF_DEBUG 0 // enable display buffer for debug
#define I2S_READ_LEN 16 * 1024 // I2S read buffer length
#define I2S_FORMAT (I2S_CHANNEL_FMT_RIGHT_LEFT)
#define I2S_CHANNEL_NUM 0 // I2S channel number
#define I2S_ADC_UNIT ADC_UNIT_1 // I2S built-in ADC unit
#define I2S_ADC_CHANNEL ADC1_CHANNEL_0 // I2S built-in ADC channel GPIO36
#define SPI_MOSI_GPIO 23
#define SPI_MISO_GPIO 19
#define SPI_CLK_GPIO 18
#define SPI_CS_GPIO 5
static const char *TAG = "I2S_ADC_REC";
#define BIT_SAMPLE 16
#define SPI_DMA_CHAN SPI_DMA_CH_AUTO
#define NUM_CHANNELS 1 // For mono recording only!
#define SD_MOUNT_POINT "/sdcard"
#define SAMPLE_SIZE (BIT_SAMPLE * 1024)
#define BYTE_RATE (I2S_SAMPLE_RATE * (BIT_SAMPLE / 8)) * NUM_CHANNELS
// When testing SD and SPI modes, keep in mind that once the card has been
// initialized in SPI mode, it can not be reinitialized in SD mode without
// toggling power to the card.
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
sdmmc_card_t *card;
static int16_t i2s_readraw_buff[SAMPLE_SIZE];
size_t bytes_read;
const int WAVE_HEADER_SIZE = 44;
/**
* @brief Initializes the slot without card detect (CD) and write protect (WP) signals.
* It formats the card if mount fails and initializes the card. After the card has been
* initialized, it print the card properties
*/
void mount_sdcard(void)
{
esp_err_t ret;
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, SD card will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
.allocation_unit_size = 8 * 1024};
ESP_LOGI(TAG, "Initializing SD card");
spi_bus_config_t bus_cfg = {
.mosi_io_num = SPI_MOSI_GPIO,
.miso_io_num = SPI_MISO_GPIO,
.sclk_io_num = SPI_CLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialize bus.");
return;
}
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = SPI_CS_GPIO;
slot_config.host_id = host.slot;
ret = esp_vfs_fat_sdspi_mount(SD_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(TAG, "Failed to mount filesystem.");
}
else
{
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.",
esp_err_to_name(ret));
}
return;
}
sdmmc_card_print_info(stdout, card); // Card has been initialized, print its properties
}
/**
* @brief Generates the header for the WAV file that is going to be stored in the SD card.
* See this for reference: http://soundfile.sapp.org/doc/WaveFormat/.
*/
void generate_wav_header(char *wav_header, uint32_t wav_size, uint32_t sample_rate)
{
uint32_t file_size = wav_size + WAVE_HEADER_SIZE - 8;
uint32_t byte_rate = BYTE_RATE;
const char set_wav_header[] = {
'R', 'I', 'F', 'F', // ChunkID
file_size, file_size >> 8, file_size >> 16, file_size >> 24, // ChunkSize
'W', 'A', 'V', 'E', // Format
'f', 'm', 't', ' ', // Subchunk1ID
0x10, 0x00, 0x00, 0x00, // Subchunk1Size (16 for PCM)
0x01, 0x00, // AudioFormat (1 for PCM)
0x01, 0x00, // NumChannels (1 channel)
sample_rate, sample_rate >> 8, sample_rate >> 16, sample_rate >> 24, // SampleRate
byte_rate, byte_rate >> 8, byte_rate >> 16, byte_rate >> 24, // ByteRate
0x02, 0x00, // BlockAlign
0x10, 0x00, // BitsPerSample (16 bits)
'd', 'a', 't', 'a', // Subchunk2ID
wav_size, wav_size >> 8, wav_size >> 16, wav_size >> 24, // Subchunk2Size
};
memcpy(wav_header, set_wav_header, sizeof(set_wav_header));
}
/**
* @brief I2S ADC mode init.
*/
void init_microphone(void)
{
int i2s_num = I2S_COMM_MODE;
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN,
.sample_rate = I2S_SAMPLE_RATE,
.bits_per_sample = I2S_SAMPLE_BITS,
.communication_format = I2S_COMM_FORMAT_STAND_MSB,
.channel_format = I2S_FORMAT,
.intr_alloc_flags = 0,
.dma_buf_count = 6,
.dma_buf_len = 256,
.use_apll = 1,
};
// Call driver installation function and adc pad.
ESP_ERROR_CHECK(i2s_driver_install(i2s_num, &i2s_config, 0, NULL));
ESP_ERROR_CHECK(i2s_set_adc_mode(I2S_ADC_UNIT, I2S_ADC_CHANNEL));
}
void record_wav(uint32_t rec_time)
{
// Use POSIX and C standard library functions to work with files.
int flash_wr_size = 0;
ESP_LOGI(TAG, "Opening file");
char wav_header_fmt[WAVE_HEADER_SIZE];
uint32_t flash_rec_size = BYTE_RATE * rec_time;
generate_wav_header(wav_header_fmt, flash_rec_size, I2S_SAMPLE_RATE);
// First check if file exists before creating a new file.
struct stat st;
if (stat(SD_MOUNT_POINT "/record.wav", &st) == 0)
{
// Delete it if it exists
unlink(SD_MOUNT_POINT "/record.wav");
}
// Create new WAV file
FILE *f = fopen(SD_MOUNT_POINT "/record.wav", "a");
if (f == NULL)
{
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
// Write the header to the WAV file
fwrite(wav_header_fmt, 1, WAVE_HEADER_SIZE, f);
i2s_adc_enable(I2S_CHANNEL_NUM);
// Start recording
while (flash_wr_size < flash_rec_size)
{
// TODO: The part below needs to be replaced with analog microphone reading //
// Read the RAW samples from the microphone
// Read data from I2S bus, in this case, from ADC. //
i2s_read(I2S_CHANNEL_NUM, (char *)i2s_readraw_buff, SAMPLE_SIZE, &bytes_read, 100);
// TODO: The part above needs to be replaced with analog microphone reading //
// Write the samples to the WAV file
fwrite(i2s_readraw_buff, 1, bytes_read, f);
flash_wr_size += bytes_read;
}
ESP_LOGI(TAG, "Recording done!");
fclose(f);
ESP_LOGI(TAG, "File written on SDCard");
// All done, unmount partition and disable SPI peripheral
esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, card);
ESP_LOGI(TAG, "Card unmounted");
// Deinitialize the bus after all devices are removed
spi_bus_free(host.slot);
}
void app_main(void)
{
int rec_time = 5;
ESP_LOGI(TAG, "Analog microphone recording Example start");
// Mount the SDCard for recording the audio file
mount_sdcard();
// I2S ADC mode microphone init.
init_microphone();
ESP_LOGI(TAG, "Starting recording for %d seconds!", rec_time);
// Start Recording
record_wav(rec_time);
// Stop I2S driver and destroy
ESP_ERROR_CHECK(i2s_driver_uninstall(I2S_COMM_MODE));
}
让我知道你认为问题出在哪里,或者/以及你是否有关于这个主题的学习材料的好建议。
1条答案
按热度按时间arknldoa1#
最近与I2 S的一次“擦肩而过”让我意识到了它的一系列功能。
当扫描你的代码并思考你的描述(“似乎在88200 Hz采样”)时,令牌
I2S_CHANNEL_FMT_RIGHT_LEFT
吸引了我的眼球。知道你将记录一个单声道信号(没有深入研究I2 S),它似乎是合理的建议设置配置为一个通道只有;即
I2S_CHANNEL_FMT_ONLY_LEFT
(或...右)。(如果您以前查看过WAV录音的“十六进制转储”,我猜您会看到@pmacfarlane指向的内容:一半的样本值是重复的或者是0,并且可以/必须被丢弃。正如您所发现的那样,这样一个强大的系统(I2 S)也可以理解
I2S_CHANNEL_MONO
,我很高兴您找到了解决方案。至于“怪”,只有当学徒从师傅那里学到很多东西时,“怪”才会成熟为理解和欣赏。以前经历过的人都不是“没精打采”的。
加班费:当您尝试采样和写入SD卡时(micro SD?),请注意写入可能会有很大的“开销”。写入10 K所需的uSeconds远远超过写入100 K所需的uSeconds的1/10。我怀疑写入发生前的“设置”是实质性的。最好写入大块,而不是可能导致您错过其他事件(如I2 S填充其DMA缓冲区)的小片段。
取样愉快!