音频处理中的变调和时间拉伸(一)

2023-10-31 23:30

本文主要是介绍音频处理中的变调和时间拉伸(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

介绍

很多用过磁带或者塑胶唱片的人都会对于一种现象感到熟悉:当我们快放或者慢放音频的时候,如果我们用两倍速度快放,除了会使得音频播放时间减少一半,还会使得音高升高一个八度,听起来音频中的人声会很像卡通动画人物的声音。同时,如果慢放会使得播放时长增长并且降低相应比例的音高。

以前使用模拟音频录音技术的时候,这种现象可以通过设置错误的播放速度来复现。现在,在数字信号处理领域,同样的现象可以通过重采样来实现。

重采样会同时改变音频的播放时长与音高,但是有时人们会有需求:只改变音高或者只改变播放时长。这类技术会被称为:

  • time/pitch scaling,
  • time/pitch shifting,
  • time stretching.

应用

Time scaling 可以慢放音乐速度,方便大家跟着跳舞或者拍视频再或者练习乐器。慢放讲话录音可以帮助人们转写,或者学习语言,盲人可能会使用快放来播放一些音频书籍来节约时间。视频网站上的倍速播放页需要音频跟着加倍但是不会变调。

类似的,在卡拉OK或者练习唱歌的时候,调整音乐的音调或者key,可以更好的匹配演唱者的声线。或者用来调音,百万调音师你们懂得。

最后,有些人可能想要通过改变他们自己声音的音调来实现身份隐藏。

实现方式

目前有两种实现上述技术的基本方式,即在时域处理或者频域处理

时域处理方式直接操作采样数据,比如后面要介绍的SOLA算法。时域处理的优势在于实现非常直接(straight-forward),因为音频数据处理时的采样格式跟它播放时或者录制时相同。缺点在于会产生一些人造回响导致失真,并且随着更大的时间调整,失真更明显。比如时间伸缩超过15%时。

频域处理是将采样声音转换为短时的 频率/振幅 成分然后在频域信息上来做伸缩,相位声码器是这类方法的一个典型。频域处理的优势在于可以支持更复杂的声音调整给出更好的听感,因为人类听力根本上只是基于频率的。

然而,由于它们明显的强大和优雅,频域处理实现起来更加复杂,计算复杂度更高。所以受限于计算资源,比如cpu速度和内存等。

SOLA

SOLA即 Synchronous-OverLap-Add,同步交叠相加法。通过将声音数据切成一系列的很短的长约几十到几百毫秒的片段,然后将这些片段通过一定的手段:跳过某些内容或者重复某些内容,重新排列起来达到比原始音频更长或者更短的播放时间。使用相同思路的算法还有TDHS(Time-Domain Harmonic Sampling),WSOLA和PSOLA,他们的区别在于实现细节。

为了避免在两个片段连接处的声音出现过于明显的断裂感,两个片段会有一定的重叠部分,所以声音的振幅从一个片段到另一个片段是渐变的,所以SOLA名字中有OverLap-Add部分。

最简单的SOLA实现可以使用统一的片段长度,然后每隔一段均匀的间隔来取一段片段。如果你想让声音缩短10%的长度,假设我们使用100毫秒(+叠加的长度)的片段长度,然后以110毫秒为间隔,从原始音频中取片段,然后将这些片段通过叠加连接起来,你就获得了想要的效果。同样的如果要延长10%的长度,选择100毫秒的片段,然后每隔90毫秒取一个片段,最后连接起来即可。是不是很简单呢?

然而,实际应用中的SOLA实现起来并没有那么简单。选取片段的时候不管片段内容的话,即使采用了渐变叠加的方法还是会由于过大的不连贯而出现噪音。(注:这里的不连贯是指的采样点不构成一个波形,虽然其实采样点实际上是连贯的,但是不构成波形的话,发出的声音也是噪音)

实现考虑

为了满足音质要求,SOLA在实现上需要在选择片段时,使得相邻片段之间交叠部分尽量相似。

实际上,音频流每次处理一个片段,为了使得相邻两个片段之间更匹配,在选择下一个片段的时候,可以在一个合适的窗口范围内来寻找。一种寻找最匹配片段的方法是通过计算上一个片段结尾部分和窗口内的待选片段的开头部分的互相关性,具有最高互相(cross-correlation)关值的两个片段的头尾是最相似的。这些片段最后通过交叠的方式连接在一起,形成了新的音频流,并且与原始的音频流长度不同。

SOLA的总体算法如下图所示。图中坐标轴的范围是可以任意设定的,所代表的时间单位仅做示意。在算法执行过程中,原始的音频会被切成合适长度的若干片段。新的片段会在与前一片段合适的间隔后被选出,从而获得想要的伸缩效果。

图1
在图1中,第一个片段从0开始,然后长度是7个时间单位,首尾各有一个时间单位的交叠部分。每隔9个时间单位取一个片段,最终的时间伸缩比例为 (7-2)/9 = 0.555,也就表示相对原始音频缩短了44.5%的时长。

然而,实际上在取片段时,并不是严格按照名义上的时间间隔来取的,而是取自以时间间隔处为中心的一个窗口范围内。比如图1中的“New Sequence”实际上是取自第8个时间单位到第10个时间单位之间,以便让新的片段和前一个片段在交叠时可以更好的重合。在图1下半部分我们可以看到交叠后的结果。

互相关函数对于评估音频片段的相似度很有效,并且也易于实现。同时,还有一些其它的相似度测量函数也被提出。其中一种方式是将片段的边缘与声音的节拍对齐,如果声音有一定的节拍并且可以被检测到,那么这种方式可以降低产生的类似混响感的人工处理痕迹。还有一种方式是评估频域频谱的相似度,而不是时域的波形相似度。

SOLA只需要基础的加法和乘法,因此可以使用整型或者定点数运算来实现,防止浮点数运算不被支持或者效率不高。

多声道处理

处理立体声时,片段交叠操作应当是在多个声道同样的位置进行的,如果我们单独处理每个声道,最终可能会造成声道之间错位。

所以我们可以同时处理所有声道,比如,可以将所有声道相加后再通过互相关函数找到共同的最优的片段获取点。

使用SOLA进行变调

SOLA只有拉伸功能,但是如果和重采样技术结合起来就可以实现变调功能了,很简单,假如要升高八度,只需要下采样音频到原始长度的一半,然后再使用SOLA拉伸到原始长度即可。

修音痕迹与参数

使用SOLA处理过的音频会有一点点回声的痕迹,这种痕迹会随着拉伸的幅度的增大而变得更加明显。

有一种减轻回声痕迹的方式是通过根据被处理音频的基本频率来选择处理片段时长。(这里的基本频率不是f0,意思是被处理音频的节拍频率)

处理时窗口范围的选择也会影响音质,越宽的窗口范围一般会获得更好的交叠音频的匹配度。但是,太宽的话产生的音频会不稳定,听起来像是在“漂移”。

交叠长度,一般是整个处理片段长度的一部分。通常使用线性的响度变化会表现的更好。

这篇关于音频处理中的变调和时间拉伸(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/318855

相关文章

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

resultMap如何处理复杂映射问题

《resultMap如何处理复杂映射问题》:本文主要介绍resultMap如何处理复杂映射问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录resultMap复杂映射问题Ⅰ 多对一查询:学生——老师Ⅱ 一对多查询:老师——学生总结resultMap复杂映射问题

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

Feign Client超时时间设置不生效的解决方法

《FeignClient超时时间设置不生效的解决方法》这篇文章主要为大家详细介绍了FeignClient超时时间设置不生效的原因与解决方法,具有一定的的参考价值,希望对大家有一定的帮助... 在使用Feign Client时,可以通过两种方式来设置超时时间:1.针对整个Feign Client设置超时时间

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义