1. 程式人生 > >用DAC解碼PCM資料播放WAV格式音訊檔案

用DAC解碼PCM資料播放WAV格式音訊檔案

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左右超出

如有錯誤歡迎斧正,相互學習,共同進步。