音频优化:SharedArrayBuffer共享 --> 二进制传输

音频优化:SharedArrayBuffer共享 --> 二进制传输

前记

在音频使用中,我使用的是Web Audio,这是一个内置于浏览器的JavaScript API,用于处理和操作音频。这个API提供了一套很强大的功能,使身为开发者的我们能够创建和控制音频流,实现音频的录制、播放、混合、合成和特效处理等。这篇文章是在使用Web Worker进行解码传输,在主线程上进行播放音频,这样做的原因是:Web Worker是后台独立运行的独立线程,无法直接访问或操作浏览器的DOM元素,包括音频播放器,而将音频解码操作放进worker中是为了防止堵塞住线程运行,提高用户体验。

因为一开始用的 SharedArrayBuffer 将worker解码出来的音频数据进行共享,后来考虑到一些因素(下面也会说)决定转 二进制 传输,如果想了解Web Audio的使用,请点击这里

直接传输 VS SharedArrayBuffer共享

直接传输

当我一开始使用Web Worker来处理解码音频数据的时候,我很自然的不对音频数据进行任何处理而直接将它post到主线程,"小"音频还好,但如果碰上了内存大的音频很多问题就渐渐浮出水面,比如:

  • 数据传输延迟:由于音频数据通常是以较大的块进行解码的,直接将大量的解码后的音频数据传输给主线程可能会导致传输延迟。大量数据的传输需要消耗一定的时间,可能导致主线程在接收和处理音频数据时发生延迟,从而影响音频播放的实时性和流畅性.
  • 主线程负载增加:直接将解码后的音频数据传输给主线程,主线程需要处理较大的数据量。如果音频数据的解码速度快于主线程处理数据的速度,可能会导致主线程的负载增加,造成主线程阻塞或卡顿的情况.
  • 内存占用:解码后的音频数据可能占用较大的内存空间,如果直接传输给主线程,会增加主线程的内存占用。特别是在处理长时间的音频流或大型音频文件时,可能会导致主线程的内存压力增大.
  • 线程间通信开销:将解码后的音频数据通过postMessage()传输给主线程需要进行线程间通信,这涉及到数据的复制和序列化操作。这些操作可能会引起一定的开销,特别是在频繁传输大量数据时,可能会对性能产生一定的影响.

SharedArrayBuffer共享(优点、用法和弊端)

使用 SharedArrayBuffer 进行共享可以很好的解决上述直接传输的问题,但是仍然存在一些弊端,接着往下看。

使用此方法的好处:
  • 零拷贝(Zero-copy):SharedArrayBuffer允许多个线程(例如主线程和Web Worker)在共享内存空间中访问相同的数据,而无需进行数据复制。这意味着数据可以直接在内存中共享,避免了传输和复制数据的额外开销,提高了性能和效率。
  • 高效的并发访问:SharedArrayBuffer提供了原子操作和锁机制,确保多个线程对共享数据的并发访问是安全和有序的。这使得多个线程可以同时读取和写入共享的数据,而不会发生竞态条件或数据不一致的问题。
  • 实时性:由于零拷贝和高效的并发访问特性,使用SharedArrayBuffer进行数据共享可以实现实时性的要求。例如,在音频或视频处理应用中,可以将音频或视频数据共享给Web Worker进行处理,然后实时返回处理后的结果,以实现实时的音视频处理和渲染。
  • 灵活性和扩展性:SharedArrayBuffer可以在多个线程之间共享任意类型的数据,不仅限于音频或视频数据。这使得它非常适用于各种并行计算和数据处理任务,如图像处理、计算密集型算法等。通过共享数据,可以将工作负载分摊到多个线程,提高整体的性能和扩展性。
用法(关键代码)
  • 主线程中的代码
js 复制代码
// 创建Web Worker
const worker = new Worker('worker.js');

// 监听Web Worker的消息事件
worker.addEventListener('message', (event) => {
  // 获取共享的音频数据
  const sharedBuffer = event.data;

  // 在主线程中创建Float32Array,读取共享的音频数据
  const sharedArray = new Float32Array(sharedBuffer);

  // 创建AudioBufferSourceNode
  const audioContext = new AudioContext();
  const audioBuffer = audioContext.createBuffer(1, sharedArray.length, audioContext.sampleRate);
  audioBuffer.getChannelData(0).set(sharedArray);
  const sourceNode = audioContext.createBufferSource();
  sourceNode.buffer = audioBuffer;
  sourceNode.connect(audioContext.destination);
  sourceNode.start();
});

// 导入音频文件
const audioFileInput = document.getElementById('audio-file-input');
audioFileInput.addEventListener('change', (event) => {
  const file = event.target.files[0];
  const fileReader = new FileReader();
  fileReader.onload = (event) => {
    // 将音频数据传递给Web Worker
    worker.postMessage(event.target.result);
  };
  fileReader.readAsArrayBuffer(file);
});
  • worker中的代码
js 复制代码
// 监听消息事件
self.addEventListener('message', (event) => {
  const audioData = event.data;

  // 解码音频数据
  self.decodeAudioData(audioData, (decodedAudioData) => {
    // 创建SharedArrayBuffer,用于存储解码后的音频数据
    const sharedBuffer = new SharedArrayBuffer(decodedAudioData.length * 4); // 4 bytes per float32

    // 在SharedArrayBuffer中创建Float32Array,存储解码后的音频数据
    const sharedArray = new Float32Array(sharedBuffer);
    sharedArray.set(decodedAudioData);

    // 将SharedArrayBuffer传输给主线程
    self.postMessage(sharedBuffer);
  });
});
使用此方法的弊端:

此方法最大的问题来自于安全性,要在浏览器中开启ShareArrayBuffer的支持,需要:

  • 使用HTTPS协议:ShareArrayBuffer 在未加密的HTTP连接上被禁用,因此需要使用HTTPS协议来提供安全的通信.
  • 添加合适的Cross-Origin-Opener-Policy(COOP)和Cross-Origin-Embedder-Policy(COEP)头部:
makefile 复制代码
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

这两个请求头,具有一定的兼容性问题,配置复杂性较高,安全性考虑及跨域等问题.

二进制传输(关键代码)

worker代码

js 复制代码
// Web Worker 代码(audio-decode-worker.js)

// 监听主线程传递的音频数据
self.onmessage = function(event) {
  const arrayBuffer = event.data;
  
  // 在Web Worker中解码音频数据
  decodeAudioData(arrayBuffer)
    .then(audioBuffer => {
      // 将解码后的音频数据转换为二进制数据
      const channelData = audioBuffer.getChannelData(0);
      const float32Array = new Float32Array(channelData);
      const binaryData = float32Array.buffer;

      // 将二进制数据发送给主线程
      self.postMessage(binaryData, [binaryData]);
    })
    .catch(error => {
      console.error('音频解码失败:', error);
    });
};

// 解码音频数据的函数
function decodeAudioData(arrayBuffer) {
  return new Promise((resolve, reject) => {
    // 创建音频上下文
    const audioContext = new AudioContext();
    
    // 解码音频数据为音频缓冲区
    audioContext.decodeAudioData(arrayBuffer, function(audioBuffer) {
      resolve(audioBuffer);
    }, function(error) {
      reject(error);
    });
  });
}
  1. 监听主线程传递的音频数据,并调用decodeAudioData()函数解码音频数据。
  2. 在解码成功后,将解码后的音频数据转换为二进制数据,这可以使用AudioBuffer对象的getChannelData()方法来获取音频数据的浮点型数组。
  3. 将二进制数据通过postMessage()方法发送回主线程。

主线程代码

js 复制代码
// 主线程代码

// 创建Web Worker实例
const audioWorker = new Worker('audio-decode-worker.js');

// 监听Web Worker的消息事件
audioWorker.onmessage = function(event) {
  const binaryData = event.data;

  // 创建音频上下文
  const audioContext = new AudioContext();

  // 将二进制数据转换为音频缓冲区
  audioContext.decodeAudioData(binaryData, function(audioBuffer) {
    // 创建音频源节点
    const audioSource = audioContext.createBufferSource();
    audioSource.buffer = audioBuffer;

    // 连接音频源节点到音频目标节点(扬声器)
    audioSource.connect(audioContext.destination);

    // 播放音频
    audioSource.start();
  });
};

// 发送音频数据给Web Worker进行解码
fetch('audio-file.mp3')
  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    // 将音频数据发送给Web Worker
    audioWorker.postMessage(arrayBuffer);
  });
  1. 监听Web Worker传递的消息,接收解码后的二进制音频数据。
  2. 创建AudioContext对象。
  3. 使用AudioContextdecodeAudioData()方法将二进制音频数据解码为AudioBuffer对象。
  4. 创建AudioBufferSourceNode对象,并将解码后的AudioBuffer对象设置为其buffer属性。
  5. AudioBufferSourceNode连接到AudioContext的目标节点(如扬声器)。
  6. 调用AudioBufferSourceNodestart()方法开始播放音频。

写累了,好好学习

相关推荐
竹林8181 天前
用 wagmi v2 + viem 监听链上事件,我踩了三天坑终于搞懂了实时日志与历史补全
javascript
用户1563068103511 天前
Day01 | 什么是Agent?
面试
Momo__1 天前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
只一1 天前
😭从回调地狱到 async/await:一文打通 Ajax 与 JS 异步编程
javascript
程序员小富1 天前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 天前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 天前
React中的forwardRef
前端·react.js·面试
槑有老呆1 天前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 天前
Verilog开发常见问题汇总解析
前端
子兮曰1 天前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端