历史上的今天
返回首页

历史上的今天

今天是:2025年07月23日(星期三)

正在发生

2018年07月23日 | 基于STM32的WAV音频格式播放器

2018-07-23 来源:eefocus

STM32从SD卡中读取语音文件进行播放,因此需要对语音进行解码,刚开始就一直使用Speex的音频压缩格式,最近发现,在进行语音格式转换时,我们不能很好地分析spx格式音频文件的文件头,这样就会导致语音的播放出现问题。由于WAV采用PCM编码,音质也十分不错,于是考虑用STM32对WAV格式音频文件进行解码,上周末开始找资料和编程,其中也遇到了不少问题,不过功夫不负有心人,最终还是顺利的跑起来了。先将资料和编程过程整理成本文,供大家一起学习和进步。

WAV文件格式是一种重要的用于存放声音文件的文件格式,尽管现在有MP3,RAM等压缩效率更高的声音文件格式,并且广泛被音乐文件所采用,但是又很多的应用程序仍然采用WAV文件格式。由于WAV文件没有采用压缩技术,所以它的文件很庞大,一般都在几MB以上。但也正是因为没有采用压缩技术,声音的采样数据很容易被读出来,便于用作其他的处理。

废话不多说了,我们直接去解析WAV文件格式吧。

WAV格式符合RIFF(Resource interchange File Format)规范。所有的WAV都有一个头文件,这个头文件音频流的编码参数。


表1、WAV文件的文件头


表2、WAV声音文件的数据块
接下来我们用已经编好的程序来读取一个WAV文件的文件头和数据块,看看各个内容都表示什么含义。


图1、WAV源文件




图2、用WinHex软件解析WAV


    图3、STM32读取WAV的信息

头文件样例说明:

?        “52 49 46 46”这个是Ascii字符“RIFF”,这部分是固定格式,表明这是一个WAVE文件头。


?        “24 33 AE 00”这个是我的WAV文件的数据大小,这个大小包括除了前面4个字节的所有字节,也就是等于文件总字节数减去8。得到图3中的11416356。11416356+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<

  tmpreg2=(DAC_Trigger_Software|DAC_WaveGeneration_None|DAC_LFSRUnmask_Bits8_0|DAC_OutputBuffer_Enable);

    tmpreg1|=tmpreg2<

    DAC->CR=tmpreg1;//Write to DAC CR

     DAC->CR|=CR_EN_Set<

       DAC1_SetData(0x000);

 

       #if 0

    tmpreg1=DAC->CR;//Get the DAC CR value 

    tmpreg1&=~(CR_CLEAR_Mask<

tmpreg1|=tmpreg2<

DAC->CR=tmpreg1;

       DAC->CR|=CR_EN_Set<

       DAC2_SetData(0x000);

       #endif

}

void DAC1_SetData(u16 data)

{

       DAC->DHR12R1=data;//通道1的12位右对齐数据

       DAC->SWTRIGR|=0x01;//软件启动转换

}

void DAC2_SetData(u16 data)

{

       DAC->DHR12R2=data;//通道2的12位右对齐数据

       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("\n\rwav1.wavlen = %ld\n\r",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("\n\rwav1.formart = %d\n\r",wav1.formart);

      

       wav1.CHnum=Get_num(wav_buf+22,2);//通道数

       printf("\n\rwav1.CHnum = %d\n\r",wav1.CHnum);

       CHanalnum=wav1.CHnum;

      

       wav1.SampleRate=Get_num(wav_buf+24,4);//采样率

       printf("\n\rwav1.SampleRate = %ld\n\r",wav1.SampleRate);

      

       wav1.speed=Get_num(wav_buf+28,4);//音频转换数率

       printf("\n\rwav1.speed = %ld\n\r",wav1.speed);

      

       wav1.ajust=Get_num(wav_buf+32,2);//数据块调速数

       printf("\n\rwav1.ajust = %d\n\r",wav1.ajust);

      

       wav1.SampleBits=Get_num(wav_buf+34,2);//样本数据位数

       printf("\n\rwav1.SampleBits = %d\n\r",wav1.SampleBits);

       Bitnum=wav1.SampleBits;

      

//if(Check_Ifo(wav_buf+36,"data"))return 4;//数据标志错误

      

       wav1.DATAlen=Get_num(wav_buf+40,4);//数据长度

       printf("\n\rwav1.DATAlen = %d\n\r",wav1.DATAlen);

      

       if(wav1.wavlen<0x100000)

       {

              printf("\n\rwav1.wavlen = %dkb\n\r",(wav1.wavlen)>>10);

       }

       else

       {

              printf("\n\rwav1.wavlen = %dMb\n\r",(wav1.wavlen)>>20);

       }

       if(wav1.formart==1)

              printf("\n\rWAV PCM\n\r");

       if(wav1.CHnum==1)

              printf("\n\rsingle\n\r");

       else

              printf("\n\rstereo\n\r");

       printf("\n\rwav1.SampleRate = %dkHz\n\r",(wav1.SampleRate)/1000);

       printf("\n\rwav1.speed = %dbps\n\r",(wav1.speed)/1000);

       printf("\n\rwav1.SampleBits = %dbit\n\r",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("\n\ropen file error : %d\n\r",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

                     {    

                            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("\n\rread file error : %d\n\r",Res);

              }

              f_close(&fwav);

       }

       return 0;

}


推荐阅读

史海拾趣

EMC Technology RF Labs公司的发展小趣事

在追求经济效益的同时,RF Labs也注重可持续发展和环保责任。他们积极采用环保材料和绿色生产工艺,降低生产过程中的能耗和排放。同时,RF Labs还积极参与环保公益活动,推动电子行业的绿色发展。这些举措不仅体现了RF Labs的社会责任感,也为公司的长期发展奠定了坚实的基础。

晨晶电子(Chenjing Electronics)公司的发展小趣事

在追求经济效益的同时,晨晶电子也积极履行社会责任,致力于绿色环保事业。公司通过了ISO14001环境管理体系认证,严格遵守环保法规,实施清洁生产。同时,晨晶电子还积极推广环保理念,倡导绿色消费,为保护环境、促进可持续发展做出了积极贡献。

这五个故事不仅展示了晨晶电子在电子行业中的发展历程和成就,也体现了其不断创新、追求卓越的企业精神。在未来的发展中,晨晶电子将继续秉承这一精神,努力成为电子行业的佼佼者。

Advanced_Linear_Devices_Inc.公司的发展小趣事

随着全球市场的不断扩大,ALD积极推进国际化战略。公司通过与全球各地的OEM制造商建立合作关系,将产品销往世界各地。同时,ALD还积极参与国际电子展会和论坛,展示其最新技术和产品,与全球同行交流合作。这些举措不仅提升了ALD的国际知名度,还为公司带来了更多的商机和发展空间。

General Industrial Controls ( GIC )公司的发展小趣事
电感器的工作频率应与LM2596的开关频率相匹配以确保最佳性能。由于LM2596的开关频率为150kHz,因此所选电感器的工作频率也应接近这个值。
胜利(VICTOR)公司的发展小趣事

随着产品的不断升级和市场的扩大,胜利公司开始将目光投向国际市场。1980年代,VICTOR品牌成功进入欧美市场,凭借优质的产品和合理的价格,迅速赢得了消费者的认可。公司还积极参加国际赛事,通过赞助等方式提升品牌知名度。

Fischer Elektronik公司的发展小趣事

Fischer Elektronik公司成立于1969年,是德国在高性能散热器和半导体制冷领域的领先制造商。成立之初,公司专注于研发和生产电子元器件的关键部件,如散热器和连接器,以满足当时快速增长的电子行业对高效散热解决方案的需求。凭借其精湛的工艺和严格的质量控制,Fischer Elektronik迅速在市场中站稳脚跟,并开始在仪器仪表、通讯/网络、交通/汽车和工业/自动化等领域展露头角。

问答坊 | AI 解惑

长城ATX-300SP电路图

谁有电脑电源长城ATX-300SP电路图啊…

查看全部问答>

奥运兴奋剂测试对测试仪器提出了新挑战

对于参与北京奥运会的运动员的违禁药物的独立检查将多达大概4500项,为了跟上不同违禁药物化合物的改变和伪装的步伐,由安捷伦公司提供的测试仪器|仪表必须不断地更新气相检测、液相检测和质谱仪检测的兴奋剂分析。   中国反兴奋剂中心(China An ...…

查看全部问答>

怎样制作三菱fx编程线

怎样制作三菱fx编程线?…

查看全部问答>

如何将程序写到 Nand Flash 的指定地址

大家好!我用的友善2440,现在在学习Nand Flash。 现在我有三个文件f1,f2,f3,我想将前两个烧到Nand FLash的0x00处,第三个文件烧到4096处, 这样上电后,f1,f2自动复制到steppingstone中执行,并实现f3从Nand Flash 复制到0x30000000, 然后程 ...…

查看全部问答>

请问2410LCD屏颜色不对的问题

    2410接LCD,调了很久了,通过修改寄存器配置,SPI配置LCD屏内寄存器,已经基本显示正常了,可以看到稳定的WinCE桌面,但是WinCE桌面的颜色始终不对,启动前有Test,将屏分四块分别显示RGB&white,四种颜色也不对,都是很奇怪的颜色, ...…

查看全部问答>

为什么EVC下加入ON_WM_NCHITTEST编译有错

为什么EVC下加入ON_WM_NCHITTEST编译有错error C2065: \'ON_WM_NCHITTEST\' : undeclared identifier…

查看全部问答>

关于获得PDA的电量问题,为什么得不到电量的值??

typedef        (WINAPI* GETSYSTEMPOWERSTATUSEX)(SYSTEM_POWER_STATUS_EX *,BOOL); GETSYSTEMPOWERSTATUSEX vGetSystemPowerStatuEx; SYSTEM_POWER_STATUS_EX  stat;         HMODULE m_ ...…

查看全部问答>

秀秀刚收到的51版数字湿度计套件!

哈哈,很高兴收到了数字湿度计套件,感谢EEWORLD。。。   …

查看全部问答>

STM区QQ群及无偿开发组招募.热爱STM32/STM8的你赶快来吧

QQ群28654446 主要为STM32.方便大家即时讨论. 专注于STM8的请参见帖子他们确实收集了许多优秀资料.做了许多工作 https://bbs.eeworld.com.cn/thread-234603-1-1.html 另外.我有一个想法,因为自己现在还是一名在校大学生.相信论坛里有不少人和我一 ...…

查看全部问答>

怎样配置boland c 4.5

我直接把boland c 4.5安装到了D盘,运行ucos2的代码时,不能运行,是不是还得配置boland c 4.5的环境变量啊,怎样才能运行啊?请兄弟姐妹们帮帮忙,谢谢!…

查看全部问答>