Raspberry Pi 3とffmpegでYouTubeへライブ配信をする

Live Streaming to YouTube with Raspberry Pi and ffmpeg

Raspberry Pi 3にUSBカメラをつないで映像をffmpegでハードウエアエンコードしてからYouTubeへライブ配信してみます。



ffmpegでUSBカメラの映像をYouTubeへライブ配信する

以前の記事でFFmpegをビルドしました。
Rapberry Pi 3 のハードウエアエンコーダh264_omxが使えるFFmpegをビルドする

そしてLAN内でストリーミングのテストをしました。
Raspberry Pi 3とFFmpegでUSBカメラの映像と音声をストリーミングする

Raspberry Piに3Gモデムも付けました。
USB 3G モデム L-02CをRaspberry Pi 3で使えるようにしてMVNOのSIMカードで通信する

これで外にRaspberry Piを持ち出して自由に通信できるようになりました。

こうなったら次はYouTubeへライブ配信をしてみましょう。
YouTube側でのライブ配信の設定は他のサイト様にお任せです。検索して配信準備をしてください。
ここではRaspberry Pi 3側での設定を説明します。

Raspberry Pi側で必要なのはYouTubeの配信用URLとあなたのストリームキーです。YouTubeでクリエイターツールを開き"ライブ ストリーミング"を開くとエンコーダの設定があり、そこにサーバーURLとストリーム名/キーがあります。

今回はUSBカメラの映像と音声をYouTubeで配信してみます。
使うUSBカメラはこちらです。

販売終了 新製品

iBuffaleの広角レンズが特徴のUSBカメラです。

他のUSBカメラでも基本的には動かせます。多くのカメラでも出力できると思いますが、異なるとするとV4L2で指定するカメラ映像の入力フォーマットになるでしょう。
USBカメラが出力できるフォーマットは次のコマンドで調べられます。
v4l2-ctl -d /dev/video0 --list-formats-ext
カメラごとに出力できるフォーマット、解像度とフレームレートの組み合わせがあります。

USBカメラの映像と音声を配信する

USBカメラの音声とマイクの音声を配信してみましょう。
次のコマンドを入れると配信が開始されます。
ffmpeg \
-f alsa -thread_queue_size 8192 -i hw:1 \
-f v4l2 -thread_queue_size 8192 -input_format yuyv422 -video_size 1280x720 -framerate 8 -i /dev/video0 \
-c:v h264_omx -b:v 768k -bufsize 768k -vsync 1 -g 16  \
-c:a aac -b:a 128k -ar 44100 \
-f flv rtmp://YOUR_SERVER_URL/YOUR_KEY
(Windowsパソコンで上のコマンドをコピー&ペーストすると改行コードが異なる影響で正しく動かない場合があります。Raspberry Piのブラウザで開いてコピーすると良いでしょう。)
一番最後の行をあなたのサーバURLとキーに入れ替えてください。

yuyv422形式では非圧縮の画像データをUSB2.0で送るとなるとこのくらいの解像度とフレームレートが限界のようです。
mjpeg形式にするとカメラ側は30fpsで出力できますがffmpegのエンコード処理は12fps程度が限界のようです。Raspberry Pi 3でのエンコードは解像度1280x720で8fpsが限界のようです。640x480ならば30fpsでエンコード可能でした。

ビットレートが低くYouTubeにもっと上げろと言われるかもしれません。ビットレートを上げても画質が大して向上しない感じです。3Gモデムを使って配信する事も考えてこの値にしています。

USBカメラの映像と音楽ファイルを配信する

マイクの音声を配信したくない時もあるでしょう。無音では面白くないのでBGMを流すようにしてみましょう。
YouTubeのオーディオライブラリから帰属表示の必要がないMP3ファイルをダウンロードして使ってみます。何曲かダウンロードしてRaspberry Piに保存してください。ディレクトリはホームディレクトリ下のMusicフォルダ""/Music"を例に説明します。

ffmpegでMP3を配信するにはffmpegコマンドでmp3ファイルを入力に指定するだけです。ですが単純に置き換えるとmp3ファイルの演奏時間が終わるとその後無音状態になってしまうでしょう。
最近のffmpegでは同じMP3ファイルを繰り返し再生するオプション"-stream_loop"が指定できるようになりました。しかし、このオプションはまだ正しく動かないようです。
それにこの方法ではひとつのファイルしか指定できません。2つ以上の曲を繰り返し流すといった事はできません。曲をつないで1つのファイルにしておく必要があります。

concatでBGMの再生リストを読み込む

BGMとして流す曲を自由に設定したい時にはconcat demuxerを使うと便利です。再生したいファイルをリストしたtxtファイルを作り指定すると、曲を順に読み込んでくれます。全ての音楽ファイルは同じフォーマット、サンプリングレート、チャンネル数である必要があります。
説明はこちらにあります。
wiki: Concatenate / FFmpeg
次のように書かれたテキストファイルを作りffmpegで指定すれば良いのです。
# this is a comment
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'
プレイリストですね。

プレイリストファイルを作るのが面倒なのでシェルコマンドで作ります。
少し具体的な例をやってみます。
今回はすべてMP3ファイルとします。MP3ファイルは"~/Music"にあるとします。フォーマットが異なると今回の方法ではつなぐことができません。
MP3ファイルをすべて順番に流すには次のコマンドを入れれば良いでしょう。
ffmpeg -re -f concat -safe 0 -i <(do for file in ~/Music/*.mp3 ; do echo file "$file" ; done) ...
再生リストのファイルを作るのは面倒なのでシェルスクリプトで生成してリダイレクトしています。
"-safe 0"を指定しないと動かないでしょう。ホームディレクトリであってもフルパス指定だと動いてくれないようです。
曲の再生順を指定したければMP3のファイル名を変える必要があります。

フォルダ内のMP3を全て1回再生した後にBGMが無くなってしまいます。フォルダ内の曲を繰り返し再生したいですよね。次のようにループを追加してみました。
ffmpeg -re -f concat  -safe 0 -i <(for i in {1..10};do for file in ~/Music/*.mp3 ; do echo file "$file" ; done; done) ...
for文をネストしただけです。10回繰り返します。必要な時間になるだけ繰り返しを指定します。

ついでに曲の順番をランダムにしてみます。
ffmpeg -re -f concat -safe 0 -i <(for i in {1..10};do for file in `ls ~/Music/*.mp3|sort -R` ; do echo file "$file" ; done; done) ...

それでは配信用コマンドを紹介します。映像は先のコマンドと同じです。
ffmpeg \
-re -f concat -thread_queue_size 8192 -safe 0 -i <(for i in {1..10};do for file in `ls ~/Music/*.mp3|sort -R` ; do echo file "$file" ; done; done) \
-f v4l2 -thread_queue_size 8192 -input_format yuyv422 -video_size 1280x720 -framerate 8 -i /dev/video0 \
-c:v h264_omx -b:v 768k -bufsize 768k -vsync 1 -g 16 \
-c:a aac -b:a 128k -ar 44100 \
-threads 0 \
-f flv rtmp://YOUR_SERVER_URL/YOUR_KEY

配信と同時にファイルへ保存する

YouTubeへ配信した映像と音声はYouTubeで保存されています。ですがローカルでも保存しておいた方が安心ですよね。配信と同時にファイルへ保存してみます。
エンコードした映像と音声を配信用とファイル用へと分けて出力できればよいのです。同じ映像を2回エンコードする必要はありません。さて、どうやったら良いのでしょうか。

このページにやり方が書いてあります。
wiki: Creating multiple outputs / FFmpeg
Duplicate outputsの項を見ます。
teeというmuxerを使えば良いようです。

音声にBGMファイルを使う場合の例は次のようになります。
ffmpeg \
-re -f concat -thread_queue_size 8192 -safe 0 -i <(for i in {1..10};do for file in `ls ~/Music/*.mp3|sort -R` ; do echo file "$file" ; done; done) \
-f v4l2 -thread_queue_size 8192 -input_format yuyv422 -video_size 1280x720 -framerate 8 -i /dev/video0 \
-flags:v +global_header \
-c:v h264_omx -b:v 768k -bufsize 768k -vsync 1 -g 16 \
-c:a aac -b:a 128k -ar 44100 \
-threads 0 \
-f tee -map 1:v -map 0:a  "[f=matroska:onfail=ignore]~/`date +%Y%m%d_%H%M%S`.mkv|[f=flv]rtmp://YOUR_SERVER_URL/YOUR_KEY"
なんかコマンドがどんどん長くなります。
映像と音声は配信と同時にmatroskaファイルでも保存します。ホームディレクトリにファイル名が日時となったmkvファイルができます。保存先フォルダが無いなど保存に失敗した場合は保存せず配信だけ行います。
"-flags:v +global_header"なるオプションを追加しました。これがないと
Non-monotonous DTS in output stream 0:0 ...
と警告が出てしまうし、YouTubeではストリーム良好と表示されるのに映像が表示されなくなってしまいました。

マイクの音声とBGMをミキシングし配信とファイル保存をする

BGMもいいですがやはりマイクの音も欲しい。マイクの音とBGMをミキシングしてみます。BGMは少しボリュームを下げる事にします。

amergeフィルタを使うと音声を自由にミックスできます。自由過ぎてオプション指定が面倒です。

それでは今回の集大成はこのようになります。
ffmpeg \
-f alsa -thread_queue_size 1k -i hw:1 \
-re -f concat -thread_queue_size 128k -safe 0 -i <(for i in {1..50};do for file in `ls ~/Music/*.mp3|sort -R` ; do echo file "$file" ; done; done) \
-f v4l2 -timestamps mono2abs -thread_queue_size 1k -input_format yuyv422 -video_size 1280x720 -framerate 8 -i /dev/video0 \
-flags:v +global_header \
-filter_complex "[1:a]volume=volume=-12.0dB,aformat=sample_fmts=s16:channel_layouts=stereo[BGM], \
  [0:a]aformat=sample_fmts=s16:channel_layouts=stereo,asetpts=PTS+1.2/TB[mic], \
  [mic][BGM]amerge=inputs=2,pan=stereo|c0<c0+c2|c1<c1+c3[aout]" \
-c:v h264_omx -b:v 768k -bufsize 768k -vsync 1 -g 16 \
-c:a aac -b:a 128k -ar 44100 \
-avoid_negative_ts 1 -threads 0 \
-f tee -map 2:v -map [aout]  "[f=matroska:onfail=ignore]~/`date +%Y%m%d_%H%M%S`.mkv|[f=flv]rtmp://YOUR_SERVER_URL/YOUR_KEY"
コマンドがどんどん長くなります。

マイク音声と映像の同期がズレてしまいました。async, vsyncでは同期が取れません。マイク音声とBGMのどちらのタイムスタンプが使われているのか、amerge時にタイムスタンプがどうなるのか、良くわかりませんでした。amergeの入力指定の順番を変えるとズレ時間が変わりますし、ズレに再現性が無い感じです。

仕方なく現物合わせでタイムスタンプ値を補正する事にしました。asetptsフィルタでズレ時間をタイムスタンプに加算しています。試される方はご自分の環境で数値を変えてください。これもちゃんと補正できない時があります。何がどうなっているのやら・・・
ズレ分の映像・音声はメモリーに蓄えなくてはなりません。メモリーの少ないRaspberry Piには優しくないですね。

SDカードの空き容量に注意

配信して放っておくとffmpegが勝手に終了してしまいました。
frame=12027 fps=8.0 q=-0.0 size=  212484kB time=00:25:03.16 bitrate=1158.0kbits
frame=12032 fps=8.0 q=-0.0 size=  212484kB time=00:25:03.75 bitrate=1157.5kbits
frame=12036 fps=8.0 q=-0.0 size=  212484kB time=00:25:04.25 bitrate=1157.2kbits
Killedd=   1x
何が起きたのでしょうか?
syslogを覗いてみると何やら怪しいログがあります。
Nov 23 17:08:30 raspberrypi kernel: [ 6959.639168] Mem-Info:
...
Nov 23 17:08:30 raspberrypi kernel: [ 6959.639276] Free swap  = 0kB
Nov 23 17:08:30 raspberrypi kernel: [ 6959.639280] Total swap = 102396kB
スワップファイルが足りないようです。どうやらメモリー不足でOSがメモリを消費しているffmpegをkillしたようです。
なぜ??メモリーリーク?

あっ・・・!
Raspbian with PIXELを8GBのSDカードで動かしていました。リモートデスクトップやらffmpegやらいろいろ追加のプログラムを入れたので空き容量は1GBもありませんでした。その状態でホームディレクトリに配信したビデオファイルを作っていました。

空き容量が無くなりますね。SDカードの寿命も縮まります。

ライブ配信をファイルへ残しておくには16GB以上のSDカードを使いましょう。8GBでは足りません。

Raspberry Pi用SDカードはSAMSUNGのEVO+が良いですよ。ランダムライト性能が良くOSの動作も速くなると期待できます。

あるいはUSBメモリーを追加してそちらへビデオファイルを保存するようにしておくと良いでしょう。パソコンへビデオファイルを持っていくのも楽になり、後で編集するのに良いでしょう。

まとめ

Raspberry Pi 3のハードウエアエンコーダを使うffmpegでh.264にエンコードしYouTubeへライブ配信する事ができました。もちろん他の動画配信サイトでも同じように配信できると思います。
しかしスマホでいいじゃん・・・と思わなくもない。

配信可能な映像は 解像度1280x720、フレームレートは8fpsが限界でした。480pなら30fps出ました。USBカメラのフォーマットによらず同じ程度のエンコード速度しか出ません。フルHDでは5fpsを維持するのがやっとでした。

BGMとマイク音声をミックスするコマンドでは8fpsを維持できないかもしれません。CPUは遊んでる感じなのですがエンコード速度が落ちます。処理のパラレル化がうまくいかないようです。映像処理と音声処理を分離すると良いようです。そのコマンドは別の機会に紹介してみます。

モバイルバッテリーで配信することが可能です。USB 3G モデムと組み合わせればコンパクトな配信局が出来上がります。

Live Streaming to YouTube with Raspberry Pi and ffmpeg

上の写真ではYouTubeへ配信中の映像をタブレットに表示している様子を撮りました。ワイヤレスで配信する事ができます。
Raspberry Piとバッテリーを収納するケースを3Dプリンタで作ろうかな。カメラモジュールを使うともっとコンパクトにできそうです。カメラモジュールのレンズでは画角が狭いので買う気にならないんですよね。

3G モデムで配信時に必要な電流は全部で約1Aでした。結構食いますね。
ざっくりとリチウムイオンバッテリーの容量1500mAで1時間ほど動くでしょう。

ライブ配信した映像の例は私のチャンネルをご覧ください。


たぶんあると思います。

コメント

最近のコメント

Threaded Recent Comments will be here.