プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

バックグラウンドタブ再生を生かすため低周波音をOBSで永続再生?

概要

  • AudioContext 経由で作った OscillatorNode を使い、機械的(周波数云々)な音を作成&再生。
  • AudioContext 経由で作った AudioBuffer に周波数を模した情報を書き込み、サウンドを作成&再生(+オマケで.wavをDL)

経緯(いきさつ)

とあるライブストリーミングサービスで自分の配信をモニタ(視聴)中、タブをバックグラウンドにすると視聴遅延が発生する。
どうやら維持判定が「オーディオが再生し続けている事」っぽい雰囲気を感じたので、
聞こえない周波数を流し続ける処理を視聴者が DevTool 上に走らせることで 維持できないかと試してみた

…が、結局うまくいかなかったので、最終的に"配信者の立場"としてOBSから不可聴周波数音を流し続ける方法にした ( そっちのがスマート )

波形再生の名残

ボリューム実数範囲が 1~0 なので、0.1
つまり、 100~0 換算で言えば 10 相当まで下げています。
周波数は 480Hz の設定で、時間は 5秒 再生。

  • 実際は不可聴周波数で運用
    例えば 1Hz とかで運用。
    遊びで 16,000Hz とかにして モスキート音 も作れますゾ~~ 👂
(()=>{
    const audioCtx = new window.AudioContext();
    const gNode = audioCtx.createGain();
    gNode.gain.value = 0.1; // ボリューム範囲 1~0
    const osc = audioCtx.createOscillator();
    osc.type = "sine"; // サイン波
    osc.frequency.setValueAtTime(480, audioCtx.currentTime); // 周波数。480Hz
    osc.connect(gNode).connect(audioCtx.destination);
    console.log(osc);
    osc.start();
    osc.stop( audioCtx.currentTime + 5 );   // 5秒間再生
})();

ですが、…折角調べて書いたのに、結局この方法では
バックグラウンドタブでの配信視聴を維持(=ラグ無効化)できませんでした 😭

同性質の wav を書き出してOBSで使うか…

という訳で、手付で波形を作成して、.wav でDL する方法へシフト。
※ 実行時音量注意 ( 音量調節部分はノータッチ )

(()=>{
    const audioCtx = new window.AudioContext();
    const ch = 1;   // 1:モノラル,2:ステレオ
    const sec = 2;  // 秒数
    const freq = 480; // 周波数。 不可聴目的なら 1Hz とか
    const frameCount = audioCtx.sampleRate * sec;
    const myBuffer = audioCtx.createBuffer( ch , frameCount , audioCtx.sampleRate );
    console.log( "サンプルレート" , audioCtx.sampleRate );
    {
        for(let nowCh=0; nowCh < ch; nowCh++){
            const nowBuff = myBuffer.getChannelData(nowCh);
            for(let i=0; i<frameCount; i++){
                // 周波数に合わせた情報を作成
                const t = i / audioCtx.sampleRate;
                nowBuff[i] = Math.sin(2 * Math.PI * freq  * t);
            }
        }
    }
    // 作ったバッファを再生するだけ。DLだけ必要であれば省いてOK
    {
        const source = audioCtx.createBufferSource();
        source.buffer = myBuffer;
        source.connect(audioCtx.destination);
        source.start();
    }

    // 変態処理をお借りしました
    toWav=w=>((
        {numberOfChannels:c,sampleRate:r},l4=x=>[x,x>>>8,x>>>16,x>>>24],l2=x=>[x,x>>>8],
        x=(x=>[...Array(x[0].length)].flatMap((_,i)=>x.flatMap(y=>l2(y[i]*0x7fff))))([...Array(c)].map((_,i)=>w.getChannelData(i)))
    )=>new Uint8Array([82,73,70,70,l4(36+x.length),87,65,86,69,102,109,116,32,16,0,0,0,1,0,l2(c),l4(r),l4(r*(c*=2)),l2(c),16,0,100,97,116,97,l4(x.length),x].flat()).buffer)(w);

    // 16bitの.wav用データへ変換 → Blob化 → DL準備 → DL → 後片付け
    {
        const resultArrBuf = toWav(myBuffer);
        const blob = new Blob( [resultArrBuf] , { type:"audio/wave" });
        const dlUrl = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = dlUrl;
        a.download = `${ch}ch_${freq}Hz_${sec}sec.wav`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(dlUrl);
    }
})();

上記設定を 1chモノラル , 1hz , 5秒 に調整したうえで、DL作成した .wav をOBS内部にループ再生配置。
OBS内の設定音量を -42.0 dB くらいにすると、普段使っているストリーミングサイトの視聴用タブがギリギリ音再生判定となった
( ※ タブにスピーカーマークがつく 🔊 )

1Hz とかにしておけば、当然人の耳には聞こえない。

おまけメモ・覚書

( ※ 執筆時の GoogleChrome バージョンは 129 系 )

雑に言うと、バックグラウンドタブに制限が掛かるのは、
リソース節約のために処理の間引きや休眠を行うChromeの機能が影響している模様。

「タブスロットリング」なる機能やら「オクルージョントラッキング」がそれらしい。

2020/11/18 窓の杜 - 「Google Chrome 87」が安定版に ~タブスロットリングやオクルージョントラッキングで高速化
2022/07/12 窓の杜 - 「Google Chrome」の省電力技術を極めた試験フラグがCanary/Dev版に導入

色々な遍歴を経て、現在は WindowOcclusionEnabled と呼ばれる機能項目になっている模様?
( しかもこれ、一度廃止されたのち再び復活したらしい )

  • Calculate window occlusion on Windows ( = WindowOcclusionEnabled )
    chrome://flags/#calculate-native-win-occlusion

    Calculate window occlusion on Windows will be used in the future to throttle and potentially unload foreground tabs in occluded windows – Windows

    Windows でウィンドウのオクルージョンを計算する機能は、将来、オクルードされたウィンドウのフォアグラウンドタブをスロットルし、アンロードするために使用される予定です。

    https://chromeenterprise.google/intl/ja_jp/policies/#WindowOcclusionEnabled

で、今回対象の配信プラットフォームでは、これを Dsabled にしても視聴遅延が発生してた、というオチ(仕様)でした orz
不可聴音のループ再生部分は制限されず、肝心の配信映像視聴部分が制限されてた、って事なんだと認識しています。