Web Audio API实现简单变声效果

news2024/12/23 5:03:51

前言

想在网页中实现实时音频变声效果该如何实现呢,之前遇到这种处理音视频的需求,可能会想到需要借助C代码实现。但是现在随着浏览器性能的提升、web API的丰富,通过浏览器原生的API也可以操作音频数据实现很多复杂的效果,为web音频开发提供了更多的选择。下面介绍几种采用原生Web Audio API实现变声效果的过程中尝试的几种方案,感兴趣的同学一起来了解下吧。

说明:本文讨论范围为变声场景中的变速变调方案,有其它两种场景:变速不变调、变调不变速需求的同学请移步参考链接或其它方案

Web Audio API介绍

开始之前先简单了解下Web Audio APIWeb Audio API提供了一组在web上操作音频的API,可以使开发者自选音频数据来源,为音频添加效果,使声音可视化,为声音添加空间效果等功能。音频的输入流可以理解为一组buffer,来源可以是读取音频文件产生到内存中的AudioBufferSourceNode,也可以是来自HTML中audio标签的MediaElementAudioSourceNode,也可以是来自音频流(例如麦克风)的MediaStreamAudioSourceNode。例如,采集自己设备上的麦克风声音连接到扬声器:

// 创建音频上下文
const audioContext = new AudioContext();
// 获取设备麦克风流
stream = await navigator.mediaDevices
  .getUserMedia({ audio: true})
  .catch(function (error) {
    console.log(error);
  });
// 创建来自麦克风的流的声音源
const sourceNode = audioContext.createMediaStreamSource(stream);
// 将声音连接的扬声器
sourceNode.connect(audioContext.destination);

就可以对着麦克风说话听到自己的声音了。对上述来源数据流的处理被设计成一个个的节点(Node),具有模块化路由的特点,需要添加什么样的效果添加什么样的node,例如一个最常见的操作是通过把输入的采样数据放大来达到扩音器的作用(GainNode),示例代码:

// 创建音频上下文
const audioContext = new AudioContext();
// 创建一个增益Node
const gainNode = audioCtx.createGain();
// 获取设备麦克风流
stream = await navigator.mediaDevices
  .getUserMedia({ audio: true})
  .catch(function (error) {
    console.log(error);
  });
// 创建来自麦克风的流的声音源
const sourceNode = audioContext.createMediaStreamSource(stream);
// 将声音经过gainNode处理
sourceNode.connect(gainNode);
// 将声音连接的扬声器
gainNode.connect(audioContext.destination);
// 设置声音增益,放大声音
gainNode.gain.value = 2.0;

以上只是连接了声音放大的node,如果想要增加其它效果,可以继续往上添加node连接connect,例如滤波器(BiquadFilterNode)、立体声控制(StereoPannerNode)、对信号进行扭曲(WaveShaperNode)等等。这种模块化设计提供了灵活的创建动态效果和复合音频的方法,是不是有种变魔法的感觉,哪里修改点哪里(添加Node)非常方便。例如,以下展示了一个利用 AudioContext 创建四项滤波器节点(Biquad filter node)的例子:

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// 创建多个不同作用功能的node节点
var analyser = audioCtx.createAnalyser();
var distortion = audioCtx.createWaveShaper();
var gainNode = audioCtx.createGain();
var biquadFilter = audioCtx.createBiquadFilter();
var convolver = audioCtx.createConvolver();

// 将所有节点连接在一起

source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(biquadFilter);
biquadFilter.connect(convolver);
convolver.connect(gainNode);
gainNode.connect(audioCtx.destination);

// 控制双二阶滤波器

biquadFilter.type = "lowshelf";
biquadFilter.frequency.value = 1000;
biquadFilter.gain.value = 25;

可以看到为声音流添加处理效果就像穿项链一样,一个接一个,最后得到最终效果,实现效果可以参考官方样例voice-change-o-matic。一个简单而典型的 web audio 流程如下:

  1. 创建音频上下文

  2. 在音频上下文里创建源 — 例如, 振荡器,流

  3. 创建效果节点,例如混响、双二阶滤波器、平移、压缩

  4. 为音频选择一个目的地,例如你的系统扬声器

  5. 连接源到效果器,对目的地进行效果输出

dd002571613d92c528e1d5866df097d9.png

变声效果实现

首先回顾一下声音的基础知识,声音是由物体振动产生的机械波,常接触到的有以下三个特性:

  • 频率:频率越大,音调越高;频率越小,音调越低。

  • 振幅:振幅越大,音量(响度)越大;振幅越小,音量越小。

  • 音色:即波形,听声辨人的主要依据

这里说的变声效果是改变声音的音调,变声效果根据不同的场景可以分为变速不变调、变调不变速以及变调又变速 3 种。变速是指把一个语音在时域上拉长或缩短,而声音的采样率、基频以及共振峰都没有发生变化。变调是指把语音的基因频率降低或升高,共振峰做出相应的改变,采样频率不变。各种方案应用场景如下:

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

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

  3. 变速变调:改变声音播放速率情况下,音调音色也会随着改变,例如玩过磁带的都知道,按快进功能会使声音变尖提高音调,慢放功能使声音变粗,降低音调。

前两种实现都要求对声音知识领域有更深的了解,声音时域、频域,信号的傅里叶变换变化都要去重新去复习一下,学习成本比较高,这里使用第3种方式,比较好接入。要改变声音的播放速率,Web Audio API中提供了AudioBufferSourceNodeplaybackRate属性,可以设置音频的播放速率,使用音频上下文AudioContext.createBufferSource获得实例,示例代码如下:

const play = ()=> {
  const audioSrc = ref("src/assets/sample_orig.mp3")
  const url = audioSrc.value
  const request = new XMLHttpRequest()
  request.open('GET', url, true)
  request.responseType = 'arraybuffer'

  request.onload = function() {
    const audioData = request.response
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  
    audioCtx.decodeAudioData(audioData, (audioBuffer) => {
      let source = audioCtx.createBufferSource();
      source.buffer = audioBuffer;
      // 改变声音播放速率,2倍播放
      source.playbackRate.value = 2;
      source.connect(audioCtx.destination);
      source.start(0);
    });
  }
  request.send()
}

可以调整source.playbackRate.value的值来改变音调,大于1提高音调,小于1降低音调。

虽然实现了变声效果,但是这种方式只适合播放音频文件,或者能获取到完整音频流的情况,对于获取麦克风这种持续输入的声音流并不适用,类似的还有SoundTouchJS,它是某大佬实现的SoundTouch的JS版本,使用也是要获取完整音频的数据流,作者也做了相应的解释,参考链接28de5a2682905030155f409a6f915506.png如何处理麦克风获取的实时音频流呢,这里可以借助Web Audio API中的ScriptProcessorNode,它允许使用 JavaScript 生成、处理、分析音频。处理流程图如下:e9669fba44c7f1c8108e1f4069528b5d.png利用它将实时音频流数据处理一下,得到慢放或加速的声音流数据。示例代码如下:

const audioprocess = async () => {
  const audioContext = new AudioContext();

  // 采集麦克风输入声音流
  let stream = await navigator.mediaDevices
    .getUserMedia({ audio: true})
    .catch(function (error) {
      console.log(error);
    });

  const sourceNode = audioContext.createMediaStreamSource(stream);

  const processor = audioContext.createScriptProcessor(4096, 1, 1);
  processor.onaudioprocess = async event => {
    // 处理回调中拿到输入声音数据
    const inputBuffer = event.inputBuffer;
    // 创建新的输出源
    const outputSource = audioContext.createMediaStreamDestination();
    const audioBuffer = audioContext.createBufferSource();
    audioBuffer.buffer = inputBuffer;
    // 设置声音加粗,慢放0.7倍
    audioBuffer.playbackRate.value = 0.7
    audioBuffer.connect(outputSource);
    audioBuffer.start();

    // 返回新的 MediaStream
    const newStream = outputSource.stream;
    const node = audioContext.createMediaStreamSource(newStream)
    // 连接到扬声器播放
    node.connect(audioContext.destination)
  };
  // 添加处理节点
  sourceNode.connect(processor);
  processor.connect(audioContext.destination)
}

另外,还有一个利用Google开源jungle实现的改变音调的库,并且还有各种混响效果,音频可视化等炫酷功能,也是使用的Web Audio API实现,github链接地址放在这里了,有兴趣也可以体验下,画面长这样52c954ff2317c6bf2fa6998d16d06464.png

总结

以上就是对Web Audio API的简单介绍和使用的分析,以及采用Web Audio API实现声音简单变声效果的几种实现,大家有哪些更好的实现方案欢迎评论区一起交流!

参考

https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

https://github.com/cwilso/Audio-Input-Effects

https://mdn.github.io/voice-change-o-matic/

https://github.com/cutterbl/SoundTouchJS

https://cloud.tencent.com/developer/news/818606

https://zhuanlan.zhihu.com/p/110278983

https://www.nxrte.com/jishu/3146.html

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

14daa515ca2baedaafa18ab0458c0e94.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/614520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mitt用100行实现发布-订阅模式还收获了9k的Star

我们先了解什么是发布-订阅模式,发布-订阅模式它是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到状态的通知。 发布-订阅模式流程如下: 订阅者将自己想订阅的事件注册到调度中心;…

快手 Flink 的稳定性和功能性扩展

摘要:本文整理自快手技术专家刘建刚,在 Flink Forward Asia 2022 生产实践专场的分享。本篇内容主要分为四个部分: 1. 快手 Flink 平台 2. 稳定性保障和智能运维 3. 复杂场景下的功能扩展 4. 批处理的定制优化 Tips:点击「阅读原文…

maven添加指定仓库和镜像

maven历史版本下载地址:https://archive.apache.org/dist/maven/maven-3/ maven版本和java版本的关系:https://maven.apache.org/docs/history.html 项目中添加仓库 有两种方式 1.在pom.xml中添加(优先级高) 2.在setting.xml中添…

【已解决】-Mac/Mac mini/Macbook上修改鼠标指针大小

问题: MacOS默认的鼠标指针太小了,平时很容易找不到在哪里。 解决办法: 在 Mac 上,点击苹果菜单 >“系统偏好设置”,然后点按“辅助功能” 。 点击“显示”,然后点击“指针”。 设定以下任一选项…

【SQL】Oracle实现远程访问

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle,是甲骨文公司的一款关系…

CDN和Web加速器之间的区别

在数字时代,网站、社交媒体、电子商务、内容流平台和超个性化网络体验激增。因此,需要实时可靠地为最终用户提供大量生成的内容,而不会出现延迟或崩溃,无论其位置、网络、设备或浏览器如何。为此,使用CDN和web加速器就…

【模电实验】运算放大器构成的温度闭环控制系统的研究

运算放大器构成的温度闭环控制系统的研究 —、实验目的 设计并连接运算放大器构成的温度闭环控制系统,测量并调试该闭环控制系统,初步形成闭环控制的概念。 二、温度闭环控制系统的工作原理 图1所示为温度闭环控制系统框图,各部分工作原理…

vue3中setup语法糖那些事儿

vue3中setup语法糖那些事儿 什么是语法糖&#xff1f;vue3相比于vue2有什么新的语法糖?refreactivewatch生命周期Teleport setup语法糖<script setup>与传统vue3有何不同传统vue3和setup语法糖&#xff0c;两者之间写法如何转换&#xff1f; 什么是语法糖&#xff1f; …

NodeJs性能分析工具

&#xff08;头等人&#xff0c;有本事&#xff0c;没脾气&#xff1b;二等人&#xff0c;有本事&#xff0c;有脾气&#xff1b;末等人&#xff0c;没本事&#xff0c;大脾气。——南怀瑾&#xff09; NodeJs内存分析的必要性 回顾过去&#xff0c;我们排查web应用问题的途径…

EasyRecovery16计算机电脑硬盘格式化数据恢复软件

EasyRecovery16是一款综合性的数据恢复工具&#xff0c;软件具备非常成熟的算法&#xff0c;适用于多种数据丢失情况&#xff0c;可以帮助用户快速恢复不同介质丢失的数据。easyrcovery软件共有三种版本&#xff0c;分别为个人版、专业版、企业版。这三种版本的软件都可以免费使…

市场类型与完全竞争市场

短期完全竞争市场 区分市场类型的几条标准&#xff1a; 生产者的数量商品的同质性&#xff08;差异化程度&#xff09;进出市场的障碍信息是否完全 市场类型&#xff1a; 完全垄断寡头垄断垄断性竞争完全竞争 完全竞争市场的特征&#xff1a; 企业&#xff1a;数目多&…

Python学习41:文本分析(1)——统计文件中的字符

描述‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ open(name[, mode[, bufferi…

MySQL了解之复制(一)

1.1、复制解决的问题 数据复制技术有以下一些特点&#xff1a; (1) 数据分布 (2) 负载平衡(load balancing) (3) 备份 (4) 高可用性(high availability)和容错 1.2、复制如何工作 从高层来看&#xff0c;复制分成三步&#xff1a; (1) master将改变记录到二进制…

第二十六章 开发Productions - ObjectScript Productions - 定义业务流程

文章目录 第二十六章 开发Productions - ObjectScript Productions - 定义业务流程介绍业务逻辑工具的比较 第二十六章 开发Productions - ObjectScript Productions - 定义业务流程 业务流程负责生产中的更高级别处理。本页介绍它们并讨论如何设计和开发业务流程类。 介绍 …

三分钟快速了解什么是MES系统

近年来在制造业的推动下&#xff0c;大家是否会经常听到MES系统这一词&#xff0c;但是对于其具体能解决什么问题却不是很清晰。接下来&#xff0c;请允许我用一个简单的故事来让大家快速地了解一下到底什么是MES系统以及MES系统能够解决什么问题。本文仅限于科普&#xff0c;没…

STM32F103xx 的USART1 移植到STM32F105RBT6

1. STM32F103 和 STM32F105 的时钟配置区别&#xff0c;STM32F105 默认使用的外部晶振是25Mhz&#xff0c;需要改成8Mhz stm32f10x.h #if !defined HSE_VALUE#ifdef STM32F10X_CL#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz …

初学Mybatis

1、Mybatis概述 Mybatis基于java的持久层框架&#xff0c;内部封装了JDBC&#xff0c;开发者只需要关注SQL语句 特点 1、sql语句在xml里&#xff0c;便于统一管理和优化 2、解除sql与程序代码耦合&#xff1a;通过DAO层&#xff0c;将业务逻辑和数据访问逻辑分离 提供映射标签&…

Spark简介和三种部署方式

1.Spark简介 1.1 Spark介绍 开源集群计算系统&#xff0c;致力于更快的处理数据 Both fast to run and fast to wrtie Spark 是专为大规模数据处理而设计的快速通用的计算引擎 Spark 可以完成各种运算&#xff0c;包括 SQL 查询、文本处理、机器学习等 Spark由Scala语言开发&a…

【618期间】超过200小时的课程全都有优惠,全年最好的加入有三AI学习的时间来了~...

正值2023年618期间&#xff0c;既然是全民购物节&#xff0c;有三AI所有付费的视频课程开启优惠活动&#xff0c;即日起至节日结束&#xff08;6月18日晚23:59&#xff09;。 当前已有课程包括数据使用/模型分析/图像分类/图像分割/目标检测/图像生成/图像翻译/图像增强/视频分…

Promise.allSettled使用

1、const apiList await Promise.allSettled([systemApi]).then((result: any) > { if (result[0].status fulfilled) { console.log(result[0].value) return result[0].value.data } }) console.log(apiList, apiList) 2、 const systemApi new Promise((resolve, rej…