之前有大概介绍了音频采样相关的思路,详情见《简洁明了的插值音频重采样算法例子 (附完整C代码)》。
音频方面的开源项目很多很多。
最知名的莫过于谷歌开源的WebRTC,
其中的音频模块就包含有
AGC自动增益补偿(Automatic Gain Control)
自动调麦克风的收音量,使与会者收到一定的音量水平,不会因发言者与麦克风的距离改变时,声音有忽大忽小声的缺点。
ANS背景噪音抑制(Automatic Noise Suppression)
探测出背景固定频率的杂音并消除背景噪音。
AEC是回声消除器(Acoustic Echo Canceller)
对扬声器信号与由它产生的多路径回声的相关性为基础,建立远端信号的语音模型,利用它对回声进行估计,并不断地修改滤波器的系数,使得估计值更加逼近真实的回声。然后,将回声估计值从话筒的输入信号中减去,从而达到消除回声的目的,AEC还将话筒的输入与扬声器过去的值相比较,从而消除延长延迟的多次反射的声学回声。根椐存储器存放的过去的扬声器的输出值的多少,AEC可以消除各种延迟的回声。
在《音频增益响度分析 ReplayGain 附完整C代码示例》也提及到了。
不过本文还不是着重于这三个算法,还是先从采样算法来。
当然有兴趣的小伙伴,建议去看下 WebRTC中与signal_processing_library相关的操作算法。
有不少优化的思路可以学习之。
这里也不展开了。
之前说过采样可以采用简单的插值的方式进行模拟处理,在精度要求不高的情况下。
但是若是对精度有所要求,那就另论了。
好在前人踩坑,后人走路。
WebRTC中有一个音频采样器的类,虽然有一定的使用限制,但是在大多数应用场景下,也够用了。
WebRTC的代码是很干净,奈何,各个头文件之间的依赖,实在混乱。
不过稍微耐心,还是能把代码理出个七七八八。
稍微花了时间,造福下大家。
将WebRTC中的采样器代码单独抽离出来,
并编写了C++示例代码。
完整示例代码:
#include <cstdio> #include <cstdlib> #include <cstdint> //采用https://github.com/mackron/dr_libs/blob/master/dr_wav.h 解码 #define DR_WAV_IMPLEMENTATION #include "dr_wav.h" #include "resampler.h" //写wav文件 void wavWrite_int16(char *filename, int16_t *buffer, size_t sampleRate, size_t totalSampleCount) { drwav_data_format format = {}; format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. format.channels = 1; format.sampleRate = (drwav_uint32) sampleRate; format.bitsPerSample = 16; drwav *pWav = drwav_open_file_write(filename, &format); if (pWav) { drwav_uint64 samplesWritten = drwav_write(pWav, totalSampleCount, buffer); drwav_uninit(pWav); if (samplesWritten != totalSampleCount) { fprintf(stderr, "ERROR "); exit(1); } } } //读取wav文件 int16_t *wavRead_int16(char *filename, uint32_t *sampleRate, uint64_t *totalSampleCount) { unsigned int channels; int16_t *buffer = drwav_open_and_read_file_s16(filename, &channels, sampleRate, totalSampleCount); if (buffer == nullptr) { printf("读取wav文件失败."); } //仅仅处理单通道音频 if (channels != 1) { drwav_free(buffer); buffer = nullptr; *sampleRate = 0; *totalSampleCount = 0; } return buffer; } //分割路径函数 void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) { const char *end; const char *p; const char *s; if (path[0] && path[1] == ':') { if (drv) { *drv++ = *path++; *drv++ = *path++; *drv = '