Permalink
Browse files

merged from master

  • Loading branch information...
2 parents 1d4f502 + 9aa4114 commit acf6ea076cca9cf67d98743941a294b0e4db5a23 @arut committed Aug 30, 2012
View
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;
- }
-
- }
-}
-
View
262 README.md
@@ -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;
+ }
+
+ }
+ }
+
View
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
View
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"
View
9 hls/README.md
@@ -0,0 +1,9 @@
+# HLS (HTTP Live Streaming) module
+
+This module should be added explicitly when building NGINX:
+
+ ./configure ... --add-module=/path/to/nginx-rtmp-module/hls ...
+
+## Requirement
+
+The module requires ffmpeg version>= 53.31.100 from ffmpeg (ffmpeg.org)
View
2 hls/config
@@ -9,5 +9,5 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
$ngx_addon_dir/ngx_rtmp_hls_module.c \
"
-CORE_LIBS="$CORE_LIBS -lavformat"
+CORE_LIBS="$CORE_LIBS -lavformat -lavcodec -lavutil"
View
94 hls/ngx_rtmp_hls_module.c
@@ -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,24 +320,26 @@ 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,
"hls: av_new_stream failed (audio)");
return NGX_ERROR;
}
- stream->codec->codec_id = ngx_rtmp_hls_get_audio_codec(
- codec_ctx->audio_codec_id);
- if (stream->codec->codec_id == CODEC_ID_NONE) {
- return NGX_OK;
- }
-
+ stream->codec->codec_id = cid;
stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
stream->codec->sample_fmt = (codec_ctx->sample_size == 1 ?
AV_SAMPLE_FMT_U8 : AV_SAMPLE_FMT_S16);
stream->codec->sample_rate = 48000;/*codec_ctx->sample_rate;*/
- stream->codec->bit_rate = 128000;
+ stream->codec->bit_rate = 2000000;
stream->codec->channels = codec_ctx->audio_channels;
ctx->out_astream = stream->index;
@@ -326,6 +349,14 @@ ngx_rtmp_hls_init_audio(ngx_rtmp_session_t *s)
"hls: audio stream: %i %iHz",
ctx->out_astream, codec_ctx->sample_rate);
+ if (ctx->header_sent) {
+ if (av_write_trailer(ctx->out_format) < 0) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: av_write_trailer failed");
+ }
+ ctx->header_sent = 0;
+ }
+
return NGX_OK;
}
@@ -340,17 +371,31 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
ssize_t n;
ngx_int_t ffrag;
ngx_rtmp_hls_app_conf_t *hacf;
+ ngx_int_t nretry;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+ nretry = 0;
+
+retry:
fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY,
NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: open failed: '%V'",
&ctx->playlist_bak);
+ /* try to create parent folder */
+ if (nretry == 0 &&
+ ngx_create_dir(hacf->path.data, NGX_RTMP_HLS_DIR_ACCESS) !=
+ NGX_INVALID_FILE)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: creating target folder: '%V'", &hacf->path);
+ ++nretry;
+ goto retry;
+ }
return NGX_ERROR;
}
@@ -406,8 +451,10 @@ static ngx_int_t
ngx_rtmp_hls_initialize(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
+ ngx_rtmp_hls_app_conf_t *hacf;
AVOutputFormat *format;
+ hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx == NULL || ctx->out_format || ctx->publishing == 0) {
return NGX_OK;
@@ -430,6 +477,7 @@ ngx_rtmp_hls_initialize(ngx_rtmp_session_t *s)
return NGX_ERROR;
}
ctx->out_format->oformat = format;
+ ctx->out_format->max_delay = (int64_t)hacf->muxdelay * AV_TIME_BASE / 1000;
return NGX_ERROR;
}
@@ -449,6 +497,10 @@ ngx_rtmp_hls_open_file(ngx_rtmp_session_t *s, u_char *fpath)
return NGX_OK;
}
+ if (!ctx->video && !ctx->audio) {
+ return NGX_OK;
+ }
+
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: open stream file '%s'", fpath);
@@ -475,11 +527,14 @@ ngx_rtmp_hls_open_file(ngx_rtmp_session_t *s, u_char *fpath)
}
/* write header */
- if (avformat_write_header(ctx->out_format, NULL) < 0) {
+ if (!ctx->header_sent &&
+ avformat_write_header(ctx->out_format, NULL) < 0)
+ {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: avformat_write_header failed");
return NGX_ERROR;
}
+ ctx->header_sent = 1;
if (astream) {
astream->codec->extradata = NULL;
@@ -651,10 +706,11 @@ ngx_rtmp_hls_close_file(ngx_rtmp_session_t *s)
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+ /*
if (av_write_trailer(ctx->out_format) < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: av_write_trailer failed");
- }
+ }*/
avio_flush(ctx->out_format->pb);
@@ -878,7 +934,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
/* write to file */
av_init_packet(&packet);
- packet.dts = h->timestamp * 90;
+ packet.dts = h->timestamp * 90L;
packet.pts = packet.dts;
packet.stream_index = ctx->out_astream;
packet.data = buffer;
@@ -918,6 +974,7 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
uint32_t len, rlen;
ngx_buf_t out;
static u_char buffer[NGX_RTMP_HLS_BUFSIZE];
+ int32_t cts;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
@@ -953,9 +1010,11 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
}
/* 3 bytes: decoder delay */
- if (ngx_rtmp_hls_copy(s, NULL, &p, 3, &in) != NGX_OK) {
+ if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) {
return NGX_ERROR;
}
+ cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16)
+ | (cts & 0x0000FF00);
out.pos = buffer;
out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE;
@@ -1027,13 +1086,14 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
}
av_init_packet(&packet);
- packet.dts = h->timestamp * 90;
- packet.pts = packet.dts;
+ packet.dts = h->timestamp * 90L;
+ packet.pts = packet.dts + cts * 90;
packet.stream_index = ctx->out_vstream;
- /*
+
if (ftype == 1) {
packet.flags |= AV_PKT_FLAG_KEY;
- }*/
+ }
+
packet.data = buffer;
packet.size = out.pos - buffer;
@@ -1058,6 +1118,7 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
conf->hls = NGX_CONF_UNSET;
conf->fraglen = NGX_CONF_UNSET;
+ conf->muxdelay = NGX_CONF_UNSET;
conf->playlen = NGX_CONF_UNSET;
conf->nbuckets = 1024;
@@ -1073,6 +1134,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->hls, prev->hls, 0);
ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);
+ ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700);
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
ngx_conf_merge_str_value(conf->path, prev->path, "");
conf->ctx = ngx_pcalloc(cf->pool,
View
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
View
13 ngx_rtmp_cmd_module.c
@@ -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;
@@ -495,14 +492,14 @@ ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
static ngx_rtmp_amf_elt_t out_inf[] = {
{ NGX_RTMP_AMF_STRING,
- ngx_string("code"),
- "NetStream.Publish.Start", 0 },
-
- { NGX_RTMP_AMF_STRING,
ngx_string("level"),
"status", 0 },
{ NGX_RTMP_AMF_STRING,
+ ngx_string("code"),
+ "NetStream.Publish.Start", 0 },
+
+ { NGX_RTMP_AMF_STRING,
ngx_string("description"),
"Publish succeeded.", 0 },
};
View
2 ngx_rtmp_codec_module.c
@@ -200,6 +200,8 @@ ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
header = NULL;
+ pheader = NULL;
+ version = NULL;
if (h->type == NGX_RTMP_MSG_AUDIO) {
if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
header = &ctx->aac_header;
View
2 ngx_rtmp_core_module.c
@@ -571,7 +571,7 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ls->ipv6only = 1;
} else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
- ls->ipv6only = 2;
+ ls->ipv6only = 0;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
View
7 ngx_rtmp_exec_module.c
@@ -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 @@ ngx_rtmp_exec_prepare_arg(ngx_rtmp_session_t *s, ngx_str_t *arg)
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;
}
View
642 ngx_rtmp_flv_module.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (c) 2012 Roman Arutyunyan
+ */
+
+
+#include "ngx_rtmp_play_module.h"
+#include "ngx_rtmp_codec_module.h"
+#include "ngx_rtmp_streams.h"
+
+
+static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);
+static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,
+ ngx_file_t *f, ngx_int_t timestamp);
+static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f,
+ ngx_uint_t offset);
+static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f);
+
+
+typedef struct {
+ ngx_uint_t nelts;
+ ngx_uint_t offset;
+} ngx_rtmp_flv_index_t;
+
+
+typedef struct {
+ ngx_int_t offset;
+ ngx_int_t start_timestamp;
+ ngx_event_t write_evt;
+ uint32_t last_audio;
+ uint32_t last_video;
+ ngx_uint_t msg_mask;
+ uint32_t epoch;
+
+ unsigned meta_read:1;
+ ngx_rtmp_flv_index_t filepositions;
+ ngx_rtmp_flv_index_t times;
+} ngx_rtmp_flv_ctx_t;
+
+
+#define NGX_RTMP_FLV_BUFFER (1024*1024)
+#define NGX_RTMP_FLV_DEFAULT_BUFLEN 1000
+#define NGX_RTMP_FLV_TAG_HEADER 11
+#define NGX_RTMP_FLV_DATA_OFFSET 13
+
+
+static u_char ngx_rtmp_flv_buffer[
+ NGX_RTMP_FLV_BUFFER];
+static u_char ngx_rtmp_flv_header[
+ NGX_RTMP_FLV_TAG_HEADER];
+
+
+static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_rtmp_flv_postconfiguration, /* postconfiguration */
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+ NULL, /* create app configuration */
+ NULL /* merge app configuration */
+};
+
+
+ngx_module_t ngx_rtmp_flv_module = {
+ NGX_MODULE_V1,
+ &ngx_rtmp_flv_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_RTMP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_int_t
+ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)
+{
+ uint32_t nelts;
+ ngx_buf_t *b;
+
+ /* we have AMF array pointed by context;
+ * need to extract its size (4 bytes) &
+ * save offset of actual array data */
+
+ b = ctx->link->buf;
+
+ if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
+ return NGX_ERROR;
+ }
+
+ ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
+
+ idx->nelts = nelts;
+ idx->offset = ctx->offset + 4;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+
+ static ngx_rtmp_amf_ctx_t filepositions_ctx;
+ static ngx_rtmp_amf_ctx_t times_ctx;
+
+ static ngx_rtmp_amf_elt_t in_keyframes[] = {
+
+ { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
+ ngx_string("filepositions"),
+ &filepositions_ctx, 0 },
+
+ { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
+ ngx_string("times"),
+ &times_ctx, 0 }
+ };
+
+ static ngx_rtmp_amf_elt_t in_inf[] = {
+
+ { NGX_RTMP_AMF_OBJECT,
+ ngx_string("keyframes"),
+ in_keyframes, sizeof(in_keyframes) }
+ };
+
+ static ngx_rtmp_amf_elt_t in_elts[] = {
+
+ { NGX_RTMP_AMF_STRING,
+ ngx_null_string,
+ NULL, 0 },
+
+ { NGX_RTMP_AMF_OBJECT,
+ ngx_null_string,
+ in_inf, sizeof(in_inf) },
+ };
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL || in == NULL) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: init index");
+
+ ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
+ ngx_memzero(&times_ctx, sizeof(times_ctx));
+
+ if (ngx_rtmp_receive_amf(s, in, in_elts,
+ sizeof(in_elts) / sizeof(in_elts[0])))
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: init index error");
+ return NGX_OK;
+ }
+
+ if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,
+ &ctx->filepositions)
+ != NGX_OK)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: failed to init filepositions");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: filepositions nelts=%ui offset=%ui",
+ ctx->filepositions.nelts, ctx->filepositions.offset);
+
+ if (times_ctx.link && ngx_rtmp_flv_fill_index(&times_ctx,
+ &ctx->times)
+ != NGX_OK)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: failed to init times");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: times nelts=%ui offset=%ui",
+ ctx->times.nelts, ctx->times.offset);
+
+ return NGX_OK;
+}
+
+
+static double
+ngx_rtmp_flv_index_value(void *src)
+{
+ double v;
+
+ ngx_rtmp_rmemcpy(&v, src, 8);
+
+ return v;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,
+ ngx_int_t timestamp)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+ ssize_t n, size;
+ ngx_uint_t offset, index, ret, nelts;
+ double v;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ goto rewind;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: lookup index start timestamp=%i",
+ timestamp);
+
+ if (ctx->meta_read == 0) {
+ ngx_rtmp_flv_read_meta(s, f);
+ ctx->meta_read = 1;
+ }
+
+ if (timestamp <= 0 || ctx->filepositions.nelts == 0
+ || ctx->times.nelts == 0)
+ {
+ goto rewind;
+ }
+
+ /* read index table from file given offset */
+ offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
+ ctx->times.offset;
+
+ /* index should fit in the buffer */
+ nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);
+ size = nelts * 9;
+
+ n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);
+
+ if (n != size) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read times index");
+ goto rewind;
+ }
+
+ /*TODO: implement binary search */
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: lookup times nelts=%ui", nelts);
+
+ for (index = 0; index < nelts - 1; ++index) {
+ v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +
+ index * 9 + 1) * 1000;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: lookup times index=%ui value=%ui",
+ index, (ngx_uint_t) v);
+
+ if (timestamp < v) {
+ break;
+ }
+ }
+
+ if (index >= ctx->filepositions.nelts) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: index out of bounds: %ui>=%ui",
+ index, ctx->filepositions.nelts);
+ goto rewind;
+ }
+
+ /* take value from filepositions */
+ offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
+ ctx->filepositions.offset + index * 9;
+
+ n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);
+
+ if (n != 8) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read filepositions index");
+ goto rewind;
+ }
+
+ ret = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: lookup index timestamp=%i offset=%ui",
+ timestamp, ret);
+
+ return ret;
+
+rewind:
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: lookup index timestamp=%i offset=begin",
+ timestamp);
+
+ return NGX_RTMP_FLV_DATA_OFFSET;
+}
+
+
+static void
+ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+ ssize_t n;
+ ngx_rtmp_header_t h;
+ ngx_chain_t *out, in;
+ ngx_buf_t in_buf;
+ ngx_rtmp_core_srv_conf_t *cscf;
+ uint32_t size;
+
+ cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: read meta");
+
+ /* read tag header */
+ n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),
+ NGX_RTMP_FLV_DATA_OFFSET);
+
+ if (n != sizeof(ngx_rtmp_flv_header)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read metadata tag header");
+ return;
+ }
+
+ if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: first tag is not metadata, giving up");
+ return;
+ }
+
+ ngx_memzero(&h, sizeof(h));
+
+ h.type = NGX_RTMP_MSG_AMF_META;
+ h.msid = NGX_RTMP_LIVE_MSID;
+ h.csid = NGX_RTMP_LIVE_CSID_META;
+
+ size = 0;
+ ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: metadata size=%D", size);
+
+ if (size > sizeof(ngx_rtmp_flv_buffer)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: too big metadata");
+ return;
+ }
+
+ /* read metadata */
+ n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
+ sizeof(ngx_rtmp_flv_header) +
+ NGX_RTMP_FLV_DATA_OFFSET);
+
+ if (n != (ssize_t) size) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read metadata");
+ return;
+ }
+
+ /* prepare input chain */
+ ngx_memzero(&in, sizeof(in));
+ ngx_memzero(&in_buf, sizeof(in_buf));
+
+ in.buf = &in_buf;
+ in_buf.pos = ngx_rtmp_flv_buffer;
+ in_buf.last = ngx_rtmp_flv_buffer + size;
+
+ ngx_rtmp_flv_init_index(s, &in);
+
+ /* output chain */
+ out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
+
+ ngx_rtmp_prepare_message(s, &h, NULL, out);
+ ngx_rtmp_send_message(s, out, 0);
+ ngx_rtmp_free_shared_chain(cscf, out);
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+ uint32_t last_timestamp;
+ ngx_rtmp_header_t h, lh;
+ ngx_rtmp_core_srv_conf_t *cscf;
+ ngx_chain_t *out, in;
+ ngx_buf_t in_buf;
+ ngx_int_t rc;
+ ssize_t n;
+ uint32_t buflen, end_timestamp, size;
+
+ cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->offset == -1) {
+ ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
+ ctx->start_timestamp);
+ ctx->start_timestamp = -1; /* set later from actual timestamp */
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: read tag at offset=%i", ctx->offset);
+
+ /* read tag header */
+ n = ngx_read_file(f, ngx_rtmp_flv_header,
+ sizeof(ngx_rtmp_flv_header), ctx->offset);
+
+ if (n != sizeof(ngx_rtmp_flv_header)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read flv tag header");
+ return NGX_DONE;
+ }
+
+ /* parse header fields */
+ ngx_memzero(&h, sizeof(h));
+
+ h.msid = NGX_RTMP_LIVE_MSID;
+ h.type = ngx_rtmp_flv_header[0];
+
+ size = 0;
+
+ ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
+ ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);
+
+ ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];
+
+ ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);
+
+ last_timestamp = 0;
+
+ switch (h.type) {
+
+ case NGX_RTMP_MSG_AUDIO:
+ h.csid = NGX_RTMP_CSID_AUDIO;
+ last_timestamp = ctx->last_audio;
+ ctx->last_audio = h.timestamp;
+ break;
+
+ case NGX_RTMP_MSG_VIDEO:
+ h.csid = NGX_RTMP_CSID_VIDEO;
+ last_timestamp = ctx->last_video;
+ ctx->last_video = h.timestamp;
+ break;
+
+ default:
+ return NGX_OK;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: read tag type=%i size=%uD timestamp=%uD "
+ "last_timestamp=%uD",
+ (ngx_int_t) h.type,size, h.timestamp, last_timestamp);
+
+ lh = h;
+ lh.timestamp = last_timestamp;
+
+ if (size > sizeof(ngx_rtmp_flv_buffer)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: too big message: %D>%uz", size,
+ sizeof(ngx_rtmp_flv_buffer));
+ goto next;
+ }
+
+ /* read tag body */
+ n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
+ ctx->offset - size - 4);
+
+ if (n != (ssize_t) size) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "flv: could not read flv tag");
+ return NGX_ERROR;
+ }
+
+ /* prepare input chain */
+ ngx_memzero(&in, sizeof(in));
+ ngx_memzero(&in_buf, sizeof(in_buf));
+
+ in.buf = &in_buf;
+ in_buf.pos = ngx_rtmp_flv_buffer;
+ in_buf.last = ngx_rtmp_flv_buffer + size;
+
+ /* output chain */
+ out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
+
+ ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
+ &lh : NULL, out);
+ rc = ngx_rtmp_send_message(s, out, 0);
+ ngx_rtmp_free_shared_chain(cscf, out);
+
+ if (rc == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ if (rc != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ctx->msg_mask |= (1 << h.type);
+
+next:
+ if (ctx->start_timestamp == -1) {
+ ctx->start_timestamp = h.timestamp;
+ ctx->epoch = ngx_current_msec;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: start_timestamp=%i", ctx->start_timestamp);
+ return NGX_OK;
+ }
+
+ buflen = (s->buflen ? s->buflen : NGX_RTMP_FLV_DEFAULT_BUFLEN);
+ end_timestamp = (ngx_current_msec - ctx->epoch) +
+ ctx->start_timestamp + buflen;
+
+ ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
+ h.timestamp > end_timestamp ? "schedule" : "advance",
+ h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
+ h.timestamp, end_timestamp, (ngx_int_t) buflen);
+
+ /* too much data sent; schedule timeout */
+ if (h.timestamp > end_timestamp) {
+ return h.timestamp - end_timestamp;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));
+
+ if (ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);
+ }
+
+ ngx_memzero(ctx, sizeof(*ctx));
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: start timestamp=%ui", timestamp);
+
+ ctx->start_timestamp = timestamp;
+ ctx->offset = -1;
+ ctx->msg_mask = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)
+{
+ ngx_rtmp_flv_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
+
+ if (ctx == NULL) {
+ return NGX_OK;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "flv: stop");
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)
+{
+ ngx_rtmp_play_main_conf_t *pmcf;
+ ngx_rtmp_play_fmt_t **pfmt, *fmt;
+
+ pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
+
+ pfmt = ngx_array_push(&pmcf->fmts);
+
+ if (pfmt == NULL) {
+ return NGX_ERROR;
+ }
+
+ fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
+
+ if (fmt == NULL) {
+ return NGX_ERROR;
+ }
+
+ *pfmt = fmt;
+
+ ngx_str_set(&fmt->name, "flv-format");
+
+ ngx_str_null(&fmt->pfx); /* default fmt */
+ ngx_str_set(&fmt->sfx, ".flv");
+
+ fmt->init = ngx_rtmp_flv_init;
+ fmt->start = ngx_rtmp_flv_start;
+ fmt->stop = ngx_rtmp_flv_stop;
+ fmt->send = ngx_rtmp_flv_send;
+
+ return NGX_OK;
+}
View
2 ngx_rtmp_handler.c
@@ -537,6 +537,8 @@ ngx_rtmp_send(ngx_event_t *wev)
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
+
+ ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
}
View
8 ngx_rtmp_live_module.h
@@ -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;
View
2,299 ngx_rtmp_mp4_module.c
@@ -0,0 +1,2299 @@
+/*
+ * 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_mp4_postconfiguration(ngx_conf_t *cf);
+static ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f,
+ ngx_uint_t offset);
+static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
+static ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f);
+
+
+#pragma pack(push,4)
+
+
+typedef struct {
+ uint32_t first_chunk;
+ uint32_t samples_per_chunk;
+ uint32_t sample_descrption_index;
+} ngx_rtmp_mp4_chunk_entry_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ ngx_rtmp_mp4_chunk_entry_t entries[0];
+} ngx_rtmp_mp4_chunks_t;
+
+
+typedef struct {
+ uint32_t sample_count;
+ uint32_t sample_delta;
+} ngx_rtmp_mp4_time_entry_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ ngx_rtmp_mp4_time_entry_t entries[0];
+} ngx_rtmp_mp4_times_t;
+
+
+typedef struct {
+ uint32_t sample_count;
+ uint32_t sample_offset;
+} ngx_rtmp_mp4_delay_entry_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ ngx_rtmp_mp4_delay_entry_t entries[0];
+} ngx_rtmp_mp4_delays_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ uint32_t entries[0];
+} ngx_rtmp_mp4_keys_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t sample_size;
+ uint32_t sample_count;
+ uint32_t entries[0];
+} ngx_rtmp_mp4_sizes_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t field_size;
+ uint32_t sample_count;
+ uint32_t entries[0];
+} ngx_rtmp_mp4_sizes2_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ uint32_t entries[0];
+} ngx_rtmp_mp4_offsets_t;
+
+
+typedef struct {
+ uint32_t version_flags;
+ uint32_t entry_count;
+ uint64_t entries[0];
+} ngx_rtmp_mp4_offsets64_t;
+
+#pragma pack(pop)
+
+
+typedef struct {
+ uint32_t timestamp;
+ uint32_t last_timestamp;
+ off_t offset;
+ size_t size;
+ ngx_int_t key;
+ uint32_t delay;
+
+ unsigned not_first:1;
+ unsigned valid:1;
+
+ ngx_uint_t pos;
+
+ ngx_uint_t key_pos;
+
+ ngx_uint_t chunk;
+ ngx_uint_t chunk_pos;
+ ngx_uint_t chunk_count;
+
+ ngx_uint_t time_pos;
+ ngx_uint_t time_count;
+
+ ngx_uint_t delay_pos;
+ ngx_uint_t delay_count;
+
+ ngx_uint_t size_pos;
+} ngx_rtmp_mp4_cursor_t;
+
+
+typedef struct {
+ ngx_uint_t id;
+
+ ngx_int_t type;
+ ngx_int_t codec;
+ uint32_t csid;
+ u_char fhdr;
+ ngx_int_t time_scale;
+ uint64_t duration;
+
+ u_char *header;
+ size_t header_size;
+ unsigned header_sent:1;
+
+ ngx_rtmp_mp4_times_t *times;
+ ngx_rtmp_mp4_delays_t *delays;
+ ngx_rtmp_mp4_keys_t *keys;
+ ngx_rtmp_mp4_chunks_t *chunks;
+ ngx_rtmp_mp4_sizes_t *sizes;
+ ngx_rtmp_mp4_sizes2_t *sizes2;
+ ngx_rtmp_mp4_offsets_t *offsets;
+ ngx_rtmp_mp4_offsets64_t *offsets64;
+ ngx_rtmp_mp4_cursor_t cursor;
+} ngx_rtmp_mp4_track_t;
+
+
+typedef struct {
+ void *mmaped;
+ size_t mmaped_size;
+
+ unsigned meta_sent:1;
+
+ ngx_rtmp_mp4_track_t tracks[2];
+ ngx_rtmp_mp4_track_t *track;
+ ngx_uint_t ntracks;
+
+ ngx_uint_t width;
+ ngx_uint_t height;
+ ngx_uint_t nchannels;
+ ngx_uint_t sample_size;
+ ngx_uint_t sample_rate;
+
+ uint32_t start_timestamp, epoch;
+} ngx_rtmp_mp4_ctx_t;
+
+
+#define ngx_rtmp_mp4_make_tag(a, b, c, d) \
+ ((uint32_t)d << 24 | (uint32_t)c << 16 | (uint32_t)b << 8 | (uint32_t)a)
+
+
+static inline uint32_t
+ngx_rtmp_mp4_to_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts)
+{
+ return (uint64_t) ts * 1000 / t->time_scale;
+}
+
+
+static inline uint32_t
+ngx_rtmp_mp4_from_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts)
+{
+ return (uint64_t) ts * t->time_scale / 1000;
+}
+
+
+#define NGX_RTMP_MP4_DEFAULT_BUFLEN 1000
+
+
+static u_char ngx_rtmp_mp4_buffer[1024*1024];
+
+
+static ngx_int_t ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+
+
+typedef ngx_int_t (*ngx_rtmp_mp4_box_pt)(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+
+typedef struct {
+ uint32_t tag;
+ ngx_rtmp_mp4_box_pt handler;
+} ngx_rtmp_mp4_box_t;
+
+
+static ngx_rtmp_mp4_box_t ngx_rtmp_mp4_boxes[] = {
+ { ngx_rtmp_mp4_make_tag('t','r','a','k'), ngx_rtmp_mp4_parse_trak },
+ { ngx_rtmp_mp4_make_tag('m','d','i','a'), ngx_rtmp_mp4_parse },
+ { ngx_rtmp_mp4_make_tag('m','d','h','d'), ngx_rtmp_mp4_parse_mdhd },
+ { ngx_rtmp_mp4_make_tag('h','d','l','r'), ngx_rtmp_mp4_parse_hdlr },
+ { ngx_rtmp_mp4_make_tag('m','i','n','f'), ngx_rtmp_mp4_parse },
+ { ngx_rtmp_mp4_make_tag('s','t','b','l'), ngx_rtmp_mp4_parse },
+ { ngx_rtmp_mp4_make_tag('s','t','s','d'), ngx_rtmp_mp4_parse_stsd },
+ { ngx_rtmp_mp4_make_tag('s','t','s','c'), ngx_rtmp_mp4_parse_stsc },
+ { ngx_rtmp_mp4_make_tag('s','t','t','s'), ngx_rtmp_mp4_parse_stts },
+ { ngx_rtmp_mp4_make_tag('c','t','t','s'), ngx_rtmp_mp4_parse_ctts },
+ { ngx_rtmp_mp4_make_tag('s','t','s','s'), ngx_rtmp_mp4_parse_stss },
+ { ngx_rtmp_mp4_make_tag('s','t','s','z'), ngx_rtmp_mp4_parse_stsz },
+ { ngx_rtmp_mp4_make_tag('s','t','z','2'), ngx_rtmp_mp4_parse_stz2 },
+ { ngx_rtmp_mp4_make_tag('s','t','c','o'), ngx_rtmp_mp4_parse_stco },
+ { ngx_rtmp_mp4_make_tag('c','o','6','4'), ngx_rtmp_mp4_parse_co64 },
+ { ngx_rtmp_mp4_make_tag('a','v','c','1'), ngx_rtmp_mp4_parse_avc1 },
+ { ngx_rtmp_mp4_make_tag('a','v','c','C'), ngx_rtmp_mp4_parse_avcC },
+ { ngx_rtmp_mp4_make_tag('m','p','4','a'), ngx_rtmp_mp4_parse_mp4a },
+ { ngx_rtmp_mp4_make_tag('m','p','4','v'), ngx_rtmp_mp4_parse_mp4v },
+ { ngx_rtmp_mp4_make_tag('e','s','d','s'), ngx_rtmp_mp4_parse_esds },
+ { ngx_rtmp_mp4_make_tag('.','m','p','3'), ngx_rtmp_mp4_parse_mp3 },
+ { ngx_rtmp_mp4_make_tag('n','m','o','s'), ngx_rtmp_mp4_parse_nmos },
+ { ngx_rtmp_mp4_make_tag('s','p','e','x'), ngx_rtmp_mp4_parse_spex }
+};
+
+
+static ngx_int_t ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+static ngx_int_t ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos,
+ u_char *last);
+
+
+typedef ngx_int_t (*ngx_rtmp_mp4_descriptor_pt)(ngx_rtmp_session_t *s,
+ u_char *pos, u_char *last);
+
+typedef struct {
+ uint8_t tag;
+ ngx_rtmp_mp4_descriptor_pt handler;
+} ngx_rtmp_mp4_descriptor_t;
+
+
+static ngx_rtmp_mp4_descriptor_t ngx_rtmp_mp4_descriptors[] = {
+ { 0x03, ngx_rtmp_mp4_parse_es }, /* MPEG ES Descriptor */
+ { 0x04, ngx_rtmp_mp4_parse_dc }, /* MPEG DecoderConfig Descriptor */
+ { 0x05, ngx_rtmp_mp4_parse_ds } /* MPEG DecoderSpec Descriptor */
+};
+
+
+static ngx_rtmp_module_t ngx_rtmp_mp4_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_rtmp_mp4_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_mp4_module = {
+ NGX_MODULE_V1,
+ &ngx_rtmp_mp4_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_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track) {
+ return NGX_OK;
+ }
+
+ ctx->track = (ctx->ntracks == sizeof(ctx->tracks) / sizeof(ctx->tracks[0]))
+ ? NULL : &ctx->tracks[ctx->ntracks];
+
+ if (ctx->track) {
+ ngx_memzero(ctx->track, sizeof(ctx->track));
+ ctx->track->id = ctx->ntracks;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: trying track %ui", ctx->ntracks);
+ }
+
+ if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (ctx->track && ctx->track->type &&
+ (ctx->ntracks == 0 ||
+ ctx->tracks[0].type != ctx->tracks[ctx->ntracks].type))
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: adding track %ui", ctx->ntracks);
+ ++ctx->ntracks;
+
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: ignoring track %ui", ctx->ntracks);
+ }
+
+ ctx->track = NULL;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+ ngx_rtmp_mp4_track_t *t;
+ uint8_t version;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL) {
+ return NGX_OK;
+ }
+
+ t = ctx->track;
+
+ if (pos + 1 > last) {
+ return NGX_ERROR;
+ }
+
+ version = *(uint8_t *) pos;
+
+ switch (version) {
+ case 0:
+ if (pos + 20 > last) {
+ return NGX_ERROR;
+ }
+
+ pos += 12;
+ t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos);
+ pos += 4;
+ t->duration = ngx_rtmp_r32(*(uint32_t *) pos);
+ break;
+
+ case 1:
+ if (pos + 28 > last) {
+ return NGX_ERROR;
+ }
+
+ pos += 20;
+ t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos);
+ pos += 4;
+ t->duration = ngx_rtmp_r64(*(uint64_t *) pos);
+ break;
+
+ default:
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: duration time_scale=%ui duration=%uL",
+ t->time_scale, t->duration);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+ uint32_t type;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL) {
+ return NGX_OK;
+ }
+
+ if (pos + 12 > last) {
+ return NGX_ERROR;
+ }
+
+ type = *(uint32_t *)(pos + 8);
+
+ if (type == ngx_rtmp_mp4_make_tag('v','i','d','e')) {
+ ctx->track->type = NGX_RTMP_MSG_VIDEO;
+ ctx->track->csid = NGX_RTMP_CSID_VIDEO;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: video track");
+
+ } else if (type == ngx_rtmp_mp4_make_tag('s','o','u','n')) {
+ ctx->track->type = NGX_RTMP_MSG_AUDIO;
+ ctx->track->csid = NGX_RTMP_CSID_AUDIO;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: audio track");
+ } else {
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: unknown track");
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_video(ngx_rtmp_session_t *s, u_char *pos, u_char *last,
+ ngx_int_t codec)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL) {
+ return NGX_OK;
+ }
+
+ ctx->track->codec = codec;
+
+ if (pos + 78 > last) {
+ return NGX_ERROR;
+ }
+
+ pos += 24;
+
+ ctx->width = ngx_rtmp_r16(*(uint16_t *) pos);
+
+ pos += 2;
+
+ ctx->height = ngx_rtmp_r16(*(uint16_t *) pos);
+
+ pos += 52;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: video settings codec=%i, width=%ui, height=%ui",
+ codec, ctx->width, ctx->height);
+
+ if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ctx->track->fhdr = ctx->track->codec;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_audio(ngx_rtmp_session_t *s, u_char *pos, u_char *last,
+ ngx_int_t codec)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+ u_char *p;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL) {
+ return NGX_OK;
+ }
+
+ ctx->track->codec = codec;
+
+ if (pos + 28 > last) {
+ return NGX_ERROR;
+ }
+
+ pos += 16;
+
+ ctx->nchannels = ngx_rtmp_r16(*(uint16_t *) pos);
+
+ pos += 2;
+
+ ctx->sample_size = ngx_rtmp_r16(*(uint16_t *) pos);
+
+ pos += 6;
+
+ ctx->sample_rate = ngx_rtmp_r16(*(uint16_t *) pos);
+
+ pos += 4;
+
+ p = &ctx->track->fhdr;
+
+ *p = 0;
+
+ if (ctx->nchannels == 2) {
+ *p |= 0x01;
+ }
+
+ if (ctx->sample_size == 16) {
+ *p |= 0x02;
+ }
+
+ switch (ctx->sample_rate) {
+ case 5512:
+ break;
+
+ case 11025:
+ *p |= 0x04;
+ break;
+
+ case 22050:
+ *p |= 0x08;
+ break;
+
+ default: /*44100 etc */
+ *p |= 0x0c;
+ break;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: audio settings codec=%i, nchannels==%ui, "
+ "sample_size=%ui, sample_rate=%ui",
+ codec, ctx->nchannels, ctx->sample_size, ctx->sample_rate);
+
+ if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ *p |= (ctx->track->codec << 4);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264);
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264);
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+
+ if (pos == last) {
+ return NGX_OK;
+ }
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL || ctx->track->codec != NGX_RTMP_VIDEO_H264) {
+ return NGX_OK;
+ }
+
+ ctx->track->header = pos;
+ ctx->track->header_size = (size_t) (last - pos);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: video h264 header size=%uz",
+ ctx->track->header_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3);
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ ngx_rtmp_mp4_ctx_t *ctx;
+ ngx_rtmp_mp4_track_t *t;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ t = ctx->track;
+
+ if (t == NULL) {
+ return NGX_OK;
+ }
+
+ t->header = pos;
+ t->header_size = (size_t) (last - pos);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "mp4: decoder header size=%uz", t->header_size);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
+{
+ uint8_t id;
+ ngx_rtmp_mp4_ctx_t *ctx;
+ ngx_int_t *pc;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
+
+ if (ctx->track == NULL) {
+ return NGX_OK;
+ }
+
+ if (pos + 13 > last) {
+ return NGX_ERROR;
+ }
+
+ id = * (uint8_t *) pos;
+ pos += 13;
+ pc = &ctx->track->codec;
+
+ switch (id) {
+ case 0x21:
+ *pc = NGX_RTMP_VIDEO_H264;
+ break;
+
+ case 0x40: