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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-04 23:10:43 +13:00
|
|
|
|
|
|
|
|
|
// Set output device & add to inputs too
|
2021-09-23 17:30:38 +12:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|