Chrome插件:雲音樂聽歌識曲
圖片來源: Chrome插件-雲音樂聽歌
本文作者:空吾
當你用網頁在視頻網站刷視頻的時候,有沒有碰到過一個 BGM 激起你內心的波瀾,而你卻不知道它的名字。此時只能打開手機進行聽歌識曲,而通過一個瀏覽器的插件卻更容易解決這個問題。不需要繁瑣的掏出手機,也不會因為需要外放而干擾他人,更不會因為環境噪音而識別困難。
如果你恰好也有這個需要,不妨試一下雲音樂出品的 Chrome 瀏覽器插件「雲音樂聽歌」,還可以直接進行紅心收藏哦。也可以到插件官網預覽實際運行的效果。
背景
目前 Chrome 商店上存在的聽歌識曲插件,大都是國外出品,國內產品寥寥,對於國內音樂支持較差。既然雲音樂有這個能力,我們希望將這樣的功能覆蓋每一個角落,傳遞音樂美好力量。與此同時市面上的插件大多還是基於 manifest v2 實現(相對於 manifest v3,安全性、性能、隱私性均較差),普遍的做法是將音頻錄製之後直接交給服務端,通過服務端進行指紋提取,徒增服務端計算壓力,增加網絡傳輸。
那麼有沒有辦法既能使用 manifest v3 協議進行功能實現,同時將音頻指紋提取這一計算放在前端呢?
Chrome瀏覽器插件新協議
本文的重心不在如何實現一個瀏覽器插件本身,如果你不瞭解插件本身的開發,可查閲 Google 官方的開發文檔。
特別説明的是,manifest v2(MV2) 即將被廢棄,在 2022 年逐步不接受更新,2023 年將會逐步不能運行,本文所有的內容都是基於更安全、性能更好、隱私更強的 manifest v3(MV3)進行實現。
協議升級對功能的實現方式也會帶來一些變化,因為 MV3 更安全的限制,一些基於 MV2 靈活的實現方式(例如:執行遠程代碼、可以使用 eval、new Function(...) 等不安全方法)將不能使用。而這會對聽歌識曲插件帶來一些實現上的難題。
MV3 協議對插件實現核心影響點:
- 原有的 Background Page 使用 Service Worker 進行替代,這意味着在 Background Page 不再能進行 Web API 等操作。
- 遠程代碼託管不再支持,無法進行動態加載代碼,意味着可執行的代碼都需要直接打包到插件中。
-
內容安全策略調整,不再支持不安全代碼的直接執行。WASM 初始化相關函數無法直接運行。
聽歌識曲的實現
聽歌識曲本身技術比較成熟,整體的思路是通過 音頻數字採樣 ,進行音頻 指紋的提取 ,最後將指紋在數據庫進行 匹配 ,特徵值最高的即是所認為識別到的歌曲。
瀏覽器插件中的音頻提取
利用插件進行網頁內的音視頻錄製其實非常簡單,只需要 chrome.tabCapture
API 即可實現網頁本身的音頻錄製,獲取到的流數據我們需要針對音頻數據進行採樣,保證計算 HASH 的規則和數據庫數據保持一致。
針對獲取的 stream 流可以進行音頻的轉錄採樣,一般有三種處理方式:
- createScriptProcessor :此方法用於音頻處理最為簡單,但是此方法已經在 W3C 標準裏標記為廢棄。不建議使用
- MediaRecorder:藉助媒體 API 也可以完成音頻的轉錄,但是沒有辦法做到精細處理。
- AudioWorkletNode:用於替代 createScriptProcessor 進行音頻處理,可以解決同步線程處理導致導致的對主線程的壓力,同時可以按 bit 進行音頻信號處理,這裏也選擇此種方式進行音頻採樣。
基於 AudioWorkletNode 實現音頻的採樣及採樣時長控制方法:
-
模塊註冊,這裏的模塊加載是通過文件的加載方式,PitchProcessor.js 對應的是根目錄下的文件:
const audio_ctx = new window.AudioContext({ sampleRate: 8000, }); await audio_ctx.audioWorklet.addModule("PitchProcessor.js");
-
創建 AudioWorkletNode,主要用於接收通過
port.message
從 WebAudio 線程傳遞回來的數據信息,從而可以在主線程進行數據處理:class PitchNode extends AudioWorkletNode { // Handle an uncaught exception thrown in the PitchProcessor. onprocessorerror(err) { console.log( `An error from AudioWorkletProcessor.process() occurred: ${err}` ); } init(callback) { this.callback = callback; this.port.onmessage = (event) => this.onmessage(event.data); } onmessage(event) { if (event.type === 'getData') { if (this.callback) { this.callback(event.result); } } } } const node = new PitchNode(audio_ctx, "PitchProcessor");
-
處理
AudioWorkletProcessor.process
,也就是 PitchProcessor.js 文件內容:process(inputs, outputs) { const inputChannels = inputs[0]; const inputSamples = inputChannels[0]; if (this.samples.length < 48000) { this.samples = concatFloat32Array(this.samples, inputSamples); } else { this.port.postMessage({ type: 'getData', result: this.samples }); this.samples = new Float32Array(0); } return true; }
取第一個輸入通道的第一個聲道進行數字信號的收集,收集到符合定義的長度(例如這裏的48000)之後通知到主線程進行信號的識別處理。
基於 process
方法可以做很多有意思的嘗試,比如最基礎的白噪音生成等。
音頻指紋提取
提取到音頻信號之後,下一步要做的就是對信號數據進行指紋提取,我們提取到的其實就是一段二進制數據,需要對數據進行傅里葉變換,轉換為頻域信息進行特徵表示。具體指紋的提取的邏輯是有一套規整的複雜算法,常規的指紋提取方法:1) 基於頻帶能量的音頻指紋;2)基於landmark的音頻指紋;3)基於神經網絡的音頻指紋,對算法感興趣的可以閲讀相關論文,例如: A Highly Robust Audio Fingerprinting System
。整個運算有一定的性能要求,基於 WebAssembly 進行運算,可以獲得更好的 CPU 性能。現如今,C++/C/Rust 都有比較便捷的方式編譯成 WebAssembly 字節碼,這裏不再展開。
接下來,當你嘗試通過在插件場景中運行 WASM 模塊初始化的時候,你大概率會遇到如下異常:
Refused to compile or instantiate WebAssembly module because 'wasm-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' ...
這是因為在使用 WebAssembly 的時候需要遵循嚴格的 CSP 定義,對於 Chrome MV2 可以通過追加 "content_security_policy":"script-src 'self' 'unsafe-eval';"
進行聲明解決。而在 MV3 中,由於更加嚴格的隱私及安全限制,已經不允許這種簡單粗暴的執行方式了。
MV3 中,對於插件頁面 CSP 定義中的 script-src object-src worker-src
只允許取值為:
-
self
-
none
-
localhost
也就是沒有辦法定義 unsafe-eval 等屬性,所以想單純在插件頁面裏直接運行 wasm 已經不可行了。
到這似乎已經到了絕路?方法總比問題多,細品文檔,發現文檔有這樣一句描述:
CSP modifications for sandbox have no such new restrictions. ——Chrome插件開發文檔
也就是説這種安全限制在沙盒模式下是沒有的。插件本身可以定義sandbox 頁面,這種頁面雖然無法訪問 web/chrome API,但是它可以運行一些所謂“不安全”的方法,例如 eval、new Function、WebAssembly.instantiate
等。
所以可以藉助沙盒頁面進行 WASM 模塊的加載及運行,將計算的結果返回給主頁面,整體的指紋採集的流程就變成,如下圖:
對於主頁面和沙盒頁面如何進行數據通信,可以通過在主頁面裏邊加載 iFrame 的方式,藉助iFrame的 contentWindow 和主 window 進行數據聯通,數據流程如下圖:
到這裏完成了基本的音頻的提取及指紋提取的過程,剩下的部分就是通過指紋在數據庫進行特徵匹配。
特徵匹配
提取到的音頻指紋後,接下來就是到指紋庫裏進行音頻檢索。指紋庫可以用散列表實現,每個表項表示相同指紋對應的音樂ID和音樂出現的時間,構建出指紋數據庫。從數據庫中訪問提取的指紋即可獲取匹配的歌曲。當然這只是一個基本流程,具體的算法優化方式各家還是有很大的差異,除了版權原因,算法直接導致了各家匹配的效率和正確率。而插件這裏的實現還是以效率優先的方式。
寫在最後
以上大致描述了基於 WebAssembly
與 MV3實現聽歌識曲插件的大致流程。插件雖然靈活易用,但是 Google 也意識到了插件帶來的一些安全、隱私等問題,從而進行了一次大規模的遷移。MV3 協議更加具備隱私和安全性,但也限制了不少功能的實現,在2023年之後會有大批量的插件無法繼續使用。
關於聽歌識曲插件目前已完成的功能包括音頻識別、紅心歌單收藏等,後續還將繼續功能拓展,希望這個小功能可以幫助到你。
參考資料
- http://developer.mozilla.org/en-US/
- http://developer.chrome.com/docs/apps/
- http://www.w3.org/TR/webaudio/#widl-AudioContext-createScriptProcessor-ScriptProcessorNode-unsigned-long-bufferSize-unsigned-long-numberOfInputChannels-unsigned-long-numberOfOutputChannels
- http://developer.mozilla.org/zh-CN/docs/WebAssembly/C_to_wasm
- http://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=152C085A95A4B5EF1E83E9EECC283931?doi=10.1.1.103.2175&rep=rep1&type=pdf
本文發佈自網易雲音樂技術團隊,文章未經授權禁止任何形式的轉載。我們常年招收各類技術崗位,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!
- 為什麼一定要從DevOps走向BizDevOps?
- 雲音樂FeatureStore建設與實踐
- web技術分享| 【高德地圖】實現自定義的軌跡回放
- Object.prototype.toString.call()的原理
- 探針技術-JavaAgent 和字節碼增強技術-Byte Buddy
- 解決方案| 快對講綜合調度系統
- MAUI模板項目閃退問題
- 2022 年你手機裏有哪些堪稱神器的 App?
- 如何在 React Native 項目中使用 MQTT
- spring-authorization-server令牌放發源碼解析
- 劉勇智:一碼通缺陷分析與架構設計方案丨聲網開發者創業講堂 Vol.02
- systrace 統計方法耗時
- 孫勇男:實時視頻 SDK 黑盒測試架構丨Dev for Dev 專欄
- 通俗易懂講解並手寫一個vue數據雙向綁定案例
- 論 T 級互動開發如何在我們手上發光發熱
- 用原生JavaScript寫一個貪吃蛇
- 面試突擊53:常見的 HTTP 狀態碼有哪些?
- 詳解“開放雲”的真正含義!
- 一字一圖,領略瀏覽器方向的優化
- 天才製造者:獨行俠、科技巨頭和AI|深度學習崛起十年