diff --git a/ShengShengBuXi.ConsoleApp/appsettings.json b/ShengShengBuXi.ConsoleApp/appsettings.json index df6159d..be2a092 100644 --- a/ShengShengBuXi.ConsoleApp/appsettings.json +++ b/ShengShengBuXi.ConsoleApp/appsettings.json @@ -1,5 +1,5 @@ { - "SignalRHubUrl": "http://localhost:81/audiohub", + "SignalRHubUrl": "http://115.159.44.16/audiohub", "ConfigBackupPath": "config.json", "AutoConnectToServer": true } diff --git a/ShengShengBuXi/Pages/Monitor.cshtml b/ShengShengBuXi/Pages/Monitor.cshtml index e3e2034..d28c8fe 100644 --- a/ShengShengBuXi/Pages/Monitor.cshtml +++ b/ShengShengBuXi/Pages/Monitor.cshtml @@ -18,14 +18,14 @@
+ style="width: 24px; height: 24px; background-color: red;">
未检测到通话 @@ -61,7 +65,7 @@
+ style="max-height: 75vh; overflow-y: auto;">
加载中...
@@ -95,11 +99,12 @@
-
文本编辑 (最多输入30个文字)
+
文本编辑 (最多输入30个文字)
- +
@@ -110,11 +115,12 @@
@@ -138,7 +144,7 @@
+ style="max-height: 75vh; overflow-y: auto;">
加载中...
@@ -169,14 +175,40 @@
- @section Scripts { + + + +@section Scripts { - } +} diff --git a/ShengShengBuXi/Services/AudioProcessingService.cs b/ShengShengBuXi/Services/AudioProcessingService.cs index cad1b15..b454289 100644 --- a/ShengShengBuXi/Services/AudioProcessingService.cs +++ b/ShengShengBuXi/Services/AudioProcessingService.cs @@ -208,11 +208,11 @@ public class AudioProcessingService : IAudioProcessingService private byte[] ApplyNoiseReductionInternal( byte[] audioData, - float noiseThreshold = 0.02f, // 噪声门限值 - float attackSeconds = 0.01f, // 攻击时间 - float releaseSeconds = 0.1f, // 释放时间 - int highPassCutoff = 80, // 高通滤波器截止频率(Hz) - float q = 1.0f) // 滤波器Q值 + float noiseThreshold = 0.015f, // 降低噪声门限值,使其更温和 + float attackSeconds = 0.05f, // 增加攻击时间,使开启更平滑 + float releaseSeconds = 0.15f, // 增加释放时间,使关闭更平滑 + int highPassCutoff = 60, // 降低高通滤波器截止频率,减少声音失真 + float q = 0.7071f) // 使用更平缓的Q值(巴特沃斯滤波器标准Q值) { // 1. 将字节数组转换为 WaveStream using (var inputStream = new MemoryStream(audioData)) @@ -221,24 +221,29 @@ public class AudioProcessingService : IAudioProcessingService // 2. 转换为浮点样本便于处理 var sampleProvider = waveStream.ToSampleProvider(); - // 3. 应用噪声门(Noise Gate) - var noiseGate = new NoiseGateSampleProvider(sampleProvider) + // 3. 应用改进的噪声门,使用平滑过渡 + var noiseGate = new ImprovedNoiseGate(sampleProvider) { Threshold = noiseThreshold, AttackSeconds = attackSeconds, - ReleaseSeconds = releaseSeconds + ReleaseSeconds = releaseSeconds, + HoldSeconds = 0.1f, // 添加保持时间,防止快速开关 + SoftKneeDb = 6.0f // 添加软膝,使过渡更平滑 }; - // 4. 应用高通滤波器去除低频噪音 + // 4. 应用高通滤波器去除低频噪音,使用更温和的设置 var highPassFilter = new BiQuadFilterSampleProvider(noiseGate); highPassFilter.Filter = BiQuadFilter.HighPassFilter( sampleProvider.WaveFormat.SampleRate, highPassCutoff, q); - // 5. 处理后的音频转回字节数组 + // 5. 添加平滑处理器 + var smoothedProvider = new SmoothingSampleProvider(highPassFilter, 5); + + // 6. 处理后的音频转回字节数组 var outputStream = new MemoryStream(); - WaveFileWriter.WriteWavFileToStream(outputStream, highPassFilter.ToWaveProvider16()); + WaveFileWriter.WriteWavFileToStream(outputStream, smoothedProvider.ToWaveProvider16()); return outputStream.ToArray(); } @@ -663,182 +668,204 @@ public class BiQuadFilterSampleProvider : ISampleProvider public class ImprovedNoiseGate : ISampleProvider { private readonly ISampleProvider source; - private float threshold; - private float attackSeconds; - private float releaseSeconds; - private float holdSeconds; - private float envelope; - private bool gateOpen; - private int holdCountRemaining; - + private float threshold = 0.015f; + private float attackSeconds = 0.05f; + private float releaseSeconds = 0.15f; + private float holdSeconds = 0.1f; + private float softKneeDb = 6.0f; + private float currentGain = 1.0f; + private float envelope = 0.0f; + private int holdSamples; + private int holdCounter; + public ImprovedNoiseGate(ISampleProvider source) { - this.source = source ?? throw new ArgumentNullException(nameof(source)); + this.source = source; this.WaveFormat = source.WaveFormat; - - // 默认参数 - Threshold = 0.015f; - AttackSeconds = 0.05f; - ReleaseSeconds = 0.3f; - HoldSeconds = 0.2f; + this.holdSamples = (int)(holdSeconds * WaveFormat.SampleRate); } - + public WaveFormat WaveFormat { get; } - - /// - /// 噪声门阈值 (0.0-1.0) - /// + public float Threshold { get => threshold; set => threshold = Math.Max(0.0f, Math.Min(1.0f, value)); } - - /// - /// 启动时间 (秒) - /// + public float AttackSeconds { get => attackSeconds; - set => attackSeconds = Math.Max(0.001f, value); + set + { + attackSeconds = Math.Max(0.001f, value); + } } - - /// - /// 释放时间 (秒) - /// + public float ReleaseSeconds { get => releaseSeconds; - set => releaseSeconds = Math.Max(0.001f, value); + set + { + releaseSeconds = Math.Max(0.001f, value); + } } - - /// - /// 保持时间 (秒),在信号低于阈值后保持门打开的时间 - /// + public float HoldSeconds { get => holdSeconds; - set => holdSeconds = Math.Max(0.0f, value); + set + { + holdSeconds = Math.Max(0.0f, value); + holdSamples = (int)(holdSeconds * WaveFormat.SampleRate); + } } - - /// - /// 当前包络值 (只读) - /// - public float CurrentEnvelope => envelope; - - /// - /// 当前门状态 (只读) - /// - public bool IsGateOpen => gateOpen; - + + public float SoftKneeDb + { + get => softKneeDb; + set => softKneeDb = Math.Max(0.0f, value); + } + public int Read(float[] buffer, int offset, int count) { int samplesRead = source.Read(buffer, offset, count); - - // 预计算系数 - float attackCoeff = CalculateCoefficient(AttackSeconds); - float releaseCoeff = CalculateCoefficient(ReleaseSeconds); - int holdSamples = (int)(WaveFormat.SampleRate * HoldSeconds); - + + float attackRate = (float)Math.Exp(-1.0 / (WaveFormat.SampleRate * attackSeconds)); + float releaseRate = (float)Math.Exp(-1.0 / (WaveFormat.SampleRate * releaseSeconds)); + for (int n = 0; n < samplesRead; n++) { - float sample = buffer[offset + n]; - float absSample = Math.Abs(sample); - - // 更新包络 - if (absSample > envelope) + float inputSample = buffer[offset + n]; + float absInput = Math.Abs(inputSample); + + // 包络跟踪 + if (absInput > envelope) { - envelope = absSample + (envelope - absSample) * attackCoeff; + envelope = absInput + attackRate * (envelope - absInput); } else { - envelope = absSample + (envelope - absSample) * releaseCoeff; + envelope = absInput + releaseRate * (envelope - absInput); } - - // 更新门状态 - if (envelope > Threshold) + + // 软膝处理 + float softKneeStart = threshold - (softKneeDb / 40.0f); // 将dB转换为线性值 + float softKneeEnd = threshold + (softKneeDb / 40.0f); + + float targetGain; + if (envelope < softKneeStart) { - gateOpen = true; - holdCountRemaining = holdSamples; // 重置保持计数器 + // 低于软膝起点,完全衰减 + holdCounter = 0; + targetGain = 0.0f; } - else if (holdCountRemaining > 0) + else if (envelope > softKneeEnd) { - holdCountRemaining--; + // 高于软膝终点,完全开启 + holdCounter = holdSamples; + targetGain = 1.0f; } else { - gateOpen = false; + // 在软膝区域内,线性插值 + float ratio = (envelope - softKneeStart) / (softKneeEnd - softKneeStart); + targetGain = ratio; + + // 更新保持计数器 + if (ratio > 0.5f) + { + holdCounter = holdSamples; + } + else if (holdCounter > 0) + { + holdCounter--; + } } - - // 应用增益 (带平滑过渡) - float gain = gateOpen ? 1.0f : CalculateSoftGain(envelope); - buffer[offset + n] = sample * gain; + + // 如果在保持时间内,保持门开启 + if (holdCounter > 0) + { + targetGain = Math.Max(targetGain, 0.5f); + } + + // 平滑增益变化 + if (targetGain > currentGain) + { + currentGain = targetGain * attackRate + currentGain * (1 - attackRate); + } + else + { + currentGain = targetGain * releaseRate + currentGain * (1 - releaseRate); + } + + // 应用增益 + buffer[offset + n] = inputSample * currentGain; } - + return samplesRead; } - - private float CalculateCoefficient(float timeInSeconds) - { - if (timeInSeconds <= 0.0f) return 0.0f; - return (float)Math.Exp(-1.0 / (WaveFormat.SampleRate * timeInSeconds)); - } - - private float CalculateSoftGain(float env) - { - // 软过渡:当包络接近阈值时逐渐降低增益 - if (env >= Threshold) return 1.0f; - - // 计算相对阈值的位置 (0.0-1.0) - float relativePosition = env / Threshold; - - // 三次方曲线实现平滑过渡 - return relativePosition * relativePosition * relativePosition; - } - - /// - /// 重置噪声门状态 - /// - public void Reset() - { - envelope = 0.0f; - gateOpen = false; - holdCountRemaining = 0; - } } + // 新增平滑处理器 public class SmoothingSampleProvider : ISampleProvider { private readonly ISampleProvider source; private readonly float[] history; private int historyIndex; - - public SmoothingSampleProvider(ISampleProvider source, int windowSize = 5) + private readonly float[] weights; + + public SmoothingSampleProvider(ISampleProvider source, int windowSize) { this.source = source; - this.history = new float[windowSize]; this.WaveFormat = source.WaveFormat; + this.history = new float[windowSize]; + this.weights = new float[windowSize]; + + // 创建高斯权重 + float sigma = windowSize / 6.0f; + float weightSum = 0; + for (int i = 0; i < windowSize; i++) + { + float x = (i - windowSize / 2.0f) / sigma; + weights[i] = (float)Math.Exp(-0.5f * x * x); + weightSum += weights[i]; + } + + // 归一化权重 + for (int i = 0; i < windowSize; i++) + { + weights[i] /= weightSum; + } } - + public WaveFormat WaveFormat { get; } - + public int Read(float[] buffer, int offset, int count) { int samplesRead = source.Read(buffer, offset, count); - + for (int n = 0; n < samplesRead; n++) { + // 保存当前样本到历史记录 history[historyIndex] = buffer[offset + n]; - historyIndex = (historyIndex + 1) % history.Length; - - // 简单移动平均平滑 + + // 计算加权平均 float sum = 0; + int index = historyIndex; for (int i = 0; i < history.Length; i++) - sum += history[i]; - - buffer[offset + n] = sum / history.Length; + { + sum += history[index] * weights[i]; + index = (index - 1 + history.Length) % history.Length; + } + + // 更新样本 + buffer[offset + n] = sum; + + // 更新历史索引 + historyIndex = (historyIndex + 1) % history.Length; } - + return samplesRead; } } \ No newline at end of file