Android音視頻開發進階:蘿莉音?御姐音?視頻中的變聲特效是如何實現的

0、引

自從有了“變聲”,你永遠猜不到隔着網線的另一邊和你開黑的隊友到底是男是女。

當然,天然會僞音的我們學不來,也沒必要,這裏主要跟大家分享一個一篇關於音視頻開發中的變聲實現的技術文。

紙上說來終覺淺,時間比較充裕的小夥伴可以去B站看看實戰視頻教程:十年老Android一節課帶你實現QQ語言變聲特效

1、前言

這邊先穿插一下變聲相關的知識 ,這一章主要講音頻的處理。

大家應該也接觸過這類應用,比如QQ的變聲,或者在遊戲直播裏,一些主播使用的變速器,那麼,到底是如何做到這樣的效果呢?這一篇文章將會給大家帶來這方面介紹。

對音頻修改的具體使用工具是 fmod和soundTouch ,按照慣例先給出源碼。這代碼裏實現了變聲的功能,可以直接使用。

github直通車

2、聲音基礎概念

2.1原理

聲音是一種波動,當演奏樂器、拍打一扇門或者敲擊桌面時,聲音的振動會引起介質——空氣分子有節奏的振動,使周圍的空氣產生疏密變化,形成疏密相間的縱波,這就產生了聲波,這種現象會一直延續到振動消失爲止。

聲音總可以被分解爲不同頻率不同強度正弦波的疊加。這種變換(或分解)的過程,稱爲傅里葉變換。

因此,一般的聲音總是包含一定的頻率範圍。人耳可以聽到的聲音的頻率範圍在20到2萬赫茲(Hz)之間。

2.2音波的性質及特性

音波常簡化爲正弦平面波的合成,各平面波可以用以下的性質來描述:

頻率:音調越高,頻率越大;音調越低,頻率越小。(介質相同時,fλ成反比)
波長:音調越高,波長越短;音調越低,波長越長。(介質相同時,fλ成反比)
波數
振幅:音量(響度)越大,振幅越大;音量越小,振幅越小。
聲壓
音強
音速
方向
音色:即波形 (音色主要決定於聲音頻譜對人的刺激,但也決定於波形、聲壓、頻譜的頻率位置和頻譜對人的時間性刺激。)

2.3音色

音色換句話說,一個物體發生的同時,會發出很多不同頻率的波(諧波)。

這許多不同頻率的波由於相位差很小(也就是相隔時間很短),人是無法單獨分辨的,所以這些波會混合起來一起給人一個整體的感受,而這個感受就叫做音色。

人耳可以感知到的聲音,其頻率範圍爲20 Hz至20,000 Hz,在標準狀況下的空氣中,上述音波對應的波長從17 m至17 mm之間。

有時音速及其方向會用速度矢量來表示,波數和其方向則會用波矢表示。

當發音體越短、越細、越緊、越薄時,音調越高、頻率越大、波長越短;發音體越長、越粗、越松、越厚時,音調越低、頻率越小、波長越長。

音色是指不同聲音表現在波形方面總是有與衆不同的特性,不同的物體振動都有不同的特點。

不同的發聲體由於其材料、結構不同,則發出聲音的音色也不同。例如鋼琴、小提琴和人發出的聲音不一樣,每一個人發出的聲音也不一樣。因此,可以把音色理解爲聲音的特徵。

人的喉嚨是立體的,發聲時喉嚨內每一部分都會產生振動,不同部位產生的振動頻率就存在差異。其中頻率的相對量最大的決定了聲音的音調,其它的頻率即泛音。

當然人說話時還有鼻子和嘴來協助,另外即便是樂器或其它任何發聲物體也往往是整體產生共鳴的結果。

有一個這樣的比喻,如果一個聲音中從1到20K赫茲頻率的波都有,並且都是1:1的關係,即相對強度都相同。這樣一個聲音就稱爲白噪音,聽起來就和收音機收不信號時的音色一樣。

如果我有2萬隻音箱,每一個音箱分別對應放從1到20k赫茲不同頻率的聲波。那麼我通過開關不同的音箱,調節每個音箱的音量,從理論上講我就可以得到任何我想要的音色。不論是韓紅的聲音還是孫楠的聲音,小提琴的聲音。

2.4聲音採集

將模擬信號數字化,分爲取樣和量化兩部分,即通常的 PCM(Pulse-code modulation) 脈衝編碼調製技術。

採樣率

採樣率指每秒音頻採樣點個數(8000/44100Hz),採樣率單位用Hz表示。

像是CD音樂的標準採樣頻率爲44.1KHz(指的就是在1s中對聲音採樣44100次,也就是對聲音1秒的聲音記錄44100個點,用44100個點來表示1秒鐘的聲音),這也是目前聲卡與計算機作業間最常用的採樣頻率。例如常見的採樣頻率有8kHz、16kHz, 44.1kHz, 48kHz。

採樣率的大小影響到聲音的質量,顯然,採樣率越高,量化後的波形越接近原始波形,聲音的質量越高,而需要的存儲空間也會越多,採樣率越低,聲音的質量越低,需要的存儲空間想相對越少。

人耳的類聽覺範圍爲20-20000Hz,那麼數字音頻採樣率至少40KkHz才能恢復原始信號(CD音頻使用44100Hz的採樣率,部分原因也在於此)。

採樣深度

量化(Quantization) 是將連續值近似爲某個範圍內有限多個離散值的處理過程,這個範圍的寬度離散值的數量表達,會直接影響到音頻採樣的準確性。一般 8位(256),和 16位(65536)來表示。

在ffmpeg處理音頻時,經常會看到類似f32be、s16le、u16be等字符串,這些字符串其實就是表徵位深的上述幾個概念,例如:s16le,就表示一個樣本用16bit有符號的整形數據表示,存儲字節序爲小端。使用ffmpeg -formats命令,可以看到ffmpeg支持的所有音視頻格式。

由於指定長度的二進制位數能表示的範圍空間有限,當要表徵的音頻範圍超過了這個二進制數據能表示的範圍後,數據就會溢出。所以在音頻放大時,有個基本的溢出保護問題。例如PCM signed 16-bit little-endian的音頻,每個樣本2個字節,每個樣本的取值空間-32768 ~ 32767,放大後的音頻超過這個區間時,就會產生溢出,嚴重時會產生破音,因此在實現軟件音頻放大時,要進行基本的溢出保護。

聲道數

單聲道的聲音只能使用一個喇叭發聲,立體聲的pcm可以使兩個喇叭都發聲,使用雙聲道記錄聲音,能夠在一定程度上再現聲音的方位,反映人耳的聽覺特性。

AudioRecord :: getMinBufferSize

static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)

sampleRateInHz:默認採樣率,單位Hz,這裏設置爲44100,44100Hz是當前唯一能保證在所有設備上工作的採樣率;

channelConfig: 描述音頻聲道設置,這裏設置爲AudioFormat.CHANNEL_CONFIGURATION_MONO,CHANNEL_CONFIGURATION_MONO保證能在所有設備上工作;

audioFormat:音頻數據的採樣精度,這裏設置爲AudioFormat.ENCODING_16BIT;

2.5 聲音的表達方式

從上面聲音的物理學定義中得知,聲音本質是自然界中的聲波,所以對聲音的表徵可以約等價於對聲波的表徵。

根據傅里葉原理,任何信號都可以表達成簡單信號的疊加,聲波也是一種信號,因而聲波也可以表徵爲不同頻率和相位的簡單正弦波複合疊加。既然是一種波,那麼我們就可以用頻率,振幅等物理概念來描述聲音。

聲波可以表徵爲不同頻率和相位的簡單正弦波複合疊加。對於信號分析,經常使用的有波形圖和頻譜圖,分別對應時域和頻域分析。

  • 波形圖:信號在時間軸隨時間變化的總體概括,橫座標是時間,波形是連續的時域信號。

  • 頻譜圖:通過對波形進行傅里葉變換,把波形中的每個頻率拆開來,再在縱軸上展開,橫座標是頻率。頻譜圖是離散的頻域信號,是三維的,越亮表示在這個頻率上越響,越暗表示越弱。

  • 時域(Time domain)是描述數學函數或物理信號對時間的關係。例如一個信號的時域波形可以表達信號隨着時間的變化。是真實世界,是惟一實際存在的域。很多物理變量的定義都是跟時間相關的,比如速度(位移與時間之比)、電流 、功率(單位時間做的功)。

  • 頻域(frequency domain)是指在對函數或信號進行分析時,分析其和頻率有關部分,而不是和時間有關的部分,和時域一詞相對。例如,許多物理元件的特性會隨着輸入訊號的頻率而改變,例如電容在低頻時阻抗變大,高頻時阻抗變小,而電感恰好相反,高頻時阻抗變大,低頻時阻抗變小。

2.6 變聲原理

我們在上文中理解到音色取決於哪些條件,意味着我們改變其中的一些參數都會對我們最終聽到的聲音有所差異。

常用的變聲,如女生、男生、小黃人都是對音調(即頻率)進行的處理。當音調高時就是女聲,低時即男聲,常常聽到的女聲比男聲高八度還是有點道理的。

聲音的高級處理,如:混響(Reverb)、回聲(Echo)、EQ、鋸齒(Flange)

3、Fmod使用

Fmod

3.1 相關配置

先下載源碼

  • 下載源碼

將lib的so庫和頭文件拷進來

  • cmake相關配置
    前幾篇文章已經講了很多相關知識點,就不做贅述了

3.2 Fmod變聲效果實現

Fmod相關的API可以看這篇文章
fmod核心API

以下舉個例子,通過修改音調來實現蘿莉音的實現方式,讓大家熟悉下調用流程。

3.2.1 fmod DSP(數字信號處理)

JNI部分


extern "C" JNIEXPORT void JNICALL Java_com_hugh_audiofun_FmodSound_playSound
        (JNIEnv *env, jclass jcls, jstring path_jstr, jint type) {
    LOGI("%s", "--> start");

    System *system;
    Sound *sound;
    DSP *dsp;
//    Channel *channel;
    float frequency;
    bool isPlaying = true;

    System_Create(&system);
    system->init(32, FMOD_INIT_NORMAL, NULL);
    const char *path_cstr = env->GetStringUTFChars(path_jstr, NULL);

    try {
        system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);
        switch (type) {
            case TYPE_NORMAL:  // 普通
                LOGI("%s", path_cstr)
                system->playSound(sound, 0, false, &channel);
                LOGI("%s", "fix normal");
                break;
            case TYPE_LOLITA:  // 蘿莉
                system->createDSPByType(FMOD_DSP_TYPE_PITCHSHIFT, &dsp);    // 可改變音調
                dsp->setParameterFloat(FMOD_DSP_PITCHSHIFT_PITCH, 8.0);     // 8.0 爲一個八度
                system->playSound(sound, 0, false, &channel);
                channel->addDSP(0, dsp);
                break;
                ………………
        }
    } catch (...) {
        LOGE("%s", "catch exception...")
        goto end;
    }

//    system->update();

    // 每隔一秒檢測是否播放結束
    while (isPlaying) {
        channel->isPlaying(&isPlaying);
        usleep(1000 * 1000);
    }

    goto end;

    //釋放資源
    end:
    env->ReleaseStringUTFChars(path_jstr, path_cstr);
    sound->release();
    system->close();
    system->release();
}

調用流程

  • 首先創建System ,再做System初始化

  • createDSPByType(創建數字信號模擬器) 來看下都能實現什麼音響效果
    有興趣的小夥伴可以通過傳入不同的type和參數來嘗試

typedef enum
{
    FMOD_DSP_TYPE_UNKNOWN,
    FMOD_DSP_TYPE_MIXER,   //混合輸入。
    FMOD_DSP_TYPE_OSCILLATOR, //生成正弦/正方形/鋸齒/三角形或噪聲音調。
    FMOD_DSP_TYPE_LOWPASS,
    FMOD_DSP_TYPE_ITLOWPASS,
    FMOD_DSP_TYPE_HIGHPASS,
    FMOD_DSP_TYPE_ECHO,  //在聲音上產生回聲,並以所需的速率衰減。
    FMOD_DSP_TYPE_FADER,
    FMOD_DSP_TYPE_FLANGE, //對聲音產生法蘭效應。
    FMOD_DSP_TYPE_DISTORTION,
    FMOD_DSP_TYPE_NORMALIZE,
    FMOD_DSP_TYPE_LIMITER,
    FMOD_DSP_TYPE_PARAMEQ,
    FMOD_DSP_TYPE_PITCHSHIFT,
    FMOD_DSP_TYPE_CHORUS,  //在聲音上產生一種合唱效果。
    FMOD_DSP_TYPE_VSTPLUGIN,
    FMOD_DSP_TYPE_WINAMPPLUGIN,
    FMOD_DSP_TYPE_ITECHO,
    FMOD_DSP_TYPE_COMPRESSOR,
    FMOD_DSP_TYPE_SFXREVERB,  //自解壓混響
    FMOD_DSP_TYPE_LOWPASS_SIMPLE,
    FMOD_DSP_TYPE_DELAY,
    FMOD_DSP_TYPE_TREMOLO,  //在聲音上產生一種顫音/斬波的效果。
    FMOD_DSP_TYPE_LADSPAPLUGIN,
    FMOD_DSP_TYPE_SEND,
    FMOD_DSP_TYPE_RETURN,
    FMOD_DSP_TYPE_HIGHPASS_SIMPLE,
    FMOD_DSP_TYPE_PAN,
    FMOD_DSP_TYPE_THREE_EQ, //三波段均衡器。
    FMOD_DSP_TYPE_FFT,
    FMOD_DSP_TYPE_LOUDNESS_METER,
    FMOD_DSP_TYPE_ENVELOPEFOLLOWER,
    FMOD_DSP_TYPE_CONVOLUTIONREVERB, //卷積混響。
    FMOD_DSP_TYPE_CHANNELMIX,
    FMOD_DSP_TYPE_TRANSCEIVER,
    FMOD_DSP_TYPE_OBJECTPAN,
    FMOD_DSP_TYPE_MULTIBAND_EQ,

    FMOD_DSP_TYPE_MAX,
    FMOD_DSP_TYPE_FORCEINT = 65536    /* Makes sure this enum is signed 32bit. */
} FMOD_DSP_TYPE;
  • system->playSound(sound, 0, false, &channel); channel->addDSP(0, dsp);

播放聲音

3.2.2 fmod Reverb3D(混響3D) 3D聲音和空間化

Reverb3D 官方文檔

有玩過csgo的小夥伴,或者一些射擊類遊戲,一些技術厲害的玩家都比較喜歡帶耳機,通過聲音來辨別對手的位置。

FMOD Core API支持多種功能,這些功能允許將聲音放置在3D空間中,從而通過平移,多普勒音高移位以及通過音量縮放甚至是特殊濾波進行衰減,使聲音作爲環境的一部分在聽衆周圍移動。

FMOD 3D空間化功能:

  • 多個衰減衰減模型
    滾降是聲音在靠近聽衆或遠離聽衆時的音量行爲。在線性,反向,線性平方,反向錐形和自定義滾降模式之間進行選擇。自定義滾降允許設置FMOD_3D_ROLLOFF_CALLBACK以允許用戶計算音量滾降的方式。如果回調不方便,則FMOD還允許使用ChannelControl :: set3DCustomRolloff在其間線性內插的點數組表示“曲線” 。

  • 多普勒音調移位
    由收聽者和通道或通道組的用戶速度設置控制的準確音高偏移是由FMOD 3D空間化系統實時計算和設置的。

  • 基於矢量的振幅平移(VBAP)
    該系統實時平移用戶揚聲器中的聲音,支持單聲道,立體聲,高達5.1和7.1環繞聲揚聲器設置。

  • 咬合
    通道或通道組可以應用低通濾波,以模擬聲音穿過牆壁或被大型物體遮擋。

  • 3D混響區用於混響平移
    混響也可以被遮擋,以免穿過牆壁或物體。

  • 基於多邊形的幾何遮擋
    將多邊形數據添加到FMOD的幾何引擎中,FMOD將使用光線投射實時自動遮擋聲音。有關更多信息,請參見基於3D多邊形的幾何遮擋部分。

  • 多個聽衆
    在分屏模式的遊戲中,FMOD可以爲每個玩家支持一個聽衆,以便3D聲音正確衰減。

  • 在多聲道聲音的2D和3D之間變形
    聲音可以是點源,也可以由用戶變形爲2D聲音,這對於基於距離的包絡非常有用。聲音越近,它越能傳播到其他揚聲器中,而不是隨着聲音從一側平移到另一側而從一側翻轉到另一側。有關允許用戶更改此混合的功能,請參見ChannelControl :: set3DLevel。

  • 立體聲和多聲道聲音可以是3D
    通常,單聲道聲音用於3D音頻。多聲道聲音可以用來產生額外的影響。默認情況下,多聲道聲音會摺疊爲單點源。要“傳播”多聲道聲音的聲道,請使用ChannelControl :: set3DSpread。對於從某個方向發出的聲音,這可以帶來更大的空間效果。聲音在遠處的細微散佈可能會給人留下更有效的空間感覺,就好像它是從附近的表面反射出來的一樣,或者是“大”的聲音並在不同方向上發出不同的聲音。

  • 空間化插件支持
    第三方VR音頻插件可用於在耳機上提供更逼真的平移。

3.2.2.1 3D實現

在創建的聲音時需要改成3D,以及距離的相關參數


    System_Create(&system);
    system->init(32, FMOD_INIT_NORMAL, NULL);
    const char *path_cstr = env->GetStringUTFChars(path_jstr, NULL);
    FMOD_VECTOR pos = { -10.0f, 0.0f, 0.0f };
    Reverb3D *reverb;
    FMOD_RESULT result = system->createReverb3D(&reverb);

    LOGI("createReverb3D %c", result);
    FMOD_REVERB_PROPERTIES prop2 = FMOD_PRESET_CONCERTHALL;
    reverb->setProperties(&prop2);
    float mindist = 10.0f;
    float maxdist = 20.0f;

    FMOD_VECTOR  listenerpos  = { 10.0f, 5.0f, -1.0f };
    system->set3DListenerAttributes(0, &listenerpos, 0, 0, 0);

    system->createSound(path_cstr, FMOD_3D, NULL, &sound);
    system->playSound(sound, 0, false, &channel);

4、SoundTouch使用

SoundTouch

SoundTouch用於更改音頻流或音頻文件的速度,音調和播放速率。

  • 速度 (時間拉伸):更改聲音以比原始速度更快或更慢的速度播放,而不影響音高。
  • 音高(音調):在保持原始速度(速度)的同時改變音高或音調。
  • 播放速率:一起改變速度和音高,就好像以不同的RPM速率播放黑膠唱片一樣。
4.1相關配置
  • 下載源碼
    sound源碼下載

  • 分配拷貝C++ 和 jni代碼 包括 soundtouch-jni.cpp 、SoundTouch.java

這邊拷貝android相關

這邊需要修改jni相關的包名和類名 以及引入so庫的名字

  • cmake腳本
cmake_minimum_required(VERSION 3.4.1)

include_directories ("src/main/cpp/include")
add_library( # Sets the name of the library.
        soundtouch-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/cpp/soundtouch-jni.cpp
        src/main/cpp/SoundTouch/AAFilter.cpp
        src/main/cpp/SoundTouch/BPMDetect.cpp
        src/main/cpp/SoundTouch/cpu_detect_x86.cpp
        src/main/cpp/SoundTouch/FIFOSampleBuffer.cpp
        src/main/cpp/SoundTouch/FIRFilter.cpp
        src/main/cpp/SoundTouch/InterpolateCubic.cpp
        src/main/cpp/SoundTouch/InterpolateLinear.cpp
        src/main/cpp/SoundTouch/InterpolateShannon.cpp
        src/main/cpp/SoundTouch/mmx_optimized.cpp
        src/main/cpp/SoundTouch/PeakFinder.cpp
        src/main/cpp/SoundTouch/RateTransposer.cpp
        src/main/cpp/SoundTouch/SoundTouch.cpp
        src/main/cpp/SoundTouch/sse_optimized.cpp
        src/main/cpp/SoundTouch/TDStretch.cpp
        src/main/cpp/SoundStretch/WavFile.cpp
        )

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log )

target_link_libraries( # Specifies the target library.
        soundtouch-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

4.2 調用soundTouch 相關功能

這邊舉個例子,看下如何調用SoundTouch的函數

  • jni部分
extern "C" DLL_PUBLIC jstring Java_com_hugh_sound_SoundTouch_getVersionString(JNIEnv *env, jobject thiz)
{
    const char *verStr;

    LOGV("JNI call SoundTouch.getVersionString");

    // Call example SoundTouch routine
    verStr = SoundTouch::getVersionString();

    /// gomp_tls storage bug workaround - see comments in _init_threading() function!
    _init_threading(false);

    int threads = 0;
    #pragma omp parallel
    {
        #pragma omp atomic
        threads ++;
    }
    LOGV("JNI thread count %d", threads);

    // return version as string
    return env->NewStringUTF(verStr);
}
  • java 部分
  public native final static String getVersionString();

  Log.e("aaa","version:"+SoundTouch.getVersionString());
  • 具體可以參考代碼裏具體代碼實現

SoundTouch 功能
處理.wav 音頻文件
廣泛的調整參數範圍:
速度和播放率可在-95%… + 5000%範圍內調節
聲音音高(鍵) 在範圍內可調-60 … 60個半音(+ - 5個八度)。
每秒節拍(BPM)檢測可以調整速度以與所需的BPM速率匹配。

5、小結

SoundTouch 與 FMOD 對比

  • SoundTouch
    優點:開源,並且用於改變音頻流或音頻文件的節奏、音調和播放速率。適合一些簡單音頻處理

缺點:功能單一,滿足不了需求。

  • FMOD
    優點:聲音處理功能強大,可以方便的對聲音進行處理。

缺點:非開源,商用不免費,定製化差。

6、參考資料

原作:https://blog.csdn.net/qq_38366777/article/details/107405903

文末

紙上說來終覺淺,時間比較充裕的小夥伴可以去B站看看實戰視頻教程:十年老Android一節課帶你實現QQ語言變聲特效

歡迎關注我的簡書,分享Android乾貨,交流Android技術。
對文章有何見解,或者有何技術問題,都可以在評論區一起留言討論,我會虔誠爲你解答。
也歡迎大家來我的B站找我玩,有各類Android架構師進階技術難點的視頻講解
B站直通車:https://space.bilibili.com/544650554

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章