119 lines
3.7 KiB
C#
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);
|
|
}
|
|
} |