1. 程式人生 > >PCM WAVE格式詳解及用C語言實現wave檔案的讀取

PCM WAVE格式詳解及用C語言實現wave檔案的讀取

1.PCM Wave格式詳解

WAVE檔案格式是微軟RIFF(Resource Interchange File Format,資源交換檔案標準)的一種,是針對於多媒體檔案儲存的一種檔案格式和標準。 一般而言,RIFF檔案由檔案頭和資料兩部分組成,一個WAVE檔案由一個“WAVE”資料塊組成,這個“WAVE”塊又由一個”fmt”子資料塊和一個“data”子 資料塊組成,也稱這種格式為“Canonical form”(權威/牧師格式),如下圖所示:

每個欄位的涵義如下: ChunkID: 佔4個位元組,內容為“RIFF”的ASCII碼(0x52494646),以大端(big endian)儲存。
ChunkSize: 4位元組,儲存整個檔案的位元組數(不包含ChunkID和ChunkSize這8個位元組),以小端(little endian)方式儲存。
Format: 4位元組,內容為“WAVE”的ASCII碼(0x57415645),以大端儲存。

其中bigendian 主要有一個特徵,在記憶體中對運算元的儲存方式和從高位元組到低位元組。例如:0x1234,這樣一個數,儲存為:
0x4000: 0x12
0x4001: 0x34
而小尾端littleendian是:
0x4000: 0x34
0x4001: 0x12
用程式在區別的話,可以考慮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
       union w
      {
       short int a;
char b; }c; c.a=1; if( c.b==1 ) printf("little endian\n"); else printf("big endian\n"); system("PAUSE"); return 0; }

“WAVE”格式由兩個子資料塊構成:“fmt”塊和“data”塊,其中“fmt”塊的詳細解釋如下: Subchunk1ID: 佔4個位元組,內容為“fmt ”的ASCII碼(0x666d7420),以大端儲存。
Subchunk1Size: 佔4個位元組,儲存該子塊的位元組數(不含前面的Subchunk1ID和Subchunk1Size這8個位元組),以小端方式儲存。
AudioFormat:佔2個位元組,以小端方式儲存,儲存音訊檔案的編碼格式,例如若為PCM則其儲存值為1,若為其他非PCM格式的則有一定的壓縮。
NumChannels: 佔2個位元組,以小端方式儲存,通道數,單通道(Mono)值為1,雙通道(Stereo)值為2,等等。
SampleRate: 佔4個位元組,以小端方式儲存,取樣率,如8k,44.1k等。
ByteRate: 佔4個位元組,以小端方式儲存,每秒儲存的bit數,其值=SampleRate * NumChannels * BitsPerSample/8
BlockAlign: 佔2個位元組,以小端方式儲存,塊對齊大小,其值=NumChannels * BitsPerSample/8
BitsPerSample: 佔2個位元組,以小端方式儲存,每個取樣點的bit數,一般為8,16,32等。
接下來是兩個可選的擴充套件引數:
ExtraParamSize: 佔2個位元組,表示擴充套件段的大小。
ExtraParams: 擴充套件段其他自定義的一些引數的具體內容,大小由前一個欄位給定。

其中,對於每個取樣點的bit數,不同的bit數讀取資料的方式不同:

1
2
3
4
5
6
7
8
9
10
// data 為讀取到的取樣點的值,speech為原始資料流,
//對應於下面的"WAVE"格式檔案的第二個子資料塊“data”塊的“Data”部分。
for(i=0;i<NumSample;i++){
  if(BitsPerSample==8)
      data[i] = (int)*((char*)speech+i);
  else if(BitsPerSample==16)
      data[i] = (int)*((short*)speech+i);
  else if(BitsPerSample==32)
      data[i] = (int)*((int*)speech+i);
}

“WAVE”格式檔案的第二個子資料塊是“data”,其個欄位的詳細解釋如下:
Subchunk2ID: 佔4個位元組,內容為“data”的ASCII碼(0x64617461),以大端儲存。
Subchunk2Size: 佔4個位元組,內容為接下來的正式的資料部分的位元組數,其值=NumSamples * NumChannels * BitsPerSample/8
Data: 真正的語音資料部分。

一個Wave檔案頭的例項

設一個wave檔案的前72個位元組的十六進位制內容如下(可以使用Ultra Edit等工具檢視wave檔案頭):

1
2
3
52 49 46 46 24 08 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00 
22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 08 00 00 00 00 00 00 
24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d
則其個欄位的解析如下圖:

C語言實現wave檔案的讀取

這裡給出一個用基本的C語言檔案操作庫函式實現的Wave檔案讀取的例項程式碼,可以跨Windows和Linux平臺。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// define Wave format structure
typedef struct tWAVEFORMATEX
{
    short wFormatTag;         /* format type */
    short nChannels;          /* number of channels (i.e. mono, stereo...) */
    unsigned int nSamplesPerSec;     /* sample rate */
    unsigned int nAvgBytesPerSec;    /* for buffer estimation */
    short nBlockAlign;        /* block size of data */
    short wBitsPerSample;     /* number of bits per sample of mono data */
    short cbSize;             /* the count in bytes of the size of */
                                    /* extra information (after cbSize) */
} WAVEFORMATEX, *PWAVEFORMATEX;
char* wavread(char *fname, WAVEFORMATEX *wf);
int main(){
  char fname[] = "test.wav";
  char *speech;
  WAVEFORMATEX wf;
  speech = wavread(fname, &wf);
  // afterward processing...
  return 0;
}
// read wave file
char* wavread(char *fname, WAVEFORMATEX *wf){
  FILE* fp;
  char str[32];
  char *speech;
  unsigned int subchunk1size; // head size
  unsigned int subchunk2size; // speech data size
  // check format type
  fp = fopen(fname,"r");
  if(!fp){
      fprintf(stderr,"Can not open the wave file: %s.\n",fname);
      return NULL;
  }
  fseek(fp, 8, SEEK_SET);
  fread(str, sizeof(char), 7, fp);
  str[7] = '\0';
  if(strcmp(str,"WAVEfmt")){
      fprintf(stderr,"The file is not in WAVE format!\n");
      return NULL;
  }
  // read format header
  fseek(fp, 16, SEEK_SET);
  fread((unsigned int*)(&subchunk1size),4,1,fp);
  fseek(fp, 20, SEEK_SET);
  fread(wf, subchunk1size, 1, fp);
  // read wave data
  fseek(fp, 20+subchunk1size, SEEK_SET);
  fread(str, 1, 4, fp);
  str[4] = '\0';
  if(strcmp(str,"data")){
      fprintf(stderr,"Locating data start point failed!\n");
      return NULL;
  }
  fseek(fp, 20+subchunk1size+4, SEEK_SET);
  fread((unsigned int*)(&subchunk2size), 4, 1, fp);
  speech = (char*)malloc(sizeof(char)*subchunk2size);
  if(!speech){
      fprintf(stderr, "Memory alloc failed!\n");
      return NULL;
  }
  fseek(fp, 20+subchunk1size+8, SEEK_SET);
  fread(speech, 1, subchunk2size, fp);
  fclose(fp);
  return speech;
}

參考

[1]WAVE PCM soundfile format: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ 
[2]Resource Interchange File Format: http://en.wikipedia.org/wiki/Resource_Interchange_File_Format 
[3]基於Visual C++6.0的聲音檔案操作: http://www.yesky.com/20030414/1663116_1.shtml