Android开发经验谈音视频

Android音视频开发进阶:萝莉音?御姐音?视频中的变声特效是

2020-12-31  本文已影响0人  唐唐_1388

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 声音的表达方式

从上面声音的物理学定义中得知,声音本质是自然界中的声波,所以对声音的表征可以约等价于对声波的表征。

根据傅里叶原理,任何信号都可以表达成简单信号的叠加,声波也是一种信号,因而声波也可以表征为不同频率和相位的简单正弦波复合叠加。既然是一种波,那么我们就可以用频率,振幅等物理概念来描述声音。

声波可以表征为不同频率和相位的简单正弦波复合叠加。对于信号分析,经常使用的有波形图和频谱图,分别对应时域和频域分析。

2.6 变声原理

我们在上文中理解到音色取决于哪些条件,意味着我们改变其中的一些参数都会对我们最终听到的声音有所差异。

常用的变声,如女生、男生、小黄人都是对音调(即频率)进行的处理。当音调高时就是女声,低时即男声,常常听到的女声比男声高八度还是有点道理的。

声音的高级处理,如:混响(Reverb)、回声(Echo)、EQ、锯齿(Flange)

3、Fmod使用

Fmod

3.1 相关配置

先下载源码

将lib的so库和头文件拷进来

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();
}

调用流程

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;

播放声音

3.2.2 fmod Reverb3D(混响3D) 3D声音和空间化

Reverb3D 官方文档

有玩过csgo的小伙伴,或者一些射击类游戏,一些技术厉害的玩家都比较喜欢带耳机,通过声音来辨别对手的位置。

FMOD Core API支持多种功能,这些功能允许将声音放置在3D空间中,从而通过平移,多普勒音高移位以及通过音量缩放甚至是特殊滤波进行衰减,使声音作为环境的一部分在听众周围移动。

FMOD 3D空间化功能:

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用于更改音频流或音频文件的速度,音调和播放速率。

4.1相关配置

这边拷贝android相关

这边需要修改jni相关的包名和类名 以及引入so库的名字

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的函数

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);
}
  public native final static String getVersionString();

  Log.e("aaa","version:"+SoundTouch.getVersionString());

SoundTouch 功能
处理.wav 音频文件
广泛的调整参数范围:
速度和播放率可在-95%… + 5000%范围内调节
声音音高(键) 在范围内可调-60 … 60个半音(+ - 5个八度)。
每秒节拍(BPM)检测可以调整速度以与所需的BPM速率匹配。

5、小结

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

image
上一篇下一篇

猜你喜欢

热点阅读