用DAC解碼PCM資料播放WAV格式音訊檔案
阿新 • • 發佈:2018-12-05
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左右超出
如有錯誤歡迎斧正,相互學習,共同進步。