tijiao11
This commit is contained in:
parent
b77c86974a
commit
3702252e00
|
|
@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
|||
using System.Runtime.InteropServices;
|
||||
using ShengShengBuXi.ConsoleApp.Models;
|
||||
using System.Linq;
|
||||
using System.Transactions;
|
||||
|
||||
namespace ShengShengBuXi.ConsoleApp.Services;
|
||||
|
||||
|
|
@ -867,6 +868,9 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public static DateTime _lasHasoudDateTime = DateTime.Now;
|
||||
public static bool _isSpeaking = false;
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有声音
|
||||
/// </summary>
|
||||
|
|
@ -894,9 +898,19 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isSpeaking = maxVolume > _config.Recording.SilenceThreshold;
|
||||
if (isSpeaking && !_isSpeaking)
|
||||
{
|
||||
_isSpeaking = true;
|
||||
}
|
||||
if (DateTime.Now.Subtract(_lasHasoudDateTime).TotalSeconds > 1)
|
||||
{
|
||||
Console.WriteLine($"当前音量:{maxVolume},设置的音量灵敏度:{_config.Recording.SilenceThreshold},是否已监测到在说话中:{_isSpeaking}");
|
||||
_lasHasoudDateTime = DateTime.Now;
|
||||
_isSpeaking = false;
|
||||
}
|
||||
// 如果最大音量超过阈值,则认为有声音
|
||||
return maxVolume > _config.Recording.SilenceThreshold;
|
||||
return isSpeaking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"SignalRHubUrl": "http://115.159.44.16/audiohub",
|
||||
|
||||
"SignalRHubUrl": "http://localhost:81/audiohub",
|
||||
"ConfigBackupPath": "config.json",
|
||||
"AutoConnectToServer": true
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"RecordingDeviceNumber": 0,
|
||||
"SampleRate": 16000,
|
||||
"Channels": 1,
|
||||
"BufferMilliseconds": 100,
|
||||
"BufferMilliseconds": 50,
|
||||
"SilenceThreshold": 0.15,
|
||||
"SilenceTimeoutSeconds": 30,
|
||||
"AllowUserHangup": true,
|
||||
|
|
|
|||
|
|
@ -480,10 +480,19 @@ namespace ShengShengBuXi.Hubs
|
|||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug($"转发音频数据到{monitoringClients.Count}个监听客户端,数据长度: {audioData.Length}");
|
||||
if (_configurationService.CurrentConfig.Network.EnableAudioNoiseReduction)
|
||||
{
|
||||
var jiangzao = _audioProcessingService.ApplyNoiseReduction(audioData, config.SampleRate, config.Channels);
|
||||
_logger.LogDebug($"转发音频数据到{monitoringClients.Count}个监听客户端,数据长度: {audioData.Length},降噪后长度:{jiangzao.Length}");
|
||||
// 尝试直接发送数据
|
||||
await Clients.Clients(monitoringClients).SendAsync("ReceiveAudioData", jiangzao);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 尝试直接发送数据
|
||||
await Clients.Clients(monitoringClients).SendAsync("ReceiveAudioData", audioData);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"转发音频数据到监听端失败: {ex.Message}");
|
||||
|
|
|
|||
|
|
@ -309,6 +309,11 @@ public class NetworkConfig
|
|||
/// 是否启用实时音频传输
|
||||
/// </summary>
|
||||
public bool EnableAudioStreaming { get; set; } = true;
|
||||
/// <summary>
|
||||
/// 是否开启音频降噪
|
||||
/// </summary>
|
||||
|
||||
public bool EnableAudioNoiseReduction { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳间隔(秒)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using NAudio.Dsp;
|
||||
using NAudio.Utils;
|
||||
using NAudio.Wave;
|
||||
|
||||
using ShengShengBuXi.Models;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -9,8 +14,8 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ShengShengBuXi.Services
|
||||
{
|
||||
namespace ShengShengBuXi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 音频处理服务实现
|
||||
/// </summary>
|
||||
|
|
@ -115,6 +120,50 @@ namespace ShengShengBuXi.Services
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用噪声消除
|
||||
/// </summary>
|
||||
/// <param name="audioData">音频数据</param>
|
||||
/// <param name="sampleRate">采样率</param>
|
||||
/// <param name="channels">声道数</param>
|
||||
/// <param name="noiseThreshold">噪声门限值</param>
|
||||
/// <param name="attackSeconds">攻击时间</param>
|
||||
/// <param name="releaseSeconds">释放时间</param>
|
||||
/// <param name="highPassCutoff">高通滤波器截止频率(Hz)</param>
|
||||
/// <param name="q">滤波器Q值</param>
|
||||
/// <returns></returns>
|
||||
public byte[] ApplyNoiseReduction(byte[] audioData, int sampleRate = 16000, int channels = 1, float noiseThreshold = 0.015f, float attackSeconds = 0.01f, float releaseSeconds = 0.1f, int highPassCutoff = 80, float q = 1.0f)
|
||||
{
|
||||
using (var inputStream = new MemoryStream(audioData))
|
||||
using (var waveStream = new RawSourceWaveStream(inputStream, new WaveFormat(16000, 16, 1)))
|
||||
{
|
||||
var sampleProvider = waveStream.ToSampleProvider();
|
||||
|
||||
// 改进1:更温和的噪声门参数
|
||||
var noiseGate = new ImprovedNoiseGate(sampleProvider)
|
||||
{
|
||||
Threshold = 0.015f, // 降低阈值(原0.02)
|
||||
AttackSeconds = 0.05f, // 延长Attack时间(原0.01)
|
||||
ReleaseSeconds = 0.3f, // 延长Release时间(原0.1)
|
||||
HoldSeconds = 0.2f // 新增保持时间
|
||||
};
|
||||
|
||||
// 改进2:更平缓的高通滤波
|
||||
var highPassFilter = new BiQuadFilterSampleProvider(noiseGate);
|
||||
highPassFilter.Filter = BiQuadFilter.HighPassFilter(
|
||||
sampleProvider.WaveFormat.SampleRate,
|
||||
60, // 降低截止频率(原80)
|
||||
0.707f); // 使用更平缓的Q值(原1.0)
|
||||
|
||||
// 改进3:添加平滑处理
|
||||
var smoothedProvider = new SmoothingSampleProvider(highPassFilter);
|
||||
|
||||
var outputStream = new MemoryStream();
|
||||
WaveFileWriter.WriteWavFileToStream(outputStream, smoothedProvider.ToWaveProvider16());
|
||||
|
||||
return outputStream.ToArray();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 开始新的音频流处理
|
||||
/// </summary>
|
||||
|
|
@ -397,4 +446,289 @@ namespace ShengShengBuXi.Services
|
|||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的噪声门实现
|
||||
public class NoiseGateSampleProvider : ISampleProvider
|
||||
{
|
||||
private readonly ISampleProvider source;
|
||||
private float threshold;
|
||||
private float attackSeconds;
|
||||
private float releaseSeconds;
|
||||
private float envelope;
|
||||
private float gain;
|
||||
|
||||
public NoiseGateSampleProvider(ISampleProvider source)
|
||||
{
|
||||
this.source = source;
|
||||
this.WaveFormat = source.WaveFormat;
|
||||
}
|
||||
|
||||
public float Threshold
|
||||
{
|
||||
get => threshold;
|
||||
set => threshold = Math.Max(0, Math.Min(1, value));
|
||||
}
|
||||
|
||||
public float AttackSeconds
|
||||
{
|
||||
get => attackSeconds;
|
||||
set => attackSeconds = Math.Max(0.001f, value);
|
||||
}
|
||||
|
||||
public float ReleaseSeconds
|
||||
{
|
||||
get => releaseSeconds;
|
||||
set => releaseSeconds = Math.Max(0.001f, value);
|
||||
}
|
||||
|
||||
public WaveFormat WaveFormat { get; }
|
||||
|
||||
public int Read(float[] buffer, int offset, int count)
|
||||
{
|
||||
int samplesRead = source.Read(buffer, offset, count);
|
||||
|
||||
float attackCoeff = (float)Math.Exp(-1.0 / (WaveFormat.SampleRate * attackSeconds));
|
||||
float releaseCoeff = (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)
|
||||
envelope = absSample;
|
||||
else
|
||||
envelope *= (absSample > threshold) ? attackCoeff : releaseCoeff;
|
||||
|
||||
// 应用增益
|
||||
if (envelope > threshold)
|
||||
gain = 1.0f;
|
||||
else
|
||||
gain = 0.0f;
|
||||
|
||||
buffer[offset + n] = sample * gain;
|
||||
}
|
||||
|
||||
return samplesRead;
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的BiQuad滤波器包装
|
||||
public class BiQuadFilterSampleProvider : ISampleProvider
|
||||
{
|
||||
private readonly ISampleProvider source;
|
||||
private BiQuadFilter filter;
|
||||
|
||||
public BiQuadFilterSampleProvider(ISampleProvider source)
|
||||
{
|
||||
this.source = source;
|
||||
this.WaveFormat = source.WaveFormat;
|
||||
}
|
||||
|
||||
public BiQuadFilter Filter
|
||||
{
|
||||
get => filter;
|
||||
set => filter = value;
|
||||
}
|
||||
|
||||
public WaveFormat WaveFormat { get; }
|
||||
|
||||
public int Read(float[] buffer, int offset, int count)
|
||||
{
|
||||
int samplesRead = source.Read(buffer, offset, count);
|
||||
|
||||
if (filter != null)
|
||||
{
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
{
|
||||
buffer[offset + n] = filter.Transform(buffer[offset + n]);
|
||||
}
|
||||
}
|
||||
|
||||
return samplesRead;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public ImprovedNoiseGate(ISampleProvider source)
|
||||
{
|
||||
this.source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
this.WaveFormat = source.WaveFormat;
|
||||
|
||||
// 默认参数
|
||||
Threshold = 0.015f;
|
||||
AttackSeconds = 0.05f;
|
||||
ReleaseSeconds = 0.3f;
|
||||
HoldSeconds = 0.2f;
|
||||
}
|
||||
|
||||
public WaveFormat WaveFormat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 噪声门阈值 (0.0-1.0)
|
||||
/// </summary>
|
||||
public float Threshold
|
||||
{
|
||||
get => threshold;
|
||||
set => threshold = Math.Max(0.0f, Math.Min(1.0f, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动时间 (秒)
|
||||
/// </summary>
|
||||
public float AttackSeconds
|
||||
{
|
||||
get => attackSeconds;
|
||||
set => attackSeconds = Math.Max(0.001f, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放时间 (秒)
|
||||
/// </summary>
|
||||
public float ReleaseSeconds
|
||||
{
|
||||
get => releaseSeconds;
|
||||
set => releaseSeconds = Math.Max(0.001f, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保持时间 (秒),在信号低于阈值后保持门打开的时间
|
||||
/// </summary>
|
||||
public float HoldSeconds
|
||||
{
|
||||
get => holdSeconds;
|
||||
set => holdSeconds = Math.Max(0.0f, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前包络值 (只读)
|
||||
/// </summary>
|
||||
public float CurrentEnvelope => envelope;
|
||||
|
||||
/// <summary>
|
||||
/// 当前门状态 (只读)
|
||||
/// </summary>
|
||||
public bool IsGateOpen => gateOpen;
|
||||
|
||||
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);
|
||||
|
||||
for (int n = 0; n < samplesRead; n++)
|
||||
{
|
||||
float sample = buffer[offset + n];
|
||||
float absSample = Math.Abs(sample);
|
||||
|
||||
// 更新包络
|
||||
if (absSample > envelope)
|
||||
{
|
||||
envelope = absSample + (envelope - absSample) * attackCoeff;
|
||||
}
|
||||
else
|
||||
{
|
||||
envelope = absSample + (envelope - absSample) * releaseCoeff;
|
||||
}
|
||||
|
||||
// 更新门状态
|
||||
if (envelope > Threshold)
|
||||
{
|
||||
gateOpen = true;
|
||||
holdCountRemaining = holdSamples; // 重置保持计数器
|
||||
}
|
||||
else if (holdCountRemaining > 0)
|
||||
{
|
||||
holdCountRemaining--;
|
||||
}
|
||||
else
|
||||
{
|
||||
gateOpen = false;
|
||||
}
|
||||
|
||||
// 应用增益 (带平滑过渡)
|
||||
float gain = gateOpen ? 1.0f : CalculateSoftGain(envelope);
|
||||
buffer[offset + n] = sample * gain;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置噪声门状态
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
this.source = source;
|
||||
this.history = new float[windowSize];
|
||||
this.WaveFormat = source.WaveFormat;
|
||||
}
|
||||
|
||||
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;
|
||||
for (int i = 0; i < history.Length; i++)
|
||||
sum += history[i];
|
||||
|
||||
buffer[offset + n] = sum / history.Length;
|
||||
}
|
||||
|
||||
return samplesRead;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,5 +76,20 @@ namespace ShengShengBuXi.Services
|
|||
/// <param name="clientId">客户端ID</param>
|
||||
/// <returns>录音文件路径,如果没有则返回null</returns>
|
||||
string GetRecordingFilePath(string clientId);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 应用噪声消除
|
||||
/// </summary>
|
||||
/// <param name="audioData">音频数据</param>
|
||||
/// <param name="sampleRate">采样率</param>
|
||||
/// <param name="channels">声道数</param>
|
||||
/// <param name="noiseThreshold">噪声门限值</param>
|
||||
/// <param name="attackSeconds">攻击时间</param>
|
||||
/// <param name="releaseSeconds">释放时间</param>
|
||||
/// <param name="highPassCutoff">高通滤波器截止频率(Hz)</param>
|
||||
/// <param name="q">滤波器Q值</param>
|
||||
/// <returns></returns>
|
||||
byte[] ApplyNoiseReduction(byte[] audioData, int sampleRate = 16000, int channels = 1, float noiseThreshold = 0.02f, float attackSeconds = 0.01f, float releaseSeconds = 0.1f, int highPassCutoff = 80, float q = 1.0f);
|
||||
}
|
||||
}
|
||||
|
|
@ -562,7 +562,7 @@ namespace ShengShengBuXi.Services
|
|||
await ProcessAudioAsync(audioData, sessionId, token);
|
||||
|
||||
// 等待一段时间,模拟实时处理
|
||||
await Task.Delay(100, token);
|
||||
await Task.Delay(20, token);
|
||||
}
|
||||
|
||||
// 结束会话
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"reconnectDelayMs": 2000,
|
||||
"enableSpeechToText": true,
|
||||
"enableAudioStreaming": true,
|
||||
"EnableAudioNoiseReduction": false,
|
||||
"heartbeatIntervalSeconds": 15,
|
||||
"tencentCloudASR": {
|
||||
"appId": "",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user