目录
一.实现原理
二.了解Wave(.wav)文件格式
三.Wave(.wav)文件转换
四.代码实现
一.实现原理
ESP32有两个DAC通道,通道1链接GPIO25, 通道2链接GPIO26。对应着GPIO25可以输出左声道,GPIO26可以输出右声道。我们知道,通过dac_output_voltage()可以设置DAC输出不同的电压。我们要做的就是将wav文件转换为C语言数组,同时通过设置一个高分辨率定时器循环输出数组(定时器频率应该与音频文件采样率一致),即可达到输出音频的效果。
二.了解Wave(.wav)文件格式
Wave文件采用的是RIFF文件结构。RIFF文件是由一个一个的chunk(块)组成的,并且chunk之间可以嵌套。总体来看Wave文件是由多个chunk嵌套组成的。文件头包括了标识符,数据大小,格式判别码,fmt子块和data子块等非音频数据。如果直接引用数组,会输出一端噪音。所以要剔除头文件处数组。
如果想详细了解Wave(.wav)文件格式,可以参考其他博主的文章。
三.wav文件转换
第一步我们应该准备一段音频,将其格式转换为wav文件,并配置其采样率及声道。我这里使用的是格式工厂 官方主页 - 免费多功能的多媒体文件转换工具。
第二步.将我们转换好格式的wav文件转换为C语言数组。我这里使用的是WinHex。当然其他博主那里也有生成数组的代码,我在这不多做阐述。
WinHex下载地址:WinHex: Hex Editor & Disk Editor, Computer Forensics & Data Recovery Software
导入文件之后---->点击编辑---->复制全部---->C源码---->复制到文本文件中代用
四.代码实现
#include "driver/dac.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stdio.h>
#include "esp_timer.h"
extern unsigned char rawData[];
void test_timer_periodic_cb();
// 定义定时器句柄
esp_timer_handle_t test_p_handle = 0;
// 定义一个周期重复运行的定时器结构体
esp_timer_create_args_t test_periodic_arg =
{
.callback = &test_timer_periodic_cb, // 设置回调函数
.arg = NULL, // 不携带参数
.name = "TestPeriodicTimer" // 定时器名字
};
unsigned int i=1;
void test_timer_periodic_cb(void *arg)
{
dac_output_voltage(DAC_CHANNEL_1, rawData[i]+0x80);
/*为什么将提取出来的数据+0x8000,原因就是16的WAV编码采用的就是 -32767~ 32767
在数轴上就是一个x轴上下波动的波形图,可是单片机的DAC里可没有负信号,所以统一加上0x8000;*/
i+=2;//每隔一个数值读取一个
if(i>=N)
{
i=1+128;//跳过wav文件开头非音频数据的类型块标识部分,从头重新播放
}
}
void app_main(void)
{
esp_timer_init(); // 使用定时器API函数,先调用接口初始化
dac_output_enable(DAC_CHANNEL_1);
// 开始创建一个重复周期的定时器并且执行
esp_err_t err = esp_timer_create(&test_periodic_arg, &test_p_handle);
err = esp_timer_start_periodic(test_p_handle,90);//我的.wav文件采样率为11025
printf("重复周期运行的定时器创建状态码: %s", err == ESP_OK ? "ok!\r\n" : "failed!\r\n");
while(1)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
unsigned char rawData[N] = {};//第二步wav文件转换得到的数组