WebRTC 音頻採樣算法 附完整C++示例代碼

以前有大概介紹了音頻採樣相關的思路,詳情見《簡潔明瞭的插值音頻重採樣算法例子 (附完整C代碼)》。html

音頻方面的開源項目不少不少。git

最知名的莫過於谷歌開源的WebRTC,github

其中的音頻模塊就包含有 算法

AGC自動增益補償(Automatic Gain Control)
自動調麥克風的收音量,使與會者收到必定的音量水平,不會因發言者與麥克風的距離改變時,聲音有忽大忽小聲的缺點。函數

ANS背景噪音抑制(Automatic Noise Suppression)
探測出背景固定頻率的雜音並消除背景噪音。post

AEC是回聲消除器(Acoustic Echo Canceller)
對揚聲器信號與由它產生的多路徑回聲的相關性爲基礎,創建遠端信號的語音模型,利用它對回聲進行估計,並不斷地修改濾波器的係數,使得估計值更加逼近真實的回聲。而後,將回聲估計值從話筒的輸入信號中減去,從而達到消除回聲的目的,AEC還將話筒的輸入與揚聲器過去的值相比較,從而消除延長延遲的屢次反射的聲學回聲。根椐存儲器存放的過去的揚聲器的輸出值的多少,AEC能夠消除各類延遲的回聲。學習

在《音頻增益響度分析 ReplayGain 附完整C代碼示例》也說起到了。優化

不過本文還不是着重於這三個算法,仍是先從採樣算法來。ui

固然有興趣的小夥伴,建議去看下 WebRTC中與signal_processing_library相關的操做算法。spa

有很多優化的思路能夠學習之。

這裏也不展開了。

以前說過採樣能夠採用簡單的插值的方式進行模擬處理,在精度要求不高的狀況下。

可是如果對精度有所要求,那就另論了。

好在前人踩坑,後人走路。

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\n");
            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 = '\0';
        }
    } else if (drv)
        *drv = '\0';
    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 = '\0';
    }
    if (dir) {
        for (s = path; s < p;)
            *dir++ = *s++;
        *dir = '\0';
    }
}

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\n");
    printf("博客:http://cpuimage.cnblogs.com/\n");
    printf("音頻插值重採樣\n");
    printf("支持採樣率: 8k、16k、32k、48k、96k\n");
    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("按任意鍵退出程序 \n");
    getchar();
    return 0;
}

 

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

採樣器的代碼很簡單,詳情見resampler.cpp

 

示例具體流程爲: 

加載wav(拖放wav文件到可執行文件上)->重採樣->保存爲_out.wav文件

 

示例比較簡單,用cmake便可進行編譯示例代碼,詳情見CMakeLists.txt。

 

如有其餘相關問題或者需求也能夠郵件聯繫俺探討。

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

相關文章
相關標籤/搜索