音频优化: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()方法开始播放音频。

写累了,好好学习

相关推荐
Jiaberrr几秒前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy25 分钟前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白25 分钟前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、26 分钟前
Web Worker 简单使用
前端
web_learning_32128 分钟前
信息收集常用指令
前端·搜索引擎
Ylucius34 分钟前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz36 分钟前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百1 小时前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao1 小时前
自动化测试常用函数
前端·css·html5
LvManBa1 小时前
Vue学习记录之三(ref全家桶)
javascript·vue.js·学习