This commit is contained in:
zpc 2025-03-29 12:14:34 +08:00
parent ff3eb1db34
commit 08462dad0b
4 changed files with 338 additions and 333 deletions

View File

@ -589,7 +589,14 @@ namespace ShengShengBuXi.Hubs
foreach (var clientId in monitoringClients)
{
// 添加音频数据发送任务,使用单独的客户端连接
tasks.Add(Clients.Client(clientId).SendAsync("ReceiveAudioData", dataToSend));
// 使用清晰的对象格式并添加WAV头以确保格式正确
var wavData = _audioProcessingService.AddWavHeader(dataToSend, config.SampleRate, config.Channels);
tasks.Add(Clients.Client(clientId).SendAsync("ReceiveAudioData", new {
format = "WAV",
sampleRate = config.SampleRate,
channels = config.Channels,
data = wavData
}));
}
// 只在日志级别为Debug时输出详细信息

View File

@ -115,12 +115,12 @@
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">
文本编辑 <span style="color:#6c757d;font-size:12px;">(最多输入30个文字)</span>
文本编辑 <span style="color:#6c757d;font-size:12px;">(最多输入100个文字)</span>
</h5>
</div>
<div class="card-body">
<textarea id="text-input" class="form-control h-100" placeholder="请输入要显示的文本..."
maxlength="30"></textarea>
maxlength="100"></textarea>
</div>
</div>
@ -269,22 +269,6 @@
let currentVolume = 1.0; // 默认音量
let volumeBoost = 3.0; // 音量增益倍数,提高接收到的音频音量
// 添加音频缓冲区变量
let audioBufferQueue = [];
const MAX_BUFFER_SIZE = 400; // 增加缓冲队列大小,提供更充足的数据缓冲
let isAudioPlaying = false;
let audioBufferTimeout = null;
const BUFFER_PROCESS_INTERVAL = 85; // 显著增加处理间隔,确保音频帧有足够的播放时间
let bufferStartSizeThreshold = 25; // 增加开始阈值,确保有足够数据开始播放
let lastAudioTimestamp = 0;
let audioJitterBuffer = 60; // 增加抖动缓冲,避免播放过快
let sampleRate = 16000; // 采样率固定为16kHz
let frameSize = 320; // 每帧样本数 (20ms @@ 16kHz)
let targetFrameDuration = 20; // 目标帧时长(毫秒)
let receivedPacketCounter = 0;
let lastPacketTime = 0;
let receivedPacketRate = 0;
// 调试日志
function log(message) {
const timestamp = new Date().toLocaleTimeString();
@ -562,9 +546,6 @@
indicator.style.backgroundColor = "red";
statusText.textContent = "未检测到通话";
// 停止缓冲区处理
stopBufferProcessing();
// 如果确认对话框还在显示(用户未点击接听),则自动关闭
const confirmDialog = bootstrap.Modal.getInstance(document.getElementById('callConfirmDialog'));
if (confirmDialog) {
@ -1109,9 +1090,6 @@
clearInterval(refreshDisplayInterval);
}
// 停止缓冲区处理
stopBufferProcessing();
// 关闭音频上下文
if (audioContext) {
audioContext.close().catch(e => console.log("关闭音频上下文失败: " + e));
@ -1441,7 +1419,6 @@
// 创建新的音频上下文
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioContext = new AudioContext({
sampleRate: sampleRate, // 固定使用16kHz采样率
latencyHint: 'interactive' // 低延迟设置
});
@ -1452,9 +1429,6 @@
log(`音频上下文已初始化: 采样率=${audioContext.sampleRate}Hz, 状态=${audioContext.state}, 音量增益=${volumeBoost}倍`);
// 重置音频缓冲处理
startBufferProcessing();
// 恢复音频上下文
if (audioContext.state === 'suspended') {
const resumeAudio = function () {
@ -1481,182 +1455,241 @@
}
}
// 启动缓冲区处理
function startBufferProcessing() {
// 停止现有的处理
if (audioBufferTimeout) {
clearInterval(audioBufferTimeout);
}
// 播放实时音频 - 适应新格式
function playRealTimeAudio(audioPacket) {
if (!audioContext || !isAudioStreamEnabled || !callInProgress) return;
// 重置状态
audioBufferQueue = [];
isAudioPlaying = false;
lastAudioTimestamp = 0;
receivedPacketCounter = 0;
lastPacketTime = 0;
// 启动处理间隔
audioBufferTimeout = setInterval(processAudioBuffer, BUFFER_PROCESS_INTERVAL);
log(`音频处理已启动: 间隔=${BUFFER_PROCESS_INTERVAL}ms, 缓冲阈值=${bufferStartSizeThreshold}帧`);
}
// 停止缓冲区处理
function stopBufferProcessing() {
if (audioBufferTimeout) {
clearInterval(audioBufferTimeout);
audioBufferTimeout = null;
}
// 清空状态
audioBufferQueue = [];
isAudioPlaying = false;
lastAudioTimestamp = 0;
log("音频处理已停止");
}
// 处理音频缓冲区
function processAudioBuffer() {
if (!audioContext || !isAudioStreamEnabled || !callInProgress) {
return;
}
// 没有数据时等待
if (audioBufferQueue.length === 0) {
if (isAudioPlaying) {
log("缓冲区已空,等待更多数据...");
isAudioPlaying = false;
}
return;
}
// 初始播放需要达到阈值
if (!isAudioPlaying && audioBufferQueue.length < bufferStartSizeThreshold) {
// 只在达到特定比例时记录
if (audioBufferQueue.length === 1 ||
audioBufferQueue.length === Math.floor(bufferStartSizeThreshold / 2) ||
audioBufferQueue.length === bufferStartSizeThreshold - 1) {
log(`缓冲中: ${audioBufferQueue.length}/${bufferStartSizeThreshold}`);
}
return;
}
// 当前时间和上次播放的时间间隔检查
const now = Date.now();
const elapsed = now - lastAudioTimestamp;
// 如果上次播放时间太近,等待足够的时间间隔
if (isAudioPlaying && elapsed < audioJitterBuffer) {
return;
}
// 从队列获取下一个音频包
const packet = audioBufferQueue.shift();
if (packet && !packet.processed) {
playBufferedAudio(packet.data, packet.estimatedDuration);
packet.processed = true;
isAudioPlaying = true;
lastAudioTimestamp = now;
// 自适应调整缓冲区处理间隔
const bufferRatio = audioBufferQueue.length / MAX_BUFFER_SIZE;
if (bufferRatio > 0.7) {
// 数据充足,可以稍微加快处理
if (audioBufferTimeout) {
clearInterval(audioBufferTimeout);
const newInterval = Math.max(BUFFER_PROCESS_INTERVAL * 0.8, 60);
audioBufferTimeout = setInterval(processAudioBuffer, newInterval);
log(`缓冲区数据充足 (${audioBufferQueue.length}/${MAX_BUFFER_SIZE}), 调整间隔为 ${newInterval.toFixed(0)}ms`);
}
} else if (bufferRatio < 0.1 && audioBufferQueue.length > 0) {
// 数据不足,需要减缓处理速度
if (audioBufferTimeout) {
clearInterval(audioBufferTimeout);
const newInterval = BUFFER_PROCESS_INTERVAL * 1.5;
audioBufferTimeout = setInterval(processAudioBuffer, newInterval);
log(`缓冲区数据不足 (${audioBufferQueue.length}/${MAX_BUFFER_SIZE}), 调整间隔为 ${newInterval.toFixed(0)}ms`);
}
}
}
}
// 播放缓冲区中的音频
async function playBufferedAudio(pcmData, estimatedDuration) {
try {
// 确保音频上下文正常
if (!audioContext || audioContext.state === 'closed') {
initAudioContext();
if (!audioContext) return;
// 解析音频元数据和数据
const { format, sampleRate, channels, data } = audioPacket;
// 确保格式正确
if (!format || !data) {
log("音频格式或数据无效");
return;
}
if (audioContext.state === 'suspended') {
try {
await audioContext.resume();
} catch (e) {
log(`接收到音频数据: 格式=${format}, 采样率=${sampleRate}, 声道=${channels}, 数据长度=${Array.isArray(data) ? data.length : '未知'}`);
// 根据不同的音频格式处理
if (format === "WAV") {
// 处理WAV格式数据 - 异步解码
processWavData(data, sampleRate, channels)
.then(audioBuffer => {
if (audioBuffer) {
playAudioBuffer(audioBuffer);
} else {
log("无法创建WAV音频缓冲区");
}
})
.catch(err => {
log("WAV处理错误: " + err);
});
} else if (format === "16bit_PCM") {
// 处理PCM格式数据 - 同步处理
const audioBuffer = processPcmData(data, sampleRate, channels);
if (audioBuffer) {
playAudioBuffer(audioBuffer);
} else {
log("无法创建PCM音频缓冲区");
}
} else {
log("不支持的音频格式: " + format);
}
} catch (e) {
log("处理实时音频失败: " + e);
}
}
// 播放音频缓冲区
function playAudioBuffer(audioBuffer) {
// 确保音频上下文活跃
if (audioContext.state === 'suspended') {
try {
audioContext.resume();
} catch (e) {
log("恢复音频上下文失败: " + e);
return;
}
}
// 创建音频源并连接
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
// 应用音量控制
source.connect(audioGainNode);
if (audioGainNode) {
audioGainNode.gain.value = currentVolume * volumeBoost;
}
// 立即播放
source.start(0);
log("开始播放音频");
}
// 处理WAV格式的数据 - 返回Promise
function processWavData(data, sampleRate, channels) {
return new Promise((resolve, reject) => {
try {
// 确保音频上下文存在
if (!audioContext || audioContext.state === 'closed') {
initAudioContext();
if (!audioContext) {
reject(new Error("无法初始化音频上下文"));
return;
}
}
// 将数据转换为ArrayBuffer
let arrayBuffer;
if (data instanceof Uint8Array) {
arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
} else if (Array.isArray(data)) {
// 转换数组为Uint8Array
const uint8Array = new Uint8Array(data);
arrayBuffer = uint8Array.buffer;
} else if (typeof data === 'string') {
// 处理Base64编码
try {
const base64Str = data.trim().replace(/^data:[^;]+;base64,/, '');
const binary = atob(base64Str);
const uint8Array = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
uint8Array[i] = binary.charCodeAt(i);
}
arrayBuffer = uint8Array.buffer;
} catch (e) {
log("WAV数据Base64解码失败: " + e);
reject(e);
return;
}
} else if (data.buffer) {
arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
} else {
const error = new Error("无法处理的WAV数据类型");
log(error.message);
reject(error);
return;
}
}
// 数据准备
let dataView;
try {
dataView = new DataView(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength);
// 使用Web Audio API解码音频
audioContext.decodeAudioData(
arrayBuffer,
(buffer) => {
log("WAV数据解码成功, 时长: " + buffer.duration.toFixed(2) + "秒");
resolve(buffer);
},
(err) => {
log("解码WAV数据失败: " + err);
reject(err);
}
);
} catch (e) {
const newBuffer = new ArrayBuffer(pcmData.length);
const newBufferView = new Uint8Array(newBuffer);
newBufferView.set(pcmData);
dataView = new DataView(newBuffer);
log("处理WAV数据失败: " + e);
reject(e);
}
});
}
// 处理PCM格式的数据
function processPcmData(data, sampleRate, channels) {
try {
// 确保音频上下文存在
if (!audioContext || audioContext.state === 'closed') {
initAudioContext();
if (!audioContext) return null;
}
// 转换为音频数据并增强音量
const floatData = new Float32Array(pcmData.length / 2);
for (let i = 0, j = 0; i < pcmData.length; i += 2, j++) {
if (i + 1 < pcmData.length) {
const int16 = dataView.getInt16(i, true);
// 标准化16位PCM数据到-1.0到1.0,但不进行音量增益
// 音量增益由audioGainNode处理避免信号失真
floatData[j] = int16 / 32768.0;
// 转换数据为适合的格式
let pcmData;
if (data instanceof Uint8Array) {
pcmData = data;
} else if (Array.isArray(data)) {
pcmData = new Uint8Array(data);
} else if (typeof data === 'object' && data.buffer) {
pcmData = new Uint8Array(data.buffer);
} else if (typeof data === 'string') {
try {
// 处理Base64编码
const base64Str = data.trim().replace(/^data:[^;]+;base64,/, '');
const binary = atob(base64Str);
pcmData = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
pcmData[i] = binary.charCodeAt(i);
}
} catch (e) {
log("PCM数据Base64解码失败: " + e);
return null;
}
} else {
log("不支持的PCM数据类型");
return null;
}
// 实际帧长度计算和调整
const actualFrameDuration = (floatData.length / sampleRate) * 1000; // 毫秒
let outputFloatData = floatData;
// 确保音频帧至少有目标时长
if (actualFrameDuration < targetFrameDuration && floatData.length > 0) {
const targetLength = Math.ceil(targetFrameDuration * sampleRate / 1000);
outputFloatData = new Float32Array(targetLength);
outputFloatData.set(floatData);
// 其余部分为0即静音填充
// 确保有效的数据
if (!pcmData || pcmData.length < 2) {
log("PCM数据无效或太短");
return null;
}
// 创建音频缓冲区
const buffer = audioContext.createBuffer(1, outputFloatData.length, sampleRate);
const channel = buffer.getChannelData(0);
channel.set(outputFloatData);
// 创建音频源
const source = audioContext.createBufferSource();
source.buffer = buffer;
// 应用音量控制
source.connect(audioGainNode);
if (audioGainNode) {
audioGainNode.gain.value = currentVolume * volumeBoost; // 应用音量增益
// 确保数据长度是偶数16位PCM
const validLength = Math.floor(pcmData.length / 2) * 2;
if (validLength < pcmData.length) {
pcmData = pcmData.slice(0, validLength);
}
// 确保音频播放完成时进行清理
source.onended = () => {
// 在音频片段播放完成时可以执行一些操作
};
try {
// 从Uint8Array创建Int16Array视图
let int16Data;
// 使用精确调度启动播放
const startTime = audioContext.currentTime;
source.start(startTime);
try {
// 创建DataView以便正确解析16位整数
const dataView = new DataView(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength);
int16Data = new Int16Array(pcmData.length / 2);
// 从小端字节序读取16位整数
for (let i = 0; i < pcmData.length; i += 2) {
if (i + 1 < pcmData.length) {
int16Data[i/2] = dataView.getInt16(i, true); // true表示小端字节序
}
}
} catch (e) {
log("创建Int16Array失败: " + e);
// 备用方法
const newBuffer = new ArrayBuffer(pcmData.length);
const newView = new Uint8Array(newBuffer);
newView.set(pcmData);
const dataView = new DataView(newBuffer);
int16Data = new Int16Array(pcmData.length / 2);
for (let i = 0; i < pcmData.length; i += 2) {
if (i + 1 < pcmData.length) {
int16Data[i/2] = dataView.getInt16(i, true);
}
}
}
// 创建音频缓冲区,使用实际采样率
const audioSampleRate = sampleRate || audioContext.sampleRate;
const buffer = audioContext.createBuffer(channels || 1, int16Data.length, audioSampleRate);
// 将Int16数据转换为Float32数据并存入缓冲区
const channelData = buffer.getChannelData(0);
for (let i = 0; i < int16Data.length; i++) {
// 将Int16转换为-1.0到1.0的Float32
channelData[i] = Math.max(-1, Math.min(1, int16Data[i] / 32768.0));
}
return buffer;
} catch (e) {
log("PCM数据处理失败: " + e);
return null;
}
} catch (e) {
log("播放缓冲音频失败: " + e);
log("处理PCM数据失败: " + e);
return null;
}
}
@ -1788,110 +1821,5 @@
updateScreenControlUI(false);
});
}
// 播放实时音频
function playRealTimeAudio(audioData) {
if (!audioContext || !isAudioStreamEnabled) return;
try {
// 计算数据接收速率
const now = Date.now();
receivedPacketCounter++;
if (now - lastPacketTime > 1000) {
receivedPacketRate = receivedPacketCounter;
receivedPacketCounter = 0;
lastPacketTime = now;
log(`音频数据接收速率: ${receivedPacketRate} 包/秒`);
}
// 处理音频数据
let pcmData;
// 不同类型的音频数据处理
if (audioData instanceof Uint8Array) {
pcmData = audioData;
} else if (audioData instanceof ArrayBuffer) {
pcmData = new Uint8Array(audioData);
} else if (Array.isArray(audioData)) {
pcmData = new Uint8Array(audioData);
} else if (typeof audioData === 'object' && audioData !== null) {
if (audioData.data && (audioData.data instanceof Uint8Array || audioData.data instanceof ArrayBuffer)) {
pcmData = audioData.data instanceof Uint8Array ? audioData.data : new Uint8Array(audioData.data);
} else if (audioData.buffer && audioData.buffer instanceof ArrayBuffer) {
pcmData = new Uint8Array(audioData.buffer);
} else {
log("无法识别的对象类型数据");
return;
}
} else if (typeof audioData === 'string') {
try {
// 移除可能的数据URL前缀
const base64Str = audioData.trim().replace(/^data:[^;]+;base64,/, '');
// Base64解码
const binary = atob(base64Str);
pcmData = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
pcmData[i] = binary.charCodeAt(i);
}
} catch (e) {
try {
pcmData = new Uint8Array(audioData.length);
for (let i = 0; i < audioData.length; i++) {
pcmData[i] = audioData.charCodeAt(i);
}
} catch (e2) {
return;
}
}
} else {
return;
}
// 数据有效性检查
if (!pcmData || pcmData.length < 2) {
return;
}
// 确保数据长度是偶数16位样本需要2个字节
const validLength = Math.floor(pcmData.length / 2) * 2;
if (validLength < pcmData.length) {
pcmData = pcmData.slice(0, validLength);
}
// 估算当前数据帧的时长
const numSamples = validLength / 2; // 16位PCM = 2字节/样本
const frameDuration = (numSamples / sampleRate) * 1000; // 毫秒
// 时间戳校准功能 - 固定帧率处理
const timestamp = Date.now();
const packet = {
data: pcmData,
timestamp: timestamp,
estimatedDuration: frameDuration,
processed: false
};
// 添加到缓冲队列
if (audioBufferQueue.length < MAX_BUFFER_SIZE) {
audioBufferQueue.push(packet);
// 只在达到重要阈值时记录
if (audioBufferQueue.length === bufferStartSizeThreshold ||
audioBufferQueue.length % 20 === 0) {
log(`缓冲区状态: ${audioBufferQueue.length}/${MAX_BUFFER_SIZE}, 估计时长: ${frameDuration.toFixed(1)}ms`);
}
} else {
// 缓冲区已满时的处理策略
const keepFrames = Math.floor(MAX_BUFFER_SIZE * 0.75);
audioBufferQueue.splice(0, MAX_BUFFER_SIZE - keepFrames);
audioBufferQueue.push(packet);
log(`缓冲区已满 (${MAX_BUFFER_SIZE}), 丢弃旧数据, 保留${keepFrames}帧`);
}
} catch (e) {
log("处理实时音频失败: " + e);
}
}
</script>
}

View File

@ -189,7 +189,7 @@ public class AudioProcessingService : IAudioProcessingService
}
/// <summary>
/// 应用降噪处理
/// 应用噪声消除
/// </summary>
/// <param name="audioData">音频数据</param>
/// <param name="sampleRate">采样率</param>
@ -197,55 +197,116 @@ public class AudioProcessingService : IAudioProcessingService
/// <param name="noiseThreshold">噪声门限值</param>
/// <param name="attackSeconds">攻击时间</param>
/// <param name="releaseSeconds">释放时间</param>
/// <param name="highPassCutoff">高通滤波器截止频率</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)
public 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)
{
// 调用内部实现
return ApplyNoiseReductionInternal(audioData, noiseThreshold, attackSeconds, releaseSeconds, highPassCutoff, q);
// 基本的噪声消除实现(简化版)
// 实际项目中,这里可以使用更复杂的噪声消除算法,如频域滤波、自适应滤波等
// 为简单起见,我们这里只实现一个简单的高通滤波和噪声门限
if (audioData == null || audioData.Length < 4)
{
return audioData;
}
try
{
// 转换为16位的短整型数据PCM格式
short[] samples = new short[audioData.Length / 2];
for (int i = 0; i < samples.Length; i++)
{
// 假设数据是小端字节序
samples[i] = (short)(audioData[i * 2] | (audioData[i * 2 + 1] << 8));
}
// 应用噪声门限 - 低于门限的声音被静音
float maxAmplitude = samples.Max(s => Math.Abs(s)) / 32768.0f; // 归一化
if (maxAmplitude < noiseThreshold)
{
// 整个片段低于门限,认为是噪声,全部静音
Array.Clear(samples, 0, samples.Length);
}
else
{
// 应用高通滤波简单的单阶IIR滤波器
float alpha = (float)Math.Exp(-2 * Math.PI * highPassCutoff / sampleRate);
float filterState = 0;
for (int i = 0; i < samples.Length; i++)
{
// 高通滤波
float input = samples[i] / 32768.0f;
float filtered = alpha * (filterState + input - filterState);
filterState = input;
// 噪声门限
float absFiltered = Math.Abs(filtered);
if (absFiltered < noiseThreshold)
{
filtered = 0;
}
else
{
// 应用平滑瞬态响应(攻击/释放)
// 这里简化实现
}
// 转回16位整数
samples[i] = (short)(filtered * 32768.0f);
}
}
// 转回字节数组
byte[] result = new byte[samples.Length * 2];
for (int i = 0; i < samples.Length; i++)
{
// 小端字节序
result[i * 2] = (byte)(samples[i] & 0xFF);
result[i * 2 + 1] = (byte)((samples[i] >> 8) & 0xFF);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "应用噪声消除时发生错误");
return audioData; // 失败时返回原始数据
}
}
private byte[] ApplyNoiseReductionInternal(
byte[] audioData,
float noiseThreshold = 0.015f, // 降低噪声门限值,使其更温和
float attackSeconds = 0.05f, // 增加攻击时间,使开启更平滑
float releaseSeconds = 0.15f, // 增加释放时间,使关闭更平滑
int highPassCutoff = 60, // 降低高通滤波器截止频率,减少声音失真
float q = 0.7071f) // 使用更平缓的Q值巴特沃斯滤波器标准Q值
/// <summary>
/// 为PCM数据添加WAV头
/// </summary>
/// <param name="pcmData">原始PCM数据</param>
/// <param name="sampleRate">采样率</param>
/// <param name="channels">声道数</param>
/// <returns>包含WAV头的完整WAV格式数据</returns>
public byte[] AddWavHeader(byte[] pcmData, int sampleRate = 16000, int channels = 1)
{
// 1. 将字节数组转换为 WaveStream
using (var inputStream = new MemoryStream(audioData))
using (var waveStream = new RawSourceWaveStream(inputStream, new WaveFormat(16000, 16, 1)))
using (var stream = new MemoryStream())
{
// 2. 转换为浮点样本便于处理
var sampleProvider = waveStream.ToSampleProvider();
// 3. 应用改进的噪声门,使用平滑过渡
var noiseGate = new ImprovedNoiseGate(sampleProvider)
{
Threshold = noiseThreshold,
AttackSeconds = attackSeconds,
ReleaseSeconds = releaseSeconds,
HoldSeconds = 0.1f, // 添加保持时间,防止快速开关
SoftKneeDb = 6.0f // 添加软膝,使过渡更平滑
};
// 4. 应用高通滤波器去除低频噪音,使用更温和的设置
var highPassFilter = new BiQuadFilterSampleProvider(noiseGate);
highPassFilter.Filter = BiQuadFilter.HighPassFilter(
sampleProvider.WaveFormat.SampleRate,
highPassCutoff,
q);
// 5. 添加平滑处理器
var smoothedProvider = new SmoothingSampleProvider(highPassFilter, 5);
// 6. 处理后的音频转回字节数组
var outputStream = new MemoryStream();
WaveFileWriter.WriteWavFileToStream(outputStream, smoothedProvider.ToWaveProvider16());
return outputStream.ToArray();
// RIFF头
stream.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"), 0, 4);
stream.Write(BitConverter.GetBytes(pcmData.Length + 36), 0, 4);
stream.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt "), 0, 8);
// 格式块
stream.Write(BitConverter.GetBytes(16), 0, 4); // fmt块大小
stream.Write(BitConverter.GetBytes((ushort)1), 0, 2); // PCM格式
stream.Write(BitConverter.GetBytes((ushort)channels), 0, 2);
stream.Write(BitConverter.GetBytes(sampleRate), 0, 4);
stream.Write(BitConverter.GetBytes(sampleRate * channels * 2), 0, 4); // 字节率
stream.Write(BitConverter.GetBytes((ushort)(channels * 2)), 0, 2); // 块对齐
stream.Write(BitConverter.GetBytes((ushort)16), 0, 2); // 位深
// 数据块
stream.Write(System.Text.Encoding.ASCII.GetBytes("data"), 0, 4);
stream.Write(BitConverter.GetBytes(pcmData.Length), 0, 4);
stream.Write(pcmData, 0, pcmData.Length);
return stream.ToArray();
}
}

View File

@ -91,5 +91,14 @@ namespace ShengShengBuXi.Services
/// <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);
/// <summary>
/// 为PCM数据添加WAV头
/// </summary>
/// <param name="pcmData">原始PCM数据</param>
/// <param name="sampleRate">采样率</param>
/// <param name="channels">声道数</param>
/// <returns>包含WAV头的完整WAV格式数据</returns>
byte[] AddWavHeader(byte[] pcmData, int sampleRate = 16000, int channels = 1);
}
}