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 { /// /// An empty page that can be used on its own or navigated to within a Frame. /// 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(); } /// /// Clear existing elements from this display, to free memory /// /// /// 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; } } /// /// When the page is navigated to, populate the list /// /// 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); } } // Set output device & add to inputs too 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; } } }