+ 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