HTML5网页播放pcm语音流 websocket ArrayBuffer DataView webaudio PCM player

HTML5网页播放pcm语音流 websocket ArrayBuffer DataView webaudio PCM player

原文地址:Github

在线DEMO:PCM player online demo
DEMO仓库 PCM player

Javascript在数据的处理上一直不是强项,比如数字不分整型和浮点数统一使用了64位浮点数,如果涉及到二进制运算则显得非常无力,在数据传输上也非常浪费带宽。在ES6针对Javascript二进制数据处理上的无力引入了原始缓冲区ArrayBuffer,并且还提供了多种位数的int类型数组以及数据视图来处理数据。

ArrayBuffer & DataView

图解

用一个 Int8 的确定类型数组来分离存放 8 位二进制字节。

在这里插入图片描述
用一个无符号的 Int16 数组来分离存放 16 位二进制字节,这样如果是一个无符号的整数也能处理。
在这里插入图片描述
甚至可以在相同基础的 buffer 上使用不同的 view,同样的操作不同的 view 会给你不同的结果。

比如,如果我们在这个 ArrayBuffer 中从 Int8 view 里获取了元素 0 和 1,在 Uint16 view 中元素 0 会返回给我们不同的值,尽管它们包含的是完全相同的二进制字节。

在这种方式中,ArrayBuffer 基本上扮演了一个原生内存的角色,它模拟了像 C 语言才有的那种直接访问内存的方式。

你可能想知道为什么我们不让程序直接访问内存,而是添加了这种抽象层。直接访问内存将导致一些安全漏洞。

更多ArrayBuffer的基础介绍可参考 《JavaScript 标准参考教程》-阮一峰-二进制数组

数据的存储方式

一个整型数字写成2进制之后以书写习惯来说左边是高位,右边是低位。举个例子,int8的5使用二进制是这么表示的:

0000 0101

最左边的0是符号位,表示非负数,101则是5的二进制写法。

负数无法使用非负数的规则来表示,比如-1用非负数的规则表示是:

1000 0001

考虑到-1如果加上1值应该为0,那么该值加上1却会变成-2:

1000 0010

也就是在计算方式上出现问题了,所以二进制是采用了补码的方式来表示,补码可以完全不考虑符号位,它的规则如下:

  • 非负数直接用正常的二进制数表示
  • 负数是绝对值二进制取反加1

按这种规则,-1的绝对值二进制是:

0000 0001

取反后是:

1111 1110

再加1则是:

1111 1111

我们再尝试给它加上1,看看值是多少:

1 0000 0000

明显高位溢出了,于是将高位多余的1截掉变成:

0000 0000

它的值如我们所料是0,这就是补码。uint8由于没有正负之分,它的所有值都是非负数,所以就不需要考虑补码。

数据溢出截断

首先需要了解一下int16用二进制是如何表示的,以3850为例子,它的二进制表示方式是这样子的:

00001111 00001010

int16占了两个字节,因此它被截成两断,我们把左边的字节称为高位字节,右边的字节称为低位字节。在内存中它是如何存放的呢?建立一个Int8Array来看一下:

var buffer = new ArrayBuffer(2);
var int16Array = new Int16Array(buffer);
var int8Array = new Int8Array(buffer);
int16Array[0] = 3850;
// Int16Array [ 3850 ]
console.log(int16Array);
// Int8Array [ 10, 15 ]
console.log(int8Array);

从运行结果看,3850被截断成两个int8值:

int8Array[0] = 10 = 00001010
int8Array[1] = 15 = 00001111

看起来很反人类是吧?高位字节不是放在左边下标为0的字节上,却放在右边下标为1的字节上,跟我们的书写习惯反过来了!

事实上,数据在内存中存储并没有硬性规则高位字节必须放在左边(虽然它更符合人类阅读习惯),具体实现也是根据当前CPU的实现。大多数计算机是以高位优先的顺序存放数据(即高位在左,低位在右),但基于Intel CPU的计算机则是反过来以低位优先存放数据,我的本子就是Intel CPU的,因此我的运行结果就是Int8Array [ 10, 15 ],或许换个电脑就会变成Int8Array [ 15, 10 ]。

那么当数据溢出截断又是怎么处理的?尝试着给一个int8字节赋值3850,看看最终得到的值是什么:

var int8Array = new Int8Array([3850]);
//Int8Array [ 10 ]
console.log(int8Array);

很明显,它把15抛弃了,也就是保存了低位字节,抛弃高位。事实上,溢出处理在各种CPU上都是保留低位能保留的字节,把高位的截断,这个与数据存储是按高位优先还是低位优先没什么关系。

字节序处理

请先看以下示例代码在我机子上跑的情况:

var buffer = new ArrayBuffer(2);
var view = new DataView(buffer);
var int16Array = new Int16Array(buffer);
view.setInt8(0, 10);
// 10
console.log(view.getInt8(0))
// 2560 用二进制表示是:00001010 00000000,
console.log(view.getInt16(0));
int16Array[0] = 10;
// 2560!
console.log(view.getInt16(0));

仔细看结果,很明显,view使用的是高位优先的读写方式,而TypeArray使用的是低位优先的读写方式。在内存中两个字节的数据在setInt8(0, 10)的时候会变成

00001010 00000000

按照我计算机的读写规则应该是低位优先,也就是实际上这里应该被解读成

00000000 00001010

也就是得到10,但实际上使用view却得到了2560,也就是高位优先。

DataView默认是使用big-endian方式,也就是高位优先进行读写。但在读写多字节数据的时候,可以通过传入值为true的little-endian参数来要求使用低位优先规则读写数据。

参考链接

1 《JavaScript 标准参考教程》-阮一峰-二进制数组
2 缓冲数组以及数据视图
3 通俗漫画介绍 ArrayBuffers 和 SharedArrayBuffers

PCM player demo

PCM player online demo

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
要实现这个功能,您需要按照以下步骤进行操作: 1. 在React组件中创建一个`<audio>`元素,并设置其`src`属性为您要录制的音频文件的URL。 ``` <audio ref={audioRef} src={audioUrl} controls /> ``` 2. 使用`MediaRecorder` API 将音频文件录制为一个`Blob`对象。 ``` const recorder = new MediaRecorder(audioRef.current.srcObject); recorder.start(); recorder.ondataavailable = (e) => { const blob = new Blob([e.data], { type: 'audio/webm' }); // 将blob转换为PCM }; recorder.onstop = () => { // 将PCM发送到服务器 }; ``` 3. 将`Blob`对象转换为PCM。您可以使用`libflac`库来完成此操作。 ``` import { Encoder } from 'libflac'; const encoder = new Encoder({ sampleRate: audioRef.current.srcObject.getAudioTracks()[0].getSettings().sampleRate, channels: audioRef.current.srcObject.getAudioTracks()[0].getSettings().channelCount, bitsPerSample: 16, }); const reader = new FileReader(); reader.onload = () => { const buffer = reader.result; const pcmData = new Int16Array(buffer); const flacData = encoder.encode(pcmData); // 将flacData发送到服务器 }; reader.readAsArrayBuffer(blob); ``` 4. 将PCM通过`WebSocket`发送到服务器。 ``` const ws = new WebSocket('ws://localhost:8080'); ws.binaryType = 'arraybuffer'; ws.onopen = () => { ws.send(flacData); }; ``` 在服务器端,您需要使用`ws`模块来处理`WebSocket`连接并解码接收到的PCM。您可以使用`node-flac-bindings`模块来解码FLAC格式的数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值