【electron6】浏览器实时播放PCM数据

pcm介绍:PCM(Puls Code Modulation)全称脉码调制录音,PCM录音就是将声音的模拟信号表示成0,1标识的数字信号,未经任何编码和压缩处理,所以可以认为PCM是未经压缩的音频原始格式。PCM格式文件中不包含头部信息,播放器无法知道采样率,声道数,采样位数,音频数据大小等信息,导致无法播放。

需求:我需要在websoket中接收实时的pcm音频流原始数据:16位,16k,单通道(意思就是:pcm 的参数采样率16000,采样位数16,声道数1)

重要!:千万不要去运用什么所谓的插件,因为它会使你绕很大一圈的弯路,实时播放的pcm浏览器是支持的,使用插件很可能还会被迫读什么源码,也很容易将你带偏,再多的播放插件底层大部分也是基于AudioContext去开发的。

首先是拿音频文件试,但是却不了解mp3和wav的区别,就首选拿了mp3这是我的第一个深坑,因为mp3经过多层的处理压缩,已经距离原始的文件很远了,通过ajax拿回来的arraybuffer是可以直接塞进去CreateBufferSource.buffer中就能直接播放,原因就是decodeAdioData就直接能处理接口返回的arraybuffer数据

mp3示例:

useEffect(() => {
      axios.request({
        url: require('@/assets/wholeWorld.mp3'), // 假如这是我们从后端请求回来的音乐文件
        responseType: 'arraybuffer', // 必须需要这个
        method: 'get'
      }).then(res => {
        // 创建AudioBufferSourceNode
        const arrayBuffer = res.data
        const context = new AudioContext()
        const source = context.createBufferSource();
        context.decodeAudioData(arrayBuffer).then(audioBuffer => {
          // 设置buffer属性
        source.buffer = audioBuffer;
        // 连接到音频上下文并播放
        source.connect(context.destination);
        })
        source.start(0);
      })
  }, [])

我处理wav的音频去播放,也是可以的使用decodeAudioData去进行播放

wav音频播放示例:

useEffect(() => {
      axios.request({
        url: require('@/assets/sample.wav'), // 假如这是我们从后端请求回来的音乐文件
        responseType: 'arraybuffer', // 必须需要这个
        method: 'get'
      }).then(res => {
        // 创建AudioBufferSourceNode
        const arrayBuffer = res.data
        const context = new AudioContext()
        const source = context.createBufferSource();
        context.decodeAudioData(arrayBuffer).then(audioBuffer => {
          // 设置buffer属性
        source.buffer = audioBuffer;
        // 连接到音频上下文并播放
        source.connect(context.destination);
        })
        source.start(0);
      })
  }, [])

但是都不支持pcm文件,所以我最开始的思路是通过获取到的pcm去处理成wav的文件一样,去搜了很多资料,都说wav的文件比pcm只是多了44字节文件头,我没从深度验证,但是我通过加字节文件头去进行pcm的播放,因为decodeAudioData可以运行其他处理过的音频文件,尽管加了字节文件头是不能解决根本问题的,这段路我绕了很大一个圈。

mp3文件和wav文件的区别示例:

WAV和PCM的区别说明:

其实浏览器是可以直接播放pcm数据的,无论是文件,还是socket返回来的原始数据,这过程涉及了Uint8转换Uint16,Uint16转成Float32,了解decodeAudioData和getChannelData究竟处理什么问题等知识。

首先,找一个pcm文件,进行播放调试,pcm的文件能播放成功,那socket就不是问题!

PCM播放代码示例:

(仅播放出声音的调试,自行调整代码规范)

// 解析PCM数据到AudioBuffer
function decodePCM(arrayBuffer: ArrayBufferLike,sampleBits: number, channelCount: number, sampleRate: number,audioContext: any) {
  return new Promise((resolve, reject) => {
      const dataView = new DataView(arrayBuffer);
      const length = (arrayBuffer.byteLength / (sampleBits / 8) / channelCount);
      const buffer = audioContext.createBuffer(channelCount, length, sampleRate);
      let offset = 0;

      for (let channel = 0; channel < channelCount; channel++) {
          const channelBuffer = buffer.getChannelData(channel);
          for (let i = 0; i < length; i++) {
              const sample = dataView.getInt16(offset, true); // 假设PCM数据是16位有符号整数
              channelBuffer[i] = sample / 32768; // 标准化到-1到1的范围
              offset += 2; // 16位 = 2字节
          }
      }

      resolve(buffer);
  });
}
let sourceNode: any = null;
  useEffect(() => {
    // 加载音频文件
    axios.request({
      method: 'get',
      url: require('@/assets/recorder.pcm'),
      responseType: "arraybuffer",
    }).then(res => {
      const arraybuffer = res.data
      
      const audioContext = new window.AudioContext();
      decodePCM(arraybuffer, 16, 1, 16000, audioContext).then(buffer => {
        sourceNode = audioContext.createBufferSource();
        sourceNode.buffer = buffer;
        sourceNode.connect(audioContext.destination);
        sourceNode.start(); // 开始播放
    }).catch(error => {
        console.error('Error decoding PCM:', error);
    });
    })
  }, [])

注:

当声音有杂音,一直是一个杂音就说明,数据错了

当声音没有声音,数据很可能都是0

当声音隐隐有正常,但是杂音很重,一定是需要它:getChannelData和DataView

后端从TCP给我实时的pcm是压缩Uint8Array,前端将Uint8Array的数据解码为Uint16Array,然后合并Uint16Array的所有音频数据,还需要将Uint16Array通过DataView处理成AudioContext可播放的Float32Array,所以不了解AudioCotext API是很难解决这些问题的,更何况仅仅是播放的功能,我后面还要处理更多复杂的应用场景,持续更新实时播放pcm的处理应用场景。

最终在和蔼可亲的同事帮助和自己不辞辛苦的研究下搞出来了,感谢我的同事。

值得参考:

张鑫旭:https://www.zhangxinxu.com/wordpress/2023/10/js-audio-audiobuffer-concat-merge/

MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/BaseAudioContext/createBuffer

推荐工具:

MEIDAINFO:https://mediaarea.net/MediaInfoOnline

相关推荐
UestcXiye1 天前
《TCP/IP网络编程》学习笔记 | Chapter 4:基于TCP的服务器端/客户端(1)
c++·计算机网络·tcp
今天也想MK代码2 天前
在Swift开发中简化应用程序发布与权限管理的解决方案——SparkleEasy
前端·javascript·chrome·macos·electron·swiftui
yqcoder2 天前
electron 中 ipcRenderer 的常用方法有哪些?
前端·javascript·electron
yqcoder2 天前
electron 中 ipcRenderer 作用
前端·javascript·electron
Liquor14193 天前
JavaScript知识点梳理及案例实践
开发语言·前端·javascript·python·css3·html5·js
fury_1233 天前
base64文本div中增加一个img标签,img标签实时渲染后端返回的base64数据成对应图片
java·服务器·前端·js
伍嘉源4 天前
关于electron进程管理的一些认识
前端·javascript·electron
yqcoder4 天前
electron 设置最小窗口缩放
前端·javascript·electron
我命由我123454 天前
CesiumJS 案例 P18:检测文本、删除所有文本、隐藏与显示文本、改变文本
前端·javascript·前端框架·html·css3·html5·js
海上彼尚4 天前
根据JSON绘制3D地区
前端·3d·js