-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * asddasd * WIP * WIp * WIP --------- Co-authored-by: xBaank <[email protected]>
- Loading branch information
Showing
15 changed files
with
174 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,116 @@ | ||
using System.Threading.Channels; | ||
using OpenTK.Audio.OpenAL; | ||
using PortAudioSharp; | ||
|
||
namespace Console.Audio; | ||
|
||
internal class AudioSender(int sourceId, ALFormat targetFormat) : IAsyncDisposable | ||
internal class AudioSender(float volume, PlayState initialState) : IAsyncDisposable | ||
{ | ||
private readonly Channel<PcmPacket> _queue = Channel.CreateBounded<PcmPacket>(150); | ||
private readonly Channel<PcmPacket<short>> _queue = Channel.CreateBounded<PcmPacket<short>>( | ||
500 | ||
); | ||
public readonly int SampleRate = 48000; | ||
public readonly int Channels = 2; | ||
private readonly int[] _buffers = AL.GenBuffers(50); | ||
private bool _clearBuffer = false; | ||
public PlayState State { get; set; } = initialState; | ||
public float Volume { get; set; } = volume; | ||
public TimeSpan CurrentTime { get; private set; } = default; | ||
public TaskCompletionSource WaitForEmptyBuffer { get; set; } = new(); | ||
private PortAudioSharp.Stream? _stream; | ||
|
||
public void ClearBuffer() | ||
{ | ||
_clearBuffer = true; | ||
while (_queue.Reader.TryRead(out var next)) | ||
{ | ||
next.Dispose(); | ||
} | ||
} | ||
|
||
public async ValueTask Add(PcmPacket data) => await _queue.Writer.WriteAsync(data); | ||
public async ValueTask Add(PcmPacket<short> data) => await _queue.Writer.WriteAsync(data); | ||
|
||
private async ValueTask ClearBufferAL(CancellationToken token) | ||
public unsafe void StartSending(CancellationToken token = default) | ||
{ | ||
AL.GetSource(sourceId, ALGetSourcei.SourceState, out int initialState); | ||
PortAudio.Initialize(); | ||
|
||
// Define a callback delegate for audio processing | ||
StreamCallbackResult callback( | ||
IntPtr input, | ||
IntPtr output, | ||
UInt32 frameCount, | ||
ref StreamCallbackTimeInfo timeInfo, | ||
StreamCallbackFlags statusFlags, | ||
IntPtr userData | ||
) | ||
{ | ||
var sizeInBytes = (int)frameCount * 2; | ||
|
||
AL.SourceStop(sourceId); | ||
AL.GetSource(sourceId, ALGetSourcei.BuffersQueued, out int queuedCount); | ||
if (token.IsCancellationRequested) | ||
return StreamCallbackResult.Abort; | ||
|
||
if (queuedCount > 0) | ||
{ | ||
int[] bufferIds = new int[queuedCount]; | ||
AL.SourceUnqueueBuffers(sourceId, queuedCount, bufferIds); | ||
foreach (var buffer in bufferIds) | ||
if (State == PlayState.Paused) | ||
{ | ||
using var next = await _queue.Reader.ReadAsync(token); | ||
AL.BufferData(buffer, targetFormat, next.Data, SampleRate); | ||
AL.SourceQueueBuffer(sourceId, buffer); | ||
var spanUnmanagedBuffer = new Span<short>(output.ToPointer(), sizeInBytes); | ||
spanUnmanagedBuffer.Clear(); | ||
return StreamCallbackResult.Continue; | ||
} | ||
} | ||
|
||
_clearBuffer = false; | ||
|
||
if ((ALSourceState)initialState == ALSourceState.Playing) | ||
{ | ||
AL.SourcePlay(sourceId); | ||
} | ||
if (State == PlayState.Stopped) | ||
return StreamCallbackResult.Abort; | ||
|
||
if ((ALSourceState)initialState == ALSourceState.Paused) | ||
{ | ||
AL.SourcePlay(sourceId); | ||
AL.SourcePause(sourceId); | ||
} | ||
} | ||
if (_queue.Reader.TryRead(out var nextBuffer)) | ||
{ | ||
using var buffer = nextBuffer; | ||
var spanUnmanagedBuffer = new Span<short>(output.ToPointer(), sizeInBytes); | ||
var source = buffer.Data[..sizeInBytes]; | ||
|
||
public async Task StartSending(CancellationToken token = default) | ||
{ | ||
var fillBuffers = await _queue | ||
.Reader.ReadAllAsync(token) | ||
.Take(10) | ||
.ToListAsync(cancellationToken: token); | ||
for (var i = 0; i < source.Length; i++) | ||
{ | ||
source[i] = (short)(source[i] * Volume); | ||
} | ||
|
||
for (int i = 0; i < fillBuffers.Count; i++) | ||
{ | ||
using var item = fillBuffers[i]; | ||
AL.BufferData(_buffers[i], targetFormat, item.Data, SampleRate); | ||
AL.SourceQueueBuffer(sourceId, _buffers[i]); | ||
source.CopyTo(spanUnmanagedBuffer); | ||
CurrentTime = buffer.Time; | ||
return StreamCallbackResult.Continue; | ||
} | ||
else | ||
{ | ||
WaitForEmptyBuffer.TrySetResult(); | ||
var spanUnmanagedBuffer = new Span<short>(output.ToPointer(), sizeInBytes); | ||
spanUnmanagedBuffer.Clear(); | ||
return StreamCallbackResult.Continue; | ||
} | ||
} | ||
|
||
var _ = Task.Run( | ||
async () => | ||
{ | ||
try | ||
{ | ||
while (!token.IsCancellationRequested) | ||
{ | ||
if (_clearBuffer) | ||
{ | ||
await ClearBufferAL(token); | ||
continue; | ||
} | ||
|
||
AL.GetSource( | ||
sourceId, | ||
ALGetSourcei.BuffersProcessed, | ||
out int releasedCount | ||
); | ||
|
||
if (releasedCount > 0) | ||
{ | ||
int[] bufferIds = new int[releasedCount]; | ||
AL.SourceUnqueueBuffers(sourceId, releasedCount, bufferIds); | ||
foreach (var buffer in bufferIds) | ||
{ | ||
using var next = await _queue.Reader.ReadAsync(token); | ||
AL.BufferData(buffer, targetFormat, next.Data, SampleRate); | ||
AL.SourceQueueBuffer(sourceId, buffer); | ||
} | ||
} | ||
|
||
AL.GetSource(sourceId, ALGetSourcei.SourceState, out int stateInt); | ||
|
||
if ((ALSourceState)stateInt == ALSourceState.Stopped) | ||
{ | ||
AL.SourcePlay(sourceId); | ||
} | ||
|
||
await Task.Delay(100); | ||
} | ||
} | ||
finally | ||
{ | ||
await ClearBufferAL(token); | ||
} | ||
}, | ||
token | ||
StreamParameters param = new(); | ||
var deviceIndex = PortAudio.DefaultOutputDevice; | ||
var info = PortAudio.GetDeviceInfo(deviceIndex); | ||
param.device = PortAudio.DefaultOutputDevice; | ||
param.channelCount = Channels; | ||
param.sampleFormat = SampleFormat.Int16; | ||
param.suggestedLatency = info.defaultLowOutputLatency; | ||
param.hostApiSpecificStreamInfo = IntPtr.Zero; | ||
|
||
_stream = new PortAudioSharp.Stream( | ||
inParams: null, | ||
outParams: param, | ||
streamFlags: StreamFlags.ClipOff, | ||
sampleRate: SampleRate, | ||
framesPerBuffer: 960, //TODO This should not be hardcoded maybe? | ||
callback: callback, | ||
userData: IntPtr.Zero | ||
); | ||
|
||
AL.SourcePlay(sourceId); | ||
_stream.Start(); | ||
} | ||
|
||
public ValueTask DisposeAsync() | ||
{ | ||
ClearBuffer(); | ||
AL.SourceStop(sourceId); | ||
AL.GetSource(sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); | ||
int[] bufferIds = new int[releasedCount]; | ||
AL.SourceUnqueueBuffers(sourceId, releasedCount, bufferIds); | ||
AL.DeleteBuffers(bufferIds); | ||
State = PlayState.Stopped; | ||
try | ||
{ | ||
_stream?.Stop(); | ||
_stream?.Dispose(); | ||
} | ||
catch { } | ||
return ValueTask.CompletedTask; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Console.Audio; | ||
|
||
public enum PlayState | ||
{ | ||
Playing, | ||
Paused, | ||
Stopped | ||
} |
Oops, something went wrong.