本文章是由機器翻譯。

DirectX 要素

以 XAudio2 產生 Windows 8 音效

Charles Petzold

下載代碼示例

Charles Petzold
對於 Windows 8 的 Windows 商店 app 可以播放 MP3 或 WMA 音效檔時輕鬆地使用 MediaElement — — 你只是給它一個 URI 或流的音效檔。Windows 存儲區的應用程式也可以訪問 API 發揮到流媒體視頻或音訊與外部設備。

但是,如果您需要更多先進的音訊處理呢?也許您想修改其路上到硬體,音訊檔的內容或動態生成的聲音。

Windows 應用程式商店還可以執行這些作業通過 DirectX。Windows 8 的聲音生成和處理支援兩個 DirectX 元件 — — 核心音訊和 XAudio2。在宏偉的東西,這些都是相當低級的介面,但核心音訊低於 XAudio2 和更適合於需要與音訊硬體的緊密連接的應用程式。

XAudio2 是的繼任者 DirectSound 和 Xbox XAudio 圖書館,和它可能是您需要做有趣的事情,與聲音的 Windows 存儲應用程式的最佳匹配。雖然 XAudio2 主要供遊戲,但這不應該阻止我們使用它為更嚴重的目的 — — 例如,使音樂或娛樂企業用戶用滑稽的聲音。

XAudio2 版本 2.8 是 Windows 8 的內建群組件。其餘的 DirectX,像 XAudio2 程式設計介面基於 com雖然理論上可能訪問 XAudio2 從 Windows 8 支援任何程式設計語言,用於 XAudio2 的最自然、 最簡單的語言是 c + +。使用聲音往往需要高性能代碼,因此 c + + 是一個不錯的選擇,在這方面也。

第一個 XAudio2 程式

讓我們開始編寫一個程式,它使用 XAudio2 播放簡單 5 秒聲音在按按鈕的回應。因為你可能對 Windows 8 和 DirectX 程式設計的新,我會採取比較慢一點。

我會認為你有適合於創建 Windows 存儲應用程式的安裝 Visual Studio 的版本。在新建專案對話方塊中,選擇 Visual c + + 和 Windows 存儲在左邊和空白 App (XAML) 的可用範本清單中。我給我的專案的名稱 SimpleAudio,和這篇文章可以找到該專案之間的可下載代碼。

在建立可執行檔檔,使用 XAudio2,您需要將程式與 xaudio2.lib 的導入庫連結。造就專案屬性對話方塊中,通過選擇專案功能表上的最後一項,或通過右擊解決方案資源管理器中的專案名稱並選擇屬性。在左邊的列中,選擇配置屬性然後連結器並輸入。附加依賴項 (頂部的專案) 和小小的箭頭,按一下。選擇編輯並在框中鍵入 xaudio2.lib。

你會也想要到的 xaudio2.h 標頭檔的引用,所以將以下語句添加到 pch.h 中的預編譯的頭清單:

#include <xaudio2.h>

在 MainPage.xaml 檔中,用於顯示程式可能遇到與 XAudio2 API 和一個按鈕,用於播放聲音的任何錯誤添加 TextBlock。 這些顯示於 [圖 1] 中。

圖 1 為 SimpleAudio 的 MainPage.xaml 檔

<Page x:Class="SimpleAudio.MainPage" ...
>
  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Name="errorText"
               FontSize="24"
               TextWrapping="Wrap"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
    <Button Name="submitButton"
            Content="Submit Audio Button"
            Visibility="Collapsed"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Click="OnSubmitButtonClick" />
  </Grid>
</Page>

所示的 MainPage.xaml.h 標頭檔大容量圖 2。 我已經刪除宣言 》 的 OnNavigatedTo 方法,因為不會使用它。 為該按鈕的 Click 處理常式聲明,正如 XAudio2 程式的使用與連接的四個欄位。

圖 2 SimpleAudio 的 MainPage.xaml.h 標頭檔

namespace SimpleAudio
{
  public ref class MainPage sealed
  {
  private:
    Microsoft::WRL::ComPtr<IXAudio2> pXAudio2;
    IXAudio2MasteringVoice * pMasteringVoice;
    IXAudio2SourceVoice * pSourceVoice;
    byte soundData[2 * 5 * 44100];
  public:
    MainPage();
  private:
    void OnSubmitButtonClick(Platform::Object^ sender,
      Windows::UI::Xaml::RoutedEventArgs^ args);
  };
}

任何程式都希望使用 XAudio2 必須創建一個實現 IXAudio2 介面的物件。 (您將看到如何做到這一點不久)。IXAudio2 從在 COM 中,著名 IUnknown 類派生,它是本質上的引用計數,這意味著它將刪除其自己的資源,當它不再被引用的程式。 Microsoft::WRL 命名空間中的 ComPtr (COM 指標) 類對 COM 物件的指標變成"智慧指標",用於跟蹤其自身的引用。 這是推薦用於處理在 Windows 應用程式商店中的 COM 物件的方法。

任何非平凡的 XAudio2 程式還需要到實現了 IXAudio2MasteringVoice 和 IXaudio2SourceVoice 介面的物件的指標。 在 XAudio2 用語中,"聲音"是生成或修改音訊資料的物件。 掌握聲音就是從概念上講,召集所有的個別聲音並準備他們的聲音代硬體混音器。 你只能得其中一個,但您可能會有多源聲音生成單獨的聲音。 (也有向源聲音應用篩選器或影響方法。)

IXAudio2MasterVoice 和 IXAudio2SourceVoice 的指標不是引用計數 ; 其存留期受的 IXAudio2 物件。

我的聲音資料值得 5 秒鐘還包含了一個大陣列:

byte soundData[5 * 2 * 44100];

在實際的程式中,您會想要在運行時分配此大小的陣列 — — 和擺脫它時你不需要它 — — 但你很快就會看到為什麼去做這種方式。

如何計算該陣列的大小? 雖然 XAudio2 支援壓縮的音訊,產生聲音的大多數程式將堅持與稱為脈衝代碼調製或 PCM 格式。 在 PCM 聲音波形由固定的取樣速率在固定大小的值表示。 在光碟上的音樂,採樣速率每秒,44,100 次是音訊的與每個身歷聲,共 176,400 位元組的資料為 1 秒採樣的 2 個位元組。 (當在應用程式中嵌入的聲音,壓縮被建議。 XAudio2 支援 ADPCM ; WMA 和 MP3 還支援媒體基礎引擎。)

對於此程式,我也選擇要使用的 44,100 的取樣速率與每個樣本的 2 個位元組。 在 c + +,每個示例都因此短。 我永遠堅持帶單聲道聲音現在,因此 88,200 位元組需要每秒的音訊。 在陣列分配,即 5 秒乘以 5。

創建物件

多的 MainPage.xaml.cpp 檔所示圖 3。 所有 XAudio2 初始化是首頁建構函式中執行的。 它始于對 XAudio2Create 的調用以獲取實現 IXAudio2 介面的物件的指標。 這是使用 XAudio2 的第一步。 與某些 COM 介面,不同的是沒有對 CoCreateInstance 的調用是必需的。

圖 3 MainPage.xaml.cpp

MainPage::MainPage()
{
  InitializeComponent();
  // Create an IXAudio2 object
  HRESULT hr = XAudio2Create(&pXAudio2);
  if (FAILED(hr))
  {
    errorText->Text = "XAudio2Create failure: " + hr.ToString();
    return;
  }
  // Create a mastering voice
  hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice);
  if (FAILED(hr))
  {
    errorText->Text = "CreateMasteringVoice failure: " + hr.ToString();
    return;
  }
  // Create a source voice
  WAVEFORMATEX waveformat;
  waveformat.wFormatTag = WAVE_FORMAT_PCM;
  waveformat.
nChannels = 1;
  waveformat.
nSamplesPerSec = 44100;
  waveformat.
nAvgBytesPerSec = 44100 * 2;
  waveformat.
nBlockAlign = 2;
  waveformat.wBitsPerSample = 16;
  waveformat.cbSize = 0;
  hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &waveformat);
  if (FAILED(hr))
  {
    errorText->Text = "CreateSourceVoice failure: " + hr.ToString();
    return;
  }
  // Start the source voice
  hr = pSourceVoice->Start();
  if (FAILED(hr))
  {
    errorText->Text = "Start failure: " + hr.ToString();
    return;
  }
  // Fill the array with sound data
  for (int index = 0, second = 0; second < 5; second++)
  {
    for (int cycle = 0; cycle < 441; cycle++)
    {
      for (int sample = 0; sample < 100; sample++)
      {
        short value = sample < 50 ?
32767 : -32768;
        soundData[index++] = value & 0xFF;
        soundData[index++] = (value >> 8) & 0xFF;
      }
    }
  }
  // Make the button visible
  submitButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
}
void MainPage::OnSubmitButtonClick(Object^ sender, 
  RoutedEventArgs^ args)
{
  // Create a button to reference the byte array
  XAUDIO2_BUFFER buffer = { 0 };
  buffer.AudioBytes = 2 * 5 * 44100;
  buffer.pAudioData = soundData;
  buffer.Flags = XAUDIO2_END_OF_STREAM;
  buffer.PlayBegin = 0;
  buffer.PlayLength = 5 * 44100;
  // Submit the buffer
  HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
  if (FAILED(hr))
  {
    errorText->Text = "SubmitSourceBuffer failure: " + hr.ToString();
    submitButton->Visibility = 
      Windows::UI::Xaml::Visibility::Collapsed;
    return;
  }
}

一旦創建 IXAudio2 物件時,CreateMasteringVoice 和 CreateSourceVoice 方法獲取指向作為標頭檔中的欄位定義的其他兩個介面的指標。

CreateSourceVoice 調用需要一個 WAVEFORMATEX 結構,這將是熟悉的人曾與 WIN32 API 中的音訊。 這種結構定義將在您使用此特定聲音的音訊資料的性質。 (不同的源的聲音可以使用不同的格式)。對於 PCM,只有三個數字為真正相關:取樣速率 (在此示例中的 44,100),每個樣本 (2 個位元組或 16 位) 和管道 (1 這裡) 的數的大小。 這些根據其他欄位:NBlockAlign 欄位除以 8,nChannels 倍 wBitsPerSample 和 nAvgBytesPerSec 欄位是 nSamplesPerSec 和 nBlockAlign 的產品。 但在此示例中,我展示了所有欄位的顯式值。

一旦獲得的 IXAudio2SourceVoice 物件,可以對它調用 Start 方法。 此時,在概念上播放 XAudio2,但我們其實還沒有給它任何要播放的音訊資料。 Stop 方法也是可用的和一個真正的程式將使用這兩種方法來控制時聲音應該和不應該玩。

這些調用的所有四個不是那麼簡單,他們在此代碼中出現的 ! 他們都有額外的參數,但方便預設值定義,我只是選擇現在接受這些預設設置。

DirectX 中的幾乎所有函數和方法調用都返回 HRESULT 值,以指示成功或失敗。 有不同的策略來處理這些錯誤。 我選擇只是為了顯示使用中的 XAML 檔和停止進一步處理定義的 TextBlock 的錯誤代碼。

PCM 音訊資料

建構函式通過填滿的 soundData 陣列與音訊資料,得出結論,但陣列不實際上分叉以上到 IXAudio2SourceVoice 直到按下的按鈕。

聲音是振動,和人類是對 20 Hz (或每秒週期數) 到 20,000 Hz 的大致範圍中空氣中振動敏感。 在鋼琴上的中央 C 是約 261.6 Hz。

假設您正在使用 44,100 Hz 和 16 位樣品的取樣速率和您想要生成 4,410 Hz,這只是超出了鋼琴上的最關鍵的頻率的波形音訊資料。 這種波形的每個週期需要 10 個樣本的有符號的 16 位值。 這些值 10 時將予以重複,每秒鐘的聲音 4,410 倍。

441 Hz 的頻率的波形 — — 非常接近 440 Hz,對應于上面中間 C 用作一種優化的標準 A — — 呈現與 100 個樣本。 這種迴圈會對聲音的每一秒 441 次重複。

因為 PCM 涉及恒定取樣速率,低頻率的聲音似乎被採樣,並呈現在解析度較高比高頻率的聲音。 這不是一個問題嗎? 只是 10 個樣本呈現 4,410 Hz 波形沒有相當數量的失真和 441 Hz 的波形進行比較嗎?

原來任何量化失真的 PCM 發生頻率大於一半的取樣速率。 (這稱為奈奎斯特頻率後貝爾實驗室工程師 Harry Nyquist)。44,100 Hz 採樣頻率被選為 CD 音訊的原因之一是奈奎斯特頻率是 22,050 Hz,上限是人類聽覺 224 個約 20,000 hz 的頻率。 換句話說,在 44,100 Hz 取樣速率、 量化失真是聽不到人類。

SimpleAudio 程式生成通過演算法簡單的波形 — — 方波 441 Hz 的頻率。 有 100 個樣本,每個週期。 在每個週期首 50 最大正值 (32,767 處理短整數時) 下, 一步的 50 最大負值 (-32768)。 請注意這些短值必須首先具有的低位位元組的位元組陣列中存儲:

soundData[index + 0] = value & 0xFF;
soundData[index + 1] = (value >> 8) & 0xFF;

到目前為止,沒有什麼實際發揮。 為該按鈕的 Click 處理常式中發生這種情況。 XAUDIO2_BUFFER 結構用於引用計數位節和持續時間指定為樣本的數目的位元組陣列。 此緩衝區傳遞給 IXAudio2SourceVoice 物件的 SubmitSourceBuffer 方法。 如果 (在此示例中一樣) 已經調用 Start 方法然後聲音立即開始播放。

我懷疑我不必提及非同步播放聲音。 SubmitSourceBuffer 的調用返回立即雖然單獨的執行緒獻給鏟到聲音硬體資料的實際進程。 可以在調用後放棄,傳遞給 SubmitSourceBuffer 的 XAUDIO2_BUFFER — — 因為它是在此程式中的 Click 處理常式退出和本地變數超出範圍時 — — 但實際的位元組陣列必須保留在可訪問的記憶體中。 事實上,您的程式可以處理這些位元組,如播放聲音。 但是,有很多更好技術 (涉及回檔方法),允許您動態地生成聲音資料的程式。

如果不使用回檔來確定聲音已完成時,此程式需要該程式的持續時間內保留 soundData 陣列。

您可以按按鈕多次,和每個調用進行有效地排隊,另一個緩衝區,當以前的緩衝區完成播放。 如果該程式將移至背景,為靜音,但它繼續發揮在沉默中。 換句話說,如果您按一下按鈕,並至少 5 秒鐘將程式移動到背景,什麼將發揮時,程式返回到前臺。

特徵的聲音

很多我們在日常生活中聽到的聲音同時來自各種不同來源,因此,是相當複雜。 然而,在某些情況下 — — 尤其是當處理音樂的聲音 — — 個別色調可以定義幾個特色:

  • 振幅,這由我們的感官解釋為卷。
  • 頻率,被解釋為螺距。
  • 空間,可以在多個揚聲器的音訊播放中模仿。
  • 音色與感知之間的差額的小號和鋼琴演奏,例如有關色彩的聲音的混合。

SoundCharacteristics 專案演示這些孤立的四個特點。 它備有 44,100 的取樣速率以及 16 位的 SimpleAudio 專案的樣本,但生成身歷聲聲音。 為兩個通道的聲音,必須交叉存取資料:左聲道,右聲道 16 位樣本跟 16 位示例。

SoundCharacteristics 的 MainPage.xaml.h 標頭檔定義了一些常量:

static const int sampleRate = 44100;
static const int seconds = 5;
static const int channels = 2;
static const int samples = seconds * sampleRate;

它還定義了四個數組為聲音資料,但這些類型而不是位元組短的是:

short volumeSoundData[samples * channels];
short pitchSoundData[samples * channels];
short spaceSoundData[samples * channels];
short timbreSoundData[samples * channels];

使用短陣列使初始化更容易,因為 16 位波形值不需要一半被打碎。 簡單強制轉換允許要提交聲音資料時由 XAUDIO2_BUFFER 引用的陣列。 因為我在此程式中使用身歷聲,這些陣列作為陣列在 SimpleAudio 中有雙位元組數。

這些陣列的所有四個首頁建構函式中初始化。 為卷演示、 441 Hz 方波仍然參與,但它起價的容積為零、 獲取過去前 2 秒內,逐漸響亮,然後在卷中下降,在最後 2 秒。 圖 4 顯示的代碼來初始化 volumeSoundData。

圖 4 聲音資料的變化量為 SoundCharacteristics

for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double amplitude = pow(2, 15 * t) - 1;
  short waveform = sample % 100 < 50 ?
1 : -1;
  short value = short(amplitude * waveform);
  volumeSoundData[index++] = value;
  volumeSoundData[index++] = value;
}

人類感知是卷的對數:每增加一倍的波形振幅等同于卷 6 dB 增加。 (用於 CD 音訊的 16 位振幅具有 96 分貝的動態範圍)。在所示的代碼圖 4 首先改變卷計算從 0 線性增加到 1 的 t 的值和跌幅然後回 0。 振幅變數的計算使用的 pow 函數和從 0 到 32767 的範圍。 這被乘以一個方形波具有 1 和-1 的值。 結果是兩次添加到陣列:第一次為左聲道,然後為右聲道。

人類感知也是頻率的對數的。 世界音樂的大部分組織各地區間的八度是頻率增加一倍的螺距。 前兩個注釋"在某個地方上彩虹"是合唱的八度飛躍是否這合唱的唱的低音或女高音。 圖 5 顯示在兩個八度音階的音域範圍內不同螺距的代碼:從 220 到 880 基於 t 中 (如卷示例) 是從 0 到 1 的值,然後調回到 0。

圖 5 聲音資料的變化頻率為 SoundCharacteristics

double angle = 0;
for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double frequency = 220 * pow(2, 2 * t);
  double angleIncrement = 360 * frequency / waveformat.
nSamplesPerSec;
  angle += angleIncrement;
  while (angle > 360)
    angle -= 360;
  short value = angle < 180 ?
32767 : -32767;
  pitchSoundData[index++] = value;
  pitchSoundData[index++] = value;
}

在前面的示例中我選擇了 441 赫茲頻率,因為它乾淨地分為 44,100 的取樣速率。 一般情況下,取樣速率不是整數倍的頻率,並因此不能有樣品每個週期的整數值。 相反,此程式維護一個浮動的點 angleIncrement 變數,是頻率成正比,用於增加一個範圍從 0 到 360 的角度值。 然後,此角度值用於構造波形。

空間示範移動聲音從中心到左通道,然後向右,然後回中心。 音色演示,波形開頭 441 hz 的正弦曲線。 正弦曲線是振動的最基本類型的數學表述 — — 力在哪裡位移成反比的差分方程的解。 所有其他定期波形包含諧波,這也是正弦波但與基頻的整數倍數為單位的頻率。 音色演示更改波形順利從正弦波到三角波,方波,鋸齒波,同時增加諧波含量的聲音。

更大的圖片

雖然我剛剛證明可以如何通過生成一個 IXAudio2SourceVoice 物件的聲音資料控制音量、 音調、 空間和音色,物件本身包括方法來更改音量和空間,以及甚至的頻率。 ("3D"空間設施是也支援。雖然它是可能生成複合的聲音資料,它一堆個別的色調結合單源的聲音,可以創建多個 IXAudio2SourceVoice 物件並通過相同的掌握語音一起發揮他們。

此外,XAudio2 定義允許您定義的篩選器和其他效果,如混響或回聲的 IXAudioSubmixVoice。 篩選器有能力動態地更改現有色調的音色,可以極大地有助於創造有趣和現實的音樂聲。

也許以後我在這兩個程式中證明了什麼最重要加強要求與 XAudio2 回呼函數的工作。 而不是分配和初始化大塊大塊的聲音資料,這兩個程式一樣,它使得程式來動態生成聲音資料,因為它正在發揮的更多意義。

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

由於以下的技術專家對本文的審閱: Scott Selfon