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)

核心算法思想如下:

  1. 图(a): 在原音频信号中取一帧(左边红色框),并加窗处理。

  2. 图(b): 在一个范围内(左边蓝色框)选取第二帧,这个帧的相位参数和第一帧(红色宽)的相位对齐。

  3. 图(c): 在另外一个范围(右上角蓝色大框)中查找和第二帧最相似的第三帧(右上角蓝色大框中的红色框)

  4. 图(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音频变速不变调的原理分析相关推荐

  1. 音视频开发之旅(66) - 音频变速不变调的原理

    目录 声音的基本知识 时域压扩(TSM)的原理 波形相似叠加(WSOLA) 资料 收获 音频的原始pcm数据是由 采样率.采样通道数以及位宽而定.常见的音频采样率是44100HZ,即一秒内采样4410 ...

  2. 如何实现音频变速不变调、变调不变速、变速又变调?

    变速和变调相关的音频处理库有SoundTouch,Sonic,RubberBand(https://breakfastquay.com/rubberband/). RubberBand暂时没有调试成功 ...

  3. Phase Vocoder的补充完善,Matlab音频变速不变调、变调不变速

    之前有站内朋友介绍了国外的Phase Vocoder,Matlab音频变速不变调.变调不变速(Phase Vocoder)_cyz0612的博客-CSDN博客_matlab变调不变速代码参考这篇文章, ...

  4. Matlab音频变速不变调、变调不变速(Phase Vocoder)

    其他文章有讲过一些方法,有OLA.WSOLA算法.LSEE-MSTFTM.Phase Vocoder(相位声码器)等等,但都是讲了个大概,没一个能说清楚的,代码就更没有了.找了一个国外的Phase V ...

  5. 变速外挂案例及原理分析

    众所周知,游戏在运行中需要以帧为单位播放画面,而计算每帧动画播放所需时间,则需要调用C库函数来获取系统时间.如: // 获取当前精确时间 gettimeofday; // 获取系统时间 clock_g ...

  6. 一行代码实现音频变速不变调

    更多信息可参见:https://www.computationalimaging.cn/2019/11/blog-post.html 准备:需安装ffmpeg 方法: 命令行输入 ffmpeg -n ...

  7. 音视频开发之旅(67) - 变速不变调之sonic源码分析

    目录 基音周期.浊音的概念 Sonic源码分析 资料 收获 上一篇我们学习了音频变速不变调的原理以及WSOLA波形相似叠加算法进行时域压扩处理.其中在寻找相似帧方面,Sonic采用AMDF(平均幅度差 ...

  8. 音频变速变调原理及 soundtouch 代码分析

    音频变速变调原理及 soundtouch 代码分析 作者:floer rivor 2021 年 4 月 30 日 本文字数:5066 字 阅读完需:约 17 分钟 概述 音频变速变调在不同的场景可以分 ...

  9. OpenSL ES利用SoundTouch实现PCM音频的变速和变调

    我的视频课程(基础):<(NDK)FFmpeg打造Android万能音频播放器> 我的视频课程(进阶):<(NDK)FFmpeg打造Android视频播放器> 我的视频课程(编 ...

最新文章

  1. 单周期十条指令CPU设计与verilog实现(Modelsim)
  2. Android系统手机端抓包方法
  3. 已知x=python是一种非常好的编程语言-为什么用Python,高级的Python是一种高级编程语言...
  4. Notepad++ 列编辑操作实例二则
  5. 第7章:项目成本管理习题总结
  6. oracle修改某表中的顺序,oracle 数据库 , 表中字段顺序修改
  7. python 连接oracle_常用的Python库,给大家分享一下!
  8. 区块链安全-以太坊智能合约静态分析
  9. Learning中的代数结构的建立
  10. php 写一个大富翁游戏,抽奖系列:如何用纯js做一个大富翁游戏
  11. dns文件传输服务器,MOOC云计算 - DNS三部曲之DNS区域传输限制
  12. mysql中乘积函数_Mysql中的函数
  13. select for update是属于排他锁,也属于悲观锁
  14. php ccbsign.rsasig,Maven整合SSM和建行龙支付
  15. 2022最新LOGO在线制作系统源码
  16. 1.个人建站的准备:购买云服务器和域名
  17. github unable to access 'https://github.com/...: Failed to connect to github.com port 443‘
  18. python *号的含义
  19. 食品专业本科生曝料中国食品行业黑幕
  20. python简单的加法问题_Python实现20以内加减法练习

热门文章

  1. centos之lnmp
  2. ipv6 “无状态地址分配”和 “有状态地址分配” 两种IPV6地址分配方式的区别说明
  3. from sklearn.cross_validation import train_test_split发生报错
  4. musicxml文档笔记(待续)
  5. java 好和不好的形容词 英语怎么说_不好的英文性格形容词
  6. 公务员计算机类面试真题及答案,2019国家公务员考试面试真题及答案——现象类题...
  7. 成年人应该看的小故事
  8. 蓝桥杯历届-巧排扑克牌
  9. 解决ECharts两条条折线图数据一样时,拐点处数据重合(设置ECharts两条折线图拐点处数据一上一下)
  10. 使用MRT批量提取modis数据所需波段(HDF转TIF)