基于STM32的WAV音频格式播放器

2019-07-20 22:55发布

正在做的项目中需要STM32SD卡中读取语音文件进行播放,因此需要对语音进行解码,刚开始就一直使用Speex的音频压缩格式,最近发现,在进行语音格式转换时,我们不能很好地分析spx格式音频文件的文件头,这样就会导致语音的播放出现问题。由于WAV采用PCM编码,音质也十分不错,于是考虑用STM32WAV格式音频文件进行解码,上周末开始找资料和编程,其中也遇到了不少问题,不过功夫不负有心人,最终还是顺利的跑起来了。先将资料和编程过程整理成本文,供大家一起学习和进步。 WAV文件格式是一种重要的用于存放声音文件的文件格式,尽管现在有MP3RAM等压缩效率更高的声音文件格式,并且广泛被音乐文件所采用,但是又很多的应用程序仍然采用WAV文件格式。由于WAV文件没有采用压缩技术,所以它的文件很庞大,一般都在几MB以上。但也正是因为没有采用压缩技术,声音的采样数据很容易被读出来,便于用作其他的处理。 废话不多说了,我们直接去解析WAV文件格式吧。 WAV格式符合RIFFResource interchange File Format)规范。所有的WAV都有一个头文件,这个头文件音频流的编码参数。
1WAV文件的文件头
2WAV声音文件的数据块
接下来我们用已经编好的程序来读取一个WAV文件的文件头和数据块,看看各个内容都表示什么含义。

1WAV源文件

2、用WinHex软件解析WAV

    3STM32读取WAV的信息
头文件样例说明:
?        “52 49 46 46”这个是Ascii字符“RIFF”,这部分是固定格式,表明这是一个WAVE文件头。 ?        “24 33 AE 00”这个是我的WAV文件的数据大小,这个大小包括除了前面4个字节的所有字节,也就是等于文件总字节数减去8。得到图3中的1141635611416356+8=11416364Byte=10.88Mb ?        “57 41 56 45 66 6D 74 20”,也是Ascii字符“WAVEfmt”,这部分是固定格式。以后是PCMWAVEFORMAT部分。 ?        “10 00 00 00”,这是一个DWORD,对应数字16,这个对应定义中的PCMWAVEFORMAT部分的大小,可以看到后面的这个段内容正好是16个字节。当为16时,最后是没有附加信息的,当为数字18时,最后多了两个字节的附加信息。 ?        “01 00”,这是一个WORD,对应定义为编码格式(WAVE_FORMAT_PCM格式用的就是这个)。 ?        “01 00”,这是一个WORD,对应数字1,表示声道数为1,是个单声道WAV,当值为2时为立体声WAV ?        “22 56 00 00”对应数字22050,代表的是采样频率220505,采样率(每秒样本数)表示每个通道的播放速度。 ?        “44 AC 00 00”对应数字44100,代表的是每秒的数据量,波形音频数据传送数率,其值为通道数×每秒样本数×每个样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。 ?        “02 00:”对应数字是2,表示块对齐的内容。数据块的调整数(按字节算),其值为通道数×每个样本的数据位置/8.播放软件需要一次处理多个改值大小的字节数据,以便将其值用于缓冲区的调整。 ?        “10 00”,此数值为16,采样大小为16bits,每样本数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。 ?        “64 61 74 61”,这个是Ascii字符“data”,表示头结束,开始数据区域。 ?        “00 33 AE 00”,十六进制数是“0xAE3300”,对应十进制11416320,是数据区的开头以后的数据总数。   再往后就是真正的WAV文件数据体了,头文件分析到此。 常见的声音文件主要有两种,分别对应单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。采样率是指:声音信号在“模->数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。 对于单声道声音文件,采样数据位8位的短整数;而对于双声道立体声声音文件,每次采样数据位一个16位的整数,高8为和低8位分别代表左右两个声道。
WAVE文件数据块包含以脉冲编码调制(PCM)格式表示样本。WAVE文件是由样本组织而成的。在单声道WAVE文件中,声道0代表左声道,声道1代表右声道。在多声道WAVE文件中,样本是交替出现的。   PCM数据的存放方式:                   样本1                    样本2    8位单声道        0声道                    0声道 8位立体声        0声道(左)1声道(右)    0声道(左) 1声道(右) 16位单声道       0声道低 0声道高          0声道低 0声道高   16位立体声 0声道(左)低 0声道() 1声道(右)低 1声道(右)高  



    系统硬件组成比较简单,可以分为液晶显示,LED指示,USB输入,SD卡,电源供电,音频功放和按键等,如图3-1所示:
3-1 系统组成框图

SD卡电路:
SD卡采用SPI驱动。

USB电路:

采用SGM7222做转换开关,识别ID的电压值来选择是作为IAP下载还是用于USB接口
音频功放电路:

充电和系统电源:




程序编写主要有三个部分:定时器初始化,DAC初始化,定时器中断服务程序,WAV播放程序。   定时器初始化: void Timerx_Init(u16 arr,u16 psc) {        NVIC_InitTypeDef NVIC_InitStructure;               RCC->APB1ENR|=1<<1;//TIM3时钟使能         TIM3->ARR=arr;  //设定计数器自动重装值           TIM3->SC=psc;  //预分频器7200,得到10KHz的计数时钟        TIM3->DIER|=1<<0;   //允许更新中断                  TIM3->DIER|=1<<6;   //允许触发中断                                                                           TIM3->CR1|=0x01;    //使能定时器3               //优先级设置       NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);        NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        NVIC_Init(&NVIC_InitStructure); }   DAC初始化: #include "dac.h"   extern u16 digital;   void MyDAC_Init(void)//DAC channel1 Configuration {     unsigned int tmpreg1=0,tmpreg2=0;       RCC->APB2ENR|=1<<2;//使能PORTA时钟        RCC->APB1ENR|=RCC_APB1Periph_DAC;//使能DAC时钟       GPIOA->CRL&=0XFFF0FFFF;        GPIOA->CRL|=0X00040000;//PA4浮空输入           tmpreg1=DAC->CR;//Get the DAC CR value      tmpreg1&=~(CR_CLEAR_Mask<<DAC_Channel_1);   tmpreg2=(DAC_Trigger_Software|DAC_WaveGeneration_None|DAC_LFSRUnmask_Bits8_0|DAC_OutputBuffer_Enable);     tmpreg1|=tmpreg2<<DAC_Channel_1;     DAC->CR=tmpreg1;//Write to DAC CR      DAC->CR|=CR_EN_Set<<DAC_Channel_1;//DAC通道1使能,PA4自动连接到DAC        DAC1_SetData(0x000);          #if 0     tmpreg1=DAC->CR;//Get the DAC CR value      tmpreg1&=~(CR_CLEAR_Mask<<DAC_Channel_2);  tmpreg2=(DAC_Trigger_Software|DAC_WaveGeneration_None|DAC_LFSRUnmask_Bits8_0|DAC_OutputBuffer_Enable); tmpreg1|=tmpreg2<<DAC_Channel_2; DAC->CR=tmpreg1;        DAC->CR|=CR_EN_Set<<DAC_Channel_2;        DAC2_SetData(0x000);        #endif } void DAC1_SetData(u16 data) {        DAC->DHR12R1=data;//通道112位右对齐数据        DAC->SWTRIGR|=0x01;//软件启动转换 } void DAC2_SetData(u16 data) {        DAC->DHR12R2=data;//通道212位右对齐数据        DAC->SWTRIGR|=0x02;//软件启动转换 } 定时器中断服务程序: void TIM3_IRQHandler(void) {                                                              u16 temp;        if(TIM3->SR&0X0001)//溢出中断        {               if(CHanalnum==1)//单声道               {                      if(Bitnum==8)//8位精度                      {                             DAC->DHR12R1=wav_buf[DApc]*10/volume;                             DAC->DHR12R2=wav_buf[DApc]*10/volume;                             DAC->SWTRIGR |=0x01;                         DApc++;                      }                      else if(Bitnum==16)                     {                temp=(((u8)(wav_buf[DApc+1]-0x80)<<4)|(wav_buf[DApc]>>4))*10/volume;                         DAC->DHR12L1=temp;                         DAC->DHR12L2=temp;                         DAC->SWTRIGR|=0x01;                         DApc+=2;                                           }               }               else if(CHanalnum==2)               {                      if(Bitnum==8)                      {                             DAC->DHR12R1=wav_buf[DApc]*10/volume;                             DApc++;                             DAC->DHR12R2=wav_buf[DApc]*10/volume;                             DApc++;                             DAC->SWTRIGR|=0x01;                   }                      else if(Bitnum==16)                      {            DAC->DHR12L1=(((u8)(wav_buf[DApc+1]-0x80)<<4)|(wav_buf[DApc]>>4))*10/volume;                       DApc+=2;             DAC->DHR12L2=(((u8)(wav_buf[DApc+1]-0x80)<<4)|(wav_buf[DApc]>>4))*10/volume;                             DApc+=2;                    DAC->SWTRIGR|=0x01;                                 }               }                          if(DApc==16384)         {         DApc=0;         DACdone=1;     }                                                                                                                }                                    TIM3->SR&=~(1<<0);   }   WAV初始化: u8 WAV_Init(u8* wav_buf) {        if(Check_Ifo(wav_buf,"RIFF"))               return 1;         wav1.wavlen=Get_num(wav_buf+4,4);        printf(" wav1.wavlen = %ld ",wav1.wavlen); //if(Check_Ifo(wav_buf+8,"WAVE"))return 2;//WAVE错误标志 //if(Check_Ifo(wav_buf+12,"fmt "))return 3;//fmt错误标志        wav1.formart=Get_num(wav_buf+20,2);//格式类别        printf(" wav1.formart = %d ",wav1.formart);               wav1.CHnum=Get_num(wav_buf+22,2);//通道数        printf(" wav1.CHnum = %d ",wav1.CHnum);        CHanalnum=wav1.CHnum;               wav1.SampleRate=Get_num(wav_buf+24,4);//采样率        printf(" wav1.SampleRate = %ld ",wav1.SampleRate);               wav1.speed=Get_num(wav_buf+28,4);//音频转换数率        printf(" wav1.speed = %ld ",wav1.speed);               wav1.ajust=Get_num(wav_buf+32,2);//数据块调速数        printf(" wav1.ajust = %d ",wav1.ajust);               wav1.SampleBits=Get_num(wav_buf+34,2);//样本数据位数        printf(" wav1.SampleBits = %d ",wav1.SampleBits);        Bitnum=wav1.SampleBits;        //if(Check_Ifo(wav_buf+36,"data"))return 4;//数据标志错误               wav1.DATAlen=Get_num(wav_buf+40,4);//数据长度        printf(" wav1.DATAlen = %d ",wav1.DATAlen);               if(wav1.wavlen<0x100000)        {               printf(" wav1.wavlen = %dkb ",(wav1.wavlen)>>10);        }        else        {               printf(" wav1.wavlen = %dMb ",(wav1.wavlen)>>20);        }        if(wav1.formart==1)               printf(" WAV PCM ");        if(wav1.CHnum==1)               printf(" single ");        else               printf(" stereo ");        printf(" wav1.SampleRate = %dkHz ",(wav1.SampleRate)/1000);        printf(" wav1.speed = %dbps ",(wav1.speed)/1000);        printf(" wav1.SampleBits = %dbit ",wav1.SampleBits);               return 0; } u8 Check_Ifo(u8* pbuf1,u8* pbuf2) {        u8 i;        for(i=0;i<4;i++)               if(pbuf1!=pbuf2)                      return 1;        return 0; }   u32 Get_num(u8* pbuf,u8 len) {   u32 num;        if(len==2)num=(pbuf[1]<<8)|pbuf[0];        else if(len==4)num=(pbuf[3]<<24)|(pbuf[2]<<16)|(pbuf[1]<<8)|pbuf[0];        return num; }   WAV播放: u8 Playwav(char *file) {        FIL fwav;        FRESULT Res;        UINT BR;        unsigned char i;        unsigned int times;        Res = f_open(&fwav, file, FA_OPEN_EXISTING | FA_READ);        if(Res != FR_OK)   {               printf(" open file error : %d ",Res);        }        else   {          Res = f_read(&fwav, wav_buf, sizeof(wav_buf), &BR);     /* Read a chunk of src file */     if(Res==FR_OK)     {                      WAV_Init(wav_buf);                      DACdone=0;                      DApc=44; //跳过头信息                                   Timerx_Init(1000000/wav1.SampleRate,72); //定时器初始化                      times=(wav1.DATAlen>>10)-1; //计算数据大小                                                         for(i=0;i<times/32;i++)//循环一次转换32KB数据                      {                                 while(!DACdone);//等待前面16384字节转换完成                                                   DACdone=0;                             Res = f_read(&fwav, wav_buf, 16384, &BR);                             while(!DACdone);// 等待前面16384字节转换完成                             DACdone=0;                             Res = f_read(&fwav, wav_buf, 16384, &BR);//读取数据                      }               }               else               {                      printf(" read file error : %d ",Res);               }               f_close(&fwav);        }        return 0; }

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
30条回答
正点原子
1楼-- · 2019-07-21 01:16
不错,谢谢分享.
要是能上传工程就更好了.
王军强
2楼-- · 2019-07-21 05:51
有代码就更好了
Cary_Liu
3楼-- · 2019-07-21 06:51
应经很棒的资料了。谢谢!很有用!
cornrn
4楼-- · 2019-07-21 11:14
 精彩回答 2  元偷偷看……
GUI
5楼-- · 2019-07-21 15:53
回复【5楼】cornrn:
---------------------------------
那你现在还有这个例子吗?有的话发我一份啊....这是我邮箱:453251337@qq.com
好见不坏
6楼-- · 2019-07-21 19:55
能解释下
temp=(((u8)(wav_buf[DApc+1]-0x80)<<4)|(wav_buf[DApc]>>4))*10/volume;
这句将16bit转为12bit的具体思想吗,看了好久也不明白。

一周热门 更多>