Chrome 64 มาพร้อมฟีเจอร์ใหม่ใน Web Audio API ที่ทุกคนรอคอยอย่าง AudioWorklet ในส่วนนี้ คุณจะได้เรียนรู้แนวคิดและการใช้งานเพื่อสร้างโปรแกรมประมวลผลเสียงที่กําหนดเองด้วยโค้ด JavaScript ดูการสาธิตแบบสด บทความถัดไปในชุดนี้คือรูปแบบการออกแบบเวิร์กเลตเสียง ซึ่งอาจเป็นสิ่งที่น่าสนใจในการอ่านเพื่อสร้างแอปเสียงขั้นสูง
เบื้องหลัง: ScriptProcessorNode
การประมวลผลเสียงใน Web Audio API จะทำงานในเธรดแยกต่างหากจากเธรด UI หลัก เพื่อให้ทำงานได้อย่างราบรื่น Web Audio API เสนอ ScriptProcessorNode ซึ่งใช้ตัวแฮนเดิลเหตุการณ์เพื่อเรียกใช้สคริปต์ของผู้ใช้ในเธรด UI หลัก เพื่อเปิดใช้การประมวลผลเสียงที่กําหนดเองใน JavaScript
การออกแบบนี้มี 2 ปัญหา ได้แก่ การจัดการเหตุการณ์เป็นแบบแอซิงโครนัสโดยการออกแบบ และการเรียกใช้โค้ดเกิดขึ้นในเธรดหลัก รายการแรกจะทำให้เกิดเวลาในการตอบสนอง และรายการที่ 2 จะกดดันชุดข้อความหลักที่มักเต็มไปด้วยงานต่างๆ ที่เกี่ยวข้องกับ UI และ DOM ซึ่งทำให้ UI "กระตุก" หรือเสียง "ขัดข้อง" ข้อบกพร่องพื้นฐานด้านการออกแบบนี้ทำให้ ScriptProcessorNode
เลิกใช้งานจากข้อกำหนดและแทนที่ด้วย AudioWorklet
แนวคิด
เวิร์กเลตเสียงจะเก็บโค้ด JavaScript ที่ผู้ใช้ระบุไว้ทั้งหมดไว้ในเธรดการประมวลผลเสียง ซึ่งหมายความว่าไม่ต้องข้ามไปที่แธรดหลักเพื่อประมวลผลเสียง ซึ่งหมายความว่าโค้ดสคริปต์ที่ผู้ใช้ระบุจะทำงานในเธรดการแสดงผลเสียง (AudioWorkletGlobalScope
) พร้อมกับ AudioNodes
ในตัวอื่นๆ ซึ่งจะช่วยลดเวลาในการตอบสนองและการแสดงผลแบบซิงโครนัส
การลงทะเบียนและการสร้างอินสแตนซ์
การใช้เวิร์กเลตเสียงประกอบด้วย 2 ส่วน ได้แก่ AudioWorkletProcessor
และ
AudioWorkletNode
วิธีนี้ซับซ้อนกว่าการใช้ ScriptProcessorNode แต่จำเป็นต้องใช้เพื่อให้นักพัฒนาซอฟต์แวร์มีความสามารถระดับล่างสำหรับการประมวลผลเสียงที่กำหนดเอง AudioWorkletProcessor
แสดงถึงตัวประมวลผลเสียงจริงที่เขียนด้วยโค้ด JavaScript และอยู่ใน AudioWorkletGlobalScope
AudioWorkletNode
เป็นคู่ของ AudioWorkletProcessor
และดูแลการเชื่อมต่อจาก AudioNodes
อื่นๆ ในชุดข้อความหลัก โดยจะแสดงในขอบเขตส่วนกลางหลักและทํางานเหมือน AudioNode
ปกติ
ต่อไปนี้คือข้อมูลโค้ด 2 รายการที่แสดงการลงทะเบียนและการสร้างอินสแตนซ์
// 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 และชื่อตัวประมวลผลเป็นสตริง คุณสามารถโหลดและลงทะเบียนคําจํากัดความของโปรเซสเซอร์ได้โดยการเรียกใช้ addModule()
ของออบเจ็กต์เวิร์กเลตเสียงใหม่
Worklet API รวมถึง Audio Worklet ใช้ได้เฉพาะในบริบทที่ปลอดภัย ดังนั้นหน้าเว็บที่ใช้ Worklet API เหล่านี้ต้องแสดงผ่าน HTTPS แม้ว่า http://localhost
จะถือว่าปลอดภัยสําหรับการทดสอบในเครื่องก็ตาม
คุณสามารถกำหนดคลาสย่อยของ AudioWorkletNode
เพื่อกำหนดโหนดที่กำหนดเองซึ่งได้รับการสนับสนุนโดยโปรเซสเซอร์ที่ทำงานในเวิร์กเลต
// 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);
เมธอด registerProcessor()
ใน AudioWorkletGlobalScope
จะรับสตริงสำหรับชื่อของโปรเซสเซอร์ที่จะลงทะเบียนและคำจำกัดความของคลาส
หลังจากการประเมินโค้ดสคริปต์ในขอบเขตส่วนกลางเสร็จสมบูรณ์แล้ว ระบบจะแก้ไขการสัญญาจาก AudioWorklet.addModule()
โดยแจ้งให้ผู้ใช้ทราบว่าการกําหนดคลาสพร้อมใช้งานในขอบเขตส่วนกลางหลักแล้ว
พารามิเตอร์เสียงที่กำหนดเอง
ประโยชน์อย่างหนึ่งของ AudioNodes คือการทำงานอัตโนมัติด้วยพารามิเตอร์ที่ตั้งเวลาได้โดยใช้ AudioParam
AudioWorkletNodes สามารถใช้ข้อมูลเหล่านี้เพื่อรับพารามิเตอร์ที่แสดงซึ่งควบคุมอัตราเสียงได้โดยอัตโนมัติ
คุณประกาศพารามิเตอร์เสียงที่ผู้ใช้กำหนดได้ใน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 จะเรียกใช้ฟังก์ชันนี้ในลักษณะแบบ Isochronous เพื่อส่งอินพุตและพารามิเตอร์ รวมถึงดึงข้อมูลเอาต์พุต
/* 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
เพื่อให้นักพัฒนาแอปจัดการร่องรอยหน่วยความจำได้ การแสดงผล false
จากเมธอด process()
จะทําให้เครื่องประมวลผลไม่ทำงาน และเครื่องมือ WebAudio
จะไม่เรียกใช้เมธอดนั้นอีก วิธีการต้องแสดงผลเป็น true
เพื่อให้โปรเซสเซอร์ทำงานต่อไป
มิฉะนั้น ระบบจะเก็บขยะคู่โหนดและโปรเซสเซอร์ในท้ายที่สุด
การสื่อสารแบบ 2 ทิศทางด้วย MessagePort
บางครั้ง AudioWorkletNode
ที่กําหนดเองต้องการแสดงการควบคุมที่ไม่ได้แมปกับ AudioParam
เช่น แอตทริบิวต์ type
ที่อิงตามสตริงซึ่งใช้ควบคุมตัวกรองที่กําหนดเอง AudioWorkletNode
และ AudioWorkletProcessor
มี MessagePort
สำหรับการสื่อสารแบบ 2 ทิศทางเพื่อวัตถุประสงค์นี้และวัตถุประสงค์อื่นๆ คุณสามารถแลกเปลี่ยนข้อมูลที่กำหนดเองทุกประเภทผ่านช่องทางนี้ได้
คุณสามารถเข้าถึง MessagePort ได้ด้วยแอตทริบิวต์ .port
ทั้งในโหนดและตัวประมวลผล เมธอด 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 ข้ามขอบเขตของเธรดได้ ซึ่งเปิดโอกาสในการใช้งานระบบเวิร์กเลตเสียงได้นับไม่ถ้วน
คำแนะนำแบบทีละขั้นตอน: สร้าง 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);
หัวข้อนี้ครอบคลุมพื้นฐานของระบบเวิร์กเลตเสียง คุณสามารถดูการสาธิตเวอร์ชันที่ใช้จริงได้ที่ที่เก็บ GitHub ของทีม Chrome WebAudio
การเปลี่ยนผ่านฟีเจอร์จากเวอร์ชันทดลองเป็นเวอร์ชันเสถียร
โดยค่าเริ่มต้นแล้ว Chrome เวอร์ชัน 66 ขึ้นไปจะเปิดใช้เวิร์กเลตเสียง ใน Chrome เวอร์ชัน 64 และ 65 ฟีเจอร์นี้อยู่หลัง Flag ทดลอง