Monday, July 22, 2013

HLS variant playlist

In v1.0.2 of nginx-rtmp-module I've added support for HLS variant playlists. Now it's easy to create multi-bitrate HLS streams. The new directive is hls_variant.
hls_variant SUFFIX [PARAM]*;
where SUFFIX is used to match incoming stream name, PARAMs are values added to each variant playlist entry describing the entry. Examples are BANDWIDTH=xxxx or CODECS=yyyy.
http {
    listen 80;

    location /hls {
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        alias /tmp;
    }
}

rtmp {
    server {
        listen 1935;
        
        application src {
            live on;

            exec ffmpeg -i rtmp://localhost/src/$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 /tmp/hls;
            hls_nested on;

            hls_variant _low BANDWIDTH=160000;
            hls_variant _mid BANDWIDTH=320000;
            hls_variant _hi  BANDWIDTH=640000;
        }
    }
}
If you stream incoming video with the following command
ffmpeg -i /var/videos/sintel.mp4 -c:a copy -c:v copy -f flv rtmp://localhost/src/sintel;
then the multi-bitrate HLS stream will be available at the following URL
ffplay http://localhost/hls/sintel.m3u8
This playlist references 3 playlist with low, medium and high qualities.

15 comments:

  1. I am not sure why this isn't working...

    I'm feeding it an h.264 stream from a raspberry pi camera module like so:

    raspivid -t 999999999 -w 960 -h 540 -fps 10 -b 500000 -vf -o - | ffmpeg -i - -c:v copy -an -f flv rtmp://localhost/src/raspi

    nginx-rtmp is accepting the stream (this is output from nginx-rtmp's stat.xsl):

    src
    live streams 1
    raspi 1 8.54 MB 0 KB 165 Kb/s 0 Kb/s 960x540 25 H264 active 6m 54s
    hls
    live streams 0
    Generated by NGINX RTMP module, NGINX , pid 24116, built Aug 25 2013 16:43:05 gcc 4.6.3 (Debian 4.6.3-14+rpi1)

    From nginx.conf (this is basically copied from your post above, but I told ffmpeg to drop the audio, which doesn't exist anyway):

    rtmp {
    server {
    listen 1935;
    ping 30s;
    notify_method get;

    application src {
    live on;

    exec ffmpeg -i rtmp://localhost/src/$name
    -an -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low
    -an -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid
    -an -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi;
    }

    application hls {
    live on;

    hls on;
    hls_path /tmp/hls;
    hls_nested on;

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

    }
    }


    FFMPEG is streaming camera video nicely:

    ffmpeg version N-55356-gb11b7ce Copyright (c) 2000-2013 the FFmpeg developers
    built on Aug 8 2013 00:43:17 with gcc 4.6 (Debian 4.6.3-14+rpi1)
    configuration:
    libavutil 52. 41.100 / 52. 41.100
    libavcodec 55. 23.100 / 55. 23.100
    libavformat 55. 13.102 / 55. 13.102
    libavdevice 55. 3.100 / 55. 3.100
    libavfilter 3. 82.100 / 3. 82.100
    libswscale 2. 4.100 / 2. 4.100
    libswresample 0. 17.103 / 0. 17.103
    Input #0, h264, from 'pipe:':
    Duration: N/A, bitrate: N/A
    Stream #0:0: Video: h264 (High), yuv420p, 960x540, 25 fps, 25 tbr, 1200k tbn, 50 tbc
    Output #0, flv, to 'rtmp://localhost/src/raspi':
    Metadata:
    encoder : Lavf55.13.102
    Stream #0:0: Video: h264 ([7][0][0][0] / 0x0007), yuv420p, 960x540, q=2-31, 25 fps, 1k tbn, 1200k tbc
    Stream mapping:
    Stream #0:0 -> #0:0 (copy)
    frame= 8571 fps= 10 q=-1.0 size= 17645kB time=00:05:42.80 bitrate= 421.7kbits/s




    So it looks like it's working, but...

    http://localhost/hls/raspi.m3u8 -> HTTP error 404 Not Found
    http://localhost/hls -> HTTP error 403 Forbidden

    What am I doing wrong?

    ReplyDelete
    Replies
    1. Change localhost in the following command to localhost:1935:
      exec ffmpeg -i rtmp://localhost/src/$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;

      Delete
  2. Hi Arut, this feature works with vod files?

    ReplyDelete
  3. No. HLS support in nginx-rtmp-module is only for live streams.

    ReplyDelete
  4. Hi Arut, thanks for your response. One more question, please.
    I'm trying to deploy this feature, but I believe that I did something wrong.
    Basically, I'm ingest a stream as below:

    Duration: 02:04:42.34, start: 0.000000, bitrate: 2127 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x808, 2030 kb/s, 23.98
    Stream #0:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, s16, 93 kb/s

    But the logs of nginx showed me like below:

    2013/10/22 17:28:11 [info] 652#0: *3 client connected '127.0.0.1'
    2013/10/22 17:28:11 [info] 652#0: *3 connect: app='src' args='' flashver='FMLE/3.0 (compatible; Lavf54.29' swf_url='' tc_url='rtmp://localhost:1935/src' page_url='' acodecs=0 vcodecs=0 object_encoding=0, client: 127.0.0.1, server: 0.0.0.0:1935
    2013/10/22 17:28:11 [info] 652#0: *3 createStream, client: 127.0.0.1, server: 0.0.0.0:1935
    2013/10/22 17:28:11 [info] 652#0: *3 publish: name='sintel' args='' type=live silent=0, client: 127.0.0.1, server: 0.0.0.0:1935
    2013/10/22 17:28:11 [notice] 652#0: signal 17 (SIGCHLD) received
    2013/10/22 17:28:11 [notice] 652#0: unknown process 1973 exited with code 1
    2013/10/22 17:28:16 [notice] 652#0: signal 17 (SIGCHLD) received
    2013/10/22 17:28:16 [notice] 652#0: unknown process 1983 exited with code 1
    2013/10/22 17:28:17 [info] 652#0: *3 deleteStream, client: 127.0.0.1, server: 0.0.0.0:1935
    2013/10/22 17:28:17 [info] 652#0: *3 disconnect, client: 127.0.0.1, server: 0.0.0.0:1935
    2013/10/22 17:28:17 [info] 652#0: *3 deleteStream, client: 127.0.0.1, server: 0.0.0.0:1935

    My nginx.conf is below:

    worker_processes 5;

    error_log logs/error.log debug;

    events {
    worker_connections 1024;
    }

    rtmp {
    server {
    listen 1935;
    ping 30s;
    notify_method get;

    application src {
    live on;

    exec ffmpeg -i rtmp://localhost/src/$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 /mnt/hls;
    hls_nested on;
    hls_fragment 5s;

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

    http {
    include mime.types;
    default_type application/octet-stream;

    sendfile on;
    keepalive_timeout 65;

    server {
    listen 80;
    server_name localhost;

    # rtmp stat
    location /stat {
    rtmp_stat all;
    rtmp_stat_stylesheet stat.xsl;
    }

    location /stat.xsl {
    # you can move stat.xsl to a different location
    root ~/build/nginx-rtmp-module/;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root html;
    }

    location /hls {
    types {
    application/vnd.apple.mpegurl m3u8;
    video/mp2t ts;
    }
    alias /mnt/hls;
    }
    }
    }

    What should be wrong?
    Thanks again.

    ReplyDelete
    Replies
    1. ffmpeg exits for some reason

      1. make sure ffmpeg is accessible from nginx worker account (unprivileged usually)
      2. add "2>>/tmp/log" to the end of exec directive to see ffmpeg error

      Delete
    2. Hi Arut. What is your recommendation for #1?

      Should I modify nginx config so worker_processes run as a user other than nobody? Or should I grant nobody access to exec ffmpeg (possible?)?

      Thank you.

      Delete
  5. application myapp {
    live on;
    # sample recorder
    recorder rec1 {
    record all;
    record_interval 30s;
    record_path /tmp;
    record_unique on;
    }
    # sample HLS
    hls on;
    hls_path /tmp/app;
    hls_fragment 15s;
    hls_nested on;
    hls_cleanup off;
    hls_variant _low BANDWIDTH=160000;
    hls_variant _mid BANDWIDTH=320000;
    hls_variant _hi BANDWIDTH=640000;
    }

    location /myapp {
    types {
    application/vnd.apple.mpegurl m3u8;
    video/mp2t ts;
    }
    alias /tmp/app;
    add_header Cache-Control no-cache;
    }

    I am able to play the m3u8 file but only audio without video. i tested in both android and iphone browsers .. while i am trying to play the ts files directly using media player it also play only audio.

    ReplyDelete
    Replies
    1. Please check video codec. It should be h264.

      Delete
  6. Can we create more than 3variants?. I am getting an exception for 4 variants:
    x264 [error]: malloc of size 6955584 failed
    Video encoding failed

    ReplyDelete
    Replies
    1. There's no limit on the number of variants. Where do you have this exception? Libx264 is not related to the rtmp module.

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Quick question: Does the on_play or exec_play type of events work with HLS variants, I cant seem to get it to fire, and my players are not even showing up in the stats page


    *****
    rtmp {
    server {
    listen 1935;
    exec_play bash -c "echo $addr $name >> /tmp/clients";
    exec_play_done bash -c "echo $addr $name done>> /tmp/clients";
    exec_publish bash -c "echo $addr $name >> /tmp/publishers";
    exec_publish_done bash -c "echo $addr $name done >> /tmp/publishers";

    application src {
    live on;

    exec ffmpeg -i rtmp://stream.reachbig.com/src/$name
    -acodec copy -b:a 64k -c:v libx264 -b:v 256k -f flv rtmp://stream.reachbig.com/live/$name_mid
    -acodec copy -vcodec copy -f flv rtmp://stream.reachbig.com/live/$name_hi;



    }


    application live {
    live on;
    hls on;
    hls_path /HLS/live;
    hls_nested on;
    hls_fragment 10;
    hls_variant _mid BANDWIDTH=320000;
    hls_variant _hi BANDWIDTH=2400000;



    }
    }
    }
    ******

    I see the publish and the client playing the src but only the publish in the live

    I ultimately want to trigger a bash script on different events.

    Jeremy

    ReplyDelete
  9. Hi Arut,

    Is a multi-bitrate configuration possible with the DASH module of your streaming module? If so, is the syntax for configuring DASH bitrate variants similar to what you've described here for HLS?

    Thank you,
    Paul

    ReplyDelete
  10. Hi Arut,

    Sorry for my english, i'm French.
    I try your configuration for make multi-bitrate HLS streams, but it's not working.

    I'm pretty sure the probleme is what have you say : make sure ffmpeg is accessible from nginx worker account (unprivileged usually)

    Have you some tips to check that ?

    Thanks, and good job for your work man.

    ReplyDelete