Web Audio API 第1章 基礎篇

webaudioapi

Web Audio API 第1章 基礎篇

我查了一下 Web Audio API 蝙蝠書居然在 2013 年就出版了

我又看了一下我的“豆瓣讀書”頻道內,這本書加入到“在讀”標籤是在 2021 年了

一直沒有堅持看這本書的原因有兩點,

一是本書是英文版的,不像中文看的那麼流暢

二是在前端開發業務中一直沒有遇到複雜到需要用到 Web Audio API 的場景

這不在 2023 年末業務上就遇上了需要處理音頻的業務麼,督促自己看下去,順便翻譯了

因爲此書已是 10 年前寫的了,代碼有些可能比較古早,我將會用 JavaScript 重新實現相關 demo

demo 會放在 https://github.com/willian12345/WebAudioAPI/tree/master/examples

我並非音樂相關專業人士,所以對一些專業用語可能翻的沒那麼專業,但這不重要,重要的就是先過一遍

對 Web Audio API 能做啥有個印象,用到的時候再仔細深入

總之沒用的知識又可以增加了!!
譯者 (王二狗 Sheldon 池中物) 注

正文開始

此章將描述從何處入手 Web Audio API, 哪些瀏覽器支持,如何檢測哪些 API 可用,什麼是音頻可視化,音頻節點是什麼,如何將音頻節點組合連接在一起,介紹一些基礎的音頻節點類型,最後加載音頻並播放

Audio 音頻在網頁中的歷史小知識

最早在網頁中播放音頻使用的是 標籤,當有人訪問網頁時,網頁創作者使用此標籤就可自動播放背景音頻。這個特性只能 IE 瀏覽器上可用,它從未被標準化或被其它瀏覽器所實現過。網景瀏覽器實現了一個類似的 標籤, 提供了相似的播放功能。

Flash 最早在網頁中實現了跨瀏覽器播放音頻的功能,但它有個比較大的缺點就是需要以插件方式運行。最近瀏覽器開發商都投向了 HTML5

儘管在網頁上播放音頻不再需要插件了,但

  • 沒有精確的可控定時器
  • 一次可播放的音頻數量太少
  • 沒有可靠的預緩衝能力
  • 無法應用實時音頻特效
  • 沒有可用的方法對音頻進行分析

他們多次嘗試推出強大的 audio API 來解決我上面提到的這些音頻限制。推出的這些 API 中值得注意的就是火狐瀏覽器設計的 Audio Data API 原型實現。Mozilla 試圖用

相比於 Audio Data API, Web Audio API 使用了全新的模式,完全獨立於

遊戲與交互

音頻是交互體驗的重要組成部分。如果你不信,試着觀看一部關掉聲音的電影。

在遊戲中更是如此!我最愛的電子遊戲令我記憶最深的就是其中的音樂與音效。就算過了將近20年,塞爾達和暗黑破壞神的音效在我的腦海中仍然揮之不去。
無論是暴雪的《魔獸爭霸》和《星際爭霸》系列中的圈點士兵音效還是任天堂經典遊戲中的各種音效,這些精心設計的遊戲音效立馬就可以被識別出來。

音效在遊戲外的應用也同樣重要。它們在命令行工具內交互之始就被應用於 UI 的交互上,當輸出出錯時就發出“嗶”的一聲。同樣被應用現代交互 UI 上,一般用於提醒功能,鈴聲,也應用於音視頻通訊比如 Skype。像 Google Now 和 Siri 這樣的助手軟件同樣提供了豐富的音效反饋。當我們深入發掘這個世界,通用計算設備,語音和手勢交互等可脫離屏幕的交互方式更加的依賴於音頻的反饋。最後,對於視障視弱的計算機用戶來說,音頻提示,語音合成與識別提供了最主要的用戶體驗原則

可交互的音頻也代表着一些有趣的挑戰。爲了創建合適的遊戲音頻,設計師需要調整好遊戲玩家所有不可預知的狀態。在實踐中,遊戲中某部分的時長可能是未知的,音效與周邊環境交互產生更復雜的混音,需要環境特效音且取決於相關的音頻位置。最終可能同一時刻播放着一堆音頻,全都得組合在一起的音效即保證質量又不影響渲染性能

Audio Context

Web Audio API 是建立在 audio context 音頻上下文的概念之上的。音頻上下文是描述音頻所有節點的,這些節點定義了音頻流是如何從起點(一般是音頻文件)到目的地(一般就是你的揚聲器)。當音頻通過每個節點時,音頻的屬性可被修改和查看。最簡單的音頻上下文就是起始節點到結束節點的直連如圖 1-1

圖 1-1

一個音頻上下文可能會非常複雜,在音頻的起點與結束節點之間包含衆多的音頻節點(圖1-2)進行任意衆多的高級合成或分析。

圖 1-1 和 1-2 方塊表示音頻節點。箭頭代表節點之間的連接。這些節點經常擁有衆多的輸入和輸出連接。默認情況下,如果一個音頻節點擁有多個輸入源,Web Audio API 會簡單的混成一個音頻信號。

音頻節點圖的概念並不新鮮,它來源於一些流行的音頻處理框架,如 Apple 的 CoreAudio, 它提供了類似的音頻處理 圖像 API。它本身的概念很久遠了,可追溯到1960年代的早期處理音頻,比如 Moog 模塊化混成系統(Moog modular synthesizer systems)。

圖 1-2

初始化音頻上下文

Web Audio API 現已被 Chrome 和 Safari 瀏覽器實現(包含 IOS 6 手機版 Safari )並且通過 JavaScript 開放給了網頁開發者。在這些瀏覽器上,音頻上下文創建時需要加上 webkit 前綴,你不能直接使用 new AudioContext 而應該使用 new webkitAudioContext 創建。然而在未來在 API 足夠穩定且多數瀏覽器供應商都實現了後前綴可去掉。Mozilla 已宣佈在火狐瀏覽器上實現 Web Audio API,Opera 也開始參與進工作組。記得這一點即可,下面是通用的兼容寫法:

var contextClass = (window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext);
if (contextClass) {
  // Web Audio API is available. 
  var context = new contextClass();
} else {
  // Web Audio API is not available. Ask the user to use a supported browser.
}

一個音頻上下文即可支持多個音頻的輸入與複雜的音頻圖譜,一般來講,一個應用使用一個音頻上下文即可。音頻上下文的實例包含了衆多的方法用於創建音頻節點以及操作全局音頻屬性。幸運的是這些方法不需要加前綴,且相對穩定。 API 仍在變更,所以還是要小心會有大的變化。

音頻節點的類型

音頻上下文主要的一個功能就是創建新的音頻節點。廣義上來講,一般包含以下幾個節點類型:

  • 源節點(Source nodes)
    音源,如音頻緩衝,直播音頻輸入,

  • 修飾節點(Modification nodes)
    Filters, convolvers, panners, JS 處理器 等等

  • 分析節點(Analysis nodes)
    分析器, JS 處理器

  • 結束節點(Destination nodes)
    音頻輸出和結束處理緩存

音頻源不限於音頻文件,可用直播音頻流或麥克風,

連接音頻圖

使用 connect() 方法可以將任意音頻節點輸出連接至任意其它的音頻節點。在以下例子,我們連接了音源節點的輸出到 gain 節點,並將其輸出連接到了結束節點:

// Create the source.
var source = context.createBufferSource(); 
// Create the gain node.
var gain = context.createGain();
// Connect source to filter, filter to destination.
source.connect(gain);
gain.connect(context.destination);

注意,context.destination 是一個特殊的節點,一般爲系統的默認音頻輸出。上述代碼形成的音頻圖如下 1-3:

圖 1-3

一旦像上面這樣我們連接了音頻圖就可以動態改變它。我們可以調用 node.disconnect(outputNumber) 來斷開節點連接。例如,重新直連源音頻節點至結束音頻節點,避開中間 gain 節點 代碼如下:

source.disconnect(0);
gain.disconnect(0);
source.connect(context.destination);

模塊化路由的力量

在很多遊戲中,通常要將多個音源最終混成一個聲音。這些音源包含了背景音樂,遊戲音效,UI反饋音,在多人遊戲中還有其它玩家的聊天音頻。Web Audio API 的一個非常重要的特性就是它允許你單獨且完整的控制某個音或者組合在一起控制。音頻圖看起來應該像下面這樣圖 1-4:

我們分別使用 gain 音頻節點將分開的聲道音源聯合在一起,並使用一個主 gain 節點來控制它們。 通過這樣的設置,非常方便按需精準控制單獨的聲道級別。例如,非常多的用戶在玩遊戲過程中更喜歡把背景音關掉。

重要理論:什麼是聲音?

就物理上而言,聲音是一種縱波(有時也被稱爲壓力波)它們通過空氣或其它媒介傳播。 音源產生是由於空氣內分子之間的振動與碰撞。它們的聚散導致了不同區域的高低壓。如果你有能力凍結時間那麼就可以觀察到聲波圖像,可能類似於圖 1-5

圖 1-5

圖 1-6

數學上來講,聲音可被表示爲函數,通過時間軸上的壓力範圍值。 圖 1-6 展示了函數圖像。 可以看到它模擬的是圖 1-5 ,值高的地方表示粒子稠密的區域(高壓),值低則表示粒子稀疏的區域(低壓)

首次捕獲並重建聲音的能力得追溯到 20 世紀初了。麥克風捕獲壓力波並轉化成電子信號,(舉例)用 +5 伏電壓代表最高壓,-5 伏電壓代表最低壓。與之相反,音頻播放器獲取這些電壓並將其轉換回壓力波,這樣我們才能聽到。

無論我們是在分析聲音還是合成聲音, 對於音頻編程者來說感興趣的比特信息都在黑盒內操作音頻信號圖 1-7 。早期操作音頻這個空間被模擬濾波器和其他硬件佔據,按照今天的標準,這些硬件會被認爲是過時的。現在有更多數字處理設備代替老的模擬設備。但在使用軟件處理音頻前,我們需要先將聲音轉換成電腦能處理的信息

圖 1-7 聲音的記錄與回放

重要理論:什麼是數字聲音?

我們可以這樣做,對模擬信號按時間進行一定頻率的採樣,對每個信號進行編碼,編成一個數字。 對模擬信號的採樣被稱爲採樣率。一般音頻軟件處理通常採樣率爲44.1kHz。這意味着每一秒對聲音進行記錄 44100 個數值。這些數值限定在某個範圍內。每個值通常分配一定數量的位,稱爲位深度(bit depth)。對於大多數數字音頻錄製,包括CD,位深度 16 對於大多數人來說足夠了。

對於音樂發燒友來說,24位深度也夠了,已經有足夠的精度,使用再高的位深度人耳已很難區別開來了。

模擬信號轉數字信號被稱爲數字化(或採樣),可抽象爲 圖 1-8

圖 1-8

在圖 1-8 ,數字化信號的版本的長條與模擬信號版本光滑的曲線看起來區別非常大。隨着採樣率增高與採樣深度的增加區別會越來越小(藍色部分)。然而這些值的增加也意謂着更多存儲內空間的付出。爲了節約空間,電話系統通常使用的採樣率低至 8 kHz, 因爲使人類聲音清晰可聽的頻率範圍遠遠小於我們可聽到的全部頻率範圍.

對於聲音的數字化,計算機通常會像對待長數字的數組一樣處理。這樣的編碼被稱爲脈衝編碼調製 PCM(pulse-code modulation)。因爲計算機非常善於處理數組,PCM 對於大多數數字音頻應用來說是一個非常強大的基元。在 Web Audio API 的世界,長數字的數組的聲音被表示爲音頻緩衝(AudioBuffer). 音頻緩衝可以存儲多條音頻通道(通常在立體聲中,包含左聲道和右聲道)被表示爲標準化從 -1 到 1 後的浮點數數組。同樣的信號當然也可以被表示爲整形數組,在 16 位長度下,範圍從 -215 至 215 - 1。

重要理論:音頻編碼格式

原始的音頻 PCM 格式非常巨大, 它會造成額外的存儲浪費,在下載時也會造成額外的帶寬浪費。正因如此,音頻通常會存儲爲壓縮後的格式。有兩種類型的壓縮方式:有損壓縮和無損壓縮。無損壓縮(如, FLAC)保證在壓縮與解壓後比特位信息完全相同。有損壓縮(如,MP3) 利用人類聽覺特性會丟棄掉部人類聽不到的分比特位信息用於節約存儲空間。有損壓縮對於大多數人來說足夠用了,音樂發燒友除外。

通常度量音頻壓縮量被稱作比特率,它代表着重放每秒音頻所需要的比特位數量。比特率越高單位時間內可利用的數據就越多所需的壓縮就越少(更精確)。通常有損壓縮格式,比如 MP3 定義爲它們的比特率(一般爲 128 到 192 Kb/s)。有損格式的編解碼有可能使用任意的比特率。舉個例子,電話人聲音頻通常使用 8Kb/s MP3 格式。一些格式支持可變比特率如 OGG 格式。比特率會隨着時間變化而變。注意此處的比特率別和採樣率或比特深度混淆了

瀏覽器對不同音頻格式的支持差別很大。一般來說,如果是在瀏覽器上, Web Audio API 的實現與

Firefox Chrommium 現在是支持 mp3 格式的。

聲音的加載與播放

Web Audio API 將緩衝與音源(source)節點區別的很清晰。這樣的架構有利於解構音頻資源與播放狀態。以唱片機爲例,緩衝區就像唱片,而源就像播放指針,而在 Web Audio API 的世界中你可以同時在多個播放指針處播放同一個唱片。由於許多應用程序涉及同一緩衝區的多個版本同時播放,因此這種模式是必不可少的。舉個例子,
如果您希望快速連續地發出多個彈跳球的聲音,則只需要加載一次彈跳緩衝並安排多個音源(source)。

在 Web Audio API 加載一個音頻樣本,我們可以使用一個 XMLHttpRequest 加載並對加載的結果用context.decodeAudioData進行音頻解碼處理。這些都是異步的不會阻塞主 UI 進程:

var request = new XMLHttpRequest(); 
request.open('GET', url, true); 
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
  context.decodeAudioData(request.response, function(theBuffer) { 
    buffer = theBuffer;
  }, onError);
}
request.send();

此代碼較老,用 fetch 請求數據更簡單,我的 demo 裏就是用 fetch 代替了 XMLHttpRequest

音頻緩衝只是播放源的一種。其它播放源包括直接來自麥克風或其它音源輸入設備,或者

一旦你加載了緩衝,你可以爲它創建一個(source node)源節點(AudioBufferSource Node),把它連接到音頻圖並在源節點(source node)調用 start(0)。結束播放調用 stop(0).注意,這兩個函數在當前音頻上下文的座標系統調用都需要消耗時間(見第2章)

function playSound(buffer) {
  var source = context.createBufferSource(); 
  source.buffer = buffer; 
  source.connect(context.destination); 
  source.start(0);
}

遊戲中通常都有循環播放的背景音樂。然而,需要注意不要過度重複你的選擇:如果玩家被困在了某地或某個關卡,同樣的樣本在背景中不斷播放,爲了防止玩家沮喪,背景音聲道的逐漸淡出可能是值得考慮的。另一種策略是根據遊戲情境將不同強度的元素混合在一起(文章後續會提到)。

將它們整合在一起

如你所見以上的代碼,Web Audio API 需要一些初始化的程序設置。在真正的遊戲中,可以考慮圍繞 Web Audio API 執行 JavaScript 抽象。例如後續的 BufferClass 類。它將所有東西都整合進一個簡單的加載器上,加載器提供了設置路徑,返回音頻緩衝的功能。以下是如何使用 BufferLoader 類的代碼:

window.onload = init; 
var context;
var bufferLoader;
function init() {
  context = new webkitAudioContext();
  bufferLoader = new BufferLoader( context,
            [
              '../sounds/hyper-reality/br-jam-loop.wav',
              '../sounds/hyper-reality/laughter.wav',
            ],
            finishedLoading
        );
  bufferLoader.load();
}

function finishedLoading(bufferList) {
    // 創建兩具音源並把它們整合到一起播放
    var source1 = context.createBufferSource();
    var source2 = context.createBufferSource(); 
    source1.buffer = bufferList[0];
    source2.buffer = bufferList[1];
    source1.connect(context.destination);
    source2.connect(context.destination);
    source1.start(0);
    source2.start(0);
}

BufferLoader 類的實現可參 https://github.com/willian12345/WebAudioAPI/blob/master/examples/Bufferloader.js

音頻播放例子可參考 https://github.com/willian12345/WebAudioAPI/blob/master/examples/ch01/index.html


注:轉載請註明出處博客園:王二狗Sheldon池中物 ([email protected])

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章