一、概述
系统分为录音模块、数据处理模块、播音模块。录音模块和播音模块使用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 获取。