關於音頻變聲演算法,這個是一個很多人特別感興趣的話題。
當然也有不少開源演算法可以參閱學習,有基於時域,也有基於頻域的演算法。
最終演算法想要達到的目的是一致。
最近也有不少網友問過關於變聲演算法的一些細節問題,郵件詢問我。
要給出一個比較合理或者說通俗易懂的解釋,看似簡單,其實還蠻難的。
按照大概的一個邏輯思路,稍微理一理,所以這個主題必須加上“大話”這個首碼。
也不打算講特別高深的,當然也是因為講不來。
之於映像演算法領域,非常重要的演算法是高斯模糊,
當然也可以認為是卷積,高斯模糊是卷積的一種特例,這裡就不展開了。
而之於音頻,也許你也猜到了,基於時間的,毫無疑問,就是重採樣演算法。
音頻採樣率是指錄音裝置在一秒鐘內對聲音訊號的採樣次數,
採樣頻率越高聲音的還原就越真實越自然。
在當今的主流採集卡上,採樣頻率一般共分為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