257 lines
10 KiB
C#
Raw Normal View History

2021-09-23 17:30:38 +12:00
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Media;
using Windows.Media.Audio;
using Windows.Media.Capture;
using Windows.Media.Devices;
using Windows.Media.Playback;
using Windows.Media.Render;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace Audio_Router
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
//AudioGraph graph;
// The list of audio devices
DeviceInformationCollection sourceDevices;
DeviceInformationCollection targetDevices;
public MainPage()
{
this.InitializeComponent();
App.appRef.Suspending += AppRef_Suspending;
App.appRef.Resuming += AppRef_Resuming;
App.appRef.EnteredBackground += AppRef_EnteredBackground;
App.appRef.LeavingBackground += AppRef_LeavingBackground;
}
private void AppRef_LeavingBackground(object sender, Windows.ApplicationModel.LeavingBackgroundEventArgs e)
{
if (IsLoaded)
{
BackgroundRestore();
}
}
private void AppRef_EnteredBackground(object sender, Windows.ApplicationModel.EnteredBackgroundEventArgs e)
{
BackgroundCleanup();
}
private void AppRef_Resuming(object sender, object e)
{
BackgroundRestore();
}
/// <summary>
/// Clear existing elements from this display, to free memory
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AppRef_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
{
BackgroundCleanup();
}
private void BackgroundCleanup()
{
// Clear all items from the lists
InputAudioComboBox.Items.Clear();
OutputAudioComboBox.Items.Clear();
CreatedGraphs.Children.Clear();
// Ensure the GC collects freed memory, by explicitly calling the GC
GC.Collect();
}
private void BackgroundRestore()
{
// Start the task that populates the lists
if (InputAudioComboBox.Items.Count > 0 || OutputAudioComboBox.Items.Count > 0) return;
_ = Task.Run(() => FindAudioSources());
// And add existing graphs back to the page
foreach (AudioGraphConnection graphConneciton in App.appRef.audioGraphConnections)
{
Grid renderGrid = graphConneciton.GetAsDisplayItem(out Button visualButton);
CreatedGraphs.Children.Add(renderGrid);
void handler(object sender, RoutedEventArgs e)
{
visualButton.Click -= handler;
DestroyVisualElement(ref renderGrid);
}
visualButton.Click += handler;
}
}
/// <summary>
/// When the page is navigated to, populate the list
/// </summary>
/// <param name="e"></param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Populate audio device list; as a Task to avoid delaying creation
_ = Task.Run(() => FindAudioSources());
}
private async Task FindAudioSources()
{
// First, clear the lists
_ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
InputAudioComboBox.Items.Clear();
OutputAudioComboBox.Items.Clear();
});
// Perform this on our spawned thread, as the operation is asynchronous-synchronous await
sourceDevices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioCaptureSelector());
targetDevices = await DeviceInformation.FindAllAsync(MediaDevice.GetAudioRenderSelector());
DeviceInformation defaultOutput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default));
DeviceInformation defaultInput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Default));
_ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Set the Input device
for (int i = 0; i < sourceDevices.Count; i++)
{
if (sourceDevices[i].Id == defaultInput.Id)
{
InputAudioComboBox.Items.Add(sourceDevices[i].Name + " (Default Device)");
InputAudioComboBox.SelectedIndex = i;
}
else
{
InputAudioComboBox.Items.Add(sourceDevices[i].Name);
}
}
for (int i = 0; i < targetDevices.Count; i++)
{
if(targetDevices[i].Id == defaultOutput.Id)
{
OutputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
OutputAudioComboBox.SelectedIndex = i;
}
else
{
OutputAudioComboBox.Items.Add(targetDevices[i].Name);
}
}
});
}
private async void startStreamButton_Click(object sender, RoutedEventArgs e)
{
await CreateAudioGraph();
}
private async Task CreateAudioGraph()
{
App.displayRequest.RequestActive();
DeviceInformation outputDevice = targetDevices[OutputAudioComboBox.SelectedIndex];
DeviceInformation inputDevice = sourceDevices[InputAudioComboBox.SelectedIndex];
AudioGraphSettings settings = LowLatencyCheckbox.IsChecked == true
? new AudioGraphSettings(AudioRenderCategory.Media)
{
QuantumSizeSelectionMode = QuantumSizeSelectionMode.ClosestToDesired,
DesiredSamplesPerQuantum = 10,
PrimaryRenderDevice = outputDevice,
DesiredRenderDeviceAudioProcessing = Windows.Media.AudioProcessing.Raw,
EncodingProperties = new Windows.Media.MediaProperties.AudioEncodingProperties()
{
Subtype = "Float",
SampleRate = 128000, // Manually set to 128 000 samples/second so that the delay is significantly reduced
ChannelCount = 2,
BitsPerSample = 32,
Bitrate = 3072000
}
}
: new AudioGraphSettings(AudioRenderCategory.Media)
{
PrimaryRenderDevice = outputDevice
};
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
AudioGraph ag = result.Graph;
if (result.Status != AudioGraphCreationStatus.Success)
{
return;
}
CreateAudioDeviceInputNodeResult deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice);
if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
// Fail-safe, switch to using the default encoding properties
settings = new AudioGraphSettings(AudioRenderCategory.Media)
{
PrimaryRenderDevice = outputDevice
};
result = await AudioGraph.CreateAsync(settings);
ag = result.Graph;
if (result.Status != AudioGraphCreationStatus.Success)
{
return;
}
deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice);
if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
return;
}
}
// Create the device output node connection
CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await ag.CreateDeviceOutputNodeAsync();
if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
return;
}
deviceInputNodeResult.DeviceInputNode.AddOutgoingConnection(deviceOutputNodeResult.DeviceOutputNode);
// appMediaControls.PlaybackStatus = MediaPlaybackStatus.Playing;
ag.Start();
AudioGraphConnection graphConnection = new AudioGraphConnection(ag, deviceOutputNodeResult.DeviceOutputNode, deviceInputNodeResult.DeviceInputNode, Dispatcher);
App.appRef.audioGraphConnections.Add(graphConnection);
// Hold a reference to the created button; when clicked we must also ensure we remove this visual element from the grid display
Grid renderGrid = graphConnection.GetAsDisplayItem(out Button visualButton);
CreatedGraphs.Children.Add(renderGrid);
// Inline decl to add a second listener to the click event - will remove the grid from the display
void handler(object sender, RoutedEventArgs e)
{
visualButton.Click -= handler;
DestroyVisualElement(ref renderGrid);
}
visualButton.Click += handler;
}
private void DestroyVisualElement(ref Grid element)
{
_ = CreatedGraphs.Children.Remove(element);
element = null;
}
}
}