BotSharp/src/Infrastructure/BotSharp.Core.Realtime/Services/WaveStreamChannel.cs

113 lines
3.7 KiB
C#
Raw Normal View History

2025-04-11 21:29:44 +00:00
using System.Collections.Concurrent;
2025-04-07 04:15:27 +00:00
using BotSharp.Abstraction.Realtime.Enums;
using NAudio.Wave;
namespace BotSharp.Core.Realtime.Services;
2025-04-09 02:48:40 +00:00
public class WaveStreamChannel : IStreamChannel
2025-04-07 04:15:27 +00:00
{
private readonly IServiceProvider _services;
private WaveInEvent _waveIn;
private WaveOutEvent _waveOut;
private BufferedWaveProvider _bufferedWaveProvider;
2025-04-11 21:29:44 +00:00
private readonly ConcurrentQueue<byte[]> _audioBufferQueue = [];
2025-04-07 04:15:27 +00:00
private readonly ILogger _logger;
2025-04-09 02:48:40 +00:00
public WaveStreamChannel(IServiceProvider services, ILogger<WaveStreamChannel> logger)
2025-04-07 04:15:27 +00:00
{
_services = services;
_logger = logger;
}
public async Task ConnectAsync(string conversationId)
{
// Initialize the WaveInEvent
_waveIn = new WaveInEvent
{
DeviceNumber = 0, // Default recording device
2025-04-14 05:24:05 +00:00
WaveFormat = new WaveFormat(16000, 16, 1), // 24000 Hz, 16-bit PCM, Mono
2025-04-14 15:13:05 +00:00
BufferMilliseconds = 100
2025-04-07 04:15:27 +00:00
};
// Set up the DataAvailable event handler
_waveIn.DataAvailable += WaveIn_DataAvailable;
// Start recording
_waveIn.StartRecording();
// Initialize audio output for streaming
var waveFormat = new WaveFormat(24000, 16, 1); // 24000 Hz, 16-bit PCM, Mono
_bufferedWaveProvider = new BufferedWaveProvider(waveFormat);
2025-04-14 06:25:16 +00:00
_bufferedWaveProvider.BufferDuration = TimeSpan.FromMinutes(10);
2025-04-14 15:13:05 +00:00
_bufferedWaveProvider.DiscardOnBufferOverflow = true;
2025-04-14 06:25:16 +00:00
_waveOut = new WaveOutEvent()
{
DeviceNumber = 0
};
2025-04-07 04:15:27 +00:00
_waveOut.Init(_bufferedWaveProvider);
_waveOut.Play();
}
public async Task<StreamReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellation)
{
// Poll the queue until data is available or cancellation is requested
while (!cancellation.IsCancellationRequested)
{
// Try to dequeue audio data
if (_audioBufferQueue.TryDequeue(out byte[]? audioData))
{
// Copy data to the provided buffer
int bytesToCopy = Math.Min(audioData.Length, buffer.Count);
Array.Copy(audioData, 0, buffer.Array, buffer.Offset, bytesToCopy);
// Return the result
return new StreamReceiveResult
{
Status = StreamChannelStatus.Open,
Count = bytesToCopy
};
}
// No data available yet, wait a short time before checking again
await Task.Delay(10, cancellation);
}
// Cancellation was requested
return new StreamReceiveResult();
}
public Task SendAsync(byte[] data, CancellationToken cancellation)
{
_logger.LogDebug($"Sending audio data of length {data.Length} to the stream channel.");
// Add the incoming data to the buffer for continuous playback
_bufferedWaveProvider.AddSamples(data, 0, data.Length);
return Task.CompletedTask;
}
2025-04-11 21:29:44 +00:00
public void ClearBuffer()
{
_bufferedWaveProvider?.ClearBuffer();
2025-04-11 21:46:18 +00:00
_audioBufferQueue?.Clear();
2025-04-11 21:29:44 +00:00
}
2025-04-07 04:15:27 +00:00
private void WaveIn_DataAvailable(object? sender, WaveInEventArgs e)
{
// Add the buffer to the queue
_audioBufferQueue.Enqueue(e.Buffer);
}
public async Task CloseAsync(StreamChannelStatus status, string description, CancellationToken cancellation)
{
// Stop recording and clean up
_waveIn?.StopRecording();
_waveIn?.Dispose();
_waveIn = null;
// Stop playback and clean up
_waveOut?.Stop();
_waveOut?.Dispose();
_waveOut = null;
}
}