Fixed a memory leak in the WPF project

This commit is contained in:
Brychan Dempsey 2021-10-04 23:10:43 +13:00
parent 09a2398967
commit 8d81ff91aa
4 changed files with 67 additions and 18 deletions

View File

@ -147,6 +147,8 @@ namespace Audio_Router
InputAudioComboBox.Items.Add(sourceDevices[i].Name); InputAudioComboBox.Items.Add(sourceDevices[i].Name);
} }
} }
// Set output device & add to inputs too
for (int i = 0; i < targetDevices.Count; i++) for (int i = 0; i < targetDevices.Count; i++)
{ {
if(targetDevices[i].Id == defaultOutput.Id) if(targetDevices[i].Id == defaultOutput.Id)
@ -211,7 +213,6 @@ namespace Audio_Router
}; };
result = await AudioGraph.CreateAsync(settings); result = await AudioGraph.CreateAsync(settings);
ag = result.Graph; ag = result.Graph;
if (result.Status != AudioGraphCreationStatus.Success) if (result.Status != AudioGraphCreationStatus.Success)
{ {
return; return;

View File

@ -25,10 +25,11 @@ namespace Audio_Router_WPF
Dispatcher uiDispatcher { get; set; } Dispatcher uiDispatcher { get; set; }
TextBlock latencyLabel { get; set; } TextBlock latencyLabel { get; set; } = new TextBlock();
int quantaCount = 0; int quantaCount;
#pragma warning disable CS8618 // Cannot convert null literal to non-nullable reference type.
public AudioGraphConnection(AudioGraph graph, AudioDeviceOutputNode targetDevice, AudioDeviceInputNode sourceDevice, Dispatcher uiDispatcher) public AudioGraphConnection(AudioGraph graph, AudioDeviceOutputNode targetDevice, AudioDeviceInputNode sourceDevice, Dispatcher uiDispatcher)
{ {
Graph = graph; Graph = graph;
@ -128,23 +129,43 @@ namespace Audio_Router_WPF
private void Graph_QuantumProcessed(AudioGraph sender, object args) private void Graph_QuantumProcessed(AudioGraph sender, object args)
{ {
// updates aren't urgent, only process after some time (this is 1s, with the Windows default of 10 ms / quanta // updates aren't urgent, only process after some time (this is 1s, with the Windows default of 10 ms / quanta
if (quantaCount++ == 100) if (quantaCount % 100 == 0)
{ {
string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms";
if (latencyLabel is null) return; if (latencyLabel is null) return;
string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms";
uiDispatcher.Invoke(() => uiDispatcher.Invoke(() =>
{ {
try try
{ {
// Try to set the label, otherwise it may have already been destroyed
latencyLabel.Text = result; latencyLabel.Text = result;
} }
catch catch (NullReferenceException)
{ {
} }
}, DispatcherPriority.Background); }, DispatcherPriority.Background);
quantaCount = 0; result = null;
} }
/// ----------------------
/// || About this: ||
/// ----------------------
/// Running AudioGraph in WPF causes what seems to be the captured audio data to
/// never go out of scope (this does not happen in UWP, as shown in the near-identical code from the
/// UWP project), so the memory allocations for the project keep increasing.
/// Stopping the AudioGraph causes these allocations to finalise, so frees up the space.
/// we can call ResetAllNodes() periodically (i.e.: every 10s (1000 * 10 ms)) to reset the
/// nodes
///
if (quantaCount == 1000)
{
//SourceDevice.Reset();
Graph.ResetAllNodes();
// GC.Collect(); // We can let the GC handle itself
}
quantaCount++;
quantaCount %= 1001;
} }
private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) private void VolumeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
@ -153,10 +174,12 @@ namespace Audio_Router_WPF
SourceDevice.OutgoingGain = e.NewValue; SourceDevice.OutgoingGain = e.NewValue;
} }
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private void DestroyButton_Click(object sender, RoutedEventArgs e) private void DestroyButton_Click(object sender, RoutedEventArgs e)
{ {
latencyLabel = null;
Graph.QuantumProcessed -= Graph_QuantumProcessed; Graph.QuantumProcessed -= Graph_QuantumProcessed;
Graph.ResetAllNodes();
latencyLabel = null;
Graph.Stop(); Graph.Stop();
TargetDevice.Dispose(); TargetDevice.Dispose();
SourceDevice.Dispose(); SourceDevice.Dispose();
@ -172,6 +195,7 @@ namespace Audio_Router_WPF
public void OnHide() public void OnHide()
{ {
latencyLabel = null; latencyLabel = null;
Graph.QuantumProcessed -= Graph_QuantumProcessed;
} }
} }
} }

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Audio_Router_WPF" xmlns:local="clr-namespace:Audio_Router_WPF"
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"> Title="Audio Router WPF" Height="450" Width="800">
<Grid VerticalAlignment="Stretch"> <Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@ -18,10 +18,10 @@
<TextBlock Text="Input Audio Device:" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="White" HorizontalAlignment="Center"/> <TextBlock Text="Input Audio Device:" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="White" HorizontalAlignment="Center"/>
<ComboBox x:Name="InputAudioComboBox" MinWidth="500" HorizontalAlignment="Center"/> <ComboBox x:Name="InputAudioComboBox" MinWidth="500" HorizontalAlignment="Center"/>
<TextBlock Text="Output Audio Device:" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="White" HorizontalAlignment="Center"/> <TextBlock Text="Output Audio Device:" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="White" HorizontalAlignment="Center"/>
<ComboBox x:Name="OutputAudioComboBox" MinWidth="500" HorizontalAlignment="Center"/> <ComboBox x:Name="OutputAudioComboBox" MinWidth="500" HorizontalAlignment="Center" Background="Black" BorderBrush="Black"/>
<CheckBox x:Name="LowLatencyCheckbox" Content="Low Latency Mode (Reduces audio quality)" HorizontalAlignment="Center" Margin="0,10,0,0"/> <CheckBox x:Name="LowLatencyCheckbox" Content="Low Latency Mode (Reduces audio quality)" HorizontalAlignment="Center" Margin="0,10,0,0" Foreground="White"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,5"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,5">
<Button x:Name="AddAudioGraphButton" Content="Add Audio Graph" HorizontalContentAlignment="Center" Background="#54FFFFFF" Click="AddAudioGraphButton_Click"/> <Button x:Name="AddAudioGraphButton" Content="Add Audio Graph" HorizontalContentAlignment="Center" Background="#54FFFFFF" Click="AddAudioGraphButton_Click" Foreground="White"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1" Margin="10,5,10,5" Padding="0,0,10,0"> <ScrollViewer Grid.Row="1" Margin="10,5,10,5" Padding="0,0,10,0">

View File

@ -31,12 +31,18 @@ namespace Audio_Router_WPF
// The list of audio devices // The list of audio devices
DeviceInformationCollection sourceDevices; DeviceInformationCollection sourceDevices;
DeviceInformationCollection targetDevices; DeviceInformationCollection targetDevices;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. (these fields cannot be assigned
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
_ = Task.Run(() => FindAudioSources()); _ = Task.Run(() => FindAudioSources());
} }
private async void AddAudioGraphButton_Click(object sender, RoutedEventArgs e) private async void AddAudioGraphButton_Click(object sender, RoutedEventArgs e)
{ {
await CreateAudioGraph(); await CreateAudioGraph();
@ -57,6 +63,7 @@ namespace Audio_Router_WPF
DeviceInformation defaultOutput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default)); DeviceInformation defaultOutput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default));
DeviceInformation defaultInput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Default)); DeviceInformation defaultInput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Default));
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
// Set the Input device // Set the Input device
@ -72,16 +79,19 @@ namespace Audio_Router_WPF
_ = InputAudioComboBox.Items.Add(sourceDevices[i].Name); _ = InputAudioComboBox.Items.Add(sourceDevices[i].Name);
} }
} }
// _ = InputAudioComboBox.Items.Add("--Output nodes--"); // This requires further work, implementing W
for (int i = 0; i < targetDevices.Count; i++) for (int i = 0; i < targetDevices.Count; i++)
{ {
if (targetDevices[i].Id == defaultOutput.Id) if (targetDevices[i].Id == defaultOutput.Id)
{ {
_ = OutputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)"); _ = OutputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
//_ = InputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
OutputAudioComboBox.SelectedIndex = i; OutputAudioComboBox.SelectedIndex = i;
} }
else else
{ {
_ = OutputAudioComboBox.Items.Add(targetDevices[i].Name); _ = OutputAudioComboBox.Items.Add(targetDevices[i].Name);
//_ = InputAudioComboBox.Items.Add(targetDevices[i].Name);
} }
} }
}); });
@ -91,8 +101,21 @@ namespace Audio_Router_WPF
private async Task CreateAudioGraph() private async Task CreateAudioGraph()
{ {
App.displayRequest.RequestActive(); App.displayRequest.RequestActive();
bool isInputDevice = true;
DeviceInformation inputDevice;
if (InputAudioComboBox.SelectedIndex < sourceDevices.Count)
{
isInputDevice = true;
inputDevice = sourceDevices[InputAudioComboBox.SelectedIndex];
}
else if (InputAudioComboBox.SelectedIndex == sourceDevices.Count) return;
else
{
isInputDevice = false;
inputDevice = targetDevices[InputAudioComboBox.SelectedIndex - (sourceDevices.Count + 1)];
}
DeviceInformation outputDevice = targetDevices[OutputAudioComboBox.SelectedIndex]; DeviceInformation outputDevice = targetDevices[OutputAudioComboBox.SelectedIndex];
DeviceInformation inputDevice = sourceDevices[InputAudioComboBox.SelectedIndex];
AudioGraphSettings settings = LowLatencyCheckbox.IsChecked == true AudioGraphSettings settings = LowLatencyCheckbox.IsChecked == true
? new AudioGraphSettings(AudioRenderCategory.Media) ? new AudioGraphSettings(AudioRenderCategory.Media)
{ {
@ -121,8 +144,9 @@ namespace Audio_Router_WPF
return; return;
} }
CreateAudioDeviceInputNodeResult deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice); //CreateAudioDeviceInputNodeResult deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(MediaCategory.Other, ag.EncodingProperties, inputDevice);
CreateAudioDeviceInputNodeResult deviceInputNodeResult = await ag.CreateDeviceInputNodeAsync(,,);
AudioFrameInputNode g = ag.CreateFrameInputNode();
if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success) if (deviceInputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{ {
// Fail-safe, switch to using the default encoding properties // Fail-safe, switch to using the default encoding properties
@ -143,9 +167,9 @@ namespace Audio_Router_WPF
return; return;
} }
} }
// Create the device output node connection // Create the device output node connection
CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await ag.CreateDeviceOutputNodeAsync(); CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await ag.CreateDeviceOutputNodeAsync();
//deviceOutputNodeResult.DeviceOutputNode.Creat
if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success) if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{ {
return; return;
@ -155,7 +179,6 @@ namespace Audio_Router_WPF
ag.Start(); ag.Start();
AudioGraphConnection graphConnection = new AudioGraphConnection(ag, deviceOutputNodeResult.DeviceOutputNode, deviceInputNodeResult.DeviceInputNode, Dispatcher); AudioGraphConnection graphConnection = new AudioGraphConnection(ag, deviceOutputNodeResult.DeviceOutputNode, deviceInputNodeResult.DeviceInputNode, Dispatcher);
App.appRef.audioGraphConnections.Add(graphConnection); 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 // 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); Grid renderGrid = graphConnection.GetAsDisplayItem(out Button visualButton);
CreatedGraphs.Children.Add(renderGrid); CreatedGraphs.Children.Add(renderGrid);
@ -165,6 +188,7 @@ namespace Audio_Router_WPF
visualButton.Click -= handler; visualButton.Click -= handler;
DestroyVisualElement(ref renderGrid); DestroyVisualElement(ref renderGrid);
} }
visualButton.Click += handler; visualButton.Click += handler;
} }