|
@@ -10,9 +10,9 @@ |
|
@@ -10,9 +10,9 @@ |
|
10
|
|
10
|
|
|
11
|
// ========== 可配置参数 ==========
|
11
|
// ========== 可配置参数 ==========
|
|
12
|
#define TAIL_MS 300 // 回声尾长(毫秒)
|
12
|
#define TAIL_MS 300 // 回声尾长(毫秒)
|
|
13
|
-#define FRAME_SAMPLES 640 // 每帧样本数 640个short
|
13
|
+#define FRAME_SAMPLES 640 // 每帧样本数(640个short)
|
|
14
|
#define SAMPLE_RATE 16000 // 采样率
|
14
|
#define SAMPLE_RATE 16000 // 采样率
|
|
15
|
-#define PLAYBACK_DELAY_FRAMES 1 // 播放延迟(帧数),1 表示用上一帧作为参考
|
15
|
+#define PLAYBACK_DELAY_FRAMES 19 // 播放延迟(帧数),软件时间0.75秒约19帧
|
|
16
|
// =================================
|
16
|
// =================================
|
|
17
|
|
17
|
|
|
18
|
typedef struct JZ_SpeexInfo {
|
18
|
typedef struct JZ_SpeexInfo {
|
|
@@ -26,12 +26,12 @@ typedef struct JZ_SpeexInfo { |
|
@@ -26,12 +26,12 @@ typedef struct JZ_SpeexInfo { |
|
26
|
|
26
|
|
|
27
|
// 环形缓冲区,存储历史输出帧(即播放过的数据)
|
27
|
// 环形缓冲区,存储历史输出帧(即播放过的数据)
|
|
28
|
short* HistoryBuffer;
|
28
|
short* HistoryBuffer;
|
|
29
|
- int HistorySize; // 缓冲区总长度(样本数)
|
|
|
|
30
|
- int WritePos; // 下一个写入位置
|
|
|
|
31
|
- int TotalWritten; // 累计写入样本数(用于判断缓冲区是否足够)
|
29
|
+ int HistorySize;
|
|
|
|
30
|
+ int WritePos;
|
|
|
|
31
|
+ int TotalWritten;
|
|
32
|
|
32
|
|
|
33
|
SpeexPreprocessState* DenoiseOnlyState;
|
33
|
SpeexPreprocessState* DenoiseOnlyState;
|
|
34
|
- int DenoiseOnlyFlag; // 是否启用独立降噪模式
|
34
|
+ int DenoiseOnlyFlag;
|
|
35
|
|
35
|
|
|
36
|
int Flag;
|
36
|
int Flag;
|
|
37
|
} JZ_SpeexInfo;
|
37
|
} JZ_SpeexInfo;
|
|
@@ -39,11 +39,14 @@ typedef struct JZ_SpeexInfo { |
|
@@ -39,11 +39,14 @@ typedef struct JZ_SpeexInfo { |
|
39
|
static JZ_SpeexInfo g_SpeexInfo = { 0 };
|
39
|
static JZ_SpeexInfo g_SpeexInfo = { 0 };
|
|
40
|
|
40
|
|
|
41
|
// 初始化历史缓冲区
|
41
|
// 初始化历史缓冲区
|
|
42
|
-static int InitHistoryBuffer(int size_samples) {
|
|
|
|
43
|
- if (g_SpeexInfo.HistoryBuffer) {
|
42
|
+static int InitHistoryBuffer(int size_samples)
|
|
|
|
43
|
+{
|
|
|
|
44
|
+ if (g_SpeexInfo.HistoryBuffer)
|
|
|
|
45
|
+ {
|
|
44
|
free(g_SpeexInfo.HistoryBuffer);
|
46
|
free(g_SpeexInfo.HistoryBuffer);
|
|
45
|
g_SpeexInfo.HistoryBuffer = NULL;
|
47
|
g_SpeexInfo.HistoryBuffer = NULL;
|
|
46
|
}
|
48
|
}
|
|
|
|
49
|
+
|
|
47
|
g_SpeexInfo.HistoryBuffer = (short*)malloc(size_samples * sizeof(short));
|
50
|
g_SpeexInfo.HistoryBuffer = (short*)malloc(size_samples * sizeof(short));
|
|
48
|
if (!g_SpeexInfo.HistoryBuffer) return -1;
|
51
|
if (!g_SpeexInfo.HistoryBuffer) return -1;
|
|
49
|
memset(g_SpeexInfo.HistoryBuffer, 0, size_samples * sizeof(short));
|
52
|
memset(g_SpeexInfo.HistoryBuffer, 0, size_samples * sizeof(short));
|
|
@@ -54,7 +57,8 @@ static int InitHistoryBuffer(int size_samples) { |
|
@@ -54,7 +57,8 @@ static int InitHistoryBuffer(int size_samples) { |
|
54
|
}
|
57
|
}
|
|
55
|
|
58
|
|
|
56
|
// 写入一帧到历史缓冲区(播放过的帧)
|
59
|
// 写入一帧到历史缓冲区(播放过的帧)
|
|
57
|
-static void WriteHistoryFrame(short* frame) {
|
60
|
+static void WriteHistoryFrame(short* frame)
|
|
|
|
61
|
+{
|
|
58
|
int fs = g_SpeexInfo.FrameSize;
|
62
|
int fs = g_SpeexInfo.FrameSize;
|
|
59
|
int hist_size = g_SpeexInfo.HistorySize;
|
63
|
int hist_size = g_SpeexInfo.HistorySize;
|
|
60
|
int write_pos = g_SpeexInfo.WritePos;
|
64
|
int write_pos = g_SpeexInfo.WritePos;
|
|
@@ -72,7 +76,8 @@ static void WriteHistoryFrame(short* frame) { |
|
@@ -72,7 +76,8 @@ static void WriteHistoryFrame(short* frame) { |
|
72
|
}
|
76
|
}
|
|
73
|
|
77
|
|
|
74
|
// 从历史缓冲区读取参考帧(对齐到当前麦克风时间)
|
78
|
// 从历史缓冲区读取参考帧(对齐到当前麦克风时间)
|
|
75
|
-static int ReadRefFrame(short* out_ref) {
|
79
|
+static int ReadRefFrame(short* out_ref)
|
|
|
|
80
|
+{
|
|
76
|
int fs = g_SpeexInfo.FrameSize;
|
81
|
int fs = g_SpeexInfo.FrameSize;
|
|
77
|
int hist_size = g_SpeexInfo.HistorySize;
|
82
|
int hist_size = g_SpeexInfo.HistorySize;
|
|
78
|
int write_pos = g_SpeexInfo.WritePos;
|
83
|
int write_pos = g_SpeexInfo.WritePos;
|
|
@@ -122,8 +127,7 @@ T_JZsdkReturnCode Speex_Deinit() |
|
@@ -122,8 +127,7 @@ T_JZsdkReturnCode Speex_Deinit() |
|
122
|
g_SpeexInfo.Flag = JZ_FLAGCODE_OFF;
|
127
|
g_SpeexInfo.Flag = JZ_FLAGCODE_OFF;
|
|
123
|
}
|
128
|
}
|
|
124
|
|
129
|
|
|
125
|
- //降噪注销
|
|
|
|
126
|
- if (g_SpeexInfo.DenoiseOnlyState)
|
130
|
+ if (g_SpeexInfo.DenoiseOnlyState)
|
|
127
|
{
|
131
|
{
|
|
128
|
speex_preprocess_state_destroy(g_SpeexInfo.DenoiseOnlyState);
|
132
|
speex_preprocess_state_destroy(g_SpeexInfo.DenoiseOnlyState);
|
|
129
|
g_SpeexInfo.DenoiseOnlyState = NULL;
|
133
|
g_SpeexInfo.DenoiseOnlyState = NULL;
|
|
@@ -133,8 +137,6 @@ T_JZsdkReturnCode Speex_Deinit() |
|
@@ -133,8 +137,6 @@ T_JZsdkReturnCode Speex_Deinit() |
|
133
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
137
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
134
|
}
|
138
|
}
|
|
135
|
|
139
|
|
|
136
|
-// 初始化
|
|
|
|
137
|
-// sample_rate: 采样率(如 16000)
|
|
|
|
138
|
T_JZsdkReturnCode Speex_Init(int sample_rate)
|
140
|
T_JZsdkReturnCode Speex_Init(int sample_rate)
|
|
139
|
{
|
141
|
{
|
|
140
|
if (g_SpeexInfo.Flag == JZ_FLAGCODE_ON)
|
142
|
if (g_SpeexInfo.Flag == JZ_FLAGCODE_ON)
|
|
@@ -153,7 +155,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
@@ -153,7 +155,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
153
|
int hist_size = g_SpeexInfo.TailLen + g_SpeexInfo.PlaybackDelaySamples + frame_samples * 2;
|
155
|
int hist_size = g_SpeexInfo.TailLen + g_SpeexInfo.PlaybackDelaySamples + frame_samples * 2;
|
|
154
|
|
156
|
|
|
155
|
g_SpeexInfo.EchoState = speex_echo_state_init(frame_samples, g_SpeexInfo.TailLen);
|
157
|
g_SpeexInfo.EchoState = speex_echo_state_init(frame_samples, g_SpeexInfo.TailLen);
|
|
156
|
- if (!g_SpeexInfo.EchoState) {
|
158
|
+ if (!g_SpeexInfo.EchoState)
|
|
|
|
159
|
+ {
|
|
157
|
JZSDK_LOG_DEBUG("Speex_Init: speex_echo_state_init failed\n");
|
160
|
JZSDK_LOG_DEBUG("Speex_Init: speex_echo_state_init failed\n");
|
|
158
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
161
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
159
|
}
|
162
|
}
|
|
@@ -169,7 +172,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
@@ -169,7 +172,8 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
169
|
|
172
|
|
|
170
|
speex_preprocess_ctl(g_SpeexInfo.PreprocessState, SPEEX_PREPROCESS_SET_ECHO_STATE, g_SpeexInfo.EchoState);
|
173
|
speex_preprocess_ctl(g_SpeexInfo.PreprocessState, SPEEX_PREPROCESS_SET_ECHO_STATE, g_SpeexInfo.EchoState);
|
|
171
|
|
174
|
|
|
172
|
- if (InitHistoryBuffer(hist_size) != 0) {
|
175
|
+ if (InitHistoryBuffer(hist_size) != 0)
|
|
|
|
176
|
+ {
|
|
173
|
speex_echo_state_destroy(g_SpeexInfo.EchoState);
|
177
|
speex_echo_state_destroy(g_SpeexInfo.EchoState);
|
|
174
|
speex_preprocess_state_destroy(g_SpeexInfo.PreprocessState);
|
178
|
speex_preprocess_state_destroy(g_SpeexInfo.PreprocessState);
|
|
175
|
JZSDK_LOG_DEBUG("Speex_Init: history buffer allocation failed\n");
|
179
|
JZSDK_LOG_DEBUG("Speex_Init: history buffer allocation failed\n");
|
|
@@ -181,66 +185,66 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
@@ -181,66 +185,66 @@ T_JZsdkReturnCode Speex_Init(int sample_rate) |
|
181
|
sample_rate, frame_samples, g_SpeexInfo.TailLen,
|
185
|
sample_rate, frame_samples, g_SpeexInfo.TailLen,
|
|
182
|
g_SpeexInfo.PlaybackDelaySamples, hist_size);
|
186
|
g_SpeexInfo.PlaybackDelaySamples, hist_size);
|
|
183
|
|
187
|
|
|
184
|
-
|
|
|
|
185
|
-
|
|
|
|
186
|
-
|
|
|
|
187
|
- /***********************************
|
|
|
|
188
|
-
|
|
|
|
189
|
- 降噪配置
|
|
|
|
190
|
-
|
|
|
|
191
|
-
|
|
|
|
192
|
- *************************************/
|
188
|
+ // 独立降噪初始化
|
|
193
|
g_SpeexInfo.DenoiseOnlyState = speex_preprocess_state_init(FRAME_SAMPLES, sample_rate);
|
189
|
g_SpeexInfo.DenoiseOnlyState = speex_preprocess_state_init(FRAME_SAMPLES, sample_rate);
|
|
194
|
- if (!g_SpeexInfo.DenoiseOnlyState) {
|
190
|
+ if (!g_SpeexInfo.DenoiseOnlyState)
|
|
|
|
191
|
+ {
|
|
195
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
192
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
196
|
}
|
193
|
}
|
|
197
|
|
194
|
|
|
198
|
- // 在 Speex_Init 中,创建 DenoiseOnlyState 后添加:
|
195
|
+ //语音活动检测
|
|
199
|
int vad = 0;
|
196
|
int vad = 0;
|
|
200
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_VAD, &vad);
|
197
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_VAD, &vad);
|
|
|
|
198
|
+
|
|
|
|
199
|
+ //自动增益
|
|
201
|
int agc = 0;
|
200
|
int agc = 0;
|
|
202
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_AGC, &agc);
|
201
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_AGC, &agc);
|
|
203
|
- int denoise = 1; // 保持开启
|
202
|
+
|
|
|
|
203
|
+ //启用降噪
|
|
|
|
204
|
+ int denoise = 1;
|
|
204
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_DENOISE, &denoise);
|
205
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_DENOISE, &denoise);
|
|
205
|
- // 可选:关闭降噪的自动增益补偿
|
|
|
|
206
|
- int noise_suppress = 0; // 或者尝试 1,2...
|
206
|
+
|
|
|
|
207
|
+ int dereverb = 0; // 关闭去混响
|
|
|
|
208
|
+ speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_DEREVERB, &dereverb);
|
|
|
|
209
|
+
|
|
|
|
210
|
+ /*
|
|
|
|
211
|
+ 设置噪声抑制最大衰减量 越低越激进,可以为负数 -40基本没有原噪音了,但是偶尔会有一点打印机一样的噪音
|
|
|
|
212
|
+ -15 没什么效果
|
|
|
|
213
|
+ -80 也是没有原噪音,但是引入的噪音没改善
|
|
|
|
214
|
+ -30 没什么效果
|
|
|
|
215
|
+ */
|
|
|
|
216
|
+ int noise_suppress = -40;
|
|
207
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noise_suppress);
|
217
|
speex_preprocess_ctl(g_SpeexInfo.DenoiseOnlyState, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noise_suppress);
|
|
208
|
|
218
|
|
|
209
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
219
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
210
|
}
|
220
|
}
|
|
211
|
|
221
|
|
|
212
|
-
|
|
|
|
213
|
-// 独立降噪处理(char* 版本,原地处理)
|
222
|
+// 独立降噪处理
|
|
214
|
T_JZsdkReturnCode Speex_DenoiseOnly_Process(short* mic, short* out)
|
223
|
T_JZsdkReturnCode Speex_DenoiseOnly_Process(short* mic, short* out)
|
|
215
|
{
|
224
|
{
|
|
216
|
- if (!g_SpeexInfo.DenoiseOnlyState)
|
225
|
+ if (!g_SpeexInfo.DenoiseOnlyState)
|
|
217
|
{
|
226
|
{
|
|
218
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
227
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
|
219
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
228
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
220
|
}
|
229
|
}
|
|
221
|
|
230
|
|
|
222
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
231
|
if (out != mic) memcpy(out, mic, FRAME_SAMPLES * sizeof(short));
|
|
223
|
-
|
|
|
|
224
|
speex_preprocess_run(g_SpeexInfo.DenoiseOnlyState, out);
|
232
|
speex_preprocess_run(g_SpeexInfo.DenoiseOnlyState, out);
|
|
225
|
-
|
|
|
|
226
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
233
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
227
|
}
|
234
|
}
|
|
228
|
|
235
|
|
|
229
|
-// 处理麦克风数据(char* 版本)
|
|
|
|
230
|
-// mic: 麦克风采集的原始 PCM 字节流(长度 = frame_samples * sizeof(short) = 640 字节)
|
|
|
|
231
|
-// out: 处理后的干净 PCM 字节流(长度相同,可与 mic 共用内存)
|
|
|
|
232
|
-// 注意:out 同时会被保存到历史缓冲区,作为下一帧的参考信号(播放数据)
|
236
|
+// 回声消除处理(使用历史输出作为参考信号)
|
|
233
|
T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out)
|
237
|
T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out)
|
|
234
|
{
|
238
|
{
|
|
235
|
- if (g_SpeexInfo.Flag == JZ_FLAGCODE_OFF)
|
239
|
+ if (g_SpeexInfo.Flag == JZ_FLAGCODE_OFF)
|
|
236
|
{
|
240
|
{
|
|
237
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
241
|
return JZ_ERROR_SYSTEM_MODULE_CODE_FAILURE;
|
|
238
|
}
|
242
|
}
|
|
239
|
|
243
|
|
|
240
|
- short ref_frame[FRAME_SAMPLES]; // 用于存放参考帧
|
|
|
|
241
|
- if (ReadRefFrame(ref_frame) != 0)
|
244
|
+ short ref_frame[FRAME_SAMPLES];
|
|
|
|
245
|
+ if (ReadRefFrame(ref_frame) != 0)
|
|
242
|
{
|
246
|
{
|
|
243
|
- // 历史数据不足,直接拷贝输出
|
247
|
+ // 历史数据不足,直接拷贝输出并写入历史缓冲区
|
|
244
|
if (out != mic) memcpy(out, mic, g_SpeexInfo.FrameSize * sizeof(short));
|
248
|
if (out != mic) memcpy(out, mic, g_SpeexInfo.FrameSize * sizeof(short));
|
|
245
|
WriteHistoryFrame(out);
|
249
|
WriteHistoryFrame(out);
|
|
246
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
250
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
@@ -249,11 +253,11 @@ T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out) |
|
@@ -249,11 +253,11 @@ T_JZsdkReturnCode Speex_ProcessMic(short* mic, short* out) |
|
249
|
// 执行回声消除
|
253
|
// 执行回声消除
|
|
250
|
speex_echo_cancellation(g_SpeexInfo.EchoState, mic, ref_frame, out);
|
254
|
speex_echo_cancellation(g_SpeexInfo.EchoState, mic, ref_frame, out);
|
|
251
|
|
255
|
|
|
252
|
- // 执行后处理(噪声抑制等)
|
|
|
|
253
|
- speex_preprocess_run(g_SpeexInfo.PreprocessState, out);
|
256
|
+ // 后处理降噪
|
|
|
|
257
|
+ speex_preprocess_run(g_SpeexInfo.DenoiseOnlyState, out);
|
|
254
|
|
258
|
|
|
255
|
- // 将处理后的帧写入历史缓冲区(因为它即将被播放)
|
|
|
|
256
|
- WriteHistoryFrame(out);
|
259
|
+ // 将输出帧写入历史缓冲区(供后续帧作为参考)
|
|
|
|
260
|
+ //WriteHistoryFrame(out);
|
|
257
|
|
261
|
|
|
258
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
262
|
return JZ_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
|
|
259
|
}
|
263
|
}
|