Chrome 64 có một tính năng mới được mong đợi trong API Web âm thanh: AudioWorklet. Tại đây, bạn sẽ tìm hiểu các khái niệm và cách sử dụng để tạo bộ xử lý âm thanh tuỳ chỉnh có mã JavaScript. Hãy xem bản minh hoạ trực tiếp. Bài viết tiếp theo trong loạt bài, Mẫu thiết kế Worklet âm thanh có thể là thông tin thú vị về cách xây dựng một ứng dụng âm thanh nâng cao.
Nền: ScriptProcessorNode
Tính năng xử lý âm thanh trong Web Audio API chạy trong một luồng riêng biệt với luồng chính Luồng giao diện người dùng để hoạt động trơn tru. Cách bật tính năng xử lý âm thanh tuỳ chỉnh trong JavaScript, API Web Audio đã đề xuất một ScriptProcessorNode sử dụng trình xử lý sự kiện để gọi tập lệnh người dùng trong luồng giao diện người dùng chính.
Có hai vấn đề trong thiết kế này: việc xử lý sự kiện không đồng bộ
theo thiết kế và việc thực thi mã diễn ra trên luồng chính. Tuỳ chọn trước đây
gây ra độ trễ và sau đó gây áp lực lên luồng chính
thường chứa nhiều tác vụ khác nhau liên quan đến UI và DOM, khiến giao diện người dùng
thành "giật" hoặc âm thanh thành "sự cố". Do lỗi thiết kế cơ bản này,
ScriptProcessorNode
không được dùng trong quy cách nữa và
được thay thế bằng AudioWorklet.
Khái niệm
Audio Worklet lưu giữ toàn bộ mã JavaScript do người dùng cung cấp trong
luồng xử lý âm thanh. Điều đó có nghĩa là bạn không cần phải chuyển sang
để xử lý âm thanh. Điều này có nghĩa là mã tập lệnh do người dùng cung cấp sẽ được chạy
trên chuỗi kết xuất âm thanh (AudioWorkletGlobalScope
) cùng với các
AudioNodes
tích hợp sẵn, đảm bảo không có độ trễ bổ sung và tính đồng bộ
kết xuất hình ảnh.
Đăng ký và tạo thực thể
Quá trình sử dụng Worklet âm thanh bao gồm 2 phần: AudioWorkletProcessor
và
AudioWorkletNode
Bước này phức tạp hơn so với việc sử dụng ScriptProcessorNode,
nhưng cần cung cấp cho nhà phát triển
chức năng cấp thấp cho âm thanh tuỳ chỉnh
đang xử lý. AudioWorkletProcessor
đại diện cho bộ xử lý âm thanh thực tế
được viết bằng mã JavaScript và nằm trong AudioWorkletGlobalScope
.
AudioWorkletNode
là phiên bản tương đương của AudioWorkletProcessor
và nhận
xử lý kết nối đến và từ AudioNodes
khác trong luồng chính. Nó
được hiển thị trong phạm vi chính toàn cục và có các chức năng như AudioNode
thông thường.
Dưới đây là một cặp đoạn mã minh hoạ việc đăng ký và thực thể hoá.
// 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);
});
Để tạo AudioWorkletNode
, bạn phải thêm AudioContext
và tên bộ xử lý dưới dạng chuỗi. Định nghĩa bộ xử lý có thể là
tải và đăng ký bằng lệnh gọi addModule()
của đối tượng Audio Worklet mới.
API Worklet bao gồm cả Audio Worklet chỉ có trong một
ngữ cảnh bảo mật, do đó sẽ
phải được phân phát qua HTTPS, mặc dù http://localhost
được coi là phương thức an toàn
cho quy trình kiểm thử cục bộ.
Bạn có thể thêm lớp con AudioWorkletNode
để xác định
một nút tuỳ chỉnh được bộ xử lý hỗ trợ đang chạy trên 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);
Phương thức registerProcessor()
trong AudioWorkletGlobalScope
sẽ lấy
chuỗi cho tên trình xử lý sẽ được đăng ký và định nghĩa lớp.
Sau khi hoàn tất đánh giá mã tập lệnh trong phạm vi toàn cầu,
lời hứa từ AudioWorklet.addModule()
sẽ được giải quyết khi thông báo cho người dùng
để bạn xác định rằng định nghĩa lớp đã sẵn sàng để sử dụng trong phạm vi toàn cục chính.
Thông số âm thanh tuỳ chỉnh
Một trong những điều hữu ích về AudioNode là tham số có thể lập lịch
tự động hoá với AudioParam
. AudioWorkletNodes có thể dùng các mã này để tải
các thông số có thể được kiểm soát tự động ở tốc độ âm thanh.
Bạn có thể khai báo các tham số âm thanh do người dùng xác định trong AudioWorkletProcessor
định nghĩa lớp bằng cách thiết lập một tập hợp AudioParamDescriptor
. Chiến lược phát hành đĩa đơn
công cụ WebAudio cơ bản nhận thông tin này trong
tạo một AudioWorkletNode, rồi tạo và liên kết
AudioParam
đối tượng đến nút tương ứng.
/* 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.
}
}
}
Phương thức AudioWorkletProcessor.process()
Quá trình xử lý âm thanh thực tế diễn ra trong phương thức gọi lại process()
trong
AudioWorkletProcessor
Ứng dụng phải do người dùng trong lớp triển khai
định nghĩa. Công cụ WebAudio gọi hàm này theo cách đồng thời
cách thức để cấp dữ liệu đầu vào và tham số, cũng như tìm nạp đầu ra.
/* 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;
}
Ngoài ra, giá trị trả về của phương thức process()
có thể được dùng để
kiểm soát vòng đời của AudioWorkletNode
để nhà phát triển có thể quản lý
mức sử dụng bộ nhớ. Trả về false
từ process()
dấu phương thức
bộ xử lý không hoạt động và công cụ WebAudio
không còn gọi hàm
. Để duy trì hoạt động cho bộ xử lý, phương thức này phải trả về true
.
Nếu không, cặp nút và bộ xử lý sẽ là rác được hệ thống thu thập
cuối cùng.
Giao tiếp hai chiều bằng MessagePort
Đôi khi, một AudioWorkletNode
tuỳ chỉnh muốn hiển thị các chế độ điều khiển không
ánh xạ đến AudioParam
, chẳng hạn như thuộc tính type
dựa trên chuỗi
dùng để kiểm soát bộ lọc tuỳ chỉnh. Vì mục đích này và hơn thế nữa,
AudioWorkletNode
và AudioWorkletProcessor
có
MessagePort
để giao tiếp hai chiều. Mọi loại dữ liệu tuỳ chỉnh
có thể trao đổi qua kênh này.
Bạn có thể truy cập MessagePort bằng thuộc tính .port
trên cả nút và nút
bộ xử lý. Phương thức port.postMessage()
của nút sẽ gửi một thông báo đến
trình xử lý port.onmessage
của đơn vị xử lý liên kết và ngược lại.
/* 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
hỗ trợ thẻ có thể chuyển, giúp bạn
chuyển bộ nhớ dữ liệu hoặc mô-đun WASM trên ranh giới luồng. Thao tác này sẽ mở
có vô vàn khả năng về cách sử dụng hệ thống Audio Worklet.
Hướng dẫn từng bước: Tạo GainNode
Dưới đây là một ví dụ hoàn chỉnh về GainNode được xây dựng dựa trên
AudioWorkletNode
và AudioWorkletProcessor
.
Tệp 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>
Tệp 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);
Bài viết này đề cập đến thông tin cơ bản về hệ thống Worklet âm thanh. Có bản minh hoạ trực tiếp tại kho lưu trữ GitHub của nhóm Chrome WebAudio.
Chuyển đổi tính năng: Thử nghiệm sang phiên bản ổn định
Theo mặc định, Worklet âm thanh sẽ bật trên Chrome 66 trở lên. Trong Chrome 64 và 65, tính năng này nằm ngoài cờ thử nghiệm.