- 原文地址:Fountain codes and animated QR
- 原文做者:Ivan Daniluk
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:EmilyQiRabbit
- 校對者:40m41h42t,Ultrasteve
在前一篇文章中,我講解了一個我在週末完成的項目:txqr,它使用了動態二維碼序列,能夠用於單向的狀態傳輸。最簡單直接的方法就是不停重複的編碼數據序列,直到接收者獲取到了完整的數據。這樣簡單的重複代碼足夠初學者用於起步學習,而且很容易執行,但方案還同時引入必定的延遲來防止接收者遺漏任何一幀的信息,在實際應用過程當中,錯失信息的狀況常常出現。前端
對於如何解決以上這種在有噪信道中傳輸數據的問題,已經有十分完整的理論研究,那就是編碼理論。python
在前一篇文章的評論中,Bojtos Kiskutya 提到了 LT 碼,它可讓 txqr 得出更佳結果。這正是我樂意看到的評論 —— 不只是優化的建議,同時也讓我能發現一些新的有趣的內容。因爲我從沒有接觸過 LT 編碼,在接下來的幾天內我盡我所能的學習了相關的內容。android
因而我知道了,LT codes(LT 是 盧比變換(Luby Transform)的簡寫)是一個更大的編碼方式:噴泉碼的一種實現方式。它是糾刪碼中的一類,它能夠從源信息塊(K 個)中產生無限數量的數據塊,而且它接收比 K 個編碼塊稍多的信息就足以正確解碼信息。接收者能夠從任意位置開始接收數據塊,也能夠按任意順序接收,並能夠設置任意的擦除機率 —— 當你接收到 K 個以上不一樣的數據塊,噴泉碼就能夠開始工做。這實際上就是「噴泉」這個名字的由來 —— 咱們將裝滿水桶這個行爲比做接收信息,噴泉噴出水滴這個行爲比做發送一系列編碼塊,換句話說,你能夠在不知曉你當前接收到的是哪個水滴的狀況下,裝滿你的水桶。ios
將它用於個人項目簡直再合適不過了,因此我快速的搜索了基於 Go 的實現方式:google/gofountain,並將我以前的初級版重複編碼的代碼替換成了盧比變換的實現。代碼替換後的測試結果很是優秀,因而在這篇文章中,我將會分享一些 LT 算法的細節,以及使用 gofountain 包容易犯錯的地方,最後我還會給出兩種代碼最終測試結果的對比。git
若是你和我同樣,還從未據說過噴泉碼,也不用擔憂 —— 由於噴泉碼還屬於比較新的技術,目前只能解決一小部分很專業的問題。可是噴泉碼其實很是酷。它完美的結合了隨機性、數學邏輯以及機率分佈,從而達成了它的最終目的。github
雖然我主要介紹 LT 編碼,可是在這個編碼系統中其實還有不少其餘算法 —— 好比 Online codes、Tornado codes、Raptor codes 等等,這其中 Raptor codes 在除了合法性以外的幾乎全部方面都更勝一籌。可是它們彷佛都受到嚴格的專利保護,因此並未獲得普遍的應用。web
LT 編碼的原理相對簡單 —— 編碼器將信息分割爲多個源信息塊,而後持續的建立編碼塊,這些編碼塊包含了 1 個或 2 個源信息塊,或者更隨機的選擇源信息塊並將全部被選擇的源信息塊做異或操做,獲得一個輸出。用於建立每一個新的編碼塊的 ID 被隨機的保存在其中。算法
在這一輪計算中,編碼器會收集全部的編碼塊(就像噴泉中的水珠)—— 它們有的僅包含一個源信息塊,有的包含兩個或者更多 —— 而後將它們和已經解碼的塊作異或操做來解碼還原成新的信息塊。後端
因此,當解碼器接收到了僅由一個源信息塊組成的編碼塊 —— 它就將它添加到解碼塊隊列中,不須要其餘操做。而若是它接收到了使用兩個源信息塊異或組成的編碼快,解碼器會檢查它們傳輸時附帶的 ID,若是其中一個已經在解碼隊列中了 —— 那麼根據異或操做的性質,恢復這個編碼快也就很是簡單了。解碼兩個以上源信息塊組成的編碼塊也同理 —— 一旦你能獲取到一個解碼塊 —— 只須要繼續作異或操做就能夠了。瀏覽器
最酷的地方在於如何選擇多少編碼塊僅由一個源信息塊編碼而來,以及多少是用兩個或更多源信息塊編碼而來。若是有太多的單源信息塊編碼包,你可能會損失須要的冗餘度。而若是太多的多源信息塊編碼包 —— 那麼在一個有噪信道獲取單源信息塊會花費過多的時間。所以 Luby 編碼的命名者,Michael Luby 稱孤子分佈幾乎是解決這個問題最完美的分佈方式,它能保證你獲得足夠多的單源信息塊編碼包,同時也有不少的雙源信息塊編碼包,它還有一個很長的尾數,可用於多源信息塊編碼包直到 N 源信息塊編碼包,其中 N 是源信息塊的數量。
這是對分佈頭部數據的更清晰的展現:
你能夠看到,這裏有一些非零數量的單源信息編碼包,其中雙源信息編碼包占據了分佈總量的很大一部分(精確地來講是一半),餘下的數量被遞減的分佈在多源信息編碼包中,一個塊中包含的源信息塊數量越多,這樣的編碼塊就越少。
全部這些特性,讓 LT 編碼具備了不依賴於發送頻率或模式通訊信道丟包率的特性。
對於個人 txqr 項目這就意味着,不管使用何種編碼和傳輸參數,使用噴泉碼都可以減小平均編碼時間。
谷歌研發的 gofountain 包使用 Go 語言實現了幾個噴泉編碼,其中包括 Luby 變換碼。它的 API 都很輕量(對於庫來講,這是一個好兆頭)—— 基本只包含了 Codec
接口以及一些實現代碼、EncodeLTBlocks()
函數,和一些做爲僞隨機生成器的幫助函數。
可是,在試圖理解 EncodeLTBlocks()
的第二個參數是什麼意義的時候,我有些迷惑了:
func EncodeLTBlocks(message []byte, encodedBlockIDs []int64, c Codec) []LTBlock
複製代碼
爲何我須要將數據塊 ID 提供給編碼器,我甚至不但願關注數據塊的其餘屬性,由於實現算法應該是庫自己而不是使用庫用戶須要關注的問題。因此最開始我猜想只需傳輸全部數據塊 ID —— 1..N
。
我猜想的和事實很接近 —— 測試的調試輸出編碼塊正如我想要的,但解碼過程卻總不能正確的執行。
我查看了 gofountain 的文檔頁,想看看還有什麼其餘包使用了它,結果發現了一個開源的用於在有損網絡環境下傳輸大型文件的庫 —— pump,其做者是 Sudhir Jonathan,因而我決定藉助一下友好的 Gopher 社區的力量,並試着在 Gopher slack 上聯繫了 Sudhir,詢問他是否能幫助我弄明白這些 ID 的用途。
後來我成功的聯繫到了 Sudhir,他給了我很縝密的答案並解除了我全部的疑惑,這對我幫助很是大。使用這個庫正確的方式是將數據塊 ID 以遞增的順序連續的發送 —— 例如,1..N
、N..2N
、2N..3N
等等。由於通常狀況下,咱們並不知道信道的噪聲級別,因此總要生成新的數據塊,這是很是重要的。
因此這些 ID 正確的用途應該是循環生成 ID 塊,並在一個循環中調用 EncodeLTBlocks
函數。可是爲了實現這個功能,我必須確保二維碼編碼速度足夠快,能在運行中及時生成新的數據塊。對於每秒 15 幀的速率,編碼下一個數據塊以及生成新的二維碼的總時間應小於 1/15 秒,也就是 66ms。很明顯這是可行的,可是須要仔細地進行基準測試並優化,以保證對於瀏覽器上的單核 GopherJS-transpiled 版本也知足這個條件。
另外,目前還有一些設計方面的限制 —— txqr.Encode()
API 指望能返回一個具體的數字,它表示了將有多少個塊會被編碼爲二維碼幀,還有 txqr-tester
會生成動態 GIF 文件,確保在瀏覽器運行時幀率的可靠性,因此我決定如今仍是不要打破 API 的限制,使用有冗餘因子的方法。
冗餘因子方法基於假設:在個人項目中,噪音多少是能夠預測的 —— 跳幀不會多於 20%。咱們能夠生成 N*redundancyFactor
個幀,而後像循環代碼方法那樣作循環,在常規案例中,這是個次優的方案,可是對於個人項目需求和受掌控外部條件,這已經足夠了。因此關於 encodedBlockIDs
參數,我是用了一個簡單的幫助函數:
// ids 函數使用 0..n 中的值生成多個 ID 切片
func ids(n int) []int64 {
ids := make([]int64, n)
for i := int64(0); i < int64(n); i++ {
ids[i] = i
}
return ids
}
複製代碼
經過以下方式調用:
codec := fountain.NewLubyCodec(N, rand.New(fountain.NewMersenneTwister(200)), solitonDistribution(N))
idsToEncode := ids(int(N * e.redundancyFactor))
lubyBlocks := fountain.EncodeLTBlocks(msg, idsToEncode, codec)
複製代碼
對於不感興趣 gofountain
的讀者,這部分多是一個非必需而且有些無聊的部分,可是我但願對那些也被這個 API 所迷惑的人有幫助,這樣他們就能夠經過搜索結果找到這篇文章了。
因爲我保存了原始包的 API,餘下的工做就很是容易了。你也許記得在前一篇文章中,我在 web 端的應用使用了名爲 txqr-tester
的 txqr 項目的 Go 語言包,它能夠在瀏覽器中運行。在這裏,Go 的可跨平臺的特性又一次讓我感到很興奮!我只須要切換到包含有新的編碼和解碼實現的 fountain-codes
分支,運行 go generate
來執行 gomobile
和 gopherjs
命令,而後只須要幾秒鐘,噴泉碼應用就能夠在 Swift 和瀏覽器中使用了。
我想,恐怕沒有其餘的語言可以作到了吧?
接下來我啓動了測試程序,包括啓動三腳架上的手機以及外界顯示器,配置測試參數,以及啓動自動測試,這個過程會持續將近半天的時間。此次我沒有爲了節省時間而修改二維碼錯誤級別,由於彷佛這個參數對結果的影響基本能夠忽略。
結果讓我很是震撼。
測試傳輸大概 13KB 數據所記錄的時間如今只有半秒,準確的說是 501ms —— 傳輸速率就接近 25kbps。這組記錄配置的是 12FPS、每一個二維碼 1850 字節信息,以及低錯誤矯正等級。解碼所須要的時間差別顯著降低,由於「須要循環迭代」以及重複代碼的部分在這一版本中都沒有了。以下是對比重複代碼和噴泉碼的解碼時間直方圖:
如你所見,大多數配置了不一樣 FPS 和數據塊大小的值的解碼測試時間都集中在時間軸上數字比較小的位置 —— 大多數都小於 4 秒。
這是一個更加詳細的結果:
測試結果很是優秀,因此我決定使用大於 1000 字節的塊來運行測試 —— 塊大小最高能夠達到 2000 字節。這爲我呈現了很是有趣的結果:不少塊大小在 1400 到 1700 字節的測試超時了,可是 1800-2000 字節的塊的結果確是目前來講最好的:
在此次測試中,FPS 的影響彷佛顯得更加微不足道了,可是卻能夠得出全部配置中最好的結果,我甚至能夠將其提高到 15FPS:
以下是測試結果的完整的可交互 3D 圖:
使用噴泉碼絕對是一件讓人興奮的事情。它很出色可是又很簡單,雖然應用的範圍比較小,但卻很是實用、巧妙和快捷,它們絕對是「超酷算法」中的一份子。而當你一旦明白了它們的工做原理,它們就是那些讓你敬佩的算法之一了。
對於 txqr 項目,它們也爲之帶來了性能和可靠性的提高,我期待着可使用比 LT 編碼還要有效率的算法,並實現能適用於噴泉碼流線特性的 API。
而 Gomobile 和 Gopherjs 則經過最大可能的減小了使用在瀏覽器和移動平臺中已經編寫和測試過的代碼的麻煩,又一次展示了它們驚人的一面。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。