• WebRTC 音频采样算法 附完整C++示例代码


    之前有大概介绍了音频采样相关的思路,详情见《简洁明了的插值音频重采样算法例子 (附完整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 = '';
            }
        } else if (drv)
            *drv = '';
        for (end = path; *end && *end != ':';)
            end++;
        for (p = end; p > path && *--p != '\' && *p != '/';)
            if (*p == '.') {
                end = p;
                break;
            }
        if (ext)
            for (s = end; (*ext = *s++);)
                ext++;
        for (p = end; p > path;)
            if (*--p == '\' || *p == '/') {
                p++;
                break;
            }
        if (name) {
            for (s = p; s < end;)
                *name++ = *s++;
            *name = '';
        }
        if (dir) {
            for (s = path; s < p;)
                *dir++ = *s++;
            *dir = '';
        }
    }
    
    int16_t *resampler(int16_t *data_in, size_t totalSampleCount, size_t in_sample_rate, size_t out_sample_rate) {
        if (data_in == nullptr)
            return nullptr;
        if (in_sample_rate == 0) return nullptr;
        size_t lengthIn = in_sample_rate / 100;
        size_t maxLen = out_sample_rate / 100;
        const int channels = 1;
        Resampler rs;
        if (rs.Reset(in_sample_rate, out_sample_rate, channels) == -1)
            return nullptr;
        size_t outLen = (size_t) (totalSampleCount * out_sample_rate / in_sample_rate);
        int16_t *data_out = (int16_t *) malloc(outLen * sizeof(int16_t));
        if (data_out == nullptr) return nullptr;
        size_t nCount = (totalSampleCount / lengthIn);
        size_t nLast = totalSampleCount - (lengthIn * nCount);
        int16_t *samplesIn = data_in;
        int16_t *samplesOut = data_out;
    
        outLen = 0;
        for (int i = 0; i < nCount; i++) {
            rs.Push(samplesIn, lengthIn, samplesOut, maxLen, outLen);
            samplesIn += lengthIn;
            samplesOut += outLen;
        }
        if (nLast != 0) {
            const int max_samples = 1920;
            int16_t samplePatchIn[max_samples] = {0};
            int16_t samplePatchOut[max_samples] = {0};
            memcpy(samplePatchIn, samplesIn, nLast * sizeof(int16_t));
            rs.Push(samplesIn, nLast, samplePatchOut, maxLen, outLen);
            memcpy(samplesOut, samplePatchOut, (nLast * out_sample_rate / in_sample_rate) * sizeof(int16_t));
        }
        return data_out;
    }
    
    void ResampleTo(char *in_file, char *out_file, size_t out_sample_rate = 16000) {
        //音频采样率
        uint32_t sampleRate = 0;
        //总音频采样数
        uint64_t inSampleCount = 0;
        int16_t *inBuffer = wavRead_int16(in_file, &sampleRate, &inSampleCount);
        //如果加载成功
        if (inBuffer != nullptr) {
            int16_t *outBuffer = resampler(inBuffer, (size_t) inSampleCount, sampleRate, out_sample_rate);
            if (outBuffer != nullptr) {
                size_t outSampleCount = (size_t) (inSampleCount * (out_sample_rate * 1.0f / sampleRate));
                wavWrite_int16(out_file, outBuffer, out_sample_rate, outSampleCount);
                free(outBuffer);
            }
            free(inBuffer);
        }
    }
    
    int main(int argc, char *argv[]) {
        printf("WebRtc Resampler
    ");
        printf("博客:http://cpuimage.cnblogs.com/
    ");
        printf("音频插值重采样
    ");
        printf("支持采样率: 8k、16k、32k、48k、96k
    ");
        if (argc < 2)
            return -1;
        char *in_file = argv[1];
        char drive[3];
        char dir[256];
        char fname[256];
        char ext[256];
        char out_file[1024];
        splitpath(in_file, drive, dir, fname, ext);
        sprintf(out_file, "%s%s%s_out%s", drive, dir, fname, ext);
        ResampleTo(in_file, out_file, 64000);
        printf("按任意键退出程序 
    ");
        getchar();
        return 0;
    }

    项目地址:https://github.com/cpuimage/WebRTC_Resampler

    采样器的代码很简单,详情见resampler.cpp

    示例具体流程为: 

    加载wav(拖放wav文件到可执行文件上)->重采样->保存为_out.wav文件

    示例比较简单,用cmake即可进行编译示例代码,详情见CMakeLists.txt。

    若有其他相关问题或者需求也可以邮件联系俺探讨。

    邮箱地址是: 
    gaozhihan@vip.qq.com

  • 相关阅读:
    成都的收藏品市场
    微信小程序 如何定义全局函数?
    Linux下 安装VMware Tools工具
    小程序圆角进度条实现方法
    Excel 将换行符替换为空
    再次学习mysql优化
    Subl 命令
    时间见证着—eternal life
    大巧不工web前端设计修炼之道—笔记
    批量更新某字段内容
  • 原文地址:https://www.cnblogs.com/cpuimage/p/8886729.html
Copyright © 2020-2023  润新知