Web Audio 的最新动态

简介

我们已经有一段时间没有在 Hacks 上讨论 Web Audio API 了。但是,随着 Firefox 37/38 发布到我们的开发者版/Nightly 浏览器通道,有一些有趣的新功能值得讨论!

本文向您介绍了一些需要注意的新的 Web Audio 技巧,例如新的StereoPannerNode,基于 Promise 的方法等等。

简单的立体声声像定位

Firefox 37 引入了 StereoPannerNode 接口,它允许您简单轻松地为音频源添加立体声声像定位效果。它接受一个属性pan——一个 a-rate AudioParam,可以接受 -1.0(完全左声道声像定位)到 1.0(完全右声道声像定位)之间的数值。

但是我们不是已经有了 PannerNode 吗?

您可能已经使用过旧的 PannerNode 接口,它允许您在 3D 空间中定位声音。将声音源连接到 PannerNode 会导致它被“空间化”,这意味着它被放置到 3D 空间中,然后您可以指定监听器在其中的位置。浏览器随后会计算出如何使声音发出声音,应用声像定位和多普勒频移效果,以及其他一些不错的 3D“效果”(如果声音随时间推移而移动等)。

var audioContext = new AudioContext();
var pannerNode = audioContext.createPanner();

// The listener is 100 units to the right of the 3D origin
audioContext.listener.setPosition(100, 0, 0);

// The panner is in the 3D origin
pannerNode.setPosition(0, 0, 0);

这与基于 WebGL 的游戏配合得很好,因为这两种环境都使用类似的单位进行定位——一个由 x、y、z 值组成的数组。因此,您可以轻松更新PannerNode的位置、方向和速度,就像更新 3D 场景中实体的位置一样。

但是,如果您只是构建一个传统的音乐播放器,其中歌曲已经是立体声轨道,并且您实际上根本不关心 3D 呢?您需要经历比必要更复杂的过程,并且计算成本也可能更高。随着移动设备使用量的增加,您不执行的每个操作都会节省一点电池寿命,并且您网站的用户会因此而感谢您。

进入 StereoPannerNode

StereoPannerNode对于上面描述的简单立体声用例来说,这是一个更好的解决方案。您无需关心监听器的位置;您只需要将想要空间化的源节点连接到StereoPannerNode实例,然后使用pan参数。

要使用立体声声像定位器,首先创建一个StereoPannerNode使用 createStereoPanner(),然后将其连接到音频源。例如

var audioCtx = window.AudioContext();
// You can use any type of source
var source = audioCtx.createMediaElementSource(myAudio);
var panNode = audioCtx.createStereoPanner();

source.connect(panNode);
panNode.connect(audioCtx.destination);

要更改应用的声像定位量,只需更新pan属性值

panNode.pan.value = 0.5; // places the sound halfway to the right
panNode.pan.value = 0.0; // centers it
panNode.pan.value = -0.5; // places the sound halfway to the left

您可以在 http://mdn.github.io/stereo-panner-node/ 中查看完整的示例。

此外,由于pan是一个 a-rate AudioParam,因此您可以使用参数自动化设计漂亮的平滑曲线,并且每个样本都会更新这些值。如果您在多个 requestAnimationFrame 调用中更新值,则尝试随时间推移进行这种更改听起来会很奇怪和不自然。而且您无法自动执行PannerNode位置。

例如,以下是如何设置从左到右持续两秒的声像定位过渡

panNode.pan.setValueAtTime(-1, audioContext.currentTime);
panNode.pan.linearRampToValueAtTime(1, audioContext.currentTime + 2);

浏览器将为您处理更新pan值。现在,从最近开始,您还可以使用 Firefox Devtools Web Audio 编辑器 可视化这些曲线。

检测 StereoPannerNode 是否可用

您使用的 Web Audio 实现可能尚未实现此类型的节点。(在撰写本文时,它仅在 Firefox 37 和 Chrome 42 中受支持。)如果您尝试在StereoPannerNode这些情况下,您将生成一个漂亮的undefined is not a function错误。

要确保StereoPannerNode可用,只需检查createStereoPanner()方法是否存在于您的AudioContext:

if (audioContext.createStereoPanner) {
    // StereoPannerNode is supported!
}

如果不存在,则需要恢复到旧的PannerNode.

对默认值的更改PannerNode声像定位算法

中使用的默认声像定位算法类型PannerNode过去是HRTF,这是一种高质量算法,使用与人类相关的数据进行卷积来渲染其输出(因此非常逼真)。但是它也非常消耗计算资源,需要在其他线程中运行处理以确保平滑播放。

作者通常不需要如此高的质量水平,只需要“足够好”的东西,因此默认的PannerNode.type现在是equalpower,计算成本要低得多。如果您想恢复使用高质量声像定位算法,只需更改类型

pannerNodeInstance.type = 'HRTF';

顺便说一句,一个PannerNode使用type = 'equalpower'产生的算法与StereoPannerNode使用的算法相同。

基于 Promise 的方法

最近添加到 Web Audio 规范中的另一个有趣功能是 Promise 版本的某些方法。这些是 OfflineAudioContext.startRendering()AudioContext.decodeAudioData

以下部分显示了带有和不带 Promise 的方法调用方式。

OfflineAudioContext.startRendering()

假设我们要以 44100 Hz 生成一分钟的音频。我们首先创建上下文

var offlineAudioContext = new OfflineAudioContext(2, 44100 * 60, 44100);

经典代码

offlineAudioContext.addEventListener('oncomplete', function(e) {
    // rendering complete, results are at `e.renderedBuffer`
});
offlineAudioContext.startRendering();

基于 Promise 的代码

offlineAudioContext.startRendering().then(function(renderedBuffer) {
    // rendered results in `renderedBuffer`
});

AudioContext.decodeAudioData

同样,在解码音频轨道时,我们将首先创建上下文

var audioContext = new AudioContext();

经典代码

audioContext.decodeAudioData(data, function onSuccess(decodedBuffer) {
    // decoded data is decodedBuffer
}, function onError(e) {
    // guess what... something didn't work out well!
});

基于 Promise 的代码

audioContext.decodeAudioData(data).then(function(decodedBuffer) {
    // decoded data is decodedBuffer
}, function onError(e) {
    // guess what... something didn't work out well!
});

在这两种情况下,差异似乎并不大,但是如果您要依次组合 Promise 的结果,或者如果您在等待事件完成后再调用其他几个方法,则 Promise 确实有助于避免“回调地狱”。

检测对基于 Promise 的方法的支持

同样,如果您运行代码的浏览器不支持这些方法的新版本,则不希望收到可怕的undefined is not a function错误消息。

一种快速检查支持的方法:查看这些调用的返回类型。如果它们返回一个 Promise,那么我们很幸运。如果它们没有,我们必须继续使用旧方法

if((new OfflineAudioContext(1, 1, 44100)).startRendering() != undefined) {
    // Promise with startRendering is supported
}

if((new AudioContext()).decodeAudioData(new Uint8Array(1)) != undefined) {
    // Promise with decodeAudioData is supported
}

音频工作线程

尽管规范尚未最终确定,并且任何浏览器中都尚未实现,但仍然值得一提的是音频工作线程,它——您猜对了——是 Web Audio 代码使用的一种特殊类型的 Web 工作线程。

音频工作线程将取代几乎过时的 ScriptProcessorNode。最初,这是在音频图中运行您自己的自定义节点的方式,但它们实际上是在主线程上运行的,从而导致各种问题,从音频故障(如果主线程停滞)到无响应的 UI 代码(如果 ScriptProcessorNode 速度不够快以处理其数据)。

音频工作线程的最大特点是它们在自己的单独线程中运行,就像任何其他 Worker 一样。这确保了音频处理的优先级,并且避免了声音故障,而人耳对此非常敏感。

在 w3c web audio 列表 上正在进行讨论;如果您对这项和其他 Web Audio 开发感兴趣,则应该去查看一下。

Web 上音频的激动人心的时代!

关于 Soledad Penadés

Sole 在 Mozilla 的开发者工具团队工作,帮助人们在 Web 上制作令人惊叹的东西,最好是实时制作。在 irc.mozilla.org 上的 #devtools 频道找到她。

更多 Soledad Penadés 的文章…

关于 Chris Mills

Chris Mills 是 Mozilla 的高级技术作家,他撰写有关开放式 Web 应用程序、HTML/CSS/JavaScript、A11y、WebAssembly 等方面的文档和演示。他喜欢摆弄 Web 技术,并在会议和大学偶尔发表技术演讲。他曾为 Opera 和 W3C 工作,喜欢演奏重金属鼓和喝好啤酒。他和他的妻子以及三个可爱的孩子住在英国曼彻斯特附近。

更多 Chris Mills 的文章…


3 条评论

  1. Felipe

    哇,这对我是个新东西,有什么学习 Web Audio 入门的资源吗?
    谢谢

    2015 年 2 月 17 日 18:49

  2. sole

    https://mdn.org.cn/en-US/docs/Web/API/Web_Audio_API

    2015 年 2 月 19 日 09:41

    1. Lara H

      感谢 API 文档,这对我入门很有用……

      2015 年 3 月 6 日 03:51

本文的评论已关闭。