Playing and Processing Audio in UWP Apps with Audio Graphs

UWP apps can take advantage of the Audio Graph API. This allows the creation of audio nodes into an audio signal processing pipeline/graph.

Audio flows from one node to the next with each node performing a defined task and optionally using any applied effects. One of the simplest graphs is to use an AudioFileInputNode to read audio data from a file and an AudioDeviceOutputNode to write it out to the device’s soundcard/headphones.

diagram of simple uwp audio graph nodes connected together

There are a number of nodes provided out of the box including the AudioDeviceInputNode to read audio from the microphone; the AudioFileOutputNode to output audio to a file, and even AudioFrameInputNode and AudioFrameOutputNode to perform custom audio sample processing as part of the graph. As the name suggests, a graph can branch out and recombine as required; for example sending input to 2 other nodes in parallel and then recombining them into a single output node.

Audio effects can also be added to nodes, such as the pre-supplied echo, reverb, EQ, and limiter. Custom effects can also be written and plugged into the graph by implementing the IAudioEffectDefinition interface.

Getting Started

To start, create an empty UWP Windows 10 project and add the following XAML to the MainPage:

<Page
    x:Class="AudioGraphDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Button Name="Play" Click="Play_OnClick">Play</Button>
    </Grid>
</Page>

In the code behind start by adding the following usings:

using System;
using System.Threading.Tasks;
using Windows.Media.Audio;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

And the following fields to hold the audio graph itself and the 2 nodes:

AudioFileInputNode _fileInputNode;
AudioGraph _graph;
AudioDeviceOutputNode _deviceOutputNode;

Next create the button click event handler:

private async void Play_OnClick(object sender, RoutedEventArgs e)
{
    await CreateGraph();
    await CreateDefaultDeviceOutputNode();
    await CreateFileInputNode();

    AddReverb();

    ConnectNodes();

    _graph.Start();
}

We can now add the following methods:

/// <summary>
/// Create an audio graph that can contain nodes
/// </summary>       
private async Task CreateGraph()
{
    // Specify settings for graph, the AudioRenderCategory helps to optimize audio processing
    AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);

    CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);

    if (result.Status != AudioGraphCreationStatus.Success)
    {
        throw new Exception(result.Status.ToString());
    }

    _graph = result.Graph;
}

/// <summary>
/// Create a node to output audio data to the default audio device (e.g. soundcard)
/// </summary>
private async Task CreateDefaultDeviceOutputNode()
{
    CreateAudioDeviceOutputNodeResult result = await _graph.CreateDeviceOutputNodeAsync();

    if (result.Status != AudioDeviceNodeCreationStatus.Success)
    {
        throw new Exception(result.Status.ToString());
    }

    _deviceOutputNode = result.DeviceOutputNode;
}

/// <summary>
/// Ask user to pick a file and use the chosen file to create an AudioFileInputNode
/// </summary>
private async Task CreateFileInputNode()
{
    FileOpenPicker filePicker = new FileOpenPicker
    {
        SuggestedStartLocation = PickerLocationId.MusicLibrary,
        FileTypeFilter = {".mp3", ".wav"}
    };

    StorageFile file = await filePicker.PickSingleFileAsync();

    // file null check code omitted

    CreateAudioFileInputNodeResult result = await _graph.CreateFileInputNodeAsync(file);

    if (result.Status != AudioFileNodeCreationStatus.Success)
    {
        throw new Exception(result.Status.ToString());
    }

    _fileInputNode = result.FileInputNode;
}

/// <summary>
/// Create an instance of the pre-supplied reverb effect and add it to the output node
/// </summary>
private void AddReverb()
{
    ReverbEffectDefinition reverbEffect = new ReverbEffectDefinition(_graph)
    {
        DecayTime = 1
    };

    _deviceOutputNode.EffectDefinitions.Add(reverbEffect);
}

/// <summary>
/// Connect all the nodes together to form the graph, in this case we only have 2 nodes
/// </summary>
private void ConnectNodes()
{
    _fileInputNode.AddOutgoingConnection(_deviceOutputNode);
}

Now when the app is executed, the user can choose a file, this file is then played with added reverb. Check out the MSDN documentation for more info.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

Comments (3) -

  • NigelP

    1/13/2018 10:07:00 AM | Reply

    Nice article Jason.
    This has helped me to understand out how to combine a microphone input with a music track input from a file and then to output this to another file.
    Would you know if it is possible to use the output from the AudioGraph as the input to a MediaCapture object?  This would then allow for recording both the video from a camera and the audio from the graph  into an MP4 file.

  • TC

    2/5/2019 2:58:06 PM | Reply

    Brilliant - this is about the only tutorial I could find out there!

    thanks TC

    • Jason Roberts

      2/8/2019 2:46:15 AM | Reply

      Awesome - glad to have helped TC Smile

Add comment

Loading