正在做的项目中需要
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<<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;//通道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("
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;
}
---------------------------------
能解释下
temp=(((u8)(wav_buf[DApc+1]-0x80)<<4)|(wav_buf[DApc]>>4))*10/volume;
这句将16bit转为12bit的具体思想吗,看了好久也不明白。
朋友,能分享一下你的工程给我么?谢谢。我的邮箱:1498873933@qq.com
朋友,这个工程你做的怎样了
原子兄,能帮我看看这段代码么?谢谢
#define SAMPLE_RATE_8000 8000
#define SAMPLE_RATE_11025 11025
#define SAMPLE_RATE_16000 16000
#define SAMPLE_RATE_22050 22050
#define SAMPLE_RATE_44100 44100
#define SAMPLE_RATE_48000 48000
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)//zhuyi
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)//zhuyi
void RCC_Init(void);
void RCC_Configuration(void);
void GPIOA_Configuration(void);
void TIM6_Configuration(void);
void DAC1_Configuration(void);
void SystemInit(void);
void DMA2_Configuration(void); //PA->DAC??3
void NVIC_Configuration(void);
uint32_t wavecount;
#define WAVELENGTH 64044
const uint8_t wavedata[64044]={
xxxxxxxxxxxxxxxxxxxx
};
u16 GetARRValue(u16 sample)
{
u16 arrValue;
/* 更新OCA值以符合.WAV文件采样率 */
switch (sample)
{
case SAMPLE_RATE_8000 :
arrValue = (uint16_t)(72000000/8000);
break; /* 8KHz = 2x36MHz / 9000 */
case SAMPLE_RATE_11025:
arrValue = (uint16_t)(72000000/11025);
break; /* 11.025KHz = 2x36MHz / 6531 */
case SAMPLE_RATE_16000:
arrValue = (uint16_t)(72000000/16000);
break; /* 16KHz = 2x36MHz / 4500 */
case SAMPLE_RATE_22050:
arrValue = (uint16_t)(72000000/22050);
break; /* 22.05KHz = 2x36MHz / 2365 */
case SAMPLE_RATE_44100:
arrValue = (uint16_t)(72000000/44100);
break; /* 44.1KHz = 2x36MHz / 1633 */
case SAMPLE_RATE_48000:
arrValue = (uint16_t)(72000000/48000);
break; /* 48KHz = 2x36MHz / 1500 */
default:
arrValue = 0;
break;
}
return arrValue;
}
void TIM6_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); /???
/??TIM2???
TIM_TimeBaseStructure.TIM_Period = arr; /??????????????????????????
TIM_TimeBaseStructure.TIM_Prescaler =psc; /?????TIMx???????????
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; /?????:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM??????
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure); /?????????TIMx???????
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE ); /????TIM2??,??????
/????NVIC??
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn; //TIM2??
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; /????0?
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; /???3?
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ?????
NVIC_Init(&NVIC_InitStructure); /??NVIC???
// TIM_Cmd(TIM6, ENABLE); /?TIMx
}
void DACInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO, ENABLE); //使能PORTA通道时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); //使能DAC通道时钟
/***********************************************************************************
问:PA4为什么要设置为模拟输入?
答:STM32F103ZET6 的 DAC 通道 1 在 PA4 上,所以,我们先要使能 PORTA 的时钟, 然后设
置 PA4 为模拟输入。 DAC 本身是输出,但是为什么端口要设置为模拟输入模式呢?因为一但
使能 DACx 通道之后,相应的 GPIO 引脚( PA4 或者 PA5)会自动与 DAC 的模拟输出相连,设
置为输入,是为了避免额外的干扰。
***********************************************************************************/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_4) ;//PA.4 输出高
TIM6_Int_Init(8999,0);
DAC_DeInit();
// DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO;
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bits8_0;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);
TIM_Cmd(TIM6, ENABLE);
}
void NVIC_Configuration(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
}
/*DMA通道3中断-------------------------*/
void DMAChannel3_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA2_IT_GL3)!=RESET)
{
DMA_ClearFlag(DMA2_IT_TC3);
DMA_Cmd(DMA2_Channel3, ENABLE);
DMA2_Channel3->CNDTR = 32;
DMA_Cmd(DMA2_Channel3, ENABLE);
}
}
/*RCC_Configuration---------------------------------*/
void RCC_Configuration(void)
{
/*开启相关通道的时钟使能*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
}
/*DMA2_Channle配置----------------------------------*/
void DMA2_Configuration(void)
{
rw_DMA2_CCR3&=0xffff8000;//寄存器清0
rw_DMA2_CPAR3=0x40007410;
rw_DMA2_CMAR3=(u32)&wavedata;
rw_DMA2_CNDTR3=37040;
rw_DMA2_CCR3|=(0<<14);//启动非存储器到存储器模式
rw_DMA2_CCR3|=(0x03<<12);//设置通道优先级为高
rw_DMA2_CCR3|=(0x00<<10);//设置存储数据宽度为8位
rw_DMA2_CCR3|=(0x00<<8);//设置外设数据宽度为8位
rw_DMA2_CCR3|=(1<<7);//启动存储器增量模式
rw_DMA2_CCR3|=(0<<6);//关闭外存储器增量模式
rw_DMA2_CCR3|=(1<<5);//开启循环模式
rw_DMA2_CCR3|=(1<<4);//从存储器读
rw_DMA2_CCR3|=(0<<1);//关闭传输完成中断
// rw_DMA2_CCR3|=(1<<0);//开启通道
// rw_DAC_CR|=(1<<12);//开启DAC->DMA通道
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(9600); //串口初始化为9600
DACInit();
while(1)
{
};
}
void TIM6_IRQHandler(void)
{
u16 tmpCap;
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)
{
if( wavecount < 48474)
{
tmpCap = wavedata1[wavecount];
wavecount++;
/* Set DAC Channel1 DHR register */
DAC_SetChannel1Data(DAC_Align_8b_R,tmpCap);
}
else
{
// 完成传输,关闭中断
wavecount = 0;
// TIM_ITConfig(TIM6, TIM_IT_Update, DISABLE);
// TIM_Cmd(TIM6, DISABLE);
// // 需要关闭DAC,不然在没有声音的时候会有杂音
// DAC_Cmd(DAC_Channel_1, DISABLE);
}
}
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
一周热门 更多>