为什么存储base64数据流不起作用?

时间:2019-01-13 18:03:36

标签: javascript react-native

使用a react native audio record library我正在尝试录制3秒钟的.wav音频文件。在记录过程中,可以通过此“连接” /“功能”接收base64数据,每次从记录中接收到一大块数据时都会激活它(不确定您会叫什么名字):

using System.Collections.Generic;

var peopletiles = new List<TextBox>{ A2, A3, A4, A5,};
Random random = new Random();
for (var i = 0; i < Edit.peopleStar; i++)
{
    var index = random.Next(0, peopletiles.Length);
    peopletiles[index].BackColor = Color.Purple;
    peopletiles.RemoveAt(index);
}

我正在执行的功能是在按下按钮时激活的。当我尝试将收到的所有数据存储在这样的变量中时,就会出现问题:

SET @rownumber = 0;
UPDATE comments
SET `order` = @rownumber:= @rownumber + 1
WHERE id IN (803, 518, 361)
ORDER BY id DESC;

由于某种原因,当录制完成后使用console.log tempString(使用settimeout)时,似乎只存储了第一次接收到任何数据的数据。另外,当我创建一个可变计数时,每次接收到数据时都会递增计数,它只是正常递增计数。

当我console.log数据时,它确实打印出所有数据。我试过将数据推送到数组,并在变量发生变化时进行监听,但是我尝试的所有结果都只是存储了我收到的第一条数据。如何将收到的所有数据存储在变量中?这有可能吗?

1 个答案:

答案 0 :(得分:1)

背景: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填充输入流来填充缺失的位。

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     ?

这两个填充位会导致解码流未对齐,并且剩余数据将被篡改。

在这些情况下,标准解决方案是在编码数据后的两个=填充字符中添加零。每个=代表六位填充数据。这些标记了编码值的结尾,但是它们也允许在输入数据和输出数据之间保持对齐:通过在流中进行适当的填充,可以将编码数据的每四个字符块明确地解码为1-3个字节。解码的数据,而无需单独了解数据对齐方式。我们的示例需要六位填充来保持对齐,从而使我们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解码库不支持此功能。

Node的Buffer只是假设它得到了一个编码值,因此当其看到填充(可能在第一个块的末尾)时,它假定它是该值的末尾,并停止解码,并丢弃其余部分您的数据。

> 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);