Web音频API:将带宽友好的声音添加到您的Web页面

本文由Mark BrownJosh Wedekind进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

在壁炉前听留声机声音的人

Web Audio API允许开发人员使用JavaScript在浏览器中利用强大的音频处理技术,而无需插件。 除了实时定义和处理基于文件的音频源外,它还可以基于各种波形来合成声音。 这对于经常在低带宽网络上使用的Web应用程序很有用。

在本教程中,我将通过介绍一些更有用的方法向您介绍Web Audio API。 我将演示如何将其用于加载和播放mp3文件以及向用户界面( demo )添加通知声音。

如果您喜欢这篇文章,并且想更深入地探讨这个主题,那么我将为SitePoint Premium制作一个由5部分组成的截屏视频系列,名为“ 您还没听说过!”!

我可以使用Web Audio API做什么?

生产中API的用例多种多样,但最常见的包括:

  • 实时音频处理,例如将混响添加到用户的语音中
  • 产生游戏音效
  • 向用户界面添加通知声音

在本文中,我们最终将编写一些代码来实现第三个用例。

浏览器是否支持良好?

Chrome,Edge,Firefox,Opera和Safari支持Web Audio。 就是说,在编写Safari时,Safari认为该浏览器功能是实验性的,并且需要webkit前缀。

我可以使用audio-api吗? 来自caniuse.com的主要浏览器对audio-api功能的支持数据。

使用API

Web Audio API的入口点是一个称为AudioContext的全局构造函数。 实例化后,它提供了用于定义符合AudioNode接口的各种节点的方法。 这些可以分为三类:

  • 源节点–例如MP3源,合成源
  • 效果节点–例如平移
  • 目标节点–由AudioContext实例公开为destination ; 这表示用户的默认输出设备,例如扬声器或耳机

可以使用connect方法这些节点链接为多种组合。 这是使用Web Audio API构建音频图的一般想法。

使用AudioContext构建音频图
资料来源: MDN

这是将MP3文件转换为AudioBufferSourceNode并通过AudioContext实例的destination节点播放它的示例:

请参阅CodePen上的SitePoint@SitePoint使用Web Audio API播放MP3文件的笔。

产生音频

除了通过AudioBufferSourceNode支持录制的音频AudioBufferSourceNode ,Web Audio API还提供了另一个名为OscillatorNode的源节点。 它允许针对指定波形生成频率。 但这实际上意味着什么?

在高水平上,频率确定以Hz为单位的声音的音高。 频率越高,音高越高。 除了自定义波形, OscillatorNode还提供了一些预定义的波形,可以通过实例的type属性来指定它们:

OscillatorNode支持的内置波形
资料来源: Omegatron / Wikipedia

  • 'sine' –听起来类似于吹口哨
  • 'square' –通常用于与旧的视频游戏机合成声音
  • 'triangle' –正弦波和方波的混合体
  • 'sawtooth' –产生强烈的嗡嗡声

这是一个示例,说明如何使用OscillatorNode实时合成声音:

请参阅CodePen上的SitePoint@SitePoint使用OscillatorNode生成 Pen的声音

OscillatorNode如何使Web受益?

与代码合成声音的能力将导致有效载荷比使用文件小得多。 这对于在从2G到4G的各种带宽上保持应用程序的奇偶校验非常重要。 无法保证移动数据连接的速度,特别是在新兴市场中。

在2G或3G上使用移动互联网的用户中有48%无法感知2G和3G服务之间的任何差异。

爱立信, 不断变化的移动宽带格局

为了证明这一点,我记录了上面的OscillatorNode示例,并使用允许相同音质的比特率将其编码为MP3文件。 生成的文件为10 KB,根据Chrome Dev Tools的网络限制功能,通过常规2G连接加载将需要2.15秒的时间。 在这种情况下,程序化方法无疑是赢家。

使用OscillatorNode发出通知声音

让我们在真实示例中使用OscillatorNode 。 我在文章开头提到,我们将通知声音添加到用户界面。 如果打开此CodePen ,则会看到一个消息传递应用程序UI。 单击发送按钮后,将出现一条通知,通知我们该消息已发送。 该样板包含我们感兴趣的两个部分: 一个称为contextAudioContext实例,以及一个名为playSound的函数。

开始之前,单击“ 叉子”按钮。 这将创建样板副本,您可以在其中保存更改。

值得一提的是,我已经在Chrome和Firefox中对此进行了测试,因此您应该使用其中一种浏览器。

playSound ,声明一个名为oscillatorNode playSound的变量,并为其分配context.createOscillator()的返回值:

const oscillatorNode = context.createOscillator();

接下来,让我们配置节点。 将其type属性设置为'sine' ,将frequency.value属性设置为150

oscillatorNode.type = 'sine';
oscillatorNode.frequency.value = 150;

要通过扬声器或耳机播放正弦波,请调用oscillatorNode.connect ,并将其传递给context.destination节点。 最后,让我们呼唤oscillatorNode.start ,其次是oscillatorNode.stop ,传递到它的参数context.currentTime + 0.5 ; 根据AudioContext's硬件调度时间戳,这将在500毫秒后停止声音。 现在,我们的playSound方法如下所示:

function playSound() {
  const oscillatorNode = context.createOscillator();

  oscillatorNode.type = 'sine';
  oscillatorNode.frequency.value = 150;

  oscillatorNode.connect(context.destination);
  oscillatorNode.start();
  oscillatorNode.stop(context.currentTime + 0.5);
}

保存更改并单击“ 发送”后 ,我们将听到通知音。

介绍GainNode

不用说,这非常扎眼。 为什么不使用效果节点使声音听起来更令人愉悦? GainNode是效果节点的一个示例。 增益是改变输入信号幅度的一种方式,在我们的情况下,它使我们能够控制音频源的音量。

oscillatorNode gainNode的声明下方,声明另一个名为gainNode变量,并为其分配context.createGain()的返回值:

const gainNode = context.createGain();

oscillatorNode gainNode的配置下,将gainNodegain.value属性设置为0.3。 这将以原始音量的30%播放声音:

gainNode.gain.value = 0.3;

最后,要将GainNode添加到我们的音频图中, gainNode传递给oscillatorNode.connect gainNode ,然后调用gainNode.connect ,我们将传递context.destination

function playSound() {
  const oscillatorNode = context.createOscillator();
  const gainNode = context.createGain();

  oscillatorNode.type = 'sine';
  oscillatorNode.frequency.value = 150;

  gainNode.gain.value = 0.3;

  oscillatorNode.connect(gainNode);
  gainNode.connect(context.destination);

  oscillatorNode.start();
  oscillatorNode.stop(context.currentTime + 0.5);
}

保存更改并单击“ 发送”后 ,我们将听到声音更安静地播放。

用AudioParam混合事物

您可能已经观察到,为了设置OscillatorNode的频率和GainNode的增益,我们必须设置一个名为value的属性。 签订合同的原因是gainfrequency都是AudioParam 。 该界面不仅可以用于设置特定值,还可以用于计划的,逐渐变化的值。 AudioParam公开了许多方法和属性,但是三个重要的方法是:

  • setValueAtTime –在给定时间立即更改值
  • linearRampToValueAtTime –计划在给定的结束时间内逐步线性变化的值
  • exponentialRampToValueAtTime –计划值的逐渐,指数变化。 与恒定的线性变化相反,随着调度程序接近结束时间,指数变化将以更大的增量增加或减少。 这可能是更可取的,因为它听起来更人耳

现在,我们将以指数方式增加频率和增益。 为了使用exponentialRampToValueAtTime方法,我们需要安排一个先验事件。 用对oscillatorNode.frequency.value节点oscillatorNode.frequency.value的调用来替换oscillatorNode.frequency.value oscillatorNode.frequency.setValueAtTime 。 传递相同的150 Hz频率,并通过传递context.currentTime作为第二个参数立即对其进行调度:

oscillatorNode.frequency.setValueAtTime(150, context.currentTime);

setValueAtTime调用下方,调用oscillatorNode.frequency.exponentialRampToValueAtTime setValueAtTime ,其值为500 Hz。 从排定的开始时间开始排定0.5秒:

oscillatorNode.frequency.exponentialRampToValueAtTime(500, context.currentTime + 0.5);

保存并单击“ 发送”后 ,您会听到频率随着播放的进行而增加。

总结一下,用与我们的OscillatorNode频率相同的方式调用gainNode.gain.setValueAtTime替换gainNode.gain.value的设置:

gainNode.gain.setValueAtTime(0.3, context.currentTime);

要淡出声音,请在0.5秒内以指数方式将增益提高到0.01

function playSound() {
  const oscillatorNode = context.createOscillator();
  const gainNode = context.createGain();

  oscillatorNode.type = 'sine';
  oscillatorNode.frequency.setValueAtTime(150, context.currentTime);
  oscillatorNode.frequency.exponentialRampToValueAtTime(500, context.currentTime + 0.5);

  gainNode.gain.setValueAtTime(0.3, context.currentTime);
  gainNode.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 0.5);

  oscillatorNode.connect(gainNode);
  gainNode.connect(context.destination);

  oscillatorNode.start();
  oscillatorNode.stop(context.currentTime + 0.5);
}

点击“ 保存发送”后 ,您会听到我们的通知声音随着时间的推移变得越来越安静。 现在,我们听起来更人性化了。

这是完成的演示。

请参阅CodePen上的SitePoint@SitePoint使用OscillatorNode发出的笔通知声音

重播源节点

在结束本文之前,重要的一点是要注意那些对API有所了解的人的困惑。 要在声音播放完毕后再次播放,写这样似乎很有意义:

oscillatorNode.start();
oscillatorNode.stop(context.currentTime + 0.5);
oscillatorNode.start(context.currentTime + 0.5);
oscillatorNode.stop(context.currentTime + 1);

完成此操作后,我们将观察到抛出InvalidStateError ,并且消息cannot call start more than once

AudioNode的创建成本AudioNode ,因此Web Audio API的设计鼓励开发人员在需要时重新创建音频节点。 在我们的例子中,我们将不得不再次调用playSound函数。

结论

希望您喜欢使用Web Audio API进行声音合成的入门。 我们已经展示了它的许多用例之一,尽管网站和Web应用程序上通知声音的增加是一个有趣的UX问题,它将随着时间的流逝而得到回答。

如果您想了解有关Web Audio API的更多信息,我将为SitePoint Premium制作一个由5部分组成的截屏视频系列,名为“ 您还没听说过!”。 。 现在可以观看第一集。

您是否在网页和应用程序中使用Web Audio API? 我很想在下面的评论中听到您的经验和用例。

From: https://www.sitepoint.com/web-audio-api-add-sound-to-web-page/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Html5网页纯JavaScript录制MP3音频 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Html5网页JavaScript录制MP3音频</title> <meta charset="utf-8" /> </head> <body> Html5网页JavaScript录制MP3音频 录制 停止 上传 调试信息: [removed][removed] [removed] var recorder = new MP3Recorder({ debug:true, funOk: function () { btnStart.disabled = false; log('初始化成功'); }, funCancel: function (msg) { log(msg); recorder = null; } }); var mp3Blob; function funStart(button) { btnStart.disabled = true; btnStop.disabled = false; btnUpload.disabled = true; log('录音开始...'); recorder.start(); } function funStop(button) { recorder.stop(); btnStart.disabled = false; btnStop.disabled = true; btnUpload.disabled = false; log('录音结束,MP3导出中...'); recorder.getMp3Blob(function (blob) { log('MP3导出成功'); mp3Blob = blob; var url = URL.createObjectURL(mp3Blob); var div = document.createElement('div'); var au = document.createElement('audio'); var hf = document.createElement('a'); au.controls = true; au.src = url; hf.href = url; hf.download = new Date().toISOString() + '.mp3'; hf[removed] = hf.download; div.appendChild(au); div.appendChild(hf); recordingslist.appendChild(div); }); } function log(str) { recordingslist[removed] += str + ''; } function funUpload() { var fd = new FormData(); var mp3Name = encodeURIComponent('audio_recording_' + new Date().getTime() + '.mp3'); fd.append('mp3Name', mp3Name); fd.append('file', mp3Blob); var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { recordingslist[removed] += '上传成功:' + mp3Name + ''; } }; xhr.open('POST', 'upload.ashx'); xhr.send(fd); } [removed] </body> </html> [javascript] view plain copy 在CODE上查看代码片派生到我的代码片 (function (exports) { var MP3Recorder = function (config) { var recorder = this; config = config || {}; config.sampleRate = config.sampleRate || 44100; config.bitRate = config.bitRate || 128; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; if (navigator.getUserMedia) { navigator.getUserMedia({ audio: true }, function (stream) { var context = new AudioContext(), microphone = context.createMediaStreamSource(stream), processor = context.createScriptProcessor(16384, 1, 1),//bufferSize大小,输入channel数,输出channel数 mp3ReceiveSuccess, currentErrorCallback; config.sampleRate = context.sampleRate; processor.onaudioprocess = function (event) { //边录音边转换 var array = event.inputBuffer.getChannelData(0); realTimeWorker.postMessage({ cmd: 'encode', buf: array }); }; var realTimeWorker = new Worker('js/worker-realtime.js'); realTimeWorker.onmessage = function (e) { switch (e.data.cmd) { case 'init': log('初始化成功'); if (config.funOk) { config.funOk(); } break; case 'end': log('MP3大小:', e.data.buf.length); if (mp3ReceiveSuccess) { mp3ReceiveSuccess(new Blob(e.data.buf, { type: 'audio/mp3' })); } break; case 'error': log('错误信息:' + e.data.error); if (currentErrorCallback) { currentErrorCallback(e.data.error); } break; default: log('未知信息:', e.data); } }; recorder.getMp3Blob = function (onSuccess, onError) { currentErrorCallback = onError; mp3ReceiveSuccess = onSuccess; realTimeWorker.postMessage({ cmd: 'finish' }); }; recorder.start = function () { if (processor && microphone) { microphone.connect(processor); processor.connect(context.destination); log('开始录音'); } } recorder.stop = function () { if (processor && microphone) { microphone.disconnect(); processor.disconnect(); log('录音结束'); } } realTimeWorker.postMessage({ cmd: 'init', config: { sampleRate: config.sampleRate, bitRate: config.bitRate } }); }, function (error) { var msg; switch (error.code || error.name) { case 'PERMISSION_DENIED': case 'PermissionDeniedError': msg = '用户拒绝访问麦客风'; break; case 'NOT_SUPPORTED_ERROR': case 'NotSupportedError': msg = '浏览器不支持麦客风'; break; case 'MANDATORY_UNSATISFIED_ERROR': case 'MandatoryUnsatisfiedError': msg = '找不到麦客风设备'; break; default: msg = '无法打开麦克风,异常信息:' + (error.code || error.name); break; } if (config.funCancel) { config.funCancel(msg); } }); } else { if (config.funCancel) { config.funCancel('当前浏览器不支持录音功能'); } } function log(str) { if (config.debug) { console.log(str); } } } exports.MP3Recorder = MP3Recorder; })(window);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值