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

View File

@ -25,10 +25,11 @@ namespace Audio_Router_WPF
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)
{
Graph = graph;
@ -128,23 +129,43 @@ namespace Audio_Router_WPF
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
if (quantaCount++ == 100)
if (quantaCount % 100 == 0)
{
string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms";
if (latencyLabel is null) return;
string result = (Graph.LatencyInSamples / (double)Graph.SamplesPerQuantum * SamplesPerMS).ToString("0.#") + " ms";
uiDispatcher.Invoke(() =>
{
try
{
// Try to set the label, otherwise it may have already been destroyed
latencyLabel.Text = result;
}
catch
catch (NullReferenceException)
{
}
}, 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)
@ -153,10 +174,12 @@ namespace Audio_Router_WPF
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)
{
latencyLabel = null;
Graph.QuantumProcessed -= Graph_QuantumProcessed;
Graph.ResetAllNodes();
latencyLabel = null;
Graph.Stop();
TargetDevice.Dispose();
SourceDevice.Dispose();
@ -172,6 +195,7 @@ namespace Audio_Router_WPF
public void OnHide()
{
latencyLabel = null;
Graph.QuantumProcessed -= Graph_QuantumProcessed;
}
}
}

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Audio_Router_WPF"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
Title="Audio Router WPF" Height="450" Width="800">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
@ -18,10 +18,10 @@
<TextBlock Text="Input Audio Device:" TextWrapping="Wrap" Margin="0,10,0,0" Foreground="White" 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"/>
<ComboBox x:Name="OutputAudioComboBox" MinWidth="500" HorizontalAlignment="Center"/>
<CheckBox x:Name="LowLatencyCheckbox" Content="Low Latency Mode (Reduces audio quality)" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<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" Foreground="White"/>
<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>
<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
DeviceInformationCollection sourceDevices;
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()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
_ = Task.Run(() => FindAudioSources());
}
private async void AddAudioGraphButton_Click(object sender, RoutedEventArgs e)
{
await CreateAudioGraph();
@ -57,6 +63,7 @@ namespace Audio_Router_WPF
DeviceInformation defaultOutput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioRenderId(AudioDeviceRole.Default));
DeviceInformation defaultInput = await DeviceInformation.CreateFromIdAsync(MediaDevice.GetDefaultAudioCaptureId(AudioDeviceRole.Default));
Dispatcher.Invoke(() =>
{
// Set the Input device
@ -72,16 +79,19 @@ namespace Audio_Router_WPF
_ = 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++)
{
if (targetDevices[i].Id == defaultOutput.Id)
{
_ = OutputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
//_ = InputAudioComboBox.Items.Add(targetDevices[i].Name + " (Default Device)");
OutputAudioComboBox.SelectedIndex = i;
}
else
{
_ = OutputAudioComboBox.Items.Add(targetDevices[i].Name);
//_ = InputAudioComboBox.Items.Add(targetDevices[i].Name);
}
}
});
@ -91,8 +101,21 @@ namespace Audio_Router_WPF
private async Task CreateAudioGraph()
{
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 inputDevice = sourceDevices[InputAudioComboBox.SelectedIndex];
AudioGraphSettings settings = LowLatencyCheckbox.IsChecked == true
? new AudioGraphSettings(AudioRenderCategory.Media)
{
@ -121,8 +144,9 @@ namespace Audio_Router_WPF
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)
{
// Fail-safe, switch to using the default encoding properties
@ -143,9 +167,9 @@ namespace Audio_Router_WPF
return;
}
}
// Create the device output node connection
CreateAudioDeviceOutputNodeResult deviceOutputNodeResult = await ag.CreateDeviceOutputNodeAsync();
//deviceOutputNodeResult.DeviceOutputNode.Creat
if (deviceOutputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
{
return;
@ -155,7 +179,6 @@ namespace Audio_Router_WPF
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);
@ -165,6 +188,7 @@ namespace Audio_Router_WPF
visualButton.Click -= handler;
DestroyVisualElement(ref renderGrid);
}
visualButton.Click += handler;
}