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