2025-10-11 18:25:59 +08:00

270 lines
9.5 KiB
C#

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<float[]> _waveOutStream = new Queue<float[]>();
// 音频输入相关组件
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();
}
}
}