音频编码ES7210调试笔记(二)
继续解读ES7210的使用
理解wav文件格式
能顺利初始化ES7210后,就顺便把录音过程,也就是用I2S从ES7210读取数据后录音简单的梳理了下,和I2C一样,图方便直接用官方的I2S,重点是WAV的录制.
这里顺便说个坑,乐鑫官方的I2C程序中有个辅助获得I2C句柄的函数i2c_master_get_bus_handle,其实是IDF5.4版本中才有的,如果自己机子上版本低了,就不要用这个函数,用参数传递或全局变量就行了.
WAV采用RIFF(Resource Interchange File Format)文件格式结构。
乐鑫官方的方法很简单,WAV文件一般由3个区块组成:RIFF chunk、Format chunk和Data chunk(这个块就是es7210通过i2s传来的数据,pcm编码),事先将WAV的前两个区块RIFF chunk、Format chunk定义成结构体,然后根据参数赋值,写入文件,再接着把I2S收到的实时数据作为Data chunk追加写入文件就成了WAV文件.具体结构体中每个变量的内容很好理解,WAV的格式网上也有很多详细介绍.
具体流程为:
//定义结构体
typedef struct {
struct {
char chunk_id[4]; /*!< Contains the letters "RIFF" in ASCII form */
uint32_t chunk_size; /*!< This is the size of the rest of the chunk following this number */
char chunk_format[4]; /*!< Contains the letters "WAVE" */
} descriptor_chunk; /*!< Canonical WAVE format starts with the RIFF header */
struct {
char subchunk_id[4]; /*!< Contains the letters "fmt " */
uint32_t subchunk_size; /*!< This is the size of the rest of the Subchunk which follows this number */
uint16_t audio_format; /*!< PCM = 1, values other than 1 indicate some form of compression */
uint16_t num_of_channels; /*!< Mono = 1, Stereo = 2, etc. */
uint32_t sample_rate; /*!< 8000, 44100, etc. */
uint32_t byte_rate; /*!< ==SampleRate * NumChannels * BitsPerSample s/ 8 */
uint16_t block_align; /*!< ==NumChannels * BitsPerSample / 8 */
uint16_t bits_per_sample; /*!< 8 bits = 8, 16 bits = 16, etc. */
} fmt_chunk; /*!< The "fmt " subchunk describes the sound data's format */
struct {
char subchunk_id[4]; /*!< Contains the letters "data" */
uint32_t subchunk_size; /*!< ==NumSamples * NumChannels * BitsPerSample / 8 */
int16_t data[0]; /*!< Holds raw audio data */
} data_chunk; /*!< The "data" subchunk contains the size of the data and the actual sound */
} wav_header_t;
ES7210录音
假设固定录10秒,事先有存储的SD卡已挂载为"/SDCARD"
根据ES7210初始化的参数,赋值这块需要事先准备的内容有:
首先要知道这个WAV文件有多大,因为WAV前两个块大小是固定的,后面录音这块需要根据事先定制的时间计算.如录制10秒,那么
10(秒)*每秒数据量就可以得出第三个块大小,再加上前两个区块大小就可以得到WAV文件大小.
WAV每秒的数据量 = 采样率 * 通道数(就是TDM下几个MIC通道) * 位宽 / 8
最终WAV的大小 = (录音时长 * 每秒数据量+结构体的大小 - 8)
结构体成员变量chunk_size里面存的就是最终WAV的大小,其他的内容后面都有注释,很好理解,但有个减去了8的计算,刚开始我也没注意.这个大小其实是指从这之后还有多少字节的数据,也就是不包括id的4个字节和size的4个字节,所以结构体大小要减去8.
下面解读录制程序.
//按事先定义好的时间采集I2S通道里实时传来的数据
//为便于理解,精简了一些错误处理和检查代码,以下代码未编译验证
record_wav(i2s_chan_handle_t i2s_rx_chan)
{
uint32_t byte_rate = EXAMPLE_I2S_SAMPLE_RATE * EXAMPLE_I2S_CHAN_NUM * EXAMPLE_I2S_SAMPLE_BITS / 8;
uint32_t wav_size = byte_rate * EXAMPLE_RECORD_TIME_SEC;
//计算相关数值并给代表WAV前两个区块的结构体赋值
const wav_header_t wav_header =
WAV_HEADER_PCM_DEFAULT(wav_size, EXAMPLE_I2S_SAMPLE_BITS, EXAMPLE_I2S_SAMPLE_RATE, EXAMPLE_I2S_CHAN_NUM);
FILE *f = fopen("/SDCARD" "录制的文件名.WAV", "w");
/* Write wav header */
fwrite(&wav_header, sizeof(wav_header_t), 1, f);
/* Start recording */
size_t wav_written = 0;
static int16_t i2s_readraw_buff[4096]; //定义数据接受I2S使用的DMA数据
i2s_channel_enable(i2s_rx_chan) //官方的使能I2S通道,i2s_rx_chan是初始化I2S后的句柄
while (wav_written < wav_size) {
if (wav_written % byte_rate < sizeof(i2s_readraw_buff)) {
ESP_LOGI(TAG, "Recording: %"PRIu32"/%ds", wav_written / byte_rate + 1, (int)EXAMPLE_RECORD_TIME_SEC);
}//这段是用来在屏幕上输出录制时间,以秒为单位
size_t bytes_read = 0;
/* Read RAW samples from ES7210 */
//用官方的I2S接口去读实时录音数据,并存入i2s_readraw_buff这个数组
i2s_channel_read(i2s_rx_chan, i2s_readraw_buff, sizeof(i2s_readraw_buff), &bytes_read, pdMS_TO_TICKS(1000));
/* Write the samples to the WAV file */
fwrite(i2s_readraw_buff, bytes_read, 1, f);
wav_written += bytes_read;
}
}
计算相关数值并给代表WAV前两个区块的结构体赋值的程序
//给结构体赋值
WAV_HEADER_PCM_DEFAULT(wav_sample_size, wav_sample_bits, wav_sample_rate, wav_channel_num) { \
.descriptor_chunk = { \
.chunk_id = {'R', 'I', 'F', 'F'}, \
.chunk_size = (wav_sample_size) + sizeof(wav_header_t) - 8, \
.chunk_format = {'W', 'A', 'V', 'E'} \
}, \
.fmt_chunk = { \
.subchunk_id = {'f', 'm', 't', ' '}, \
.subchunk_size = 16, /* 16 for PCM */ \
.audio_format = 1, /* 1 for PCM */ \
.num_of_channels = (wav_channel_num), \
.sample_rate = (wav_sample_rate), \
.byte_rate = (wav_sample_bits) * (wav_sample_rate) * (wav_channel_num) / 8, \
.block_align = (wav_sample_bits) * (wav_channel_num) / 8, \
.bits_per_sample = (wav_sample_bits)\
}, \
.data_chunk = { \
.subchunk_id = {'d', 'a', 't', 'a'}, \
.subchunk_size = (wav_sample_size) \
} \
}
如果是想任意时长录音,那就先保存I2S的数据为一个临时文件,录音结束后根据长度再制作WAV前面两个块,组合成一个新文件就可以了.
到这,使用ES7210就没什么问题了,但没有写停止ES7210工作的程序.看官方新的ES7210芯片驱动中启动和停止函数中如下表述,理解起来不难,对比下初始化过程改下接口函数就能用了.
static int es7210_start(audio_codec_es7210_t *codec, uint8_t clock_reg_value)
{
int ret = 0;
ret |= es7210_write_reg(codec, ES7210_CLOCK_OFF_REG01, clock_reg_value);
ret |= es7210_write_reg(codec, ES7210_POWER_DOWN_REG06, 0x00);
ret |= es7210_write_reg(codec, ES7210_ANALOG_REG40, 0x43);
ret |= es7210_write_reg(codec, ES7210_MIC1_POWER_REG47, 0x08);
ret |= es7210_write_reg(codec, ES7210_MIC2_POWER_REG48, 0x08);
ret |= es7210_write_reg(codec, ES7210_MIC3_POWER_REG49, 0x08);
ret |= es7210_write_reg(codec, ES7210_MIC4_POWER_REG4A, 0x08);
ret |= es7210_mic_select(codec, codec->mic_select);
ret |= es7210_write_reg(codec, ES7210_ANALOG_REG40, 0x43);
ret |= es7210_write_reg(codec, ES7210_RESET_REG00, 0x71);
ret |= es7210_write_reg(codec, ES7210_RESET_REG00, 0x41);
return ret;
}
static int es7210_stop(audio_codec_es7210_t *codec)
{
int ret = 0;
ret |= es7210_write_reg(codec, ES7210_MIC1_POWER_REG47, 0xff);
ret |= es7210_write_reg(codec, ES7210_MIC2_POWER_REG48, 0xff);
ret |= es7210_write_reg(codec, ES7210_MIC3_POWER_REG49, 0xff);
ret |= es7210_write_reg(codec, ES7210_MIC4_POWER_REG4A, 0xff);
ret |= es7210_write_reg(codec, ES7210_MIC12_POWER_REG4B, 0xff);
ret |= es7210_write_reg(codec, ES7210_MIC34_POWER_REG4C, 0xff);
ret |= es7210_write_reg(codec, ES7210_ANALOG_REG40, 0xc0);
ret |= es7210_write_reg(codec, ES7210_CLOCK_OFF_REG01, 0x7f);
ret |= es7210_write_reg(codec, ES7210_POWER_DOWN_REG06, 0x07);
return ret;
}
IDF官方已经更新了ES7210组件,原来的老组件停止更新,并且老组件是结合老的I2C驱动使用的(原i2c.h,现在是i2c_master.h,不兼容),现在使用的是esp_codec_dev组件,里面包含了ES7210还有播放声音的ES8311等一些多个音频编码解码芯片驱动,有些初始化参数有调整,调用I2C的接口函数全部变了,还不兼容老的,看着套娃一样的参数传递,移植时有些头疼,业余时间不足也影响了进度.关于寄存器虽然通过现在的解读也编程完成了录音试验,但对确保正确控制硬件不出错没有把握.准备和ES8311的寄存器一起研究,把涉及的寄存器都了解下,好重新自己制作这些器件库,不再依赖官方驱动组件了.
个人以为,喜欢DIY的业余爱好者玩单片机,更重要的是了解硬件在如何工作,解读官方程序可以为适配不同硬件,灵活更换器件带来帮助.
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)