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

作者:floer rivor
  • 2021 年 4 月 30 日
  • 本文字数:5066 字

    阅读完需:约 17 分钟

概述

音频变速变调在不同的场景可以分为变速不变调、变调不变速以及变调又变速 3 种应用。语音变速是指把一个语音在时域上拉长或则缩短,而语音的采样率、基频以及共振峰都没有发生变化。语音变调是指把语音的基因频率降低或升高,共振峰做出相应的的改变,采样频率不变。简单介绍下音频变速变调的应用场景。

1) 变速不变调:各种各样的视频播放器中的 2 倍速,0.5 倍速播放就是应用的语音变速不变调原理;当然变速不变调还应用于网络电话 VOIP 中的应对网络抖动,简单的说,就是当网络不好的时候,播放端从网络中拉取到的数据少,缓存区的数据不够用,这个时候就使用缓存的数据播放的慢一点。反之,缓存区数据过多,就播放的快一点。这部分的实现可以参照 webrtc 的 netEQ 模块。平时在使用微信语音的时候应该能感受到网络特别卡时,为了保持语音连续,会故意慢放语音。

2)变调不变速:变调不变速主要应用在声效上,声音提高音调将男声变成女生,或则将女生变成男声;另外,变速不变调配合其他一些音效算法,如 EQ,混响,tremolo 和 vibrato 可以实现变声效果,比如 QQ 上的萝莉音,大叔音等。这里以声网的 MAC 端的音频 sdkdemo 为例,见下图

虽然我看不到其中的源码,但是图中红色部分肯定是涉及到了语音变调的实现的。

音频变速变调在各种音频编辑器如 cooledit,audition,audacity 上都有实现,常用的开源代码是 soundtouch,另外还有一个开源代码为 sonic,都可在 github 上找到。

音频变速原理

音频变速不变调的经典文章为<<A Review of Time-Scale Modification of Music Signals>>,其中

介绍了很多种变速不变调算法的实现。

TSM(Time-Scale Modifacaiton)

变速不变调的经典算法为 TSM(Time-Scale Modifacaiton),还有一些语音合成的方法来实现变速,通过提取音频的基音信息和声道的激励模型来实现。我们重点介绍 TSM 方法

TSM 方法的原理很简单。熟悉音频处理的朋友都知道,音频信号为了保证前后帧处理特征的平滑性,会在帧与帧之间设置一个重叠(overlap),因此就出现了分帧(analysis fames)和合帧(synthesis frames), 一般设置重叠为 50%。如果分帧(analysis fames)以 50%的 overlap,而合帧(synthesis frames)时以 75%,那么就实现了慢放。

TSM 算法的步骤为

  • step1: 原始信号分帧

  • step2:分解好的帧重新定位

  • step3: 合成最终的帧

Hs 和 Ha 分别代码分帧和合帧的 overlap。Rate = Hs/ Ha,如果 Ha=Hs,则原速;Hs<Ha 时,加速;Hs>Ha 时,减速。

OLA(Overlap-and-Add)

OLA(Overlap-and-Add, OLA)重叠相加算是音频变速算法中最简单的时域方法,它是后续时域算法(SOLA, SOLA-FS, TD-PSOLA, WSOLA)的基础。

下图演示语音被加速播放的情况

图中 x 和 y 分别表示处理前后的语音信息。变速的关键在 c 图和 d 图,能够看到 OLA 直接暴力的将 x(m+1)的波形拷贝到 y(m+1)处,并与 y(m)进行叠加。此时语音省略掉了 x(m)和 x(m+1)之间的信息,实现了快放。这个算法最简单,但是缺点也很明显,就是没有考虑到 x(m+1)和 y(m)之间的连续性,换句话说,没有考虑到不加速播放时本来该播放的语音 y'(m+1)和拷贝过来的 y(m+1)之间的相似性。因此会造成下图这样的后果,造成相位不连续,相邻帧重叠区域产生基频失真。

出现了基频断裂的问题,当然是要去解决了,因此出现了改进版的 SOLA 算法和 WSOLA 算法。y'(m+1)和 y(m)是连续的,只需找到一帧 y(m+1)最相似 y'(m+1),用它来替换 y'(m+1),那么语音就会很自然。SOLA 算法和 WSOLA 算法都是这个原理。不同的是 SOLA 算法是固定 y(m+1),去寻找 y'(m+1)与 y(m+1)最相似。WSOLA 则是固定 y'(m+1),寻找 y(m+1)与 y'(m+1)最相似。由于 soundtouch 中使用的是 WSOLA 算法,主要研究 WSOLA 算法,关于 SOLA 算法的具体处理可以看上面的那篇论文

WSOLA(Waveform Similarity Overlap-Add)

下图是 WSOLA 的实现步骤

a 图中的实现还是和 OLA 一样的,OLA 寻找找的替换帧为 x(m+1)。在 b 图中有一个 Delta(max),会在 Delta(max)这个窗口内寻找与替换帧最相似的那一帧 x'(m+1),这个就是 WSOLA 算法的原理。如何来定义相似呢,常用的方法有:

1)相关法(寻找相关峰,soundtouch 使用方法)

2)AMDF(sonic 使用的方法)

音频变速还有其他方法:PV-TSM 等,

音频变调不变速原理

最常见的音频变调就是使用重采样了,如果将一个 8Khz 的语音使用 16K 采样率播放,那么能明显感受到音调升高,但是语速也提高了 2 倍。因此,音频变调不变速就是首先使用重采样算法进行采样,然后使用变速不变调算法纠正速度。重采样其实就是对数据一个抽取或则内插的过程,常使用的方法是线性插值重采样的方法。

soundtouch 的原理

SoundTouch 是实现音频变速变调的开源代码。其中音频变调使用的是升降采样的方法,变速则是使用的是 wsola 算法。SoundTouch 的源代码目录结构清晰简单,在开源代码中有 vs 工程文件,soundtouch 的主要代码文件如下:

SoundTouch 中设置变速变调的范围,在其使用手册中各个范围为:

" -tempo=n : Change sound tempo by n percents (n=-95..+5000 %)\n" 变速不变调

" -pitch=n : Change sound pitch by n semitones (n=-60..+60 semitones)\n" 变调不变速

" -rate=n : Change sound rate by n percents (n=-95..+5000 %)\n" 变调变速

SoundTouch 的主要 API 是定义在 SoundTouch 类中的,SoundTouch 包含了主要的数据处理类 RateTransposer 和 TDStretch,SoundTouch 只负责数据之间的传递,和关键参数的控制。RateTransposer 实现语音的变速变调,TDStretch 实现变速不变调。SoundTouch 继承自 FIFOProcessor,在 rate<=1 的情况下会设置 TDStretch 为输出,在 rate>1 的情况下设置 RateTransposer 为输出。

变速不变调 TDStretch

变速不变调通过 wsola 实现,类中主要成员为:

class TDStretch : public FIFOProcessor{  protected:  int channels;  int sampleReq; //变速不变调 需要缓存的数据  int overlapLength; //wsola涉及的overlap  int seekLength; //在seekLength的长度里寻找与pMidBuffer相关值最大的offset  int seekWindowLength; //一帧数据的长度+overlap的长度  int sampleRate;  int sequenceMs; //默认40ms  int seekWindowMs; //默认15ms  int overlapMs;  //默认8ms 根据ms去计算length  float maxnormf;  double tempo;  double skipFract; //变速需要跳过的步长  SAMPLETYPE *pMidBuffer;  //缓存中间的数据,用于计算相关性  数据长度为overlap   FIFOSampleBuffer outputBuffer;  FIFOSampleBuffer inputBuffer;}

复制代码

overlapLength、seekLength、seekWindowLength 见下图。

以 tempo=2 overlapLength=128 seekLength = 240 seekWindowLength=640 为例。soundtouch 的处理流程为:

首先处理第一帧:

  1. 拷贝输入数据 inputBuffer 中 seekWindowLength-2*overlapLength = 384 的数据到 outputBuffer 中

  2. 拷贝 inputBuffer 从 384 起的 overlapLength=128 数据到 pMidBuffer 中

  3. inputBuffer 的数据跳跃到 skipFract = 648 的位置

其中 skipFract += nominalSkip

nominalSkip = tempo * (seekWindowLength - overlapLength);

  1. 计算 inputBuffer 中 648 位置起的 seekLength 个数据 与 pMidBuffer 的相关性 选取相关性最大的 offset 这里 offset = 119

  2. 将 648 + offset 起的 128 个数据与 pMindbuffer 中的 128 个数据叠加输出到 outputBuffer 中

  3. 拷贝 inputBuffer 648 + offset +128 位置起的 seekWindowLength-2*overlapLength = 384 的数据到 outputBuffer 中

  4. go on

具体实现为:

void TDStretch::processSamples(){int ovlSkip;int offset = 0;int temp;while ((int)inputBuffer.numSamples() >= sampleReq)  //缓存数据sampleReq{    if (isBeginning == false)    {        offset = seekBestOverlapPosition(inputBuffer.ptrBegin()); //寻找相关性最大的位置        overlap(outputBuffer.ptrEnd((uint)overlapLength), inputBuffer.ptrBegin(), (uint)offset); //overlap         outputBuffer.putSamples((uint)overlapLength);        offset += overlapLength;    }    else    {        isBeginning = false;        int skip = (int)(tempo * overlapLength + 0.5 * seekLength + 0.5);        #ifdef ST_SIMD_AVOID_UNALIGNED        // in SIMD mode, round the skip amount to value corresponding to aligned memory address        if (channels == 1)        {            skip &= -4;        }        else if (channels == 2)        {            skip &= -2;        }        #endif        skipFract -= skip;        assert(nominalSkip >= -skipFract);    }    if ((int)inputBuffer.numSamples() < (offset + seekWindowLength - overlapLength))    {        continue;    // just in case, shouldn't really happen    }    // length of sequence    temp = (seekWindowLength - 2 * overlapLength);    outputBuffer.putSamples(inputBuffer.ptrBegin() + channels * offset, (uint)temp);    assert((offset + temp + overlapLength) <= (int)inputBuffer.numSamples());    memcpy(pMidBuffer, inputBuffer.ptrBegin() + channels * (offset + temp),         channels * sizeof(SAMPLETYPE) * overlapLength);.    skipFract += nominalSkip;   // real skip size    ovlSkip = (int)skipFract;   // rounded to integer skip    skipFract -= ovlSkip;       // maintain the fraction part, i.e. real vs. integer skip    inputBuffer.receiveSamples((uint)ovlSkip);  //跳过数据 和tempo相关 实现变速的关键}}

复制代码

变速变调 RateTranposer 类

RateTransPoser 其实就是通过升降采样来实现语音的变速变调的。类具体实现如下:

class RateTransposer :   public FIFOProcessor{  protected:    /// Anti-alias filter object  AAFilter *pAAFilter; //抗混叠滤波器  TransposerBase *pTransposer; //提供实现变速变调的算法  FIFOSampleBuffer inputBuffer;  /// Buffer for keeping samples between transposing & anti-alias filter  FIFOSampleBuffer midBuffer;  /// Output sample buffer  FIFOSampleBuffer outputBuffer;  bool bUseAAFilter;  void processSamples(const SAMPLETYPE *src,                       uint numSamples);}

复制代码

其中核心函数 processSamples

void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples){  uint count;  if (nSamples == 0) return;  // 接受数据  inputBuffer.putSamples(src, nSamples);  // 数据处理  if (bUseAAFilter == false)  {  count = pTransposer->transpose(outputBuffer, inputBuffer);  return;  }  assert(pAAFilter);  if (pTransposer->rate < 1.0f)   {      // 实现升降采样      pTransposer->transpose(midBuffer, inputBuffer);         // AAFilter滤波      pAAFilter->evaluate(outputBuffer, midBuffer);  }   else    {      pAAFilter->evaluate(midBuffer, inputBuffer);      pTransposer->transpose(outputBuffer, midBuffer);  }}

复制代码

TransposerBase 类其实为一个工厂方法,其中涉及了 3 种升降采样方法,其实也就是插值方法。分别为:

插值算法原理待补充。

soundtouch 需要注意的点:

其中数据缓存和每次跳跃的大小为:

nominalSkip = tempo * (seekWindowLength - overlapLength);

sampleReq = max(intskip + overlapLength, seekWindowLength) + seekLength;

变速不变调中计算相关性根据不同的平台可以使用 TDStretchSSE 或 TDStretchMMX。

TDStreach 的实现来看,其具有较大的算法延迟。要实时实现减小延迟,可考虑减小 sequenceMs 的大小以及缓存数据来处理。

最后

简要的分析了一下 soundtouch 的实现,关于 soundtouch 的数据输入输出 FIFOProcessor 等类没有详细的介绍。后面会分析下 sonic 算法原理的实现。A Review of Time-Scale Modification of Music Signals 这篇经典文章的地址为:https://www.mdpi.com/2076-3417/6/2/57。文章中有什么不对的地方,希望大家一起讨论。

参考

A Review of Time-Scale Modification of Music Signals:https://www.mdpi.com/2076-3417/6/2/57

知乎变声导论:https://zhuanlan.zhihu.com/p/110278983

论文:基于 WSOLA 算法的语音时长调整研究

论文:AN OVERLAP-ADD TECHNIQUE BASED ON WAVEFORM SIMILARITY (WSOLA) FOR HIGH QUALITY TIME-SCALE MODIFICATION OF SPEECH

soundtouch 官网:http://www.surina.net/soundtouch/
版权声明: 本文为 InfoQ 作者【floer rivor】的原创文章。

原文链接:【https://xie.infoq.cn/article/6c522d10615dfaa4abe6df4f6】。文章转载请联系作者。

音频变速变调原理及 soundtouch 代码分析相关推荐

  1. 音频变速播放原理分析及实现方案

    [时间:2019-05] [状态:Open] [关键词:音频,audio,倍速,变速,变调,soundtouch,sonic] 音频变调变速原理分析 先来一段语音处理的理论: 变速变调可分为:变速不变 ...

  2. ffmpeg+soundtouch实现音频变速变调

    实现并不难 ,本人只贴出相关代码,想要详细了解的朋友可以另行百度一下 相关参数设置和部分用到的函数定义 typedef float SAMPLETYPE; #define BUFF_SIZE 6720 ...

  3. 变速变调原理与方法总结

    转载自:https://www.cnblogs.com/welen/p/3782896.html 变速变调原理与方法总结 变调和变速原理 自然语音的产生可以简化为图2-1模型,激励源出来的声门波信号与 ...

  4. SoundTouch实现音频变速变调

    音频基础 声音属性 响度(Loudness):音量,与声波的振幅有关 音调(Pitch):音调与声音的频率有关--声音频率越大时,音调就越高,否则就越低 音色(Quality):由物体结构特性所决定 ...

  5. MP3、PCM、WAV等音频基础格式编码总结与代码分析

    MP3文件在生活中可以说非常熟悉了,几乎每天豆豆它本身是一种二进制文件,本篇文章就来看看它内部是如何编码的. 本项目用到的代码可以参考(其实核心的都在下边,最多不用移植了而已): https://gi ...

  6. linux ecc校验原理,Nand ECC校验和纠错原理及ECC代码分析

    校验码生成算法的C语言实现 在Linux内核中ECC校验算法所在的文件为drivers/mtd/nand/nand_ecc.c,其实现有新.旧两种,在2.6.27及更早的内核中使用的程序,从2.6.2 ...

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

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

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

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

  9. OpenStack 虚拟机冷/热迁移的实现原理与代码分析

    目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...

最新文章

  1. 哈工大计算机专业去哪里工作,想读计算机专业,哈工深和华科应该选择哪个?...
  2. c语言什么是合法的变量名,在C语言中,下列合法的变量名包括
  3. python编程300集免费-python 300本电子书合集
  4. Java 8 - Stream流骚操作解读2_归约操作
  5. PHPStorm不能修改PHP langauge level
  6. 超融合基础架构需要完全更换现有网络吗?
  7. input el-input 打印是取不到值 print()
  8. html实现点击切换页面,JavaScript实现的简单Tab点击切换功能示例
  9. Ubuntu20.04安装OpenCV3.4.15
  10. 织梦响应式酒店民宿住宿类网站织梦模板(自适应手机端)
  11. java cron在线_在线cron生成器
  12. 电源完整性之Cadence Sigrity Power DC_电热协同仿真
  13. 康佳电视系统升级服务器地址,【当贝市场】康佳智能电视本地升级教程
  14. chat后缀域名_.chat域名简介
  15. b站的服务器在哪个文件夹,b站缓存的视频在哪个文件 具体操作步骤
  16. python 邮件合并的基本操作步骤_邮件合并操作过程
  17. 关于新冠疫情,美国专家们终于认定了这9大事实
  18. 左声道,右声道和立体声
  19. Java绘制正态分布统计图
  20. 列车运行图的编制原则是什么_列车运行图的编制有什么要求?

热门文章

  1. 植物的生长与模拟之一:概述
  2. 痞子衡嵌入式:导致串行NOR Flash在i.MXRT下无法正常下载/启动的常见因素之QE bit...
  3. 手写文字识别软件有哪些?教你怎么识别手写文字
  4. 鼠标滚轮滚动切换内容
  5. 《现代控制理论》第一章常见题型及解法
  6. 淘宝助理5.5官方版最新版
  7. 广域网优化的技术实现和展望
  8. 个人电脑重装WINDOWN XP 论坛 1
  9. ibmt60桌面怎么没有计算机,ibm t60
  10. 校企合作培育未来新工匠