音频图

本文介绍如何使用 Windows.Media.Audio 命名空间中的 API 来创建音频路由、混合和处理方案的音频图。

音频图是音频数据流经的一组相互连接的音频节点。

  • 音频输入节点为音频图提供来自音频输入设备、音频文件或自定义代码的音频数据。 lat

  • 音频输出节点是音频图处理的音频的目标。 可以绕过音频图将音频路由到音频输出设备、音频文件或自定义代码。

  • 子混合节点从一个或多个节点获取音频,并将其合并为可以路由到音频图中其他节点的单个输出。

创建完所有节点并在它们之间建立连接后,你只需启动音频图,音频数据便会从输入节点开始,流经所有子混合节点,最后流至输出节点。 此模型可以快速轻松地实现以下方案:将设备麦克风的音频录制到音频文件、通过设备扬声器播放文件中的音频,或混合来自多个源的音频。

其他方案通过向音频图添加音频效果实现。 音频图中的每个节点都可以通过零或更多种音频效果来填充, 音频效果会对通过该节点的音频执行音频处理。 提供回音、均衡器、限制和混响等几种内置效果, 只需几行代码即可将其附加到音频节点。 你还可以创建其效果与内置效果完全相同、你自己的自定义音频效果。

注意

AudioGraph UWP 示例可实现本概述中所讨论的代码。 你可以下载该示例以查看上下文中的代码,或将该示例用作你自己的应用的起点。

选择 Windows 运行时 AudioGraph 或 XAudio2

Windows 运行时音频图 API 提供也可通过使用基于 COM 的 XAudio2 API 实现的功能。 以下是不同于 XAudio2 的 Windows 运行时音频图框架的功能。

Windows 运行时音频图 API:

  • 比使用 XAudio2 简单得多。
  • 除了受 C++ 支持,还可以通过 C# 使用。
  • 可以直接使用音频文件,包括压缩的文件格式。 XAudio2 仅在音频缓冲区上运行,不提供任何文件 I/O 功能。
  • 可以使用 Windows 10 中的低延迟音频管道。
  • 支持在默认终结点参数处于使用状态时,自动切换终结点。 例如,如果用户从设备扬声器切换到耳机,则音频会自动重定向到新输入。

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.ToString());
    }

    audioGraph = result.Graph;

}
  • 所有音频节点类型均通过使用 AudioGraph 类的 Create* 方法创建。

  • AudioGraph.Start 方法可使音频图开始处理音频数据。 AudioGraph.Stop 方法终止音频处理。 在音频图运行时,音频图中的每一个节点都可以单独启动和停止,但在音频图停止运行时,没有任何节点会处于活动状态。 ResetAllNodes 将使图形中的所有节点丢弃当前处于其音频缓冲区中的任何数据。

  • 当音频图开始处理新的音频数据量子时,将发生 QuantumStarted 事件。 当处理完某个量子时,将发生 QuantumProcessed 事件。

  • 仅需的 AudioGraphSettings 属性是 AudioRenderCategory。 指定此值将允许系统优化指定类别的音频管道。

  • 音频图的量子大小确定一次处理的样本数。 默认情况下,基于默认采样率,量子大小为 10 毫秒。 如果你通过设置 DesiredSamplesPerQuantum 属性来指定自定义量子大小,则还必须将 QuantumSizeSelectionMode 属性设置为 ClosestToDesired,或忽略提供的值。 如果使用此值,则系统将选择尽可能接近你所指定大小的量子大小。 若要确定实际量子大小,请在创建 AudioGraph 之后检查它的 SamplesPerQuantum

  • 如果你仅计划将音频图和文件结合使用,而并不打算输出到音频设备,建议你不设置 DesiredSamplesPerQuantum 属性而使用默认量子大小。

  • DesiredRenderDeviceAudioProcessing 属性确定主呈现设备量对音频图输出执行的处理量。 Default 设置允许系统针对指定的音频呈现类别使用默认音频处理。 此处理可以明显地改善音频在某些设备上的声音,尤其是配备小型扬声器的移动设备。 Raw 设置可以通过尽量减少执行的信号处理量来提高性能,但会导致某些设备上的声音质量变差。

  • 如果 QuantumSizeSelectionMode 设置为 LowestLatency,则音频图会自动将 Raw 用于 DesiredRenderDeviceAudioProcessing

  • 自 Windows 10 版本 1803 起,可设置 AudioGraphSettings.MaxPlaybackSpeedFactor 属性,进而设置用于 AudioFileInputNode.PlaybackSpeedFactorAudioFrameInputNode.PlaybackSpeedFactorMediaSourceInputNode.PlaybackSpeedFactor 属性的最大值。 音频图支持的播放速度系数大于 1 时,系统必须分配额外的内存,以确保拥有足够大的音频数据缓存区。 为此,如果将 MaxPlaybackSpeedFactor 设置为应用所需的最低值,则会减少应用的内存消耗。 如果应用仅以正常速度播放内容,建议将 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;
}

如果要为设备输入节点指定特定音频捕获设备,可以通过调用 FindAllAsync 并传入由 Windows.Media.Devices.MediaDevice.GetAudioCaptureSelector 返回的音频呈现设备选择器,使用 Windows.Devices.Enumeration.DeviceInformation 类来获取系统的可用音频捕获设备列表。 你可以以编程方式选择返回的 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 并在文件中指定播放位置应移动到的时间偏移,在音频文件中寻找某个位置。 指定的值必须介于 StartTimeEndTime 范围内。 通过只读的 Position 属性获取节点的当前播放位置。
  • 通过设置 LoopCount 属性启用音频文件的循环播放。 如果此值为非 null,则指示该文件在初始播放后继续播放的次数。 例如,将 LoopCount 设置为 1 将导致该文件总共播放 2 次,而将其设置为 5 将导致该文件总共播放 6 次。 将 LoopCount 设置为 null 将导致该文件无限循环播放。 若要停止循环播放,请将该值设置为 0。
  • 通过设置 PlaybackSpeedFactor 调整音频文件的播放速度。 值 1 表示该文件的原始速度、0.5 表示半速,2 表示双倍速度。

MediaSource 输入节点

MediaSource 类提供从不同的源引用媒体的常用方法,并公开用于访问媒体数据的常用模型,而不考虑基础媒体格式(可能是磁盘上的文件或自适应流式处理网络源)。 可使用 **MediaSourceAudioInputNode 节点将 MediaSource 中的音频数据定向到音频图中。 通过调用 CreateMediaSourceAudioInputNodeAsync 创建 MediaSourceAudioInputNode,进而传入代表想要播放的内容的 MediaSource 对象。 返回了 **CreateMediaSourceAudioInputNodeResult,你可以使用它检查 Status 属性,从而确定操作的状态。 如果状态是成功,可通过访问 Node 属性获取所创建的 MediaSourceAudioInputNode。 以下示例介绍如何使用代表通过网络进行内容流式处理的 AdaptiveMediaSource 对象创建节点。 若要详细了解如何使用 MediaSource,请参阅媒体项、播放列表和曲目。 若要详细了解如何通过 Internet 流式处理媒体内容,请参阅自适应流式处理

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;
}

音频帧输入节点

音频帧输入节点允许你将使用自己的代码生成的音频数据推送到音频图中。 这可实现创建自定义软件合成器等方案。 通过调用 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);

可创建多个从某个输入节点到其他节点的连接。 下面的示例添加了从 AudioFileInputNodeAudioFileOutputNode 的另一种连接方法。 现在,音频文件中的音频将在设备的扬声器中播放,还将写出到音频文件。

fileInputNode.AddOutgoingConnection(fileOutputNode);

输出节点也可以接收来自其他节点的多个连接。 下面的示例将建立从 AudioDeviceInputNodeAudioDeviceOutput 节点的连接。 由于输出节点具有来自文件输入节点和设备输入节点的连接,输出将包含来自这两个源的混合音频。 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 时,音频图将开始处理音频数据。 每个节点类型都提供可使单个节点开始或停止处理数据的 StartStop 方法。 调用 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 使用指定定义启用效果。

空间音频

自 Windows 10 版本 1607 开始,AudioGraph 支持空间音频,这使你可以根据来自任何输入或子混合节点发出的音频指定 3D 空间中的位置。 还可以指定发出音频的形状和方向、将用于 Doppler 切换节点音频的速率,以及定义描述音频如何随距离衰减的衰减模型。

若要创建发射器,可以先创建从发射器投射声音所用的形状,该形状可以是锥形或全向。 AudioNodeEmitterShape 类提供用于创建其中每个形状的静态方法。 接下来,创建一个衰减模型。 该模型定义发射器的音频音量如何随侦听器距离的增加而降低。 CreateNatural 方法创建衰减模型,可使用距离平方衰减模型模拟声音的自然衰减。 最后,创建 AudioNodeEmitterSettings 对象。 当前,此对象仅用于启用和禁用基于速率的发射器音频的 Doppler 衰减。 调用 AudioNodeEmitter 构造函数,传入刚创建的初始化对象。 默认情况下,将发射器放置在原点,但你可以使用 Position 属性设置发射器的位置。

注意

音频节点发射器只可以处理采样频率为 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 轴正面向前),但可以使用 PositionOrientation 属性设置其位置和方向。

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 和内存资源也较少)。

另请参阅