こんにちはjunです。前回の記事から2年も経ってしまいましたが、サイクリング欲が再度湧いてきたのでこの内容を完成させるように頑張ります。ではとりあえず前回はgoproからdockerのRTMPサーバに送信してローカルマシン上で見れるかを確かめました。最終的には以下のような構成を開発します。(その1のものと変わっています)
今回の記事は
の3点を解説したいと思います。
これらの内容をまずローカルで実装するためにDockerを用いて構築します。使用しているOSはmacOs Ventura 13.4.1でdockerは4.17.0です。また前回から2年たっているのでdockerfileなどを修正します。
この記事でのゴールはこんな感じです。(字幕ON推奨)
とりあえず完成した必要なファイルと構成を載せます。
.
├── Dockerfile
├── conf
│ ├── 000-default.conf
│ ├── default.conf
│ └── nginx.conf
├── debug
│ └── index.html
├── docker-compose.yml
└── src
└── public
└── test.html
Dockerfileは管理用コンテナの構成に変更。あとでwebフレームワークのlumenを入れるためにcomposerを入れています。(この回では入れません)。そしてffmpegを入れておきます。
FROM php:apache
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN curl -O https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
tar -xJf ffmpeg-release-amd64-static.tar.xz && \
mv ffmpeg-*/ffmpeg /usr/local/bin/ && \
mv ffmpeg-*/ffprobe /usr/local/bin/ && \
rm -rf ffmpeg-*
rtmptarget
がテスト配信先コンテナrtmp
がGorpoなどからの受信用コンテナphp
が管理用web uiや配信媒体へ送信する処理を行うコンテナversion: '2'
services:
rtmptarget:
image: tiangolo/nginx-rtmp
ports:
- "1930:1935"
- "1931:80"
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./conf/default.conf:/etc/nginx/conf.d/default.conf
- ./debug:/usr/share/nginx/
rtmp:
image: tiangolo/nginx-rtmp
ports:
- "1935:1935"
volumes:
- shared_data:/usr/share/nginx/hls
- ./conf/nginx.conf:/etc/nginx/nginx.conf
php:
build: .
expose:
- 1935
ports:
- "8080:80"
volumes:
- shared_data:/var/www/html/hls
- ./src:/var/www/html
- ./conf/.htpasswd:/var/www/.htpasswd
- ./conf/000-default.conf:/etc/apache2/sites-enabled/000-default.conf
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
shared_data:
主にRTMPサーバ用の内容です。
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
rtmp_auto_push on;
rtmp {
server {
listen 1935;
listen [::]:1935 ipv6only=on;
access_log /var/log/rtmp_access.log;
chunk_size 4096;
timeout 10s;
application live {
live on;
# HLSの記述欄
hls on;
# ここに映像ファイルが配置される
hls_path /usr/share/nginx/hls;
hls_fragment 10s;
hls_playlist_length 30s;
}
}
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
主にRTMPサーバ用の内容です。
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
管理用コンテナに使用します。ドキュメントルートが/var/www/html/public
なのは後でlumenを入れるためです。
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
#ServerName www.example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
とりあえず確認したい時に使用します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>MediaElement</title>
<!-- MediaElement style -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/mediaelement/4.2.9/mediaelementplayer.css" />
</head>
<body>
<!-- MediaElement -->
<script src="//cdnjs.cloudflare.com/ajax/libs/mediaelement/4.2.9/mediaelement-and-player.js"></script>
<video id="player" width="640" height="360">
</body>
<script type="text/javascript">
var player = new MediaElementPlayer('player', {
success: function(mediaElement, originalNode) {
console.log("Player initialised");
}
});
player.setSrc("/hls/.m3u8");
</script>
</html>
では上記のファイルを用いて解説します。
まずは受信用コンテナから得られたGoproの映像を管理用コンテナで使用できるようにします。受信用コンテナにPHPなどを入れようとしましたが結構面倒だったので、別のコンテナとして独立させました。しかしそのままでは受信した映像データ(HLS)を共有できないため、受信用コンテナで生成されるHLSファイルを共有ボリュームで共有します。
その設定のために
version: '2'
services:
#...
rtmp:
#...
volumes:
- shared_data:/usr/share/nginx/hls
#...
php:
#...
volumes:
- shared_data:/var/www/html/hls
#...
volumes:
shared_data:
volumes
でボリュームを定義してそのボリューム名を各コンテナに配置するパスを指定します。するとrtmp
(受信用コンテナ)では/usr/share/nginx/hls
に*.ts
ファイルや.m3u8
ファイルが作成されます。そしてphp
(管理用コンテナ)では/var/www/html/hls
に同じファイル群が作成されます。
管理用コンテナでgoproから受信したデータをffmpegで色々したり、読み取ったり、配信できるようになりました。
その1の記事または後述するOBSの方法を参考にGopro・OBSからrtmp://YOUR_PC_IP/live
にRTMP送信をします。
# <YOUR_PHP_CONTAINER_NAME>はあなたの環境の管理用コンテナ名に書き換えてください。
docker exec -it <YOUR_PHP_CONTAINER_NAME> /bin/bash
ボリュームのため、/var/www/html/hls
というディレクトリがあると思います。そしてGoproでの収録を開始するとディレクトリ内にファイルが配置されます。
そして/var/www/html/public/test.html
があるのでhttp://localhost:8080
でweb playerを用いて確認できるはずです。
次にffmpegを用いて管理用コンテナからyoutube liveに流してみます。ffmpegはlinux上で動画、音声の変換・記録・再生・配信を行うことができるOSSです。今はコマンド上で配信をするだけですが、最終的にはffmpegを利用して管理画面から配信内容を制御する予定です。現時点で自分もまだ詳細なプロパティーや映像系の知識に乏しいので、調べた際のコマンドのコピペやchat gptで調査した内容になります。
本記事では簡単に述べておきます。すでにGoogle、Youtubeアカウントがありライブストリーミングが有効になっている前提とします。
プライバシーを非公開にしておくことをおすすめします。
Goproからの映像をローカルで確認できたらとりあえず以下のコマンドを管理用コンテナ内から打ってみてください。
ffmpeg -re -i "/var/www/html/hls/.m3u8" -c:a aac -ar 44100 -ab 128k -ac 2 -strict -2 -flags +global_header -bsf:a aac_adtstoasc -f flv "rtmp://64.233.189.134/live2/YOUR_LIVE_KEY"
YOUR_LIVE_KEY
はyoutuebのライブストリームキーを使用してください。ffmpegが起動してyoutubeにデータが送信されます。20秒ぐらいするとGoproからの映像の配信が開始されるはずです。
本来ライブストリームURLにはa.rtmp.youtube.com
というドメインを使用すべきですが、私のdockerで管理用コンテナから実行するとffmpegが以下のようなDNSエラーが発生します。
[tcp @ 0x696d2c0] Failed to resolve hostname a.rtmp.youtube.com: System error
[rtmp @ 0x696d480] Cannot open connection tcp://a.rtmp.youtube.com:1935?tcp_nodelay=0
[out#0/flv @ 0x6890880] Error opening output rtmp://a.rtmp.youtube.com/live2/efth-usbu-ye2g-f0sk-a7ta: Input/output error
Error opening output file rtmp://a.rtmp.youtube.com/live2/efth-usbu-ye2g-f0sk-a7ta.
Error opening output files: Input/output error
ホストマシン上でffmpegを使用すると問題なく配信できるますが、docker内から行うとなぜかa.rtmp.youtube.com
のホストが見つからないというエラーが発生します。そのためホストマシンでnslookup
を使用してa.rtmp.youtube.com
のIPを調べて、直接IPを指定したら問題なく動作しました。
ここはdockerの問題かつまだ調査中です。Googleなどはこの辺のIPをころころ変えてきそうなので、最終的にはきちんとドメインを利用できるようにしますがとりあえず今はIPで指定します。
ひととおり受信→中継→配信を行えるdockerコンテナが構築できました。しかし毎回goproを起動したり、youtube liveを開始するのは面倒ですし、複数人で開発する時に機材がなかったり送信先が重複してします。そのため開発用にデバッグできるように送信元、配信先をローカルで実現できるようにします。
この記事では送信元としてGoproを使用してホストマシンの受信用コンテナにRTMPを送信しています。ですがこれはOBSなどの配信ソフトを利用することで簡単に代替できます。
OBSはフリーのライブ配信や録画が行えます。PC上のウィンドウキャプチャをしたり、機材をそろえればnintedo switchなどのゲーム機の映像を配信することができます。とりあえずOBSをインストールして、webカメラなりウィンドウキャプチャを映像・音声ソースとして利用します。OBSのインストールはこちらから
OBSをインストールしたらアプリを開きます。以下のような似た画面になっているはずです。(すでに設定がいくつかあるのは気にしないでください)
「シーン」に何もなければ「+」をクリックしてシーンを追加します。そして映像・音声ソースも追加していきます。「ソース」のとこの「+」をクリックして「映像キャプチャデバイス」を選択します。
ソースのラベルを適当に入れておきます。
ソースの選択画面に移動するので「デバイス」から「FaceTime HDカメラ」などを選択しておきます。
これだとまだ映像しか拾わないので次は「ソース」のとこの「+」をクリックして「音声入力キャプチャ」を選択します。同じようにソースのラベルを適当に入れておきます。ソースの選択画面に移動するので「デバイス」から「既定」などを選択しておきます。どれでもとりあえず音声が取得できればOKです。
こんな感じで映像となんか奇声を発して「音声入力キャプチャ」が反応していればOKです。
次に配信の設定をします。OBS→「設定」を開いて配信のタブをクリックします。 サービスから「カスタム」を選択します。
そして「サーバー」に受信用コンテナのRTMPパスをいれます。このとき127.0.0.1
というローカルループバックアドレスにしておきます。localhostにするとうまくいかない時があります。自分はlocalhost:1935
に受信用コンテナのエンドポイントが控えていますが、違うポートの時はそのポートを指定してください。「適用」をクリックして「OK」をクリックします。
最初の画面にもどったら「配信開始」をクリックすると配信されます。下の方にこのような表示がされていたり、確認用playerで確認したり、HLSファイルが生成されていれば成功しています。
終了する時は「配信終了」をクリックします。
最後に配信先のRTMPサーバをローカルに用意します。一番簡単な方法としてはもう1つRTMPサーバを立ててしまうことです。気をつける点としては管理用コンテナから配信先のホストを指定できるようにします。
version: '2'
services:
rtmptarget:
image: tiangolo/nginx-rtmp
ports:
- "1930:1935"
- "1931:80"
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./conf/default.conf:/etc/nginx/conf.d/default.conf
- ./debug:/usr/share/nginx/
# ...
php:
# ...
extra_hosts:
- "host.docker.internal:host-gateway"
# ...
volumes:
shared_data:
管理用コンテナのプロパティにextra_hosts
にhost.docker.internal:host-gateway
を加えることで、コンテナから利用できるホストマシンの特殊なIPアドレスがコンテナのhostsに追記されます。
cat /etc/hosts
を管理用サーバで実行すると追加したhostのIPがあるはずです。
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.65.2 host.docker.internal ←これ
172.23.0.3 8227ce3421ec
次に配信先のRTMPコンテナに向けてffmpegを用いて配信します。ローカルのホスト、ポート、パスに合わせればいいだけですが以下のコマンドを使用します。(youtubeの時とすこし違います!!)
ffmpeg -re -i "/var/www/html/hls/.m3u8" -c:v libx264 -preset ultrafast -c:a aac -ar 44100 -ab 128k -ac 2 -f flv "rtmp://192.168.65.2:1930/live"
このコマンドを打って配信先コンテナの確認用htmlのプレイヤーhttp://localhost:1931/index.html
から映像を確認できれば受信できています。(20秒ほど待つといいです)
すこしオプションの内容がyoutubeのときと違っている理由として、単純にyoutubeのIPから変えればいいと思っていたらなぜかweb playerで映像が見れず音声しか流れなかったのが原因です。色々探ってこのコマンドにしたら問題なくいけました。もしかするとIPだけ変更してyoutubeもこのコマンドでやったらほうがいいかもしれません。検証中です。
記事は以上となります。一通り管理用の中継コンテナを用いてgoproの映像をffmpegでyoutubeに送信したり、ローカルの開発環境を整えました。次回はffmpegを用いて任意のシーンに切り替えたり、goproからの映像が途絶えた時などの例外処理を加えていくwebシステム部分をphpを使用して実装しようと思います。
今回参考にした資料です。
コメント
コメント読み込み中..