【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文件的区别示例:
在这里插入图片描述
mp3和wav的区别说明:
在这里插入图片描述

其实浏览器是可以直接播放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

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Electron 是一个基于 Chromium 和 Node.js 的开源框架,可以用来构建跨平台的桌面应用程序。在 Electron 中,浏览器和主进程之间的交互是通过 IPC(进程间通信)实现的。 在 Electron 中,主进程是一个 Node.js 运行环境,负责管理应用程序的生命周期和执行一些底层操作,而渲染进程则是一个 Chromium 浏览器实例,用于展示界面和执行前端代码。 要实现 Electron浏览器之间的交互,可以使用 Electron 提供的 IPC 模块。主进程和渲染进程可以通过 IPC 模块发送消息和接收消息。 在渲染进程中,可以使用 `ipcRenderer` 对象来发送消息给主进程,代码示例如下: ```javascript const { ipcRenderer } = require('electron'); ipcRenderer.send('message', 'Hello from renderer process!'); // 接收主进程的回复消息 ipcRenderer.on('reply', (event, arg) => { console.log(arg); // 打印主进程回复的消息 }); ``` 在主进程中,可以使用 `ipcMain` 对象来监听渲染进程发送的消息,并回复消息,代码示例如下: ```javascript const { ipcMain } = require('electron'); // 监听渲染进程发送的消息 ipcMain.on('message', (event, arg) => { console.log(arg); // 打印渲染进程发送的消息 // 回复消息给渲染进程 event.reply('reply', 'Hello from main process!'); }); ``` 通过这种方式,你可以在 Electron 应用程序中实现浏览器和主进程之间的双向通信。可以根据具体的业务需求,定义不同的消息类型和处理逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值