merged from master

This commit is contained in:
Roman Arutyunyan 2012-08-30 17:39:31 +04:00
commit acf6ea076c
32 changed files with 3973 additions and 787 deletions

253
README
View file

@ -1,253 +0,0 @@
== nginx-rtmp-module ==
NGINX-based RTMP server
Project page:
http://arut.github.com/nginx-rtmp-module
Wiki manual:
https://github.com/arut/nginx-rtmp-module/wiki
Features:
* Live streaming of video/audio
* Video on demand (FLV)
* Stream relay support for distributed
streaming: push & pull models
* Recording published streams in FLV file
* H264/AAC support
* Online transcoding with FFmpeg
(experimental; Linux only)
* HLS (HTTP Live Streaming) support
(experimental; libavformat >= 53.31.100)
* HTTP callbacks on publish/play/record
* Advanced buffering techniques
to keep memory allocations at a minimum
level for faster streaming and low
memory footprint
* Works with Flash RTMP clients as well as
ffmpeg/rtmpdump/flvstreamer etc
(see examples in test/ subdir)
* Statistics in XML/XSL in machine- & human-
readable form
Build:
cd to NGINX source directory & run this:
./configure --add-module=<path-to-nginx-rtmp-module>
make
make install
Known issue:
The module does not share data between workers.
Because of this live streaming is only available
in one-worker mode so far. Video-on-demand has no
such limitations.
RTMP URL format:
rtmp://rtmp.example.com/<app>[/<name>]
<app> - should match one of application {}
blocks in config
<name> - interpreted by each application
can be empty
Example nginx.conf:
rtmp {
server {
listen 1935;
chunk_size 4000;
# TV mode: one publisher, many subscribers
application mytv {
# enable live streaming
live on;
# record first 1K of stream
record all;
record_path /tmp/av;
record_max_size 1K;
# append current timestamp to each flv
record_unique on;
# publish only from localhost
allow publish 127.0.0.1;
deny publish all;
#allow play all;
}
# Transcoding (ffmpeg needed)
application big {
live on;
# On every pusblished stream run this command (ffmpeg)
# with substitutions: $app/${app}, $name/${name} for application & stream name.
#
# This ffmpeg call receives stream from this application &
# reduces the resolution down to 32x32. The stream is the published to
# 'small' application (see below) under the same name.
#
# ffmpeg can do anything with the stream like video/audio
# transcoding, resizing, altering container/codec params etc
#
# Multiple exec lines can be specified.
exec /usr/bin/ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 -f flv rtmp://localhost:1935/small/${name};
}
application small {
live on;
# Video with reduced resolution comes here from ffmpeg
}
application mypush {
live on;
# Every stream published here
# is automatically pushed to
# these two machines
push rtmp1.example.com;
push rtmp2.example.com:1934;
}
application mypull {
live on;
# Pull all streams from remote machine
# and play locally
pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html;
}
# video on demand
application vod {
play /var/flvs;
}
# Many publishers, many subscribers
# no checks, no recording
application videochat {
live on;
# The following notifications receive all
# the session variables as well as
# particular call arguments in HTTP POST
# request
# Make HTTP request & use HTTP retcode
# to decide whether to allow publishing
# from this connection or not
on_publish http://localhost:8080/publish;
# Same with playing
on_play http://localhost:8080/play;
# Publish/play end (repeats on disconnect)
on_done http://localhost:8080/done;
# All above mentioned notifications receive
# standard connect() arguments as well as
# play/publish ones. If any arguments are sent
# with GET-style syntax to play & publish
# these are also included.
# Example URL:
# rtmp://localhost/myapp/mystream?a=b&c=d
# record 10 video keyframes (no audio) every 2 minutes
record keyframes;
record_path /tmp/vc;
record_max_frames 10;
record_interval 2m;
# Async notify about an flv recorded
on_record_done http://localhost:8080/record_done;
}
# HLS (experimental)
# HLS requires libavformat & should be configured as a separate
# NGINX module in addition to nginx-rtmp-module:
# ./configure ... --add-module=/path/to/nginx-rtmp-module/hls ...
# For HLS to work please create a directory in tmpfs (/tmp/app here)
# for the fragments. The directory contents is served via HTTP (see
# http{} section in config)
#
# Incoming stream must be in H264/AAC/MP3. For iPhones use baseline H264
# profile (see ffmpeg example).
# This example creates RTMP stream from movie ready for HLS:
#
# ffmpeg -loglevel verbose -re -i movie.avi -vcodec libx264
# -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
# -f flv rtmp://localhost:1935/hls/movie
#
# If you need to transcode live stream use 'exec' feature.
#
application hls {
hls on;
hls_path /tmp/app;
hls_fragment 5s;
}
}
}
# HTTP can be used for accessing RTMP stats
http {
server {
listen 8080;
# This URL provides RTMP statistics in XML
location /stat {
rtmp_stat all;
# Use this stylesheet to view XML as web page
# in browser
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
# XML stylesheet to view RTMP stats.
# Copy stat.xsl wherever you want
# and put the full directory path here
root /path/to/stat.xsl/;
}
location /hls {
# Serve HLS fragments
alias /tmp/app;
}
}
}

262
README.md Normal file
View file

@ -0,0 +1,262 @@
# NGINX-based RTMP server
## nginx-rtmp-module
### Project page:
http://arut.github.com/nginx-rtmp-module
### Wiki manual:
https://github.com/arut/nginx-rtmp-module/wiki
### Features:
* Live streaming of video/audio
* Video on demand FLV/MP4
* Stream relay support for distributed
streaming: push & pull models
* Recording published streams in FLV file
* H264/AAC support
* Online transcoding with FFmpeg
* HLS (HTTP Live Streaming) support;
experimental; requires recent libavformat
(>= 53.31.100) from ffmpeg (ffmpeg.org)
* HTTP callbacks on publish/play/record
* Advanced buffering techniques
to keep memory allocations at a minimum
level for faster streaming and low
memory footprint
* Proved to work with Wirecast,FMS,Wowza,
JWPlayer,FlowPlayer,StrobeMediaPlayback,
ffmpeg,avconv,rtmpdump,flvstreamer
and many more
* Statistics in XML/XSL in machine- & human-
readable form
### Build:
cd to NGINX source directory & run this:
./configure --add-module=<path-to-nginx-rtmp-module>
make
make install
### Known issue:
The module does not share data between workers.
Because of this live streaming is only available
in one-worker mode so far. Video-on-demand has no
such limitations.
You can try auto-push branch with multi-worker
support if you really need that.
### RTMP URL format:
rtmp://rtmp.example.com/app[/name]
app - should match one of application {}
blocks in config
name - interpreted by each application
can be empty
### Example nginx.conf:
rtmp {
server {
listen 1935;
chunk_size 4000;
# TV mode: one publisher, many subscribers
application mytv {
# enable live streaming
live on;
# record first 1K of stream
record all;
record_path /tmp/av;
record_max_size 1K;
# append current timestamp to each flv
record_unique on;
# publish only from localhost
allow publish 127.0.0.1;
deny publish all;
#allow play all;
}
# Transcoding (ffmpeg needed)
application big {
live on;
# On every pusblished stream run this command (ffmpeg)
# with substitutions: $app/${app}, $name/${name} for application & stream name.
#
# This ffmpeg call receives stream from this application &
# reduces the resolution down to 32x32. The stream is the published to
# 'small' application (see below) under the same name.
#
# ffmpeg can do anything with the stream like video/audio
# transcoding, resizing, altering container/codec params etc
#
# Multiple exec lines can be specified.
exec /usr/bin/ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 -f flv rtmp://localhost:1935/small/${name};
}
application small {
live on;
# Video with reduced resolution comes here from ffmpeg
}
application mypush {
live on;
# Every stream published here
# is automatically pushed to
# these two machines
push rtmp1.example.com;
push rtmp2.example.com:1934;
}
application mypull {
live on;
# Pull all streams from remote machine
# and play locally
pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html;
}
# video on demand
application vod {
play /var/flvs;
}
application vod2 {
play /var/mp4s;
}
# Many publishers, many subscribers
# no checks, no recording
application videochat {
live on;
# The following notifications receive all
# the session variables as well as
# particular call arguments in HTTP POST
# request
# Make HTTP request & use HTTP retcode
# to decide whether to allow publishing
# from this connection or not
on_publish http://localhost:8080/publish;
# Same with playing
on_play http://localhost:8080/play;
# Publish/play end (repeats on disconnect)
on_done http://localhost:8080/done;
# All above mentioned notifications receive
# standard connect() arguments as well as
# play/publish ones. If any arguments are sent
# with GET-style syntax to play & publish
# these are also included.
# Example URL:
# rtmp://localhost/myapp/mystream?a=b&c=d
# record 10 video keyframes (no audio) every 2 minutes
record keyframes;
record_path /tmp/vc;
record_max_frames 10;
record_interval 2m;
# Async notify about an flv recorded
on_record_done http://localhost:8080/record_done;
}
# HLS (experimental)
# HLS requires libavformat & should be configured as a separate
# NGINX module in addition to nginx-rtmp-module:
# ./configure ... --add-module=/path/to/nginx-rtmp-module/hls ...
# For HLS to work please create a directory in tmpfs (/tmp/app here)
# for the fragments. The directory contents is served via HTTP (see
# http{} section in config)
#
# Incoming stream must be in H264/AAC/MP3. For iPhones use baseline H264
# profile (see ffmpeg example).
# This example creates RTMP stream from movie ready for HLS:
#
# ffmpeg -loglevel verbose -re -i movie.avi -vcodec libx264
# -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
# -f flv rtmp://localhost:1935/hls/movie
#
# If you need to transcode live stream use 'exec' feature.
#
application hls {
hls on;
hls_path /tmp/app;
hls_fragment 5s;
}
}
}
# HTTP can be used for accessing RTMP stats
http {
server {
listen 8080;
# This URL provides RTMP statistics in XML
location /stat {
rtmp_stat all;
# Use this stylesheet to view XML as web page
# in browser
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
# XML stylesheet to view RTMP stats.
# Copy stat.xsl wherever you want
# and put the full directory path here
root /path/to/stat.xsl/;
}
location /hls {
# Serve HLS fragments
alias /tmp/app;
}
}
}

12
TODO
View file

@ -1,14 +1,14 @@
- Add per-client audio/video bias to stats;
implement synchronization after frames were dropped
- Auto-pushing pulled stream
- File path checks in record & play modules
(check for '/../', maybe smth else)
- Pull secondary address support
- Binary search in play module
- More Wiki docs
- Auto-relays (multi-worker)
- Binary search in play module
Style:
======
- Move out & merge stream ids from live & cmd modules

9
config
View file

@ -6,14 +6,15 @@ CORE_MODULES="$CORE_MODULES
ngx_rtmp_cmd_module \
ngx_rtmp_access_module \
ngx_rtmp_live_module \
ngx_rtmp_play_module \
ngx_rtmp_flv_module \
ngx_rtmp_mp4_module \
ngx_rtmp_record_module \
ngx_rtmp_netcall_module \
ngx_rtmp_notify_module \
ngx_rtmp_relay_module \
ngx_rtmp_exec_module \
ngx_rtmp_codec_module \
ngx_rtmp_play_module \
ngx_rtmp_auto_push_module \
"
@ -35,6 +36,9 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_rtmp_cmd_module.c \
$ngx_addon_dir/ngx_rtmp_access_module.c \
$ngx_addon_dir/ngx_rtmp_live_module.c \
$ngx_addon_dir/ngx_rtmp_play_module.c \
$ngx_addon_dir/ngx_rtmp_flv_module.c \
$ngx_addon_dir/ngx_rtmp_mp4_module.c \
$ngx_addon_dir/ngx_rtmp_record_module.c \
$ngx_addon_dir/ngx_rtmp_netcall_module.c \
$ngx_addon_dir/ngx_rtmp_notify_module.c \
@ -43,7 +47,6 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_rtmp_bandwidth.c \
$ngx_addon_dir/ngx_rtmp_exec_module.c \
$ngx_addon_dir/ngx_rtmp_codec_module.c \
$ngx_addon_dir/ngx_rtmp_play_module.c \
$ngx_addon_dir/ngx_rtmp_auto_push_module.c \
"
CFLAGS="$CFLAGS -I$ngx_addon_dir"

9
hls/README.md Normal file
View file

@ -0,0 +1,9 @@
# HLS (HTTP Live Streaming) module
This module should be added explicitly when building NGINX:
./configure ... --add-module=/path/to/nginx-rtmp-module/hls ...
## Requirement
The module requires ffmpeg version>= 53.31.100 from ffmpeg (ffmpeg.org)

View file

@ -9,5 +9,5 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_rtmp_hls_module.c \
"
CORE_LIBS="$CORE_LIBS -lavformat"
CORE_LIBS="$CORE_LIBS -lavformat -lavcodec -lavutil"

View file

@ -49,6 +49,8 @@ ngx_rtmp_hls_av_log_callback(void* avcl, int level, const char* fmt,
#define NGX_RTMP_HLS_BUFSIZE (1024*1024)
#define NGX_RTMP_HLS_DIR_ACCESS 0744
typedef struct {
ngx_uint_t flags;
@ -58,6 +60,7 @@ typedef struct {
unsigned opened:1;
unsigned audio:1;
unsigned video:1;
unsigned header_sent:1;
ngx_str_t playlist;
ngx_str_t playlist_bak;
@ -78,6 +81,7 @@ typedef struct {
typedef struct {
ngx_flag_t hls;
ngx_msec_t fraglen;
ngx_msec_t muxdelay;
ngx_msec_t playlen;
size_t nfrags;
ngx_rtmp_hls_ctx_t **ctx;
@ -116,6 +120,14 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
offsetof(ngx_rtmp_hls_app_conf_t, playlen),
NULL },
{ ngx_string("hls_muxdelay"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_msec_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, muxdelay),
NULL },
ngx_null_command
};
@ -262,7 +274,7 @@ ngx_rtmp_hls_init_video(ngx_rtmp_session_t *s)
stream->codec->codec_id = CODEC_ID_H264;
stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codec->pix_fmt = PIX_FMT_YUV420P;
stream->codec->time_base.den = 1;
stream->codec->time_base.den = 25;
stream->codec->time_base.num = 1;
stream->codec->width = 100;
stream->codec->height = 100;
@ -277,6 +289,14 @@ ngx_rtmp_hls_init_video(ngx_rtmp_session_t *s)
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: video stream: %i", ctx->out_vstream);
if (ctx->header_sent) {
if (av_write_trailer(ctx->out_format) < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: av_write_trailer failed");
}
ctx->header_sent = 0;
}
return NGX_OK;
}
@ -287,6 +307,7 @@ ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s)
AVStream *stream;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
enum CodecID cid;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
@ -299,6 +320,13 @@ ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s)
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: adding audio stream");
cid = ngx_rtmp_hls_get_audio_codec(codec_ctx->audio_codec_id);
if (cid == CODEC_ID_NONE) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: no audio");
return NGX_OK;
}
stream = avformat_new_stream(ctx->out_format, NULL);
if (stream == NULL) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
@ -306,17 +334,12 @@ ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s)
return NGX_ERROR;
}
stream->codec->codec_id = ngx_rtmp_hls_get_audio_codec(
codec_ctx->audio_codec_id);
if (stream->codec->codec_id == CODEC_ID_NONE) {
return NGX_OK;
}
stream->codec->codec_id = cid;
stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
stream->codec->sample_fmt = (codec_ctx->sample_size == 1 ?
AV_SAMPLE_FMT_U8 : AV_SAMPLE_FMT_S16);
stream->codec->sample_rate = 48000;/*codec_ctx->sample_rate;*/
stream->codec->bit_rate = 128000;
stream->codec->bit_rate = 2000000;
stream->codec->channels = codec_ctx->audio_channels;
ctx->out_astream = stream->index;
@ -326,6 +349,14 @@ ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s)
"hls: audio stream: %i %iHz",
ctx->out_astream, codec_ctx->sample_rate);
if (ctx->header_sent) {
if (av_write_trailer(ctx->out_format) < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: av_write_trailer failed");
}
ctx->header_sent = 0;
}
return NGX_OK;
}
@ -340,17 +371,31 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
ssize_t n;
ngx_int_t ffrag;
ngx_rtmp_hls_app_conf_t *hacf;
ngx_int_t nretry;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
nretry = 0;
retry:
fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY,
NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: open failed: '%V'",
&ctx->playlist_bak);
/* try to create parent folder */
if (nretry == 0 &&
ngx_create_dir(hacf->path.data, NGX_RTMP_HLS_DIR_ACCESS) !=
NGX_INVALID_FILE)
{
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: creating target folder: '%V'", &hacf->path);
++nretry;
goto retry;
}
return NGX_ERROR;
}
@ -406,8 +451,10 @@ static ngx_int_t
ngx_rtmp_hls_initialize(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
AVOutputFormat *format;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx == NULL || ctx->out_format || ctx->publishing == 0) {
return NGX_OK;
@ -430,6 +477,7 @@ ngx_rtmp_hls_initialize(ngx_rtmp_session_t *s)
return NGX_ERROR;
}
ctx->out_format->oformat = format;
ctx->out_format->max_delay = (int64_t)hacf->muxdelay * AV_TIME_BASE / 1000;
return NGX_ERROR;
}
@ -449,6 +497,10 @@ ngx_rtmp_hls_open_file(ngx_rtmp_session_t *s, u_char *fpath)
return NGX_OK;
}
if (!ctx->video && !ctx->audio) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: open stream file '%s'", fpath);
@ -475,11 +527,14 @@ ngx_rtmp_hls_open_file(ngx_rtmp_session_t *s, u_char *fpath)
}
/* write header */
if (avformat_write_header(ctx->out_format, NULL) < 0) {
if (!ctx->header_sent &&
avformat_write_header(ctx->out_format, NULL) < 0)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: avformat_write_header failed");
return NGX_ERROR;
}
ctx->header_sent = 1;
if (astream) {
astream->codec->extradata = NULL;
@ -651,10 +706,11 @@ ngx_rtmp_hls_close_file(ngx_rtmp_session_t *s)
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
/*
if (av_write_trailer(ctx->out_format) < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: av_write_trailer failed");
}
}*/
avio_flush(ctx->out_format->pb);
@ -878,7 +934,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
/* write to file */
av_init_packet(&packet);
packet.dts = h->timestamp * 90;
packet.dts = h->timestamp * 90L;
packet.pts = packet.dts;
packet.stream_index = ctx->out_astream;
packet.data = buffer;
@ -918,6 +974,7 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
uint32_t len, rlen;
ngx_buf_t out;
static u_char buffer[NGX_RTMP_HLS_BUFSIZE];
int32_t cts;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
@ -953,9 +1010,11 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
}
/* 3 bytes: decoder delay */
if (ngx_rtmp_hls_copy(s, NULL, &p, 3, &in) != NGX_OK) {
if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) {
return NGX_ERROR;
}
cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16)
| (cts & 0x0000FF00);
out.pos = buffer;
out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE;
@ -1027,13 +1086,14 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
}
av_init_packet(&packet);
packet.dts = h->timestamp * 90;
packet.pts = packet.dts;
packet.dts = h->timestamp * 90L;
packet.pts = packet.dts + cts * 90;
packet.stream_index = ctx->out_vstream;
/*
if (ftype == 1) {
packet.flags |= AV_PKT_FLAG_KEY;
}*/
}
packet.data = buffer;
packet.size = out.pos - buffer;
@ -1058,6 +1118,7 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
conf->hls = NGX_CONF_UNSET;
conf->fraglen = NGX_CONF_UNSET;
conf->muxdelay = NGX_CONF_UNSET;
conf->playlen = NGX_CONF_UNSET;
conf->nbuckets = 1024;
@ -1073,6 +1134,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->hls, prev->hls, 0);
ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);
ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700);
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
ngx_conf_merge_str_value(conf->path, prev->path, "");
conf->ctx = ngx_pcalloc(cf->pool,

View file

@ -178,6 +178,8 @@ typedef struct {
ngx_str_t *addr_text;
int connected;
ngx_event_t *posted_dry_events;
/* client buffer time in msec */
uint32_t buflen;
@ -378,6 +380,27 @@ void * ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n);
(((u_char*)ngx_rtmp_rmemcpy(dst, src, n)) + (n))
static inline uint16_t
ngx_rtmp_r16(uint16_t n)
{
return (n << 8) | (n >> 8);
}
static inline uint32_t
ngx_rtmp_r32(uint32_t n)
{
return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24);
}
static inline uint64_t
ngx_rtmp_r64(uint64_t n)
{
return (uint64_t) ngx_rtmp_r32(n) << 32 | ngx_rtmp_r32(n >> 32);
}
/* Receiving messages */
ngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
ngx_rtmp_header_t *h, ngx_chain_t *in);
@ -472,6 +495,10 @@ ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,
ngx_rtmp_amf_elt_t *elts, size_t nelts);
/* AMF status sender */
ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code,
char* level, char *desc);
/* Frame types */
#define NGX_RTMP_VIDEO_KEY_FRAME 1

View file

@ -3,15 +3,12 @@
*/
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rtmp_streams.h"
#define NGX_RTMP_FMS_VERSION "FMS/3,0,1,123"
#define NGX_RTMP_CAPABILITIES 31
#define NGX_RTMP_CMD_CSID_AMF_INI 3
#define NGX_RTMP_CMD_CSID_AMF 5
#define NGX_RTMP_CMD_MSID 1
ngx_rtmp_connect_pt ngx_rtmp_connect;
ngx_rtmp_create_stream_pt ngx_rtmp_create_stream;
@ -494,14 +491,14 @@ ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
static ngx_rtmp_amf_elt_t out_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
"NetStream.Publish.Start", 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
"status", 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
"NetStream.Publish.Start", 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("description"),
"Publish succeeded.", 0 },

View file

@ -200,6 +200,8 @@ ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
header = NULL;
pheader = NULL;
version = NULL;
if (h->type == NGX_RTMP_MSG_AUDIO) {
if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
header = &ctx->aac_header;

View file

@ -571,7 +571,7 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ls->ipv6only = 1;
} else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
ls->ipv6only = 2;
ls->ipv6only = 0;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

View file

@ -4,7 +4,10 @@
#include "ngx_rtmp_cmd_module.h"
#include <stdlib.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#ifdef NGX_LINUX
#include <unistd.h>
@ -317,7 +320,7 @@ dollar:
static ngx_int_t
ngx_rtmp_exec_run(ngx_rtmp_session_t *s, size_t n)
{
#ifdef NGX_LINUX
#ifndef NGX_WIN32
ngx_rtmp_exec_app_conf_t *eacf;
ngx_rtmp_exec_ctx_t *ctx;
int pid;
@ -413,7 +416,7 @@ ngx_rtmp_exec_run(ngx_rtmp_session_t *s, size_t n)
&ec->cmd, (ngx_uint_t)pid);
break;
}
#endif /* NGX_LINUX */
#endif /* NGX_WIN32 */
return NGX_OK;
}

642
ngx_rtmp_flv_module.c Normal file
View file

@ -0,0 +1,642 @@
/*
* Copyright (c) 2012 Roman Arutyunyan
*/
#include "ngx_rtmp_play_module.h"
#include "ngx_rtmp_codec_module.h"
#include "ngx_rtmp_streams.h"
static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);
static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);
static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,
ngx_file_t *f, ngx_int_t timestamp);
static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f);
static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f,
ngx_uint_t offset);
static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f);
typedef struct {
ngx_uint_t nelts;
ngx_uint_t offset;
} ngx_rtmp_flv_index_t;
typedef struct {
ngx_int_t offset;
ngx_int_t start_timestamp;
ngx_event_t write_evt;
uint32_t last_audio;
uint32_t last_video;
ngx_uint_t msg_mask;
uint32_t epoch;
unsigned meta_read:1;
ngx_rtmp_flv_index_t filepositions;
ngx_rtmp_flv_index_t times;
} ngx_rtmp_flv_ctx_t;
#define NGX_RTMP_FLV_BUFFER (1024*1024)
#define NGX_RTMP_FLV_DEFAULT_BUFLEN 1000
#define NGX_RTMP_FLV_TAG_HEADER 11
#define NGX_RTMP_FLV_DATA_OFFSET 13
static u_char ngx_rtmp_flv_buffer[
NGX_RTMP_FLV_BUFFER];
static u_char ngx_rtmp_flv_header[
NGX_RTMP_FLV_TAG_HEADER];
static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_flv_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create app configuration */
NULL /* merge app configuration */
};
ngx_module_t ngx_rtmp_flv_module = {
NGX_MODULE_V1,
&ngx_rtmp_flv_module_ctx, /* module context */
NULL, /* module directives */
NGX_RTMP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t
ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)
{
uint32_t nelts;
ngx_buf_t *b;
/* we have AMF array pointed by context;
* need to extract its size (4 bytes) &
* save offset of actual array data */
b = ctx->link->buf;
if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
return NGX_ERROR;
}
ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
idx->nelts = nelts;
idx->offset = ctx->offset + 4;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
{
ngx_rtmp_flv_ctx_t *ctx;
static ngx_rtmp_amf_ctx_t filepositions_ctx;
static ngx_rtmp_amf_ctx_t times_ctx;
static ngx_rtmp_amf_elt_t in_keyframes[] = {
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
ngx_string("filepositions"),
&filepositions_ctx, 0 },
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
ngx_string("times"),
&times_ctx, 0 }
};
static ngx_rtmp_amf_elt_t in_inf[] = {
{ NGX_RTMP_AMF_OBJECT,
ngx_string("keyframes"),
in_keyframes, sizeof(in_keyframes) }
};
static ngx_rtmp_amf_elt_t in_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL || in == NULL) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: init index");
ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
ngx_memzero(&times_ctx, sizeof(times_ctx));
if (ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0])))
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: init index error");
return NGX_OK;
}
if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,
&ctx->filepositions)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: failed to init filepositions");
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: filepositions nelts=%ui offset=%ui",
ctx->filepositions.nelts, ctx->filepositions.offset);
if (times_ctx.link && ngx_rtmp_flv_fill_index(&times_ctx,
&ctx->times)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: failed to init times");
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: times nelts=%ui offset=%ui",
ctx->times.nelts, ctx->times.offset);
return NGX_OK;
}
static double
ngx_rtmp_flv_index_value(void *src)
{
double v;
ngx_rtmp_rmemcpy(&v, src, 8);
return v;
}
static ngx_int_t
ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,
ngx_int_t timestamp)
{
ngx_rtmp_flv_ctx_t *ctx;
ssize_t n, size;
ngx_uint_t offset, index, ret, nelts;
double v;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
goto rewind;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: lookup index start timestamp=%i",
timestamp);
if (ctx->meta_read == 0) {
ngx_rtmp_flv_read_meta(s, f);
ctx->meta_read = 1;
}
if (timestamp <= 0 || ctx->filepositions.nelts == 0
|| ctx->times.nelts == 0)
{
goto rewind;
}
/* read index table from file given offset */
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
ctx->times.offset;
/* index should fit in the buffer */
nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);
size = nelts * 9;
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);
if (n != size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read times index");
goto rewind;
}
/*TODO: implement binary search */
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: lookup times nelts=%ui", nelts);
for (index = 0; index < nelts - 1; ++index) {
v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +
index * 9 + 1) * 1000;
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: lookup times index=%ui value=%ui",
index, (ngx_uint_t) v);
if (timestamp < v) {
break;
}
}
if (index >= ctx->filepositions.nelts) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: index out of bounds: %ui>=%ui",
index, ctx->filepositions.nelts);
goto rewind;
}
/* take value from filepositions */
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
ctx->filepositions.offset + index * 9;
n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);
if (n != 8) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read filepositions index");
goto rewind;
}
ret = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: lookup index timestamp=%i offset=%ui",
timestamp, ret);
return ret;
rewind:
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: lookup index timestamp=%i offset=begin",
timestamp);
return NGX_RTMP_FLV_DATA_OFFSET;
}
static void
ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)
{
ngx_rtmp_flv_ctx_t *ctx;
ssize_t n;
ngx_rtmp_header_t h;
ngx_chain_t *out, in;
ngx_buf_t in_buf;
ngx_rtmp_core_srv_conf_t *cscf;
uint32_t size;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: read meta");
/* read tag header */
n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),
NGX_RTMP_FLV_DATA_OFFSET);
if (n != sizeof(ngx_rtmp_flv_header)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read metadata tag header");
return;
}
if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: first tag is not metadata, giving up");
return;
}
ngx_memzero(&h, sizeof(h));
h.type = NGX_RTMP_MSG_AMF_META;
h.msid = NGX_RTMP_LIVE_MSID;
h.csid = NGX_RTMP_LIVE_CSID_META;
size = 0;
ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: metadata size=%D", size);
if (size > sizeof(ngx_rtmp_flv_buffer)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: too big metadata");
return;
}
/* read metadata */
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
sizeof(ngx_rtmp_flv_header) +
NGX_RTMP_FLV_DATA_OFFSET);
if (n != (ssize_t) size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read metadata");
return;
}
/* prepare input chain */
ngx_memzero(&in, sizeof(in));
ngx_memzero(&in_buf, sizeof(in_buf));
in.buf = &in_buf;
in_buf.pos = ngx_rtmp_flv_buffer;
in_buf.last = ngx_rtmp_flv_buffer + size;
ngx_rtmp_flv_init_index(s, &in);
/* output chain */
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
ngx_rtmp_prepare_message(s, &h, NULL, out);
ngx_rtmp_send_message(s, out, 0);
ngx_rtmp_free_shared_chain(cscf, out);
}
static ngx_int_t
ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f)
{
ngx_rtmp_flv_ctx_t *ctx;
uint32_t last_timestamp;
ngx_rtmp_header_t h, lh;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *out, in;
ngx_buf_t in_buf;
ngx_int_t rc;
ssize_t n;
uint32_t buflen, end_timestamp, size;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
return NGX_ERROR;
}
if (ctx->offset == -1) {
ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
ctx->start_timestamp);
ctx->start_timestamp = -1; /* set later from actual timestamp */
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: read tag at offset=%i", ctx->offset);
/* read tag header */
n = ngx_read_file(f, ngx_rtmp_flv_header,
sizeof(ngx_rtmp_flv_header), ctx->offset);
if (n != sizeof(ngx_rtmp_flv_header)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read flv tag header");
return NGX_DONE;
}
/* parse header fields */
ngx_memzero(&h, sizeof(h));
h.msid = NGX_RTMP_LIVE_MSID;
h.type = ngx_rtmp_flv_header[0];
size = 0;
ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);
((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];
ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);
last_timestamp = 0;
switch (h.type) {
case NGX_RTMP_MSG_AUDIO:
h.csid = NGX_RTMP_CSID_AUDIO;
last_timestamp = ctx->last_audio;
ctx->last_audio = h.timestamp;
break;
case NGX_RTMP_MSG_VIDEO:
h.csid = NGX_RTMP_CSID_VIDEO;
last_timestamp = ctx->last_video;
ctx->last_video = h.timestamp;
break;
default:
return NGX_OK;
}
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: read tag type=%i size=%uD timestamp=%uD "
"last_timestamp=%uD",
(ngx_int_t) h.type,size, h.timestamp, last_timestamp);
lh = h;
lh.timestamp = last_timestamp;
if (size > sizeof(ngx_rtmp_flv_buffer)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: too big message: %D>%uz", size,
sizeof(ngx_rtmp_flv_buffer));
goto next;
}
/* read tag body */
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
ctx->offset - size - 4);
if (n != (ssize_t) size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"flv: could not read flv tag");
return NGX_ERROR;
}
/* prepare input chain */
ngx_memzero(&in, sizeof(in));
ngx_memzero(&in_buf, sizeof(in_buf));
in.buf = &in_buf;
in_buf.pos = ngx_rtmp_flv_buffer;
in_buf.last = ngx_rtmp_flv_buffer + size;
/* output chain */
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
&lh : NULL, out);
rc = ngx_rtmp_send_message(s, out, 0);
ngx_rtmp_free_shared_chain(cscf, out);
if (rc == NGX_AGAIN) {
return NGX_AGAIN;
}
if (rc != NGX_OK) {
return NGX_ERROR;
}
ctx->msg_mask |= (1 << h.type);
next:
if (ctx->start_timestamp == -1) {
ctx->start_timestamp = h.timestamp;
ctx->epoch = ngx_current_msec;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: start_timestamp=%i", ctx->start_timestamp);
return NGX_OK;
}
buflen = (s->buflen ? s->buflen : NGX_RTMP_FLV_DEFAULT_BUFLEN);
end_timestamp = (ngx_current_msec - ctx->epoch) +
ctx->start_timestamp + buflen;
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
h.timestamp > end_timestamp ? "schedule" : "advance",
h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
h.timestamp, end_timestamp, (ngx_int_t) buflen);
/* too much data sent; schedule timeout */
if (h.timestamp > end_timestamp) {
return h.timestamp - end_timestamp;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f)
{
ngx_rtmp_flv_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);
}
ngx_memzero(ctx, sizeof(*ctx));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)
{
ngx_rtmp_flv_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: start timestamp=%ui", timestamp);
ctx->start_timestamp = timestamp;
ctx->offset = -1;
ctx->msg_mask = 0;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)
{
ngx_rtmp_flv_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"flv: stop");
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_play_main_conf_t *pmcf;
ngx_rtmp_play_fmt_t **pfmt, *fmt;
pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
pfmt = ngx_array_push(&pmcf->fmts);
if (pfmt == NULL) {
return NGX_ERROR;
}
fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
if (fmt == NULL) {
return NGX_ERROR;
}
*pfmt = fmt;
ngx_str_set(&fmt->name, "flv-format");
ngx_str_null(&fmt->pfx); /* default fmt */
ngx_str_set(&fmt->sfx, ".flv");
fmt->init = ngx_rtmp_flv_init;
fmt->start = ngx_rtmp_flv_start;
fmt->stop = ngx_rtmp_flv_stop;
fmt->send = ngx_rtmp_flv_send;
return NGX_OK;
}

View file

@ -537,6 +537,8 @@ ngx_rtmp_send(ngx_event_t *wev)
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
}

View file

@ -10,19 +10,13 @@
#include "ngx_rtmp.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rtmp_bandwidth.h"
#include "ngx_rtmp_streams.h"
/* session flags */
#define NGX_RTMP_LIVE_PUBLISHING 0x01
/* Chunk stream ids for output */
#define NGX_RTMP_LIVE_CSID_META 5
#define NGX_RTMP_LIVE_CSID_AUDIO 6
#define NGX_RTMP_LIVE_CSID_VIDEO 7
#define NGX_RTMP_LIVE_MSID 1
typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t;
typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t;

2299
ngx_rtmp_mp4_module.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -431,8 +431,8 @@ ngx_rtmp_notify_save_name_args(ngx_rtmp_session_t *s,
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_notify_module);
}
ngx_memcpy(ctx->name, name, sizeof(name));
ngx_memcpy(ctx->args, args, sizeof(args));
ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME);
ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS);
}

View file

@ -3,8 +3,9 @@
*/
#include "ngx_rtmp_play_module.h"
#include "ngx_rtmp_cmd_module.h"
#include "ngx_rtmp_live_module.h"
#include "ngx_rtmp_streams.h"
static ngx_rtmp_play_pt next_play;
@ -13,55 +14,16 @@ static ngx_rtmp_seek_pt next_seek;
static ngx_rtmp_pause_pt next_pause;
static void *ngx_rtmp_play_create_main_conf(ngx_conf_t *cf);
static ngx_int_t ngx_rtmp_play_postconfiguration(ngx_conf_t *cf);
static void * ngx_rtmp_play_create_app_conf(ngx_conf_t *cf);
static char * ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static void ngx_rtmp_play_send(ngx_event_t *e);
static void ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s);
static ngx_int_t ngx_rtmp_play_start(ngx_rtmp_session_t *s, ngx_int_t offset);
void *parent, void *child);
static ngx_int_t ngx_rtmp_play_init(ngx_rtmp_session_t *s);
static ngx_int_t ngx_rtmp_play_done(ngx_rtmp_session_t *s);
static ngx_int_t ngx_rtmp_play_start(ngx_rtmp_session_t *s, double timestamp);
static ngx_int_t ngx_rtmp_play_stop(ngx_rtmp_session_t *s);
static ngx_int_t ngx_rtmp_play_timestamp_to_offset(ngx_rtmp_session_t *s,
ngx_int_t timestamp);
typedef struct {
ngx_str_t root;
} ngx_rtmp_play_app_conf_t;
typedef struct {
ngx_uint_t nelts;
ngx_uint_t offset;
} ngx_rtmp_play_index_t;
typedef struct {
ngx_file_t file;
ngx_int_t offset;
ngx_int_t start_timestamp;
ngx_event_t write_evt;
uint32_t last_audio;
uint32_t last_video;
ngx_uint_t msg_mask;
uint32_t epoch;
unsigned meta_read:1;
ngx_rtmp_play_index_t filepositions;
ngx_rtmp_play_index_t times;
} ngx_rtmp_play_ctx_t;
#define NGX_RTMP_PLAY_BUFFER (1024*1024)
#define NGX_RTMP_PLAY_DEFAULT_BUFLEN 1000
#define NGX_RTMP_PLAY_TAG_HEADER 11
#define NGX_RTMP_PLAY_DATA_OFFSET 13
static u_char ngx_rtmp_play_buffer[
NGX_RTMP_PLAY_BUFFER];
static u_char ngx_rtmp_play_header[
NGX_RTMP_PLAY_TAG_HEADER];
static void ngx_rtmp_play_send(ngx_event_t *e);
static ngx_command_t ngx_rtmp_play_commands[] = {
@ -80,7 +42,7 @@ static ngx_command_t ngx_rtmp_play_commands[] = {
static ngx_rtmp_module_t ngx_rtmp_play_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_play_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
ngx_rtmp_play_create_main_conf, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
@ -105,12 +67,35 @@ ngx_module_t ngx_rtmp_play_module = {
};
static void *
ngx_rtmp_play_create_main_conf(ngx_conf_t *cf)
{
ngx_rtmp_play_main_conf_t *pmcf;
pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_main_conf_t));
if (pmcf == NULL) {
return NULL;
}
if (ngx_array_init(&pmcf->fmts, cf->pool, 1,
sizeof(ngx_rtmp_play_fmt_t *))
!= NGX_OK)
{
return NULL;
}
return pmcf;
}
static void *
ngx_rtmp_play_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_play_app_conf_t *pacf;
pacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_app_conf_t));
if (pacf == NULL) {
return NULL;
}
@ -131,452 +116,123 @@ ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
}
static ngx_int_t
ngx_rtmp_play_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_play_index_t *idx)
static void
ngx_rtmp_play_send(ngx_event_t *e)
{
uint32_t nelts;
ngx_buf_t *b;
ngx_rtmp_session_t *s = e->data;
ngx_rtmp_play_ctx_t *ctx;
ngx_int_t rc;
/* we have AMF array pointed by context;
* need to extract its size (4 bytes) &
* save offset of actual array data */
b = ctx->link->buf;
if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL || ctx->fmt == NULL || ctx->fmt->send == NULL) {
return;
}
rc = ctx->fmt->send(s, &ctx->file);
if (rc > 0) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: send schedule %i", rc);
ngx_add_timer(e, rc);
return;
}
if (rc == NGX_AGAIN) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: send buffer full");
ngx_post_event(e, &s->posted_dry_events);
return;
}
if (rc == NGX_OK) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: send restart");
ngx_post_event(e, &ngx_posted_events);
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: send done");
ngx_rtmp_send_user_stream_eof(s, NGX_RTMP_MSID);
ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stopped");
}
static ngx_int_t
ngx_rtmp_play_init(ngx_rtmp_session_t *s)
{
ngx_rtmp_play_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
idx->nelts = nelts;
idx->offset = ctx->offset + 4;
if (ctx->fmt && ctx->fmt->init &&
ctx->fmt->init(s, &ctx->file) != NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_play_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
ngx_rtmp_play_done(ngx_rtmp_session_t *s)
{
ngx_rtmp_play_ctx_t *ctx;
static ngx_rtmp_amf_ctx_t filepositions_ctx;
static ngx_rtmp_amf_ctx_t times_ctx;
static ngx_rtmp_amf_elt_t in_keyframes[] = {
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
ngx_string("filepositions"),
&filepositions_ctx, 0 },
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
ngx_string("times"),
&times_ctx, 0 }
};
static ngx_rtmp_amf_elt_t in_inf[] = {
{ NGX_RTMP_AMF_OBJECT,
ngx_string("keyframes"),
in_keyframes, sizeof(in_keyframes) }
};
static ngx_rtmp_amf_elt_t in_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
in_inf, sizeof(in_inf) },
};
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL || in == NULL) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: init index");
ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
ngx_memzero(&times_ctx, sizeof(times_ctx));
if (ngx_rtmp_receive_amf(s, in, in_elts,
sizeof(in_elts) / sizeof(in_elts[0])))
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: init index error");
return NGX_OK;
}
if (filepositions_ctx.link && ngx_rtmp_play_fill_index(&filepositions_ctx,
&ctx->filepositions)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: failed to init filepositions");
if (ctx == NULL) {
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: filepositions nelts=%ui offset=%ui",
ctx->filepositions.nelts, ctx->filepositions.offset);
if (times_ctx.link && ngx_rtmp_play_fill_index(&times_ctx,
&ctx->times)
!= NGX_OK)
if (ctx->fmt && ctx->fmt->done &&
ctx->fmt->done(s, &ctx->file) != NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: failed to init times");
return NGX_ERROR;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: times nelts=%ui offset=%ui",
ctx->times.nelts, ctx->times.offset);
return NGX_OK;
}
static double
ngx_rtmp_play_index_value(void *src)
{
double v;
ngx_rtmp_rmemcpy(&v, src, 8);
return v;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_play_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_int_t timestamp)
ngx_rtmp_play_start(ngx_rtmp_session_t *s, double timestamp)
{
ngx_rtmp_play_ctx_t *ctx;
ssize_t n, size;
ngx_uint_t offset, index, ret, nelts;
double v;
ngx_uint_t ts;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
goto rewind;
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: lookup index start timestamp=%i",
timestamp);
if (ctx->meta_read == 0) {
ngx_rtmp_play_read_meta(s);
ctx->meta_read = 1;
}
if (timestamp <= 0 || ctx->filepositions.nelts == 0
|| ctx->times.nelts == 0)
{
goto rewind;
}
/* read index table from file given offset */
offset = NGX_RTMP_PLAY_DATA_OFFSET + NGX_RTMP_PLAY_TAG_HEADER
+ ctx->times.offset;
/* index should fit in the buffer */
nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_play_buffer) / 9);
size = nelts * 9;
n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, offset);
if (n != size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read times index");
goto rewind;
}
/*TODO: implement binary search */
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: lookup times nelts=%ui", nelts);
for (index = 0; index < nelts - 1; ++index) {
v = ngx_rtmp_play_index_value(ngx_rtmp_play_buffer
+ index * 9 + 1) * 1000;
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: lookup times index=%ui value=%ui",
index, (ngx_uint_t) v);
if (timestamp < v) {
break;
}
}
if (index >= ctx->filepositions.nelts) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: index out of bounds: %ui>=%ui",
index, ctx->filepositions.nelts);
goto rewind;
}
/* take value from filepositions */
offset = NGX_RTMP_PLAY_DATA_OFFSET + NGX_RTMP_PLAY_TAG_HEADER
+ ctx->filepositions.offset + index * 9;
n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, 8, offset + 1);
if (n != 8) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read filepositions index");
goto rewind;
}
ret = ngx_rtmp_play_index_value(ngx_rtmp_play_buffer);
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: lookup index timestamp=%i offset=%ui",
timestamp, ret);
return ret;
rewind:
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: lookup index timestamp=%i offset=begin",
timestamp);
return NGX_RTMP_PLAY_DATA_OFFSET;
}
static void
ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s)
{
ngx_rtmp_play_ctx_t *ctx;
ssize_t n;
ngx_rtmp_header_t h;
ngx_chain_t *out, in;
ngx_buf_t in_buf;
ngx_rtmp_core_srv_conf_t *cscf;
uint32_t size;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
return;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: read meta");
/* read tag header */
n = ngx_read_file(&ctx->file, ngx_rtmp_play_header,
sizeof(ngx_rtmp_play_header), NGX_RTMP_PLAY_DATA_OFFSET);
if (n != sizeof(ngx_rtmp_play_header)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read metadata tag header");
return;
}
if (ngx_rtmp_play_header[0] != NGX_RTMP_MSG_AMF_META) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: first tag is not metadata, giving up");
return;
}
ngx_memzero(&h, sizeof(h));
h.type = NGX_RTMP_MSG_AMF_META;
h.msid = NGX_RTMP_LIVE_MSID;
h.csid = NGX_RTMP_LIVE_CSID_META;
size = 0;
ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: metadata size=%D", size);
if (size > sizeof(ngx_rtmp_play_buffer)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: too big metadata");
return;
}
/* read metadata */
n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer,
size, sizeof(ngx_rtmp_play_header) +
NGX_RTMP_PLAY_DATA_OFFSET);
if (n != (ssize_t) size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read metadata");
return;
}
/* prepare input chain */
ngx_memzero(&in, sizeof(in));
ngx_memzero(&in_buf, sizeof(in_buf));
in.buf = &in_buf;
in_buf.pos = ngx_rtmp_play_buffer;
in_buf.last = ngx_rtmp_play_buffer + size;
ngx_rtmp_play_init_index(s, &in);
/* output chain */
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
ngx_rtmp_prepare_message(s, &h, NULL, out);
ngx_rtmp_send_message(s, out, 0);
ngx_rtmp_free_shared_chain(cscf, out);
}
static void
ngx_rtmp_play_send(ngx_event_t *e)
{
ngx_rtmp_session_t *s;
ngx_rtmp_play_ctx_t *ctx;
uint32_t last_timestamp;
ngx_rtmp_header_t h, lh;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *out, in;
ngx_buf_t in_buf;
ssize_t n;
uint32_t buflen, end_timestamp, size;
s = e->data;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
return;
}
if (ctx->offset == -1) {
ctx->offset = ngx_rtmp_play_timestamp_to_offset(s,
ctx->start_timestamp);
ctx->start_timestamp = -1; /* set later from actual timestamp */
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: read tag at offset=%i", ctx->offset);
/* read tag header */
n = ngx_read_file(&ctx->file, ngx_rtmp_play_header,
sizeof(ngx_rtmp_play_header), ctx->offset);
if (n != sizeof(ngx_rtmp_play_header)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read flv tag header");
ngx_rtmp_send_user_stream_eof(s, 1);
return;
}
/* parse header fields */
ngx_memzero(&h, sizeof(h));
h.msid = NGX_RTMP_LIVE_MSID;
h.type = ngx_rtmp_play_header[0];
size = 0;
ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);
ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_play_header + 4, 3);
((u_char *) &h.timestamp)[3] = ngx_rtmp_play_header[7];
ctx->offset += (sizeof(ngx_rtmp_play_header) + size + 4);
last_timestamp = 0;
switch (h.type) {
case NGX_RTMP_MSG_AUDIO:
h.csid = NGX_RTMP_LIVE_CSID_AUDIO;
last_timestamp = ctx->last_audio;
ctx->last_audio = h.timestamp;
break;
case NGX_RTMP_MSG_VIDEO:
h.csid = NGX_RTMP_LIVE_CSID_VIDEO;
last_timestamp = ctx->last_video;
ctx->last_video = h.timestamp;
break;
default:
goto skip;
}
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: read tag type=%i size=%uD timestamp=%uD "
"last_timestamp=%uD",
(ngx_int_t) h.type,size, h.timestamp, last_timestamp);
lh = h;
lh.timestamp = last_timestamp;
if (size > sizeof(ngx_rtmp_play_buffer)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: too big message: %D>%uz", size,
sizeof(ngx_rtmp_play_buffer));
goto next;
}
/* read tag body */
n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size,
ctx->offset - size - 4);
if (n != (ssize_t) size) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: could not read flv tag");
return;
}
/* prepare input chain */
ngx_memzero(&in, sizeof(in));
ngx_memzero(&in_buf, sizeof(in_buf));
in.buf = &in_buf;
in_buf.pos = ngx_rtmp_play_buffer;
in_buf.last = ngx_rtmp_play_buffer + size;
/* output chain */
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
&lh : NULL, out);
ngx_rtmp_send_message(s, out, 0); /* TODO: priority */
ngx_rtmp_free_shared_chain(cscf, out);
ctx->msg_mask |= (1 << h.type);
next:
if (ctx->start_timestamp == -1) {
ctx->start_timestamp = h.timestamp;
ctx->epoch = ngx_current_msec;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: start_timestamp=%i", ctx->start_timestamp);
goto skip;
}
buflen = (s->buflen ? s->buflen : NGX_RTMP_PLAY_DEFAULT_BUFLEN);
end_timestamp = (ngx_current_msec - ctx->epoch) +
ctx->start_timestamp + buflen;
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
h.timestamp > end_timestamp ? "schedule" : "advance",
h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
h.timestamp, end_timestamp, (ngx_int_t) buflen);
/* too much data sent; schedule timeout */
if (h.timestamp > end_timestamp) {
ngx_add_timer(e, h.timestamp - end_timestamp);
return;
}
skip:
ngx_post_event(e, &ngx_posted_events);
}
static ngx_int_t
ngx_rtmp_play_start(ngx_rtmp_session_t *s, ngx_int_t timestamp)
{
ngx_rtmp_play_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: start timestamp=%i", timestamp);
ngx_rtmp_play_stop(s);
ctx->start_timestamp = timestamp;
ctx->offset = -1;
ctx->msg_mask = 0;
ts = (timestamp > 0 ? (ngx_uint_t) timestamp : 0);
ngx_post_event((&ctx->write_evt), &ngx_posted_events)
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: start timestamp=%ui", ts);
if (ctx->fmt && ctx->fmt->start &&
ctx->fmt->start(s, &ctx->file, ts) != NGX_OK)
{
return NGX_ERROR;
}
ngx_post_event((&ctx->send_evt), &ngx_posted_events);
return NGX_OK;
}
@ -588,19 +244,26 @@ ngx_rtmp_play_stop(ngx_rtmp_session_t *s)
ngx_rtmp_play_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
return NGX_OK;
return NGX_ERROR;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: stop");
"play: stop");
if (ctx->write_evt.timer_set) {
ngx_del_timer(&ctx->write_evt);
if (ctx->send_evt.timer_set) {
ngx_del_timer(&ctx->send_evt);
}
if (ctx->write_evt.prev) {
ngx_delete_posted_event((&ctx->write_evt));
if (ctx->send_evt.prev) {
ngx_delete_posted_event((&ctx->send_evt));
}
if (ctx->fmt && ctx->fmt->stop &&
ctx->fmt->stop(s, &ctx->file) != NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
@ -613,15 +276,18 @@ ngx_rtmp_play_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
ngx_rtmp_play_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL) {
goto next;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: close_stream");
"play: close_stream");
ngx_rtmp_play_stop(s);
ngx_rtmp_play_done(s);
if (ctx->file.fd != NGX_INVALID_FILE) {
ngx_close_file(ctx->file.fd);
ctx->file.fd = NGX_INVALID_FILE;
@ -643,7 +309,7 @@ ngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v)
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: seek timestamp=%i", (ngx_int_t) v->offset);
"play: seek offset=%f", v->offset);
ngx_rtmp_play_start(s, v->offset);
@ -658,13 +324,14 @@ ngx_rtmp_play_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)
ngx_rtmp_play_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) {
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: pause=%i timestamp=%i",
(ngx_int_t) v->pause, (ngx_int_t) v->position);
"play: pause=%i timestamp=%f",
(ngx_int_t) v->pause, v->position);
if (v->pause) {
ngx_rtmp_play_stop(s);
@ -680,20 +347,28 @@ next:
static ngx_int_t
ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
{
ngx_rtmp_play_main_conf_t *pmcf;
ngx_rtmp_play_app_conf_t *pacf;
ngx_rtmp_play_ctx_t *ctx;
u_char *p;
ngx_event_t *e;
size_t len, slen;
ngx_rtmp_play_fmt_t *fmt, **pfmt;
ngx_str_t *pfx, *sfx;
ngx_str_t name;
ngx_uint_t n;
static ngx_str_t nosfx;
static u_char path[NGX_MAX_PATH];
pmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_play_module);
pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);
if (pacf == NULL || pacf->root.len == 0) {
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: play name='%s' timestamp=%i",
"play: play name='%s' timestamp=%i",
v->name, (ngx_int_t) v->start);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
@ -706,52 +381,107 @@ ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
/* check for double-dot in v->name;
* we should not move out of play directory */
p = v->name;
while (*p) {
if (*p == '.' && *(p + 1) == '.') {
for (p = v->name; *p; ++p) {
if (ngx_path_separator(p[0]) &&
p[1] == '.' && p[2] == '.' &&
ngx_path_separator(p[3]))
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: bad name '%s'", v->name);
return NGX_ERROR;
}
++p;
}
if (ctx == NULL) {
ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_play_ctx_t));
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_play_module);
}
ngx_memzero(ctx, sizeof(*ctx));
ctx->file.log = s->connection->log;
/* make file path */
len = ngx_strlen(v->name);
slen = sizeof(".flv") - 1;
p = ngx_snprintf(path, sizeof(path), "%V/%s%s", &pacf->root, v->name,
len > slen && ngx_strncasecmp((u_char *) ".flv",
v->name + len - slen, slen) == 0 ? "" : ".flv");
*p = 0;
name.len = ngx_strlen(v->name);
name.data = v->name;
/* open file */
ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN,
NGX_FILE_DEFAULT_ACCESS);
if (ctx->file.fd == NGX_INVALID_FILE) {
pfmt = pmcf->fmts.elts;
for (n = 0; n < pmcf->fmts.nelts; ++n, ++pfmt) {
fmt = *pfmt;
pfx = &fmt->pfx;
sfx = &fmt->sfx;
if (pfx->len == 0 && ctx->fmt == NULL) {
ctx->fmt = fmt;
}
if (pfx->len && name.len >= pfx->len &&
ngx_strncasecmp(pfx->data, name.data, pfx->len) == 0)
{
name.data += pfx->len;
name.len -= pfx->len;
ctx->fmt = fmt;
break;
}
if (name.len >= sfx->len &&
ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len,
sfx->len) == 0)
{
ctx->fmt = fmt;
}
}
if (ctx->fmt == NULL) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: error opening file %s", path);
"play: fmt not found");
goto next;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: opened file '%s'", path);
"play: fmt found: '%V'", &ctx->fmt->name);
e = &ctx->write_evt;
sfx = &ctx->fmt->sfx;
if (name.len >= sfx->len &&
ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len,
sfx->len)
== 0)
{
sfx = &nosfx;
}
p = ngx_snprintf(path, sizeof(path), "%V/%V%V", &pacf->root, &name, sfx);
*p = 0;
ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN,
NGX_FILE_DEFAULT_ACCESS);
if (ctx->file.fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"play: error opening file '%s'", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"play: opened file '%s'", path);
e = &ctx->send_evt;
e->data = s;
e->handler = ngx_rtmp_play_send;
e->log = s->connection->log;
ngx_rtmp_send_user_recorded(s, 1);
ngx_rtmp_play_start(s, v->start);
if (ngx_rtmp_play_init(s) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_rtmp_play_start(s, v->start) != NGX_OK) {
return NGX_ERROR;
}
next:
return next_play(s, v);

58
ngx_rtmp_play_module.h Normal file
View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2012 Roman Arutyunyan
*/
#ifndef _NGX_RTMP_PLAY_H_INCLUDED_
#define _NGX_RTMP_PLAY_H_INCLUDED_
#include "ngx_rtmp.h"
typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s,
ngx_file_t *f);
typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s,
ngx_file_t *f);
typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s,
ngx_file_t *f, ngx_uint_t offs);
typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s,
ngx_file_t *f);
typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s,
ngx_file_t *f);
typedef struct {
ngx_str_t name;
ngx_str_t pfx;
ngx_str_t sfx;
ngx_rtmp_play_init_pt init;
ngx_rtmp_play_done_pt done;
ngx_rtmp_play_start_pt start;
ngx_rtmp_play_stop_pt stop;
ngx_rtmp_play_send_pt send;
} ngx_rtmp_play_fmt_t;
typedef struct {
ngx_file_t file;
ngx_rtmp_play_fmt_t *fmt;
ngx_event_t send_evt;
} ngx_rtmp_play_ctx_t;
typedef struct {
ngx_str_t root;
} ngx_rtmp_play_app_conf_t;
typedef struct {
ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */
} ngx_rtmp_play_main_conf_t;
extern ngx_module_t ngx_rtmp_play_module;
#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */

View file

@ -301,6 +301,7 @@ ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
ngx_rtmp_record_app_conf_t *racf;
ngx_rtmp_record_ctx_t *ctx;
u_char *p;
if (s->auto_pushed) {
goto next;
@ -327,6 +328,17 @@ ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));
/* terminate name on /../ */
for (p = ctx->name; *p; ++p) {
if (ngx_path_separator(p[0]) &&
p[1] == '.' && p[2] == '.' &&
ngx_path_separator(p[3]))
{
*p = 0;
break;
}
}
if (ngx_rtmp_record_open(s) != NGX_OK) {
return NGX_ERROR;
}

View file

@ -5,6 +5,7 @@
#include "ngx_rtmp.h"
#include "ngx_rtmp_amf.h"
#include "ngx_rtmp_streams.h"
#define NGX_RTMP_USER_START(s, tp) \
@ -262,7 +263,9 @@ ngx_rtmp_append_amf(ngx_rtmp_session_t *s,
return rc;
}
ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_int_t
ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
ngx_chain_t *first;
@ -287,3 +290,59 @@ done:
return rc;
}
ngx_int_t
ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc)
{
ngx_rtmp_header_t h;
static double trans;
static ngx_rtmp_amf_elt_t out_inf[] = {
{ NGX_RTMP_AMF_STRING,
ngx_string("code"),
NULL, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("level"),
NULL, 0 },
{ NGX_RTMP_AMF_STRING,
ngx_string("description"),
NULL, 0 },
};
static ngx_rtmp_amf_elt_t out_elts[] = {
{ NGX_RTMP_AMF_STRING,
ngx_null_string,
"onStatus", 0 },
{ NGX_RTMP_AMF_NUMBER,
ngx_null_string,
&trans, 0 },
{ NGX_RTMP_AMF_NULL,
ngx_null_string,
NULL, 0 },
{ NGX_RTMP_AMF_OBJECT,
ngx_null_string,
out_inf,
sizeof(out_inf) },
};
out_inf[0].data = code;
out_inf[1].data = level;
out_inf[2].data = desc;
memset(&h, 0, sizeof(h));
h.type = NGX_RTMP_MSG_AMF_CMD;
h.csid = NGX_RTMP_CSID_AMF;
h.msid = NGX_RTMP_MSID;
return ngx_rtmp_send_amf(s, &h, out_elts,
sizeof(out_elts) / sizeof(out_elts[0]));
}

28
ngx_rtmp_streams.h Normal file
View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2012 Roman Arutyunyan
*/
#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_
#define _NGX_RTMP_STREAMS_H_INCLUDED_
#define NGX_RTMP_MSID 1
#define NGX_RTMP_CSID_AMF_INI 3
#define NGX_RTMP_CSID_AMF 5
#define NGX_RTMP_CSID_AUDIO 6
#define NGX_RTMP_CSID_VIDEO 7
/*legacy*/
#define NGX_RTMP_CMD_CSID_AMF_INI NGX_RTMP_CSID_AMF_INI
#define NGX_RTMP_CMD_CSID_AMF NGX_RTMP_CSID_AMF
#define NGX_RTMP_CMD_MSID NGX_RTMP_MSID
#define NGX_RTMP_LIVE_CSID_META NGX_RTMP_CSID_AMF
#define NGX_RTMP_LIVE_CSID_AUDIO NGX_RTMP_CSID_AUDIO
#define NGX_RTMP_LIVE_CSID_VIDEO NGX_RTMP_CSID_VIDEO
#define NGX_RTMP_LIVE_MSID NGX_RTMP_MSID
#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */

11
test/README.md Normal file
View file

@ -0,0 +1,11 @@
# RTMP tests
nginx.conf is sample config for testing nginx-rtmp.
Please update paths in it before using.
RTMP port: 1935, HTTP port: 8080
* http://localhost:8080/ - play myapp/mystream with JWPlayer
* http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer
* http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet
* http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet

View file

@ -85,6 +85,10 @@ http {
root /home/rarutyunyan/nginx-rtmp-module/;
}
location /rtmp-publisher {
root /home/rarutyunyan/nginx-rtmp-module/test;
}
location / {
root /home/rarutyunyan/nginx-rtmp-module/test/www;
}

View file

@ -0,0 +1,15 @@
# RTMP Publisher
Simple RTMP publisher.
Edit the following flashvars in publisher.html & player.html to suite your needs.
streamer: RTMP endpoint
file: live stream name
## Compile
Install flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html
mxmlc RtmpPublisher.mxml
mxmlc RtmpPlayer.mxml

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="500" minHeight="350" creationComplete="init()">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.core.FlexGlobals;
private var streamer:String;
private var file:String;
private function toggleFeedListener(event:MouseEvent):void {
if(toggleFeed.label == 'Start Feed') {
toggleFeed.label = 'Stop Feed';
toggleAudio.enabled = true;
videoDisplay.play();
} else {
toggleFeed.label = 'Start Feed';
toggleAudio.enabled = false;
videoDisplay.close();
}
}
private function toggleAudioHandler(event:MouseEvent):void {
if(toggleAudio.label == 'Unmute') {
toggleAudio.label = 'Mute';
videoDisplay.volume = 1;
} else {
toggleAudio.label = 'Unmute';
videoDisplay.volume = 0;
}
}
private function initListeners():void {
toggleFeed.addEventListener(MouseEvent.CLICK, toggleFeedListener);
toggleAudio.addEventListener(MouseEvent.CLICK, toggleAudioHandler);
}
private function init():void {
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
file = FlexGlobals.topLevelApplication.parameters.file;
if(file == null) {
Alert.show('Missing flashvars: file');
return;
}
if(streamer == null) {
Alert.show('Missing flashvars: streamer');
return;
}
videoDisplay.source = streamer + "/" + file;
initListeners();
}
]]>
</fx:Script>
<s:Panel x="0" y="0" width="100%" height="100%" title="RTMP Player">
<mx:VideoDisplay width="100%" height="100%" id="videoDisplay" autoPlay="false">
</mx:VideoDisplay>
<s:controlBarContent>
<s:Button label="Start Feed" id="toggleFeed"></s:Button>
<s:Spacer width="100%" height="100%"/>
<s:Button label="Mute" id="toggleAudio" enabled="false"></s:Button>
</s:controlBarContent>
</s:Panel>
</s:Application>

Binary file not shown.

View file

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="500" minHeight="350" creationComplete="init()">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.core.FlexGlobals;
private var streamer:String;
private var file:String;
private var camera:Camera;
private var microphone:Microphone;
private var connection:NetConnection;
private var stream:NetStream;
private function toggleFeedListener(event:MouseEvent):void {
if(toggleFeed.label == 'Start Feed') {
toggleFeed.label = 'Stop Feed';
stream.publish(file, 'live');
videoDisplay.attachCamera(camera);
toggleVideo.enabled = true;
toggleAudio.enabled = true;
} else {
toggleFeed.label = 'Start Feed';
stream.close();
videoDisplay.attachCamera(null);
toggleVideo.enabled = false;
toggleAudio.enabled = false;
}
}
private function toggleVideoListener(event:MouseEvent):void {
if(toggleVideo.label == 'Start Video') {
toggleVideo.label = 'Stop Video';
videoDisplay.attachCamera(camera);
stream.attachCamera(camera);
} else {
toggleVideo.label = 'Start Video';
videoDisplay.attachCamera(null);
stream.attachCamera(null);
}
}
private function toggleAudioListener(event:MouseEvent):void {
if(toggleAudio.label == 'Start Audio') {
toggleAudio.label = 'Stop Audio';
stream.attachAudio(microphone);
} else {
toggleAudio.label = 'Start Audio';
stream.attachAudio(null);
}
}
private function initListeners():void {
toggleFeed.addEventListener(MouseEvent.CLICK, toggleFeedListener);
toggleVideo.addEventListener(MouseEvent.CLICK, toggleVideoListener);
toggleAudio.addEventListener(MouseEvent.CLICK, toggleAudioListener);
}
private function netStatusHander(event:NetStatusEvent):void {
switch(event.info.code) {
case 'NetConnection.Connect.Success':
stream = new NetStream(connection);
stream.attachCamera(camera);
stream.attachAudio(microphone);
break;
}
}
private function init():void {
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
file = FlexGlobals.topLevelApplication.parameters.file;
if(file == null) {
Alert.show('Missing flashvars: file');
return;
}
if(streamer == null) {
Alert.show('Missing flashvars: streamer');
return;
}
initListeners();
camera = Camera.getCamera();
microphone = Microphone.getMicrophone();
connection = new NetConnection();
connection.connect(streamer);
connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHander);
}
]]>
</fx:Script>
<s:Panel x="0" y="0" width="100%" height="100%" title="RTMP Publisher">
<mx:VideoDisplay width="100%" height="100%" id="videoDisplay">
</mx:VideoDisplay>
<s:controlBarContent>
<s:Button label="Start Feed" id="toggleFeed"></s:Button>
<s:Spacer width="100%" height="100%"/>
<s:Button label="Stop Video" id="toggleVideo" enabled="false"></s:Button>
<s:Button label="Stop Audio" id="toggleAudio" enabled="false"></s:Button>
</s:controlBarContent>
</s:Panel>
</s:Application>

Binary file not shown.

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>RTMP Player</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
var flashVars = {
streamer: 'rtmp://localhost/myapp',
file:'mystream'
};
swfobject.embedSWF("RtmpPlayer.swf", "rtmp-publisher", "500", "400", "9.0.0", null, flashVars);
</script>
</head>
<body>
<div id="rtmp-publisher">
<p>Flash not installed</p>
</div>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>RTMP Publisher</title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">
var flashVars = {
streamer: 'rtmp://localhost/myapp',
file:'mystream'
};
swfobject.embedSWF("RtmpPublisher.swf", "rtmp-publisher", "500", "400", "9.0.0", null, flashVars);
</script>
</head>
<body>
<div id="rtmp-publisher">
<p>Flash not installed</p>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long