javascript - 为什么存储 base64 数据流不起作用?
问题描述
使用反应原生音频记录库我正在尝试录制 3 秒的 .wav 音频文件。在录制期间,可以使用此“连接”/“功能”接收 base64 数据,每次从录制中接收到一大块数据时都会激活该功能(不确定您将如何称呼它):
AudioRecord.on('data', data => {
// base64-encoded audio data chunks
});
我在按下按钮时激活的功能中执行此操作。当我尝试将收到的所有数据存储在这样的变量中时,就会出现问题:
var tempString = '';
AudioRecord.on('data', data => {
tempString += data;
});
出于某种原因,当我在录制完成后(使用 settimeout)console.log tempString 时,它似乎只存储了第一次收到任何数据时的数据。此外,当我创建一个变量计数时,每次接收到数据时都会计数,它只会正常计数。
当我 console.log 数据时,它会打印出所有数据。我已经尝试推送到一个数组并监听变量何时发生变化,但我尝试的一切都导致它只存储我收到的第一条数据。如何将收到的所有数据存储在变量中?这甚至可能吗?
解决方案
背景:Base64 填充
在Base64中,每个输出字符代表输入的 6 位 (2 6 = 64)。编码数据时,第一步是将输入的位拆分为 6 位块。让我们以输入字符串“”为例hello
(编码为 ASCII 或 UTF-8 的二进制)。如果我们尝试将其位拆分为 6 位块,我们将意识到它不会平均划分:最后一个块只有 4 位。
h e l l o
01101000 01100101 01101100 01101100 01101111
011010 000110 010101 101100 011011 000110 1111??
a G V s b G ?
我们可以用0
s 填充缺失的位来填充输入流。
011010 000110 010101 101100 011011 000110 111100
a G V s b G 8
这给了我们"aGVsbG8"
,并且在 JavaScript 中的快速健全性测试证实了atob("aGVsbG8") === "hello"
. 还没有问题。
如果我们自己解码这个块,这是可行的,因为我们知道一旦我们到达块的末尾,我们还没有解码的剩余两位必须是填充,并且可以被忽略。但是,如果这只是一个流的一部分,紧接着是更多的 base64 数据,我们不能说我们在一个块的末尾!
例如,让我们尝试aGVsbG8
与自身连接,并将其解码aGVsbG8aGVsbG8
为单个值。
a G V s b G 8 a G V s b G 8
011010 000110 010101 101100 011011 000110 111100 011010 000110 010101 101100 011011 000110 111100
||- padding that should be ignored
01101000 01100101 01101100 01101100 01101111 00011010 00011001 01011011 00011011 00011011 1100????
h e l l o \x1A \x19 [ \x1B \x1B ?
两个填充位会导致解码流未对齐,并且剩余数据被破坏。
在这些情况下,标准解决方案是在编码数据后添加零到两个=
填充字符。每个=
代表六位填充数据。这些标记了编码值的结束,但它们也允许保持输入数据和输出数据之间的对齐:在流中使用适当的填充,每四个字符的编码数据块可以明确地解码为一到三个字节解码数据,无需单独了解数据对齐。我们的示例需要六位填充来保持对齐,给我们aGVsbG8=
. 如果我们将其与自身连接,我们可以看到解码现在是成功的:
a G V s b G 8 = a G V s b G 8 =
011010 000110 010101 101100 011011 000110 111100 PPPPPP 011010 000110 010101 101100 011011 000110 111100 PPPPPP
01101000 01100101 01101100 01101100 01101111 00PPPPPP 01101000 01100101 01101100 01101100 01101111 00PPPPPP
h e l l o padding h e l l o padding
问题:无能力的解码器
使用功能齐全的编码器和解码器,您的方法应该可以正常工作。每个块都应该包含适当的填充,并且解码器应该能够跳过它并组装正确的结果。
不幸的是,许多最常见的 base64 解码库不支持这一点。
NodeBuffer
只是假设它得到一个单一的编码值,所以当它看到填充(可能在第一个块的末尾)时,它假设它是值的结尾,并停止解码,丢弃其余的数据。
> Buffer.from('aGVsbG8=', 'base64')
<Buffer 68 65 6c 6c 6f>
> Buffer.from('aGVsbG8=aGVsbG8=', 'base64')
<Buffer 68 65 6c 6c 6f>
浏览器会atob
抛出错误,而不是默默地忽略数据:
> atob("aGVsbG8=")
"hello"
> atob("aGVsbG8=aGVsbG8=")
InvalidCharacterError: String contains an invalid character
解决方案
在填充上手动拆分
如果我们保持您将所有数据存储在单个字符串中的方法,我们需要自己负责拆分填充。(注意:通常,重复附加到字符串可能会出现问题,因为如果 JavaScript 引擎无法对其进行优化,它会变得非常慢。在实践中这可能不是问题,但通常可以避免。)
我们可以使用匹配一个或多个=
填充字符序列的正则表达式来做到这一点,
const input = "aGVsbG8=aGVsbG8=aGVsbG8=aGVsbG8=";
const delimiter = /=+/g;
在上面分割字符串,
const pieces = input.split(delimiter);
单独解码片段,
const decodedPieces = pieces.map(piece => Buffer.from(piece, 'base64'));
然后在一个步骤中组合它们的输出(比增量执行更有效)。
const decoded = Buffer.concat(decodedPieces);
console.log(decoded.toString('ascii'));
'hellohellohellohello'
单独存储块
但是,在您的情况下,从头开始将块存储在数组中并完全跳过连接和拆分可能会更简单。
const decodedPieces = [];
AudioRecord.on('data', data => {
decodedPieces.push(Buffer.from(data, 'base64'));
});
// later, when you need to collect the data...
const decoded = Buffer.concat(decodedPieces);
推荐阅读
- mysql - 有没有办法在 SELECT 语句中用 NULL 替换所有列中的零长度字符串字段(一次)?
- php - gitlab 注销后页面依然认证
- java - 使用@JsonAnyGetter /@JsonAnySetter 序列化 JSON 并更改属性名称 (Jackson)
- reactjs - 新的 create-react-app 无法编译 sass
- mysql - 与 5.7.26 版本相比,8.0.17 版本的 MySQL 查询执行时间较长
- r - 如何从变量名中删除子字符串?
- prometheus - 使用 metric 值查询 prometheus 标签值
- arrays - 在 Mongo 中,如何将 $IN 条件与来自另一个字段的值一起使用?
- typescript - 是否可以自动检测“任何”类型的重播?
- python-3.x - 搜索 Socket 主机并显示它们的 IP 地址 Python