sonic音频变速不变调的原理分析
1.问题背景
音频的原始pcm(Pulse Code Modulation)数据是由采样频率、通道数以及采样精度(位宽)而决定。人耳能听到的频率范围是[20H~20kHz],所以常见的音频采样率是44100HZ,即一秒内采样44100次,通道数一般为2, 代表双声道,而采样精度代表着每个“样本点”的大小,常用的大小为8bit, 16bit,24bit位宽,一般是16bit 即2个字节。
最简单的音频变速方法就是通过改变采样频率,比如音视频播放器中的 2 倍速,0.5 倍速播放。如果想要实现音频的2.0倍速播放,只需要每隔一个样本点丢一个点,即采样率降低一半。如果想要实现0.5倍速播放,只需要每隔一个样本点插入一个值为0的样本点。但是如果仅仅这样做,会造成声音的音调也发生变化,如一个大汉的粗犷之音变为一个娇柔少女的夹子音,这是我们无法接受的。
目前成熟的商业化技术,一般想要实现变速不变调的功能都通过sonic或者sountTouch方法来处理pcm数据。
2.基础知识
2.1声音的三要素
声音的三要素包括: 响度、音调、音色。在变速时,需要变的是音频的播放速度,同时要保持音调不变。
响度:
响度代表声音的能量强弱,主要取决于振幅大小,声音的响度一般用声压来计量,声压的单位为帕(Pa),它与基准声压比值的对数值称为声压级,单位是分贝(db spl)。人耳对于响度的感知变化并不是线性的,且对低频和高频都不太敏感,对1000HZ-3000HZ的频率比较敏感。
音调:
声波是有可以看作是有无数个不同频谱、振幅和相位的正弦波组成,音调的大小主要取决于声波基频的高低,不同乐器的基频不同,比如 bass的频很低,而军鼓的频率就比较高;钢琴键不同键的频率也不同,男生和女生的基频也不相同,女生声音的基频比男声要高。
音色:不同音色的声音,即使在相同响度和音调的情况下,也能让人区分开来。声音是由发声的物体的振动产生的。当发声物体的主体振动时会发出一个基音,同时其余各部分也有复合的振动,这些振动组合产生泛音。正是这些泛音决定了发生物体的音色,使人能辨别出不同的乐器甚至不同的人发出的声音。所以根据音色的不同可以划分出男音和女音;高音、中音和低音;弦乐和管乐等。所有泛音都比基音的频率高,但强度都相当弱。
2.2时域和频域
音频分析处理领域可以分为时域和频域。
时域上表现为波形随着时间变化而变化。
频域分析则是首先对时域信号分帧、加窗、做stft(短时傅立叶变换)等处理,更方便的进行计算。比如把20ms-50ms的一个波形看作一个周期,进行分帧加窗处理,计算出该帧不同频率的响度值。
我们今天的主题变速不变调就是在频域上进行处理。
2.3浊音、基音周期的概念
了解原理之前要先理解一下浊音的发音过程:来自肺部的气流冲击声门,造成声门的一张一合,形成一系列准周期的气流脉冲,经过声道(含口腔、鼻腔)的谐振及唇齿的辐射最终形成语音信号。故浊音波形呈现一定的准周期性。
所谓基音周期,就是对这种准周期而言的,它反映了声门相邻两次开闭之间的时间间隔或开闭的频率。
3.sonic原理分析
Sonic变调不变速的主要原理通过使用波形相似叠加算法并采用AMDF(平均幅度差函数法)方法来寻找最相似的第三帧来实现变速不变调。
3.1 波形相似叠加(WSOLA)
核心算法思想如下:
图(a): 在原音频信号中取一帧(左边红色框),并加窗处理。
图(b): 在一个范围内(左边蓝色框)选取第二帧,这个帧的相位参数和第一帧(红色宽)的相位对齐。
图(c): 在另外一个范围(右上角蓝色大框)中查找和第二帧最相似的第三帧(右上角蓝色大框中的红色框)
图(d): 对第三帧进行加窗处理,然后和第一帧进行叠加。
那么如何寻找最相似的第三帧呐?
有两个波形相似叠加算法的实现,一个是Soundtouch,另外一个是Sonic,但它们在寻找最相似帧采用了不同的算法。其中Soundtouch采用了寻找相关峰算法来实现,而Sonic采用了AMDF(平均幅度差函数法)来实现。
3.2AMDF(平均幅度差函数法)
sonic 中使用的是 AMDF(平均幅度差函数法)方法,原理就是计算 2 个对比帧每个采样点幅度差之和。sonic 计算的方法为 findPitchPeriodInRange,代码如下:
static int findPitchPeriodInRange(short* samples, int minPeriod, int maxPeriod,int* retMinDiff, int* retMaxDiff) {int period, bestPeriod = 0, worstPeriod = 255;short* s;short* p;short sVal, pVal;unsigned long diff, minDiff = 1, maxDiff = 0;int i;
//minPeriod maxPeriod为定义好的经验值,不同的采用率下会不同for (period = minPeriod; period <= maxPeriod; period++) {diff = 0;s = samples;p = samples + period;for (i = 0; i < period; i++) {sVal = *s++;pVal = *p++;diff += sVal >= pVal ? (unsigned short)(sVal - pVal) // diff就是AMDF: (unsigned short)(pVal - sVal);}if (bestPeriod == 0 || diff * bestPeriod < minDiff * period) {minDiff = diff;bestPeriod = period;}if (diff * worstPeriod > maxDiff * period) {maxDiff = diff;worstPeriod = period;}}*retMinDiff = minDiff / bestPeriod;*retMaxDiff = maxDiff / worstPeriod;return bestPeriod;
}
在 range 范围内遍历每个帧与起始帧的 AMDF 值,值最小的帧与起始帧的距离则是基因周期。为了加速计算,sonic 统一将数据的采用率降低到了 4000。
寻找到最相似的第三帧后,便进行变速变调。
加速原理:
以基因周期 period = 60、inputbuffer 点为 0、采样率为 16000、speed = 1.5 为例:
1.根据 inputbuffer 的首个帧 在 range 范围内寻找到基因周期 peroid = 60
2.根据 speed 计算当前 inputbuffer 需要保存的点 。为 60 * (2 - 1.5)/(1.5-1) = 60
保存的点计算方式如下:
3. 将 inputbuffer 中连续的 2 个基因周期进行合并输出到 outputbuffer 中,及 inputbuffer 中的 0-60、60-120 进行合并输出到 outputbuffer 中的 0-60。 合并时按照 1: 1/60 : 0 和 0: 1/60 : 1 的比重进行叠加。
4. 将 inputbuffer 中保存的 60 个点的数据拷贝到 outputbuffer 中的 60-120。保存的 60 个点为 inputbuffer 中的 120-180
5. 此时 inpoutbuffer 的 60+60+60 = 180 点数据变成了 outputbuffer 中的 60+60 = 120 点数据 完成了 1.5 倍加速
减速原理
和加速大致一样,不同的是减速需要插入数据0。
以基因周期 period = 60, inputbuffer 起始点为 0, 采样率为 16000,speed = 0.8 为例
1.根据 inputbuffer 的首个帧 在 range 范围内寻找到基因周期 peroid = 60
2.根据 speed 计算当前需要保存的点 为 60 * (2 * 0.8- 1)/(1-0.8) = 180
3. 将 inputbuffer 中 0:60 的数据输出到 outputbuffer 中,并将 60-120 0-60 进行合并。 合并时按照 1:1/60:0 和 0:1/60:1 的比重进行叠加。
4. 将 inputbuffer 中保存的 180 个点的数据拷贝到 outputbuffer 中。
5. 此时 inpoutbuffer 的 60+60+180 = 300 点数据变成了 outputbuffer 中的 60+60+60+180 = 360 点数据 完成了 0.8 倍减速
加速、减速的核心函数代码如下:
static int changeSpeed(sonicStream stream, float speed) {short* samples;int numSamples = stream->numInputSamples;int position = 0, period, newSamples;int maxRequired = stream->maxRequired;/* printf("Changing speed to %f\n", speed); */if (stream->numInputSamples < maxRequired) {return 1;}do {if (stream->remainingInputToCopy > 0) {newSamples = copyInputToOutput(stream, position);position += newSamples;} else {samples = stream->inputBuffer + position * stream->numChannels;period = findPitchPeriod(stream, samples, 1);
#ifdef SONIC_SPECTROGRAMif (stream->spectrogram != NULL) {sonicAddPitchPeriodToSpectrogram(stream->spectrogram, samples, period,stream->numChannels);newSamples = period;position += period;} else
#endif /* SONIC_SPECTROGRAM */if (speed > 1.0) {newSamples = skipPitchPeriod(stream, samples, speed, period);position += period + newSamples;} else {newSamples = insertPitchPeriod(stream, samples, speed, period);position += newSamples;}}if (newSamples == 0) {return 0; /* Failed to resize output buffer */}} while (position + maxRequired <= numSamples);removeInputSamples(stream, position);return 1;
}
sonic音频变速不变调的原理分析相关推荐
- 音视频开发之旅(66) - 音频变速不变调的原理
目录 声音的基本知识 时域压扩(TSM)的原理 波形相似叠加(WSOLA) 资料 收获 音频的原始pcm数据是由 采样率.采样通道数以及位宽而定.常见的音频采样率是44100HZ,即一秒内采样4410 ...
- 如何实现音频变速不变调、变调不变速、变速又变调?
变速和变调相关的音频处理库有SoundTouch,Sonic,RubberBand(https://breakfastquay.com/rubberband/). RubberBand暂时没有调试成功 ...
- Phase Vocoder的补充完善,Matlab音频变速不变调、变调不变速
之前有站内朋友介绍了国外的Phase Vocoder,Matlab音频变速不变调.变调不变速(Phase Vocoder)_cyz0612的博客-CSDN博客_matlab变调不变速代码参考这篇文章, ...
- Matlab音频变速不变调、变调不变速(Phase Vocoder)
其他文章有讲过一些方法,有OLA.WSOLA算法.LSEE-MSTFTM.Phase Vocoder(相位声码器)等等,但都是讲了个大概,没一个能说清楚的,代码就更没有了.找了一个国外的Phase V ...
- 变速外挂案例及原理分析
众所周知,游戏在运行中需要以帧为单位播放画面,而计算每帧动画播放所需时间,则需要调用C库函数来获取系统时间.如: // 获取当前精确时间 gettimeofday; // 获取系统时间 clock_g ...
- 一行代码实现音频变速不变调
更多信息可参见:https://www.computationalimaging.cn/2019/11/blog-post.html 准备:需安装ffmpeg 方法: 命令行输入 ffmpeg -n ...
- 音视频开发之旅(67) - 变速不变调之sonic源码分析
目录 基音周期.浊音的概念 Sonic源码分析 资料 收获 上一篇我们学习了音频变速不变调的原理以及WSOLA波形相似叠加算法进行时域压扩处理.其中在寻找相似帧方面,Sonic采用AMDF(平均幅度差 ...
- 音频变速变调原理及 soundtouch 代码分析
音频变速变调原理及 soundtouch 代码分析 作者:floer rivor 2021 年 4 月 30 日 本文字数:5066 字 阅读完需:约 17 分钟 概述 音频变速变调在不同的场景可以分 ...
- OpenSL ES利用SoundTouch实现PCM音频的变速和变调
我的视频课程(基础):<(NDK)FFmpeg打造Android万能音频播放器> 我的视频课程(进阶):<(NDK)FFmpeg打造Android视频播放器> 我的视频课程(编 ...
最新文章
- 单周期十条指令CPU设计与verilog实现(Modelsim)
- Android系统手机端抓包方法
- 已知x=python是一种非常好的编程语言-为什么用Python,高级的Python是一种高级编程语言...
- Notepad++ 列编辑操作实例二则
- 第7章:项目成本管理习题总结
- oracle修改某表中的顺序,oracle 数据库 , 表中字段顺序修改
- python 连接oracle_常用的Python库,给大家分享一下!
- 区块链安全-以太坊智能合约静态分析
- Learning中的代数结构的建立
- php 写一个大富翁游戏,抽奖系列:如何用纯js做一个大富翁游戏
- dns文件传输服务器,MOOC云计算 - DNS三部曲之DNS区域传输限制
- mysql中乘积函数_Mysql中的函数
- select for update是属于排他锁,也属于悲观锁
- php ccbsign.rsasig,Maven整合SSM和建行龙支付
- 2022最新LOGO在线制作系统源码
- 1.个人建站的准备:购买云服务器和域名
- github unable to access 'https://github.com/...: Failed to connect to github.com port 443‘
- python *号的含义
- 食品专业本科生曝料中国食品行业黑幕
- python简单的加法问题_Python实现20以内加减法练习
热门文章
- centos之lnmp
- ipv6 “无状态地址分配”和 “有状态地址分配” 两种IPV6地址分配方式的区别说明
- from sklearn.cross_validation import train_test_split发生报错
- musicxml文档笔记(待续)
- java 好和不好的形容词 英语怎么说_不好的英文性格形容词
- 公务员计算机类面试真题及答案,2019国家公务员考试面试真题及答案——现象类题...
- 成年人应该看的小故事
- 蓝桥杯历届-巧排扑克牌
- 解决ECharts两条条折线图数据一样时,拐点处数据重合(设置ECharts两条折线图拐点处数据一上一下)
- 使用MRT批量提取modis数据所需波段(HDF转TIF)