簡単ストリーミング中継サーバの作り方
TL;DR
- OBS -> nginx -> web brouwser といった流れでストリーミングする
- dockerでnginxは立てる。
- 細かいところは省きます。
docker
nginxはRTMPが含まれているこちらを使っている。
このdocker自体はffmpegによってマルチバンドのストリーミングを実現するような高機能な内容になってる。ひとまずこれのReadme読んで立ち上げてみるとOBSからの配信をウェブで見ることができます。だがしかし。
ffmpegがとにかく落ちまくる。安定させるために色々と調べて手を尽くしたけども一向に安定する気配を見せなかった。で、いろいろ調べているうちに「nginx自体で受け取ったストリーミングデータをhlsに変換することができる」ということがわかったのでそちらに舵を切った。今のネット環境超早いので。
nginx.conf
dockerに設定されている当該のRTMPのコンフィグはこちら。
# RTMP configuration
rtmp {
server {
listen 1935; # Listen on standard RTMP port
chunk_size 4000;
# ping 30s;
# notify_method get;
# This application is to accept incoming stream
application live {
live on; # Allows live input
# for each received stream, transcode for adaptive streaming
# This single ffmpeg command takes the input and transforms
# the source into 4 different streams with different bitrates
# and qualities.
# these settings respect the aspect ratio.
exec_push /usr/local/bin/ffmpeg -i rtmp://localhost:1935/$app/$name -async 1 -vsync -1
-c:v libx264 -c:a aac -b:v 256k -b:a 64k -vf "scale=480:trunc(ow/a/2)*2" -tune zerolatency -preset superfast -crf 30 -f flv rtmp://localhost:1935/show/$name_low
-c:v libx264 -c:a aac -b:v 768k -b:a 128k -vf "scale=720:trunc(ow/a/2)*2" -tune zerolatency -preset superfast -crf 30 -f flv rtmp://localhost:1935/show/$name_mid
-c:v libx264 -c:a aac -b:v 1024k -b:a 128k -vf "scale=960:trunc(ow/a/2)*2" -tune zerolatency -preset superfast -crf 30 -f flv rtmp://localhost:1935/show/$name_high
-c:v libx264 -c:a aac -b:v 1920k -b:a 128k -vf "scale=1280:trunc(ow/a/2)*2" -tune zerolatency -preset superfast -crf 30 -f flv rtmp://localhost:1935/show/$name_hd720
-c copy -f flv rtmp://localhost:1935/show/$name_src > /tmp/ffmpeg.log 2>&1;
drop_idle_publisher 10s;
}
# This is the HLS application
application show {
live on; # Allows live input from above application
deny play all; # disable consuming the stream from nginx as rtmp
hls on; # Enable HTTP Live Streaming
hls_fragment 3;
hls_playlist_length 20;
hls_path /mnt/hls/; # hls fragments path
# Instruct clients to adjust resolution according to bandwidth
hls_variant _src BANDWIDTH=4096000; # Source bitrate, source resolution
hls_variant _hd720 BANDWIDTH=2048000; # High bitrate, HD 720p resolution
hls_variant _high BANDWIDTH=1152000; # High bitrate, higher-than-SD resolution
hls_variant _mid BANDWIDTH=448000; # Medium bitrate, SD resolution
hls_variant _low BANDWIDTH=288000; # Low bitrate, sub-SD resolution
# MPEG-DASH
dash on;
dash_path /mnt/dash/; # dash fragments path
dash_fragment 3;
dash_playlist_length 20;
}
}
}
ffmpeg で色々やってるのがわかる。
で、これをこう変更した。
# RTMP configuration
rtmp {
server {
listen 1935; # Listen on standard RTMP port
chunk_size 4000;
application live {
live on;
drop_idle_publisher 10s;
hls on;
hls_fragment 3;
hls_playlist_length 20;
hls_path /mnt/hls/;
hls_variant _mid BANDWIDTH=448000;
dash on;
dash_path /mnt/dash/;
dash_fragment 3;
dash_playlist_length 20;
}
}
}
これだけ。
http側のコンフィグは基本的に変更する必要ないのでそのままで。
TIPS
dockerの起動コマンド
nginxのコンフィグファイルとhtmlのドキュメントルートになるディレクトリの変更を行いたかったので、以下のようにしている。
docker run --name <起動後のプロセス名> -d -p 1935:1935 -p 80:8080 -v /home/<user_dir>/nginx_conf/nginx.conf:/etc/nginx/nginx.conf -v /home/<user_dir>/html:/usr/local/nginx/html --cpus=3 alqutami/rtmp-hls
-v
で、コンフィグファイルやディレクトリをマウントして書き換えている。
結果
OBSから配信してるストリーミングの内容そのままが中継されるようになった。なので、OBS側の配信設定がそのままストリーミングに反映されてしまうので、解像度などは良きに計らうと良いと思う。ffmpegの時にはあまりに頻繁に落ちるので、スクリプトを作ってプロセスの監視を行い、都度dockerのrestartをする、という涙ぐましいその場対応してたのだが、いまのところ30時間以上連続してなにも問題なく配信が続いてるのでこれで良いかな、、、という気持ち。
やってみてあらためて実感したのだが、YouTubeとかマジでとんでもないサーバリソースとネットワーク帯域使ってんだなぁ、と。
おまけ。
「なにがなんでもffmpeg使いたいんだい!」っていう人のために監視につかっていたスクリプトをここに供養しておく。自分の環境に合わせてcrontabに仕掛けてください。ffmpegのプロセスが閾値を下回るとSlackのweb hookで通知してくれます。 ./monitor_ffmpeg.sh test
で、テストモード(CPU監視せずに適当に閾値を下回ったことにしてSlackに通知を投げてくれる)。
#!/bin/bash
# 監視するプロセス名
PROCESS_NAME="ffmpeg"
# 監視する回数
MAX_MONITOR_COUNT=12
THRESHOLD_COUNT=3
THRESHOLD_USAGE=20
# コンテナ名
CONTAINER_NAME="<docker-container-name>"
# Slack Webhook URL
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/<slackのwebhook>"
# Slackへの通知関数
send_slack_notification() {
message="$1"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" $SLACK_WEBHOOK_URL
}
# Nginxのプロセス名
NGINX_PROCESS_NAME="nginx"
# Nginxが起動しているかチェック
is_nginx_running() {
pgrep -f "$NGINX_PROCESS_NAME" > /dev/null
}
# プロセスのCPU利用率がしきい値を下回っているかチェック
is_usage_below_threshold() {
CPU_USAGE=$1
if (( $(echo "$CPU_USAGE < $THRESHOLD_USAGE" | bc -l) )); then
return 1
else
return 0
fi
}
# テストモードでの監視結果を生成
generate_test_results() {
local results=()
local threshold_usage_count=0
local non_threshold_usage_count=0
while [ ${#results[@]} -lt $MAX_MONITOR_COUNT ]; do
if [ $threshold_usage_count -lt $THRESHOLD_COUNT ]; then
results+=("true")
threshold_usage_count=$((threshold_usage_count + 1))
else
results+=("false")
non_threshold_usage_count=$((non_threshold_usage_count + 1))
fi
done
shuf <<<"${results[*]}"
}
# Nginxが起動している場合のみ監視を行う
if is_nginx_running; then
if [ "$1" = "test" ]; then
echo "-------------------------"
echo "TEST MODE: Nginx monitoring and restart script is running in test mode. No actual restart will be performed."
echo "-------------------------"
test_results=($(generate_test_results))
echo "Test Results: ${test_results[@]}"
below_threshold_count=0
monitor_count=0
index=0
while true; do
test_result=${test_results[$index]}
if [ "$test_result" = "true" ]; then
below_threshold_count=$((below_threshold_count + 1))
fi
monitor_count=$((monitor_count + 1))
index=$((index + 1))
if [ $monitor_count -eq $MAX_MONITOR_COUNT ]; then
if [ $below_threshold_count -ge $THRESHOLD_COUNT ]; then
echo "TEST MODE: In actual mode, nginx container would have been restarted..."
notification_message="[TEST MODE] In actual mode, Nginx container would have been restarted due to low CPU usage."
send_slack_notification "$notification_message"
break
fi
monitor_count=0
below_threshold_count=0
fi
sleep 5
done
exit 0
else
echo "Running in normal mode."
below_threshold_count=0
monitor_count=0
while true; do
CPU_USAGE=$(ps aux | grep -v grep | grep $PROCESS_NAME | awk '{usage+=$3} END {print usage}')
echo "Checking process: $PROCESS_NAME with CPU usage: $CPU_USAGE"
if ! is_usage_below_threshold $CPU_USAGE; then
below_threshold_count=$((below_threshold_count + 1))
echo "CPU usage is below threshold. Incremented below_threshold_count to $below_threshold_count."
fi
monitor_count=$((monitor_count + 1))
if [ $below_threshold_count -ge $THRESHOLD_COUNT ]; then
echo "Below threshold count has reached the limit. Restarting the container and sending notification..."
docker restart $CONTAINER_NAME
notification_message="Nginx container has been restarted due to low CPU usage."
send_slack_notification "$notification_message"
exit 0
fi
if [ $monitor_count -ge $MAX_MONITOR_COUNT ]; then
exit 0
fi
sleep 5
done
fi
fi
実際のプロセスでスクリプトをテストしたければ下記のスクリプト用意して、上記のスクリプトの監視対象を設定するところPROCESS_NAME="<process-name>"
にプロセス名を設定して、あとはプロセスを実行したりkillしたりしてやれば状態監視ができる(はず)。
#!/bin/bash
while true
do
sleep 1
done
Discussion