大話音頻變聲原理 附簡單示例代碼

關於音頻變聲算法,這個是一個不少人特別感興趣的話題。html

固然也有很多開源算法能夠參閱學習,有基於時域,也有基於頻域的算法。git

最終算法想要達到的目的是一致。github

最近也有很多網友問過關於變聲算法的一些細節問題,郵件詢問我。算法

要給出一個比較合理或者說通俗易懂的解釋,看似簡單,其實還蠻難的。函數

按照大概的一個邏輯思路,稍微理一理,因此這個主題必須加上「大話」這個前綴。學習

也不打算講特別高深的,固然也是由於講不來。ui

之於圖像算法領域,很是重要的算法是高斯模糊,spa

固然也能夠認爲是卷積,高斯模糊是卷積的一種特例,這裏就不展開了。code

而之於音頻,也許你也猜到了,基於時間的,毫無疑問,就是重採樣算法。htm

音頻採樣率是指錄音設備在一秒鐘內對聲音信號的採樣次數,

採樣頻率越高聲音的還原就越真實越天然。

在當今的主流採集卡上,採樣頻率通常共分爲22.05KHz、44.1KHz、48KHz三個等級,

22.05KHz只能達到FM廣播的聲音品質,

44.1KHz則是理論上的CD音質界限,48KHz則更加精確一些。

看到這裏,也許大多數人仍是無法理解採樣頻率大概是什麼意思。

換個角度來講的話,就是假設一我的說「你好」,花了20毫秒,而機器在這20毫秒內,

採集的數據多少就能夠理解爲採樣率高低。

也就是說,20毫秒內,採集到的數據量就是能夠大概認爲目前的採樣率,數據量越大,精度越高,採樣率越高。

那麼,咱們再換一個思路,想一個問題。

若是在一樣的速率的狀況下,

一我的的語速快,一我的的語速慢,那也可能形成採樣數據分佈不一致。

這裏就能夠展開一個音頻算法,就是變速。

嗯,是的,就是變速。

從原理上來說的話,其實變速就是在一樣的採樣率環境下,對採樣數據進行拉伸或壓縮。

從算法的角度上來講的話,能夠認爲是插值或抽值。

若是你讓一我的講話的速度變得更快怎麼作,

很明顯,就是在一樣的採樣率下,抽掉一些樣本。

反之,降速則是插入一些樣本。

最終決定變速效果的就是插入樣本和抽離樣本的權重計算。

例如原來採樣到的數據是

1234

加速的時候,抽離樣本 1 和 4

23

降速的時候,增長樣本 

11223344

固然只是舉個例子,便於你們理解這個概念邏輯。

看到這裏,確定有人會問,

那聲音的大小呢?或者說信號的強弱呢?

其實也就是提高音量和下降音量,我想這個應該不用解釋。

變速是時域變,空間不變。

而音量則反之,時域不變,空間變。

能夠簡單粗暴地理解,就是線性拉伸。

例如原來採樣到的數據是

1234

每一個樣本+4,直接拉伸爲

5678

也有采用乘法進行拉伸的,

例如 乘以2

2468

上面是增大音量,下降音量反之就是減和除。

而最終無論變速仍是音量調節,

最終算法要作的事情就是肯定對應位置的對應權重。

固然也要看最終想要達到什麼樣的效果,適配權重。

饒了這麼一大圈,仍是沒有說到變聲的問題。

其實,變聲就是變速+音量調節。

以上變速也好,音量調節也好,相對而言都是線性拉伸,

直接的加減乘除而後插值抽值就能達到的。

而變聲的概念其實也是相似的,

就是在在同一時域內同時調節對應時域的音量權重。

換言之就是在同一個採樣率內,同時控制語速和音量在一個特定的權重內。

其實就是一個時域和空間的二維拉伸。

理解這個邏輯確實有點繞。

用採樣算法來作一個簡單的示例。

參閱前面的文章《簡潔明瞭的插值音頻重採樣算法例子 (附完整C代碼)》

這個示例中的採樣函數是:

void resampler(char *in_file, char *out_file) {
    //音頻採樣率
    uint32_t in_sampleRate = 0;
    //總音頻採樣數
    uint64_t totalSampleCount = 0;
    int16_t *data_in = wavRead_int16(in_file, &in_sampleRate, &totalSampleCount);
    uint32_t out_sampleRate = in_sampleRate * 2;
    uint32_t out_size = (uint32_t) (totalSampleCount * ((float) out_sampleRate / in_sampleRate));
    int16_t *data_out = (int16_t *) malloc(out_size * sizeof(int16_t));
    //若是加載成功
    if (data_in != NULL && data_out != NULL) {
        resampleData(data_in, in_sampleRate, (uint32_t) totalSampleCount, data_out, out_sampleRate);
        wavWrite_int16(out_file, data_out, out_sampleRate, (uint32_t) out_size);
        free(data_in);
        free(data_out);
    } else {
        if (data_in) free(data_in);
        if (data_out) free(data_out);
    }
}

讓咱們稍微變通一下,設一個採樣速率,用來調節聲音的速度,同時保證採樣率不變。

void resampler(char *in_file, char *out_file) {
    //音頻採樣率
    uint32_t in_sampleRate = 0;
    //總音頻採樣數
    uint64_t totalSampleCount = 0;
    int16_t *data_in = wavRead_int16(in_file, &in_sampleRate, &totalSampleCount);
    float speed = 0.88;//增長一個速度權重
    uint32_t out_sampleRate = in_sampleRate * speed;
    uint32_t out_size = (uint32_t) (totalSampleCount * ((float) out_sampleRate / in_sampleRate));
    int16_t *data_out = (int16_t *) malloc(out_size * sizeof(int16_t));
    //若是加載成功
    if (data_in != NULL && data_out != NULL) {
        resampleData(data_in, in_sampleRate, (uint32_t) totalSampleCount, data_out, out_sampleRate);
        //out_sampleRate改成輸出同樣的採樣率in_sampleRate
        wavWrite_int16(out_file, data_out, in_sampleRate, (uint32_t) out_size);
        free(data_in);
        free(data_out);
    } else {
        if (data_in) free(data_in);
        if (data_out) free(data_out);
    }
}

修改後是這個樣子的。

有心的朋友發現了。out_size數值有可能增大或縮小了。

以上示例代碼,就是一個簡單的變速算法。

變速就是這麼一個原理,音量增大下降就不作示例了。

而變聲是一個什麼算法呢?

說白了,就是變速的同時保證out_size仍是原來的totalSampleCount。

那要怎麼保證呢?

答案就是插值,若是簡單粗暴一點,補0或者刪0便可。

固然這樣作的話,可能會致使音量不一致,最終發聲不對的狀況。

這確定是不科學的,最終的插值時候的權重和對應的內容,產生的效果就看各家本領了。

以上原理,也說得差很少了,具體怎麼實現的話,

你們自行參閱相關的開源代碼,再去理解一下。

另外說一下前面《聲音變調算法PitchShift(模擬湯姆貓) 附完整C++算法實現代碼》

這篇文章中的sin和cos 沒有在有效區間內,因此fastsin fastcos計算的結果是有問題的。

詳情你們仍是參閱做者原算法吧。

固然,後面有時間我會放出,

簡單清晰的變聲算法的完整c代碼和對應的示例代碼。

而關於基於傅里葉變換的重採樣算法,《基於傅里葉變換的音頻重採樣算法 (附完整c代碼)》

在對應的github 項目fftResample上,我也作了算法邏輯上的修正。

發表過的文章通常不多進行二次編輯了,

關於後期的一些修正和變動,你們仍是關注一下github項目的更新比較直接一點。

具體變聲的實現原理,

如上所述,但願經過這篇文章,

你們對音頻變聲算法能有比較直觀的理解和認識。

以上,權當拋磚引玉。

獨樂樂不如一塊兒玩樂。

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

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

相關文章
相關標籤/搜索