Add managed codec DSP and initial control server
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
namespace VoiceCat.Dsp;
|
||||
|
||||
public sealed class EnergyVadProcessor
|
||||
{
|
||||
private readonly TimeProvider timeProvider;
|
||||
private long lastVoiceTimestamp;
|
||||
private bool hasVoice;
|
||||
private float threshold;
|
||||
|
||||
public float Threshold
|
||||
{
|
||||
get => Volatile.Read(ref threshold);
|
||||
set
|
||||
{
|
||||
if (!float.IsFinite(value) || value is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(value));
|
||||
Volatile.Write(ref threshold, value);
|
||||
}
|
||||
}
|
||||
public TimeSpan HangTime { get; }
|
||||
|
||||
public EnergyVadProcessor(float threshold = 0.02f, TimeSpan? hangTime = null, TimeProvider? timeProvider = null)
|
||||
{
|
||||
Threshold = threshold;
|
||||
HangTime = hangTime ?? TimeSpan.FromMilliseconds(300);
|
||||
if (HangTime < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(hangTime));
|
||||
this.timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public bool Process(ReadOnlySpan<short> pcm)
|
||||
{
|
||||
long now = timeProvider.GetTimestamp();
|
||||
if (!pcm.IsEmpty)
|
||||
{
|
||||
double sum = 0;
|
||||
foreach (short sample in pcm)
|
||||
{
|
||||
double normalized = sample / 32768.0;
|
||||
sum += normalized * normalized;
|
||||
}
|
||||
if (Math.Sqrt(sum / pcm.Length) >= Threshold)
|
||||
{
|
||||
lastVoiceTimestamp = now;
|
||||
hasVoice = true;
|
||||
}
|
||||
}
|
||||
return hasVoice && timeProvider.GetElapsedTime(lastVoiceTimestamp, now) < HangTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace VoiceCat.Dsp;
|
||||
|
||||
public sealed unsafe partial class RnnoiseProcessor : IDisposable
|
||||
{
|
||||
public const int SampleRate = 48000;
|
||||
public const int FrameSamples = 480;
|
||||
private readonly RnnoiseHandle handle;
|
||||
private readonly float[] input = new float[FrameSamples];
|
||||
private readonly float[] output = new float[FrameSamples];
|
||||
|
||||
public RnnoiseProcessor()
|
||||
{
|
||||
handle = new(Create());
|
||||
if (handle.IsInvalid) { handle.Dispose(); throw new OutOfMemoryException(); }
|
||||
}
|
||||
|
||||
public void Process(Span<short> pcm, int sampleRate = SampleRate)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(handle.IsClosed, this);
|
||||
if (sampleRate != SampleRate) return;
|
||||
if (pcm.Length % FrameSamples != 0) throw new ArgumentException("RNNoise requires complete 480-sample mono chunks.", nameof(pcm));
|
||||
fixed (float* source = input)
|
||||
fixed (float* destination = output)
|
||||
{
|
||||
for (int offset = 0; offset < pcm.Length; offset += FrameSamples)
|
||||
{
|
||||
for (int i = 0; i < FrameSamples; i++) input[i] = pcm[offset + i];
|
||||
ProcessFrame(handle, destination, source);
|
||||
for (int i = 0; i < FrameSamples; i++)
|
||||
pcm[offset + i] = (short)Math.Clamp(MathF.Round(output[i], MidpointRounding.AwayFromZero), short.MinValue, short.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => handle.Dispose();
|
||||
|
||||
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_create")]
|
||||
private static partial nint Create();
|
||||
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_destroy")]
|
||||
private static partial void Destroy(nint state);
|
||||
[LibraryImport("voicecat_media", EntryPoint = "vcm_rnnoise_process")]
|
||||
private static partial float ProcessFrame(RnnoiseHandle state, float* output, float* input);
|
||||
|
||||
private sealed class RnnoiseHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public RnnoiseHandle() : base(true) { }
|
||||
internal RnnoiseHandle(nint value) : this() => SetHandle(value);
|
||||
protected override bool ReleaseHandle() { Destroy(handle); return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0": {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user