Chienomi

配信ガチ勢のためのLinuxでのAVミキシング

Live With Linux::technique

配信ガチ勢ならわかるはずだ。

複数の音ソースを混ぜたい(主にBGMとマイク、あるいはゲームサウンドとマイク)、複数の動画を混ぜたい(要はワイプを出したい)と思っても、アプリ側で対応しているかどうかにすごく左右されると。

COVID-19の流行により今まであまり配信やweb会議、ビデオ通話に縁がなかった人もそうしたものを使うようになっただろう。 それに伴い、もっと凝ったことをしたいと考えている人もいるはずだ。

だが、そうなるとどうしてもミキサーが必要になる。 Roland VR-1HDやYAMAHA AG03のようなミキサーを欲するだろうが、これらは結構高い。 (ついでに言うと、今は品薄だ)

一方、PCではごく日常的にソフトウェアミキサーというものを使っている。 もしソフトウェアミキサーがなければ、ひとつのサウンドデバイスから音を出せるのはひとつのアプリケーションだけだ。 複数のアプリケーションの音を混ぜて出力しているのは、ハードウェアにその機能が備わっているのでなければ、ソフトウェアミキサーの仕事だ。

本記事では、Linuxを利用してその機能を拡張し、PC内で扱うあらゆるソースをミックスして出力する強力なソフトウェアミキサーとして利用する話をする。

なお、この記事では完全にLinux固有の機能を含んだ解説となっている。

音声編

概要

Linuxのオーディオ機能は、現在一般的にPulseAudioが使用されている。

「PulseAudioは音が悪い」という謎風潮もあるが、非常に便利で強力なものであり、ここではPulseAudioのルーティング機能を活用する。

まずPulseAudio用語を理解する

sink

出力デバイスのこと。 サウンドカードの出力ポートなどがこれになる。

PulseAudio Volume Controlでは「再生」タブで選択できる項目になる。 音を再生しようとするときに候補になるのがsink。

source

入力デバイスのこと。主にマイク。

PulseAudio Volume Controlでは「録音」タブで選択できる項目になる。 録音というよりも、アプリケーションが音を拾うときに候補になるのがsource

monitor

そのsinkから鳴っているミックス済みの音の仮想source。 Windowsでもあるもの。

物理的なsinkであれば、要はそのスピーカーから鳴る全ての音、ということになる。

sinkの音ではあるけれど、種別はsourceである。

下準備

まずは仮想sinkを用意する。

ここではsinkを用意しているけれど、これは実際にsinkとして使うためではなく、monitorを配信のsourceにするためのものである。

pacmd load-module module-null-sink sink_name=mixmic

PulseAudioのmodule-null-sinkを使用している。 sink_nameは識別名なので好きにつけて良いが、わかりやすいものにしたほうが良い。

さらに、名前を合わせておこう。

pacmd update-sink-proplist mixmic device.description=mixmic

PulseAudio Volume Controlに反映されるが、混乱を避けるため同じ名前にしておいたほうが良い。 本当は丁寧な名前をつけたいところだが、descriptionにホワイトスペースを含んだ名前は指定できない。

これで実用上は問題ないが、これだとこの仮想sinkのmonitorは「Monitor of Null出力」となり、非常にわかりづらい。 特に、複数の仮想sinkを使う場合区別できなくなってしまう。 そこで、monitorのdescriptionも変更しておく。

pacmd update-source-proplist mixmic.monitor device.description=Monitor_of_mixmic

さらに、合成sinkを用意する。 これは、合成sinkに音を流したとき、複数のsinkから音が鳴るようになる。

これを用意するのは「返し」のためである。 もしも、「返し」が必要ない、つまり配信している音を一切確認する必要がないのであれば、この合成sinkは必要ない。

まずは

pacmd list-sinks | grep -F "name: "

として、sinkを確認する。 <...>の形式になっている、三角カッコの中の値で出力に使用したいsinkの名前を確認する。

% pacmd list-sinks | grep name:
    name: <alsa_output.usb-Native_Instruments_Audio_4_DJ_SN-vwr0bed2-00.analog-stereo-a>
    name: <alsa_output.usb-Native_Instruments_Audio_4_DJ_SN-vwr0bed2-00.analog-stereo-b-output>
    name: <alsa_output.pci-0000_00_1f.3.analog-stereo>
    name: <alsa_output.pci-0000_3b_00.1.hdmi-stereo-extra5>
    name: <mixmic>

そして、合成sinkに、先程の仮想sinkと共に列挙する。

pacmd load-module module-combine-sink sink_name=combined slaves=alsa_output.pci-0000_00_1f.3.analog-stereo,mixmic

そしたらPulseAudio Volume Control (コマンド名 pavucontrol)を起動しよう。

混ぜて出力する

返しのない=スピーカーから再生する必要がない、つまりあくまでマイク入力として扱えば良い音源は仮想sink(先程の例ではmixmic)に出力する。

一方、返しがある、スピーカーから鳴っている音を確認したい音源は合成sinkに出力する。

これらは音が鳴っている状態でPulseAudio Volume Controlの「再生」タブからsinkを選べば良い。

だが、これだけではマイクの音を含むことができない。 module-loopbackを使えばマイクの入力をダイレクトにsinkに流すことができる。

まずはpacmd list-sources | grep -F name: | grep -F -v .monitorとしてリダイレクトしたいマイク入力を確認しよう。

% pacmd list-sources | grep -F name: | grep -v -F .monitor
    name: <alsa_input.usb-Native_Instruments_Audio_4_DJ_SN-vwr0bed2-00.analog-stereo-a>
    name: <alsa_input.usb-Native_Instruments_Audio_4_DJ_SN-vwr0bed2-00.analog-stereo-b-input>
    name: <alsa_input.pci-0000_00_1f.3.analog-stereo>
    name: <alsa_input.usb-046d_0825_D457DB60-02.mono-fallback>

目的のマイクをloopbackのsourceに記載する。そして、sinkに流し込みたいsinkを指定する。 マイクは返しはいらないだろうから、仮想sinkになるだろう。

pacmd load-module module-loopback source=alsa_input.usb-046d_0825_D457DB60-02.analog-mono sink=mixmic

マイクを選択する

これは簡単な話で、PulseAudio Volume Controlで「録音」タブから配信アプリケーションのsourceを仮想sinkのmonitorにすれば良い。

合成sinkのmonitorではないので注意すること。

ボリューム調整

アプリケーションから流れる音量

アプリケーション内部の音量調整を利用するか、 もしくは「再生」タブからボリュームをコントロールする。

マイクの音量

「入力装置」タブからマイクデバイスの音量。

配信への入力全体の音量

「入力装置」から仮想sinkのmonitorの音量。

返しの音量

「出力装置」から実際に出力しているスピーカーデバイスの音量

音が割れる

個々では割れるほどの入力がなくても、混ぜると割れてしまうこともある。

「入力装置」でそれぞれの入力値を下げる。 また、再生から合成sinkや仮想sinkに流している場合は、「再生」からそのアプリケーションの音量を下げる。

全体の入力が過大にならないようにした上で、全体のバランスをとったら、「入力装置」の仮想sinkのmonitorの音量で調整する。

補足事項

combine-source

combine-sinkは複数のsinkへ出力を分配するものであるが、逆に複数のsourceを集約するcombine-sourceは残念ながらない。

音声出力をsourceにするための方法としてmodule-pipe-sourceというのがある。これは

pacmd load-module module-pipe-source file=/tmp/pipe-source.fifo

のようにしてfifoを使って読み書きできる。これに対して

ffmpeg -re -i source.mp4 -vn -f s16le -ac 2 -ar 48000 /tmp/pipe-source.fifo

のようにして音を入力することができ、

ffmpeg -f alsa -i pulse recording.flac

のように拾うことができる。 この仕組みを使うことでカスタムsourceを作ることができるが、combine-sourceはないのでミックスすることができない。

compressor

音を割れないようにするためにコンプレッサーを使いたい、と思うかもしれない。

コンプレッサーそのものは存在し、例えばLADPSAのswh-pluginsを使ったコンプレッションも可能だが、私の意見としてはあまり実用的ではない。 SC4を使ったのだが、コンプレッサーとしてのまろやかさが足りず、リミッター的な使い方をするにしてもちょっときつい。

使い方としては

pacmd load-module module-ladspa-sink sink_name=compressor master=mixmic plugin=sc4_1882 label=sc4 control=1,1.5,401,-30,20,10,12

といった感じ。controlの値など詳しくはこちら

ここでの難しさはもうひとつ、返しの有無でnull sinkとcombine sinkを使いわけているというのがある。 割れないように圧縮するのであれば、両方にコンプレッサーを挿す必要があるのだ。 そうしないと、combine sink→null sinkで、combine sinkで既に割れる状況には対応できない。

また、利用するのであれば現実的にゲイナーとしては使えないので、そうした目的であればマイク入力を大きめに設定するなどする必要がある。

映像編

概要

音声では強力なPulseAudioをミキサー、パッチとして活用したが、映像に関してはそういうミキサーがあるわけではない。

映像を柔軟に扱う上で活躍するのが

  • v4l2ドライバ
  • ffmpeg
  • mpv

である。

v4l2はLinuxに普通は含まれている(組み込まれてビルドされている)。 ウェブカムなどを扱う上で必須だ。

ffmpegは映像を扱う非常に便利なソフトウェアだ。 そして、mpvはメディアプレイヤーだが、とんでもなく強力な機能を持つ。 もちろん、ウェブカムの映像の再生なども可能だ。

これらを駆使して問題を解決していく。

スクリーンキャスト

画面共有機能がない場合などにおいて画面を映したい場合、v4l2loopbackを利用する。

Arch Linuxの場合、次のようにしてインストールする。 (AURパッケージなので、Trizenを使っている)

trizen -S v4l2-loopback-dkms

なお、 カーネルヘッダーが必要なので、事前にインストールしておくこと

準備できたらモジュールをロードする。

sudo modprobe v4l2loopback exclusive_caps=1

これで、/dev/video*に新しく仮想カメラが追加されているはずだ。

% v4l2-ctl --list-devices
Dummy video device (0x0000) (platform:v4l2loopback-000):
    /dev/video0

ではffmpegを使ってこのカメラに流し込もう。

ffmpeg -re -f x11grab -video_size 1920x1080 -i $DISPLAY -pix_fmt yuv420p -f v4l2 /dev/video0

ビデオサイズは実際に流したい画面のサイズにする。 マルチディスプレイの場合などで特定のディスプレイのみ出力したい場合はオフセットを加える。

ffmpeg -re -f x11grab -video_size 1920x1080 -i $DISPLAY+0,1080 -pix_fmt yuv420p -f v4l2 /dev/video0

これは、ディスプレイの並びに影響されるという点にくれぐれも注意してほしい。 私は中央に4kディスプレイで左右にFHDディスプレイなのだが、左側のディスプレイは4kディスプレイと下端を合わせている。 そうすると、レイアウト上の上端が縦の0になるため、左のFHDディスプレイの左上は0,1080になるのである。

これで、カメラからデスクトップを出力することができるようになった。

ピクチャインピクチャ (ワイプ)

デスクトップを映すカメラができたので、切り替えながら使えば良いと思うが、「カメラを切り替える機能がなく、最初に選択したもので固定である」という場合、あるいはそれと関係なくウェブカメラとデスクトップを合成したい場合などはどうしたらいいだろうか。

これは、ffmpegの-filter_complex機能を使うのだが、話としては相当複雑である。 結論を示すと、

ffmpeg -re -f x11grab -framerate 24 -probesize 64M -video_size 1920x1080 -i $DISPLAY -pix_fmt yuv420p -f v4l2 -i /dev/video1 -pix_fmt yuv420p -filter_complex "[1:v] scale=360:-1,setpts=PTS-(10/TB) [1v]; [0:v][1v] overlay=x=25:y=25[outv]" -map "[outv]" -f v4l2 /dev/video0

みたいにやるのだが、問題として、重すぎてきつい。ソースがスクリーンキャストでなければそんなに問題はないのだけど、スクリーンキャストとwebcamの組み合わせだと厳しすぎるようだ。

さらなる問題として、アプリケーションによってはひとつでもカメラが利用中の状態ではカメラが使えないものとみなす、ということがあるようだ。 この場合、既にウェブカムを(合成のために)使ってしまっているので使えなくなってしまう。

そこで発想の転換でひと工夫しよう。複数のソースから録画しようとするからいけないのだ。ひとつのソースにしてしまえばいい。

というわけで、まずウェブカムを画面に出そう。

mpv --ontop --no-osc --no-border av://v4l2:/dev/video1

これで/dev/video1のウェブカムがmpvによって再生される。ラグも結構少ないし、フレームレートも出る。

移動とリサイズは、ウィンドウマネージャにもよるが、Alt+左ドラッグで移動できるし(GNOMEはSuper+左ドラッグ)、リサイズはAlt+右ドラッグでいける。

さて、これで画面上にウェブカムの映像が出ているので、 キャストするスクリーン上にmpvのウィンドウを配置しよう。 あとは、mpvのウィンドウごとキャストしてしまえばいい。

ffmpeg -re -f x11grab -video_size 1920x1080 -i $DISPLAY -pix_fmt yuv420p -f v4l2 /dev/video0

これで全く問題ない(もちろん、ウェブカムのほうが若干ラグがある感じになるが)。

補足

ゲーム機によるゲーム映像など、ビデオキャプチャカードなどを使って外部の入力を受け取る場合も、Linux上ではv4l2ドライバを使うことになるため、ウェブカムと同じ扱いが可能。

例えばキャプチャした映像にワイプを載せたいというような場合も、mpvでキャプチャ映像を再生、さらにmpvでウェブカムのワイプをトップ固定にして、その画面をキャストするという方法が使える。

これはキャプチャ映像ではなく手元のビデオだという場合も、同様にmpvでビデオを再生するだけの話で簡単だ。

なお、Nvidiaビデオカードでnvidiaドライバを使っている場合、ウィンドウが部分的に消えてデスクトップが見えてしまうという画面欠けが発生しやすいので注意してほしい。