Chrome外掛:雲音樂聽歌識曲

雲音樂技術團隊發表於2022-05-05

圖片來源: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 實現音訊的取樣及取樣時長控制方法:

  1. 模組註冊,這裡的模組載入是通過檔案的載入方式,PitchProcessor.js 對應的是根目錄下的檔案:

    const audio_ctx = new window.AudioContext({
      sampleRate: 8000,
    });
    await audio_ctx.audioWorklet.addModule("PitchProcessor.js");
  2. 建立 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");
  3. 處理 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年之後會有大批量的外掛無法繼續使用。

關於聽歌識曲外掛目前已完成的功能包括音訊識別、紅心歌單收藏等,後續還將繼續功能擴充,希望這個小功能可以幫助到你。

參考資料

本文釋出自網易雲音樂技術團隊,文章未經授權禁止任何形式的轉載。我們常年招收各類技術崗位,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!

相關文章