この内容を踏まえて、実際にコードを書いていく際のポイントなど。
2019年末の情報です。
AudioContext
今も昔も、WebAudio APIを触るなら絶対に必要なやつ。
const ctx = new AudioContext();
基本的にはコレで万事よしと言いたいところですが、Safariは今でも`webkitAudioContext`とプレフィックスが必要。
あんま知られてないと思うけど、サンプリングレートの指定もできる。
const ctx = new AudioContext({ sampleRate: 8000 });
ただしこれも、Safariでは動かない・・。
PCMデータを得る
2019年の末の選択肢としては2つ。
- ScriptProcessor
- AudioWorkletProcessor
それぞれ見てみる。
ScriptProcessor
- いわゆるデファクト
- ただしメインスレッドで動くので、非推奨とされ仕様からは消された存在
- 正確には、コールバックがメインスレッドで呼ばれてしまう
const ctx = new AudioContext(); // bufferSize, numberOfInputChannels, numberOfOutputChannels const processorNode = ctx.createScriptProcessor(1024, 1, 1); processorNode.onaudioprocess = ev => { const { inputBuffer, outputBuffer } = ev; // ここでinputBufferから各チャンネルを取り出す // もちろん最初に指定したチャンネル数しかない // それぞれは AudioBuffer というクラスになってる // こうすると得られるサンプルは、Float32Array const samples = inputBuffer.getChannelData(0); // 音声処理はここでやる // 最終的にoutputBufferへ返す // 単にbypassするならこう outputBuffer.getChannelData(0).set(samples); }; // IN fooNode.connect(processorNode); // OUT processorNode.connect(ctx.destination);
サンプリングレートに応じた頻度で、この`onaudioprocess`イベントが発火するようになってる。
なので`bufferSize`が`1024`で、`sampleRate`が`8000`の場合は、7回/秒くらいで呼ばれる。
ちなみに量子化ビット数は`Float32Array`なので32bitと、中々の高音質。
1イベントで得られるサンプルの長さは`bufferSize`によって決まり、範囲は`256~16384`らしい(Chromeいわく)
引数を`0`にすると、ブラウザが自動で選んでくれる + それがおすすめらしい。
AudioWorkletProcessor
`ScriptProcessor`が非推奨になって、こっちを使えというやつ。
詳しいことは以下の記事を。
ちょっと古いけど、状況は何も変わってなかったので・・。
ただこのモダンなAPIは、Chromeでしか実装されておりません!
こっちは`process()`が呼ばれるたびに、チャンネルごとの`Float32Array(128)`が固定長で得られる。
呼ばれる頻度は変わらずサンプリングレートに依存していて、8000Hzの場合は`8000 / 128 = 62.5回/秒`くらい。
こっちは`AudioBuffer`ではなくただの`Float32Array`なので、`getChannelData()`とかできないので注意。
はやく普及してくれ〜〜。
WebRTCもどき(エンコード)
OPUSやPCMUやFLACやらなんでもいいけど、独自にエンコードしたい場合。
基本的にはもう察しが付くと思いますが、`ScriptProcessor`か`AudioWorkletProcessor`で、PCMをエンコードして飛ばす。
`ScriptProcessor`ならメインスレッドにいるのでそのまま`WebSocket`などにつなげる。
単に`WebWorker`を作って、そっちにPCMを渡して、処理 + `WebSocket`で飛ばすのも手。
`AudioWorkletProcessor`は`AudioWorkletGlobalScope`にいるので、`WebSocket`がありません。
なので、`port`プロパティを使って`postMessage()`してメインスレッドに戻す必要がある。
そう考えると、この用途に`AudioWorkletProcessor`はあんまりいらなくて、単なる`WebWorker` + `ScriptProcessor`でいい説。
`WebWorker`でメインスレッドからPCMデータを受け取って、中でWASMを使ってエンコードして、メインスレッドへ返す or WebSocketで飛ばす。
WebRTCもどき(デコード)
実はコーデック次第では、`decodeAudioData()`でそのまま`AudioBuffer`にして再生できちゃう。
ブラウザが`audio`要素でそのまま再生できるような`mp3`とか`flac`とかそういったものなら。
Media Capabilities APIとかで確認してもよいはず。
あとはそれを適当にキューイングしながら再生すればよい。
自分でデコードする場合は、送信側と受信側でサンプルレートをあわせることをお忘れなく。
おまけ: 他にわかったこと
AudioNodeの作成
const ctx = new AudioContext(); // old const gain = ctx.createGain(); gain.gain.value = 0; // new const gain = new GainNode(ctx, { gain: 0 })
AudioNodeの接続
// old sourceNode.connect(compNode); compNode.connect(gainNode); gainNode.connect(ctx.destination); // new sourceNode .connect(compNode) .connect(gainNode) .connect(ctx.destination);
まぁ複数つなぎたい場合は困るけど、1本ならシュッと書ける。
sampleRate
最近のモダンブラウザは、だいたいデフォルトで`48000`です。
ただし、iOSのSafariだけは、`44100`だった。
かつ、Safariは`sampleRate`の指定ができないので、他の環境で指定して合わせる必要がある。
OfflineAudioContext
使ったことないけど、一気にダウンサンプリングしたいときなどに有用らしい。
// Chrome, Firefox new OfflineAudioContext({ channels: 1, length: 10, sampleRate: 48000 }); // Safari new webkitOfflineAudioContext(1, 10, 48000);
引数がぜんぜん違うとSOで話題だった。
ConstantSourceNode
入力としてのPCMはいらないけど、決まったレートで`AudioWorkletProcessor `を動かしたいときとかに便利。