読者です 読者をやめる 読者になる 読者になる

Nginxとffmpegで映像プレイヤーの検証環境を構築(RTMP、HLS)

サーバ

背景

FlowPlayerを仕事で使うことになり、デザインの変更やFlash Failbackの検証などを行うための軽量な環境が欲しかった。

やりたいこと

  • 映像ソースを送る側と、それを配信するサーバーをローカル環境上に立ち上げたい(LIVE配信で!!)
  • 同一LANの他の端末から接続できるようにしたい
  • RTMPとHLSどちらも検証したい
  • コマンドひとつで環境を立ち上げたい!

構成

そこで以下のような構成を作ることにしました。

構成図

コンテナ1の作成(Web、配信サーバ)

要件

  • Webサーバの機能を持つこと
  • RTMPサーバの機能を持つこと
  • HLSのセグメント化もできること

設定ファイル

nginxのRTMPモジュールを使えば、nginxだけで上記の機能のすべてを賄えるとのこと。

インストール方法はこちらを参照。

これをベースにDockerfileを作成します。

FROM centos:latest
RUN yum update -y && \
    yum -y install gcc pcre-devel openssl openssl-devel wget unzip tar git make && \
    useradd --shell /sbin/nologin nginx && \
    wget http://nginx.org/download/nginx-1.10.2.tar.gz && \
    tar -zxvf nginx-1.10.2.tar.gz && \
    wget https://github.com/arut/nginx-rtmp-module/archive/master.zip && \
    unzip master && \
    cd nginx-1.10.2 && \
    ./configure \
      --user=www-data \
      --group=www-data \
      --with-http_ssl_module \
      --with-http_realip_module \
      --add-module=../nginx-rtmp-module-master && \
    make && \
    make install
COPY ./nginx.conf /usr/local/nginx/conf/nginx.conf
WORKDIR /usr/local/nginx/html
EXPOSE 80 1935
ENTRYPOINT ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;", "-c", "/usr/local/nginx/conf/nginx.conf"]

RTMPサーバの設定については、以下の記述をnginx.confに追記すればOKのようです。 参考:nginx-rtmp-module/wiki

rtmp {
    server {
        listen 1935;
        application live {
            live on;
            wait_video on;
            exec ffmpeg -i rtmp://localhost/live/$name
              -c:a libfdk_aac -b:a 32k  -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low
              -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid
              -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi;
        }
        application hls {
            live on;

            hls on;
            hls_path /usr/local/nginx/html/hls;
            hls_nested on;
            hls_fragment 5s;

            hls_variant _low BANDWIDTH=160000;
            hls_variant _mid BANDWIDTH=320000;
            hls_variant _hi  BANDWIDTH=640000;
        }
    }
}

まずrtmp://{IPアドレス}/live/{ストリーム名}にストリームを送ると、 それを受け取ったあと、

exec ffmpeg -i rtmp://localhost/live/$name
  -c:a libfdk_aac -b:a 32k  -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low
  -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid
  -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi;

上記が実行されrtmp://localhost/hls/$name_lowrtmp://localhost/hls/$name_midrtmp://localhost/hls/$name_hiの3ストリームに更に流されるという仕組みのようです。 そして、下記の部分でHLSについての設定が記述してあるので、ここでセグメント化がされるという流れみたいです。

application hls {
    live on;

    hls on;
    hls_path /usr/local/nginx/html/hls;
    hls_nested on;
    hls_fragment 5s;

    hls_variant _low BANDWIDTH=160000;
    hls_variant _mid BANDWIDTH=320000;
    hls_variant _hi  BANDWIDTH=640000;
}

しかし、自分の環境ではこのHLS化の部分がうまく動作してくれなかったので、nginxでストリームを受け取ってからHLSに流すのではなく、最初から映像ソースを送信するffmpeg側でRTMP+HLS×3の4ストリーム流すことで対応しました。 最終的に、nginx.confは以下のようになりました。

user  nginx nginx;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /usr/local/nginx/html;
            index  index.html index.htm;
            expires -1;
            sendfile off;
        }
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
            }
            root /usr/local/nginx/html;
            add_header Cache-Control no-cache;
            # To avoid issues with cross-domain HTTP requests (e.g. during development)
            add_header Access-Control-Allow-Origin *;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    include servers/*;
}
rtmp {
    server {
        listen       1935;
        application live {
            live on;
            wait_video on;
        }
        application hls {
            live on;

            hls on;
            hls_path /usr/local/nginx/html/hls;
            hls_nested on;
            hls_fragment 5s;

            hls_variant _low BANDWIDTH=160000;
            hls_variant _mid BANDWIDTH=320000;
            hls_variant _hi  BANDWIDTH=640000;
        }
    }
}

参考

コンテナ2の作成(映像ソース)

要件

  • 映像ソースを無限ループで配信できること。これで擬似的にLIVE配信とみなす。
  • RTMP用とHLS用とで4ストリーム配信できること。コンテナ1の設定よりffmpeg側で4ストリーム流すことが必須。

このあたり↓を参考に設定させていただきました。 テスト用の自動配信環境をDockerとFFmpegで構築してみる というかDockerfileに関しては全く同じです(汗)。

FROM jrottenberg/ffmpeg:3.2-centos

COPY source.mp4 ./
COPY start.sh ./

COPY ./ffserver.conf /etc/ffserver.conf
EXPOSE 80 1935
ENTRYPOINT bash start.sh

ただし、前述のようにffmpeg側で4ストリーム流す必要があったので、実行するシェルスクリプトを以下のように書き換えました。

#!/bin/bash

ffmpeg -re -stream_loop -1 -i 'source.mp4' 
     -acodec libfdk_aac -vcodec libx264 -f flv "${TARGET_RTMP_IP}/live/{設定したストリーム名}"  \
     -c:a libfdk_aac -b:a 32k -c:v libx264 -b:v 128K -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_low" \
     -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_mid" \
     -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_hi"

この状態でDockerコンテナを立ち上げれば映像ソースを以下の4つに対して延々と配信してくれるはずです。

${TARGET_RTMP_IP}/live/{設定したストリーム名}
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_low
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_mid
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_hi

docker-composeでの立ち上げ

ffmpeg(コンテナ2)からnginx(コンテナ1)に映像ストリームを送る際にはコンテナ間通信となるため、コンテナをリンクさせる必要がある(??Dockerまだあまり詳しくないので多分です。。。)ので、docker-composeを使ってコンテナをリンクさせて立ち上げるようにします。

結果的に以下のようなdocker-compose.ymlとなりました。

version: '2'
services:
  nginx:
    build:
      context: ./
      dockerfile: ./Dockerfile_nginx
    container_name: nginx
    ports:
      - "80:80"
      - "1935:1935"
    volumes:
      - ./:/usr/local/nginx/html

  ffmpeg:
    build:
      context: ./
      dockerfile: ./Dockerfile_ffmpeg
    container_name: ffmpeg
    environment:
      - TARGET_RTMP_IP=rtmp://{Dockerが動作するゲストOSのIPアドレス}:1935
    links:
      - nginx

TARGET_RTMP_IPとして映像ソースの送信先環境変数として渡しています。 コンテナ2で記述したシェルスクリプトがこの宛先に向けて映像を送ります。 {Dockerが動作するゲストOSのIPアドレス}については、ゲストOSのIPアドレスなので、次項で設定するVagrant上のCentOSIPアドレスを記載します。

参考

ゲストOSの立ち上げ

VagrantCentOSを立ち上げる。

要件

  • LAN内の外部端末から接続できるような基盤とすること
  • 立ち上げる際にDockerも自動で実行できること

ここまでで、メインの機能となるDockerコンテナは作成できました。 そしていよいよそのDockerの土台となるゲストOSを立ち上げてきます。

Dockerのコンテナに別の端末から接続する際には、そのDockerが動作しているOSのIPアドレスを指定すれば可能なようです。 Docker の基本学習 ~ コンテナ間のリンク

また、Vagrantで立ち上げる仮想OSも以下の記述を加えるだけで簡単に外部からのアクセスが可能になるようです。

config.vm.network "public_network", :ip => "{IPアドレス}" , :bridge => "en0: Wi-Fi (AirPort)"

[Vagrant] LAN内の別マシンから仮想マシンにアクセスする

よって、Vagrantで立ち上げるOSに上記の設定を加えれば、その上で動作しているDockerコンテナのサーバにも外部から接続ができるようになるということです。

更に、Vagrantで仮想OSを立ち上げる際に、同時にDocker、docker-composeも実行したいです。

dockerをVagrantのprovisionerとして使う方法は公式に記述してあります。 DOCKER PROVISIONER

また、docker-composeをprovisionerとして使うためのプラグインもあり、

$ vagrant plugin install vagrant-docker-compose

でインストールできるようです。 leighmcculloch/vagrant-docker-compose

設定

結果的に以下のようなVagrantfileになりました。

Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.2"
  # LAN内部に公開するため、public_networkを選択
  # IPアドレスはホストOSと同じセグメントのIPアドレスでないと接続できない
  config.vm.network "public_network", :ip => "192.168.0.100" , :bridge => "en0: Wi-Fi (AirPort)"
  config.vm.synced_folder "./", "/vagrant", type: "virtualbox"
  # provisionerとしてdockerとdocker-composeが使えるようにする
  # 起動のたびにコンテナを起動
  config.vm.provision :docker
  config.vm.provision :docker_compose, yml: "/vagrant/docker-compose.yml", run: "always"
end

参考

プレイヤーを作る

今回はFlowPlayerの検証のために環境を作ることが目的だったので、FlowPlayerのセットアップ方法に従いindexhtmlを作成しました。

まずは、flowplayerのライブラリを読み込みます。

<head>
  <!-- flow player -->
  <link rel="stylesheet" href="//releases.flowplayer.org/6.0.5/skin/minimalist.css">
  <script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
  <script src="//releases.flowplayer.org/6.0.5/flowplayer.min.js"></script>

今回はFlowPlayer上でHLSも使いたいので、hls.jsも読み込みます。

  <!-- load the Flowplayer hlsjs engine, including hls.js -->
  <script src="//releases.flowplayer.org/hlsjs/flowplayer.hlsjs.min.js"></script>
  <!-- optional: the quality selector plugin for quality selection when hlsjs is not supported -->
  <script src="//releases.flowplayer.org/quality-selector/flowplayer.quality-selector.min.js"></script>
</head>

プレイヤーを作成するには、<div>要素に対して、jsでソースを指定します。 RTMPは下記のように指定。

var rtmp_player_flv = document.getElementById("rtmp_player_flv");
flowplayer(rtmp_player_flv, {
  autoplay : true,
  clip: {
    sources: [
      {
        type: "video/flash",
        rtmpt : false,
        src:  "rtmp://{配信サーバのIPアドレス}:1935/live/mdst"
      },
    ]
  }
});

HLSは下記のように指定。

var hls_player = document.getElementById("hls_player");
flowplayer(hls_player,{
  autoplay : true,
  live : true,
  clip: {
    sources: [
      {
        type: "application/x-mpegurl",
        src:  "http://{配信サーバのIPアドレス}/hls/mdst.m3u8"
      },
    ]
  },
});

最終的にindex.htmlは以下のような形になりました。

<!DOCTYPE html>
<html lang="en" class="">
<head>
  <!-- flow player -->
  <link rel="stylesheet" href="//releases.flowplayer.org/6.0.5/skin/minimalist.css">
  <script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
  <script src="//releases.flowplayer.org/6.0.5/flowplayer.min.js"></script>
  <!-- load the Flowplayer hlsjs engine, including hls.js -->
  <script src="//releases.flowplayer.org/hlsjs/flowplayer.hlsjs.min.js"></script>
  <!-- optional: the quality selector plugin for quality selection when hlsjs is not supported -->
  <script src="//releases.flowplayer.org/quality-selector/flowplayer.quality-selector.min.js"></script>
</head>
<body>
  <div>
    <h2>RTMP</h2>
    <div id="rtmp_player_flv"></div>
  </div>
  <div>
    <h2>HTTP Live Streaming</h2>
    <div id="hls_player"></div>
  </div>
  <p id="vinfo" class="info">&nbsp;</p>

  <script type="text/javascript">
    flowplayer(function (api) {
      api.on("load", function (e, api, video) {
        $("#vinfo").text(api.engine.engineName + " engine playing " + video.type);
      });
    });
    var rtmp_player_flv = document.getElementById("rtmp_player_flv");
    flowplayer(rtmp_player_flv, {
      autoplay : true,
      clip: {
        sources: [
          {
            type: "video/flash",
            rtmpt : false,
            src:  "rtmp://{配信サーバのIPアドレス}:1935/live/mdst"
          },
        ]
      }
    });
    var hls_player = document.getElementById("hls_player");
    flowplayer(hls_player,{
      autoplay : true,
      live : true,
      clip: {
        sources: [
          {
            type: "application/x-mpegurl",
            src:  "http://{配信サーバのIPアドレス}/hls/mdst.m3u8"
          },
        ]
      },
    });
  </script>
</body>
</html>

起動

起動する際は、Vagrantfileがあるディレクトリで下記のコマンドを実行します。

$ vagrant plugin install vagrant-docker-compose
$ vagrant up

まとめ

これにて、コマンド一つ(実際には2つ)で動画配信と視聴の環境を構築することができました。 あとはindex.htmlを修正することで様々なプレイヤーの検証を行うことができます。