【Node.js】FFmpegで HLS 配信 をしてみる

今回はメルチメディア系の話です。映像配信で良く使われる RTP/RTSP サーバではなく FFmpeg と Node.js で, MP4配信 / HLS( HTTP Live Streaming )配信 / ffserverを試してみました。

nodejs

環境は CentOS6.4 (Final)ですが, ビルドは Debian7.4 でも確認しています。

FFmpegインストール

h264S/Wコーデックのx264を使う場合,まずyasm(アセンブラ)をインストールした方がパフォーマンスが上がります。

# you need gcc & make-tools
$ wget https://www.tortall.net/projects/yasm/releases/yasm-1.2.0.tar.gz
$ tar zxvf yasm-1.2.0.tar.gz 
$ cd yasm-1.2.0
$ ./configure
$ make
$ make install

続いて, x264のインストールです。

$ git clone git://git.videolan.org/x264
$ ./configure  –disable-opencl --enable-static
$ make 
$ make install

AACエンコーダのインストールです。(使わない場合は飛ばします)

$ wget https://downloads.sourceforge.net/faac/faac-1.28.tar.gz
$ tar zxvf faac-1.28.tar.gz
$ cd faac-1.28
$ ./configure --with-mp4v2
$ make
$ make install

debian7.4に入ってるffmpegはver0.8.10なのでソースからインストールします。
ffmpegのソースコードはgitから取得できます。tarball bzip2も手に入ります。

$ git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg
$ https://www.ffmpeg.org/releases/ffmpeg-1.2.6.tar.bz2
$ ./configure --enable-gpl --enable-libx264 --enable-libfaac
$ make 
$ sudo make install

アセンブラを使わない場合は–disable-yasmを指定。
コンパイルは30分くらいかかりました…長かった。

HTTP Live Streamingで配信する

まずffmpegでファイルを分割しプレイリストを生成します。

$ ffmpeg -i test.mp4 -acodec copy -vcodec copy   -vbsf h264_mp4toannexb   -map 0   -f segment   -segment_format mpegts  -segment_time 2   -segment_list playlist.m3u8   -segment_list_flags -cache   stream/stream_%d.ts

ffmpegで生成したplaylistとtsファイルを以下のような構成で配置します。

$ tree
├── ffmpeg_test.js
├── stream
│   ├── playlist.m3u8
│   ├── stream_0.ts
│   ├── stream_1.ts
│   └── stream_2.ts
├── test_playHLS.html

Node.jsで配信する場合、例えばexpressを使っていたら以下のようにpathを設定します。

 app.use(express.static(path.join(__dirname,'stream')));

HTML5videoタグのsrcで./playlist.m3u8をしてすれば配信されます。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
    <title>HTTP Live Streaming Test</title>
  </head>
  <body>
    <header>
      <h1>HTTP Live Streaming Test</h1>
    </header>
    <div>
      <video width="640" height="480" src="./playlist.m3u8" preload="none" onclick="this.play()" controls />
    </div>
  </body>
</html>

ただし,application/vnd.apple.mpegurlかapplication/x-mpegURのMIMEをサポートしているブラウザでないと再生されません。
Appleのプロトコルなので iOS / Safariでは再生できます。

USBCamの映像をffserverで配信する

USBCamからリアルタイム配信する場合, inputをUSBCamにしてUVCを扱うためV4L2を使います。
今回はBeagleBoneBlack(以下BBB)に繋いだUSBCamから映像を取得しffserverで配信し, 同一セグメント上のPCで映像を確認します。

まずffserverの設定ファイルを配置します。

# /etc/ffserver.conf

# Port on which the server is listening. You must select a different
# port from your standard HTTP web server if it is running on the same
# computer.
Port 8090

# Address on which the server is bound. Only useful if you have
# several network interfaces.
BindAddress 0.0.0.0

# Number of simultaneous HTTP connections that can be handled. It has
# to be defined *before* the MaxClients parameter, since it defines the
# MaxClients maximum limit.
MaxHTTPConnections 2000

# Number of simultaneous requests that can be handled. Since FFServer
# is very fast, it is more likely that you will want to leave this high
# and use MaxBandwidth, below.
MaxClients 1000

# This the maximum amount of kbit/sec that you are prepared to
# consume when streaming to clients.
MaxBandwidth 4096

# Access log file (uses standard Apache log file format)
# '-' is the standard output.
CustomLog -

##################################################################


File /tmp/feed1.ffm
FileMaxSize 100M
# Only allow connections from localhost to the feed.
ACL allow 127.0.0.1


##################################################################

# coming from live feed 'feed1'
Feed feed1.ffm

# Bitrate for the video stream
VideoBitRate 256

# Ratecontrol buffer size
#VideoBufferSize 40

# Number of frames per second
#VideoFrameRate 3
VideoSize 320x240

# Suppress audio
NoAudio


##################################################################
# Example streams
# Flash


Feed feed1.ffm
Format swf
VideoFrameRate 5
VideoSize 320x240
NoAudio
VideoBitRate 1024


##################################################################
# Special streams
# Server status


Format status
# Only allow local people to get the status
ACL allow localhost
ACL allow 192.168.0.0 192.168.255.255
#FaviconURL https://pond1.gladstonefamily.net:8080/favicon.ico

# Redirect index.html to the appropriate site


URL https://www.ffmpeg.org/

ffserverを起動します。

$ ffserver -d -f /etc/ffserver.conf &
$ ffmpeg -s 320x240  -f video4linux2 -i /dev/video0  https://127.0.0.1:8090/feed1.ffm

ffmpegのオプションで/etc/ffserver.confと矛盾する設定をするとfeedが上手く処理されず配信されないので注意してください。
stat.htmlにアクセスするとサーバ状態が確認でき,test.swfでvideoが見れます。

ffserver

Node.jsからffmpegを叩く

続いて, h264ファイルをリアルタイムでMP4コンテナフォーマットに変換して配信してみます。

また, スマホ等で取得した映像もサーバで受け取ってから工夫すれば変換できると思います。

Node.jsでリクエスト時にリアルタイム変換したい場合。
child_process.spawnで子プロセスを起動して,ffmpegを叩きその出力をSTDOUTにしpipeでresponseに流します。

app.get('/conversion_test.mp4', function(request, response) {

 setTimeout(function(){
  // Write header
  response.writeHead(200, {  'Content-Type': 'application/mp4' });

  // video size 
  width = 640;
  height = 480;
 
  var ffmpeg = child_process.spawn("ffmpeg",[
                "-i","./stream/in.264", // if you need STDIN , set "pipe:0"
                "-re",               // Real time mode
                "-f","mp4",          // mp4
                "-s",width+"x"+height,   // VGA
                "-r","3",              // Framerate
                "-vcodec","libx264",     // 264 Enc
                "-acodec","libfaac",      //  AAC Enc 
                "-vb","256k",
                "-ab","64k",
                "pipe:1" // Output -> STDOUT
  ]);
   // Pipe the video output to the client response
   ffmpeg.stdout.pipe(response);
   // Kill the subprocesses when client disconnects
   response.on("close",function(){
     ffmpeg.kill();
   })
  },1000);
});

これだとエラーでした。

[mp4 @ 0x2b8f520] muxer does not support non seekable output

標準出力だとseekできないようなので、オプション -f mpegts に変えたらOKできました。

スマホ連携あたりも面白そうですね。

FFmpeg参考

streamingGuide
ffplay

* この記事はkarotaの活動に関する記事です。