本文說明如何使用 Windows 中的 API。Media.Audio命名空間,用於建立音訊路由、混音與處理場景的音訊圖表。
音訊圖是一組相互連接的音訊節點,音訊資料會透過這些節點流動。
音訊輸入節點會 從音訊輸入裝置、音訊檔案或自訂程式碼中,向圖形提供音訊資料。
音訊輸出節點 是圖形處理音訊的目的地。 音訊可以從圖表中路由到音訊輸出裝置、音訊檔案或自訂程式碼。
次混音節點會 將一個或多個節點的音訊合併成單一輸出,並可路由到圖中其他節點。
當所有節點建立完成並建立好它們之間的連接後,你只要開始音訊圖,音訊資料就會從輸入節點,經過任何子混音節點,流向輸出節點。 此模型使得從裝置麥克風錄製音訊檔案、將音訊播放至裝置喇叭,或混合多個來源音訊等情境變得快速且簡單。
透過加入音頻效果到音頻圖表,還能啟用更多情境。 音訊圖中的每個節點都可以被填充零個或多個音訊效果,這些效果會對通過該節點的音訊進行音訊處理。 內建多種效果,如回聲、均衡器、限制和混響,只需幾行程式碼即可附加於音訊節點。 你也可以自訂音效,效果和內建音效完全一樣。
選擇 Windows 執行階段 AudioGraph 或 XAudio2
Windows 執行階段 音訊圖 API 提供的功能,也可透過基於 COM 的 XAudio2 API來實作。 以下是 Windows 執行階段 音訊圖框架與 XAudio2 不同的特點。
Windows 執行階段 音訊圖形 API:
- 比 XAudio2 容易上手很多。
- 除了支援 C++ 外,也能從 C# 使用。
- 可以直接使用音訊檔案,包括壓縮檔案格式。 XAudio2 只支援音訊緩衝區,且不提供任何檔案 I/O 功能。
- 可以在 Windows 中使用低延遲音訊管線。
- 當使用預設端點參數時,支援自動端點切換。 例如,如果使用者從裝置的喇叭切換到耳機,音訊會自動導向新的輸出。
AudioGraph 類別
AudioGraph 類別是構成該圖的所有節點的父節點。 使用這個物件來建立所有音訊節點類型的實例。 建立 AudioGraph 類別的實例,方法是初始化包含圖組設定的物件 AudioGraphSettings,然後呼叫 AudioGraph.CreateAsync。 回傳的 CreateAudioGraphResult 會提供已建立的音訊圖表存取權,或在建立音訊圖表失敗時提供錯誤值。
AudioGraph audioGraph;
private async Task InitAudioGraph()
{
AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
if (result.Status != AudioGraphCreationStatus.Success)
{
ShowErrorMessage("AudioGraph creation error: " + result.Status);
return;
}
audioGraph = result.Graph;
}
所有音訊節點類型皆透過 AudioGraph 類別的 Create* 方法建立。
AudioGraph.Start 方法會讓音訊圖開始處理音訊資料。 AudioGraph.Stop 方法會停止音訊處理。 圖中每個節點在圖執行時可獨立啟動與停止,但停止圖時則無節點處於活躍狀態。 ResetAllNodes 會讓圖中所有節點丟棄目前音訊緩衝區中的資料。
QuantumStarted 事件發生在圖形開始處理新的音訊資料量子時。 當量子處理完成時,量子 處理 事件就會發生。
唯一需要的 AudioGraphSettings屬性是 AudioRenderCategory。 指定此值可讓系統針對指定類別優化音訊管線。
音頻圖的量子大小決定了一次處理的取樣數量。 預設情況下,量子大小為 10 毫秒,基於預設取樣率。 如果你透過設定 DesiredSamplesPerQuantum 屬性來指定自訂量子大小,你也必須將 QuantumSizeSelectionMode 屬性設為 ClosestToDesire ,否則所提供的值會被忽略。 如果使用這個值,系統會選擇一個盡可能接近你指定的量子尺寸。 若要確定實際量子大小,請在 AudioGraph 建立後檢查其 SamplesPerQuantum。
如果你只打算用音訊圖和檔案,且不打算輸出到音訊裝置,建議你使用預設的量子大小,不設定 DesiredSamplesPerQuantum 屬性。
DesiredRenderDeviceAudioProcessing 屬性決定主要渲染裝置對音訊圖輸出的處理量。 預設設定允許系統使用指定的音訊渲染類別的預設音訊處理。 這種處理能顯著提升某些裝置的音質,特別是帶有小型喇叭的行動裝置。 Raw 設定可透過減少訊號處理量來提升效能,但在某些裝置上可能導致音質較差。
如果 QuantumSizeSelectionMode 設定為 LowestLatency,音訊圖表會自動使用 Raw 來執行 DesiredRenderDeviceAudioProcessing。
你可以設定 AudioGraphSettings.MaxPlaybackSpeedFactor 屬性,讓 AudioFileInputNode.PlaybackSpeedFactor 、 AudioFrameFrameInputNode.PlaybackSpeedFactor 和 MediaSourceInputNode.PlaybackSpeedFactor 屬性使用最大值。 當音訊圖支援播放速度因子大於1時,系統必須分配額外記憶體以維持足夠的音訊資料緩衝區。 因此,將 MaxPlaybackSpeedFactor 設定為應用程式所需的最低值,可以減少應用程式的記憶體使用量。 如果你的應用程式只能以正常速度播放內容,建議將 MaxPlaybackSpeedFactor 設為 1。
EncodingProperties 決定了圖形所使用的音訊格式。 僅支援 32 位元浮點數格式。
PrimaryRenderDevice 設定了音訊圖的主要渲染裝置。 如果你沒設定這個,預設系統裝置就會被使用。 主要的渲染裝置用於計算圖中其他節點的量子大小。 如果系統中沒有音訊渲染裝置,音訊圖表的建立將失敗。
你可以讓音訊圖形使用預設的音訊轉譯裝置,或使用 Windows.Devices.Enumeration.DeviceInformation 類別,透過呼叫 FindAllAsync,並傳入由 Windows.Media.Devices.MediaDevice.GetAudioRenderSelector 傳回的音訊轉譯裝置選取器,來取得系統中可用的音訊轉譯裝置清單。 你可以以程式方式選擇回傳的 DeviceInformation 物件,或顯示 UI 讓使用者選擇裝置,然後用它來設定 PrimaryRenderDevice 屬性。
Windows.Devices.Enumeration.DeviceInformationCollection devices =
await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioRenderSelector());
// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);
settings.PrimaryRenderDevice = selectedDevice;
裝置輸入節點
裝置輸入節點會從連接系統的音訊擷取裝置(如麥克風)輸入音訊到圖表中。 透過呼叫
AudioDeviceInputNode deviceInputNode;
private async Task CreateDeviceInputNode()
{
// Create a device output node
CreateAudioDeviceInputNodeResult result = await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media);
if (result.Status != AudioDeviceNodeCreationStatus.Success)
{
// Cannot create device output node
ShowErrorMessage(result.Status.ToString());
return;
}
deviceInputNode = result.DeviceInputNode;
}
如果您想為裝置輸入節點指定特定的音訊擷取裝置,可以使用 Windows.Devices.Enumeration.DeviceInformation 類別,方法是呼叫 FindAllAsync,並傳入由 Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector 傳回的音訊擷取裝置選取器,以取得系統可用的音訊擷取裝置清單。 你可以用程式方式選擇回傳的 DeviceInformation 物件,或顯示 UI 讓使用者選擇裝置,然後傳入 CreateDeviceInputNodeAsync。
Windows.Devices.Enumeration.DeviceInformationCollection devices =
await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector());
// Show UI to allow the user to select a device
Windows.Devices.Enumeration.DeviceInformation selectedDevice = ShowMyDeviceSelectionUI(devices);
CreateAudioDeviceInputNodeResult result =
await audioGraph.CreateDeviceInputNodeAsync(Windows.Media.Capture.MediaCategory.Media, audioGraph.EncodingProperties, selectedDevice);
裝置輸出節點
裝置輸出節點將音訊從圖表推送到音訊渲染裝置,例如喇叭或耳機。 呼叫 CreateDeviceOutputNodeAsync 來建立 DeviceOutputNode。 輸出節點使用音訊圖的 PrimaryRenderDevice 。
AudioDeviceOutputNode deviceOutputNode;
private async Task CreateDeviceOutputNode()
{
// Create a device output node
CreateAudioDeviceOutputNodeResult result = await audioGraph.CreateDeviceOutputNodeAsync();
if (result.Status != AudioDeviceNodeCreationStatus.Success)
{
// Cannot create device output node
ShowErrorMessage(result.Status.ToString());
return;
}
deviceOutputNode = result.DeviceOutputNode;
}
檔案輸入節點
檔案輸入節點允許你將音訊檔案的資料輸入圖表。 呼叫 CreateFileInputNodeAsync 來建立 AudioFileInputNode。
AudioFileInputNode fileInputNode;
private async Task CreateFileInputNode()
{
if (audioGraph == null)
{
return;
}
FileOpenPicker filePicker = new FileOpenPicker();
filePicker.SuggestedStartLocation = PickerLocationId.MusicLibrary;
filePicker.FileTypeFilter.Add(".mp3");
filePicker.FileTypeFilter.Add(".wav");
filePicker.FileTypeFilter.Add(".wma");
filePicker.FileTypeFilter.Add(".m4a");
filePicker.ViewMode = PickerViewMode.Thumbnail;
WinRT.Interop.InitializeWithWindow.Initialize(filePicker, _hwnd);
StorageFile file = await filePicker.PickSingleFileAsync();
// File can be null if cancel is hit in the file picker
if (file == null)
{
return;
}
CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file);
if (result.Status != AudioFileNodeCreationStatus.Success)
{
ShowErrorMessage(result.Status.ToString());
return;
}
fileInputNode = result.FileInputNode;
}
- 檔案輸入節點支援以下檔案格式:mp3、wav、wma、m4a。
- 設定 StartTime 屬性,指定播放開始的檔案時間偏移。 若此屬性為空,則使用檔案的開頭。 設定 EndTime 屬性,以指定播放應在檔案中的哪個時間位移點結束。 若此屬性為空,則使用檔案結尾。 開始時間值必須低於結束時間值,結束時間值則必須小於或等於音訊檔案的長度,這可透過檢查 Duration 屬性值來確定。
- 透過呼叫 Seek 並指定播放位置應該移動到檔案的時間偏移,來尋找音訊檔案中的某一位置。 指定的值必須在 StartTime 和 EndTime 範圍內。 使用唯讀的 Position 屬性取得節點目前的播放位置。
- 透過設定 LoopCount 屬性來啟用音訊檔案的循環。 當非空值時,此值表示該檔案在初次播放後將播放的次數。 舉例來說,將 LoopCount 設為 1,檔案總共播放 2 次;設為 5,檔案總共播放 6 次。 將 LoopCount 設為 null 會讓檔案無限循環。 要停止迴圈,請將值設為 0。
- 透過設定 播放速度因子(PlaybackSpeedFactor)來調整音訊檔案播放速度。 1 表示檔案的原始速度,0.5 表示半速,2 表示雙倍速度。
MediaSource 輸入節點
MediaSource 類別提供了一種共通的方式來參考不同來源的媒體,並暴露出一種共通的模型,無論底層媒體格式為何,這些媒體格式可以是磁碟上的檔案、串流或自適應串流網路來源。 MediaSourceAudioInputNode 節點可以讓你將 MediaSource 的音訊資料導向音訊圖表。 藉由呼叫 CreateMediaSourceAudioInputNodeAsync,並傳入代表您要播放內容的 MediaSource 物件,建立 MediaSourceAudioInputNode。 會回傳一個 CreateMediaSourceAudioInputNodeResult ,你可以透過檢查 Status 屬性來判斷操作狀態。 如果狀態是成功,你可以透過存取節點屬性取得已建立的 MediaSourceAudioInputNode。 以下範例展示了從 AdaptiveMediaSource 物件建立節點,代表網路上串流的內容。
MediaSourceAudioInputNode mediaSourceInputNode;
private async Task CreateMediaSourceInputNode(Uri contentUri)
{
if (audioGraph == null)
{
return;
}
var adaptiveMediaSourceResult = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);
if (adaptiveMediaSourceResult.Status != AdaptiveMediaSourceCreationStatus.Success)
{
Debug.WriteLine("Failed to create AdaptiveMediaSource");
return;
}
MediaSource mediaSource = MediaSource.CreateFromAdaptiveMediaSource(adaptiveMediaSourceResult.MediaSource);
CreateMediaSourceAudioInputNodeResult mediaSourceAudioInputNodeResult =
await audioGraph.CreateMediaSourceAudioInputNodeAsync(mediaSource);
if (mediaSourceAudioInputNodeResult.Status != MediaSourceAudioInputNodeCreationStatus.Success)
{
switch (mediaSourceAudioInputNodeResult.Status)
{
case MediaSourceAudioInputNodeCreationStatus.FormatNotSupported:
Debug.WriteLine("The MediaSource uses an unsupported format");
break;
case MediaSourceAudioInputNodeCreationStatus.NetworkError:
Debug.WriteLine("The MediaSource requires a network connection and a network-related error occurred");
break;
case MediaSourceAudioInputNodeCreationStatus.UnknownFailure:
default:
Debug.WriteLine("An unknown error occurred while opening the MediaSource");
break;
}
return;
}
mediaSourceInputNode = mediaSourceAudioInputNodeResult.Node;
}
若要在 MediaSource 內容播放結束時收到通知,請註冊 MediaSourceCompleted 事件的處理程序。
mediaSourceInputNode.MediaSourceCompleted += MediaSourceInputNode_MediaSourceCompleted;
private void MediaSourceInputNode_MediaSourceCompleted(MediaSourceAudioInputNode sender, object args)
{
audioGraph.Stop();
}
雖然從磁碟播放檔案通常都能成功完成,但從網路來源串流的媒體在播放時可能因網路連線變更或其他音訊圖表無法控制的問題而失敗。 如果 MediaSource 在播放時無法播放,音訊圖表會觸發 UnrecoverableErrorOccurred 事件。 你可以使用此事件的處理常式來停止並釋放音訊圖形,然後重新初始化你的圖形。
if (audioGraph != null)
{
audioGraph.UnrecoverableErrorOccurred += AudioGraph_UnrecoverableErrorOccurred;
}
private void AudioGraph_UnrecoverableErrorOccurred(AudioGraph sender, AudioGraphUnrecoverableErrorOccurredEventArgs args)
{
if (sender == audioGraph && args.Error != AudioGraphUnrecoverableError.None)
{
Debug.WriteLine("The audio graph encountered and unrecoverable error.");
audioGraph.Stop();
audioGraph.Dispose();
_ = InitAudioGraph();
}
}
檔案輸出節點
檔案輸出節點可讓你將圖形中的音訊資料輸出至音訊檔案。 藉由呼叫 CreateFileOutputNodeAsync 來建立 AudioFileOutputNode。
AudioFileOutputNode fileOutputNode;
private async Task CreateFileOutputNode()
{
FileSavePicker saveFilePicker = new FileSavePicker();
saveFilePicker.FileTypeChoices.Add("Pulse Code Modulation", new System.Collections.Generic.List<string>() { ".wav" });
saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new System.Collections.Generic.List<string>() { ".wma" });
saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new System.Collections.Generic.List<string>() { ".mp3" });
saveFilePicker.SuggestedFileName = "New Audio Track";
WinRT.Interop.InitializeWithWindow.Initialize(saveFilePicker, _hwnd);
StorageFile file = await saveFilePicker.PickSaveFileAsync();
// File can be null if cancel is hit in the file picker
if (file == null)
{
return;
}
MediaEncodingProfile mediaEncodingProfile;
switch (file.FileType.ToLowerInvariant())
{
case ".wma":
mediaEncodingProfile = MediaEncodingProfile.CreateWma(AudioEncodingQuality.High);
break;
case ".mp3":
mediaEncodingProfile = MediaEncodingProfile.CreateMp3(AudioEncodingQuality.High);
break;
case ".wav":
mediaEncodingProfile = MediaEncodingProfile.CreateWav(AudioEncodingQuality.High);
break;
default:
throw new ArgumentException();
}
// Operate node at the graph format, but save file at the specified format
CreateAudioFileOutputNodeResult result = await audioGraph.CreateFileOutputNodeAsync(file, mediaEncodingProfile);
if (result.Status != AudioFileNodeCreationStatus.Success)
{
// FileOutputNode creation failed
ShowErrorMessage(result.Status.ToString());
return;
}
fileOutputNode = result.FileOutputNode;
}
- 檔案輸出節點支援以下檔案格式:mp3、wav、wma、m4a。
- 你必須先呼叫 AudioFileOutputNode.Stop 來停止該節點的處理,才能呼叫 AudioFileOutputNode.FinalizeAsync ,否則會拋出例外。
音訊框架輸入節點
音訊框架輸入節點允許你將自己程式碼中產生的音訊資料推送到音訊圖中。 這讓像是建立自訂軟體合成器等情境成為可能。 透過呼叫 CreateFrameInputNode 建立 AudioFrameInputNode。
AudioFrameInputNode frameInputNode;
private void CreateFrameInputNode()
{
// Create the FrameInputNode at the same format as the graph, except explicitly set mono.
AudioEncodingProperties nodeEncodingProperties = audioGraph.EncodingProperties;
nodeEncodingProperties.ChannelCount = 1;
frameInputNode = audioGraph.CreateFrameInputNode(nodeEncodingProperties);
// Initialize the Frame Input Node in the stopped state
frameInputNode.Stop();
// Hook up an event handler so we can start generating samples when needed
// This event is triggered when the node is required to provide data
frameInputNode.QuantumStarted += node_QuantumStarted;
}
當音訊圖準備開始處理下一量子音訊資料時,會觸發 FrameInputNode.QuantumStarted 事件。 你可以在處理常式內,將自訂產生的音訊資料提供給此事件。
private void node_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQuantumStartedEventArgs args)
{
// GenerateAudioData can provide PCM audio data by directly synthesizing it or reading from a file.
// Need to know how many samples are required. In this case, the node is running at the same rate as the rest of the graph
// For minimum latency, only provide the required amount of samples. Extra samples will introduce additional latency.
uint numSamplesNeeded = (uint)args.RequiredSamples;
if (numSamplesNeeded != 0)
{
AudioFrame audioData = GenerateAudioData(numSamplesNeeded);
frameInputNode.AddFrame(audioData);
}
}
- FrameInputNodeQuantumStartedEventArgs 物件傳遞至 QuantumStarted 事件處理程序,會暴露出 RequiredSamples 屬性,指示音訊圖需要多少樣本才能填滿要處理的量子。
- 呼叫 AudioFrameInputNode.AddFrame,將一個充滿音訊資料的 AudioFrame 物件傳送到圖表中。
- 你可以使用 MediaFrameReader 搭配音訊資料,從媒體影格來源取得 AudioFrame 物件,並可透過 AddFrame 方法傳送至 FrameInputNode。
- 以下展示了 GenerateAudioData 輔助工具方法的實作範例。
要將音訊資料填充 AudioFrame,必須取得該音訊框架底層的記憶體緩衝區存取權。 為此,請初始化如下所示的 IMemoryBufferByteAccess COM 介面。
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
以下程式碼展示了一個範例實作的 GenerateAudioData 輔助方法,該方法可建立 AudioFrame 並填充音訊資料。
private double audioWaveTheta = 0;
unsafe private AudioFrame GenerateAudioData(uint samples)
{
// Buffer size is (number of samples) * (size of each sample)
// We choose to generate single channel (mono) audio. For multi-channel, multiply by number of channels
uint bufferSize = samples * sizeof(float);
AudioFrame frame = new AudioFrame(bufferSize);
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
using (IMemoryBufferReference reference = buffer.CreateReference())
{
byte* dataInBytes;
uint capacityInBytes;
float* dataInFloat;
// Get the buffer from the AudioFrame
((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);
// Cast to float since the data we are generating is float
dataInFloat = (float*)dataInBytes;
float freq = 1000; // choosing to generate frequency of 1kHz
float amplitude = 0.3f;
int sampleRate = (int)audioGraph.EncodingProperties.SampleRate;
double sampleIncrement = (freq * (Math.PI * 2)) / sampleRate;
// Generate a 1kHz sine wave and populate the values in the memory buffer
for (int i = 0; i < samples; i++)
{
double sinValue = amplitude * Math.Sin(audioWaveTheta);
dataInFloat[i] = (float)sinValue;
audioWaveTheta += sampleIncrement;
}
}
return frame;
}
- 由於此方法會存取Windows 執行階段類型底下的原始緩衝區,必須使用
unsafe 關鍵字來宣告。 你也必須在Microsoft Visual Studio中設定專案,允許編譯不安全的程式碼,方法是開啟專案的Properties頁面,點擊 Build屬性頁面,並選擇 Allow Unsafe Code勾選框。 - 將所需的緩衝區大小傳遞給建構函式,以初始化 Windows.Media 命名空間中的新 AudioFrame 執行個體。 緩衝區大小是取樣數乘以每個取樣的大小。
- 藉由呼叫 LockBuffer,取得音訊幀的 AudioBuffer。
- 從音訊緩衝區呼叫 CreateReference 取得 IMemoryBufferByteAccess COM 介面的實例。
- 透過呼叫 IMemoryBufferByteAccess.GetBuffer 取得原始音訊緩衝區資料的指標,並將其轉型為音訊資料的樣本資料型別。
- 將緩衝區填滿資料,並回傳 AudioFrame 提交到音訊圖中。
音訊幀輸出節點
音訊幀輸出節點允許你透過自訂程式碼接收並處理來自音訊圖的音訊資料輸出。 一個例子是對音訊輸出進行訊號分析。 呼叫 CreateFrameOutputNode 來建立 AudioFrameOutputNode。
AudioFrameOutputNode frameOutputNode;
private void CreateFrameOutputNode()
{
frameOutputNode = audioGraph.CreateFrameOutputNode();
audioGraph.QuantumStarted += AudioGraph_QuantumStarted;
}
當音訊圖開始處理一量子音訊資料時,AudioGraph.QuantumStarted事件會被觸發。 你可以在此事件的處理常式中存取音訊資料。
Note
如果你想以固定週期擷取音訊影格,並與音訊圖形同步,請在同步的 QuantumStarted 事件處理常式內呼叫 AudioFrameOutputNode.GetFrame。 QuantumProcessed 事件會在音訊引擎完成音訊處理後非同步提出,這意味著其節奏可能會不規則。 因此,你不應該使用 QuantumProcessed 事件來同步處理音訊影格資料。
private void AudioGraph_QuantumStarted(AudioGraph sender, object args)
{
AudioFrame frame = frameOutputNode.GetFrame();
ProcessFrameOutput(frame);
}
- 呼叫 GetFrame 取得一個 AudioFrame 物件,內含圖表中的音訊資料。
- 以下展示了 ProcessFrameOutput 輔助方法的實作範例。
unsafe private void ProcessFrameOutput(AudioFrame frame)
{
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
using (IMemoryBufferReference reference = buffer.CreateReference())
{
byte* dataInBytes;
uint capacityInBytes;
float* dataInFloat;
// Get the buffer from the AudioFrame
((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacityInBytes);
dataInFloat = (float*)dataInBytes;
}
}
- 就像前面的音訊框架輸入節點範例一樣,你需要宣告 IMemoryBufferByteAccess COM 介面,並設定專案允許不安全的程式碼存取底層的音訊緩衝區。
- 藉由呼叫 LockBuffer,取得音訊幀的 AudioBuffer。
- 從音訊緩衝區呼叫 CreateReference 取得 IMemoryBufferByteAccess COM 介面的實例。
- 透過呼叫IMemoryBufferByteAccess.GetBuffer取得原始音訊緩衝區資料的指標,並將其轉型為音訊資料的樣本資料類型。
節點連接與子混合節點
所有輸入節點類型都提供 AddOutgoingConnection 方法,將由該節點產生的音訊路由至傳入此方法的節點。 以下範例連接 AudioFileInputNode 與 AudioDeviceOutputNode,這是一個簡單的裝置喇叭播放音訊檔案的設定。
fileInputNode.AddOutgoingConnection(deviceOutputNode);
你可以從一個輸入節點建立多個連接到其他節點的連線。 以下範例新增另一個從 AudioFileInputNode 到 AudioFileOutputNode 的連線。 現在,音訊檔案會播放到裝置的喇叭,並輸出成音訊檔。
fileInputNode.AddOutgoingConnection(fileOutputNode);
輸出節點也可能接收來自其他節點的多個連線。 以下範例中,從 AudioDeviceInputNode 連接到 AudioDeviceOutput 節點。 由於輸出節點連接檔案輸入節點與裝置輸入節點,輸出會包含兩個來源的混合音訊。 AddOutgoingConnection 提供一個過載功能,讓你能為通過連接的訊號設定增益值。
deviceInputNode.AddOutgoingConnection(deviceOutputNode, .5);
雖然輸出節點可以接受來自多個節點的連線,但你可能會想先將一個或多個節點的訊號混合成中介訊號,再將其傳送至輸出節點。 例如,你可能想設定音量或對圖表中的部分音訊訊號套用效果。 要做到這點,請使用 AudioSubmixNode。 你可以從一個或多個輸入節點或其他子混頻節點連接到子混合節點。 在以下範例中,會用 AudioGraph.CreateSubmixNode 建立一個新的子混音節點。 接著,將從檔案輸入節點和影格輸入節點到子混音節點的連線加入。 最後,子混音節點連接到檔案輸出節點。
private void CreateSubmixNode()
{
AudioSubmixNode submixNode = audioGraph.CreateSubmixNode();
fileInputNode.AddOutgoingConnection(submixNode);
frameInputNode.AddOutgoingConnection(submixNode);
submixNode.AddOutgoingConnection(fileOutputNode);
}
啟動與停止音訊圖節點
當 AudioGraph.Start 被呼叫時,音訊圖開始處理音訊資料。 每種節點類型都提供 開始 與 停止 方法,使個別節點開始或停止處理資料。 當呼叫 AudioGraph.Stop 時,所有節點的所有音訊處理都會停止,無論個別節點的狀態如何,但每個節點的狀態可以在音訊圖停止時設定。 例如,你可以在圖停止時呼叫單一節點的 Stop ,然後呼叫 AudioGraph.Start,該節點仍維持停止狀態。
所有節點類型都會暴露 ConsumeInput 屬性,當該屬性設為 false 時,允許節點繼續音訊處理,但不會消耗來自其他節點輸入的音訊資料。
所有節點類型都會暴露 Reset 方法,該方法會讓節點丟棄目前緩衝區中的任何音訊資料。
加入音效
音訊圖 API 允許你為圖中每種節點加入音訊效果。 輸出節點、輸入節點與次混音節點各自可擁有無限數量的音訊效果,僅受硬體能力限制。 以下範例示範如何在子混音節點中加入內建的回聲效果。
EchoEffectDefinition echoEffect = new EchoEffectDefinition(audioGraph);
echoEffect.Delay = 1000.0;
echoEffect.Feedback = .2;
echoEffect.WetDryMix = .5;
submixNode.EffectDefinitions.Add(echoEffect);
- 所有音效皆實作 IAudioEffectDefinition。 每個節點都會暴露一個 EffectDefinitions 屬性,代表該節點所施加的效果清單。 透過將效果的定義物件加入清單來新增效果。
- Windows.Media.Audio 命名空間中提供了數個效果定義類別。 這些包括:
- 你可以自己建立實作 IAudioEffectDefinition 的音效,並套用到音訊圖中的任意節點。
- 每個節點類型都會暴露一個 DisableEffectsByDefinition 方法,該方法會停用該節點 EffectDefinitions 清單中所有使用指定定義新增的效果。 EnableEffectsByDefinition 能啟用指定定義的效果。
空間音效
AudioGraph 支援空間音訊,讓你能指定 3D 空間中任何輸入或子混音節點音訊的輸出位置。 你也可以指定音訊發射的形狀與方向、用於對該節點的音訊套用都卜勒偏移的速度,並定義衰減模型,以描述音訊如何隨距離增加而衰減。
要製作發射器,你可以先創造一個形狀,讓聲音從發射器投射出去,可以是錐形或全向型。 AudioNodeEmitterShape類別提供建立這些形狀的靜態方法。 接著,建立衰減模型。 這定義了隨著距離聽者增加,發射器發出的音訊音量如何降低。 CreateNatural 方法建立一個衰減模型,利用距離平方衰減模型模擬聲音的自然衰減。 最後,建立一個 AudioNodeEmitterSettings 物件。 目前,這個物件僅用於啟用或停用發射器音訊中基於速度的多普勒衰減。 呼叫 AudioNodeEmitter 建構子,傳入你剛建立的初始化物件。 預設情況下,發射器會放在原點,但你可以用 Position 屬性設定發射器的位置。
Note
音訊節點發射器只能處理以單聲道格式、取樣率為 48kHz 的音訊。 嘗試使用立體聲音訊或取樣率不同的音訊,會引發例外狀況。
你在建立音訊節點時,會使用你想要之節點類型的多載建立方法,將發射器指派給該音訊節點。 在此範例中,CreateFileInputNodeAsync 用於從指定檔案與你想關聯的 AudioNodeEmitter 物件建立檔案輸入節點。
var emitterShape = AudioNodeEmitterShape.CreateOmnidirectional();
var decayModel = AudioNodeEmitterDecayModel.CreateNatural(.1, 1, 10, 100);
var settings = AudioNodeEmitterSettings.None;
var emitter = new AudioNodeEmitter(emitterShape, decayModel, settings);
emitter.Position = new Vector3(10, 0, 5);
CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file, emitter);
if (result.Status != AudioFileNodeCreationStatus.Success)
{
ShowErrorMessage(result.Status.ToString());
return;
}
fileInputNode = result.FileInputNode;
AudioDeviceOutputNode 從圖中輸出音訊給使用者,有一個聆聽者物件,該物件以 Listener 屬性存取,代表使用者在三維空間中的位置、方向與速度。 圖中所有發射器的位置相對於聆聽物件的位置與方向。 預設情況下,聆聽器位於原點(0,0,0),面向前方,沿著 Z 軸,但你可以用 Position 和 Orientation 屬性設定其位置與方向。
deviceOutputNode.Listener.Position = new Vector3(100, 0, 0);
deviceOutputNode.Listener.Orientation = Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI, 0);
你可以在執行時更新發射器的位置、速度和方向,模擬音源在三維空間中的移動。
AudioNodeEmitter emitter = fileInputNode.Emitter;
emitter.Position = newObjectPosition;
emitter.DopplerVelocity = newObjectPosition - oldObjectPosition;
你也可以在執行時更新聆聽物件的位置、速度和方向,以模擬使用者在 3D 空間中的移動。
deviceOutputNode.Listener.Position = newUserPosition;
預設情況下,空間音訊是利用 Microsoft 的頭部相對傳遞函數(HRTF)演算法計算,根據音訊的形狀、速度及相對於聽者的位置來衰減音訊。 你可以將 SpatialAudioModel 屬性設為 FoldDown ,這樣可以用簡單的立體聲混音方法模擬空間音效,雖然準確度較低,但需要的 CPU 和記憶體資源較少。