1. 程式人生 > >PCM音訊檔案(.wav)壓縮成ADPCM(.wav)

PCM音訊檔案(.wav)壓縮成ADPCM(.wav)

 PCM音訊檔案壓縮成adpcm格式的檔案有多中方法(如使用ms ACM、sox等),本文主要介紹使用公開的演算法(如下所示,如果需要可到網上搜一下:

=======================================

** Intel/DVI ADPCM coder/decoder.
**
** The algorithm for this coder was taken from the IMA Compatability Project
** proceedings, Vol 2, Number 2; May 1992.
**
** Version 1.2, 18-Dec-92.

=======================================)進行PCM到ADPCM轉換的方法。

首先,要了解一下PCM格式和ADPCM格式的.wav檔案的構成,可參考:

非PCM格式的檔案會至少多加入一個“fact”塊,它用來記錄資料解壓縮後的大小。(注意是資料而不是檔案)這個“fact”塊一般加在“data”塊的前面。

1.PCM格式的wav檔案的內部結構

PCM格式的.wav檔案結構
2.WAVEFORMAT結構的認識

PCM和非PCM的主要區別是聲音資料的組織不同,這些區別可以通過兩者的WAVEFORMAT結構來區分。下面以PCM和IMA-ADPCM來進行對比:

WAVE的基本結構WAVEFORMATEX結構定義如下:

typedef struct

{

WORD wFormatag;  //編碼格式,包括WAVE_FORMAT_PCM,//WAVEFORMAT_ADPCM等

WORD  nChannls;       //聲道數,單聲道為1,雙聲道為2;

DWORD nSamplesPerSec;//取樣頻率;

DWORD nAvgBytesperSec;//每秒的資料量;

WORD  nBlockAlign;//塊對齊;

WORD  wBitsPerSample;//WAVE檔案的取樣大小;

WORD  sbSize;        //PCM中忽略此值

}WAVEFORMATEX;


PCM的結構就是基本結構;


IMAADPCMWAVEFORMAT結構定義如下:

Typedef struct

{

    WAVEFORMATEX wfmt;

    WORD nSamplesPerBlock;

}IMAADPCMWAVEFORMAT;


IMA-ADPCM的wfmt->cbsize不能忽略,一般取值為2,表示此型別的WAVEFORMAT比一般的WAVEFORMAT多出2個位元組。這兩個字元也就是nSamplesPerBlock。


3.“fact”chunk的內部組織

在非PCM格式的檔案中,一般會在WAVEFORMAT結構後面加入一個“fact”chunk,結構如下:

typedef struct{

    char[4];          //“fact”字串

       DWORD chunksize;

       DWORD datafactsize;    //資料轉換為PCM格式後的大小。

}factchunk;

datafactsize是這個chunk中最重要的資料,如果這是某種壓縮格式的聲音檔案,那麼從這裡就可以知道他解壓縮後的大小。對於解壓時的計算會有很大的好處!


4.“data”chunk的內部組織

從“data”chunk的第9個位元組開始,儲存的就是聲音資訊的資料了,(前八個位元組儲存的是標誌符“data”和後接資料大小size(DWORD)。這些資料可能是壓縮的,也可能是沒有壓縮的。

PCM中的聲音資料沒有被壓縮,如果是單聲道的檔案,取樣資料按時間的先後順序依次存入。(它的基本組織單位是BYTE(8bit)或WORD(16bit))如果是雙聲道的檔案,取樣資料按時間先後順序交叉地存入。

IMA-ADPCM是壓縮格式,它是從PCM16位取樣壓縮成4位的。對於單聲道的IMA-ADPCM來說,它是將PCM的資料按時間次序依次壓縮並寫入檔案中的,每個byte中含兩個取樣,低四位對應第一個取樣,高四位對應第二個取樣。而對於雙聲道的IMA-ADPCM來說,它的儲存相對就麻煩一些了,它是將PCM的左聲道的前8個取樣依次壓縮並寫入到一個DWORD中,然後寫入“datachunk裡。緊接著是右聲道的前8個取樣。以此迴圈,當取樣數不足8時(到資料尾端),應該把多出來的取樣用0填充。

在IMA-ADPCM中,“data”chuck中的資料是以block形式來組織的,我把它叫做“段”,也就是說在進行壓縮時,並不是依次把所有的資料進行壓縮儲存,而是分段進行的,這樣有一個十分重要的好處:那就是在只需要檔案中的某一段資訊時,可以在解壓縮時可以只解所需資料所在的段就行了,沒有必要再從檔案開始起一個一個地解壓縮。這對於處理大檔案將有相當的優勢。同時,這樣也可以保證聲音效果。

Block一般是由block header (block頭) 和 data 兩者組成的。其中block header是一個結構,它在單聲道下的定義如下:

Typedef struct

{

    short  sample0;    //block中第一個取樣值(未壓縮)

    BYTE  index;     //上一個block最後一個index,第一個block的index=0;

    BYTE  reserved;   //尚未使用

}MonoBlockHeader;

有了blockheader的資訊後,就可以不需要知道這個block前面和後面的資料而輕鬆地解出本block中的壓縮資料。對於雙聲道,它的blockheader應該包含兩個MonoBlockHeader其定義如下:

typedaf struct

{

    MonoBlockHeader leftbher;

    MonoBlockHeader rightbher;

}StereoBlockHeader;

在解壓縮時,左右聲道是分開處理的,所以必須有兩個MonoBlockHeader;

===================================================================

接下來,就是要實現從PCM到ADPCM的轉換了。(以8KHz,16Bits,單聲道為例)

 定義結構:

#define ADPCM_BLOCKSIZE 252  //256 - 4;

typedef unsigned long  ULONG;
typedef unsigned short USHORT;
typedef unsigned char  BYTE;

typedef struct {
 long           chunkid;
 long           chunksize;
 long     wave_id;
} RIFFCHUNK;

typedef struct tagDATA
{
 long ID;
 unsigned long Size;
}DATA;

typedef struct
{
 long          chunkid;
 unsigned long  chunksize;
 unsigned long  timelength;
} FACTCHUNK;

typedef struct tagWaveFormat{
 //long chunkSize;
 short          wFormatTag;
 unsigned short wChannels;
 unsigned long  dwSamplesPerSec;
 unsigned long  awAvgBytesPerSec;
 unsigned short wBlockAlign;
 unsigned short wBitsPerSample;
 unsigned short cbsize;

 int  headerSize ;
    long dataLength ;
} WAVEFMT;

typedef struct
{
 RIFFCHUNK      riff;
 long          chunkid;
 unsigned long  chunksize;
 USHORT wformattag; /* format type */
 USHORT nchannels; /* number of channels (i.e. mono, stereo...) */
 ULONG nsamplespersec; /* sample rate */
 ULONG navgbytespersec; /* for buffer estimation */
 USHORT nblockalign; /* block size of data */
 USHORT wbitspersample; /* number of bits per sample of mono data */
 USHORT cbsize; /* the count in bytes of the extra size */
 USHORT wsamplesperblock;
 
 FACTCHUNK fact;
 DATA   data;
}INTELADPCM_HEADER;

void adpcmEncoder()
{
    FILE *inFile, *outFile;

    inFile = fopen("c://input.wav", "rb");
    if (!inFile)
    {
        printf("can't open the input file.");
        return -2;
    }

 WAVEFMT waveFmt;
  if(!checkWaveFileHeaderFormat(inFile, &waveFmt)) {
  printf("input file's format is wrong.");
  return -1;
 }


 outFile = fopen("c://output.wav", "wb+");
 if (!outFile)
    {
        printf("can't open the output file.");
        return -2;
    }


 setAdpcmHeader(outFile, waveFmt.dataLength);

 int len;
 short inData[ADPCM_BLOCKSIZE * 2];
 BYTE outData[ADPCM_BLOCKSIZE];
 adpcm_state state;
 state.index = 0;
 state.valprev = 0;
 long samples = waveFmt.dataLength;
 while (samples > 0)
 {
  /* Read two frames worth of samples */
  len = fread(inData, (size_t )1, sizeof (inData), inFile);
  if (len <= 0)
  {
   perror("error reading input");
   exit(1);
  }
  samples -= len;
  if (len < sizeof (inData))
  {
   printf("memset. /n");
   memset((char *)inData + len, 0, sizeof (inData) - len);
  }
  fwrite(&state, 4, 1, outFile);
  len = adpcm_coder(inData, outData, len / 2, &state);
  writeFile(outFile, outData, len);// 此處高低位要互換
  }
 fclose(inFile);
 fclose(outFile);

 return 0;
}

void setAdpcmHeader( FILE *outFile, ULONG dataSize)
{
 ULONG   flen = 0;
 INTELADPCM_HEADER   adpcmhead;
 
 adpcmhead.riff.chunkid = WAV_ID_RIFF;//4BYTE
 adpcmhead.riff.wave_id = WAV_ID_WAVE;//4BYTE
 adpcmhead.chunkid = WAV_ID_FMT;//4BYTE
 adpcmhead.chunksize = 0x14;//2BYTE
 adpcmhead.wformattag = 0x11;//2BYTE
 adpcmhead.nchannels = 1;//2BYTE
 adpcmhead.nsamplespersec = 8000;//4BYTE
 adpcmhead.navgbytespersec = 4055;// = nsamplespersec / wsamplesperblock  *nblockalign, 可調,我用4064效果更好
 adpcmhead.nblockalign = 0x100;//2BYTE
 adpcmhead.wbitspersample = 4;//2BYTE  
 adpcmhead.cbsize = 2;//2BYTE
 adpcmhead.wsamplesperblock = 505;// 為什麼是這個數?沒搞明白
 
 adpcmhead.fact.chunkid = WAV_ID_FACT;
 adpcmhead.fact.chunksize = 4;
 adpcmhead.data.ID = WAV_ID_DATA;
 
 int adpcmDataSize = (dataSize / (ADPCM_BLOCKSIZE * 4)) * (ADPCM_BLOCKSIZE + 4); //blockcount * 256

 // 判斷最後的一個block是否是1008(252*4)byte,如果不是,按實際大小設定

 int lastBlockSize = dataSize % (ADPCM_BLOCKSIZE * 4);
 if(lastBlockSize > 0)
 {
  adpcmDataSize += lastBlockSize / 4 + 4(sizeof(state));
 }
 adpcmhead.riff.chunksize = adpcmDataSize + sizeof(INTELADPCM_HEADER) - 8 ;
 adpcmhead.fact.timelength = dataSize / 2;
 adpcmhead.data.Size = adpcmDataSize;
 
 fwrite( &adpcmhead, sizeof(adpcmhead), 1, outFile);
}

以上只是給出了部分關鍵的source,其他的按照以上的介紹完善以下即可。