Worklet âm thanh hiện có sẵn theo mặc định

Hongchan Choi

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.

Phạm vi toàn cầu chính và sơ đồ phạm vi Worklet âm thanh
Fig.1

Đăng ký và tạo thực thể

Quá trình sử dụng Worklet âm thanh bao gồm 2 phần: AudioWorkletProcessorAudioWorkletNode 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.

Sơ đồ bộ xử lý và nút worklet âm thanh
Fig.2

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, AudioWorkletNodeAudioWorkletProcessorMessagePort để 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.

Fig.2
Fig.2

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 AudioWorkletNodeAudioWorkletProcessor.

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.