|
...
|
...
|
@@ -10,9 +10,9 @@ |
|
|
|
|
|
|
|
// ========== 可配置参数 ==========
|
|
|
|
#define TAIL_MS 300 // 回声尾长(毫秒)
|
|
|
|
#define FRAME_SAMPLES 640 // 每帧样本数 640个short
|
|
|
|
#define FRAME_SAMPLES 640 // 每帧样本数(640个short)
|
|
|
|
#define SAMPLE_RATE 16000 // 采样率
|
|
|
|
#define PLAYBACK_DELAY_FRAMES 1 // 播放延迟(帧数),1 表示用上一帧作为参考
|
|
|
|
#define PLAYBACK_DELAY_FRAMES 19 // 播放延迟(帧数),软件时间0.75秒约19帧
|
|
|
|
// =================================
|
|
|
|
|
|
|
|
typedef struct JZ_SpeexInfo {
|
|
...
|
...
|
@@ -26,12 +26,12 @@ typedef struct JZ_SpeexInfo { |
|
|
|
|
|
|
|
// 环形缓冲区,存储历史输出帧(即播放过的数据)
|
|
|
|
short* HistoryBuffer;
|
|
|
|
int HistorySize; // 缓冲区总长度(样本数)
|
|
|
|
int WritePos; // 下一个写入位置
|
|
|
|
int TotalWritten; // 累计写入样本数(用于判断缓冲区是否足够)
|
|
|
|
int HistorySize;
|
|
|
|
int WritePos;
|
|
|
|
int TotalWritten;
|
|
|
|
|
|
|
|
SpeexPreprocessState* DenoiseOnlyState;
|
|
|
|
int DenoiseOnlyFlag; // 是否启用独立降噪模式
|
|
|
|
int DenoiseOnlyFlag;
|
|
|
|
|
|
|
|
int Flag;
|
|
|
|
} JZ_SpeexInfo;
|
|
...
|
...
|
@@ -39,11 +39,14 @@ typedef struct JZ_SpeexInfo { |
|
|
|
static JZ_SpeexInfo g_SpeexInfo = { 0 };
|
|
|
|
|
|
|
|
// 初始化历史缓冲区
|
|
|
|
static int InitHistoryBuffer(int size_samples) {
|
|
|
|
if (g_SpeexInfo.HistoryBuffer) {
|
|
|
|
static int InitHistoryBuffer(int size_samples)
|
|
|
|
{
|
|
|
|
if (g_SpeexInfo.HistoryBuffer)
|
|
|
|
{
|
|
|
|
free(g_SpeexInfo.HistoryBuffer);
|
|
|
|
g_SpeexInfo.HistoryBuffer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_SpeexInfo.HistoryBuffer = (short*)malloc(size_samples * sizeof(short));
|
|
|
|
if (!g_SpeexInfo.HistoryBuffer) return -1;
|
|
|
|
memset(g_SpeexInfo.HistoryBuffer, 0, size_samples * sizeof(short));
|
|
...
|
...
|
@@ -54,7 +57,8 @@ static int InitHistoryBuffer(int size_samples) { |
|
|
|
}
|
|
|
|
|
|
|
|
// 写入一帧到历史缓冲区(播放过的帧)
|
|
|
|
static void WriteHistoryFrame(short* frame) {
|
|
|
|
static void WriteHistoryFrame(short* frame)
|
|
|
|
{
|
|
|
|
int fs = g_SpeexInfo.FrameSize;
|
|
|
|
int hist_size = g_SpeexInfo.HistorySize;
|
|
|
|
int write_pos = g_SpeexInfo.WritePos;
|
|
...
|
...
|
@@ -72,7 +76,8 @@ static void WriteHistoryFrame(short* frame) { |
|
|
|
}
|
|
|
|
|
|
|
|
// 从历史缓冲区读取参考帧(对齐到当前麦克风时间)
|
|
|
|
static int ReadRefFrame(short* out_ref) {
|
|
|
|
static int ReadRefFrame(short* out_ref)
|
|
|
|
{
|
|
|
|
int fs = g_SpeexInfo.FrameSize;
|
|
|
|
int hist_size = g_SpeexInfo.HistorySize;
|
|
|
|
int write_pos = g_SpeexInfo.WritePos;
|
|
...
|
...
|
@@ -122,8 +127,7 @@ T_JZsdkReturnCode Speex_Deinit() |
|
|
|
g_SpeexInfo.Flag = JZ_FLAGCODE_OFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
//降噪注销
|
|
|
|
if (g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
if (g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
{
|
|
|
|
speex_preprocess_state_destroy(g_SpeexInfo.DenoiseOnlyState);
|
|
|
|
g_SpeexInfo.DenoiseOnlyState = NULL;
|
|
...
|
...
|
@@ -133,8 +137,6 @@ T_JZsdkReturnCode Speex_Deinit() |
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
// sample_rate: 采样率(如 16000)
|
|
|
|
T_JZsdkReturnCode Speex_Init(int sample_rate)
|
|
|
|
{
|
|
|
|
if (g_SpeexInfo.Flag == JZ_FLAGCODE_ON)
|
|
...
|
...
|
@@ -153,7 +155,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
|
|
int hist_size = g_SpeexInfo.TailLen + g_SpeexInfo.PlaybackDelaySamples + frame_samples * 2;
|
|
|
|
|
|
|
|
g_SpeexInfo.EchoState = speex_echo_state_init(frame_samples, g_SpeexInfo.TailLen);
|
|
|
|
if (!g_SpeexInfo.EchoState) {
|
|
|
|
if (!g_SpeexInfo.EchoState)
|
|
|
|
{
|
|
|
|
JZSDK_LOG_DEBUG("Speex_Init: speex_echo_state_init failed\n");
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
|
|
}
|
|
...
|
...
|
@@ -169,7 +172,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
|
|
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.PreprocessState, SPEEX_PREPROCESS_SET_ECHO_STATE, g_SpeexInfo.EchoState);
|
|
|
|
|
|
|
|
if (InitHistoryBuffer(hist_size) != 0) {
|
|
|
|
if (InitHistoryBuffer(hist_size) != 0)
|
|
|
|
{
|
|
|
|
speex_echo_state_destroy(g_SpeexInfo.EchoState);
|
|
|
|
speex_preprocess_state_destroy(g_SpeexInfo.PreprocessState);
|
|
|
|
JZSDK_LOG_DEBUG("Speex_Init: history buffer allocation failed\n");
|
|
...
|
...
|
@@ -181,66 +185,66 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
|
|
sample_rate, frame_samples, g_SpeexInfo.TailLen,
|
|
|
|
g_SpeexInfo.PlaybackDelaySamples, hist_size);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************
|
|
|
|
|
|
|
|
降噪配置
|
|
|
|
|
|
|
|
|
|
|
|
*************************************/
|
|
|
|
// 独立降噪初始化
|
|
|
|
g_SpeexInfo.DenoiseOnlyState = speex_preprocess_state_init(FRAME_SAMPLES, sample_rate);
|
|
|
|
if (!g_SpeexInfo.DenoiseOnlyState) {
|
|
|
|
if (!g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
{
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 在 Speex_Init 中,创建 DenoiseOnlyState 后添加:
|
|
|
|
//语音活动检测
|
|
|
|
int vad = 0;
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_VAD, &vad);
|
|
|
|
|
|
|
|
//自动增益
|
|
|
|
int agc = 0;
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_AGC, &agc);
|
|
|
|
int denoise = 1; // 保持开启
|
|
|
|
|
|
|
|
//启用降噪
|
|
|
|
int denoise = 1;
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_DENOISE, &denoise);
|
|
|
|
// 可选:关闭降噪的自动增益补偿
|
|
|
|
int noise_suppress = 0; // 或者尝试 1,2...
|
|
|
|
|
|
|
|
int dereverb = 0; // 关闭去混响
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_DEREVERB, &dereverb);
|
|
|
|
|
|
|
|
/*
|
|
|
|
设置噪声抑制最大衰减量 越低越激进,可以为负数 -40基本没有原噪音了,但是偶尔会有一点打印机一样的噪音
|
|
|
|
-15 没什么效果
|
|
|
|
-80 也是没有原噪音,但是引入的噪音没改善
|
|
|
|
-30 没什么效果
|
|
|
|
*/
|
|
|
|
int noise_suppress = -40;
|
|
|
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noise_suppress);
|
|
|
|
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 独立降噪处理(char* 版本,原地处理)
|
|
|
|
// 独立降噪处理
|
|
|
|
T_JZsdkReturnCode Speex_DenoiseOnly_Process(short* mic, short* out)
|
|
|
|
{
|
|
|
|
if (!g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
if (!g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
{
|
|
|
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
|
|
|
|
|
|
|
speex_preprocess_run(g_SpeexInfo.DenoiseOnlyState, out);
|
|
|
|
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 处理麦克风数据(char* 版本)
|
|
|
|
// mic: 麦克风采集的原始 PCM 字节流(长度 = frame_samples * sizeof(short) = 640 字节)
|
|
|
|
// out: 处理后的干净 PCM 字节流(长度相同,可与 mic 共用内存)
|
|
|
|
// 注意:out 同时会被保存到历史缓冲区,作为下一帧的参考信号(播放数据)
|
|
|
|
// 回声消除处理(使用历史输出作为参考信号)
|
|
|
|
T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out)
|
|
|
|
{
|
|
|
|
if (g_SpeexInfo.Flag == JZ_FLAGCODE_OFF)
|
|
|
|
if (g_SpeexInfo.Flag == JZ_FLAGCODE_OFF)
|
|
|
|
{
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
short ref_frame[FRAME_SAMPLES]; // 用于存放参考帧
|
|
|
|
if (ReadRefFrame(ref_frame) != 0)
|
|
|
|
short ref_frame[FRAME_SAMPLES];
|
|
|
|
if (ReadRefFrame(ref_frame) != 0)
|
|
|
|
{
|
|
|
|
// 历史数据不足,直接拷贝输出
|
|
|
|
// 历史数据不足,直接拷贝输出并写入历史缓冲区
|
|
|
|
if (out != mic) memcpy(out, mic, g_SpeexInfo.FrameSize * sizeof(short));
|
|
|
|
WriteHistoryFrame(out);
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
...
|
...
|
@@ -249,11 +253,11 @@ T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out) |
|
|
|
// 执行回声消除
|
|
|
|
speex_echo_cancellation(g_SpeexInfo.EchoState, mic, ref_frame, out);
|
|
|
|
|
|
|
|
// 执行后处理(噪声抑制等)
|
|
|
|
speex_preprocess_run(g_SpeexInfo.PreprocessState, out);
|
|
|
|
// 后处理降噪
|
|
|
|
speex_preprocess_run(g_SpeexInfo.DenoiseOnlyState, out);
|
|
|
|
|
|
|
|
// 将处理后的帧写入历史缓冲区(因为它即将被播放)
|
|
|
|
WriteHistoryFrame(out);
|
|
|
|
// 将输出帧写入历史缓冲区(供后续帧作为参考)
|
|
|
|
//WriteHistoryFrame(out);
|
|
|
|
|
|
|
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
|
|
}
|
...
|
...
|
|