Audio-Worklet ist jetzt standardmäßig verfügbar

Hongchan Choi

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.

<ph type="x-smartling-placeholder">
</ph> Diagramm zum globalen Hauptumfang und zum Umfang des Audio-Worklet <ph type="x-smartling-placeholder">
</ph> Fig.1

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.

<ph type="x-smartling-placeholder">
</ph> Audio-Worklet-Knoten und Prozessordiagramm <ph type="x-smartling-placeholder">
</ph> Fig.2

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.

<ph type="x-smartling-placeholder">
</ph> Fig.2 <ph type="x-smartling-placeholder">
</ph> Fig.2

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.