簡體   English   中英

為什么存儲 base64 數據流不起作用?

[英]Why does storing a stream of base64 data not work?

使用本機音頻記錄庫,我正在嘗試錄制 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填充輸入流來填充缺失的位。

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 解碼庫都不支持這一點。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM