0、引
自从有了“变声”,你永远猜不到隔着网线的另一边和你开黑的队友到底是男是女。
当然,天然会伪音的我们学不来,也没必要,这里主要跟大家分享一个一篇关于音视频开发中的变声实现的技术文。
纸上说来终觉浅,时间比较充裕的小伙伴可以去B站看看实战视频教程:十年老Android一节课带你实现QQ语言变声特效
1、前言
这边先穿插一下变声相关的知识 ,这一章主要讲音频的处理。
大家应该也接触过这类应用,比如QQ的变声,或者在游戏直播里,一些主播使用的变速器,那么,到底是如何做到这样的效果呢?这一篇文章将会给大家带来这方面介绍。
对音频修改的具体使用工具是 fmod和soundTouch ,按照惯例先给出源码。这代码里实现了变声的功能,可以直接使用。
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使用
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声音和空间化
有玩过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用于更改音频流或音频文件的速度,音调和播放速率。
- 速度 (时间拉伸):更改声音以比原始速度更快或更慢的速度播放,而不影响音高。
- 音高(音调):在保持原始速度(速度)的同时改变音高或音调。
- 播放速率:一起改变速度和音高,就好像以不同的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
网友评论