VC下調用ACM音頻編程接口壓縮Wave音頻

摘要:本文介紹了在Microsoft Visual C++ 6.0下如何調用ACM(Audio Compression Manager,音頻 壓縮治理器)音頻壓縮編程接口對原始錄入的Wave音頻進行數據壓縮。
  
  要害字:ACM、CODECs、Wave音頻、音頻編碼壓縮
  
  引言
   <!-- frame contents -->

<!-- /frame contents -->
   音頻和視頻是多媒體應用程序向用戶提供信息的主要方式,這些音頻、視頻數據一般都具有較高的採樣率,經過壓縮的原始數據才具有實用價值,否則不僅要佔用大量存儲空間而且在播放或進行網絡傳輸時效率也是非常低下的,所以音頻、視頻數字壓縮編碼在多媒體應用中有着廣泛而又重要的用途。本文主要對音頻的編碼壓縮作了闡述。
  
   音頻的編碼壓縮方式有許多種,如基於ITU-T G.728語音編碼協議的LD-CELP 低時延碼激勵線性猜測編碼、基於ITU-T G.711語音編碼協議的PCM(Pulse Code Modulation ,脈衝編碼調製)編碼以及我們非常熟悉的GSM數字蜂窩移動電話的語音編碼標準等等。這些不同的壓縮方式有着不同的數據壓縮比和還原音質,具體的編碼格式和算法更是大相徑庭。多數協議都比較複雜,普通程序難以實現其加、解壓算法,而爲多媒體提供了較強支持的Windows 98操作系統引入了ACM和VCM技術,用來治理系統中存在的所有的音頻和視頻編、解碼器(Coder-Decoder,即CODECs,用來實現音頻、視頻數據編解碼的驅動程序)。可以通過它們提供的編程接口調用系統中存在的現成的編解碼器來實現音頻數據的加、解壓。Windows 98系統自帶的音頻CODECs 支持一些早期的音頻數據壓縮標準,如ADPCM (Adaptive Differential Pulse Code Modulation,自適應差分脈衝編碼調製)編碼等,而Internet EXPlorer 5.0 等應用程序包含的音頻CODECs支持一些較新 的壓縮標準, 如MPEG Layer 3等。本文所要介 紹的就是ACM音頻壓縮接口的編程方法,所使用的編程工具爲Microsoft Visual C++ 6.0。
  
  實現思路
  
   儘管一個CODEC在理論上能夠用於壓縮、解壓縮任一種數據流,但還是設計有各種各樣的CODECs 以實現更高的壓縮比、更高的保真度或實時壓縮性能來壓縮某種特定的數據類型。例如,把獲取很高的視頻壓縮數據壓縮率的最好方法應用到音頻數據時未必就能得到相同的效果。
  
   壓縮音頻數據的主要原理是降低存儲某一聲音序列所需的數據量。少的數據量就意味着聲音所佔有的空間更少,就能夠以更快的速度通過MODEM在網絡上傳遞。假如數據以Windows系統所支持的某種通用格式壓縮的話,就可不經手工解壓縮而直接播放--系 統將使用它自己的CODECs解壓縮數據並播放。Windows 98本身附帶有幾種標準的CODECs,如DSP Group,Inc. TrueSpeech CODEC等。因此我們寫的任何應用於 Windows 98下的程序都可應用這些CODEC,具體系統中都存在有哪些CODECs可以在控制面版的"多媒體"選項的"設備"標籤頁中查到。
  
   CODEC 支持從源音頻格式到目標格式的轉換,而在實際應用中, 可能某種CODEC 不支持直接將源音頻格式轉換成目標格式,比如我們通過麥克風向多媒體計算機錄入了一些頻率爲11025Hz、8位數據、單聲道的PCM數據,假如選用系統的TrueSpeech CODEC進行處理,就會引起失敗,因爲這種CODEC只能處理頻率爲8KHz,16位單聲道的數據。所以轉換時要採取兩步轉換法,即先將源格式轉換成一種中間格式,再將此中間格式轉換成目標格式,因爲線性PCM 編碼 最爲簡單,且爲絕大多數CODEC 所支持,所以一般中間格式都選爲線性PCM 格式的一種。比如就可以先將原始數據轉換成TrueSpeech CODEC所支持的中間PCM格式,然後再將其通過TrueSpeech CODEC轉換成最終的壓縮格式。
  
  程序的設計實現
  
   有關ACM的API函數定義在頭文件msacm.h中, 除了在工程中加入對此頭文件的引用之外, 對ACM編程還必須包含頭文件mmsystem.h和mmreg.h,這兩個頭文件定義了多媒體編程中最基本 的常量和數據結構。爲了避免有些高 版 本ACM才提供的函數和功能在較低版本的ACM中上不可用,程序中應調用acmGetVersion函 數查詢用戶機器中ACM 的版本信息。
  
  
   雖然可以根據控制面版手工得到關於某種音頻CODECs的信息,但在應用程序中也經常需要知道某種音頻CODECs是否存在,並獲取其編解碼參數等信息,可以通過回調函數find_format_enum來枚舉系統中的音頻壓縮格式:
  
   BOOL CALLBACK find_format_enum(HACMDRIVERID hadid, LPACMFORMATDETAILS pafd, DWord dwInstance, DWORD fdwSupport)
  {
    FIND_DRIVER_INFO* pdi = (FIND_DRIVER_INFO*) dwInstance;
    if (pafd->dwFormatTag == (DWORD)pdi->wFormatTag) {
   pdi->hadid = hadid;
   return FALSE; //停止枚舉
    }
    return TRUE; //繼續枚舉
  }
   在該回調函數中用到的FIND_DRIVER_INFO是自定義的數據結構,其兩個成員變量分別用來保存ACM驅動器號的句柄和要轉換的數據格式:
  
   typedef strUCt {
    HACMDRIVERID hadid;
    WORD wFormatTag;
  } FIND_DRIVER_INFO;
   現在可以枚舉出系統中當前所有的驅動程序。我們在程序中所調用的枚舉函數使用回調函數來彙報每個設備的數據,這在Windows編程是一種很普遍的方法。要獲得有關某一驅動程序能力更多的具體信息,必須裝載驅動程序並打開它,可通過調用 acmOpenDriver實現。一旦驅動程序打開,可請求枚舉它所支持的wave數據格式。但這就存在一個問題:所有wave格式描述結構都基於WAVEFORAMTEX,許多格式使用此結構的擴展形式來保存其特定的信息。假如我們想枚舉所有格式,需要知道爲此結構分配多少供驅動 程序填寫具體信息的空間。可以通過向acmMetrics函數傳遞ACM_METRIC_MAX_SIZE_FORMAT標 志得到所需的最大的結構的尺寸。打開驅動程序後要通過acmMetrics函數枚舉到所支持的格式,該函數可以獲取到許多ACM對象的有用信息。實現該過程的主要代碼如下:
  
   BOOL CALLBACK find_driver_enum (HACMDRIVERID hadid, DWORD dwInstance, DWORD fdwSupport)
  {
    ……
    MMRESULT mmr = acmDriverOpen(&had, hadid, 0);
    //枚舉所支持的格式
    ……
    mmr = acmMetrics((HACMOBJ)had, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);
    if (dwSize < sizeof(WAVEFORMATEX)) dwSize = sizeof(WAVEFORMATEX);
    WAVEFORMATEX* pwf = (WAVEFORMATEX*) malloc(dwSize);
    ……
    pwf->cbSize = LOWORD(dwSize) - sizeof(WAVEFORMATEX);
    pwf->wFormatTag = pdi->wFormatTag;
    ACMFORMATDETAILS fd;
    ……
    fd.cbStruct = sizeof(fd);
    fd.pwfx = pwf;
    fd.cbwfx = dwSize;
    fd.dwFormatTag = pdi->wFormatTag;
    mmr=acmFormatEnum(had, &fd, find_format_enum, (DWORD)(VOID*)pdi, 0); //枚舉格式
    ……
    acmDriverClose(had, 0); //關閉驅動器
    ……
  }
   根據指定的格式要找到其所對應的ACM驅動器號可以用枚舉所有音頻CODECs的ACM API函數acmDriverEnum來實現,在acmDriverEnum() 的參數中指定了在前面描述過的回調函數find_driver_enum,可以 進 一 步查詢每個CODEC的信息,最終可以獲取到ACM驅動器號的句柄。實現此功能的回調函數名爲find_driver,本文後面將會用到。
  
   在把原始Wave音頻數據轉換到中間PCM格式數據之前,需要做些前期預備工作,填充一些相關的結構信息,具體有:WAVEFORMATEX結構描述源格式、中間PCM格式、以及最終的壓縮格式等。下面先填充一個用來描述源數據格式的WAVEFORMATEX結構:
  
   WAVEFORMATEX wfSrc;
  memset(&wfSrc, 0, sizeof(wfSrc));
  wfSrc.cbSize = 0;
  wfSrc.wFormatTag = WAVE_FORMAT_PCM; //PCM脈衝編碼調製
  wfSrc.nChannels = 1; //單聲道
  wfSrc.nSamplesPerSec = 11025; //11.025kHz
  wfSrc.wBitsPerSample = 8; //8 bit
  wfSrc.nBlockAlign = wfSrc.nChannels * wfSrc.wBitsPerSample / 8;
  wfSrc.nAvgBytesPerSec = wfSrc.nSamplesPerSec * wfSrc.nBlockAlign;
   然後通過前面提到的回調函數find_driver來獲取由wFormatTag指定的中間數據格式所對應的驅動程序的ACM驅動器號,在此設定的是由WAVE_FORMAT_DSPGROUP_TRUESPEECH指定的有Windows 98系統自帶的TrueSpeech CODEC:
  
   WORD wFormatTag = WAVE_FORMAT_DSPGROUP_TRUESPEECH;
  HACMDRIVERID hadid = find_driver(wFormatTag);
  
      選定了驅動程序,現在要爲最終驅動程序將產生的壓縮數據格式創建一個WAVEFORMATEX結構,併爲驅動程序用於輸入的中間PCM格式產生一個WAVEFORMATEX結構:
  
   WAVEFORMATEX* pwfDrv = get_driver_format(hadid, wFormatTag); // 獲得格式的詳情
   在結構pwfDrv的成員變量wBitsPerSample裏存放着驅動格式的位數,在nSamplesPerSec裏存放着驅動格式的採樣率。然後可以用非常類似的方法獲取驅動程序所支持的PCM格式標籤:
  
   WAVEFORMATEX* pwfPCM = get_driver_format(hadid, WAVE_FORMAT_PCM);
   當以上所需信息都以獲取到後就可以開始轉換數據了。轉換由被ACM稱作流的對象來實現。我們可以打開流,將源格式、目標格式傳遞給它,要求它進行轉換。先將其轉換成中間PCM格式。


  將Wave音頻轉換爲CODEC所支持的PCM格式
  
   通過CODEC將源Wave音頻轉換成CODEC所支持的PCM格式,可以使用任何可以做PCM間轉換的驅動器。另外還有一點很重要:我們打開轉換流時,要指明ACM_STREAMOPENF_NONREALTIME標誌。若省略此標誌,那麼一些驅動程序(例如TrueSpeech CODEC)將會報告發生第512號"不可能發生的"錯誤。該錯誤指明所要求的轉換不能實時進行,假如在試圖播放數據的同時轉換大量數據,就必須注重這點。下面是該步轉換過程的簡要描述:
  
   mmr = acmStreamOpen(&hstr,
  NULL, //任意驅動器
  &wfSrc, //源格式
  pwfPCM, //目標格式
  NULL, //無過濾
  NULL, //無回調
  0, //初始數據
  ACM_STREAMOPENF_NONREALTIME);
   根據以字節計的平均速率計算出輸出緩衝區的大小,並加上一機動位(bit)假如沒有此額外的空間IMA_ADPCM驅動程序將不能轉換。中間的轉換結果將存放在pDst1Data中:
  
   DWORD dwSrcBytes = dwSrcSamples * wfSrc.wBitsPerSample / 8;
  DWORD dwDst1Samples = dwSrcSamples * pwfPCM->nSamplesPerSec / wfSrc.nSamplesPerSec;
  DWORD dwDst1Bytes = dwDst1Samples * pwfPCM->wBitsPerSample / 8;
  unsigned char * pDst1Data = new unsigned char[dwDst1Bytes];
  ……
  ACMSTREAMHEADER strhdr; //填充轉換信息
  memset(&strhdr, 0, sizeof(strhdr));
  strhdr.cbStruct = sizeof(strhdr);
  strhdr.pbSrc = cpBuf; //指定要轉換的源Wave音頻數據爲cpBuf中的數據
  strhdr.cbSrcLength = dwSrcBytes;
  strhdr.pbDst = pDst1Data;
  strhdr.cbDstLength = dwDst1Bytes;
  mmr = acmStreamPrepareHeader(hstr, &strhdr, 0);
  mmr = acmStreamConvert(hstr, &strhdr, 0); //轉換數據
  ……
  acmStreamClose(hstr, 0);
   當流打開時,第二個參數爲NULL,表示接受任何驅動程序進行轉換。複雜的只是計算需要給輸出數據分配多大的緩衝區。PCM格式間的轉換不牽扯壓縮和解壓縮,緩衝區大小直接就計算出來了。至於調用acmStreamPrepareHeader這個ACM API函數,是由於它可以爲驅動程序安排好一切並答應驅動程序在轉換前鎖定內存。
  
  生成最終的壓縮格式
  
   向最終壓縮格式的轉換過程與前面的PCM格式間轉換非常相似,只不過此次轉換我們提供了打開流時想要使用的驅動程序的句柄。實際上,此處仍可使用NULL,因爲已預知此驅動程序存在,但提供句柄避免了系統浪費時間爲我們查找此驅動程序:
  
   mmr = acmStreamOpen(&hstr,
  had, //驅動器句柄
  pwfPCM, //源格式
  pwfDrv, //目標格式
  NULL, //無過濾
  NULL, //無回調
  0, //實例化數據
  ACM_STREAMOPENF_NONREALTIME);
   另外,計算用於壓縮數據的緩衝區的尺寸有點難辦,需要憑猜測。WAVEFORMATEX結構的nAvgBytesPerSec 域表示回放期間讀取字節的平均速率。我們可使用它來估計存儲壓縮的wave需要多大空間。一 些驅動程序給出的數據確實是平均的,而不是最差場合下的值,因此我選擇多增加50%的空間。 此方法在實踐中雖然有些浪費但很有效:
  
   DWORD dwDst2Bytes = pwfDrv->nAvgBytesPerSec * dwDst1Samples / pwfPCM->nSamplesPerSec;
  dwDst2Bytes = dwDst2Bytes * 3 / 2;
  unsigned char * pDst2Data = new unsigned char [dwDst2Bytes];
   其中,無符號字符型指針pDst2Data用於存儲壓縮的最終Wave音頻數據,其大小經上式估算後存到dwDst2Bytes中。一旦轉換完成,ACMSTREAMHEADER的結構的cbDstLengthUsed 域指出緩衝區實際用了多少字節。可以通過它來計算出壓縮比:
  
   double result= (double) dwSrcBytes / (double) strhdr2.cbDstLengthUsed;
  
      當源信號爲8K採樣、16bits PCM編碼、單聲道、長度爲1秒的Wave音頻信號, 驅動程序採用Windows 98自帶的TrueSpeech 音頻CODEC,它能實現大約10:1的壓縮,這樣高的壓縮率還是比較另人滿足的。
  
  小結:
  
   本文以TrueSpeech CODEC爲例對使用ACM音頻壓縮編程接口實現Wave音頻壓縮的過程作了介紹。假如有自已的壓縮格式,也可創建並安裝自已的CODEC,實現的方法與之基本類似。在理解了上述編程思想的前提下,對代碼稍加改動就可編寫出相 應的解壓程序。本程序在windows 2000 Professional下,由Microsoft Visual C++ 6.0編譯通過

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章