当前位置: 首页 > article >正文

啸叫抑制(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(&params);
    /*初始化参数对象*/
    /* 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(&params);

    /*初始化参数对象*/
    /* 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 获取。


http://www.kler.cn/a/610795.html

相关文章:

  • React 组件之间的通信
  • Sublime全局搜索快捷键Ctrl+Shift+F不能使用解决
  • 计算机二级WPS Office第六套WPS演示
  • 字典树与01trie
  • 靶场(十八)---小白心得思路分享---shenzi
  • 26考研——栈、队列和数组_队列(3)
  • Elasticsearch7.X建模各属性文档
  • Ubuntu 24.04 LTS磁盘空间不足解决
  • Python爬虫:Feapder 的详细使用和案例
  • WRC世界机器人大会-2024年展商汇总
  • 解决PHP内存溢出问题的讨论和分析
  • LINUX基础IO [六] - 文件理解与操作
  • 第三天 开始Unity Shader的学习之旅之第二天的补充
  • 顺序表(C语言源码详解,附加测试代码)
  • el-table + el-pagination 前端实现分页操作
  • 16-CSS3新增选择器
  • SpringCloud 面试备战指南
  • Linux dma的使用与理解
  • [学成在线]07-视频转码
  • 极光优化PLO-Transformer-LSTM多变量时序