啸叫抑制(AFS)从算法仿真到工程源码实现-第八节-系统搭建
一、概述
系统分为录音模块、数据处理模块、播音模块。录音模块和播音模块使用alsa库进行读写数据。各模块为独立进程处理,模块之间使用命名管道进行数据的传输。数据处理模块我们使用基于频域的自适应滤波去啸叫算法。
二、工程实现
2.1 系统流程图
2.2 录音模块(从ALSA 读数据)
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <stdio.h>
#define OUTPUT_PATH_NAME "/tmp/node2_to_node1.tmp"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
long loops;
int rc;
int size;
unsigned int val;
int dir;
char *buffer;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
/*以录制模式打开*/
/* Open PCM device for recording (capture). */
rc = snd_pcm_open( &handle, "default", SND_PCM_STREAM_CAPTURE, 0);
if (rc < 0) {
fprintf(stderr, "unable to open pcm device");
exit(EXIT_FAILURE);
}
/*分配一个参数对象*/
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/*初始化参数对象*/
/* Fill it in with default values. */
rc = snd_pcm_hw_params_any(handle, params);
if (rc < 0) {
printf("Err\n");
}
/* Set the desired hardware parameters. */
/*交错模式*/
/* Interleaved mode */
rc = snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0) {
printf("Err\n");
}
/*PCM格式*/
/* Signed 16-bit little-endian format */
rc = snd_pcm_hw_params_set_format(handle, params,
SND_PCM_FORMAT_S16_LE);
if (rc < 0) {
printf("Err\n");
}
/*设置通道数*/
/* Two channels (stereo) */
rc = snd_pcm_hw_params_set_channels(handle, params, 1);
if (rc < 0) {
printf("Err\n");
}
/*设置采样率*/
/* 44100 bits/second sampling rate (CD quality) */
val = 16000;
rc = snd_pcm_hw_params_set_rate_near(handle, params,
&val, &dir);
if (rc < 0) {
printf("Err\n");
}
/*没周期的帧数*/
/* Set period size to 32 frames. */
frames = 128;
rc = snd_pcm_hw_params_set_period_size_near(handle,
params, &frames, &dir);
if (rc < 0) {
printf("Err\n");
}
/* Write the parameters to the driver */
rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
fprintf(stderr,
"unable to set hw parameters: %s/n",
snd_strerror(rc));
exit(1);
}
/* Use a buffer large enough to hold one period */
rc = snd_pcm_hw_params_get_period_size(params,
&frames, &dir);
if (rc < 0) {
printf("Err\n");
}
size = frames * 2 * 1; /* 2 bytes/sample, 2 channels */
buffer = (char *) malloc(size);
/* We want to loop for 5 seconds */
rc = snd_pcm_hw_params_get_period_time(params, &val, &dir);
loops = 5000000 / val;
//printf("====================:%d\n", frames);
//printf("====================\n");
printf("++++ start record!\n");
FILE *fp = fopen("bak_record1.pcm", "wb");
//mkfifo(OUTPUT_PATH_NAME, 0666);
int fout = open(OUTPUT_PATH_NAME, O_WRONLY);
while (loops > 0) {
//loops--;
rc = snd_pcm_readi(handle, buffer, frames);
if (rc == -EPIPE) {
/* EPIPE means overrun */
fprintf(stderr, "overrun occurred/n");
//把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。
snd_pcm_prepare(handle);
} else if (rc < 0) {
fprintf(stderr,
"error from read: %s/n",
snd_strerror(rc));
} else if (rc != (int)frames) {
fprintf(stderr, "short read, read %d frames/n", rc);
}
short *buf = (short*)buffer;
for (int i = 0; i < frames; i++) {
buf[i] *= atoi(argv[1]);
}
rc = fwrite(buffer, 1, size, fp);
if (rc != size) {
fprintf(stderr,
"short write: wrote %d bytes/n", rc);
break;
}
if (write(fout, buffer, size) <= 0) {
break;
}
}
//调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全
rc = snd_pcm_drain(handle);
//关闭该音频流,释放之前动态分配的缓冲区,退出
rc = snd_pcm_close(handle);
free(buffer);
fclose(fp);
close(fout);
return 0;
}
2.3 播音模块(数据写入ALSA)
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <stdio.h>
#define OUTPUT_PATH_NAME "/tmp/node1_to_node2.tmp"
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
long loops;
int rc;
int size;
snd_pcm_t *handle;
snd_pcm_hw_params_t *params;
unsigned int val;
int dir;
snd_pcm_uframes_t frames;
char *buffer;
/* Open PCM device for playback. */
rc = snd_pcm_open(&handle, "default",
SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
fprintf(stderr,
"unable to open pcm device: %s/n",
snd_strerror(rc));
exit(1);
}
/*分配一个参数对象*/
/* Allocate a hardware parameters object. */
snd_pcm_hw_params_alloca(¶ms);
/*初始化参数对象*/
/* Fill it in with default values. */
snd_pcm_hw_params_any(handle, params);
/* Set the desired hardware parameters. */
/*交错模式*/
/* Interleaved mode */
snd_pcm_hw_params_set_access(handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
/*设置PCM格式*/
/* Signed 16-bit little-endian format */
snd_pcm_hw_params_set_format(handle, params,
SND_PCM_FORMAT_S16_LE);
/*设置通道数*/
/* Two channels (stereo) */
snd_pcm_hw_params_set_channels(handle, params, 1);
/*设置采样率*/
/* 44100 bits/second sampling rate (CD quality) */
val = 16000;
snd_pcm_hw_params_set_rate_near(handle, params,
&val, &dir);
/* Set period size to 32 frames. */
frames = 128;
snd_pcm_hw_params_set_period_size_near(handle,
params, &frames, &dir);
/* Write the parameters to the driver */
rc = snd_pcm_hw_params(handle, params);
if (rc < 0) {
fprintf(stderr,
"unable to set hw parameters: %s/n",
snd_strerror(rc));
exit(1);
}
/* Use a buffer large enough to hold one period */
snd_pcm_hw_params_get_period_size(params, &frames, &dir);
size = frames * 2 * 1; /* 2 bytes/sample, 2 channels */
printf("size value:%d, frames value:%d\n", size, frames);
buffer = (char *) malloc(size);
/* We want to loop for 5 seconds */
snd_pcm_hw_params_get_period_time(params,
&val, &dir);
/* 5 seconds in microseconds divided by
* period time */
loops = 5000000 / val;
snd_pcm_prepare(handle);
printf("====start\n");
mkfifo(OUTPUT_PATH_NAME, 0666);
//FILE *fp = fopen(argv[1], "rb");
FILE *fp = fopen("bak_play1.pcm", "wb");
int fd = open(OUTPUT_PATH_NAME, O_RDONLY);
while (loops > 0) {
//loops--;
//rc = read(0, buffer, size);
//rc = fread(buffer, 1, size, fp);
rc = read(fd, buffer, size);
if (rc <= 0) {
fprintf(stderr, "end of file on input/n");
break;
} else if (rc != size) {
fprintf(stderr,
"short read: read %d != %d bytes/n", rc, size);
}
rc = fwrite(buffer, 1, size, fp);
if (rc <= 0) {
break;
}
rc = snd_pcm_writei(handle, buffer, frames);
if (rc == -EPIPE) {
/* EPIPE means underrun */
fprintf(stderr, "underrun occurred/n");
//把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。
snd_pcm_prepare(handle);
} else if (rc < 0) {
fprintf(stderr,
"error from writei: %s/n",
snd_strerror(rc));
} else if (rc != (int)frames) {
fprintf(stderr,
"short write, write %d frames/n", rc);
}
}
//调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全
snd_pcm_drain(handle);
//关闭该音频流,释放之前动态分配的缓冲区,退出
snd_pcm_close(handle);
free(buffer);
fclose(fp);
close(fd);
unlink(OUTPUT_PATH_NAME);
return 0;
}
2.4 数据处理模块
(1)时域实现(加O3)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define OUTPUT_PATH_NAME "/tmp/node1_to_node2.tmp"
#define INPUT_PATH_NAME "/tmp/node2_to_node1.tmp"
int main() {
if (access(INPUT_PATH_NAME, F_OK) != -1) {
printf("File exists.\n");
} else {
mkfifo(INPUT_PATH_NAME, 0666);
}
int fin = open(INPUT_PATH_NAME, O_RDONLY);
int fout = open(OUTPUT_PATH_NAME, O_WRONLY);
int frames = 128;
int size = frames * 2;
char *buffer = (char *) malloc(size);
int chunk_num = 5;
float mu = 0.01;
float *u = (float*) malloc(sizeof(float) * frames * chunk_num);
float *w = (float*) malloc(sizeof(float) * frames * chunk_num);
for (int i = 0; i < frames * chunk_num; i++) {
u[i] = 0;
w[i] = 0;
}
float *current_out_data = (float*) malloc(sizeof(float) * frames);
float *current_data = (float*) malloc(sizeof(float) * frames);
float *last_data = (float*) malloc(sizeof(float) * frames);
short *out_data = (short*) malloc(sizeof(short) * frames);
for (int i = 0; i < frames; i++) {
current_out_data[i] = 0;
current_data[i] = 0;
last_data[i] = 0;
out_data[i] = 0;
}
while (1) {
int rc = read(fin, buffer, size);
if (rc <= 0) {
fprintf(stderr, "end of file on input/n");
break;
} else if (rc != size) {
fprintf(stderr,
"short read: read %d bytes/n", rc);
} else {
printf("read len: %d\n", rc);
}
short *input_data = (short*)buffer;
//printf("current_data:");
for (int i = 0; i < frames; i++) {
current_data[i] = (float)(input_data[i]) / 32768.0;
//printf(" %f", current_data[i]);
}
//printf("\n");
//printf("current_out_data:");
for (int i = 0; i < frames; i++) {
memmove(u+1, u, sizeof(float)*(frames*chunk_num-1));
u[0] = last_data[i];
float uw = 0;
float uu = 1e-6;
for (int k = 0; k < frames*chunk_num; k++) {
uw += u[k]*w[k];
uu += u[k]*u[k];
}
current_out_data[i] = current_data[i] - uw;
//printf(" (%f %f %f)", current_out_data[i], uw, uu);
for (int k = 0; k < frames*chunk_num; k++) {
w[k] = w[k] + mu * current_out_data[i] * u[k] / uu;
}
}
//printf("\n");
//printf("out_data:");
for (int i = 0; i < frames; i++) {
if (current_out_data[i] > 1.0) {
current_out_data[i] = 1.0;
} else if (current_out_data[i] < -1.0) {
current_out_data[i] = -1.0;
}
out_data[i] = (short)((current_out_data[i]) * 32767.0);
//printf(" %d", out_data[i]);
}
//printf("\n");
for (int i = 0; i < frames; i++) {
last_data[i] = current_data[i];
}
rc = write(fout, (char*)out_data, size);
if (rc <= 0) {
break;
} else {
printf("write len: %d\n", rc);
}
}
close(fin);
close(fout);
free(buffer);
free(u);
free(w);
free(current_out_data);
free(current_data);
free(last_data);
free(out_data);
unlink(INPUT_PATH_NAME);
return 0;
}
(2)频域实现(使用经典的频域分块自适应滤波aec算法)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "afs-api.h"
#define NUM_FRAMES 128
#define OUTPUT_PATH_NAME "/tmp/node1_to_node2.tmp"
#define INPUT_PATH_NAME "/tmp/node2_to_node1.tmp"
int main() {
if (access(INPUT_PATH_NAME, F_OK) != -1) {
printf("File exists.\n");
} else {
mkfifo(INPUT_PATH_NAME, 0666);
}
int fin = open(INPUT_PATH_NAME, O_RDONLY);
int fout = open(OUTPUT_PATH_NAME, O_WRONLY);
int frames = NUM_FRAMES;
int size = frames * 2;
char *buffer = (char *) malloc(size);
float *in_data_f = (float*) malloc(sizeof(float) * frames);
float *out_data_f = (float*) malloc(sizeof(float) * frames);
float *ref_data_f = (float*) malloc(sizeof(float) * frames);
short *out_data_s = (short*) malloc(sizeof(short) * frames);
for (int i = 0; i < frames; i++) {
in_data_f[i] = 0;
out_data_f[i] = 0;
ref_data_f[i] = 0;
out_data_s[i] = 0;
}
void *handle = api_create_afs_handle(NUM_FRAMES, 16000);
api_start_afs(handle);
while (1) {
int rc = read(fin, buffer, size);
if (rc <= 0) {
fprintf(stderr, "end of file on input/n");
break;
} else if (rc != size) {
fprintf(stderr,
"short read: read %d bytes/n", rc);
} else {
printf("read len: %d\n", rc);
}
short *input_data = (short*)buffer;
for (int i = 0; i < frames; i++) {
in_data_f[i] = (float)(input_data[i]);
}
api_process_afs(handle, in_data_f, ref_data_f, out_data_f, NUM_FRAMES);
for (int i = 0; i < frames; i++) {
out_data_s[i] = (short)((out_data_f[i])*1);
if (out_data_s[i] > 32000) {
out_data_s[i] = 32000;
} else if (out_data_s[i] < -32000) {
out_data_s[i] = -32000;
}
}
for (int i = 0; i < frames; i++) {
ref_data_f[i] = out_data_f[i];
}
rc = write(fout, (char*)out_data_s, size);
if (rc <= 0) {
break;
} else {
printf("write len: %d\n", rc);
}
}
close(fin);
close(fout);
free(buffer);
api_end_afs(handle);
api_destroy_afs_handle(handle);
free(in_data_f);
free(out_data_f);
free(ref_data_f);
free(out_data_s);
unlink(INPUT_PATH_NAME);
return 0;
}
2.5 效果
三、总结
本节搭建了一个完整的扩声系统,解决了啸叫问题,如大家还有什么疑问,可以留言或私信讨论,由于篇幅限制,完整代码可以进入https://t.zsxq.com/qgmoN 获取。