WAV音频用的是PCM协议,大致就是前面44字节的一堆描述,用于辨别文件类型、大小,后面一堆音频数据。
关于WAV格式、RIFF格式、PCM协议这些的关系,在这篇文章描述得很详细,这里就不做介绍了。
RIFF和WAVE音频文件格式
先看代码:
void readWave()
{
FIL *f_test;
u8 buffer[100];
u8 res;
int a;
UINT br;
unsigned int FATFSNumSize;
u16 pcmDataSize=0; //用于计算一次性读取多少字节的数据给DAC
u16 fmt_samplehz=0; //用于计算多久扔一次数据给DAC
f_test=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
if(f_test==NULL)
{
printf("申请内存失败\r\n");
return;
}
f_open(f_test, "0:1.wav",FA_OPEN_EXISTING | FA_READ);
FATFSNumSize = f_size(f_test); //fat32文件系统返回的文件大小
printf("file size:%d\r\n\r\n",FATFSNumSize);
res = f_read(f_test,buffer, 44, &br); //将文件内容读出到数据缓冲区 br存储此次读出数据的数量,最大512
printf("RIFF chunck:%c%c%c%c\r\n",buffer[0],buffer[1],buffer[2],buffer[3]); //值为'R' 'I' 'F' 'F'
printf("RIFF size:%d\r\n",buffer[4]+buffer[5]*256+buffer[6]*256*256+buffer[7]*256*256*256);
printf("RIFF data:%c%c%c%c\r\n",buffer[8],buffer[9],buffer[10],buffer[11]); //值为 'W' 'A' 'V' 'E'
printf("\tFormat sub-chunck:%c%c%c%c\r\n",buffer[12],buffer[13],buffer[14],buffer[15]); //值为 'f' 'm' 't' ' '
printf("\tFormat size:%d\r\n",buffer[16]+buffer[17]*256+buffer[18]*256*256+buffer[19]*256*256*256);
printf("\tFormat data:\r\n");
printf("\t\tFormat tag:%d\r\n",buffer[20]+buffer[21]*256); //如值为1,表示使用PCM格式
printf("\t\tFormat channel:%d\r\n",buffer[22]+buffer[23]*256); //声道数。值为1则为单声道,为2则是双声道。
printf("\t\tFormat fmt_samplehz:%d\r\n",buffer[24]+buffer[25]*256+buffer[26]*256*256+buffer[27]*256*256*256);//采样率,主要有22.05KHz,44.1kHz和48KHz。
printf("\t\tFormat fmt_bytepsec:%d\r\n",buffer[28]+buffer[29]*256+buffer[30]*256*256+buffer[31]*256*256*256);//音频的码率,每秒播放的字节数,一般一分钟读至Buffer一次
printf("\t\tFormat fmt_bytesample:%d\r\n",buffer[32]+buffer[33]*256);//数据块对齐单位,一次采样的大小,值为声道数 * 量化位数 / 8,用于一次扔给1/2路DAC
printf("\t\tFormat fmt_bitpsample:%d\r\n",buffer[34]+buffer[35]*256);//音频sample的量化位数,有16位,24位和32位等。用于配置DAC
printf("\tData sub_chunk:%c%c%c%c\r\n",buffer[36],buffer[37],buffer[38],buffer[39]);// 值为'd' 'a' 't' 'a'
printf("\tData size:%d\r\n",buffer[40]+buffer[41]*256+buffer[42]*256*256+buffer[43]*256*256*256);
printf("\tData data:\r\n");
//以上都是废话,和解码没有任何关系,仅调试用而已
pcmDataSize=(buffer[34]+buffer[35]*256)/8*(buffer[22]+buffer[23]*256); //一个采样点的数据大小:采样精度/8*声道数(即16/8*2=4byte)
fmt_samplehz=buffer[24]+buffer[25]*256+buffer[26]*256*256+buffer[27]*256*256*256; //采样率
while(1)
{
TIM_SetCounter(TIM3,0);//重设TIM3定时器的计数器值
//循环读出被打开文件的扇区
res = f_read(f_test,buffer,pcmDataSize, &br); //将文件内容读出到数据缓冲区 br存储此次读出数据的数量,最大512
//高位用补码形式保存的,转换时需要先减去0x80
DAC_SetChannel2Data(DAC_Align_12b_R,(buffer[0]+(buffer[1]-0x80)*256)>>4);//12位右对齐数据格式设置DAC值
time=0;
timeout=0;
while(time<(1000000/fmt_samplehz))
{
time=TIM_GetCounter(TIM3)+(u32)timeout*65536; //计算所用时间
}
if (res || br == 0) break; // error or eof //判断是否到文件结束
}
f_close(f_test);
myfree(SRAMIN,f_test); //释放内存
printf("read ok\r\n");
}
代码是根据正点原子F407的 实验39 FATFS实验 例程修改,添加了DAC和TIM模块。
代码先是输出了一堆文件信息,然后是定时像DAC里面扔数据。
虽然这里是双通道的数据,但是因为我这里只有单路功放和喇叭,所以一次性读4个字节的数据,却只用了2个字节。
由于16bit的数据是unsigned int
型,用的是补码存放,所以需要处理掉。
因为stm32是12bit的DAC,所以整体需要右移4位,损失4bit的精度,保留大头。
DAC_SetChannel2Data(DAC_Align_12b_R,(buffer[0]+(buffer[1]-0x80)*256)>>4);
如果是8bit的数据,我想直接这样就可以了。
DAC_SetChannel2Data(DAC_Align_8b_R,buffer[0]);
代码中的那些printf输出内容如下:
file size:43105937
RIFF chunck:RIFF
RIFF size:43105812
RIFF data:WAVE
Format sub-chunck:fmt
Format size:16
Format data:
Format tag:1
Format channel:2
Format fmt_samplehz:44100
Format fmt_bytepsec:176400
Format fmt_bytesample:4
Format fmt_bitpsample:16
Data sub_chunk:data
Data size:43105776
Data data:
这些就是文件头中的信息,我们比较关注的其实就是采样率、通道数、比特率这些东西。
用的TC8002E功放芯片及4欧3W的小喇叭,最终的播放还是有些杂音,不知道是因为精度损失了还是定时器不够准的原因。
附DAC初始化代码:
void Dac2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitType;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);//使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);//使能DAC时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;//模拟输入
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化
DAC_InitType.DAC_Trigger=DAC_Trigger_None; //不使用触发功能 TEN1=0
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;//屏蔽、幅值设置
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1输出缓存关闭 BOFF1=1
DAC_Init(DAC_Channel_2,&DAC_InitType); //初始化DAC通道1
DAC_Cmd(DAC_Channel_2, ENABLE); //使能DAC通道1
DAC_SetChannel2Data(DAC_Align_12b_R, 0); //12位右对齐数据格式设置DAC值
}
附定时器初始化代码:
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器3更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
//LED1=!LED1;
timeout++;
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
TIM3_Int_Init(65535,84-1); //1Mhz计数频率,最大计时65ms左右超出
如有错误欢迎斧正,相互学习,共同进步。