音訊圖
本文介紹如何使用 Windows.Media.Audio 命名空間中的 API 為音訊路由、混音和處理案例建立音訊圖。
音訊圖是音訊資料流經的一組互連的音訊節點。
音訊輸入節點會從音訊輸入裝置、音訊檔案或自訂程式碼提供音訊資料給圖形。 lat
音訊輸出節點是圖形所處理音訊的目的地。 音訊可以從圖形路由傳送至音訊輸出裝置、音訊檔案或自訂程式碼。
子混合節點從一個或多個節點獲取音訊,並將它們組合成單一輸出,該輸出可以路由到圖中的其他節點。
建立所有節點並設定它們之間的連線之後,您只需啟動音訊圖,音訊資料就會從輸入節點通過任何子混合節點流向輸出節點。 此模型使得諸如從裝置的麥克風錄製到音訊檔案、將檔案中的音訊播放到裝置的喇叭或混合來自多個來源的音訊等案例變得快速且容易實作。
新增音訊效果至音訊圖時,會啟用其他案例。 音訊圖中的每個節點都可以填入零個或多個音訊效果,以在透過節點傳遞的音訊上執行音訊處理。 有多種內建效果,例如回音、均衡器、限制和殘響,只需幾行程式碼即可將其附加到音訊節點。 您也可以建立自己的自訂音訊效果,其運作方式與內建效果完全相同。
注意
AudioGraph UWP 範例會實作此概觀中討論的程式碼。 您可以下載範例,以查看內容中的程式碼,或做為您自己的應用程式的起點。
選擇 Windows 執行階段 AudioGraph 或 XAudio2
Windows 執行階段音訊圖 API 提供的功能,也可以透過使用依據 COM 的 XAudio2 APIs 來實作。 以下是 Windows 執行階段音訊圖架構與 XAudio2 不同的功能。
Windows 執行階段音訊圖 API:
- 比 XAudio2 更容易使用。
- 除了支援 C++ 之外,還可以在 C# 中使用。
- 可以直接使用音訊檔案,包括壓縮檔案格式。 XAudio2 僅對音訊緩衝區進行操作,不提供任何檔案 I/O 功能。
- 可以在 Windows 10 中使用低延遲音訊管線。
- 使用預設端點參數時,支援自動切換端點。 例如,如果使用者從裝置的喇叭切換到頭戴式裝置,音訊會自動重新導向至新的輸入。
AudioGraph 類別
AudioGraph 類別是構成圖形之所有節點的父系。 使用此物件來建立所有音訊節點類型的執行個體。 透過初始化包含圖形組態設定的 AudioGraphSettings 物件,然後呼叫 AudioGraph.CreateAsync,建立 AudioGraph 類別的執行個體。 傳回的 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.ToString());
}
audioGraph = result.Graph;
}
所有音訊節點類型都是使用 AudioGraph 類別的 Create* 方法建立的。
AudioGraph.Start 方法會使音訊圖開始處理音訊資料。 AudioGraph.Stop 方法會停止音訊處理。 圖形中的每個節點都可以在圖形執行時獨立啟動和停止,但在圖形停止時,不會有任何節點是使用中。 ResetAllNodes 會導致圖形中的所有節點捨棄目前在其音訊緩衝區中的任何資料。
當圖形開始處理新的音訊資料量子時,就會發生 QuantumStarted 事件。 QuantumProcessed 事件會在完成量子處理時發生。
唯一需要的 AudioGraphSettings 屬性是 AudioRenderCategory。 指定此值可讓系統最佳化指定類別的音訊管線。
音訊圖的量子大小決定一次處理的樣本數。 預設情況下,基於預設取樣率的量子大小為 10 毫秒。 如果您藉由設定 DesiredSamplesPerQuantum 屬性來指定自訂量子大小,您也必須將 QuantumSizeSelectionMode 屬性設定為 ClosestToDesired 或被忽略的提供值。 如果使用此值,系統將選擇盡可能接近您指定的量子大小。 若要判斷實際的量子大小,請在建立 AudioGraph 之後檢查 AudioGraph 的 SamplesPerQuantum。
如果您僅計劃將音訊圖與檔案一起使用,而不計劃輸出到音訊裝置,建議您透過不設定 DesiredSamplesPerQuantum 屬性來使用預設量子大小。
DesiredRenderDeviceAudioProcessing 屬性會決定主要轉譯裝置在音訊圖輸出上執行的處理量。 預設設定可讓系統針對指定的音訊轉譯類別使用預設音訊處理。 此處理可顯著改善某些裝置上的音訊聲音,特別是具有小型喇叭的行動裝置。 Raw 設定可以藉由將所執行的訊號處理量降至最低來改善效能,但可能會導致某些裝置的音效品質不佳。
如果 QuantumSizeSelectionMode 設定為 LowestLatency,音訊圖將自動使用 Raw 於 DesiredRenderDeviceAudioProcessing。
從 Windows 10 版本 1803 開始,您可以設定 AudioGraphSettings.MaxPlaybackSpeedFactor 屬性以設定用於 AudioFileInputNode.PlaybackSpeedFactor, AudioFrameInputNode.PlaybackSpeedFactor 的最大值,以及 MediaSourceInputNode.PlaybackSpeedFactor 屬性。 當音訊圖支援大於 1 的播放速度係數時,系統必須配置額外的記憶體,才能維護足夠的音訊資料緩衝區。 出於這個原因,將 MaxPlaybackSpeedFactor 設定為應用程式所需的最低值,將會減少您的應用程式記憶體使用量。 如果您的 app 只會以正常速度播放內容,建議您將 MaxPlaybackSpeedFactor 設定為 1。
EncodingProperties 會決定圖形所使用的音訊格式。 僅支援 32 位元浮點格式。
PrimaryRenderDevice 會設定音訊圖的主要轉譯裝置。 如果您並未進行此設定,則會使用預設系統裝置。 主要轉譯裝置可用來計算圖形中其他節點的量子大小。 如果系統上沒有音訊轉譯裝置,音訊圖建立將會失敗。
您可以透過呼叫 FindAllAsync 並傳入 Windows.Media.Devices.MediaDevice.GetAudioRenderSelector 傳回的音訊轉譯裝置選擇器,讓音訊圖使用預設音訊轉譯裝置或使用 Windows.Devices.Enumeration.DeviceInformation 類別取得系統可用音訊轉譯裝置的清單。 您可以選擇其中一個以程序設計方式傳 回的 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;
裝置輸入節點
裝置輸入節點會將音訊從連線到系統的音訊擷取裝置 (例如麥克風) 饋送到圖形中。 透過呼叫 CreateDeviceInputNodeAsync,建立使用系統預設音訊擷取裝置的 DeviceInputNode 物件。 提供 AudioRenderCategory,讓系統能夠最佳化指定類別的音訊管線。
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;
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());
}
fileInputNode = result.FileInputNode;
}
- 檔案輸入節點支援下列檔案格式:mp3、wav、wma、m4a。
- 設定 StartTime 屬性,以指定應該開始播放之檔案的時間位移。 如果此屬性為 null,則使用檔案的開頭。 設定 EndTime 屬性,以指定應該結束播放之檔案的時間位移。 如果此屬性為 null,則使用檔案的結尾。 開始時間值必須小於結束時間值,且結束時間值必須小於或等於音訊檔案的持續時間,這可以透過檢查 Duration 屬性值來確定。
- 透過呼叫 Seek 並指定要移動播放位置之檔案的時間位移,以搜尋音訊檔案中的位置。 指定的值必須位於 StartTime 和 EndTime 範圍內。 使用唯讀的 Position 屬性取得節點的目前播放位置。
- 透過設定 LoopCount 屬性來啟用音訊檔案的迴圈。 當非 null 時,這個值表示檔案在初始播放之後將播放的次數。 因此,例如,將 LoopCount 設定為 1 會導致檔案總共播放 2 次,而設定為 5 會導致檔案總共播放 6 次。 將 LoopCount 設定為 null 會導致檔案無限期地迴圈。 若要停止迴圈,請將值設定為 0。
- 透過設定 PlaybackSpeedFactor 來調整音訊檔案的播放速度。 值 1 表示檔案的原始速度,0.5 為半速,2 為雙倍速。
MediaSource 輸入節點
MediaSource 類別提供一種引用來自不同來源的媒體的通用方法,並公開用於存取媒體資料的通用模型,無論底層媒體格式如何 (可以是磁碟上的檔案、串流或自適性串流網路來源)。 **MediaSourceAudioInputNode 節點可讓您將音訊資料從 MediaSource 導向音訊圖。 透過呼叫 CreateMediaSourceAudioInputNodeAsync,傳入表示您希望播放的內容的 MediaSource物件,來建立 MediaSourceAudioInputNode。 **CreateMediaSourceAudioInputNodeResult 傳回可用來透過檢查 Status 屬性來決定操作的狀態。 如果狀態為 Success,您可以藉由存取 Node 屬性來取得已建立的 MediaSourceAudioInputNode。 以下範例顯示了從表示網路內容串流的 AdaptiveMediaSource 物件建立節點。 如需使用 MediaSource 的詳細資訊,請參閱媒體項目、播放清單和曲目。 有關網際網路上串流媒體內容的更多資訊,請參閱自適性串流。
MediaSourceAudioInputNode mediaSourceInputNode;
private async Task CreateMediaSourceInputNode(System.Uri contentUri)
{
if (audioGraph == null)
return;
var adaptiveMediaSourceResult = await AdaptiveMediaSource.CreateFromUriAsync(contentUri);
if(adaptiveMediaSourceResult.Status != AdaptiveMediaSourceCreationStatus.Success)
{
Debug.WriteLine("Failed to create AdaptiveMediaSource");
return;
}
var 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 事件。 您可以使用此事件的處理常式來停止並處理音訊圖,然後重新初始化您的圖形。
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 List<string>() { ".wav" });
saveFilePicker.FileTypeChoices.Add("Windows Media Audio", new List<string>() { ".wma" });
saveFilePicker.FileTypeChoices.Add("MPEG Audio Layer-3", new List<string>() { ".mp3" });
saveFilePicker.SuggestedFileName = "New Audio Track";
StorageFile file = await saveFilePicker.PickSaveFileAsync();
// File can be null if cancel is hit in the file picker
if (file == null)
{
return;
}
Windows.Media.MediaProperties.MediaEncodingProfile mediaEncodingProfile;
switch (file.FileType.ToString().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);
}
}
- 傳遞到 QuantumStarted 事件處理常式中的 FrameInputNodeQuantumStartedEventArgs 物件公開了 RequiredSamples 屬性,該屬性指示音訊圖需要多少個樣本來填入要處理的量子。
- 呼叫 AudioFrameInputNode.AddFrame 即可將填滿音訊資料的 AudioFrame 物件傳遞到圖形中。
- Windows 10 版本 1803 中引入了一組新的 API,用於將 MediaFrameReader 與音訊資料結合使用。 這些 API 可讓您從媒體畫面來源取得 AudioFrame 物件,可以使用 AddFrame 方法將其傳遞到 FrameInputNode 中。 如需詳細資訊,請參閱使用 MediaFrameReader 處理音訊畫面。
- 下面顯示了 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 Windows.Media.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 中設定專案以允許編譯不安全程式碼,方法是開啟專案的屬性頁面,按一下建置屬性頁面,然後選取允許不安全程式碼核取方塊。
- 透過將所需的緩衝區大小傳遞給建構函式,在 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 事件。 您可以從此事件的處理常式中存取音訊資料。
注意
如果您想要定期擷取音訊畫面並與音訊圖同步,請從同步 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 時,無論各個節點的狀態如何,所有節點中的所有音訊處理都會停止,但在音訊圖停止時可以設定每個節點的狀態。 例如,您可以在圖形停止時在單一節點上呼叫停止,然後呼叫 AudioGraph.Start,單一節點將保持停止狀態。
所有節點類型都會公開 ConsumInput 屬性,當設定為 false 時,可讓節點繼續音訊處理,但會阻止它取用來自其他節點輸入的任何音訊資料。
所有節點類型都會公開重設方法,讓節點捨棄其緩衝區中目前的任何音訊資料。
新增音訊效果
音訊圖 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 會啟用具有指定定義的效果。
空間音訊
從 Windows 10 版本 1607 開始,AudioGraph 支援空間音訊,這可讓您指定從任何輸入或子混合節點發出音訊的 3D 空間位置。 您也可以指定發出音訊的形狀和方向、用於對節點音訊進行 Doppler 頻移的速度,並定義描述音訊如何隨距離衰減的衰減模型。
若要建立發射器,您可以先建立一個形狀,聲音從發射器投射到該形狀中,該形狀可以是圓錐形或全向的。 AudioNodeEmitterShape 類別提供了用於建立每個形狀的靜態方法。 接下來,建立衰減模型。 這定義了發射器發出的音訊音量,如何隨著與聆聽者距離的增加而減少。 CreateNatural 方法會建立一個衰減模型,該模型使用距離平方衰減模型來模擬聲音的自然衰減。 最後,建立 AudioNodeEmitterSettings 物件。 目前,這個物件僅用於啟用和停用發射器音訊的速度型 Doppler 衰減。 呼叫 AudioNodeEmitter 建構函式,傳入您剛才建立的初始化物件。 預設情況下,發射器放置在原點,但您可以使用位置屬性設定發射器的位置。
注意
音訊節點發射器只能處理使用取樣速率為 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 System.Numerics.Vector3(10, 0, 5);
CreateAudioFileInputNodeResult result = await audioGraph.CreateFileInputNodeAsync(file, emitter);
if (result.Status != AudioFileNodeCreationStatus.Success)
{
ShowErrorMessage(result.Status.ToString());
}
fileInputNode = result.FileInputNode;
將音訊從圖形輸出給使用者的 AudioDeviceOutputNode 有一個接聽程式物件,可透過 Listener 屬性存取,該物件表示使用者在 3D 空間中的位置、方向和速度。 圖形中所有發射器的位置都相對於接聽程式物件的位置和方向。 預設情況下,接聽程式位於原點 (0,0,0),沿著 Z 軸面向前,但您可以使用位置和方向屬性設定其位置和方向。
deviceOutputNode.Listener.Position = new System.Numerics.Vector3(100, 0, 0);
deviceOutputNode.Listener.Orientation = System.Numerics.Quaternion.CreateFromYawPitchRoll(0, (float)Math.PI, 0);
您可以在執行階段更新發射器的位置、速度和方向,以模擬音訊來源在 3D 空間中的移動。
var emitter = fileInputNode.Emitter;
emitter.Position = newObjectPosition;
emitter.DopplerVelocity = newObjectPosition - oldObjectPosition;
您也可以在執行階段更新接聽程式物件的位置、速度和方向,以模擬使用者在 3D 空間中的移動。
deviceOutputNode.Listener.Position = newUserPosition;
預設情況下,空間音訊是使用 Microsoft 的頭部相關傳輸函式 (HRTF) 演算法計算的,以根據音訊的形狀、速度和相對於接聽程式的位置來衰減音訊。 您可以將 SpatialAudioModel 屬性設定為 FoldDown 以使用簡單的立體聲混合方法來模擬空間音訊,這種方法不太準確,但需要較少的 CPU 和記憶體資源。
另請參閱