原标题:“秒变萝莉音”,游戏中的变声是如何实现的?

本文作者

作者:soso密斯密斯

1

前言

这边先穿插一下变声相关的知识 ,这一章主要讲音频的处理。

大家应该也接触过这类应用,比如QQ的变声,或者在游戏直播里,一些主播使用的变速器,那么,到底是如何做到这样的效果呢?这一篇文章将会给大家带来这方面介绍。对音频修改的具体使用工具是 fmod和soundTouch ,按照惯例先给出源码。这代码里实现了变声的功能,可以直接使用。

github直通车

https://github.com/hughcoder/AudioFun

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可以使两个喇叭都发声,使用双声道记录声音,能够在一定程度上再现声音的方位,反映人耳的听觉特性。

2.5 声音的表达方式

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

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

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

波形图:信号在时间轴随时间变化的总体概括,横坐标是时间,波形是连续的时域信号。

频谱图:通过对波形进行傅里叶变换,把波形中的每个频率拆开来,再在纵轴上展开,横坐标是频率。频谱图是离散的频域信号,是三维的,越亮表示在这个频率上越响,越暗表示越弱。

一个声音频号(左图)及其对应的频谱(右图)

时域(Time domain)是描述数学函数或物理信号对时间的关系。例如一个信号的时域波形可以表达信号随着时间的变化。是真实世界,是惟一实际存在的域。很多物理变量的定义都是跟时间相关的,比如速度(位移与时间之比)、电流 、功率(单位时间做的功)。

频域(frequency domain)是指在对函数或信号进行分析时,分析其和频率有关部分,而不是和时间有关的部分,和时域一词相对。例如,许多物理元件的特性会随着输入讯号的频率而改变,例如电容在低频时阻抗变大,高频时阻抗变小,而电感恰好相反,高频时阻抗变大,低频时阻抗变小。

2.6 变声原理

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

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

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

3

Fmod使用

https://www.fmod.com/studio

3.1 相关配置

先下载源码

https://www.fmod.com/download

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

cmake相关配置

前几篇文章已经讲了很多相关知识点,就不做赘述了。

注:如果 cmake 不熟悉,需要翻看作者前面的文章。

3.2 Fmod变声效果实现

Fmod相关的API可以看这篇文章

https://fmod.com/resources/documentation-api?version=2.0&page=core-api.html

以下举个例子,通过修改音调来实现萝莉音的实现方式,让大家熟悉下调用流程。

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

constchar *path_cstr = env->GetStringUTFChars(path_jstr, NULL);

try{

system->createSound(path_cstr, FMOD_DEFAULT, NULL, &sound);

switch(type) {

caseTYPE_NORMAL: // 普通

LOGI( "%s", path_cstr)

system->playSound(sound, 0, false, &channel);

LOGI( "%s", "fix normal");

break;

caseTYPE_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...")

gotoend;

}

// system->update;

// 每隔一秒检测是否播放结束

while(isPlaying) {

channel->isPlaying(&isPlaying);

usleep( 1000* 1000);

}

gotoend;

//释放资源

end:

env->ReleaseStringUTFChars(path_jstr, path_cstr);

sound->release;

system->close;

system->release;

}

调用流程

首先创建System ,再做System初始化

createDSPByType(创建数字信号模拟器) 来看下都能实现什么音响效果

有兴趣的小伙伴可以通过传入不同的type和参数来尝试

typedefenum

{

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 官方文档

https://fmod.com/resources/documentation-api?version=2.1&page=core-guide.html#3d-sound-and-spatialization

有玩过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);

constchar *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使用

http://www.surina.net/soundtouch/

SoundTouch用于更改音频流或音频文件的速度,音调和播放速率。

速度 (时间拉伸):更改声音以比原始速度更快或更慢的速度播放,而不影响音高。

音高(音调):在保持原始速度(速度)的同时改变音高或音调。

播放速率:一起改变速度和音高,就好像以不同的RPM速率播放黑胶唱片一样。

4.1相关配置

下载源码https://gitlab.com/soundtouch/soundtouch#

1. 分配拷贝C++ 和 jni代码 包括 soundtouch-jni.cpp 、SoundTouch.java

2. 这边拷贝android相关

3. 这边需要修改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)

{

constchar*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);

intthreads = 0;

# pragmaomp parallel

{

# pragmaomp atomic

threads ++;

}

LOGV( "JNI thread count %d", threads);

// return version as string

returnenv->NewStringUTF(verStr);

}

java 部分

publicnativefinalstaticString getVersionString;

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

具体可以参考代码里具体代码实现。

SoundTouch 功能

处理.wav 音频文件。

广泛的调整参数范围:

速度和播放率可在-95%… + 5000%范围内调节

声音音高(键) 在范围内可调-60 … 60个半音(+ - 5个八度)。

每秒节拍(BPM)检测可以调整速度以与所需的BPM速率匹配。

5

小结

SoundTouch 与 FMOD 对比

SoundTouch

优点:开源,并且用于改变音频流或音频文件的节奏、音调和播放速率。适合一些简单音频处理

缺点:功能单一,满足不了需求。

FMOD

优点:声音处理功能强大,可以方便的对声音进行处理。

缺点:非开源,商用不免费,定制化差。

本文源码:

https://github.com/hughcoder/AudioFun

参考资料

https://www.jianshu.com/p/50ca2118db92返回搜狐,查看更多

责任编辑:

java 变声_“秒变萝莉音”,游戏中的变声是如何实现的?相关推荐

  1. 音视频开发必看:“秒变萝莉音”,游戏中的变声是如何实现的

    0.引 自从有了"变声",你永远猜不到隔着网线的另一边和你开黑的队友到底是男是女. 当然,天然会伪音的我们学不来,也没必要,这里主要跟大家分享一个一篇关于音视频开发中的变声实现的技 ...

  2. pipeline和java的区别_总结:四个Pipeline脚本式与声明式语法差异

    如果您阅读此博客文章,则很有可能正在寻找有关脚本化和声明性管道之间的实际差异的信息,对吗?那你找不到更好的地方了.我将向您展示这两者之间的四个最实际的区别.和我待几分钟,享受旅程! 为什么要有两种管道 ...

  3. 圆变成长方形什么变了_中班数学教案详案图形变变变含PPT课件

    设计意图: 纲要中所述:教师要利用幼儿身边感兴趣的事物与现象作为科学探究的对象.既符合幼儿现实的需要,又有利于其长远的发展;既贴近幼儿的生活,又有助于拓展幼儿的经验和视野,几何图形是幼儿生活中常见的事 ...

  4. mac移动鼠标光标会变大_如何在Mac上使鼠标光标变大或变小

    mac移动鼠标光标会变大 The macOS cursor isn't tiny, but some people have trouble seeing it. If you're one of t ...

  5. eclipse建java项目不见_秒建一个后台管理系统?用这5个开源免费的Java项目就够了...

    这是我的第 196 期分享 作者 | Guide 来源 | JavaGuide(ID:JavaGuide) 分享 | Java中文社群(ID:javacn666) 大家好,我是 Guide 哥,一个三 ...

  6. goupby 两个值 结果变了_一道问题引出的python中可变数据类型与不可变数据类型...

    一. 问题的提出 我们先来看两个对比 第一道题,当对象为整数时,最终结果:b = 2, a = 1,b的变化没有引起a的变化 第二道题,当对象为字典时,最终结果:a = {"name&quo ...

  7. java恋爱代码_陷入与代码的恋爱中:终生的爱情故事

    java恋爱代码 by Daragh Byrne 达拉·伯恩(Daragh Byrne) 陷入与代码的恋爱中:终生的爱情故事 (Falling in and out of love with code ...

  8. java excel 删除行_使用Apache POI在Excel中删除多行

    我有一张包含75行表格的Excel表格 . 在第76行我有各列的总功能 =SUM(A1:A75) 和 =SUM(B1:B75) 在第77到第92位的行我有一个Excel图表这需要A1:A75和B1:为 ...

  9. mac电脑配置java环境变量_教你在Mac系统中配置JAVA环境变量的方法

    Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征.下面向大家介绍Mac电脑上如何配置Java ...

最新文章

  1. 如何在Windows下使用Linux系统来编译和运行程序?
  2. OWASP 2017 Top10 漏洞体系
  3. UrlPager免费分页控件2.0版发布!
  4. 科普 | USB 协议与接口
  5. Scrapy-xpath用法以及实例
  6. 剑指 Offer 28. 对称的二叉树【无取巧,易于理解!】
  7. SAP Spartacus Loader Meta Reducer学习笔记 - loaderReducer
  8. jvm类加载机制和类加载器_在JVM之下–类加载器
  9. 把 mysql 整个加载进内存磁盘中_MySQL 缓冲池 是什么?
  10. kubernetes_Kubernetes领域的标准安全性。
  11. 如何在不使用try语句的情况下查看文件是否存在
  12. GHOSTXP_SP3
  13. linux 如何选择新的内核,如何更新内核??__boot_内核_vmlinuz_linux_System__169IT.COM
  14. WinAPI: Rectangle - 绘制矩形
  15. java经常用到的英文_Java中用到的英文单词,你知道多少?
  16. AForge.net获取摄像头
  17. MATLAB 神经网络函数
  18. OKR助理源代码说明
  19. Python基础-Week2
  20. datepicker的具体用法

热门文章

  1. 在Hfss中使用优化法快速确定喇叭天线相位中心
  2. 常用HTML代码,字体颜色等
  3. 数据结构练习题---先序遍历二叉树
  4. NetApp 数据保护解决方案
  5. 恒生电子怎样——应届生眼中的恒生
  6. linux 剪刀石头布c语言,C语言实现最简单的剪刀石头布小游戏示例
  7. 贵州省六盘水谷歌卫星地图下载
  8. 练字格子纸模板pdf_a4田字格练字纸打印版-练字标准田字格模板-a4打印版下载最新免费excel版-西西软件下载...
  9. 主流雷达供应商的4D成像雷达方案梳理
  10. Ubuntu中的Load/Unload Cycle Count问题及解决方案