Skip to content

Commit 8ad439e

Browse files
committed
Add Null audio backend
1 parent 82b80fe commit 8ad439e

File tree

9 files changed

+159
-57
lines changed

9 files changed

+159
-57
lines changed

src/NitroSharp.Launcher/ConfigurationReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ private static void Set(Configuration configuration, KeyValuePair<string, JsonVa
135135
{
136136
"XAUDIO" or "XAUDIO2" => AudioBackend.XAudio2,
137137
"OPENAL" or "OPENALSOFT" or "OPENAL SOFT" => AudioBackend.OpenAL,
138+
"NULL" or "Null" => AudioBackend.Null,
138139
_ => null,
139140
};
140141
}

src/NitroSharp/GameContext.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,12 @@ private static (Logger, LogEventRecorder) SetupLogging()
223223
private static AudioContext InitAudio(Configuration configuration)
224224
{
225225
var audioParameters = AudioParameters.Default;
226-
AudioBackend backend = configuration.PreferredAudioBackend
227-
?? AudioDevice.GetPlatformDefaultBackend();
226+
AudioBackend backend = AudioDevice.GetPlatformDefaultBackend();
227+
if (configuration.PreferredAudioBackend is AudioBackend preferredBackend
228+
&& AudioDevice.IsBackendAvailable(preferredBackend))
229+
{
230+
backend = preferredBackend;
231+
}
228232
var audioDevice = AudioDevice.Create(backend, audioParameters);
229233
return new AudioContext(audioDevice);
230234
}
@@ -269,7 +273,7 @@ private static ContentManager CreateContentManager(
269273
Configuration configuration)
270274
{
271275
TextureLoader textureLoader;
272-
if (OperatingSystem.IsWindows())
276+
if (OperatingSystem.IsWindows() && false)
273277
{
274278
textureLoader = new WicTextureLoader(device);
275279
}

src/NitroSharp/Media/AudioContext.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ namespace NitroSharp.Media
99
internal readonly struct PooledAudioSource : IDisposable
1010
{
1111
private readonly AudioContext _pool;
12-
public readonly XAudio2AudioSource Value;
12+
public readonly AudioSource Value;
1313

14-
public PooledAudioSource(AudioContext pool, XAudio2AudioSource audioSource)
14+
public PooledAudioSource(AudioContext pool, AudioSource audioSource)
1515
{
1616
_pool = pool;
1717
Value = audioSource;
@@ -25,12 +25,12 @@ public void Dispose()
2525

2626
internal sealed class AudioContext : IAsyncDisposable
2727
{
28-
private readonly ConcurrentQueue<XAudio2AudioSource> _freeSources;
28+
private readonly ConcurrentQueue<AudioSource> _freeSources;
2929

3030
public AudioContext(AudioDevice device, uint initialSize = 1)
3131
{
3232
Device = device;
33-
_freeSources = new ConcurrentQueue<XAudio2AudioSource>();
33+
_freeSources = new ConcurrentQueue<AudioSource>();
3434

3535
VoiceAudioSource = Device.CreateAudioSource( bufferSize: 4400, bufferCount: 64);
3636
for (int i = 0; i < initialSize; i++)
@@ -40,17 +40,17 @@ public AudioContext(AudioDevice device, uint initialSize = 1)
4040
}
4141

4242
public AudioDevice Device { get; }
43-
public XAudio2AudioSource VoiceAudioSource { get; }
43+
public AudioSource VoiceAudioSource { get; }
4444

4545
public PooledAudioSource RentAudioSource()
4646
{
47-
XAudio2AudioSource audioSource = _freeSources.TryDequeue(out XAudio2AudioSource? pooled)
47+
AudioSource audioSource = _freeSources.TryDequeue(out AudioSource? pooled)
4848
? pooled
4949
: Device.CreateAudioSource();
5050
return new PooledAudioSource(this, audioSource);
5151
}
5252

53-
public void ReturnAudioSource(XAudio2AudioSource audioSource)
53+
public void ReturnAudioSource(AudioSource audioSource)
5454
{
5555
_freeSources.Enqueue(audioSource);
5656
}

src/NitroSharp/Media/AudioDevice.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Runtime.InteropServices;
33
using System.Threading.Tasks;
4+
using NitroSharp.Media.NullAudio;
45
using NitroSharp.Media.XAudio2;
56

67
namespace NitroSharp.Media
@@ -16,43 +17,44 @@ protected AudioDevice(in AudioParameters audioParameters)
1617

1718
public AudioParameters AudioParameters { get; }
1819

19-
public abstract XAudio2AudioSource CreateAudioSource(
20+
public abstract AudioSource CreateAudioSource(
2021
int bufferSize = 16 * 1024,
2122
int bufferCount = 16
2223
);
2324

2425
public abstract ValueTask DisposeAsync();
2526

26-
public static AudioDevice CreatePlatformDefault(in AudioParameters audioParameters)
27-
{
28-
return Create(GetPlatformDefaultBackend(), audioParameters);
29-
}
30-
3127
public static AudioBackend GetPlatformDefaultBackend()
3228
{
33-
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
29+
return OperatingSystem.IsWindows()
3430
? AudioBackend.XAudio2
35-
: AudioBackend.OpenAL;
31+
: AudioBackend.Null;
3632
}
3733

3834
public static bool IsBackendAvailable(AudioBackend backend)
3935
{
4036
if (backend == AudioBackend.XAudio2)
4137
{
42-
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
38+
return OperatingSystem.IsWindows();
4339
}
4440

4541
return true;
4642
}
4743

4844
public static AudioDevice Create(AudioBackend backend, in AudioParameters audioParameters)
4945
{
50-
return new XAudio2AudioDevice(audioParameters);
46+
return backend switch
47+
{
48+
AudioBackend.Null => new NullAudioDevice(audioParameters),
49+
AudioBackend.XAudio2 => new XAudio2AudioDevice(audioParameters),
50+
_ => throw new NotImplementedException($"Backend '{backend}' is not implemented")
51+
};
5152
}
5253
}
5354

5455
public enum AudioBackend
5556
{
57+
Null,
5658
XAudio2,
5759
OpenAL
5860
}
Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
11
using System;
2-
using System.Collections.Concurrent;
2+
using System.IO.Pipelines;
3+
using System.Threading.Tasks;
34

45
namespace NitroSharp.Media
56
{
6-
public abstract class AudioSource : IDisposable
7+
internal abstract class AudioSource : IAsyncDisposable
78
{
8-
public const uint MaxQueuedBuffers = 16;
9-
10-
protected ConcurrentQueue<IntPtr> _processedBufferPointers = new();
11-
12-
public abstract IntPtr CurrentBuffer { get; }
13-
public abstract uint BuffersQueued { get; }
14-
public abstract uint TotalBuffersReferenced { get; }
15-
public abstract double PositionInCurrentBuffer { get; }
16-
9+
public abstract bool IsPlaying { get; }
10+
public abstract double SecondsElapsed { get; }
1711
public abstract float Volume { get; set; }
1812

19-
public abstract bool TrySubmitBuffer(IntPtr data, uint size);
20-
21-
public virtual bool TryDequeueProcessedBuffer(out IntPtr pointer)
22-
{
23-
return _processedBufferPointers.TryDequeue(out pointer);
24-
}
25-
26-
public abstract void FlushBuffers();
27-
public abstract void Play();
13+
public abstract void Play(PipeReader audioData);
14+
public abstract void Pause();
15+
public abstract void Resume();
2816
public abstract void Stop();
29-
public abstract void Dispose();
17+
public abstract void FlushBuffers();
18+
public abstract ReadOnlySpan<short> GetCurrentBuffer();
19+
20+
public abstract ValueTask DisposeAsync();
3021
}
3122
}

src/NitroSharp/Media/MediaStream.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ internal sealed class MediaStream : IDisposable
4646
private readonly StreamContext? _audio;
4747
private readonly StreamContext? _video;
4848
private readonly Pipe? _audioPipe;
49-
private readonly XAudio2AudioSource? _audioSource;
49+
private readonly AudioSource? _audioSource;
5050
private readonly AudioParameters _outAudioParams;
5151
private readonly YCbCrBuffer? _videoBuffer;
5252
private readonly Size? _videoResolution;
@@ -239,7 +239,7 @@ public unsafe void Dispose()
239239
public unsafe MediaStream(
240240
Stream stream,
241241
GraphicsDevice? graphicsDevice,
242-
XAudio2AudioSource? audioSource,
242+
AudioSource? audioSource,
243243
AudioParameters outAudioParams)
244244
{
245245
_fileStream = stream;
@@ -324,7 +324,7 @@ public unsafe MediaStream(
324324

325325
public TimeSpan Duration { get; }
326326

327-
public XAudio2AudioSource AudioSource => _audioSource!;
327+
public AudioSource AudioSource => _audioSource!;
328328

329329
public Size VideoResolution
330330
{
@@ -371,7 +371,7 @@ public void Start()
371371
_unpauseSignal.Set();
372372
var tasks = new List<Task>(5);
373373
tasks.Add(Task.Run(Read));
374-
if (_audio is { } && _audioSource is XAudio2AudioSource audioSource)
374+
if (_audio is { } && _audioSource is AudioSource audioSource)
375375
{
376376
Debug.Assert(_audioPipe is not null);
377377
tasks.Add(Task.Run(() => Decode(_audio)));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Threading.Tasks;
2+
3+
namespace NitroSharp.Media.NullAudio
4+
{
5+
internal sealed class NullAudioDevice : AudioDevice
6+
{
7+
public NullAudioDevice(in AudioParameters audioParameters)
8+
: base(in audioParameters)
9+
{
10+
}
11+
12+
public override AudioSource CreateAudioSource(int bufferSize = 16384, int bufferCount = 16)
13+
{
14+
return new NullAudioSource();
15+
}
16+
17+
public override ValueTask DisposeAsync() => default;
18+
}
19+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO.Pipelines;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace NitroSharp.Media.NullAudio
8+
{
9+
internal sealed class NullAudioSource : AudioSource
10+
{
11+
private PipeReader? _audioData;
12+
private CancellationTokenSource? _cts;
13+
private Task? _consumeTask;
14+
15+
public override bool IsPlaying => false;
16+
public override double SecondsElapsed => 0;
17+
public override float Volume { get; set; }
18+
public override ReadOnlySpan<short> GetCurrentBuffer() => default;
19+
20+
public override void Play(PipeReader audioData)
21+
{
22+
_ = PlayAsync(audioData);
23+
}
24+
25+
public override void Pause()
26+
{
27+
}
28+
29+
public override void Resume()
30+
{
31+
}
32+
33+
public override void Stop()
34+
{
35+
_ = StopAsync();
36+
}
37+
38+
public override void FlushBuffers()
39+
{
40+
}
41+
42+
private async Task PlayAsync(PipeReader audioData)
43+
{
44+
await StopAsync();
45+
_audioData = audioData;
46+
_cts = new CancellationTokenSource();
47+
_consumeTask = Task.Run(() => ConsumeLoop(_audioData));
48+
}
49+
50+
private async Task ConsumeLoop(PipeReader audioData)
51+
{
52+
Debug.Assert(_cts is not null);
53+
while (!_cts.IsCancellationRequested)
54+
{
55+
while (!_cts.IsCancellationRequested)
56+
{
57+
ReadResult readResult = await audioData.ReadAsync();
58+
audioData.AdvanceTo(readResult.Buffer.End);
59+
}
60+
}
61+
}
62+
63+
private async Task StopAsync()
64+
{
65+
if (_audioData is not null && _cts is not null)
66+
{
67+
_cts.Cancel();
68+
_audioData = null;
69+
if (_consumeTask is not null)
70+
{
71+
await _consumeTask;
72+
_consumeTask = null;
73+
}
74+
}
75+
}
76+
77+
public override async ValueTask DisposeAsync()
78+
{
79+
if (_consumeTask is not null)
80+
{
81+
await StopAsync();
82+
}
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)