
Thanks to Adrian Drzewicki (webina.rs project) for the amazing t-shirt.
I'm often asked to help with exec directive not starting child ffmpeg (or any other process). One common error is the following
In this case exec will not start child process because nginx wipes its environment before starting workers. So child processes have empty PATH variable and cannot find the executable. In such cases the binary is usually looked up in default locations like /bin or /usr/bin.
To solve the issue please add env PATH directive to nginx.conf. It will preserve PATH environment variable.
...
# keep $PATH
env PATH;
...
rtmp {
server {
listen 1935;
application myapp {
live on;
exec ffmpeg -i rtmp://localhost/myapp/$name
-c copy -f flv rtmp://localhost/app2/$name;
}
application app2 {
live on;
}
}
}
In nginx-rtmp-module version 1.0.9 I've implemented parsing H264 SPS block. Now it's possible to see video dimensions even if no RTMP metadata came from publisher. It's important for arutcam which does not generate such metadata. In earlier versions this prevented MPEG-DASH engine from generating valid initialization fragment.
Stat page is updated as well with smart codec names and profiles.
location /dash {
root /tmp;
}
...
application myapp {
live on;
dash on;
dash_path /tmp/dash;
}
Dash module will create m4v, m4a, mpd files in its directory.
application live {
live on;
idle_streams off;
}
application myapp {
live on;
exec_pull ffmpeg -i http://example.com/video.ts -c:v copy
-c:a libfaac -ar 44100 -ac 1
-f flv rtmp://localhost/$app/$name;
}
The code is in exec-pull branch of nginx-rtmp-module.
hls_type live|event
application mystream {
live on;
hls on;
hls_path /tmp/hls;
hls_playlist_length 1h;
hls_type event;
}
application /myapp {
live on;
meta copy;
}
ffplay rtmp://localhost/myapp/_definst_/mystream
ffplay rtmp://localhost/myapp/mystream
location /hls {
root /tmp;
}
...
application mystream {
live on;
hls on;
hls_path /tmp/hls;
hls_base_url http://localhost:8080/hls/;
}
$ cat /tmp/hls/mystream.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-TARGETDURATION:15
#EXT-X-DISCONTINUITY
#EXTINF:9.333,
http://localhost:8080/hls/mystream-0.ts
#EXTINF:7.167,
http://localhost:8080/hls/mystream-1.ts
#EXTINF:5.417,
http://localhost:8080/hls/mystream-2.ts
#EXTINF:5.500,
http://localhost:8080/hls/mystream-3.ts
#EXTINF:15.166,
http://localhost:8080/hls/mystream-4.ts
#EXTINF:9.584,
http://localhost:8080/hls/mystream-5.ts
#EXTINF:9.333,
http://localhost:8080/hls/mystream-6.ts
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.
gst-launch-1.0 v4l2src ! "video/x-raw,width=640,height=480,framerate=15/1" !
h264enc bitrate=1000 ! video/x-h264,profile=high ! h264parse ! queue !
flvmux name=mux alsasrc device=hw:1 ! audioresample ! audio/x-raw,rate=48000 !
queue ! voaacenc bitrate=32000 ! queue ! mux. mux. !
rtmpsink location=\"rtmp://example.com/myapp/mystream live=1\"
publish_time_fix off;
gst-launch-1.0 v4l2src ! "video/x-raw,width=640,height=480,framerate=15/1" !
omxh264enc target-bitrate=1000000 control-rate=variable !
video/x-h264,profile=high ! h264parse ! queue !
flvmux name=mux alsasrc device=hw:1 ! audioresample ! audio/x-raw,rate=48000 !
queue ! voaacenc bitrate=32000 ! queue ! mux. mux. !
rtmpsink location=\"rtmp://example.com/myapp/mystream live=1\"
listen 80; # usual listen directive
listen 9000 per_worker; # per-worker listener
events {
worker_connections 1024;
accept_mutex off;
}
server {
listen 9000 per_worker;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /tmp;
}
location /control {
rtmp_control all;
}
}
http {
server {
listen 8080;
server_name localhost;
location /on_play {
# set connection secure link
secure_link $arg_st,$arg_e;
secure_link_md5 mysecretkey$arg_app/$arg_name$arg_e;
# bad hash
if ($secure_link = "") {
return 501;
}
# link expired
if ($secure_link = "0") {
return 502;
}
return 200;
}
}
}
rtmp {
server {
listen 1935;
notify_method get;
# protected application
application myapp {
live on;
on_play http://localhost:8080/on_play;
}
}
}
> ffplay 'rtmp://localhost/myapp/mystream'
ffplay version 1.0.6 Copyright (c) 2003-2013 the FFmpeg developers
...
rtmp://localhost/myapp/mystream: Unknown error occurred
notify: HTTP retcode: 5xx
> date +%s
1370777449
> echo $((1370777449+3600))
1370781049
> echo -n "mysecretkeymyapp/mystream1370781049" | openssl dgst -md5 -binary |
openssl enc -base64 | tr '+/' '-_' | tr -d '='
Mbjev5ld4mmCN00mwIqD7w
> ffplay 'rtmp://localhost/myapp/mystream?e=1370781049&st=Mbjev5ld4mmCN00mwIqD7w'
location / {
hls;
root /var/mp4;
}
application myapp {
live on;
hls on;
hls_path /tmp/hsls;
hls_cleanup off;
}
application myapp {
live on;
pull rtmp://video.example.com/someapp;
}
http {
...
location /local_redirect {
rewrite ^.*$ newname? permanent;
}
location /remote_redirect {
# no domain name here, only ip
rewrite ^.*$ rtmp://192.168.1.123/someapp/somename? permanent;
}
...
}
rtmp {
...
application myapp1 {
live on;
# stream will be redirected to 'newname'
on_play http://localhost:8080/local_redirect;
}
application myapp2 {
live on;
# stream will be pulled from remote location
# requires nginx >= 1.3.10
on_play http://localhost:8080/remote_redirect;
}
...
}
recorder rec1 {
record all;
record_interval 5s;
record_suffix -%d-%b-%y-%T.flv;
record_path /tmp/rec;
}
application myapp {
live on;
exec ffmpeg -i rtmp://localhost/myapp/$name -c copy
-f flv rtmp://example.com/myapp/$name 2>>/var/log/ffmpeg-$name.log;
}
curl -v http://localhost:8080/on_connect?...
...
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.2.4
< Date: Sun, 14 Apr 2013 04:41:18 GMT
< Content-Type: text/html
< Content-Length: 184
< Connection: keep-alive
< Location: fallback
...
http {
server {
listen 8080;
server_name localhost;
location /on_connect {
if ($arg_flashver != "my_secret_flashver") {
rewrite ^.*$ fallback? permanent;
}
return 200;
}
}
}
rtmp {
server {
listen 1935;
notify_method get;
on_connect http://localhost:8080/on_connect;
application myapp {
# Live stream for good clients
live on;
}
application fallback {
# VOD video for bad clients
live on;
play /var/fallback;
}
}
}
<script type="text/javascript" src="/jwplayer/jwplayer.js"></script></pre>
<div id="container">Loading the player ...</div>
<script type="text/javascript">
jwplayer("container").setup({
sources:[
{
file:"/sintel.smil"
}
],
ga: {},
autostart: true,
width: 640,
height: 480,
primary: 'flash'
});
</script>
<smil>
<head>
<meta base="rtmp://localhost/vod/" />
</head>
<body>
<switch>
<video src="mp4:sintel-120p.mp4" height="120" />
<video src="mp4:sintel-240p.mp4" height="240" />
<video src="mp4:sintel-480p.mp4" height="480" />
</switch>
</body>
</smil>
ffmpeg -i sintel.mp4 -c:v libx264 -c:a copy -s 640x480 sintel-480p.mp4
ffmpeg -i sintel.mp4 -c:v libx264 -c:a copy -s 320x240 sintel-240p.mp4
ffmpeg -i sintel.mp4 -c:v libx264 -c:a copy -s 160x120 sintel-120p.mp4
record all;
record_path /tmp/rec;
record_append on;
max_connections 200;
application vod {
# search in /var/videos; in case of failure search in /www
play /var/videos /www;
}
application vod {
# search in local directories; if not found go to remote server
play /tmp/cache /var/videos http://example.com/videos;
}
application cached_vod {
# remote files are copied to /tmp/videos after download
# and played later from local directory until deleted manually
play_local_path /tmp/videos;
play /tmp/videos http://video.example.com/videos;
}
application myapp {
live on;
}
application big {
live on;
exec ffmpeg -i rtmp://localhost/big/$name -i rtmp://localhost/small/$name
-filter_complex "[0][1]overlay=10:10" -f flv rtmp://localhost/myapp/$name;
}
application small {
live on;
}
ffmpeg -re -i /var/video/sintel.mp4 -c:v flv -s 800x600 -c:a libfaac -ar 22050 -ac 1
-f flv rtmp://localhost/big/mystream
ffmpeg -re -i /var/video/ed.avi -c:v flv -s 400x200 -c:a libfaac -ar 22050 -ac 1
-f flv rtmp://localhost/small/mystream
movie=/var/pictures/mychan.png[logo];[0][logo]overlay=0:70
application myapp {
live on;
}
application addlogo{
live on;
exec ffmpeg -i rtmp://localhost/addlogo/$name
-vf "movie=/var/pictures/mychan.png[logo];[0][logo]overlay=0:70"
-c:v flv -f flv rtmp://localhost/myapp/$name;
}
ffmpeg -re -i /var/video/sintel.mp4 -c:v flv -s 800x600 -c:a libfaac -ar 44100 -ac 1
-f flv rtmp://localhost/addlogo/mystream
jwplayer("container").setup({
modes: [
{ type: "flash",
src: "/jwplayer/player.swf",
config: {
bufferlength: 1,
file: "mystream",
streamer: "rtmp://localhost/myapp?arg1=v1&arg2=v2",
provider: "rtmp",
autostart: "1"
}
}
]
});
rtmp {
server {
listen 1935;
notify_method get;
on_connect http://localhost:8080/on_connect;
on_disconnect http://localhost:8080/on_disconnect;
application myapp {
live on;
}
}
}
pull rtmp://remote.example.com/app/stream name=localstream static;
exec_static ffmpeg -re -i /var/movies/movie.avi -c:v libx264
-c:a libmp3lame -ar 22050 -ac 1 -f flv rtmp://localhost/myapp/mystream;
exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264
-an -f flv rtmp://localhost/myapp/mystream
exec_static ffmpeg -i http://video.example.com/livechannel.ts -c copy
-f flv rtmp://localhost/myapp/mystream
#EXT-X-ALLOW-CACHE:NO
hls on;
hls_path /tmp/hls;
hls_continuous on;
rtmp://localhost/vod/file.mp4?aindex=1
rtmp://localhost/vod/file.mp4?vindex=1
# ignore video track
rtmp://localhost/vod/file.mp4?vindex=-1
rtmp://localhost/vod/file.mp4?aindex=0&vindex=2
recorder myrec {
record all;
record_interval 30s;
record_path /tmp/rec;
record_unique on;
record_lock on;
}
#!/usr/bin/python
import fcntl, sys
sys.stderr.close()
fcntl.lockf(open(sys.argv[1], "a"), fcntl.LOCK_EX|fcntl.LOCK_NB)
for file in /tmp/rec/*; do
printf "$file : ";
if ~/isunlocked.py $file; then echo "ready"; else echo "locked"; fi;
done
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
}
alias /tmp/hls;
}
rtmp {
access_log logs/rtmp_access.log;
server {
...
}
}
access_log off;
log_format new '$remote_addr $bytes_sent';
access_log logs/rtmp_access.log new;
$remote_addr [$time_local] $command "$app" "$name" "$args" -
$bytes_received $bytes_sent "$pageurl" "$flashver" ($session_readable_time)
pull rtmp://remote.example.com/myapp/rstream pageUrl=ABC name=mystream static;