using PortAudioSharp; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using XiaoZhiSharp.Utils; namespace XiaoZhiSharp.Services { public class AudioPortService : IAudioService, IDisposable { // 音频输出相关组件 private readonly PortAudioSharp.Stream? _waveOut; private readonly Queue _waveOutStream = new Queue(); // 音频输入相关组件 private readonly PortAudioSharp.Stream? _waveIn; public delegate Task PcmAudioEventHandler(byte[] pcm); public event IAudioService.PcmAudioEventHandler? OnPcmAudioEvent; // 音频参数 private const int SampleRate = 24000; public int SampleRate_WaveIn { get; set; } = 16000; private const int Bitrate = 16; private const int Channels = 1; private const int FrameDuration = 60; private const int FrameSize = SampleRate * FrameDuration / 1000; // 帧大小 public bool IsRecording { get; private set; } public bool IsPlaying { get; private set; } public int VadCounter { get; private set; } = 0; // 用于语音活动检测的计数器 public AudioPortService() { // 初始化音频输出组件 PortAudio.Initialize(); int outputDeviceIndex = PortAudio.DefaultOutputDevice; if (outputDeviceIndex == PortAudio.NoDevice) { Console.WriteLine("No default output device found"); LogConsole.InfoLine(PortAudio.VersionInfo.versionText); LogConsole.WriteLine($"Number of devices: {PortAudio.DeviceCount}"); for (int i = 0; i != PortAudio.DeviceCount; ++i) { LogConsole.WriteLine($" Device {i}"); DeviceInfo deviceInfo = PortAudio.GetDeviceInfo(i); LogConsole.WriteLine($" Name: {deviceInfo.name}"); LogConsole.WriteLine($" Max input channels: {deviceInfo.maxInputChannels}"); LogConsole.WriteLine($" Default sample rate: {deviceInfo.defaultSampleRate}"); } //Environment.Exit(1); } var outputInfo = PortAudio.GetDeviceInfo(outputDeviceIndex); var outparam = new StreamParameters { device = outputDeviceIndex, channelCount = Channels, sampleFormat = SampleFormat.Float32, suggestedLatency = outputInfo.defaultLowOutputLatency, hostApiSpecificStreamInfo = IntPtr.Zero }; LogConsole.InfoLine($"开始创建音频输出流..."); _waveOut = new PortAudioSharp.Stream( inParams: null, outParams: outparam, sampleRate: SampleRate, framesPerBuffer: 1440, streamFlags: StreamFlags.ClipOff, callback: PlayCallback, userData: IntPtr.Zero ); LogConsole.InfoLine($"成功创建音频输出流..."); // 初始化音频输入组件 int inputDeviceIndex = PortAudio.DefaultInputDevice; if (inputDeviceIndex == PortAudio.NoDevice) { Console.WriteLine("No default input device found"); //Environment.Exit(1); } var inputInfo = PortAudio.GetDeviceInfo(inputDeviceIndex); var inparam = new StreamParameters { device = inputDeviceIndex, channelCount = Channels, sampleFormat = SampleFormat.Float32, suggestedLatency = inputInfo.defaultLowInputLatency, hostApiSpecificStreamInfo = IntPtr.Zero }; LogConsole.InfoLine($"开始创建音频输入流..."); _waveIn = new PortAudioSharp.Stream( inParams: inparam, outParams: null, sampleRate: SampleRate_WaveIn, framesPerBuffer: 1440, streamFlags: StreamFlags.ClipOff, callback: InCallback, userData: IntPtr.Zero ); LogConsole.InfoLine($"成功创建音频输入流..."); // 启动音频播放 StartPlaying(); LogConsole.InfoLine($"当前默认音频输入设备: {inputDeviceIndex} ({inputInfo.name})"); LogConsole.InfoLine($"当前默认音频输出设备: {outputDeviceIndex} ({outputInfo.name})"); } private StreamCallbackResult PlayCallback( IntPtr input, IntPtr output, uint frameCount, ref StreamCallbackTimeInfo timeInfo, StreamCallbackFlags statusFlags, IntPtr userData) { if (_waveOutStream.Count <= 0) { //return StreamCallbackResult.Complete; } try { while (_waveOutStream.Count > 0) { float[]? buffer; lock (_waveOutStream) { if (_waveOutStream.TryDequeue(out buffer)) { if (buffer.Length < frameCount) { float[] paddedBuffer = new float[frameCount]; Array.Copy(buffer, paddedBuffer, buffer.Length); Marshal.Copy(paddedBuffer, 0, output, (int)frameCount); //Thread.Sleep(10); } else { Marshal.Copy(buffer, 0, output, (int)frameCount); } } return StreamCallbackResult.Continue; } } return StreamCallbackResult.Continue; //return StreamCallbackResult.Complete; } catch (Exception ex) { Console.WriteLine(ex.Message); return StreamCallbackResult.Complete; } } private StreamCallbackResult InCallback( IntPtr input, IntPtr output, uint frameCount, ref StreamCallbackTimeInfo timeInfo, StreamCallbackFlags statusFlags, IntPtr userData) { try { if (!IsRecording) { return StreamCallbackResult.Complete; } // 创建一个数组来存储输入的音频数据 float[] samples = new float[frameCount]; // 将输入的音频数据从非托管内存复制到托管数组 Marshal.Copy(input, samples, 0, (int)frameCount); // 将音频数据转换为字节数组 byte[] buffer = FloatArrayToByteArray(samples); // 处理音频数据 if (OnPcmAudioEvent != null) OnPcmAudioEvent(buffer); return StreamCallbackResult.Continue; } catch (Exception ex) { Console.WriteLine(ex.ToString()); return StreamCallbackResult.Complete; } } public static byte[] FloatArrayToByteArray(float[] floatArray) { // 初始化一个与 float 数组长度两倍的 byte 数组,因为每个 short 占 2 个字节 byte[] byteArray = new byte[floatArray.Length * 2]; for (int i = 0; i < floatArray.Length; i++) { // 将 float 类型的值映射到 short 类型的范围 short sample = (short)(floatArray[i] * short.MaxValue); // 将 short 类型的值拆分为两个字节 byteArray[i * 2] = (byte)(sample & 0xFF); byteArray[i * 2 + 1] = (byte)(sample >> 8); } return byteArray; } public static float[] ByteArrayToFloatArray(byte[] byteArray) { int floatArrayLength = byteArray.Length / 2; float[] floatArray = new float[floatArrayLength]; for (int i = 0; i < floatArrayLength; i++) { floatArray[i] = BitConverter.ToInt16(byteArray, i * 2) / 32768f; } return floatArray; } public void StartRecording() { if (!IsRecording) { _waveIn?.Start(); IsRecording = true; } } public void StopRecording() { if (IsRecording) { _waveIn?.Stop(); IsRecording = false; } } public void StartPlaying() { if (!IsPlaying) { _waveOut?.Start(); IsPlaying = true; } } public void StopPlaying() { if (IsPlaying) { _waveOut?.Stop(); IsPlaying = false; } } public void AddOutSamples(byte[] pcmData) { lock (_waveOutStream) { _waveOutStream.Enqueue(ByteArrayToFloatArray(pcmData)); } } public void AddOutSamples(float[] pcmData) { lock (_waveOutStream) { _waveOutStream.Enqueue(pcmData); } } public void Dispose() { IsPlaying = false; IsRecording = false; _waveIn?.Dispose(); _waveOut?.Dispose(); PortAudio.Terminate(); } } }