一招教你快速解析WAV文件格式

发布者:Mingyue1314最新更新时间:2024-05-06 来源: elecfans关键字:WAV  文件格式 手机看文章 扫描二维码
随时随地手机看文章

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

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


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

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


表1、WAV文件的文件头

WAV-2.jpg

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


图2、用WinHex软件解析WAV

WAV-5.jpg

图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;//

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('nrwav1.wavlen = %ldnr',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('nrwav1.formart = %dnr',wav1.formart);

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

printf('nrwav1.CHnum = %dnr',wav1.CHnum);

CHanalnum=wav1.CHnum;

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

printf('nrwav1.SampleRate = %ldnr',wav1.SampleRate);

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

printf('nrwav1.speed = %ldnr',wav1.speed);

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

printf('nrwav1.ajust = %dnr',wav1.ajust);

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

printf('nrwav1.SampleBits = %dnr',wav1.SampleBits);

Bitnum=wav1.SampleBits;

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

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

printf('nrwav1.DATAlen = %dnr',wav1.DATAlen);

if(wav1.wavlen<0x100000)

{

printf('nrwav1.wavlen = %dkbnr',(wav1.wavlen)>>10);

}

else

{

printf('nrwav1.wavlen = %dMbnr',(wav1.wavlen)>>20);

}

if(wav1.formart==1)

printf('nrWAV PCMnr');

if(wav1.CHnum==1)

printf('nrsinglenr');

else

printf('nrstereonr');

printf('nrwav1.SampleRate = %dkHznr',(wav1.SampleRate)/1000);

printf('nrwav1.speed = %dbpsnr',(wav1.speed)/1000);

printf('nrwav1.SampleBits = %dbitnr',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('nropen file error : %dnr',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('nrread file error : %dnr',Res);

}

f_close(&fwav);

}

return 0;

}


关键字:WAV  文件格式 引用地址:一招教你快速解析WAV文件格式

上一篇:CANFD总线异构通讯简单实例
下一篇:smt32h750扩展sdram

推荐阅读最新更新时间:2024-11-11 17:01

STM32学习笔记一一HEX文件和BIN文件格式
1. 引言 今天看串口的 IAP ,平时我们通过 JTAG 等工具下载的都是 HEX 文件,都没有思考一下 HEX 的文件组成。而串口 IAP 下载的是 BIN 文件,刚好在这里区分学习一下。 我们平时烧写 HEX 文件是不需要设置地址信息的,因为已经包含在文件里面,而使用 BIN 烧写,需要在程序中指定地址。 2. 简述 Intel hex 文件是记录文本行的 ASCII 文本文件,在 Intel HEX 文件中,每一行是一个 HEX 记录,由十六进制数组成的机器码或者数据常量。Intel HEX 文件经常被用于将程序或数据传输存储到 ROM、EPROM,大多数编程器和模拟器使用Intel HEX文件。 2.1 H
[单片机]
STM32学习笔记一一HEX文件和BIN<font color='red'>文件格式</font>
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved