Skip to content

Commit

Permalink
Finish implementing basic device enumeration API
Browse files Browse the repository at this point in the history
  • Loading branch information
FiniteReality committed Sep 13, 2019
1 parent adf6fa9 commit e76cd05
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 70 deletions.
1 change: 0 additions & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<PackageReference Update="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="System.Composition" Version="1.3.0-preview9.19421.4" />
<PackageReference Update="System.IO.Pipelines" Version="4.6.0-preview9.19421.4" />
<PackageReference Update="System.Threading.Channels" Version="4.6.0-preview9.19421.4" />
<PackageReference Update="TerraFX.Interop.PulseAudio" Version="0.1.0-alpha-20190823.1" />
<PackageReference Update="TerraFX.Interop.Vulkan" Version="0.1.0-alpha-20190823.1" />
<PackageReference Update="TerraFX.Interop.Windows" Version="0.1.0-alpha-20190823.1" />
Expand Down
67 changes: 40 additions & 27 deletions samples/TerraFX/Audio/EnumerateAudioAdapters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,27 @@
using System.Threading.Tasks;
using TerraFX.ApplicationModel;
using TerraFX.Audio;
using TerraFX.Provider.PulseAudio.Audio;
using TerraFX.Utilities;

namespace TerraFX.Samples.Audio
{
public sealed class EnumerateAudioAdapters : Sample
{
bool _started = false;
public EnumerateAudioAdapters(string name, params Assembly[] compositionAssemblies)
private readonly bool _async = false;

public EnumerateAudioAdapters(string name, bool @async, params Assembly[] compositionAssemblies)
: base(name, compositionAssemblies)
{
_async = @async;
}

public override async void OnIdle(object sender, ApplicationIdleEventArgs eventArgs)
public override void OnIdle(object? sender, ApplicationIdleEventArgs eventArgs)
{
var application = (Application)sender;

if (_started)
{
return;
}
else
{
_started = true;
}
ExceptionUtilities.ThrowIfNull(sender, nameof(sender));

try
{
await RunAsync(application);
}
catch(Exception e)
{
Console.WriteLine($"Unhandled exception caught: {e.GetType().Name}");
Console.WriteLine(e);
}

application.RequestExit();
var application = (Application)sender;
RunAsync(application).Wait();
}

private async Task RunAsync(Application application)
Expand All @@ -49,24 +35,51 @@ private async Task RunAsync(Application application)
{
await audioProvider.StartAsync();

await foreach (var audioAdapter in audioProvider.EnumerateAudioDevices())
if (_async)
{
Console.WriteLine($"Adapter null: {audioAdapter == null}");
if (audioAdapter != null)
await foreach (var audioAdapter in audioProvider.EnumerateAudioDevices())
{
PrintAudioAdapter(audioAdapter);
}
}
else
{
foreach (var audioAdapter in audioProvider.EnumerateAudioDevices())
{
PrintAudioAdapter(audioAdapter);
}
}

void PrintAudioAdapter(IAudioAdapter audioAdapter)
{
Console.WriteLine($" Name: {audioAdapter.Name}");
Console.WriteLine($" Type: {audioAdapter.DeviceType}");
Console.WriteLine($" Channel count: {audioAdapter.Channels}");
Console.WriteLine($" Sample rate: {audioAdapter.SampleRate}");
Console.WriteLine($" Bit depth: {audioAdapter.BitDepth}");
Console.WriteLine($" Endianness: {(audioAdapter.IsBigEndian ? "Big" : "Little")}");

if (audioAdapter is PulseSourceAdapter source)
{
Console.WriteLine($" Number type: {(source.IsFloatingPoint ? "float" : $"{(source.IsUnsigned ? "unsigned " : "signed ")}integer")}");
Console.WriteLine($" Number of bits per sample: {source.PackedSize}");
Console.WriteLine($" Description: {source.Description}");
}

if (audioAdapter is PulseSinkAdapter sink)
{
Console.WriteLine($" Number type: {(sink.IsFloatingPoint ? "float" : $"{(sink.IsUnsigned ? "unsigned " : "signed ")}integer")}");
Console.WriteLine($" Number of bits per sample: {sink.PackedSize}");
Console.WriteLine($" Description: {sink.Description}");
}
}
}
finally
{
await audioProvider.StopAsync();
}

application.RequestExit();
}
}
}
3 changes: 2 additions & 1 deletion samples/TerraFX/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public static unsafe class Program
new HelloWindow("D3D12.HelloWindow", s_d3d12Provider),
new HelloWindow("Vulkan.HelloWindow", s_vulkanProvider),

new EnumerateAudioAdapters("PulseAudio.EnumerateAudioAdapter", s_pulseAudioProvider)
new EnumerateAudioAdapters("PulseAudio.EnumerateAudioAdapter.Sync", false, s_pulseAudioProvider),
new EnumerateAudioAdapters("PulseAudio.EnumerateAudioAdapter.Async", true, s_pulseAudioProvider),
};

public static void Main(string[] args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using TerraFX.Audio;
using TerraFX.Interop;

namespace TerraFX.Provider.PulseAudio.Audio
{
/// <inheritdoc />
public class PulseAudioAdapterEnumerable : IAudioAdapterEnumerable, IDisposable
public class PulseAudioAdapterEnumerable : IAudioAdapterEnumerable
{
// Only call the internal methods from a single thread (usually the pulse event loop thread)
// _completeSignal may be overwritten improperly causing a deadlock if multiple threads try
// to call internal methods concurrently

private readonly LinkedList<IAudioAdapter> _backingCollection;

private TaskCompletionSource<bool> _completeSignal;

private readonly pa_source_info_cb_t _sourceCallback;
private readonly pa_sink_info_cb_t _sinkCallback;

Expand All @@ -22,48 +29,103 @@ public class PulseAudioAdapterEnumerable : IAudioAdapterEnumerable, IDisposable
internal PulseAudioAdapterEnumerable(pa_source_info_cb_t sourceCallback, pa_sink_info_cb_t sinkCallback)
{
_backingCollection = new LinkedList<IAudioAdapter>();
// Run continuations asynchronously so that we do not block the event loop thread and potentially deadlock
_completeSignal = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_sourceCallback = sourceCallback;
_sinkCallback = sinkCallback;

SourceCallback = Marshal.GetFunctionPointerForDelegate(_sourceCallback);
SinkCallback = Marshal.GetFunctionPointerForDelegate(_sinkCallback);
}

private void SetCompleteSignal(bool value)
{
// No need to use Interlocked.CompareExchange or anything like this
// since we are only called from one thread
_completeSignal.TrySetResult(value);
_completeSignal = new TaskCompletionSource<bool>();
}

internal unsafe void Add(pa_source_info* i)
{
var adapter = new PulseSourceAdapter(i);

_backingCollection.AddLast(adapter);
SetCompleteSignal(false);
}

internal unsafe void Add(pa_sink_info* i)
{
var adapter = new PulseSinkAdapter(i);

_backingCollection.AddLast(adapter);
SetCompleteSignal(false);
}

internal void Complete()
{
// Should always be successful due to being called only from one thread
// Do not overwrite _completeSignal past this point
_completeSignal.TrySetResult(true);
}

/// <inheritdoc />
public async IAsyncEnumerator<IAudioAdapter> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
var current = _backingCollection.First;
var done = false;

while (current?.Next != null || !done)
{
if (current == null)
{
done = await _completeSignal.Task;
current = _backingCollection.First!;
}

yield return current.Value;

if (current.Next == null)
{
done = await _completeSignal.Task;
}

current = current.Next;
}
}

/// <inheritdoc />
public IEnumerator<IAudioAdapter> GetEnumerator()
{
throw new NotSupportedException();
var current = _backingCollection.First;
var done = false;

while (current?.Next != null || !done)
{
if (current == null)
{
var tsk = _completeSignal.Task;
tsk.Wait();
done = tsk.Result;
current = _backingCollection.First!;
}

yield return current.Value;

if (current.Next == null)
{
var tsk = _completeSignal.Task;
tsk.Wait();
done = tsk.Result;
}

current = current.Next;
}
}

IEnumerator IEnumerable.GetEnumerator()
{
throw new NotSupportedException();
}

/// <inheritdoc />
public void Dispose()
{
_completeSource.Dispose();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public class PulseSinkAdapter : IAudioAdapter
{
internal unsafe PulseSinkAdapter(pa_sink_info* i)
{
Name = Marshal.PtrToStringUTF8((IntPtr)i->name);
Description = Marshal.PtrToStringUTF8((IntPtr)i->description);
Name = Marshal.PtrToStringUTF8((IntPtr)i->name)!;
Description = Marshal.PtrToStringUTF8((IntPtr)i->description)!;
SampleRate = (int)i->sample_spec.rate;
Channels = i->sample_spec.channels;

Expand All @@ -37,7 +37,7 @@ internal unsafe PulseSinkAdapter(pa_sink_info* i)
case pa_sample_format.PA_SAMPLE_FLOAT32BE:
IsUnsigned = false;
BitDepth = 32;
PackedSize = 23;
PackedSize = 32;
IsBigEndian = format == pa_sample_format.PA_SAMPLE_FLOAT32BE;
IsFloatingPoint = true;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public class PulseSourceAdapter : IAudioAdapter
{
internal unsafe PulseSourceAdapter(pa_source_info* i)
{
Name = Marshal.PtrToStringUTF8((IntPtr)i->name);
Description = Marshal.PtrToStringUTF8((IntPtr)i->description);
Name = Marshal.PtrToStringUTF8((IntPtr)i->name)!;
Description = Marshal.PtrToStringUTF8((IntPtr)i->description)!;
SampleRate = (int)i->sample_spec.rate;
Channels = i->sample_spec.channels;

Expand All @@ -37,7 +37,7 @@ internal unsafe PulseSourceAdapter(pa_source_info* i)
case pa_sample_format.PA_SAMPLE_FLOAT32BE:
IsUnsigned = false;
BitDepth = 32;
PackedSize = 23;
PackedSize = 32;
IsBigEndian = format == pa_sample_format.PA_SAMPLE_FLOAT32BE;
IsFloatingPoint = true;
break;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

<ItemGroup>
<PackageReference Include="TerraFX.Interop.PulseAudio" />
<PackageReference Include="System.Threading.Channels" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit e76cd05

Please sign in to comment.