BotSharp/tests/BotSharp.Test.RealtimeVoice/MicrophoneAudioStream.cs
2025-04-14 01:25:16 -05:00

119 lines
3.7 KiB
C#

using NAudio.Wave;
namespace BotSharp.Test.RealtimeVoice;
public class MicrophoneAudioStream : Stream, IDisposable
{
private const int SAMPLES_PER_SECOND = 24000;
private const int BYTES_PER_SAMPLE = 2;
private const int CHANNELS = 1;
// For simplicity, this is configured to use a static 10-second ring buffer.
private readonly byte[] _buffer = new byte[BYTES_PER_SAMPLE * SAMPLES_PER_SECOND * CHANNELS * 10];
private readonly object _bufferLock = new();
private int _bufferReadPos = 0;
private int _bufferWritePos = 0;
private readonly WaveInEvent _waveInEvent;
private MicrophoneAudioStream()
{
_waveInEvent = new()
{
WaveFormat = new WaveFormat(SAMPLES_PER_SECOND, BYTES_PER_SAMPLE * 8, CHANNELS),
DeviceNumber = 0
};
_waveInEvent.DataAvailable += (_, e) =>
{
lock (_bufferLock)
{
int bytesToCopy = e.BytesRecorded;
if (_bufferWritePos + bytesToCopy >= _buffer.Length)
{
int bytesToCopyBeforeWrap = _buffer.Length - _bufferWritePos;
Array.Copy(e.Buffer, 0, _buffer, _bufferWritePos, bytesToCopyBeforeWrap);
bytesToCopy -= bytesToCopyBeforeWrap;
_bufferWritePos = 0;
}
Array.Copy(e.Buffer, e.BytesRecorded - bytesToCopy, _buffer, _bufferWritePos, bytesToCopy);
_bufferWritePos += bytesToCopy;
}
};
_waveInEvent.StartRecording();
}
public static MicrophoneAudioStream Start() => new();
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => throw new NotImplementedException();
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
int totalCount = count;
int GetBytesAvailable() => _bufferWritePos < _bufferReadPos
? _bufferWritePos + (_buffer.Length - _bufferReadPos)
: _bufferWritePos - _bufferReadPos;
// For simplicity, we'll block until all requested data is available and not perform partial reads.
while (GetBytesAvailable() < count)
{
Thread.Sleep(100);
}
lock (_bufferLock)
{
if (_bufferReadPos + count >= _buffer.Length)
{
int bytesBeforeWrap = _buffer.Length - _bufferReadPos;
Array.Copy(
sourceArray: _buffer,
sourceIndex: _bufferReadPos,
destinationArray: buffer,
destinationIndex: offset,
length: bytesBeforeWrap);
_bufferReadPos = 0;
count -= bytesBeforeWrap;
offset += bytesBeforeWrap;
}
Array.Copy(_buffer, _bufferReadPos, buffer, offset, count);
_bufferReadPos += count;
}
return totalCount;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
protected override void Dispose(bool disposing)
{
_waveInEvent?.Dispose();
base.Dispose(disposing);
}
}