首页 教程 Web前端 webrtc 3A移植以及实时处理

webrtc 3A移植以及实时处理

文章目录

  • 前言
  • 一、交叉编译
    • 1.Pulse Audio webrtc-audio-processing
    • 2.交叉编译
  • 二、基于alsa进行实时3A处理
    • 1.demo源码
    • 2.注意项
    • 3.效果展示
  • 总结

前言

由于工作需要,硬件3A中的AEC效果实在太差,后面使用SpeexDSP的软3A,效果依旧不是很好,猜测是内部时延估计和时延补偿做的并不是很好,于是采用了webrtc的3A算法,这里记录一下3A移植过程。

|版本声明:山河君,未经博主允许,禁止转载


一、交叉编译

1.Pulse Audio webrtc-audio-processing

在linux下,webrtc 3A是比较好移植的,原因是Pulse Audio是支持webrtc 3A插件的,也就是说,不需要我们自己翻墙下载配置webrtc的环境以及编译链路,Pulse Audio已经帮我们做好了这一步,剩下的就是交叉编译的工作。

对应的gitlab地址:Pulse Audio webrtc-audio-processing

2.交叉编译

先选择好版本,截至到当前博客时间,最新版本是1.3.1,网上之前也有介绍的,基本都是0.3版本的
webrtc 3A移植以及实时处理
1.0版本和1.0之前的版本最大的区别就是编译器的不同,之前是通过脚本配置,1.0后都是使用meson 进行配置,所以需要下载meson 以及ninja(用于编译webrtc)

meson和代码下载之后,就需要配置交叉编译链路,meson对应交叉编译器环境需要我们自己写meson配置文件,在源文件目录下,打开cross_file.txt,内容如下:

[binaries] c ='/usr/bin/aarch64-linux-gnu-gcc' cpp ='/usr/bin/aarch64-linux-gnu-g++' ar ='/usr/bin/aarch64-linux-gnu-ar' strip ='/usr/bin/aarch64-linux-gnu-strip' pkgconfig ='/usr/bin/aarch64-linux-gnu-pkg-config'[host_machine] system ='linux' cpu_family ='aarch64' cpu ='armv8-a' endian ='little'[paths] prefix ='/home/aaron/workplace/webrtc-audio-processing/build'

在源文件目录下创建编译缓存目录以及安装目录

meson . build -Dprefix=$PWD/install --cross-file=cross_file.txt ninja -C build ninja -C build install

最后在install目录下可以看到编译好的文件
webrtc 3A移植以及实时处理

二、基于alsa进行实时3A处理

1.demo源码

编译好的完整demo下载:demo下载,如果没有积分的话就自己编译,这里只是少了demo的脚本

#include<stdio.h>#include<stdlib.h>#include<alsa/asoundlib.h>#include<pthread.h>#include<errno.h>#include<mutex>#include"modules/audio_processing/include/audio_processing.h"constexprint sample_rate_hz =16000;// 假设采样率为16kHzconstexpr size_t num_channels =1;// 假设单声道constexpr size_t frame_size = sample_rate_hz /100;// 10ms 帧大小#defineCHANNELS2#defineSAMPLE_RATE16000#definePERIOD_SIZE160#defineBUFFER_SIZE(PERIOD_SIZE *2)bool gRun =true;typedefstruct{ snd_pcm_t *playback_handle; FILE *pcm_file;int8_t m_pPlayoutBuffer[PERIOD_SIZE *2*2];int m_nPlayoutBufferSizeIn20MS = PERIOD_SIZE *2*2;int m_nPlayoutFramesLeft =0; snd_pcm_t *capture_handle; FILE *pcm_out_file;int8_t m_pRecordBuffer[PERIOD_SIZE *2*2];int m_nRecordBufferSizeIn20MS = PERIOD_SIZE *2*2;int m_nRecordingFramesLeft = PERIOD_SIZE; FILE *pcm_3a_file; FILE *pcm_ref_file;int8_t m_pRefBuffer[PERIOD_SIZE *2];int8_t m_p3ABuffer[PERIOD_SIZE *2];// in byteint8_t m_pNearBuffer[PERIOD_SIZE *2];// in byte rtc::scoped_refptr<webrtc::AudioProcessing> apm;} audio_data_t;voidmonoTurnStereo(constint16_t*pSrc,int16_t*pDts, size_t size){for(int j =0; j < size; j++){ pDts[2* j]= pSrc[j]; pDts[2* j +1]= pSrc[j];}}voidstereoTurnMono(constunsignedchar*pSrc,unsignedchar*pDts, size_t size){int nLeftCount =0;for(size_t i =0; i < size; i +=4){ pDts[nLeftCount]= pSrc[i]; pDts[nLeftCount +1]= pSrc[i +1]; nLeftCount +=2;}}int32_terrorRecovery(int32_t nRet, snd_pcm_t *pDeviceHandle){int st =snd_pcm_state(pDeviceHandle);printf("Trying to recover from %s error: %s nRet:(%d) (state:%d)\n",((snd_pcm_stream(pDeviceHandle)== SND_PCM_STREAM_CAPTURE)?"capture":"playout"),snd_strerror(nRet), nRet, st);int res =snd_pcm_recover(pDeviceHandle, nRet,1);if(0== res){printf("Recovery - snd_pcm_recover OK\n");if((nRet ==-EPIPE || nRet ==-ESTRPIPE)&&snd_pcm_stream(pDeviceHandle)== SND_PCM_STREAM_CAPTURE){// For capture streams we also have to repeat the explicit start()// to get data flowing again.int nRet =snd_pcm_start(pDeviceHandle);if(nRet !=0){printf("Recovery - snd_pcm_start error: %d\n", nRet);return-1;}}if((nRet ==-EPIPE || nRet ==-ESTRPIPE)&&snd_pcm_stream(pDeviceHandle)== SND_PCM_STREAM_PLAYBACK){// For capture streams we also have to repeat the explicit start() to get// data flowing again. snd_pcm_state_t state =snd_pcm_state(pDeviceHandle);if(state != SND_PCM_STATE_PREPARED){snd_pcm_prepare(pDeviceHandle);}int nRet =snd_pcm_start(pDeviceHandle);if(nRet !=0){printf("Recovery - snd_pcm_start error: %s\n",snd_strerror(nRet));return-1;}}return-EPIPE == nRet ?1:0;}else{printf("Unrecoverable alsa stream error: %d\n", res);}return res;}void*playback_thread(void*arg){printf("playback_thread\n"); audio_data_t *data =(audio_data_t *)arg;constint policy = SCHED_FIFO;constint min_prio =sched_get_priority_min(policy);constint max_prio =sched_get_priority_max(policy); sched_param param;constint top_prio = max_prio -1;constint low_prio = min_prio +1; param.sched_priority = top_prio;pthread_setschedparam(pthread_self(), policy,&param);while(gRun){int nRet; snd_pcm_sframes_t sndFrames; snd_pcm_sframes_t sndAvailFrames; sndAvailFrames =snd_pcm_avail_update(data->playback_handle);if(sndAvailFrames <0){printf("playout snd_pcm_avail_update error: %s\n",snd_strerror(sndAvailFrames));errorRecovery(sndAvailFrames, data->playback_handle);continue;}elseif(sndAvailFrames ==0){ nRet =snd_pcm_wait(data->playback_handle,2);// if (nRet == 0)// printf("playout snd_pcm_wait timeout\n");continue;}if(data->m_nPlayoutFramesLeft <=0){ size_t frames =fread(data->m_pRefBuffer,2, PERIOD_SIZE, data->pcm_file);if(frames ==0|| frames != PERIOD_SIZE){// 文件播放完毕,重新开始fseek(data->pcm_file,0,SEEK_SET);continue;}monoTurnStereo((int16_t*)data->m_pRefBuffer,(int16_t*)data->m_pPlayoutBuffer, PERIOD_SIZE); data->m_nPlayoutFramesLeft = frames;}if((uint32_t)(sndAvailFrames)> data->m_nPlayoutFramesLeft){ sndAvailFrames =(uint32_t)data->m_nPlayoutFramesLeft;}int size =snd_pcm_frames_to_bytes(data->playback_handle, data->m_nPlayoutFramesLeft); sndFrames =snd_pcm_writei(data->playback_handle,&data->m_pPlayoutBuffer[data->m_nPlayoutBufferSizeIn20MS - size], sndAvailFrames);if(sndFrames <0){printf("playout snd_pcm_writei error: %s\n",snd_strerror(sndFrames)); data->m_nPlayoutFramesLeft =0;errorRecovery(sndFrames, data->playback_handle);continue;}else{fwrite(&data->m_pRefBuffer[PERIOD_SIZE *2- data->m_nPlayoutFramesLeft *2],1, sndFrames *2, data->pcm_ref_file); data->m_nPlayoutFramesLeft -= sndFrames;}}returnNULL;}void*capture_thread(void*arg){printf("capture_thread\n"); audio_data_t *data =(audio_data_t *)arg;constint policy = SCHED_FIFO;constint min_prio =sched_get_priority_min(policy);constint max_prio =sched_get_priority_max(policy); sched_param param;constint top_prio = max_prio -1;constint low_prio = min_prio +1; param.sched_priority = top_prio;pthread_setschedparam(pthread_self(), policy,&param);while(gRun){int nRet; snd_pcm_sframes_t sndFrames; snd_pcm_sframes_t sndAvailFrames;int8_t buffer[data->m_nRecordBufferSizeIn20MS]; sndAvailFrames =snd_pcm_avail_update(data->capture_handle);if(sndAvailFrames <0){printf("capture snd_pcm_avail_update error: %s\n",snd_strerror(sndAvailFrames));errorRecovery(sndAvailFrames, data->capture_handle);continue;}elseif(sndAvailFrames ==0){continue;}if((uint32_t)(sndAvailFrames)> data->m_nRecordingFramesLeft) sndAvailFrames = data->m_nRecordingFramesLeft; sndFrames =snd_pcm_readi(data->capture_handle, buffer, sndAvailFrames);if(sndFrames <0){printf("capture snd_pcm_readi error: %s\n",snd_strerror(sndFrames));errorRecovery(sndFrames, data->capture_handle);continue;}elseif(sndFrames >0){int nLeftSize =snd_pcm_frames_to_bytes(data->capture_handle, data->m_nRecordingFramesLeft);int size =snd_pcm_frames_to_bytes(data->capture_handle, sndFrames);memcpy(&data->m_pRecordBuffer[data->m_nRecordBufferSizeIn20MS - nLeftSize], buffer, size); data->m_nRecordingFramesLeft -= sndFrames;}if(!data->m_nRecordingFramesLeft){ data->m_nRecordingFramesLeft = PERIOD_SIZE;stereoTurnMono((unsignedchar*)data->m_pRecordBuffer,(unsignedchar*)data->m_pNearBuffer, PERIOD_SIZE *2*2);fwrite(data->m_pNearBuffer,1, PERIOD_SIZE *2, data->pcm_out_file); webrtc::StreamConfig stream_config(sample_rate_hz, num_channels);if(data->apm->ProcessReverseStream((int16_t*)data->m_pRefBuffer, stream_config, stream_config,(int16_t*)data->m_pRefBuffer)!= webrtc::AudioProcessing::kNoError){printf("ProcessReverseStream fail\n");}if(data->apm->ProcessStream((int16_t*)data->m_pNearBuffer, stream_config, stream_config,(int16_t*)data->m_p3ABuffer)!= webrtc::AudioProcessing::kNoError){printf("ProcessStream fail\n");}fwrite(data->m_p3ABuffer,1, PERIOD_SIZE *2, data->pcm_3a_file);}}returnNULL;}intsetup_pcm_device(snd_pcm_t **handle,constchar*device, snd_pcm_stream_t stream){ snd_pcm_hw_params_t *params;int err;if((err =snd_pcm_open(handle, device, stream, SND_PCM_NONBLOCK))<0){printf("无法打开 PCM 设备 %s: %s\n", device,snd_strerror(err));return err;}snd_pcm_hw_params_alloca(&params);snd_pcm_hw_params_any(*handle, params);snd_pcm_hw_params_set_access(*handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);snd_pcm_hw_params_set_format(*handle, params, SND_PCM_FORMAT_S16_LE);snd_pcm_hw_params_set_channels(*handle, params, CHANNELS);snd_pcm_hw_params_set_rate(*handle, params, SAMPLE_RATE,0);snd_pcm_hw_params_set_period_size(*handle, params, PERIOD_SIZE,0);snd_pcm_hw_params_set_buffer_size(*handle, params, PERIOD_SIZE *4);if((err =snd_pcm_hw_params(*handle, params))<0){printf("设置 PCM 参数失败: %s\n",snd_strerror(err));snd_pcm_close(*handle);return err;}return0;}intmain(int argc,char*argv[]){ audio_data_t data;if((data.pcm_file =fopen("./far.pcm","rb"))==NULL||(data.pcm_3a_file =fopen("./3a.pcm","wb"))==NULL||(data.pcm_out_file =fopen("./near.pcm","wb"))==NULL||(data.pcm_ref_file =fopen("./ref.pcm","wb"))==NULL){printf("fail to open file\n");return-1;}// 3a data.apm = webrtc::AudioProcessingBuilder().Create(); webrtc::AudioProcessing::Config config;// 噪声抑制配置 config.noise_suppression.enabled =true; config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::Level::kHigh;// 增益控制配置 config.gain_controller1.enabled =true; config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital; config.gain_controller1.target_level_dbfs =3;// 目标输出电平// 回声抑制配置 config.echo_canceller.enabled =true; config.echo_canceller.mobile_mode =false;// 如果是移动设备可以设置为 true// 语音活动检测(可选) config.voice_detection.enabled =true;// 应用配置 data.apm->ApplyConfig(config);//if(setup_pcm_device(&data.playback_handle,"hw:0,0", SND_PCM_STREAM_PLAYBACK)<0){fclose(data.pcm_file);return-1;}if(setup_pcm_device(&data.capture_handle,"hw:0,0", SND_PCM_STREAM_CAPTURE)<0){snd_pcm_close(data.playback_handle);fclose(data.pcm_file);return-1;} pthread_t play_thread, record_thread;pthread_create(&play_thread,NULL, playback_thread,&data);int nRet =snd_pcm_prepare(data.playback_handle);if(nRet <0){printf("playout snd_pcm_prepare failed (%s)\n",snd_strerror(nRet));}pthread_create(&record_thread,NULL, capture_thread,&data); nRet =snd_pcm_prepare(data.capture_handle);if(nRet <0){printf("capture snd_pcm_prepare failed:%s \n",snd_strerror(nRet));} nRet =snd_pcm_start(data.capture_handle);if(nRet <0){printf("capture snd_pcm_start err:%s\n",snd_strerror(nRet)); nRet =snd_pcm_start(data.capture_handle);if(nRet <0){printf("capture snd_pcm_start 2nd try err:%s\n",snd_strerror(nRet));returnfalse;}}getchar(); gRun =false;pthread_join(play_thread,NULL);pthread_join(record_thread,NULL);snd_pcm_close(data.playback_handle);snd_pcm_close(data.capture_handle);fclose(data.pcm_file);fclose(data.pcm_out_file);fclose(data.pcm_3a_file);fclose(data.pcm_ref_file);printf("end................... \n");return0;}

2.注意项

  1. webrtc audio processing是基于10ms为一帧进行处理的
  2. 当前版本中可以设置3A配置等级,具体3A参数调参请参考我另一篇文章音频3A一——webrtc源码3A的启用方法和具体流程
  3. 对于资源消耗,如果没有对资源特别要求,或者其他特殊情况,尽量不要追求类似于WebRtcAec_Process,WebRtcAgc_Process这种方式单独使用3A的某一个模块,而是通过audio_processing进行处理
  4. 对于时延,如果有固定时延,应该对于AEC进行设置

3.效果展示

远端参考信号
webrtc 3A移植以及实时处理
近端采集信号
webrtc 3A移植以及实时处理
回声消除后的信号
webrtc 3A移植以及实时处理


总结

webrtc不愧是音视频领域的顶尖,值得我们学习的东西太多了。实际上demo里对于设备的读写,也是从webrtc中摘录出来的。

如果对您有所帮助,请帮忙点个赞吧!

评论(0)条

提示:请勿发布广告垃圾评论,否则封号处理!!

    猜你喜欢
    【MySQL】用户管理

    【MySQL】用户管理

     服务器/数据库  3个月前  2.41k

    我们推荐使用普通用户对数据的访问。而root作为管理员可以对普通用户对应的权限进行设置和管理。如给张三和李四这样的普通用户权限设定后。就只能操作给你权限的库了。

    Cursor Rules 让开发效率变成10倍速

    Cursor Rules 让开发效率变成10倍速

     服务器/数据库  3个月前  1.4k

    在AI与编程的交汇点上,awesome-cursorrules项目犹如一座灯塔,指引着开发者们驶向更高效、更智能的编程未来。无论你是经验丰富的老手,还是刚入行的新人,这个项目都能为你的编程之旅增添一抹亮色。这些规则文件就像是你私人定制的AI助手,能够根据你的项目需求和个人偏好,精确地调教AI的行为。突然间,你会发现AI不仅能理解Next.js的最佳实践,还能自动应用TypeScript的类型检查,甚至主动提供Tailwind CSS的类名建议。探索新的应用场景,推动AI辅助编程的边界。

    探索Django 5: 从零开始,打造你的第一个Web应用

    探索Django 5: 从零开始,打造你的第一个Web应用

     服务器/数据库  3个月前  1.3k

    Django 是一个开放源代码的 Web 应用程序框架,由 Python 写成。它遵循 MVT(Model-View-Template)的设计模式,旨在帮助开发者高效地构建复杂且功能丰富的 Web 应用程序。随着每个版本的升级,Django 不断演变,提供更多功能和改进,让开发变得更加便捷。《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体,是一本从零基础到精通Django Web企业级开发技术的实战指南《Django 5 Web应用开发实战》内容以。

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

     服务器/数据库  3个月前  1.2k

    mysql_secure_installation 是 MySQL 提供的一个安全脚本,用于提高数据库服务器的安全性

    【MySQL基础篇】概述及SQL指令:DDL及DML

    【MySQL基础篇】概述及SQL指令:DDL及DML

     服务器/数据库  3个月前  559

    数据库是长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库不仅仅是数据的简单堆积,而是遵循一定的规则和模式进行组织和管理的。数据库中的数据可以包括文本、数字、图像、音频等各种类型的信息。

    Redis中的哨兵(Sentinel)

    Redis中的哨兵(Sentinel)

     服务器/数据库  3个月前  345

    ​ 上篇文章我们讲述了Redis中的主从复制(Redis分布式系统中的主从复制-CSDN博客),本篇文章针对主从复制中的问题引出Redis中的哨兵,希望本篇文章会对你有所帮助。