Chrome 64 bietet eine mit Spannung erwartete neue Funktion der Web Audio API: AudioWorklet erstellt werden. Hier lernen Sie die Konzepte und Verwendungsmöglichkeiten kennen, mit denen Sie benutzerdefinierten Audioprozessor mit JavaScript-Code. Werfen Sie einen Blick auf die Live-Demos ansehen. Im nächsten Artikel der Reihe, Audio-Worklet-Designmuster, könnte ein interessanter Artikel zum Erstellen einer erweiterten Audio-App sein.
Hintergrund: ScriptProcessorNode
Die Audioverarbeitung im Web Audio API wird in einem anderen Thread als der Hauptthread ausgeführt. damit sie reibungslos läuft. Um die benutzerdefinierte Audioverarbeitung in JavaScript schlug das Web Audio API einen ScriptProcessorNode vor, mit dem Event-Handler zum Aufrufen eines Nutzerskripts im Haupt-UI-Thread.
Bei diesem Design gibt es zwei Probleme: Die Ereignisverarbeitung erfolgt asynchron.
und die Codeausführung erfolgt im Hauptthread. Erster
löst die Latenz aus, wodurch Letzteres den Hauptthread
häufig mit verschiedenen UI- und DOM-bezogenen Aufgaben überfüllt, was dazu führt,
zu „reißen“ oder Audio zu Störungen führen. Aufgrund dieses grundlegenden Designfehlers
ScriptProcessorNode
wurde aus der Spezifikation eingestellt und
durch AudioWorklet ersetzt.
Konzepte
Das Audio-Worklet speichert den vom Nutzer bereitgestellten JavaScript-Code im
einen Thread in der Audioverarbeitung. Es ist also nicht nötig, zum Hauptbildschirm der
um Audio zu verarbeiten. Das bedeutet, dass der
vom Nutzer bereitgestellte Skriptcode
im Audio-Rendering-Thread (AudioWorkletGlobalScope
) zusammen mit anderen
integrierte AudioNodes
, die keine zusätzliche Latenz und synchrone
zu verbessern.
Registrierung und Instanziierung
Das Audio-Worklet besteht aus zwei Teilen: AudioWorkletProcessor
und
AudioWorkletNode
. Dies ist komplexer als
ScriptProcessorNode,
aber um Entwicklern die Low-Level-Funktionalität für benutzerdefinierte Audioinhalte zu bieten,
Datenverarbeitung. AudioWorkletProcessor
steht für den tatsächlichen Audioprozessor.
in JavaScript-Code geschrieben und befindet sich in der AudioWorkletGlobalScope
.
AudioWorkletNode
ist das Gegenstück zu AudioWorkletProcessor
und enthält
die Verbindung zu und von anderen AudioNodes
im Hauptthread. Es
wird im globalen Hauptbereich bereitgestellt und funktioniert wie ein regulärer AudioNode
.
Hier ist ein Paar Code-Snippets, die die Registrierung und die Instanziierung.
// 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);
});
Sie müssen einen AudioContext hinzufügen, um ein AudioWorkletNode
zu erstellen
und den Namen des Prozessors als String. Eine Prozessordefinition kann
geladen und durch den Aufruf addModule()
des neuen Audio-Worklet-Objekts registriert.
Worklet-APIs mit Audio-Worklet sind nur in einer
sicheren Kontext bereitstellen.
Seite, für die sie verwendet werden, muss über HTTPS bereitgestellt werden. http://localhost
ist jedoch
für lokale Tests als sicher betrachtet.
Sie können AudioWorkletNode
ableiten, um
einen benutzerdefinierten Knoten, der von dem auf dem Worklet ausgeführten Prozessor unterstützt wird.
// 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);
Die Methode registerProcessor()
in AudioWorkletGlobalScope
nimmt eine
String für den Namen des zu registrierenden Prozessors und die Klassendefinition.
Nach Abschluss der Skriptcodebewertung auf globaler Ebene
Das Versprechen von AudioWorklet.addModule()
wird gelöst, indem Nutzer benachrichtigt werden
dass die Klassendefinition zur Verwendung im globalen Hauptbereich bereit ist.
Benutzerdefinierte Audioparameter
Ein nützlicher Vorteil von AudioNodes ist der planbare Parameter
Automatisierung mit AudioParam
. AudioWorkletNodes können diese verwenden, um
, die automatisch über die Audiorate gesteuert werden können.
Benutzerdefinierte Audioparameter können in einem AudioWorkletProcessor
deklariert werden.
Klassendefinition, indem eine Gruppe von AudioParamDescriptor
eingerichtet wird. Die
die zugrunde liegende WebAudio-Engine diese Informationen während der
Konstruktion eines AudioWorkletNode und erstellt und verknüpft
AudioParam
-Objekten entsprechend an.
/* 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.
}
}
}
Methode AudioWorkletProcessor.process()
Die eigentliche Audioverarbeitung erfolgt in der Callback-Methode process()
im
AudioWorkletProcessor
. Sie muss von einem Nutzer in der Klasse implementiert werden.
Definition. Die WebAudio-Engine ruft diese Funktion in einem isochronen
um Eingaben und Parameter einzuspeisen und Ausgaben abzurufen.
/* 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;
}
Darüber hinaus kann der Rückgabewert der Methode process()
für Folgendes verwendet werden:
steuern die Lebensdauer von AudioWorkletNode
, sodass Entwickler
des Arbeitsspeicher-Fußabdrucks. false
von process()
-Methodenmarkierungen zurückgeben
ist der Prozessor inaktiv und die WebAudio
-Engine ruft den
. Die Methode muss true
zurückgeben, damit der Prozessor aktiv bleibt.
Andernfalls ist das Knoten- und Prozessorpaar eine automatische Speicherbereinigung vom System
werden.
Bidirektionale Kommunikation mit MessagePort
Manchmal möchte ein benutzerdefiniertes AudioWorkletNode
Steuerelemente freigeben, die keine
Zuordnung zu AudioParam
, z. B. ein stringbasiertes type
-Attribut
zum Steuern eines benutzerdefinierten Filters. Zu diesem Zweck und darüber hinaus
AudioWorkletNode
und AudioWorkletProcessor
sind mit einem
MessagePort
für die bidirektionale Kommunikation. Jede Art von benutzerdefinierten Daten
über diesen Kanal ausgetauscht werden können.
Auf MessagePort kann mit dem Attribut .port
sowohl auf dem Knoten als auch auf dem
für den Datenverarbeiter. Die Methode port.postMessage()
des Knotens sendet eine Nachricht an
port.onmessage
-Handler des zugehörigen Prozessors und umgekehrt.
/* 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
unterstützt übertragbare Elemente,
oder ein WASM-Modul
über die Thread-Grenze übertragen. Dies öffnet sich
zahllose Möglichkeiten, wie das Audio Worklet-System eingesetzt werden kann.
Schritt-für-Schritt-Anleitung: GainNode erstellen
Hier ist ein vollständiges Beispiel für GainNode auf der Basis von
AudioWorkletNode
und AudioWorkletProcessor
.
Die Datei 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>
Die Datei 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);
Hier werden die Grundlagen des Audio-Worklet-Systems behandelt. Es sind Live-Demos verfügbar. im GitHub-Repository des Chrome WebAudio-Teams.
Funktionsübergang: Von Experimental zur stabilen Version
Audio-Worklet ist ab Chrome 66 standardmäßig aktiviert. In Chrome 64 und 65 lag die Funktion hinter dem experimentellen Flag.