Web Audio API 實現簡單變聲效果

前言

想在網頁中實現實時音頻變聲效果該如何實現呢,之前遇到這種處理音視頻的需求,可能會想到需要藉助 C 代碼實現。但是現在隨着瀏覽器性能的提升、web API 的豐富,通過瀏覽器原生的 API 也可以操作音頻數據實現很多複雜的效果,爲 web 音頻開發提供了更多的選擇。下面介紹幾種採用原生 Web Audio API 實現變聲效果的過程中嘗試的幾種方案,感興趣的同學一起來了解下吧。

說明:本文討論範圍爲變聲場景中的變速變調方案,有其它兩種場景:變速不變調、變調不變速需求的同學請移步參考鏈接或其它方案

Web Audio API 介紹

開始之前先簡單瞭解下 Web Audio APIWeb Audio API 提供了一組在 web 上操作音頻的 API,可以使開發者自選音頻數據來源,爲音頻添加效果,使聲音可視化,爲聲音添加空間效果等功能。音頻的輸入流可以理解爲一組 buffer,來源可以是讀取音頻文件產生到內存中的AudioBufferSourceNode,也可以是來自 HTML 中 audio 標籤的MediaElementAudioSourceNode,也可以是來自音頻流(例如麥克風)的MediaStreamAudioSourceNode。例如,採集自己設備上的麥克風聲音連接到揚聲器:

// 創建音頻上下文
const audioContext = new AudioContext();
// 獲取設備麥克風流
stream = await navigator.mediaDevices
  .getUserMedia({ audio: true})
  .catch(function (error) {
    console.log(error);
  });
// 創建來自麥克風的流的聲音源
const sourceNode = audioContext.createMediaStreamSource(stream);
// 將聲音連接的揚聲器
sourceNode.connect(audioContext.destination);

就可以對着麥克風說話聽到自己的聲音了。對上述來源數據流的處理被設計成一個個的節點(Node),具有模塊化路由的特點,需要添加什麼樣的效果添加什麼樣的 node,例如一個最常見的操作是通過把輸入的採樣數據放大來達到擴音器的作用(GainNode),示例代碼:

// 創建音頻上下文
const audioContext = new AudioContext();
// 創建一個增益Node
const gainNode = audioCtx.createGain();
// 獲取設備麥克風流
stream = await navigator.mediaDevices
  .getUserMedia({ audio: true})
  .catch(function (error) {
    console.log(error);
  });
// 創建來自麥克風的流的聲音源
const sourceNode = audioContext.createMediaStreamSource(stream);
// 將聲音經過gainNode處理
sourceNode.connect(gainNode);
// 將聲音連接的揚聲器
gainNode.connect(audioContext.destination);
// 設置聲音增益,放大聲音
gainNode.gain.value = 2.0;

以上只是連接了聲音放大的 node,如果想要增加其它效果,可以繼續往上添加 node 連接 connect,例如濾波器(BiquadFilterNode)、立體聲控制(StereoPannerNode)、對信號進行扭曲(WaveShaperNode)等等。這種模塊化設計提供了靈活的創建動態效果和複合音頻的方法,是不是有種變魔法的感覺,哪裏修改點哪裏(添加 Node)非常方便。例如,以下展示了一個利用 AudioContext 創建四項濾波器節點(Biquad filter node)的例子:

var audioCtx = new (window.AudioContext || window.webkitAudioContext)();

// 創建多個不同作用功能的node節點
var analyser = audioCtx.createAnalyser();
var distortion = audioCtx.createWaveShaper();
var gainNode = audioCtx.createGain();
var biquadFilter = audioCtx.createBiquadFilter();
var convolver = audioCtx.createConvolver();

// 將所有節點連接在一起

source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(biquadFilter);
biquadFilter.connect(convolver);
convolver.connect(gainNode);
gainNode.connect(audioCtx.destination);

// 控制雙二階濾波器

biquadFilter.type = "lowshelf";
biquadFilter.frequency.value = 1000;
biquadFilter.gain.value = 25;

可以看到爲聲音流添加處理效果就像穿項鍊一樣,一個接一個,最後得到最終效果,實現效果可以參考官方樣例 voice-change-o-matic。一個簡單而典型的 web audio 流程如下:

  1. 創建音頻上下文

  2. 在音頻上下文裏創建源 — 例如, 振盪器,流

  3. 創建效果節點,例如混響、雙二階濾波器、平移、壓縮

  4. 爲音頻選擇一個目的地,例如你的系統揚聲器

  5. 連接源到效果器,對目的地進行效果輸出

變聲效果實現

首先回顧一下聲音的基礎知識,聲音是由物體振動產生的機械波,常接觸到的有以下三個特性:

這裏說的變聲效果是改變聲音的音調,變聲效果根據不同的場景可以分爲變速不變調、變調不變速以及變調又變速 3 種。變速是指把一個語音在時域上拉長或縮短,而聲音的採樣率、基頻以及共振峯都沒有發生變化。變調是指把語音的基因頻率降低或升高,共振峯做出相應的改變,採樣頻率不變。各種方案應用場景如下:

  1. 變速不變調: 各種各樣的視頻播放器中的 2 倍速,0.5 倍速播放就是應用的語音變速不變調原理;當然變速不變調還應用於網絡電話 VOIP 中的應對網絡抖動,簡單的說,就是當網絡不好的時候,播放端從網絡中拉取到的數據少,緩存區的數據不夠用,這個時候就使用緩存的數據播放的慢一點。反之,緩存區數據過多,就播放的快一點。這部分的實現可以參照 webrtc 的 netEQ 模塊。平時在使用微信語音的時候應該能感受到網絡特別卡時,爲了保持語音連續,會故意慢放語音。

  2. 變調不變速: 變調不變速主要應用在聲效上,聲音提高音調將男聲變成女生,或則將女生變成男聲;另外,變調不變速配合其他一些音效算法,如 EQ,混響,tremolo 和 vibrato 可以實現變聲效果,比如 QQ 上的蘿莉音,大叔音等。

  3. 變速變調: 改變聲音播放速率情況下,音調音色也會隨着改變,例如玩過磁帶的都知道,按快進功能會使聲音變尖提高音調,慢放功能使聲音變粗,降低音調。

前兩種實現都要求對聲音知識領域有更深的瞭解,聲音時域、頻域,信號的傅里葉變換變化都要去重新去複習一下,學習成本比較高,這裏使用第 3 種方式,比較好接入。要改變聲音的播放速率,Web Audio API 中提供了AudioBufferSourceNodeplaybackRate屬性,可以設置音頻的播放速率,使用音頻上下文AudioContext.createBufferSource獲得實例,示例代碼如下:

const play = ()=> {
  const audioSrc = ref("src/assets/sample_orig.mp3")
  const url = audioSrc.value
  const request = new XMLHttpRequest()
  request.open('GET', url, true)
  request.responseType = 'arraybuffer'

  request.onload = function() {
    const audioData = request.response
    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  
    audioCtx.decodeAudioData(audioData, (audioBuffer) => {
      let source = audioCtx.createBufferSource();
      source.buffer = audioBuffer;
      // 改變聲音播放速率,2倍播放
      source.playbackRate.value = 2;
      source.connect(audioCtx.destination);
      source.start(0);
    });
  }
  request.send()
}

可以調整source.playbackRate.value的值來改變音調,大於 1 提高音調,小於 1 降低音調。

雖然實現了變聲效果,但是這種方式只適合播放音頻文件,或者能獲取到完整音頻流的情況,對於獲取麥克風這種持續輸入的聲音流並不適用,類似的還有 SoundTouchJS,它是某大佬實現的SoundTouch的 JS 版本,使用也是要獲取完整音頻的數據流,作者也做了相應的解釋,參考鏈接如何處理麥克風獲取的實時音頻流呢,這裏可以藉助 Web Audio API 中的ScriptProcessorNode,它允許使用 JavaScript 生成、處理、分析音頻。處理流程圖如下:利用它將實時音頻流數據處理一下,得到慢放或加速的聲音流數據。示例代碼如下:

const audioprocess = async () => {
  const audioContext = new AudioContext();

  // 採集麥克風輸入聲音流
  let stream = await navigator.mediaDevices
    .getUserMedia({ audio: true})
    .catch(function (error) {
      console.log(error);
    });

  const sourceNode = audioContext.createMediaStreamSource(stream);

  const processor = audioContext.createScriptProcessor(4096, 1, 1);
  processor.onaudioprocess = async event => {
    // 處理回調中拿到輸入聲音數據
    const inputBuffer = event.inputBuffer;
    // 創建新的輸出源
    const outputSource = audioContext.createMediaStreamDestination();
    const audioBuffer = audioContext.createBufferSource();
    audioBuffer.buffer = inputBuffer;
    // 設置聲音加粗,慢放0.7倍
    audioBuffer.playbackRate.value = 0.7
    audioBuffer.connect(outputSource);
    audioBuffer.start();

    // 返回新的 MediaStream
    const newStream = outputSource.stream;
    const node = audioContext.createMediaStreamSource(newStream)
    // 連接到揚聲器播放
    node.connect(audioContext.destination)
  };
  // 添加處理節點
  sourceNode.connect(processor);
  processor.connect(audioContext.destination)
}

另外,還有一個利用 Google 開源 jungle 實現的改變音調的庫,並且還有各種混響效果,音頻可視化等炫酷功能,也是使用的 Web Audio API 實現,github 鏈接地址放在這裏了,有興趣也可以體驗下,畫面長這樣

總結

以上就是對 Web Audio API 的簡單介紹和使用的分析,以及採用 Web Audio API 實現聲音簡單變聲效果的幾種實現,大家有哪些更好的實現方案歡迎評論區一起交流!

參考

https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

https://github.com/cwilso/Audio-Input-Effects

https://mdn.github.io/voice-change-o-matic/

https://github.com/cutterbl/SoundTouchJS

https://cloud.tencent.com/developer/news/818606

https://zhuanlan.zhihu.com/p/110278983

https://www.nxrte.com/jishu/3146.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MuBqhyDUWbqU9p8e9LhdlQ