用DAC解码PCM数据播放WAV格式音频文件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27508477/article/details/83245300

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左右超出

如有错误欢迎斧正,相互学习,共同进步。

猜你喜欢

转载自blog.csdn.net/qq_27508477/article/details/83245300