本文章是由機器翻譯。

DirectX 要素

音效處理物件簡介

Charles Petzold

下載代碼示例

DirectX 的 XAudio2 元件更多隻是方式在 Windows 8 應用程式中播放聲音和音樂。我是來查看它而是作為一多功能工程組的聲音處理。通過使用多個 IXAudio2SourceVoice 和 IXAudio2SubmixVoice 的情況下,程式師可以將聲音拆分成單獨的管道進行自訂處理,然後將它們合併在最後 IXAudio2MasteringVoice 組合。

如在此列的在上一篇文章中 (msdn.microsoft.com/magazine/dn198248),XAudio2 允許標準音頻將篩選器應用於源和 submix 的聲音。這些篩選器衰減的頻率範圍,並因此改變的諧波含量和音色的聲音。

但更強大的是廣義的設施,提供對通過聲音傳遞實際的音訊流的訪問。你可以簡單地分析這個音訊流,或者修改它。

這一設施被非正式地稱為"音訊效果"。更正式,它涉及到的音訊處理物件 (APO),也稱為一個跨平臺的阿婆或 XAPO,它可以使用 Xbox 360 的應用程式,以及 Windows 時創建。

XAudio2 包括兩個預定義的 APOs 常見任務。XAudio2CreateVolumeMeter 函數創建允許一個程式來動態地獲取應用方便的時間間隔在音訊流的峰值振幅的 APO。XAudio­2CreateReverb 函數創建適用于基於 23 運行時參數語音的回聲或混響的 APO — — 在此上下文中的"運行時"是指亞洲生產力組織積極處理音訊時可以動態更改的參數。此外,一個稱為 XAPOFX 的庫提供了回聲和混響效果,以及卷力矩限制器和一個四波段等化器。

APO 實現 IXAPO 介面,介面和 APO 與運行時參數實現 IXAPOParameters 介面。但比較簡單的方法創建您自己的 APOs 涉及衍生物­ing 從的 CXAPOBase 和 CXAPOParametersBase 的類,這些實現這些介面和處理開銷。

從這兩個類派生是我會在這篇文章中使用的策略。除了其他的標頭檔和重要圖書館我在先前的專欄中討論過,專案的實施 APOs 需要引用 xapobase.h 標頭檔和 xapobase.lib 的導入庫。

使用音訊處理物件

在討論之前 APO 類的內部結構,讓我向您展示如何將效果應用於 XAudio2 的聲音。在此列的可下載代碼中的 SimpleEffectDemo 專案允許您從您的 Windows 8 的音樂庫載入一個檔並播放它。這是我已經在前面的列中顯示的代碼類似:檔載入和解碼使用媒體基礎類,以及 XAudio2 和玩。SimpleEffectDemo 創建只有兩個 XAudio2 聲音:用於生成的音訊和漏斗音訊到聲音硬體所需的掌握語音源聲音。

SimpleEffectDemo 還包含兩個非設定 CXAPO­基地稱為 OneSecondTremoloEffect (這適用顫音或搖擺不定的卷,基於簡單的振幅調製) 和 OneSecondEchoEffect 的衍生品。圖 1 顯示載入的音樂檔與運行的程式。這兩個效果的每個啟用或禁用的切換按鈕。螢幕截圖顯示啟用的回聲效果但顫音效果被禁用。


圖 1 SimpleEffectDemo Program 與兩個音訊效果

APOs 可以應用於任何類型的 XAudio2 的聲音。當應用於源聲音或 submix 聲音,音訊處理後發生內置篩檢程式您設置與 SetFilterParameters,但在應用到音訊發送到使用 SetOutputFilterParameters 其他聲音的篩選器之前。

我選擇對掌握語音應用這些兩個效果。具現化影響並將它們附加到掌握聲音的代碼所示圖 2。每個效果引用與 XAUDIO2_­EFFECT_DESCRIPTOR。如果有多個效果 (正如在這裡的情況),你使用這種結構的陣列。所結構 (或陣列) 然後由一個 XAUDIO2_EFFECT_CHAIN 結構,傳遞給 SetEffectChain 方法支援所有 XAudio2 聲音的引用。該命令影響的事項:在這種情況下回聲效果得到音訊流已應用的顫音效果。

圖 2 將兩個音訊效果應用到一個掌握的聲音

// Create tremolo effect
ComPtr<OneSecondTremoloEffect> pTremoloEffect = new OneSecondTremoloEffect();
// Create echo effect
ComPtr<OneSecondEchoEffect> pEchoEffect = new OneSecondEchoEffect();
// Reference those effects with an effect descriptor array
std::array<XAUDIO2_EFFECT_DESCRIPTOR, 2> effectDescriptors;
effectDescriptors[0].pEffect = pTremoloEffect.Get();
effectDescriptors[0].InitialState = tremoloToggle->IsChecked->Value;
effectDescriptors[0].OutputChannels = 2;
effectDescriptors[1].pEffect = pEchoEffect.Get();
effectDescriptors[1].InitialState = echoToggle->IsChecked->Value;
effectDescriptors[1].OutputChannels = 2;
// Reference that array with an effect chain
XAUDIO2_EFFECT_CHAIN effectChain;
effectChain.EffectCount = effectDescriptors.size();
effectChain.pEffectDescriptors = effectDescriptors.data();
hresult = pMasteringVoice->SetEffectChain(&effectChain);
if (FAILED(hresult))
  throw ref new COMException(hresult, "pMasteringVoice->SetEffectChain failure");

在 SetEffectChain 調用後,效果實例不應進一步引用由該程式。 XAudio2 已添加到這些實例的引用和該程式可以釋放自己的副本,或 ComPtr 可以為你做的。 從這裡,影響確定的指數 — — 在本例中 0 的顫音效果和 1 的回聲效果。 你可能想要使用這些常量的枚舉。

為兩者的影響,我已經將 XAUDIO2_ 的 InitialState 欄位設置­EFFECT_DESCRIPTOR 到切換按鈕選中狀態。 此功能控制效果是最初啟用還是禁用。 後來啟用和禁用由選中和未選中的處理的兩個切換按鈕控制項,如圖所示的影響圖 3

圖 3 啟用和禁用音訊效果

void MainPage::OnTremoloToggleChecked(Object^ sender, 
  RoutedEventArgs^ args)
{
  EnableDisableEffect(safe_cast<ToggleButton^>(sender), 0);
}
void MainPage::OnEchoToggleChecked(Object^ sender, 
  RoutedEventArgs^ args)
{
  EnableDisableEffect(safe_cast<ToggleButton^>(sender), 1);
}
void MainPage::EnableDisableEffect(ToggleButton^ toggle, int index)
{
  HRESULT hresult = toggle->IsChecked->Value ?
pMasteringVoice->EnableEffect(index) :
  pMasteringVoice->DisableEffect(index);
  if (FAILED(hresult))
    throw ref new COMException(hresult, "pMasteringVoice->Enable/DisableEffect " +
       index.ToString());
}

具現化和初始化

OneSecondTremoloEffect 和 OneSecondEchoEffect 從 CXAPOBase 派生。 也許你會遇到從該類派生時的第一個困惑處理 CXAPOBase 建構函式。 此建構函式需要指向已初始化的 XAPO_REGISTRATION_PROPERTIES 結構的指標,但如何不會初始化此結構嗎? C + + 要求在基類的建構函式完成之前執行任何派生類中的代碼。

這是有點困惑,您可以通過定義和初始化結構作為全域變數或靜態欄位,或在一個靜態方法的範圍內解決。 在這種情況下的靜態欄位的方法,你可以看到在 OneSecondTremoloEffect.h 標頭檔中到喜歡圖 4

圖 4 的 OneSecondTremoloEffect.h 標頭檔

#pragma once
class OneSecondTremoloEffect sealed : public CXAPOBase
{
private:
  static const XAPO_REGISTRATION_PROPERTIES RegistrationProps;
  WAVEFORMATEX waveFormat;
  int tremoloIndex;
public:
  OneSecondTremoloEffect() : CXAPOBase(&RegistrationProps),
                             tremoloIndex(0)
  {
  }
protected:
  virtual HRESULT __stdcall LockForProcess(
    UINT32 inpParamCount,
    const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pInpParams,
    UINT32 outParamCount,
    const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pOutParam) override;
  virtual void __stdcall Process(
    UINT32 inpParameterCount,
    const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
    UINT32 outParamCount,
    XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
    BOOL isEnabled) override;
};
class __declspec(uuid("6FB2EBA3-7DCB-4ADF-9335-686782C49911"))
                       OneSecondTremoloEffect;

RegistrationProperties 欄位被初始化 (快到了) 的代碼檔中。 指向它的指標傳遞給 CXAPOBase 的建構函式。 在很多時候 CXAPOBase 導數將還定義欄位類型 WAVEFORMATEX 的 (如同這一個) 或 WAVEFORMATEXTENSIBLE (在一般情況下) 為保存波形格式傳遞通過效果的音訊流。

此外請注意 __declspec ("聲明說明符") 底部的檔,將與一個 GUID 關聯的 OneSecondTremoloEffect 類。 您可以為您自己從Visual Studio中的工具功能表上的創建 GUID 選項的效果類生成一個 GUID。

CXAPOBase 導數必須重寫的過程方法和通常重寫 LockForProcess 方法,以及。 LockForProcess 方法允許 APO 來執行初始化基於特定的音訊格式,其中包括取樣速率、 通道和樣本資料類型的數目。 過程方法實際上執行的分析或音訊資料的修改。

圖 5 顯示了這兩個方法,以及 RegistrationProperties 欄位的初始化。 請注意 XAPO_REGISTRATION_PROPERTIES 的第一個欄位是用類標識的 GUID。

圖 5 OneSecondTremoloEffect.cpp 檔

#include "pch.h"
#include "OneSecondTremoloEffect.h"
const XAPO_REGISTRATION_PROPERTIES OneSecondTremoloEffect::RegistrationProps =
{
  __uuidof(OneSecondTremoloEffect),
  L"One-Second Tremolo Effect",
  L"Coded by Charles Petzold",
  1,      // Major version number
  0,      // Minor version number
  XAPOBASE_DEFAULT_FLAG | XAPO_FLAG_INPLACE_REQUIRED,
  1,      // Min input buffer count
  1,      // Max input buffer count
  1,      // Min output buffer count
  1       // Max output buffer count
};
HRESULT OneSecondTremoloEffect::LockForProcess(
  UINT32 inpParamCount,
  const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pInpParams,
  UINT32 outParamCount,
  const XAPO_LOCKFORPROCESS_BUFFER_PARAMETERS  *pOutParams)
{
  waveFormat = * pInpParams[0].pFormat;
  return CXAPOBase::LockForProcess(inpParamCount, pInpParams,
                                   outParamCount, pOutParams);
}
void OneSecondTremoloEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
  BOOL isEnabled)
{
  XAPO_BUFFER_FLAGS flags = pInpParams[0].BufferFlags;
  int frameCount = pInpParams[0].ValidFrameCount;
  const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParams[0].pBuffer);
  int numChannels = waveFormat.
nChannels;
  switch(flags)
  {
  case XAPO_BUFFER_VALID:
    for (int frame = 0; frame < frameCount; frame++)
    {
      float sin = 1;
      if (isEnabled)
      {
        sin = fabs(DirectX::XMScalarSin(DirectX::XM_PI * tremoloIndex /
                                        waveFormat.
nSamplesPerSec));
        tremoloIndex = (tremoloIndex + 1) % waveFormat.
nSamplesPerSec;
      }
      for (int channel = 0; channel < numChannels; channel++)
      {
        int index = numChannels * frame + channel;
        pDst[index] = sin * pSrc[index];
      }
    }
    break;
  case XAPO_BUFFER_SILENT:
    break;
  }
  pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount;
  pOutParams[0].BufferFlags = pInpParams[0].BufferFlags;
}

在理論上,APOs 可以處理多個輸入的緩衝區和多個輸出緩衝區。 但是,當前僅限於一個輸入的緩衝區和一個輸出緩衝區 APOs。 這種限制影響的 XAPO_REGISTRATION_PROPERTIES 結構和 LockForProcess 和過程方法的參數的最後四個欄位。 對於這兩種方法,inpParamCount 和 outParamCount 總是等於 1,並且指標參數始終指向該指示結構的只是一個實例。

100 每秒調用的速度,APO 的過程方法接收音訊資料輸入的緩衝區和輸出緩衝區進行準備。 它是可能為 APOs 執行格式轉換 — — 例如,若要更改輸入和輸出緩衝區,或通道,數目或樣本的資料類型之間的取樣速率。

這些格式轉換可以很困難,所以您可以指示第六領域的 XAPO_REGISTRATION_PROPERTIES 結構的轉換你不准備實施。 XAPOBASE_DEFAULT_FLAG 指示您不希望執行轉換的取樣速率、 通道的數量、 樣品的位大小或幀大小 (每個程序呼叫中的樣本數)。

通過 APO 的音訊資料的格式是從參數獲得的 LockForProcess 重寫標準的 WAVEFORMATEX 結構的形式。 通常,LockForProcess 只調用一次。 大多數 APOs 需要知道的取樣速率和通道數和這是最好地概括你 APO 的任何可能的值。

同樣關鍵的是示例本身的資料類型。 最常使用的 XAudio2 時,你面對的是 16 位整數或 32 位浮點值的樣本。 在內部,然而,XAudio2 更喜歡使用浮點數據 (c + + 浮點類型),那是你會看到在您 APOs。 如果您願意,您可以驗證中的 LockForProcess 方法的示例資料類型。 然而,它也是我的 WAVEFORMATEX 結構的 wFormatTag 欄位不等於 WAVE_FORMAT_IEEE_FLOAT,可能會按預期的那樣的經驗。 相反,它是 WAVE_FORMAT_EXTENSIBLE (值 65534),這意味著你真的在處理一個 WAVEFORMATEXTENSIBLE 結構,在其中案例子格式欄位指示的資料類型 KSDATAFORMAT_SUBTYPE_IEEE_FLOAT。

如果在 LockForProcess 方法遇到它不能處理的音訊格式,則應返回 HRESULT 指示錯誤,也許 E_NOTIMPL 表示"未實現"。

音訊資料處理

LockForProcess 方法可以花不管什麼時候它需要進行初始化,但過程方法線上程上運行音訊處理,和它必須不拖拖拉拉。 你就會發現為 44,100 Hz 取樣速率,ValidFrameCount 欄位,指示過程稱為每秒 100 次的緩衝區參數等於 441,每次以 10 ms 的音訊資料。 為雙聲道的身歷聲音響,緩衝區包含 882 的浮點型值與交錯的管道:其次是右聲道左的聲道。

BufferFlags 欄位是 XAPO_BUFFER_VALID 或 XAPO_BUFFER_SILENT。 此標誌允許您跳過處理如果沒有實際的音訊資料,來了。 此外,啟用參數,指示是否你已經看過的 EnableEffect 和 DisableEffect 的方法通過啟用了這種效果。

如果該緩衝區是有效的 OneSecondTremoloEffect APO 迴圈通過幀和管道,計算指數為緩衝區,並轉讓浮到目標緩衝區 (pDst) 的值的源緩衝區 (預應力鋼骨混凝土)。 如果禁用了效果,因數為 1 被應用於源值。 如果啟用它,則被應用正弦值,從 DirectX 數學庫使用 zippy XMScalarSin 函數來計算。

過程方法的末尾,在 ValidFrameCount 和 BufferFlags 的輸入的參數結構的對應值的輸出參數結構上設置。

雖然代碼將輸入和輸出緩衝區視為單獨的物件,但這不是實際情況。 之間你可以在 XAPO_REGISTRATION_PROPERTIES 結構中設置的標誌是 (其中包括在 XAPOBASE_DEFAULT_FLAG) 的 XAPO_FLAG_INPLACE_SUPPORTED 和 XAPO_FLAG_INPLACE_REQUIRED。 "就地"一詞意味著對輸入和輸出緩衝區指標 — — 在我的代碼中調用預應力鋼骨混凝土和 pDst — — 實際上相等。 有只有一個緩衝區,用於輸入和輸出。 你絕對應該意識到這一事實在編寫代碼時。

但要小心:這是我的經驗如果刪除了這些標誌,單獨的緩衝區是的確存在,但只輸入的緩衝區是有效的兩個輸入和輸出。

過去的範例保存

顫音效果只需要改變樣本。 回聲效果需要保存前面的示例,因為一秒回聲效果的輸出是當前音訊再加上一秒鐘前從音訊。

這意味著 OneSecondEchoEffect 類需要維護它自己的緩衝區的音訊資料,它在 LockForProcess 方法期間將定義為浮點類型和大小的方法:

delayLength = waveFormat.
nSamplesPerSec;   

int numDelaySamples = waveFormat.
nChannels * 
                      waveFormat.
nSamplesPerSec;delayBuffer.resize(numDelaySamples);

此 delayBuffer 向量是不足以容納一秒的音訊資料,,它被視為一個迴圈緩衝區。 LockForProcess 方法初始化為 0 值的緩衝區,並初始化此緩衝區的索引:

delayIndex = 0;

圖 6 OneSecondEchoEffect 中顯示進程的方法。 因為源音訊已完成後,必須繼續回聲效果,你可以不再跳過處理時,XAPO_BUFFER_SILENT 標誌表示沒有輸入的音訊。 相反,該音效檔完成後,必須繼續發揮回聲末尾的音訊輸出。 變數命名源因此是輸入的音訊或值為 0,根據 XAPO_BUFFER_SILENT 標誌的存在。 此源值的一半加一半的延遲緩衝區中存儲的值和結果保存回入延遲緩衝區中。 在任何時候,你聽到一半當前音訊,加四分之一的音訊從一秒鐘前,八分之一的音訊從兩秒前等等。 您可以調整不同的效果,包括獲取大聲與每個重複的回聲的平衡。

圖 6 OneSecondEchoEffect 中的過程方法

void OneSecondEchoEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParams,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParams,
  BOOL isEnabled)
{
  const float * pSrc = static_cast<float *>(pInpParams[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParams[0].pBuffer);
  int frameCount = pInpParams[0].ValidFrameCount;
  int numChannels = waveFormat.
nChannels;
  bool isSourceValid = pInpParams[0].BufferFlags == XAPO_BUFFER_VALID;
  for (int frame = 0; frame < frameCount; frame++)
  {
    for (int channel = 0; channel < numChannels; channel++)
    {
      // Get sample based on XAPO_BUFFER_VALID flag
      int index = numChannels * frame + channel;
      float source = isSourceValid ?
pSrc[index] : 0.0f;
      // Combine sample with contents of delay buffer and save back
      int delayBufferIndex = numChannels * delayIndex + channel;
      float echo = 0.5f * source + 0.5f * delayBuffer[delayBufferIndex];
      delayBuffer[delayBufferIndex] = echo;
      // Transfer to destination buffer
      pDst[index] = isEnabled ?
echo : source;
    }
    delayIndex = (delayIndex + 1) % delayLength;
  }
  pOutParams[0].BufferFlags = XAPO_BUFFER_VALID;
  pOutParams[0].ValidFrameCount = pInpParams[0].ValidFrameCount;
}

嘗試設置延遲緩衝區的長度為十分之一的第二次:

delayLength = waveFormat.
nSamplesPerSec / 10;

現在您得到更多的比一個獨特的回聲的混響效果。 當然,在真正的阿婆,你會想程式設計控制這些各種參數 (和其他人以及),這就是為什麼真正的回聲混響 APO 由 XAUDIO2FX_REVERB_PARAMETERS 結構與 23 欄位控制。

APO 與參數

大多數 APOs 允許其運行時的參數,可以以程式設計方式設置要更改的行為。 SetEffectParameters 方法為所有的語音類定義和引用特定的 APO 與索引。 設定的 APO 是有點棘手的實現,但不是多。

在本專欄的前一部分中,我演示了如何使用在 XAudio2 源和 submix 聲音中實現內置帶通濾波器來創建 26 波段圖形等化器,在其中每個帶區會影響總的音訊頻譜的三分之一倍頻程。 GraphicEqualizer 程式有效地將聲音拆分成 26 部分的這些篩選器,應用程式,然後重新組合這些音訊流。 這種技術可能似乎有些效率低下。

它是可能在單一的阿婆,實現整個圖形等化器演算法,讓前面的程式只是一個源聲音和一個掌握聲音的效果相同。這是我在 GraphicEqualizer2 程式中做了什麼。 這個新的程式相同的外觀和聲音相同,早期版本的程式,但國內很大的不同。

在將參數傳遞給 APO 的問題之一是執行緒同步。 過程方法的音訊處理執行緒中運行,並可能正在從 UI 執行緒設置參數。 幸運的是,CXAPOParametersBase 類為您執行此同步。

首先,您需要定義一個結構的參數。 26-波段等化器效果,該結構包含是的 26 幅水準陣列的只是一個欄位:

struct OneThirdOctaveEqualizerParameters
{
  std::array<float, 26> Amplitude;};

在程式內,此陣列的成員的計算是從滑塊的分貝值。

若要初始化 CXAPOParametersBase,您需要三個參數的結構的陣列傳遞給其建構函式。 CXAPOParametersBase 使用此區塊的執行的執行緒同步。

我們再次遇到問題的傳遞已初始化派生類對基類建構函式的資料。 我選擇這個時候的解決方案是為受保護定義派生的類的建構函式並將從名為創建、 中所示的公共靜態方法的類具現化圖 7

圖 7 靜態創建的方法為 OneThirdOctaveEqualizerEffect

OneThirdOctaveEqualizerEffect * OneThirdOctaveEqualizerEffect::Create()
{
  // Create and initialize three effect parameters
  OneThirdOctaveEqualizerParameters * pParameterBlocks =
    new OneThirdOctaveEqualizerParameters[3];
  for (int i = 0; i < 3; i++)
    for (int band = 0; band < 26; band++)
      pParameterBlocks[i].Amplitude[band] = 1.0f;
  // Create the effect
  return new OneThirdOctaveEqualizerEffect(
    &RegistrationProps,
    (byte *) pParameterBlocks,
    sizeof(OneThirdOctaveEqualizerParameters),
    false);
}

在 XAudio2 實施的數位二階篩檢程式 (這仿效此 APO) 涉及以下公式:

y = (b0·x + b1·x’ + b2·x’’ – a1·y’ – a2·y’’) / a0

在此公式中,x 是輸入樣品,x' 是上面輸入的示例中和 x ' 是在那之前的示例。 輸出是 y,y' 是的以前的輸出和 y ' 是在那之前的輸出。

一種等化器效果因而需要保存兩個以前的輸入的值,對於每個通道,和以前兩個輸出每個通道,每個帶區的值。

在此公式中的六個常數取決於類型的篩選器 ; 截止頻率 (或帶通濾波器的中心頻率) 與取樣速率 ; 和 Q,篩選器品質。 一-三-八度圖形等化器,每個篩選器具有相應的頻寬,三分之一倍頻程或 4.318 Q。 每個帶區有一套獨特的中所示的代碼計算的 LockForProcess 方法中的常量圖 8

圖 8 等化器篩選器常數的計算

Q = 4.318f;       // One-third octave
static float frequencies[26] =
{
  20.0f, 25.0f, 31.5f, 40.0f, 50.0f, 63.0f, 80.0f, 100.0f, 125.0f,
  160.0f, 200.0f, 250.0f, 320.0f, 400.0f, 500.0f, 630.0f, 800.0f, 1000.0f,
  1250.0f, 1600.0f, 2000.0f, 2500.0f, 3150.0f, 4000.0f, 5000.0f, 6300.0f
};
for (int band = 0; band < 26; band++)
{
  float frequency = frequencies[band];
  float omega = 2 * 3.14159f * frequency / waveFormat.
nSamplesPerSec;
  float alpha = sin(omega) / (2 * Q);
  a0[band] = 1 + alpha;
  a1[band] = -2 * cos(omega);
  a2[band] = 1 - alpha;
  b0[band] = Q * alpha;       // == sin(omega) / 2;
  b1[band] = 0;
  b2[band] = -Q * alpha;      // == -sin(omega) / 2;
}

期間的過程方法,APO 獲取指向當前參數結構與調用 CXAPOParametersBase::BeginProcess,在這種情況下將傳回值轉換為一個結構類型愛撫的­OctaveEqualizerParameters。 在結束的過程方法,對 CXAPOParametersBase::EndProcess 的調用釋放的參數結構的方法等等。 所示的完整的過程方法圖 9

圖 9 OneThirdOctaveEqualizerEffect 中的過程方法

void OneThirdOctaveEqualizerEffect::Process(UINT32 inpParamCount,
  const XAPO_PROCESS_BUFFER_PARAMETERS *pInpParam,
  UINT32 outParamCount,
  XAPO_PROCESS_BUFFER_PARAMETERS *pOutParam,
  BOOL isEnabled)
{
  // Get effect parameters
  OneThirdOctaveEqualizerParameters * pEqualizerParams =
    (OneThirdOctaveEqualizerParameters *) CXAPOParametersBase::BeginProcess();
  // Get buffer pointers and other information
  const float * pSrc = static_cast<float *>(pInpParam[0].pBuffer);
  float * pDst = static_cast<float *>(pOutParam[0].pBuffer);
  int frameCount = pInpParam[0].ValidFrameCount;
  int numChannels = waveFormat.
nChannels;
  switch(pInpParam[0].BufferFlags)
  {
  case XAPO_BUFFER_VALID:
    for (int frame = 0; frame < frameCount; frame++)
    {
      for (int channel = 0; channel < numChannels; channel++)
      {
        int index = numChannels * frame + channel;
        // Do very little if filter is disabled
        if (!isEnabled)
        {
          pDst[index] = pSrc[index];
          continue;
        }
        // Get previous inputs
        float x = pSrc[index];
        float xp = pxp[channel];
        float xpp = pxpp[channel];
        // Initialize accumulated value
        float accum = 0;
        for (int band = 0; band < 26; band++)
        {
          int bandIndex = numChannels * band + channel;
          // Get previous outputs
          float yp = pyp[bandIndex];
          float ypp = pypp[bandIndex];
          // Calculate filter output
          float y = (b0[band] * x + b1[band] * xp + b2[band] * xpp
                                  - a1[band] * yp - a2[band] * ypp) / a0[band];
          // Accumulate amplitude-adjusted filter output
          accum += y * pEqualizerParams->Amplitude[band];
          // Save previous output values
          pypp[bandIndex] = yp;
          pyp[bandIndex] = y;
        }
        // Save previous input values
        pxpp[channel] = xp;
        pxp[channel] = x;
        // Save final value adjusted for filter gain
        pDst[index] = accum / Q;
      }
    }
    break;
  case XAPO_BUFFER_SILENT:
    break;
  }
  // Set output parameters
  pOutParam[0].ValidFrameCount = pInpParam[0].ValidFrameCount;
  pOutParam[0].BufferFlags = pInpParam[0].BufferFlags;
  CXAPOParametersBase::EndProcess();
}

我一直喜歡的一個特點是程式設計的問題往往有多個解決方案。 有時不同的解決辦法是以某種方式,效率更高,有時不。 當然 26 IXAudio2SubmixVoice 實例替換為單個的 APO 是徹底的改變。 但如果您認為此更改會反映在極大地改進了性能,你錯了。 Windows 8 的工作管理員顯示這兩個 GraphicEqualizer 程式是大約相等,暗示將音訊流分割成 26 submix 聲音畢竟不那麼瘋狂。

CharlesPetzold 是 MSDN 雜誌和作者的"程式設計視窗,第 6 版"的長期貢獻 (O'Reilly 媒體,2012年),一本關於編寫應用程式的 Windows 8 書。 他的網站是 charlespetzold.com

衷心感謝以下技術專家對本文的審閱:鄧肯麥凱 (Microsoft) 和JamesMcNellis (Microsoft)