Chrome 64 为 Web Audio API 推出了备受期待的新功能: AudioWorklet。本单元将向您介绍创建 自定义音频处理器和 JavaScript 代码。请参阅 现场演示。 本系列的下一篇文章音频 Worklet 设计模式 可能是构建高级音频应用的有趣读物。
背景:ScriptProcessorNode
Web Audio API 中的音频处理在独立于主线程的线程中运行 界面线程,以便它顺畅运行。要在 在 JavaScript 中,Web Audio API 建议了一个 ScriptProcessorNode, 事件处理脚本,以便在主界面线程中调用用户脚本。
这种设计存在两个问题:事件处理是异步的
而且代码执行在主线程上执行。前者
导致延迟,而后者会加大
通常与各种界面和 DOM 相关的任务拥挤,导致出现上述两个界面
更改为“卡顿”或音频出现“故障”。由于存在这个基本的设计缺陷
规范中废弃了 ScriptProcessorNode
,
替换为 AudioWorklet。
概念
音频 Worklet 将用户提供的 JavaScript 代码
音频处理线程。也就是说,无需跳转到主页面,
线程处理音频。这意味着用户提供的脚本代码
在音频渲染线程 (AudioWorkletGlobalScope
) 中与其他资源一起
内置 AudioNodes
,可确保零额外延迟和同步
呈现。
注册和实例化
使用音频 Worklet 包括两部分:AudioWorkletProcessor
和
AudioWorkletNode
。这比使用 ScriptProcessorNode 更复杂
但开发者需要实现对自定义音频的低级功能
处理。AudioWorkletProcessor
表示实际的音频处理器
使用 JavaScript 代码编写而成,位于 AudioWorkletGlobalScope
中。
AudioWorkletNode
是 AudioWorkletProcessor
的对应项,并采用
处理与主线程中的其他 AudioNodes
之间的连接。它
在主全局作用域中公开,其功能类似于常规 AudioNode
。
以下提供了一对代码段,分别展示了注册和 实例化。
// The code in the main global scope.
class MyWorkletNode extends AudioWorkletNode {
constructor(context) {
super(context, 'my-worklet-processor');
}
}
let context = new AudioContext();
context.audioWorklet.addModule('processors.js').then(() => {
let node = new MyWorkletNode(context);
});
如需创建 AudioWorkletNode
,您必须添加 AudioContext
对象和处理器名称(字符串形式)。处理器定义可以是
由新的 Audio Worklet 对象的 addModule()
调用加载和注册。
包括音频 Worklet 在内的 Worklet API 仅在
安全上下文,因此
虽然http://localhost
是
被视为安全的本地测试
您可以将 AudioWorkletNode
子类化,以定义
由在 Worklet 上运行的处理器支持的自定义节点。
// This is the "processors.js" file, evaluated in AudioWorkletGlobalScope
// upon audioWorklet.addModule() call in the main global scope.
class MyWorkletProcessor extends AudioWorkletProcessor {
constructor() {
super();
}
process(inputs, outputs, parameters) {
// audio processing code here.
}
}
registerProcessor('my-worklet-processor', MyWorkletProcessor);
AudioWorkletGlobalScope
中的 registerProcessor()
方法接受一个
要注册的处理器名称和类定义的字符串。
完成全局范围内的脚本代码评估后,
AudioWorklet.addModule()
的承诺将得到解决,通知用户
表明类定义已准备好在主全局作用域中使用。
自定义音频参数
AudioNode 的一项实用功能是可调度参数
使用 AudioParam
实现自动化。AudioWorkletNode 可以使用
可以自动以音频速率控制的公开参数。
用户定义的音频参数可以在 AudioWorkletProcessor
中声明
定义一组 AudioParamDescriptor
。通过
底层 WebAudio 引擎会提取
然后构建 AudioWorkletNode,
AudioParam
对象相应地分配给该节点。
/* A separate script file, like "my-worklet-processor.js" */
class MyWorkletProcessor extends AudioWorkletProcessor {
// Static getter to define AudioParam objects in this custom processor.
static get parameterDescriptors() {
return [{
name: 'myParam',
defaultValue: 0.707
}];
}
constructor() { super(); }
process(inputs, outputs, parameters) {
// |myParamValues| is a Float32Array of either 1 or 128 audio samples
// calculated by WebAudio engine from regular AudioParam operations.
// (automation methods, setter) Without any AudioParam change, this array
// would be a single value of 0.707.
const myParamValues = parameters.myParam;
if (myParamValues.length === 1) {
// |myParam| has been a constant value for the current render quantum,
// which can be accessed by |myParamValues[0]|.
} else {
// |myParam| has been changed and |myParamValues| has 128 values.
}
}
}
AudioWorkletProcessor.process()
方法
实际的音频处理发生在process()
AudioWorkletProcessor
。它必须由类中的用户来实现
定义。WebAudio 引擎以等时时间调用此函数。
以提供输入和参数以及提取输出。
/* AudioWorkletProcessor.process() method */
process(inputs, outputs, parameters) {
// The processor may have multiple inputs and outputs. Get the first input and
// output.
const input = inputs[0];
const output = outputs[0];
// Each input or output may have multiple channels. Get the first channel.
const inputChannel0 = input[0];
const outputChannel0 = output[0];
// Get the parameter value array.
const myParamValues = parameters.myParam;
// if |myParam| has been a constant value during this render quantum, the
// length of the array would be 1.
if (myParamValues.length === 1) {
// Simple gain (multiplication) processing over a render quantum
// (128 samples). This processor only supports the mono channel.
for (let i = 0; i < inputChannel0.length; ++i) {
outputChannel0[i] = inputChannel0[i] * myParamValues[0];
}
} else {
for (let i = 0; i < inputChannel0.length; ++i) {
outputChannel0[i] = inputChannel0[i] * myParamValues[i];
}
}
// To keep this processor alive.
return true;
}
此外,process()
方法的返回值可用于
控制 AudioWorkletNode
的生命周期,以便开发者
内存占用从 process()
方法标记返回 false
处理器处于非活动状态,并且 WebAudio
引擎不再调用
方法。如需使处理器保持活动状态,方法必须返回 true
。
否则,该节点和处理器对由系统进行垃圾回收
最终。
与 MessagePort 进行双向通信
有时,自定义 AudioWorkletNode
想要公开
映射到 AudioParam
,例如基于字符串的 type
属性
用于控制自定义过滤器。为了实现这一目的
AudioWorkletNode
和AudioWorkletProcessor
配备了
MessagePort
用于双向通信。任何类型的自定义数据
都可通过该渠道进行交换
可以使用节点上的 .port
属性访问 MessagePort,
处理器。节点的 port.postMessage()
方法会将消息发送到
关联处理器的 port.onmessage
处理程序,反之亦然。
/* The code in the main global scope. */
context.audioWorklet.addModule('processors.js').then(() => {
let node = new AudioWorkletNode(context, 'port-processor');
node.port.onmessage = (event) => {
// Handling data from the processor.
console.log(event.data);
};
node.port.postMessage('Hello!');
});
/* "processors.js" file. */
class PortProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = (event) => {
// Handling data from the node.
console.log(event.data);
};
this.port.postMessage('Hi!');
}
process(inputs, outputs, parameters) {
// Do nothing, producing silent output.
return true;
}
}
registerProcessor('port-processor', PortProcessor);
MessagePort
支持转移
或 WASM 模块。此操作会打开
为如何使用音频 Worklet 系统提供了无数可能性。
演示:构建 GainNode
下面是 GainNode 基于
AudioWorkletNode
和AudioWorkletProcessor
。
index.html
文件会执行以下操作:
<!doctype html>
<html>
<script>
const context = new AudioContext();
// Loads module script with AudioWorklet.
context.audioWorklet.addModule('gain-processor.js').then(() => {
let oscillator = new OscillatorNode(context);
// After the resolution of module loading, an AudioWorkletNode can be
// constructed.
let gainWorkletNode = new AudioWorkletNode(context, 'gain-processor');
// AudioWorkletNode can be interoperable with other native AudioNodes.
oscillator.connect(gainWorkletNode).connect(context.destination);
oscillator.start();
});
</script>
</html>
gain-processor.js
文件会执行以下操作:
class GainProcessor extends AudioWorkletProcessor {
// Custom AudioParams can be defined with this static getter.
static get parameterDescriptors() {
return [{ name: 'gain', defaultValue: 1 }];
}
constructor() {
// The super constructor call is required.
super();
}
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const gain = parameters.gain;
for (let channel = 0; channel < input.length; ++channel) {
const inputChannel = input[channel];
const outputChannel = output[channel];
if (gain.length === 1) {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[0];
} else {
for (let i = 0; i < inputChannel.length; ++i)
outputChannel[i] = inputChannel[i] * gain[i];
}
}
return true;
}
}
registerProcessor('gain-processor', GainProcessor);
其中介绍了音频 Worklet 系统的基础知识。提供现场演示 (位于 Chrome WebAudio 团队的 GitHub 代码库中)。
功能转换:从实验性版本转换为稳定版
对于 Chrome 66 或更高版本,音频 Worklet 默认处于启用状态。在 Chrome 64 和 65 中,该功能位于实验性标记之后。