mirror of
https://github.com/zotanmew/nginx-rtmp-module.git
synced 2024-05-15 08:21:09 +02:00
merged from master
This commit is contained in:
commit
acf6ea076c
253
README
253
README
|
@ -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
262
README.md
Normal 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
12
TODO
|
@ -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
9
config
|
@ -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
9
hls/README.md
Normal 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)
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
27
ngx_rtmp.h
27
ngx_rtmp.h
|
@ -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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
642
ngx_rtmp_flv_module.c
Normal 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"),
|
||||
×_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(×_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(×_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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
2299
ngx_rtmp_mp4_module.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"),
|
||||
×_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(×_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(×_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
58
ngx_rtmp_play_module.h
Normal 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_ */
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
28
ngx_rtmp_streams.h
Normal 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
11
test/README.md
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
15
test/rtmp-publisher/README.md
Normal file
15
test/rtmp-publisher/README.md
Normal 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
|
70
test/rtmp-publisher/RtmpPlayer.mxml
Normal file
70
test/rtmp-publisher/RtmpPlayer.mxml
Normal 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>
|
BIN
test/rtmp-publisher/RtmpPlayer.swf
Normal file
BIN
test/rtmp-publisher/RtmpPlayer.swf
Normal file
Binary file not shown.
108
test/rtmp-publisher/RtmpPublisher.mxml
Normal file
108
test/rtmp-publisher/RtmpPublisher.mxml
Normal 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>
|
BIN
test/rtmp-publisher/RtmpPublisher.swf
Normal file
BIN
test/rtmp-publisher/RtmpPublisher.swf
Normal file
Binary file not shown.
19
test/rtmp-publisher/player.html
Normal file
19
test/rtmp-publisher/player.html
Normal 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>
|
19
test/rtmp-publisher/publisher.html
Normal file
19
test/rtmp-publisher/publisher.html
Normal 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>
|
4
test/rtmp-publisher/swfobject.js
Normal file
4
test/rtmp-publisher/swfobject.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue