WAVE文件信息(頭參數,PCM數據)存儲規則
關於wave文件的頭信息參看如下兩篇博客:
(1) http://www.cnblogs.com/liyiwen/archive/2010/04/19/1715715.html
(2) http://syp0316.blog.163.com/blog/static/49178801200793133424635/
在對WAVE格式音頻文件有個大致瞭解之後,下面我們結合樸實的代碼逐字節讀取wave格式音頻文件信息,來分析wave文件數據的存放和存儲的規則。(Word中代碼加註釋有點亂,但不影響複製到VS中調試,建議先看最後的實驗結果。見諒!)
/*****傳統方法(C語言),運行環境爲VS2010*****/
#include <iostream>
using namespace std;
#include "stdlib.h"
#include "stdio.h"
#include <math.h>
#define N 160
float *Read_Data(FILE *fp_speech,int front_info);
void main()
{
float *a;
int i,flag=0; //i用作循環計數,flag用作判斷是否存在
unsigned char ch[100]; //用來存儲wav文件的頭信息
FILE *fp;
short buf[N];//用來存放PCM數據,看是否讀取正確
fp=fopen("E:\\Audio\\Result\\sc02_single.wav","rb");//爲讀,打開一個wav文件
if((fp=fopen("E:\\Audio\\Result\\sc02_single.wav","rb"))==NULL) //若打開文件失敗,退出
{
printf("can't open this file\n");
exit(0);
}
/*****
注意:這裏的去讀方式是比較直接的,現將字符逐個存儲再按照數據塊大小逐個讀出翻譯成我們想要的形式。有些事字符,如data,只需按順序逐個輸出即可。有些是進制數,分高低位,我們這裏是逐個有高位到底爲組成一串。如整個文件大小size,從高位到地位輸出之後爲0x00 07 68 aa,一個字節等於八位,若讀取這個字節數小於16,則用第四位就能表示,所以高四位用16進制0表示,組合起來正好四個字節
*****/
/**********輸出wav文件的所有信息**********/
printf("該wav文件的所有信息:");
for(i=0;i<46;i++)
{
ch[i]=fgetc(fp); //每次讀取一個字符,存在數組ch中
if(i%16==0) //每行輸出16個字符對應的十六進制數
printf("\n");
if(ch[i]<16) //對小於16的數,在前面加0,使其用8bit顯示出來,保持結構的完整性,也符合我們從左到右的閱讀習慣
printf("0%x ",ch[i]);
else
printf("%x ",ch[i]);
}
/*********RIFF WAVE Chunk的輸出*********/
/****************************************
RIFF WAVE Chunk
==================================
| |所佔字節數| 具體內容 |
==================================
| ID | 4 Bytes | 'RIFF' |
----------------------------------
| Size | 4 Bytes | |
----------------------------------
| Type | 4 Bytes | 'WAVE' |
******************************************/
printf("\n\nRIFF WAVE Chunk信息:");
//輸出RIFF標誌
printf("\nRIFF標誌:");
for(i=0;i<4;i++)
{
printf("%c ",ch[i]);
}
//輸出size大小
printf("\nsize:ox");
for(i=7;i>=4;i--) //低字節表示數值低位,高字節表示數值高位,有高位開始逐個讀取,在放到一起
{
if(ch[i]<16)
printf("0%x",ch[i]); //這裏SIZE大小爲4個字節,1個字節有8位,而1個16進制數用4位就可以表示
else //若讀到該字節發現數小於16,則前面補零
printf("%x",ch[i]); //運行程序中該段內容顯示爲:0x 00 07 68 aa 形式上比較嚴格,八位表示一字節,不大於四位前面補零顯示
}
//輸出WAVE標誌
printf("\nWAVE標誌:");
for(i=8;i<12;i++)
{
if(ch[i]<16)
printf("0%c ",ch[i]);
else
printf("%c ",ch[i]);
}
/*******Format Chunk的輸出*******/
/****************************************
Format Chunk
====================================================================
| | 字節數 | 具體內容 |
====================================================================
| ID | 4 Bytes | 'fmt ' |
--------------------------------------------------------------------
| Size | 4 Bytes | 數值爲16或18,18則最後又附加信息|
-------------------------------------------------------------------- ----
| FormatTag| 2 Bytes | 編碼方式,一般爲0x0001 | |
-------------------------------------------------------------------- |
| Channels | 2 Bytes | 聲道數目,1--單聲道;2--雙聲道 |
-------------------------------------------------------------------- |
| SamplesPerSec | 4 Bytes | 採樣頻率
-------------------------------------------------------------------- |
| AvgBytesPerSec| 4 Bytes | 每秒所需字節數
-------------------------------------------------------------------- |
| BlockAlign| 2 Bytes | 數據塊對齊單位(每個採樣需要的字節數) |
-------------------------------------------------------------------- |
| BitsPerSample | 2 Bytes | 每個採樣需要的bit數
-------------------------------------------------------------------- |
| | 2 Bytes | 附加信息(可選,通過Size來判斷有無) |
-------------------------------------------------------------------- ----
******************************************/
printf("\n\nFormat Chunk信息:");
//輸出fmt 標誌
printf("\nfmt 標誌:");
for(i=12;i<16;i++)
{
//if(ch[i]<16)
//printf("0%c ",ch[i]);
//else
printf("%c ",ch[i]); //輸出字符型,格式類型直接用 %c 即可
}
//輸出size段
printf("\nsize:ox");
for(i=19;i>15;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出編碼方式
printf("\n編碼方式:ox");
for(i=21;i>19;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出聲道數目
printf("\n聲道數目:ox");
for(i=23;i>21;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
if(ch[i+1]==1) //1表示單聲道,2表示雙聲道,這裏寫i++ 是因爲出循環之後執行了i--
printf(" 單聲道");
else
printf(" 雙聲道");
//輸出採樣頻率,用十六進制表示
printf("\n採樣頻率:ox");
for(i=27;i>23;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出每秒所需字節數
printf("\n每秒所需字節數:ox");
for(i=31;i>27;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出數據塊對齊單位
printf("\n數據塊對齊單位:ox");
for(i=33;i>31;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出每個採樣所需bit數 16 bit
printf("\n每個採樣所需bit數:ox");
for(i=35;i>33;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出附加信息,附加信息的有無直接進行判斷即可
if(ch[16]==18) //若Format Chunk的size大小爲18,則該模塊的最後兩個字節爲附加信息
{ //若爲16,則無附加信息
printf("\n附加信息:ox");
for(i=37;i>35;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
}
if (ch[38]=='f') //判斷是否有Fact Chunk信息段,以fact標誌f a c t爲特徵進行判斷
{ //若括號裏面爲真,則直接按順序輸出Fact Chunk信息段和data Chunk信息段
/*******Fact Chunk的輸出*******/
/****************************************
Fact Chunk
==================================
| |所佔字節數| 具體內容 |
==================================
| ID | 4 Bytes | 'fact' |
----------------------------------
| Size | 4 Bytes | 數值爲4 |
----------------------------------
| data | 4 Bytes | |
----------------------------------
******************************************/
printf("\n\nFact Chunk信息:");
//輸出fact標誌
printf("\nfact標誌:");
for(i=38;i<42;i++)
{
if(ch[i]<16)
printf("0%c ",ch[i]);
else
printf("%c ",ch[i]);
}
//輸出size
printf("\nsize:ox");
for(i=45;i>41;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出data段數據
printf("\ndata段數據:");
for(i=46;i<50;i++)
{
if(ch[i]<16)
printf("0%x ",ch[i]);
else
printf("%x ",ch[i]);
}
/*******Data Chunk的輸出*******/
/****************************************
Data Chunk
==================================
| |所佔字節數| 具體內容 |
==================================
| ID | 4 Bytes | 'data' |
----------------------------------
| Size | 4 Bytes | |
----------------------------------
| data | | |
----------------------------------
******************************************/
printf("\n\nData Chunk信息:");
//輸出data標誌
printf("\ndata標誌:");
for(i=50;i<54;i++)
{
if(ch[i]<16)
printf("0%x ",ch[i]);
else
printf("%x ",ch[i]);
}
//輸出數據大小
printf("\n數據大小:ox");
for(i=57;i>53;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
printf("\n輸出PCM數據:");
fseek(fp,58,0);//注意偏移量
fread(&buf,sizeof(buf),1,fp);
cout<<float(buf[0])/(1024*32)<<" "<<float(buf[1])/(1024*32)<<" "<<float(buf[2])/(1024*32)<<" "<<float(buf[3])/(1024*32)<<" "<<float(buf[4])/(1024*32)<<endl;
}
if (ch[38]=='d') //只有Data Chunk信息,而沒有Fact Chunk信息的情況。
{ //此時Data Chunk信息的存放位置取代了Fact Chunk信息的位置,i要要提前到Fact Chunk信息段
printf("\n\nData Chunk信息:");
//輸出fact標誌
printf("\ndata標誌:");
for(i=38;i<42;i++)
{
if(ch[i]<16)
printf("0%c ",ch[i]);
else
printf("%c ",ch[i]);
}
//輸出數據大小
printf("\nsize:ox");
for(i=45;i>41;i--)
{
if(ch[i]<16)
printf("0%x",ch[i]);
else
printf("%x",ch[i]);
}
//輸出PCM數據
printf("\n輸出PCM數據:");
fseek(fp,46,0);
fread(&buf,sizeof(buf),1,fp);
cout<<float(buf[0])/(1024*32)<<" "<<float(buf[1])/(1024*32)<<" "<<float(buf[2])/(1024*32)<<" "<<float(buf[3])/(1024*32)<<" "<<float(buf[4])/(1024*32)<<endl;
/*
注意該文件的採樣值(每個採樣所需的比特數)爲16bit,我們讀取的是short型數據(具有兩個字節)是用16 個比特位來描述得到,音頻文件波形值應該是浮點型,需要用短整型值除以1024*32(2的15次方),即可得到浮點型數據。此處應該先轉浮點,再除以1024*32,不然結果爲零。當我們已知PCM數據浮點型數值,我們可以先乘(1024*32)再強制轉換爲短整型存入。理解原理,順序不能搞錯了。
*/
}
printf("\n");
fclose(fp);
}
//這是個讀取PCM數據的函數,按短整型讀,然後轉換成float型,注意偏移量
float *Read_Data(FILE *fp_speech,int front_info)
{
short data; //爲什麼用short?參看文獻中的相應wav文件的pcm數據存放格式。16位單聲道爲兩個字節,故爲short
float static a[N];
fseek(fp_speech,front_info,0);//跳過頭,經過分析該文件頭部信息佔46個字節,之後的存放PCM數據
for(int i=0;i<N;i++)
{
fread(&data,sizeof(short),1,fp_speech);
a[i]=(float)data/(float)(1024*32);
}
return a;
}
實驗結果對比:
前部讀出了音頻文件所有信息,都是十六進制數。如十六進制數52,轉換爲十進制即爲82,正好是R對應ASCII碼。以此類推,你會發現讀取正確。
這裏我們在意的是最重要的PCM數據是否讀取正確。我們用MATLAB讀取音頻文件信息作對比(MATLAB讀取音頻文件信息比較直觀,而且簡單),對比結果發現PCM數據完全一致。說明我們的讀取方式正確的。
希望通過以上簡單的實驗讓大家對Wave格式音頻文件有更加深入的瞭解,在以後從事音頻相關的編碼工作時更加得心應手。
以上代碼是最原始的讀取方式,大家要習慣用一些對文件操作的函數如fread(),fwrite()和fseek()等函數來處理,會更加快捷,安全。
最後,大家自己寫的時候,一定不要忘了判斷附加信息段是否有信息,0也算信息,也要佔兩個字節,只有當沒有信息時,fmt chunk後面的信息段纔要往前移動兩個字節。我這裏附加信息段爲0,也佔用兩個字節,所以後面的信息段不用前移兩個字節。
C語言實驗結果圖:
Matlab實驗結果圖: