Showing preview only (962K chars total). Download the full file or copy to clipboard to get everything.
Repository: arut/nginx-rtmp-module
Branch: master
Commit: 6c7719d0ba32
Files: 78
Total size: 927.9 KB
Directory structure:
gitextract_2a2ek2fd/
├── AUTHORS
├── LICENSE
├── README.md
├── config
├── dash/
│ ├── ngx_rtmp_dash_module.c
│ ├── ngx_rtmp_mp4.c
│ └── ngx_rtmp_mp4.h
├── doc/
│ └── README.md
├── hls/
│ ├── ngx_rtmp_hls_module.c
│ ├── ngx_rtmp_mpegts.c
│ └── ngx_rtmp_mpegts.h
├── ngx_rtmp.c
├── ngx_rtmp.h
├── ngx_rtmp_access_module.c
├── ngx_rtmp_amf.c
├── ngx_rtmp_amf.h
├── ngx_rtmp_auto_push_module.c
├── ngx_rtmp_bandwidth.c
├── ngx_rtmp_bandwidth.h
├── ngx_rtmp_bitop.c
├── ngx_rtmp_bitop.h
├── ngx_rtmp_cmd_module.c
├── ngx_rtmp_cmd_module.h
├── ngx_rtmp_codec_module.c
├── ngx_rtmp_codec_module.h
├── ngx_rtmp_control_module.c
├── ngx_rtmp_core_module.c
├── ngx_rtmp_eval.c
├── ngx_rtmp_eval.h
├── ngx_rtmp_exec_module.c
├── ngx_rtmp_flv_module.c
├── ngx_rtmp_handler.c
├── ngx_rtmp_handshake.c
├── ngx_rtmp_init.c
├── ngx_rtmp_limit_module.c
├── ngx_rtmp_live_module.c
├── ngx_rtmp_live_module.h
├── ngx_rtmp_log_module.c
├── ngx_rtmp_mp4_module.c
├── ngx_rtmp_netcall_module.c
├── ngx_rtmp_netcall_module.h
├── ngx_rtmp_notify_module.c
├── ngx_rtmp_play_module.c
├── ngx_rtmp_play_module.h
├── ngx_rtmp_proxy_protocol.c
├── ngx_rtmp_proxy_protocol.h
├── ngx_rtmp_receive.c
├── ngx_rtmp_record_module.c
├── ngx_rtmp_record_module.h
├── ngx_rtmp_relay_module.c
├── ngx_rtmp_relay_module.h
├── ngx_rtmp_send.c
├── ngx_rtmp_shared.c
├── ngx_rtmp_stat_module.c
├── ngx_rtmp_streams.h
├── ngx_rtmp_version.h
├── stat.xsl
└── test/
├── README.md
├── dump.sh
├── ffstream.sh
├── nginx.conf
├── play.sh
├── rtmp-publisher/
│ ├── README.md
│ ├── RtmpPlayer.mxml
│ ├── RtmpPlayer.swf
│ ├── RtmpPlayerLight.mxml
│ ├── RtmpPlayerLight.swf
│ ├── RtmpPublisher.mxml
│ ├── RtmpPublisher.swf
│ ├── player.html
│ ├── publisher.html
│ └── swfobject.js
└── www/
├── index.html
├── jwplayer/
│ ├── jwplayer.flash.swf
│ └── jwplayer.js
├── jwplayer_old/
│ ├── player.swf
│ └── swfobject.js
└── record.html
================================================
FILE CONTENTS
================================================
================================================
FILE: AUTHORS
================================================
Project author:
Roman Arutyunyan
Moscow, Russia
Contacts:
arut@qip.ru
arutyunyan.roman@gmail.com
================================================
FILE: LICENSE
================================================
Copyright (c) 2012-2014, Roman Arutyunyan
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# NGINX-based Media Streaming Server
## nginx-rtmp-module
### Project blog
http://nginx-rtmp.blogspot.com
### Wiki manual
https://github.com/arut/nginx-rtmp-module/wiki/Directives
### Google group
https://groups.google.com/group/nginx-rtmp
https://groups.google.com/group/nginx-rtmp-ru (Russian)
### Donation page (Paypal etc)
http://arut.github.com/nginx-rtmp-module/
### Features
* RTMP/HLS/MPEG-DASH live streaming
* RTMP Video on demand FLV/MP4,
playing from local filesystem or HTTP
* Stream relay support for distributed
streaming: push & pull models
* Recording streams in multiple FLVs
* H264/AAC support
* Online transcoding with FFmpeg
* HTTP callbacks (publish/play/record/update etc)
* Running external programs on certain events (exec)
* HTTP control module for recording audio/video and dropping clients
* 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
* Linux/FreeBSD/MacOS/Windows
### Build
cd to NGINX source directory & run this:
./configure --add-module=/path/to/nginx-rtmp-module
make
make install
Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be
added as well:
./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module
For building debug version of nginx add `--with-debug`
./configure --add-module=/path/to-nginx/rtmp-module --with-debug
[Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log)
### Windows limitations
Windows support is limited. These features are not supported
* execs
* static pulls
* auto_push
### 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
### Multi-worker live streaming
Module supports multi-worker live
streaming through automatic stream pushing
to nginx workers. This option is toggled with
rtmp_auto_push directive.
### 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 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 webcam {
live on;
# Stream from local webcam
exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an
-f flv rtmp://localhost:1935/webcam/mystream;
}
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;
}
application mystaticpull {
live on;
# Static pull is started at nginx start
pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static;
}
# 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
# For HLS to work please create a directory in tmpfs (/tmp/hls here)
# for the fragments. The directory contents is served via HTTP (see
# http{} section in config)
#
# Incoming stream must be in H264/AAC. 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 {
live on;
hls on;
hls_path /tmp/hls;
}
# MPEG-DASH is similar to HLS
application dash {
live on;
dash on;
dash_path /tmp/dash;
}
}
}
# 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
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp;
add_header Cache-Control no-cache;
}
location /dash {
# Serve DASH fragments
root /tmp;
add_header Cache-Control no-cache;
}
}
}
### Multi-worker streaming example
rtmp_auto_push on;
rtmp {
server {
listen 1935;
application mytv {
live on;
}
}
}
================================================
FILE: config
================================================
ngx_addon_name="ngx_rtmp_module"
RTMP_CORE_MODULES=" \
ngx_rtmp_module \
ngx_rtmp_core_module \
ngx_rtmp_cmd_module \
ngx_rtmp_codec_module \
ngx_rtmp_access_module \
ngx_rtmp_record_module \
ngx_rtmp_live_module \
ngx_rtmp_play_module \
ngx_rtmp_flv_module \
ngx_rtmp_mp4_module \
ngx_rtmp_netcall_module \
ngx_rtmp_relay_module \
ngx_rtmp_exec_module \
ngx_rtmp_auto_push_module \
ngx_rtmp_auto_push_index_module \
ngx_rtmp_notify_module \
ngx_rtmp_log_module \
ngx_rtmp_limit_module \
ngx_rtmp_hls_module \
ngx_rtmp_dash_module \
"
RTMP_HTTP_MODULES=" \
ngx_rtmp_stat_module \
ngx_rtmp_control_module \
"
RTMP_DEPS=" \
$ngx_addon_dir/ngx_rtmp_amf.h \
$ngx_addon_dir/ngx_rtmp_bandwidth.h \
$ngx_addon_dir/ngx_rtmp_cmd_module.h \
$ngx_addon_dir/ngx_rtmp_codec_module.h \
$ngx_addon_dir/ngx_rtmp_eval.h \
$ngx_addon_dir/ngx_rtmp.h \
$ngx_addon_dir/ngx_rtmp_version.h \
$ngx_addon_dir/ngx_rtmp_live_module.h \
$ngx_addon_dir/ngx_rtmp_netcall_module.h \
$ngx_addon_dir/ngx_rtmp_play_module.h \
$ngx_addon_dir/ngx_rtmp_record_module.h \
$ngx_addon_dir/ngx_rtmp_relay_module.h \
$ngx_addon_dir/ngx_rtmp_streams.h \
$ngx_addon_dir/ngx_rtmp_bitop.h \
$ngx_addon_dir/ngx_rtmp_proxy_protocol.h \
$ngx_addon_dir/hls/ngx_rtmp_mpegts.h \
$ngx_addon_dir/dash/ngx_rtmp_mp4.h \
"
RTMP_CORE_SRCS=" \
$ngx_addon_dir/ngx_rtmp.c \
$ngx_addon_dir/ngx_rtmp_init.c \
$ngx_addon_dir/ngx_rtmp_handshake.c \
$ngx_addon_dir/ngx_rtmp_handler.c \
$ngx_addon_dir/ngx_rtmp_amf.c \
$ngx_addon_dir/ngx_rtmp_send.c \
$ngx_addon_dir/ngx_rtmp_shared.c \
$ngx_addon_dir/ngx_rtmp_eval.c \
$ngx_addon_dir/ngx_rtmp_receive.c \
$ngx_addon_dir/ngx_rtmp_core_module.c \
$ngx_addon_dir/ngx_rtmp_cmd_module.c \
$ngx_addon_dir/ngx_rtmp_codec_module.c \
$ngx_addon_dir/ngx_rtmp_access_module.c \
$ngx_addon_dir/ngx_rtmp_record_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_netcall_module.c \
$ngx_addon_dir/ngx_rtmp_relay_module.c \
$ngx_addon_dir/ngx_rtmp_bandwidth.c \
$ngx_addon_dir/ngx_rtmp_exec_module.c \
$ngx_addon_dir/ngx_rtmp_auto_push_module.c \
$ngx_addon_dir/ngx_rtmp_notify_module.c \
$ngx_addon_dir/ngx_rtmp_log_module.c \
$ngx_addon_dir/ngx_rtmp_limit_module.c \
$ngx_addon_dir/ngx_rtmp_bitop.c \
$ngx_addon_dir/ngx_rtmp_proxy_protocol.c \
$ngx_addon_dir/hls/ngx_rtmp_hls_module.c \
$ngx_addon_dir/dash/ngx_rtmp_dash_module.c \
$ngx_addon_dir/hls/ngx_rtmp_mpegts.c \
$ngx_addon_dir/dash/ngx_rtmp_mp4.c \
"
RTMP_HTTP_SRCS=" \
$ngx_addon_dir/ngx_rtmp_stat_module.c \
$ngx_addon_dir/ngx_rtmp_control_module.c \
"
if [ -f auto/module ] ; then
ngx_module_incs=$ngx_addon_dir
ngx_module_deps=$RTMP_DEPS
if [ $ngx_module_link = DYNAMIC ] ; then
ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES"
ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
. auto/module
else
ngx_module_type=CORE
ngx_module_name=$RTMP_CORE_MODULES
ngx_module_srcs=$RTMP_CORE_SRCS
. auto/module
ngx_module_type=HTTP
ngx_module_name=$RTMP_HTTP_MODULES
ngx_module_incs=
ngx_module_deps=
ngx_module_srcs=$RTMP_HTTP_SRCS
. auto/module
fi
else
CORE_MODULES="$CORE_MODULES $RTMP_CORE_MODULES"
HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
CFLAGS="$CFLAGS -I$ngx_addon_dir"
fi
USE_OPENSSL=YES
================================================
FILE: dash/ngx_rtmp_dash_module.c
================================================
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_rtmp.h>
#include <ngx_rtmp_codec_module.h>
#include "ngx_rtmp_live_module.h"
#include "ngx_rtmp_mp4.h"
static ngx_rtmp_publish_pt next_publish;
static ngx_rtmp_close_stream_pt next_close_stream;
static ngx_rtmp_stream_begin_pt next_stream_begin;
static ngx_rtmp_stream_eof_pt next_stream_eof;
static ngx_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf);
static void * ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf);
static char * ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s);
#define NGX_RTMP_DASH_BUFSIZE (1024*1024)
#define NGX_RTMP_DASH_MAX_MDAT (10*1024*1024)
#define NGX_RTMP_DASH_MAX_SAMPLES 1024
#define NGX_RTMP_DASH_DIR_ACCESS 0744
typedef struct {
uint32_t timestamp;
uint32_t duration;
} ngx_rtmp_dash_frag_t;
typedef struct {
ngx_uint_t id;
ngx_uint_t opened;
ngx_uint_t mdat_size;
ngx_uint_t sample_count;
ngx_uint_t sample_mask;
ngx_fd_t fd;
char type;
uint32_t earliest_pres_time;
uint32_t latest_pres_time;
ngx_rtmp_mp4_sample_t samples[NGX_RTMP_DASH_MAX_SAMPLES];
} ngx_rtmp_dash_track_t;
typedef struct {
ngx_str_t playlist;
ngx_str_t playlist_bak;
ngx_str_t name;
ngx_str_t stream;
time_t start_time;
ngx_uint_t nfrags;
ngx_uint_t frag;
ngx_rtmp_dash_frag_t *frags; /* circular 2 * winfrags + 1 */
unsigned opened:1;
unsigned has_video:1;
unsigned has_audio:1;
ngx_file_t video_file;
ngx_file_t audio_file;
ngx_uint_t id;
ngx_rtmp_dash_track_t audio;
ngx_rtmp_dash_track_t video;
} ngx_rtmp_dash_ctx_t;
typedef struct {
ngx_str_t path;
ngx_msec_t playlen;
} ngx_rtmp_dash_cleanup_t;
typedef struct {
ngx_flag_t dash;
ngx_msec_t fraglen;
ngx_msec_t playlen;
ngx_flag_t nested;
ngx_str_t path;
ngx_uint_t winfrags;
ngx_flag_t cleanup;
ngx_path_t *slot;
} ngx_rtmp_dash_app_conf_t;
static ngx_command_t ngx_rtmp_dash_commands[] = {
{ ngx_string("dash"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_dash_app_conf_t, dash),
NULL },
{ ngx_string("dash_fragment"),
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_dash_app_conf_t, fraglen),
NULL },
{ ngx_string("dash_path"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_dash_app_conf_t, path),
NULL },
{ ngx_string("dash_playlist_length"),
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_dash_app_conf_t, playlen),
NULL },
{ ngx_string("dash_cleanup"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_dash_app_conf_t, cleanup),
NULL },
{ ngx_string("dash_nested"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_dash_app_conf_t, nested),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_dash_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_dash_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_dash_create_app_conf, /* create location configuration */
ngx_rtmp_dash_merge_app_conf, /* merge location configuration */
};
ngx_module_t ngx_rtmp_dash_module = {
NGX_MODULE_V1,
&ngx_rtmp_dash_module_ctx, /* module context */
ngx_rtmp_dash_commands, /* 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_rtmp_dash_frag_t *
ngx_rtmp_dash_get_frag(ngx_rtmp_session_t *s, ngx_int_t n)
{
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
return &ctx->frags[(ctx->frag + n) % (dacf->winfrags * 2 + 1)];
}
static void
ngx_rtmp_dash_next_frag(ngx_rtmp_session_t *s)
{
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
if (ctx->nfrags == dacf->winfrags) {
ctx->frag++;
} else {
ctx->nfrags++;
}
}
static ngx_int_t
ngx_rtmp_dash_rename_file(u_char *src, u_char *dst)
{
/* rename file with overwrite */
#if (NGX_WIN32)
return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING);
#else
return ngx_rename_file(src, dst);
#endif
}
static ngx_int_t
ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
{
char *sep;
u_char *p, *last;
ssize_t n;
ngx_fd_t fd;
struct tm tm;
ngx_str_t noname, *name;
ngx_uint_t i;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
ngx_rtmp_dash_frag_t *f;
ngx_rtmp_dash_app_conf_t *dacf;
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
static u_char start_time[sizeof("1970-09-28T12:00:00Z")];
static u_char pub_time[sizeof("1970-09-28T12:00:00Z")];
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (dacf == NULL || ctx == NULL || codec_ctx == NULL) {
return NGX_ERROR;
}
if (ctx->id == 0) {
ngx_rtmp_dash_write_init_segments(s);
}
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,
"dash: open failed: '%V'", &ctx->playlist_bak);
return NGX_ERROR;
}
#define NGX_RTMP_DASH_MANIFEST_HEADER \
"<?xml version=\"1.0\"?>\n" \
"<MPD\n" \
" type=\"dynamic\"\n" \
" xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" \
" availabilityStartTime=\"%s\"\n" \
" publishTime=\"%s\"\n" \
" minimumUpdatePeriod=\"PT%uiS\"\n" \
" minBufferTime=\"PT%uiS\"\n" \
" timeShiftBufferDepth=\"PT%uiS\"\n" \
" profiles=\"urn:hbbtv:dash:profile:isoff-live:2012," \
"urn:mpeg:dash:profile:isoff-live:2011\"\n" \
" xmlns:xsi=\"http://www.w3.org/2011/XMLSchema-instance\"\n" \
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\">\n" \
" <Period start=\"PT0S\" id=\"dash\">\n"
#define NGX_RTMP_DASH_MANIFEST_VIDEO \
" <AdaptationSet\n" \
" id=\"1\"\n" \
" segmentAlignment=\"true\"\n" \
" maxWidth=\"%ui\"\n" \
" maxHeight=\"%ui\"\n" \
" maxFrameRate=\"%ui\">\n" \
" <Representation\n" \
" id=\"%V_H264\"\n" \
" mimeType=\"video/mp4\"\n" \
" codecs=\"avc1.%02uxi%02uxi%02uxi\"\n" \
" width=\"%ui\"\n" \
" height=\"%ui\"\n" \
" frameRate=\"%ui\"\n" \
" startWithSAP=\"1\"\n" \
" bandwidth=\"%ui\">\n" \
" <SegmentTemplate\n" \
" timescale=\"1000\"\n" \
" media=\"%V%s$Time$.m4v\"\n" \
" initialization=\"%V%sinit.m4v\">\n" \
" <SegmentTimeline>\n"
#define NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER \
" </SegmentTimeline>\n" \
" </SegmentTemplate>\n" \
" </Representation>\n" \
" </AdaptationSet>\n"
#define NGX_RTMP_DASH_MANIFEST_TIME \
" <S t=\"%uD\" d=\"%uD\"/>\n"
#define NGX_RTMP_DASH_MANIFEST_AUDIO \
" <AdaptationSet\n" \
" id=\"2\"\n" \
" segmentAlignment=\"true\">\n" \
" <AudioChannelConfiguration\n" \
" schemeIdUri=\"urn:mpeg:dash:" \
"23003:3:audio_channel_configuration:2011\"\n" \
" value=\"1\"/>\n" \
" <Representation\n" \
" id=\"%V_AAC\"\n" \
" mimeType=\"audio/mp4\"\n" \
" codecs=\"mp4a.%s\"\n" \
" audioSamplingRate=\"%ui\"\n" \
" startWithSAP=\"1\"\n" \
" bandwidth=\"%ui\">\n" \
" <SegmentTemplate\n" \
" timescale=\"1000\"\n" \
" media=\"%V%s$Time$.m4a\"\n" \
" initialization=\"%V%sinit.m4a\">\n" \
" <SegmentTimeline>\n"
#define NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER \
" </SegmentTimeline>\n" \
" </SegmentTemplate>\n" \
" </Representation>\n" \
" </AdaptationSet>\n"
#define NGX_RTMP_DASH_MANIFEST_FOOTER \
" </Period>\n" \
"</MPD>\n"
ngx_libc_gmtime(ctx->start_time, &tm);
ngx_sprintf(start_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z",
tm.tm_year + 1900, tm.tm_mon + 1,
tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec);
ngx_libc_gmtime(ngx_time(), &tm);
ngx_sprintf(pub_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z",
tm.tm_year + 1900, tm.tm_mon + 1,
tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec);
last = buffer + sizeof(buffer);
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER,
start_time,
pub_time,
(ngx_uint_t) (dacf->fraglen / 1000),
(ngx_uint_t) (dacf->fraglen / 1000),
(ngx_uint_t) (dacf->fraglen / 250 + 1));
/*
* timeShiftBufferDepth formula:
* 2 * minBufferTime + max_fragment_length + 1
*/
n = ngx_write_fd(fd, buffer, p - buffer);
ngx_str_null(&noname);
name = (dacf->nested ? &noname : &ctx->name);
sep = (dacf->nested ? "" : "-");
if (ctx->has_video) {
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO,
codec_ctx->width,
codec_ctx->height,
codec_ctx->frame_rate,
&ctx->name,
codec_ctx->avc_profile,
codec_ctx->avc_compat,
codec_ctx->avc_level,
codec_ctx->width,
codec_ctx->height,
codec_ctx->frame_rate,
(ngx_uint_t) (codec_ctx->video_data_rate * 1000),
name, sep,
name, sep);
for (i = 0; i < ctx->nfrags; i++) {
f = ngx_rtmp_dash_get_frag(s, i);
p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME,
f->timestamp, f->duration);
}
p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER);
n = ngx_write_fd(fd, buffer, p - buffer);
}
if (ctx->has_audio) {
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_AUDIO,
&ctx->name,
codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ?
(codec_ctx->aac_sbr ? "40.5" : "40.2") : "6b",
codec_ctx->sample_rate,
(ngx_uint_t) (codec_ctx->audio_data_rate * 1000),
name, sep,
name, sep);
for (i = 0; i < ctx->nfrags; i++) {
f = ngx_rtmp_dash_get_frag(s, i);
p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME,
f->timestamp, f->duration);
}
p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER);
n = ngx_write_fd(fd, buffer, p - buffer);
}
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER);
n = ngx_write_fd(fd, buffer, p - buffer);
if (n < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: write failed: '%V'", &ctx->playlist_bak);
ngx_close_file(fd);
return NGX_ERROR;
}
ngx_close_file(fd);
if (ngx_rtmp_dash_rename_file(ctx->playlist_bak.data, ctx->playlist.data)
== NGX_FILE_ERROR)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: rename failed: '%V'->'%V'",
&ctx->playlist_bak, &ctx->playlist);
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s)
{
ngx_fd_t fd;
ngx_int_t rc;
ngx_buf_t b;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (ctx == NULL || codec_ctx == NULL) {
return NGX_ERROR;
}
/* init video */
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4v") = 0;
fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
NGX_FILE_DEFAULT_ACCESS);
if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: error creating video init file");
return NGX_ERROR;
}
b.start = buffer;
b.end = b.start + sizeof(buffer);
b.pos = b.last = b.start;
ngx_rtmp_mp4_write_ftyp(&b);
ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_VIDEO_TRACK);
rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start));
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: writing video init failed");
}
ngx_close_file(fd);
/* init audio */
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4a") = 0;
fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
NGX_FILE_DEFAULT_ACCESS);
if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: error creating dash audio init file");
return NGX_ERROR;
}
b.pos = b.last = b.start;
ngx_rtmp_mp4_write_ftyp(&b);
ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_AUDIO_TRACK);
rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start));
if (rc == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: writing audio init failed");
}
ngx_close_file(fd);
return NGX_OK;
}
static void
ngx_rtmp_dash_close_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t)
{
u_char *pos, *pos1;
size_t left;
ssize_t n;
ngx_fd_t fd;
ngx_buf_t b;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_frag_t *f;
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
if (!t->opened) {
return;
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: close fragment id=%ui, type=%c, pts=%uD",
t->id, t->type, t->earliest_pres_time);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
b.start = buffer;
b.end = buffer + sizeof(buffer);
b.pos = b.last = b.start;
ngx_rtmp_mp4_write_styp(&b);
pos = b.last;
b.last += 44; /* leave room for sidx */
ngx_rtmp_mp4_write_moof(&b, t->earliest_pres_time, t->sample_count,
t->samples, t->sample_mask, t->id);
pos1 = b.last;
b.last = pos;
ngx_rtmp_mp4_write_sidx(&b, t->mdat_size + 8 + (pos1 - (pos + 44)),
t->earliest_pres_time, t->latest_pres_time);
b.last = pos1;
ngx_rtmp_mp4_write_mdat(&b, t->mdat_size + 8);
/* move the data down to make room for the headers */
f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uD.m4%c",
f->timestamp, t->type) = 0;
fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR,
NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
if (fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: error creating dash temp video file");
goto done;
}
if (ngx_write_fd(fd, b.pos, (size_t) (b.last - b.pos)) == NGX_ERROR) {
goto done;
}
left = (size_t) t->mdat_size;
#if (NGX_WIN32)
if (SetFilePointer(t->fd, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"dash: SetFilePointer error");
goto done;
}
#else
if (lseek(t->fd, 0, SEEK_SET) == -1) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: lseek error");
goto done;
}
#endif
while (left > 0) {
n = ngx_read_fd(t->fd, buffer, ngx_min(sizeof(buffer), left));
if (n == NGX_ERROR) {
break;
}
n = ngx_write_fd(fd, buffer, (size_t) n);
if (n == NGX_ERROR) {
break;
}
left -= n;
}
done:
if (fd != NGX_INVALID_FILE) {
ngx_close_file(fd);
}
ngx_close_file(t->fd);
t->fd = NGX_INVALID_FILE;
t->opened = 0;
}
static ngx_int_t
ngx_rtmp_dash_close_fragments(ngx_rtmp_session_t *s)
{
ngx_rtmp_dash_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
if (ctx == NULL || !ctx->opened) {
return NGX_OK;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: close fragments");
ngx_rtmp_dash_close_fragment(s, &ctx->video);
ngx_rtmp_dash_close_fragment(s, &ctx->audio);
ngx_rtmp_dash_next_frag(s);
ngx_rtmp_dash_write_playlist(s);
ctx->id++;
ctx->opened = 0;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_open_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t,
ngx_uint_t id, char type)
{
ngx_rtmp_dash_ctx_t *ctx;
if (t->opened) {
return NGX_OK;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: open fragment id=%ui, type='%c'", id, type);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "raw.m4%c", type) = 0;
t->fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR,
NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
if (t->fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: error creating fragment file");
return NGX_ERROR;
}
t->id = id;
t->type = type;
t->sample_count = 0;
t->earliest_pres_time = 0;
t->latest_pres_time = 0;
t->mdat_size = 0;
t->opened = 1;
if (type == 'v') {
t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE|
NGX_RTMP_MP4_SAMPLE_DURATION|
NGX_RTMP_MP4_SAMPLE_DELAY|
NGX_RTMP_MP4_SAMPLE_KEY;
} else {
t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE|
NGX_RTMP_MP4_SAMPLE_DURATION;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s)
{
ngx_rtmp_dash_ctx_t *ctx;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: open fragments");
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
if (ctx->opened) {
return NGX_OK;
}
ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v');
ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a');
ctx->opened = 1;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s)
{
size_t len;
ngx_file_info_t fi;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_app_conf_t *dacf;
static u_char path[NGX_MAX_PATH + 1];
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
*ngx_snprintf(path, sizeof(path) - 1, "%V", &dacf->path) = 0;
if (ngx_file_info(path, &fi) == NGX_FILE_ERROR) {
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: " ngx_file_info_n " failed on '%V'",
&dacf->path);
return NGX_ERROR;
}
/* ENOENT */
if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: " ngx_create_dir_n " failed on '%V'",
&dacf->path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: directory '%V' created", &dacf->path);
} else {
if (!ngx_is_dir(&fi)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"dash: '%V' exists and is not a directory",
&dacf->path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: directory '%V' exists", &dacf->path);
}
if (!dacf->nested) {
return NGX_OK;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
len = dacf->path.len;
if (dacf->path.data[len - 1] == '/') {
len--;
}
*ngx_snprintf(path, sizeof(path) - 1, "%*s/%V", len, dacf->path.data,
&ctx->name) = 0;
if (ngx_file_info(path, &fi) != NGX_FILE_ERROR) {
if (ngx_is_dir(&fi)) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: directory '%s' exists", path);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"dash: '%s' exists and is not a directory", path);
return NGX_ERROR;
}
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: " ngx_file_info_n " failed on '%s'", path);
return NGX_ERROR;
}
/* NGX_ENOENT */
if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: " ngx_create_dir_n " failed on '%s'", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: directory '%s' created", path);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
u_char *p;
size_t len;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_frag_t *f;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
if (dacf == NULL || !dacf->dash || dacf->path.len == 0) {
goto next;
}
if (s->auto_pushed) {
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: publish: name='%s' type='%s'", v->name, v->type);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_dash_ctx_t));
if (ctx == NULL) {
goto next;
}
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_dash_module);
} else {
if (ctx->opened) {
goto next;
}
f = ctx->frags;
ngx_memzero(ctx, sizeof(ngx_rtmp_dash_ctx_t));
ctx->frags = f;
}
if (ctx->frags == NULL) {
ctx->frags = ngx_pcalloc(s->connection->pool,
sizeof(ngx_rtmp_dash_frag_t) *
(dacf->winfrags * 2 + 1));
if (ctx->frags == NULL) {
return NGX_ERROR;
}
}
ctx->id = 0;
if (ngx_strstr(v->name, "..")) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"dash: bad stream name: '%s'", v->name);
return NGX_ERROR;
}
ctx->name.len = ngx_strlen(v->name);
ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1);
if (ctx->name.data == NULL) {
return NGX_ERROR;
}
*ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0;
len = dacf->path.len + 1 + ctx->name.len + sizeof(".mpd");
if (dacf->nested) {
len += sizeof("/index") - 1;
}
ctx->playlist.data = ngx_palloc(s->connection->pool, len);
p = ngx_cpymem(ctx->playlist.data, dacf->path.data, dacf->path.len);
if (p[-1] != '/') {
*p++ = '/';
}
p = ngx_cpymem(p, ctx->name.data, ctx->name.len);
/*
* ctx->stream holds initial part of stream file path
* however the space for the whole stream path
* is allocated
*/
ctx->stream.len = p - ctx->playlist.data + 1;
ctx->stream.data = ngx_palloc(s->connection->pool,
ctx->stream.len + NGX_INT32_LEN +
sizeof(".m4x"));
ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1);
ctx->stream.data[ctx->stream.len - 1] = (dacf->nested ? '/' : '-');
if (dacf->nested) {
p = ngx_cpymem(p, "/index.mpd", sizeof("/index.mpd") - 1);
} else {
p = ngx_cpymem(p, ".mpd", sizeof(".mpd") - 1);
}
ctx->playlist.len = p - ctx->playlist.data;
*p = 0;
/* playlist bak (new playlist) path */
ctx->playlist_bak.data = ngx_palloc(s->connection->pool,
ctx->playlist.len + sizeof(".bak"));
p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data,
ctx->playlist.len);
p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1);
ctx->playlist_bak.len = p - ctx->playlist_bak.data;
*p = 0;
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: playlist='%V' playlist_bak='%V' stream_pattern='%V'",
&ctx->playlist, &ctx->playlist_bak, &ctx->stream);
ctx->start_time = ngx_time();
if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) {
return NGX_ERROR;
}
next:
return next_publish(s, v);
}
static ngx_int_t
ngx_rtmp_dash_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
if (dacf == NULL || !dacf->dash || ctx == NULL) {
goto next;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"dash: delete stream");
ngx_rtmp_dash_close_fragments(s);
next:
return next_close_stream(s, v);
}
static void
ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary,
uint32_t timestamp)
{
int32_t d;
ngx_int_t hit;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_dash_frag_t *f;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);
d = (int32_t) (timestamp - f->timestamp);
if (d >= 0) {
f->duration = timestamp - f->timestamp;
hit = (f->duration >= dacf->fraglen);
/* keep fragment lengths within 2x factor for dash.js */
if (f->duration >= dacf->fraglen * 2) {
boundary = 1;
}
} else {
/* sometimes clients generate slightly unordered frames */
hit = (-d > 1000);
}
if (ctx->has_video && !hit) {
boundary = 0;
}
if (!ctx->has_video && ctx->has_audio) {
boundary = hit;
}
if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {
boundary = 1;
}
if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {
boundary = 1;
}
if (!ctx->opened) {
boundary = 1;
}
if (boundary) {
ngx_rtmp_dash_close_fragments(s);
ngx_rtmp_dash_open_fragments(s);
f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);
f->timestamp = timestamp;
}
}
static ngx_int_t
ngx_rtmp_dash_append(ngx_rtmp_session_t *s, ngx_chain_t *in,
ngx_rtmp_dash_track_t *t, ngx_int_t key, uint32_t timestamp, uint32_t delay)
{
u_char *p;
size_t size, bsize;
ngx_rtmp_mp4_sample_t *smpl;
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
p = buffer;
size = 0;
for (; in && size < sizeof(buffer); in = in->next) {
bsize = (size_t) (in->buf->last - in->buf->pos);
if (size + bsize > sizeof(buffer)) {
bsize = (size_t) (sizeof(buffer) - size);
}
p = ngx_cpymem(p, in->buf->pos, bsize);
size += bsize;
}
ngx_rtmp_dash_update_fragments(s, key, timestamp);
if (t->sample_count == 0) {
t->earliest_pres_time = timestamp;
}
t->latest_pres_time = timestamp;
if (t->sample_count < NGX_RTMP_DASH_MAX_SAMPLES) {
if (ngx_write_fd(t->fd, buffer, size) == NGX_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: " ngx_write_fd_n " failed");
return NGX_ERROR;
}
smpl = &t->samples[t->sample_count];
smpl->delay = delay;
smpl->size = (uint32_t) size;
smpl->duration = 0;
smpl->timestamp = timestamp;
smpl->key = (key ? 1 : 0);
if (t->sample_count > 0) {
smpl = &t->samples[t->sample_count - 1];
smpl->duration = timestamp - smpl->timestamp;
}
t->sample_count++;
t->mdat_size += (ngx_uint_t) size;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_dash_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
u_char htype;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (dacf == NULL || !dacf->dash || ctx == NULL ||
codec_ctx == NULL || h->mlen < 2)
{
return NGX_OK;
}
/* Only AAC is supported */
if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC ||
codec_ctx->aac_header == NULL)
{
return NGX_OK;
}
if (in->buf->last - in->buf->pos < 2) {
return NGX_ERROR;
}
/* skip AAC config */
htype = in->buf->pos[1];
if (htype != 1) {
return NGX_OK;
}
ctx->has_audio = 1;
/* skip RTMP & AAC headers */
in->buf->pos += 2;
return ngx_rtmp_dash_append(s, in, &ctx->audio, 0, h->timestamp, 0);
}
static ngx_int_t
ngx_rtmp_dash_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
u_char *p;
uint8_t ftype, htype;
uint32_t delay;
ngx_rtmp_dash_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
ngx_rtmp_dash_app_conf_t *dacf;
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (dacf == NULL || !dacf->dash || ctx == NULL || codec_ctx == NULL ||
codec_ctx->avc_header == NULL || h->mlen < 5)
{
return NGX_OK;
}
/* Only H264 is supported */
if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {
return NGX_OK;
}
if (in->buf->last - in->buf->pos < 5) {
return NGX_ERROR;
}
ftype = (in->buf->pos[0] & 0xf0) >> 4;
/* skip AVC config */
htype = in->buf->pos[1];
if (htype != 1) {
return NGX_OK;
}
p = (u_char *) &delay;
p[0] = in->buf->pos[4];
p[1] = in->buf->pos[3];
p[2] = in->buf->pos[2];
p[3] = 0;
ctx->has_video = 1;
/* skip RTMP & H264 headers */
in->buf->pos += 5;
return ngx_rtmp_dash_append(s, in, &ctx->video, ftype == 1, h->timestamp,
delay);
}
static ngx_int_t
ngx_rtmp_dash_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
{
return next_stream_begin(s, v);
}
static ngx_int_t
ngx_rtmp_dash_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
{
ngx_rtmp_dash_close_fragments(s);
return next_stream_eof(s, v);
}
static ngx_int_t
ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
{
time_t mtime, max_age;
u_char *p;
u_char path[NGX_MAX_PATH + 1], mpd_path[NGX_MAX_PATH + 1];
ngx_dir_t dir;
ngx_err_t err;
ngx_str_t name, spath, mpd;
ngx_int_t nentries, nerased;
ngx_file_info_t fi;
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup path='%V' playlen=%M", ppath, playlen);
if (ngx_open_dir(ppath, &dir) != NGX_OK) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno,
"dash: cleanup open dir failed '%V'", ppath);
return NGX_ERROR;
}
nentries = 0;
nerased = 0;
for ( ;; ) {
ngx_set_errno(0);
if (ngx_read_dir(&dir) == NGX_ERROR) {
err = ngx_errno;
if (ngx_close_dir(&dir) == NGX_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
"dash: cleanup " ngx_close_dir_n " \"%V\" failed",
ppath);
}
if (err == NGX_ENOMOREFILES) {
return nentries - nerased;
}
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err,
"dash: cleanup " ngx_read_dir_n
" '%V' failed", ppath);
return NGX_ERROR;
}
name.data = ngx_de_name(&dir);
if (name.data[0] == '.') {
continue;
}
name.len = ngx_de_namelen(&dir);
p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name);
*p = 0;
spath.data = path;
spath.len = p - path;
nentries++;
if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
"dash: cleanup " ngx_de_info_n " \"%V\" failed",
&spath);
continue;
}
if (ngx_de_is_dir(&dir)) {
if (ngx_rtmp_dash_cleanup_dir(&spath, playlen) == 0) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup dir '%V'", &name);
/*
* null-termination gets spoiled in win32
* version of ngx_open_dir
*/
*p = 0;
if (ngx_delete_dir(path) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
"dash: cleanup " ngx_delete_dir_n
" failed on '%V'", &spath);
} else {
nerased++;
}
}
continue;
}
if (!ngx_de_is_file(&dir)) {
continue;
}
if (name.len >= 8 && name.data[name.len - 8] == 'i' &&
name.data[name.len - 7] == 'n' &&
name.data[name.len - 6] == 'i' &&
name.data[name.len - 5] == 't' &&
name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'm' &&
name.data[name.len - 2] == '4')
{
if (name.len == 8) {
ngx_str_set(&mpd, "index");
} else {
mpd.data = name.data;
mpd.len = name.len - 9;
}
p = ngx_snprintf(mpd_path, sizeof(mpd_path) - 1, "%V/%V.mpd",
ppath, &mpd);
*p = 0;
if (ngx_file_info(mpd_path, &fi) != NGX_FILE_ERROR) {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup '%V' delayed, mpd exists '%s'",
&name, mpd_path);
continue;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup '%V' allowed, mpd missing '%s'",
&name, mpd_path);
max_age = 0;
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'm' &&
name.data[name.len - 2] == '4' &&
name.data[name.len - 1] == 'v')
{
max_age = playlen / 500;
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'm' &&
name.data[name.len - 2] == '4' &&
name.data[name.len - 1] == 'a')
{
max_age = playlen / 500;
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'm' &&
name.data[name.len - 2] == 'p' &&
name.data[name.len - 1] == 'd')
{
max_age = playlen / 500;
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'r' &&
name.data[name.len - 2] == 'a' &&
name.data[name.len - 1] == 'w')
{
max_age = playlen / 1000;
} else {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup skip unknown file type '%V'", &name);
continue;
}
mtime = ngx_de_mtime(&dir);
if (mtime + max_age > ngx_cached_time->sec) {
continue;
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"dash: cleanup '%V' mtime=%T age=%T",
&name, mtime, ngx_cached_time->sec - mtime);
if (ngx_delete_file(path) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
"dash: cleanup " ngx_delete_file_n " failed on '%V'",
&spath);
continue;
}
nerased++;
}
}
#if (nginx_version >= 1011005)
static ngx_msec_t
#else
static time_t
#endif
ngx_rtmp_dash_cleanup(void *data)
{
ngx_rtmp_dash_cleanup_t *cleanup = data;
ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen);
#if (nginx_version >= 1011005)
return cleanup->playlen * 2;
#else
return cleanup->playlen / 500;
#endif
}
static void *
ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_dash_app_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dash_app_conf_t));
if (conf == NULL) {
return NULL;
}
conf->dash = NGX_CONF_UNSET;
conf->fraglen = NGX_CONF_UNSET_MSEC;
conf->playlen = NGX_CONF_UNSET_MSEC;
conf->cleanup = NGX_CONF_UNSET;
conf->nested = NGX_CONF_UNSET;
return conf;
}
static char *
ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_dash_app_conf_t *prev = parent;
ngx_rtmp_dash_app_conf_t *conf = child;
ngx_rtmp_dash_cleanup_t *cleanup;
ngx_conf_merge_value(conf->dash, prev->dash, 0);
ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);
ngx_conf_merge_value(conf->nested, prev->nested, 0);
if (conf->fraglen) {
conf->winfrags = conf->playlen / conf->fraglen;
}
/* schedule cleanup */
if (conf->dash && conf->path.len && conf->cleanup) {
if (conf->path.data[conf->path.len - 1] == '/') {
conf->path.len--;
}
cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
if (cleanup == NULL) {
return NGX_CONF_ERROR;
}
cleanup->path = conf->path;
cleanup->playlen = conf->playlen;
conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));
if (conf->slot == NULL) {
return NGX_CONF_ERROR;
}
conf->slot->manager = ngx_rtmp_dash_cleanup;
conf->slot->name = conf->path;
conf->slot->data = cleanup;
conf->slot->conf_file = cf->conf_file->file.name.data;
conf->slot->line = cf->conf_file->line;
if (ngx_add_path(cf, &conf->slot) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
ngx_conf_merge_str_value(conf->path, prev->path, "");
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_handler_pt *h;
ngx_rtmp_core_main_conf_t *cmcf;
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
*h = ngx_rtmp_dash_video;
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
*h = ngx_rtmp_dash_audio;
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_dash_publish;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_rtmp_dash_close_stream;
next_stream_begin = ngx_rtmp_stream_begin;
ngx_rtmp_stream_begin = ngx_rtmp_dash_stream_begin;
next_stream_eof = ngx_rtmp_stream_eof;
ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof;
return NGX_OK;
}
================================================
FILE: dash/ngx_rtmp_mp4.c
================================================
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_mp4.h"
#include <ngx_rtmp_codec_module.h>
static ngx_int_t
ngx_rtmp_mp4_field_32(ngx_buf_t *b, uint32_t n)
{
u_char bytes[4];
bytes[0] = ((uint32_t) n >> 24) & 0xFF;
bytes[1] = ((uint32_t) n >> 16) & 0xFF;
bytes[2] = ((uint32_t) n >> 8) & 0xFF;
bytes[3] = (uint32_t) n & 0xFF;
if (b->last + sizeof(bytes) > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_field_24(ngx_buf_t *b, uint32_t n)
{
u_char bytes[3];
bytes[0] = ((uint32_t) n >> 16) & 0xFF;
bytes[1] = ((uint32_t) n >> 8) & 0xFF;
bytes[2] = (uint32_t) n & 0xFF;
if (b->last + sizeof(bytes) > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_field_16(ngx_buf_t *b, uint16_t n)
{
u_char bytes[2];
bytes[0] = ((uint32_t) n >> 8) & 0xFF;
bytes[1] = (uint32_t) n & 0xFF;
if (b->last + sizeof(bytes) > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_field_8(ngx_buf_t *b, uint8_t n)
{
u_char bytes[1];
bytes[0] = n & 0xFF;
if (b->last + sizeof(bytes) > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_put_descr(ngx_buf_t *b, int tag, size_t size)
{
ngx_rtmp_mp4_field_8(b, (uint8_t) tag);
ngx_rtmp_mp4_field_8(b, size & 0x7F);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_data(ngx_buf_t *b, void *data, size_t n)
{
if (b->last + n > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, (u_char *) data, n);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_box(ngx_buf_t *b, const char box[4])
{
if (b->last + 4 > b->end) {
return NGX_ERROR;
}
b->last = ngx_cpymem(b->last, (u_char *) box, 4);
return NGX_OK;
}
static u_char *
ngx_rtmp_mp4_start_box(ngx_buf_t *b, const char box[4])
{
u_char *p;
p = b->last;
if (ngx_rtmp_mp4_field_32(b, 0) != NGX_OK) {
return NULL;
}
if (ngx_rtmp_mp4_box(b, box) != NGX_OK) {
return NULL;
}
return p;
}
static ngx_int_t
ngx_rtmp_mp4_update_box_size(ngx_buf_t *b, u_char *p)
{
u_char *curpos;
if (p == NULL) {
return NGX_ERROR;
}
curpos = b->last;
b->last = p;
ngx_rtmp_mp4_field_32(b, (uint32_t) (curpos - p));
b->last = curpos;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_matrix(ngx_buf_t *buf, uint32_t a, uint32_t b, uint32_t c,
uint32_t d, uint32_t tx, uint32_t ty)
{
/*
* transformation matrix
* |a b u|
* |c d v|
* |tx ty w|
*/
ngx_rtmp_mp4_field_32(buf, a << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, b << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, 0); /* u in 2.30 format */
ngx_rtmp_mp4_field_32(buf, c << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, d << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, 0); /* v in 2.30 format */
ngx_rtmp_mp4_field_32(buf, tx << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, ty << 16); /* 16.16 format */
ngx_rtmp_mp4_field_32(buf, 1 << 30); /* w in 2.30 format */
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "ftyp");
/* major brand */
ngx_rtmp_mp4_box(b, "iso6");
/* minor version */
ngx_rtmp_mp4_field_32(b, 1);
/* compatible brands */
ngx_rtmp_mp4_box(b, "isom");
ngx_rtmp_mp4_box(b, "iso6");
ngx_rtmp_mp4_box(b, "dash");
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mp4_write_styp(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "styp");
/* major brand */
ngx_rtmp_mp4_box(b, "iso6");
/* minor version */
ngx_rtmp_mp4_field_32(b, 1);
/* compatible brands */
ngx_rtmp_mp4_box(b, "isom");
ngx_rtmp_mp4_box(b, "iso6");
ngx_rtmp_mp4_box(b, "dash");
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_mvhd(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "mvhd");
/* version */
ngx_rtmp_mp4_field_32(b, 0);
/* creation time */
ngx_rtmp_mp4_field_32(b, 0);
/* modification time */
ngx_rtmp_mp4_field_32(b, 0);
/* timescale */
ngx_rtmp_mp4_field_32(b, 1000);
/* duration */
ngx_rtmp_mp4_field_32(b, 0);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0x00010000);
ngx_rtmp_mp4_field_16(b, 0x0100);
ngx_rtmp_mp4_field_16(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
/* next track id */
ngx_rtmp_mp4_field_32(b, 1);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_tkhd(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
ngx_rtmp_codec_ctx_t *codec_ctx;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
pos = ngx_rtmp_mp4_start_box(b, "tkhd");
/* version */
ngx_rtmp_mp4_field_8(b, 0);
/* flags: TrackEnabled */
ngx_rtmp_mp4_field_24(b, 0x0000000f);
/* creation time */
ngx_rtmp_mp4_field_32(b, 0);
/* modification time */
ngx_rtmp_mp4_field_32(b, 0);
/* track id */
ngx_rtmp_mp4_field_32(b, 1);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
/* duration */
ngx_rtmp_mp4_field_32(b, 0);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
/* reserved */
ngx_rtmp_mp4_field_16(b, ttype == NGX_RTMP_MP4_VIDEO_TRACK ? 0 : 0x0100);
/* reserved */
ngx_rtmp_mp4_field_16(b, 0);
ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0);
if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {
ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->width << 16);
ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->height << 16);
} else {
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
}
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_mdhd(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "mdhd");
/* version */
ngx_rtmp_mp4_field_32(b, 0);
/* creation time */
ngx_rtmp_mp4_field_32(b, 0);
/* modification time */
ngx_rtmp_mp4_field_32(b, 0);
/* time scale*/
ngx_rtmp_mp4_field_32(b, 1000);
/* duration */
ngx_rtmp_mp4_field_32(b, 0);
/* lanuguage */
ngx_rtmp_mp4_field_16(b, 0x15C7);
/* reserved */
ngx_rtmp_mp4_field_16(b, 0);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_hdlr(ngx_buf_t *b, ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "hdlr");
/* version and flags */
ngx_rtmp_mp4_field_32(b, 0);
/* pre defined */
ngx_rtmp_mp4_field_32(b, 0);
if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {
ngx_rtmp_mp4_box(b, "vide");
} else {
ngx_rtmp_mp4_box(b, "soun");
}
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {
/* video handler string, NULL-terminated */
ngx_rtmp_mp4_data(b, "VideoHandler", sizeof("VideoHandler"));
} else {
/* sound handler string, NULL-terminated */
ngx_rtmp_mp4_data(b, "SoundHandler", sizeof("SoundHandler"));
}
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_vmhd(ngx_buf_t *b)
{
/* size is always 20, apparently */
ngx_rtmp_mp4_field_32(b, 20);
ngx_rtmp_mp4_box(b, "vmhd");
/* version and flags */
ngx_rtmp_mp4_field_32(b, 0x01);
/* reserved (graphics mode=copy) */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_smhd(ngx_buf_t *b)
{
/* size is always 16, apparently */
ngx_rtmp_mp4_field_32(b, 16);
ngx_rtmp_mp4_box(b, "smhd");
/* version and flags */
ngx_rtmp_mp4_field_32(b, 0);
/* reserved (balance normally=0) */
ngx_rtmp_mp4_field_16(b, 0);
ngx_rtmp_mp4_field_16(b, 0);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_dref(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "dref");
/* version and flags */
ngx_rtmp_mp4_field_32(b, 0);
/* entry count */
ngx_rtmp_mp4_field_32(b, 1);
/* url size */
ngx_rtmp_mp4_field_32(b, 0xc);
ngx_rtmp_mp4_box(b, "url ");
/* version and flags */
ngx_rtmp_mp4_field_32(b, 0x00000001);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_dinf(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "dinf");
ngx_rtmp_mp4_write_dref(b);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_avcc(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
u_char *pos, *p;
ngx_chain_t *in;
ngx_rtmp_codec_ctx_t *codec_ctx;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (codec_ctx == NULL) {
return NGX_ERROR;
}
in = codec_ctx->avc_header;
if (in == NULL) {
return NGX_ERROR;
}
pos = ngx_rtmp_mp4_start_box(b, "avcC");
/* assume config fits one chunk (highly probable) */
/*
* Skip:
* - flv fmt
* - H264 CONF/PICT (0x00)
* - 0
* - 0
* - 0
*/
p = in->buf->pos + 5;
if (p < in->buf->last) {
ngx_rtmp_mp4_data(b, p, (size_t) (in->buf->last - p));
} else {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"dash: invalid avcc received");
}
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_video(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
u_char *pos;
ngx_rtmp_codec_ctx_t *codec_ctx;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
pos = ngx_rtmp_mp4_start_box(b, "avc1");
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_16(b, 0);
/* data reference index */
ngx_rtmp_mp4_field_16(b, 1);
/* codec stream version & revision */
ngx_rtmp_mp4_field_16(b, 0);
ngx_rtmp_mp4_field_16(b, 0);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
/* width & height */
ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->width);
ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->height);
/* horizontal & vertical resolutions 72 dpi */
ngx_rtmp_mp4_field_32(b, 0x00480000);
ngx_rtmp_mp4_field_32(b, 0x00480000);
/* data size */
ngx_rtmp_mp4_field_32(b, 0);
/* frame count */
ngx_rtmp_mp4_field_16(b, 1);
/* compressor name */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_16(b, 0x18);
ngx_rtmp_mp4_field_16(b, 0xffff);
ngx_rtmp_mp4_write_avcc(s, b);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_esds(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
size_t dsi_len;
u_char *pos, *dsi;
ngx_buf_t *db;
ngx_rtmp_codec_ctx_t *codec_ctx;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (codec_ctx == NULL || codec_ctx->aac_header == NULL) {
return NGX_ERROR;
}
db = codec_ctx->aac_header->buf;
if (db == NULL) {
return NGX_ERROR;
}
dsi = db->pos + 2;
if (dsi > db->last) {
return NGX_ERROR;
}
dsi_len = db->last - dsi;
pos = ngx_rtmp_mp4_start_box(b, "esds");
/* version */
ngx_rtmp_mp4_field_32(b, 0);
/* ES Descriptor */
ngx_rtmp_mp4_put_descr(b, 0x03, 23 + dsi_len);
/* ES_ID */
ngx_rtmp_mp4_field_16(b, 1);
/* flags */
ngx_rtmp_mp4_field_8(b, 0);
/* DecoderConfig Descriptor */
ngx_rtmp_mp4_put_descr(b, 0x04, 15 + dsi_len);
/* objectTypeIndication: Audio ISO/IEC 14496-3 (AAC) */
ngx_rtmp_mp4_field_8(b, 0x40);
/* streamType: AudioStream */
ngx_rtmp_mp4_field_8(b, 0x15);
/* bufferSizeDB */
ngx_rtmp_mp4_field_24(b, 0);
/* maxBitrate */
ngx_rtmp_mp4_field_32(b, 0x0001F151);
/* avgBitrate */
ngx_rtmp_mp4_field_32(b, 0x0001F14D);
/* DecoderSpecificInfo Descriptor */
ngx_rtmp_mp4_put_descr(b, 0x05, dsi_len);
ngx_rtmp_mp4_data(b, dsi, dsi_len);
/* SL Descriptor */
ngx_rtmp_mp4_put_descr(b, 0x06, 1);
ngx_rtmp_mp4_field_8(b, 0x02);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_audio(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
u_char *pos;
ngx_rtmp_codec_ctx_t *codec_ctx;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
pos = ngx_rtmp_mp4_start_box(b, "mp4a");
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_16(b, 0);
/* data reference index */
ngx_rtmp_mp4_field_16(b, 1);
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_field_32(b, 0);
/* channel count */
ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->audio_channels);
/* sample size */
ngx_rtmp_mp4_field_16(b, (uint16_t) (codec_ctx->sample_size * 8));
/* reserved */
ngx_rtmp_mp4_field_32(b, 0);
/* time scale */
ngx_rtmp_mp4_field_16(b, 1000);
/* sample rate */
ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->sample_rate);
ngx_rtmp_mp4_write_esds(s, b);
#if 0
/* tag size*/
ngx_rtmp_mp4_field_32(b, 8);
/* null tag */
ngx_rtmp_mp4_field_32(b, 0);
#endif
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stsd(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stsd");
/* version & flags */
ngx_rtmp_mp4_field_32(b, 0);
/* entry count */
ngx_rtmp_mp4_field_32(b, 1);
if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {
ngx_rtmp_mp4_write_video(s, b);
} else {
ngx_rtmp_mp4_write_audio(s, b);
}
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stts(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stts");
ngx_rtmp_mp4_field_32(b, 0); /* version */
ngx_rtmp_mp4_field_32(b, 0); /* entry count */
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stsc(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stsc");
ngx_rtmp_mp4_field_32(b, 0); /* version */
ngx_rtmp_mp4_field_32(b, 0); /* entry count */
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stsz(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stsz");
ngx_rtmp_mp4_field_32(b, 0); /* version */
ngx_rtmp_mp4_field_32(b, 0); /* entry count */
ngx_rtmp_mp4_field_32(b, 0); /* moar zeros */
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stco(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stco");
ngx_rtmp_mp4_field_32(b, 0); /* version */
ngx_rtmp_mp4_field_32(b, 0); /* entry count */
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_stbl(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "stbl");
ngx_rtmp_mp4_write_stsd(s, b, ttype);
ngx_rtmp_mp4_write_stts(b);
ngx_rtmp_mp4_write_stsc(b);
ngx_rtmp_mp4_write_stsz(b);
ngx_rtmp_mp4_write_stco(b);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_minf(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "minf");
if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {
ngx_rtmp_mp4_write_vmhd(b);
} else {
ngx_rtmp_mp4_write_smhd(b);
}
ngx_rtmp_mp4_write_dinf(b);
ngx_rtmp_mp4_write_stbl(s, b, ttype);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_mdia(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "mdia");
ngx_rtmp_mp4_write_mdhd(b);
ngx_rtmp_mp4_write_hdlr(b, ttype);
ngx_rtmp_mp4_write_minf(s, b, ttype);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_trak(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "trak");
ngx_rtmp_mp4_write_tkhd(s, b, ttype);
ngx_rtmp_mp4_write_mdia(s, b, ttype);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_mvex(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "mvex");
ngx_rtmp_mp4_field_32(b, 0x20);
ngx_rtmp_mp4_box(b, "trex");
/* version & flags */
ngx_rtmp_mp4_field_32(b, 0);
/* track id */
ngx_rtmp_mp4_field_32(b, 1);
/* default sample description index */
ngx_rtmp_mp4_field_32(b, 1);
/* default sample duration */
ngx_rtmp_mp4_field_32(b, 0);
/* default sample size, 1024 for AAC */
ngx_rtmp_mp4_field_32(b, 0);
/* default sample flags, key on */
ngx_rtmp_mp4_field_32(b, 0);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "moov");
ngx_rtmp_mp4_write_mvhd(b);
ngx_rtmp_mp4_write_mvex(b);
ngx_rtmp_mp4_write_trak(s, b, ttype);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_tfhd(ngx_buf_t *b)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "tfhd");
/* version & flags */
ngx_rtmp_mp4_field_32(b, 0x00020000);
/* track id */
ngx_rtmp_mp4_field_32(b, 1);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_tfdt(ngx_buf_t *b, uint32_t earliest_pres_time)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "tfdt");
/* version == 1 aka 64 bit integer */
ngx_rtmp_mp4_field_32(b, 0x00000000);
ngx_rtmp_mp4_field_32(b, earliest_pres_time);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_trun(ngx_buf_t *b, uint32_t sample_count,
ngx_rtmp_mp4_sample_t *samples, ngx_uint_t sample_mask, u_char *moof_pos)
{
u_char *pos;
uint32_t i, offset, nitems, flags;
pos = ngx_rtmp_mp4_start_box(b, "trun");
nitems = 0;
/* data offset present */
flags = 0x01;
if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) {
nitems++;
flags |= 0x000100;
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) {
nitems++;
flags |= 0x000200;
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) {
nitems++;
flags |= 0x000400;
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) {
nitems++;
flags |= 0x000800;
}
offset = (pos - moof_pos) + 20 + (sample_count * nitems * 4) + 8;
ngx_rtmp_mp4_field_32(b, flags);
ngx_rtmp_mp4_field_32(b, sample_count);
ngx_rtmp_mp4_field_32(b, offset);
for (i = 0; i < sample_count; i++, samples++) {
if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) {
ngx_rtmp_mp4_field_32(b, samples->duration);
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) {
ngx_rtmp_mp4_field_32(b, samples->size);
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) {
ngx_rtmp_mp4_field_32(b, samples->key ? 0x00000000 : 0x00010000);
}
if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) {
ngx_rtmp_mp4_field_32(b, samples->delay);
}
}
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_traf(ngx_buf_t *b, uint32_t earliest_pres_time,
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
ngx_uint_t sample_mask, u_char *moof_pos)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "traf");
ngx_rtmp_mp4_write_tfhd(b);
ngx_rtmp_mp4_write_tfdt(b, earliest_pres_time);
ngx_rtmp_mp4_write_trun(b, sample_count, samples, sample_mask, moof_pos);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mp4_write_mfhd(ngx_buf_t *b, uint32_t index)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "mfhd");
/* don't know what this is */
ngx_rtmp_mp4_field_32(b, 0);
/* fragment index. */
ngx_rtmp_mp4_field_32(b, index);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, ngx_uint_t reference_size,
uint32_t earliest_pres_time, uint32_t latest_pres_time)
{
u_char *pos;
uint32_t duration;
duration = latest_pres_time - earliest_pres_time;
pos = ngx_rtmp_mp4_start_box(b, "sidx");
/* version */
ngx_rtmp_mp4_field_32(b, 0);
/* reference id */
ngx_rtmp_mp4_field_32(b, 1);
/* timescale */
ngx_rtmp_mp4_field_32(b, 1000);
/* earliest presentation time */
ngx_rtmp_mp4_field_32(b, earliest_pres_time);
/* first offset */
ngx_rtmp_mp4_field_32(b, duration); /*TODO*/
/* reserved */
ngx_rtmp_mp4_field_16(b, 0);
/* reference count = 1 */
ngx_rtmp_mp4_field_16(b, 1);
/* 1st bit is reference type, the rest is reference size */
ngx_rtmp_mp4_field_32(b, reference_size);
/* subsegment duration */
ngx_rtmp_mp4_field_32(b, duration);
/* first bit is startsWithSAP (=1), next 3 bits are SAP type (=001) */
ngx_rtmp_mp4_field_8(b, 0x90);
/* SAP delta time */
ngx_rtmp_mp4_field_24(b, 0);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
ngx_uint_t sample_mask, uint32_t index)
{
u_char *pos;
pos = ngx_rtmp_mp4_start_box(b, "moof");
ngx_rtmp_mp4_write_mfhd(b, index);
ngx_rtmp_mp4_write_traf(b, earliest_pres_time, sample_count, samples,
sample_mask, pos);
ngx_rtmp_mp4_update_box_size(b, pos);
return NGX_OK;
}
ngx_uint_t
ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size)
{
ngx_rtmp_mp4_field_32(b, size);
ngx_rtmp_mp4_box(b, "mdat");
return NGX_OK;
}
================================================
FILE: dash/ngx_rtmp_mp4.h
================================================
#ifndef _NGX_RTMP_MP4_H_INCLUDED_
#define _NGX_RTMP_MP4_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_rtmp.h>
#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01
#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02
#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04
#define NGX_RTMP_MP4_SAMPLE_KEY 0x08
typedef struct {
uint32_t size;
uint32_t duration;
uint32_t delay;
uint32_t timestamp;
unsigned key:1;
} ngx_rtmp_mp4_sample_t;
typedef enum {
NGX_RTMP_MP4_FILETYPE_INIT,
NGX_RTMP_MP4_FILETYPE_SEG
} ngx_rtmp_mp4_file_type_t;
typedef enum {
NGX_RTMP_MP4_VIDEO_TRACK,
NGX_RTMP_MP4_AUDIO_TRACK
} ngx_rtmp_mp4_track_type_t;
ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b);
ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b);
ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,
ngx_rtmp_mp4_track_type_t ttype);
ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
ngx_uint_t sample_mask, uint32_t index);
ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b,
ngx_uint_t reference_size, uint32_t earliest_pres_time,
uint32_t latest_pres_time);
ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size);
#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */
================================================
FILE: doc/README.md
================================================
Documentation is available here:
https://github.com/arut/nginx-rtmp-module/wiki
================================================
FILE: hls/ngx_rtmp_hls_module.c
================================================
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_rtmp.h>
#include <ngx_rtmp_cmd_module.h>
#include <ngx_rtmp_codec_module.h>
#include "ngx_rtmp_mpegts.h"
static ngx_rtmp_publish_pt next_publish;
static ngx_rtmp_close_stream_pt next_close_stream;
static ngx_rtmp_stream_begin_pt next_stream_begin;
static ngx_rtmp_stream_eof_pt next_stream_eof;
static char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf);
static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf);
static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s);
static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s,
ngx_str_t *path);
#define NGX_RTMP_HLS_BUFSIZE (1024*1024)
#define NGX_RTMP_HLS_DIR_ACCESS 0744
typedef struct {
uint64_t id;
uint64_t key_id;
double duration;
unsigned active:1;
unsigned discont:1; /* before */
} ngx_rtmp_hls_frag_t;
typedef struct {
ngx_str_t suffix;
ngx_array_t args;
} ngx_rtmp_hls_variant_t;
typedef struct {
unsigned opened:1;
ngx_rtmp_mpegts_file_t file;
ngx_str_t playlist;
ngx_str_t playlist_bak;
ngx_str_t var_playlist;
ngx_str_t var_playlist_bak;
ngx_str_t stream;
ngx_str_t keyfile;
ngx_str_t name;
u_char key[16];
uint64_t frag;
uint64_t frag_ts;
uint64_t key_id;
ngx_uint_t nfrags;
ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */
ngx_uint_t audio_cc;
ngx_uint_t video_cc;
ngx_uint_t key_frags;
uint64_t aframe_base;
uint64_t aframe_num;
ngx_buf_t *aframe;
uint64_t aframe_pts;
ngx_rtmp_hls_variant_t *var;
} ngx_rtmp_hls_ctx_t;
typedef struct {
ngx_str_t path;
ngx_msec_t playlen;
ngx_uint_t frags_per_key;
} ngx_rtmp_hls_cleanup_t;
typedef struct {
ngx_flag_t hls;
ngx_msec_t fraglen;
ngx_msec_t max_fraglen;
ngx_msec_t muxdelay;
ngx_msec_t sync;
ngx_msec_t playlen;
ngx_uint_t winfrags;
ngx_flag_t continuous;
ngx_flag_t nested;
ngx_str_t path;
ngx_uint_t naming;
ngx_uint_t slicing;
ngx_uint_t type;
ngx_path_t *slot;
ngx_msec_t max_audio_delay;
size_t audio_buffer_size;
ngx_flag_t cleanup;
ngx_array_t *variant;
ngx_str_t base_url;
ngx_int_t granularity;
ngx_flag_t keys;
ngx_str_t key_path;
ngx_str_t key_url;
ngx_uint_t frags_per_key;
} ngx_rtmp_hls_app_conf_t;
#define NGX_RTMP_HLS_NAMING_SEQUENTIAL 1
#define NGX_RTMP_HLS_NAMING_TIMESTAMP 2
#define NGX_RTMP_HLS_NAMING_SYSTEM 3
#define NGX_RTMP_HLS_SLICING_PLAIN 1
#define NGX_RTMP_HLS_SLICING_ALIGNED 2
#define NGX_RTMP_HLS_TYPE_LIVE 1
#define NGX_RTMP_HLS_TYPE_EVENT 2
static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = {
{ ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL },
{ ngx_string("timestamp"), NGX_RTMP_HLS_NAMING_TIMESTAMP },
{ ngx_string("system"), NGX_RTMP_HLS_NAMING_SYSTEM },
{ ngx_null_string, 0 }
};
static ngx_conf_enum_t ngx_rtmp_hls_slicing_slots[] = {
{ ngx_string("plain"), NGX_RTMP_HLS_SLICING_PLAIN },
{ ngx_string("aligned"), NGX_RTMP_HLS_SLICING_ALIGNED },
{ ngx_null_string, 0 }
};
static ngx_conf_enum_t ngx_rtmp_hls_type_slots[] = {
{ ngx_string("live"), NGX_RTMP_HLS_TYPE_LIVE },
{ ngx_string("event"), NGX_RTMP_HLS_TYPE_EVENT },
{ ngx_null_string, 0 }
};
static ngx_command_t ngx_rtmp_hls_commands[] = {
{ ngx_string("hls"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, hls),
NULL },
{ ngx_string("hls_fragment"),
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, fraglen),
NULL },
{ ngx_string("hls_max_fragment"),
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, max_fraglen),
NULL },
{ ngx_string("hls_path"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, path),
NULL },
{ ngx_string("hls_playlist_length"),
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, 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_string("hls_sync"),
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, sync),
NULL },
{ ngx_string("hls_continuous"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, continuous),
NULL },
{ ngx_string("hls_nested"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, nested),
NULL },
{ ngx_string("hls_fragment_naming"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_enum_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, naming),
&ngx_rtmp_hls_naming_slots },
{ ngx_string("hls_fragment_slicing"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_enum_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, slicing),
&ngx_rtmp_hls_slicing_slots },
{ ngx_string("hls_type"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_enum_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, type),
&ngx_rtmp_hls_type_slots },
{ ngx_string("hls_max_audio_delay"),
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, max_audio_delay),
NULL },
{ ngx_string("hls_audio_buffer_size"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_size_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, audio_buffer_size),
NULL },
{ ngx_string("hls_cleanup"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, cleanup),
NULL },
{ ngx_string("hls_variant"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
ngx_rtmp_hls_variant,
NGX_RTMP_APP_CONF_OFFSET,
0,
NULL },
{ ngx_string("hls_base_url"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, base_url),
NULL },
{ ngx_string("hls_fragment_naming_granularity"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, granularity),
NULL },
{ ngx_string("hls_keys"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, keys),
NULL },
{ ngx_string("hls_key_path"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, key_path),
NULL },
{ ngx_string("hls_key_url"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, key_url),
NULL },
{ ngx_string("hls_fragments_per_key"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_RTMP_APP_CONF_OFFSET,
offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key),
NULL },
ngx_null_command
};
static ngx_rtmp_module_t ngx_rtmp_hls_module_ctx = {
NULL, /* preconfiguration */
ngx_rtmp_hls_postconfiguration, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_rtmp_hls_create_app_conf, /* create location configuration */
ngx_rtmp_hls_merge_app_conf, /* merge location configuration */
};
ngx_module_t ngx_rtmp_hls_module = {
NGX_MODULE_V1,
&ngx_rtmp_hls_module_ctx, /* module context */
ngx_rtmp_hls_commands, /* 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_rtmp_hls_frag_t *
ngx_rtmp_hls_get_frag(ngx_rtmp_session_t *s, ngx_int_t n)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
return &ctx->frags[(ctx->frag + n) % (hacf->winfrags * 2 + 1)];
}
static void
ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
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->nfrags == hacf->winfrags) {
ctx->frag++;
} else {
ctx->nfrags++;
}
}
static ngx_int_t
ngx_rtmp_hls_rename_file(u_char *src, u_char *dst)
{
/* rename file with overwrite */
#if (NGX_WIN32)
return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING);
#else
return ngx_rename_file(src, dst);
#endif
}
static ngx_int_t
ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s)
{
static u_char buffer[1024];
u_char *p, *last;
ssize_t rc;
ngx_fd_t fd;
ngx_str_t *arg;
ngx_uint_t n, k;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_variant_t *var;
ngx_rtmp_hls_app_conf_t *hacf;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
fd = ngx_open_file(ctx->var_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: " ngx_open_file_n " failed: '%V'",
&ctx->var_playlist_bak);
return NGX_ERROR;
}
#define NGX_RTMP_HLS_VAR_HEADER "#EXTM3U\n#EXT-X-VERSION:3\n"
rc = ngx_write_fd(fd, NGX_RTMP_HLS_VAR_HEADER,
sizeof(NGX_RTMP_HLS_VAR_HEADER) - 1);
if (rc < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_write_fd_n " failed: '%V'",
&ctx->var_playlist_bak);
ngx_close_file(fd);
return NGX_ERROR;
}
var = hacf->variant->elts;
for (n = 0; n < hacf->variant->nelts; n++, var++)
{
p = buffer;
last = buffer + sizeof(buffer);
p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1");
arg = var->args.elts;
for (k = 0; k < var->args.nelts; k++, arg++) {
p = ngx_slprintf(p, last, ",%V", arg);
}
if (p < last) {
*p++ = '\n';
}
p = ngx_slprintf(p, last, "%V%*s%V",
&hacf->base_url,
ctx->name.len - ctx->var->suffix.len, ctx->name.data,
&var->suffix);
if (hacf->nested) {
p = ngx_slprintf(p, last, "%s", "/index");
}
p = ngx_slprintf(p, last, "%s", ".m3u8\n");
rc = ngx_write_fd(fd, buffer, p - buffer);
if (rc < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_write_fd_n " failed '%V'",
&ctx->var_playlist_bak);
ngx_close_file(fd);
return NGX_ERROR;
}
}
ngx_close_file(fd);
if (ngx_rtmp_hls_rename_file(ctx->var_playlist_bak.data,
ctx->var_playlist.data)
== NGX_FILE_ERROR)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: rename failed: '%V'->'%V'",
&ctx->var_playlist_bak, &ctx->var_playlist);
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
{
static u_char buffer[1024];
ngx_fd_t fd;
u_char *p, *end;
ngx_rtmp_hls_ctx_t *ctx;
ssize_t n;
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_frag_t *f;
ngx_uint_t i, max_frag;
ngx_str_t name_part, key_name_part;
uint64_t prev_key_id;
const char *sep, *key_sep;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
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: " ngx_open_file_n " failed: '%V'",
&ctx->playlist_bak);
return NGX_ERROR;
}
max_frag = hacf->fraglen / 1000;
for (i = 0; i < ctx->nfrags; i++) {
f = ngx_rtmp_hls_get_frag(s, i);
if (f->duration > max_frag) {
max_frag = (ngx_uint_t) (f->duration + .5);
}
}
p = buffer;
end = p + sizeof(buffer);
p = ngx_slprintf(p, end,
"#EXTM3U\n"
"#EXT-X-VERSION:3\n"
"#EXT-X-MEDIA-SEQUENCE:%uL\n"
"#EXT-X-TARGETDURATION:%ui\n",
ctx->frag, max_frag);
if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) {
p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n");
}
n = ngx_write_fd(fd, buffer, p - buffer);
if (n < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_write_fd_n " failed: '%V'",
&ctx->playlist_bak);
ngx_close_file(fd);
return NGX_ERROR;
}
sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-";
key_sep = hacf->nested ? (hacf->key_url.len ? "/" : "") : "-";
name_part.len = 0;
if (!hacf->nested || hacf->base_url.len) {
name_part = ctx->name;
}
key_name_part.len = 0;
if (!hacf->nested || hacf->key_url.len) {
key_name_part = ctx->name;
}
prev_key_id = 0;
for (i = 0; i < ctx->nfrags; i++) {
f = ngx_rtmp_hls_get_frag(s, i);
p = buffer;
end = p + sizeof(buffer);
if (f->discont) {
p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n");
}
if (hacf->keys && (i == 0 || f->key_id != prev_key_id)) {
p = ngx_slprintf(p, end, "#EXT-X-KEY:METHOD=AES-128,"
"URI=\"%V%V%s%uL.key\",IV=0x%032XL\n",
&hacf->key_url, &key_name_part,
key_sep, f->key_id, f->key_id);
}
prev_key_id = f->key_id;
p = ngx_slprintf(p, end,
"#EXTINF:%.3f,\n"
"%V%V%s%uL.ts\n",
f->duration, &hacf->base_url, &name_part, sep, f->id);
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, "
"discont=%i",
ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont);
n = ngx_write_fd(fd, buffer, p - buffer);
if (n < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_write_fd_n " failed '%V'",
&ctx->playlist_bak);
ngx_close_file(fd);
return NGX_ERROR;
}
}
ngx_close_file(fd);
if (ngx_rtmp_hls_rename_file(ctx->playlist_bak.data, ctx->playlist.data)
== NGX_FILE_ERROR)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: rename failed: '%V'->'%V'",
&ctx->playlist_bak, &ctx->playlist);
return NGX_ERROR;
}
if (ctx->var) {
return ngx_rtmp_hls_write_variant_playlist(s);
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n,
ngx_chain_t **in)
{
u_char *last;
size_t pn;
if (*in == NULL) {
return NGX_ERROR;
}
for ( ;; ) {
last = (*in)->buf->last;
if ((size_t)(last - *src) >= n) {
if (dst) {
ngx_memcpy(dst, *src, n);
}
*src += n;
while (*in && *src == (*in)->buf->last) {
*in = (*in)->next;
if (*in) {
*src = (*in)->buf->pos;
}
}
return NGX_OK;
}
pn = last - *src;
if (dst) {
ngx_memcpy(dst, *src, pn);
dst = (u_char *)dst + pn;
}
n -= pn;
*in = (*in)->next;
if (*in == NULL) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: failed to read %uz byte(s)", n);
return NGX_ERROR;
}
*src = (*in)->buf->pos;
}
}
static ngx_int_t
ngx_rtmp_hls_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out)
{
static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };
if (out->last + sizeof(aud_nal) > out->end) {
return NGX_ERROR;
}
out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal));
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out)
{
ngx_rtmp_codec_ctx_t *codec_ctx;
u_char *p;
ngx_chain_t *in;
ngx_rtmp_hls_ctx_t *ctx;
int8_t nnals;
uint16_t len, rlen;
ngx_int_t n;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (ctx == NULL || codec_ctx == NULL) {
return NGX_ERROR;
}
in = codec_ctx->avc_header;
if (in == NULL) {
return NGX_ERROR;
}
p = in->buf->pos;
/*
* Skip bytes:
* - flv fmt
* - H264 CONF/PICT (0x00)
* - 0
* - 0
* - 0
* - version
* - profile
* - compatibility
* - level
* - nal bytes
*/
if (ngx_rtmp_hls_copy(s, NULL, &p, 10, &in) != NGX_OK) {
return NGX_ERROR;
}
/* number of SPS NALs */
if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
return NGX_ERROR;
}
nnals &= 0x1f; /* 5lsb */
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: SPS number: %uz", nnals);
/* SPS */
for (n = 0; ; ++n) {
for (; nnals; --nnals) {
/* NAL length */
if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) {
return NGX_ERROR;
}
ngx_rtmp_rmemcpy(&len, &rlen, 2);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: header NAL length: %uz", (size_t) len);
/* AnnexB prefix */
if (out->end - out->last < 4) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: too small buffer for header NAL size");
return NGX_ERROR;
}
*out->last++ = 0;
*out->last++ = 0;
*out->last++ = 0;
*out->last++ = 1;
/* NAL body */
if (out->end - out->last < len) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: too small buffer for header NAL");
return NGX_ERROR;
}
if (ngx_rtmp_hls_copy(s, out->last, &p, len, &in) != NGX_OK) {
return NGX_ERROR;
}
out->last += len;
}
if (n == 1) {
break;
}
/* number of PPS NALs */
if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: PPS number: %uz", nnals);
}
return NGX_OK;
}
static uint64_t
ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
switch (hacf->naming) {
case NGX_RTMP_HLS_NAMING_TIMESTAMP:
return ts;
case NGX_RTMP_HLS_NAMING_SYSTEM:
return (uint64_t) ngx_cached_time->sec * 1000 + ngx_cached_time->msec;
default: /* NGX_RTMP_HLS_NAMING_SEQUENTIAL */
return ctx->frag + ctx->nfrags;
}
}
static ngx_int_t
ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx == NULL || !ctx->opened) {
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: close fragment n=%uL", ctx->frag);
ngx_rtmp_mpegts_close_file(&ctx->file);
ctx->opened = 0;
ngx_rtmp_hls_next_frag(s);
ngx_rtmp_hls_write_playlist(s);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
ngx_int_t discont)
{
uint64_t id;
ngx_fd_t fd;
ngx_uint_t g;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_frag_t *f;
ngx_rtmp_hls_app_conf_t *hacf;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx->opened) {
return NGX_OK;
}
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
if (ngx_rtmp_hls_ensure_directory(s, &hacf->path) != NGX_OK) {
return NGX_ERROR;
}
if (hacf->keys &&
ngx_rtmp_hls_ensure_directory(s, &hacf->key_path) != NGX_OK)
{
return NGX_ERROR;
}
id = ngx_rtmp_hls_get_fragment_id(s, ts);
if (hacf->granularity) {
g = (ngx_uint_t) hacf->granularity;
id = (uint64_t) (id / g) * g;
}
ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts%Z", id);
if (hacf->keys) {
if (ctx->key_frags == 0) {
ctx->key_frags = hacf->frags_per_key - 1;
ctx->key_id = id;
if (RAND_bytes(ctx->key, 16) < 0) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: failed to create key");
return NGX_ERROR;
}
ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, "%uL.key%Z", id);
fd = ngx_open_file(ctx->keyfile.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: failed to open key file '%s'",
ctx->keyfile.data);
return NGX_ERROR;
}
if (ngx_write_fd(fd, ctx->key, 16) != 16) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: failed to write key file '%s'",
ctx->keyfile.data);
ngx_close_file(fd);
return NGX_ERROR;
}
ngx_close_file(fd);
} else {
if (hacf->frags_per_key) {
ctx->key_frags--;
}
if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_errno,
ngx_set_file_time_n " '%s' failed",
ctx->keyfile.data);
}
}
}
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: open fragment file='%s', keyfile='%s', "
"frag=%uL, n=%ui, time=%uL, discont=%i",
ctx->stream.data,
ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "",
ctx->frag, ctx->nfrags, ts, discont);
if (hacf->keys &&
ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: failed to initialize hls encryption");
return NGX_ERROR;
}
if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data,
s->connection->log)
!= NGX_OK)
{
return NGX_ERROR;
}
ctx->opened = 1;
f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
ngx_memzero(f, sizeof(*f));
f->active = 1;
f->discont = discont;
f->id = id;
f->key_id = ctx->key_id;
ctx->frag_ts = ts;
/* start fragment with audio to make iPhone happy */
ngx_rtmp_hls_flush_audio(s);
return NGX_OK;
}
static void
ngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_file_t file;
ssize_t ret;
off_t offset;
u_char *p, *last, *end, *next, *pa, *pp, c;
ngx_rtmp_hls_frag_t *f;
double duration;
ngx_int_t discont;
uint64_t mag, key_id, base;
static u_char buffer[4096];
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
ngx_memzero(&file, sizeof(file));
file.log = s->connection->log;
ngx_str_set(&file.name, "m3u8");
file.fd = ngx_open_file(ctx->playlist.data, NGX_FILE_RDONLY, NGX_FILE_OPEN,
0);
if (file.fd == NGX_INVALID_FILE) {
return;
}
offset = 0;
ctx->nfrags = 0;
f = NULL;
duration = 0;
discont = 0;
key_id = 0;
for ( ;; ) {
ret = ngx_read_file(&file, buffer, sizeof(buffer), offset);
if (ret <= 0) {
goto done;
}
p = buffer;
end = buffer + ret;
for ( ;; ) {
last = ngx_strlchr(p, end, '\n');
if (last == NULL) {
if (p == buffer) {
goto done;
}
break;
}
next = last + 1;
offset += (next - p);
if (p != last && last[-1] == '\r') {
last--;
}
#define NGX_RTMP_MSEQ "#EXT-X-MEDIA-SEQUENCE:"
#define NGX_RTMP_MSEQ_LEN (sizeof(NGX_RTMP_MSEQ) - 1)
if (ngx_memcmp(p, NGX_RTMP_MSEQ, NGX_RTMP_MSEQ_LEN) == 0) {
ctx->frag = (uint64_t) strtod((const char *)
&p[NGX_RTMP_MSEQ_LEN], NULL);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: restore sequence frag=%uL", ctx->frag);
}
#define NGX_RTMP_XKEY "#EXT-X-KEY:"
#define NGX_RTMP_XKEY_LEN (sizeof(NGX_RTMP_XKEY) - 1)
if (ngx_memcmp(p, NGX_RTMP_XKEY, NGX_RTMP_XKEY_LEN) == 0) {
/* recover key id from initialization vector */
key_id = 0;
base = 1;
pp = last - 1;
for ( ;; ) {
if (pp < p) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: failed to read key id");
break;
}
c = *pp;
if (c == 'x') {
break;
}
if (c >= '0' && c <= '9') {
c -= '0';
goto next;
}
c |= 0x20;
if (c >= 'a' && c <= 'f') {
c -= 'a' - 10;
goto next;
}
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: bad character in key id");
break;
next:
key_id += base * c;
base *= 0x10;
pp--;
}
}
#define NGX_RTMP_EXTINF "#EXTINF:"
#define NGX_RTMP_EXTINF_LEN (sizeof(NGX_RTMP_EXTINF) - 1)
if (ngx_memcmp(p, NGX_RTMP_EXTINF, NGX_RTMP_EXTINF_LEN) == 0) {
duration = strtod((const char *) &p[NGX_RTMP_EXTINF_LEN], NULL);
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: restore durarion=%.3f", duration);
}
#define NGX_RTMP_DISCONT "#EXT-X-DISCONTINUITY"
#define NGX_RTMP_DISCONT_LEN (sizeof(NGX_RTMP_DISCONT) - 1)
if (ngx_memcmp(p, NGX_RTMP_DISCONT, NGX_RTMP_DISCONT_LEN) == 0) {
discont = 1;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: discontinuity");
}
/* find '.ts\r' */
if (p + 4 <= last &&
last[-3] == '.' && last[-2] == 't' && last[-1] == 's')
{
f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
ngx_memzero(f, sizeof(*f));
f->duration = duration;
f->discont = discont;
f->active = 1;
f->id = 0;
discont = 0;
mag = 1;
for (pa = last - 4; pa >= p; pa--) {
if (*pa < '0' || *pa > '9') {
break;
}
f->id += (*pa - '0') * mag;
mag *= 10;
}
f->key_id = key_id;
ngx_rtmp_hls_next_frag(s);
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: restore fragment '%*s' id=%uL, "
"duration=%.3f, frag=%uL, nfrags=%ui",
(size_t) (last - p), p, f->id, f->duration,
ctx->frag, ctx->nfrags);
}
p = next;
}
}
done:
ngx_close_file(file.fd);
}
static ngx_int_t
ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path)
{
size_t len;
ngx_file_info_t fi;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
static u_char zpath[NGX_MAX_PATH + 1];
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
if (path->len + 1 > sizeof(zpath)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path");
return NGX_ERROR;
}
ngx_snprintf(zpath, sizeof(zpath), "%V%Z", path);
if (ngx_file_info(zpath, &fi) == NGX_FILE_ERROR) {
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_file_info_n " failed on '%V'", path);
return NGX_ERROR;
}
/* ENOENT */
if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_create_dir_n " failed on '%V'", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: directory '%V' created", path);
} else {
if (!ngx_is_dir(&fi)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: '%V' exists and is not a directory", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: directory '%V' exists", path);
}
if (!hacf->nested) {
return NGX_OK;
}
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
len = path->len;
if (path->data[len - 1] == '/') {
len--;
}
if (len + 1 + ctx->name.len + 1 > sizeof(zpath)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path");
return NGX_ERROR;
}
ngx_snprintf(zpath, sizeof(zpath) - 1, "%*s/%V%Z", len, path->data,
&ctx->name);
if (ngx_file_info(zpath, &fi) != NGX_FILE_ERROR) {
if (ngx_is_dir(&fi)) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: directory '%s' exists", zpath);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: '%s' exists and is not a directory", zpath);
return NGX_ERROR;
}
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_file_info_n " failed on '%s'", zpath);
return NGX_ERROR;
}
/* NGX_ENOENT */
if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: " ngx_create_dir_n " failed on '%s'", zpath);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: directory '%s' created", zpath);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
u_char *p, *pp;
ngx_rtmp_hls_frag_t *f;
ngx_buf_t *b;
size_t len;
ngx_rtmp_hls_variant_t *var;
ngx_uint_t n;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
if (hacf == NULL || !hacf->hls || hacf->path.len == 0) {
goto next;
}
if (s->auto_pushed) {
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: publish: name='%s' type='%s'",
v->name, v->type);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx == NULL) {
ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t));
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module);
} else {
f = ctx->frags;
b = ctx->aframe;
ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t));
ctx->frags = f;
ctx->aframe = b;
if (b) {
b->pos = b->last = b->start;
}
}
if (ctx->frags == NULL) {
ctx->frags = ngx_pcalloc(s->connection->pool,
sizeof(ngx_rtmp_hls_frag_t) *
(hacf->winfrags * 2 + 1));
if (ctx->frags == NULL) {
return NGX_ERROR;
}
}
if (ngx_strstr(v->name, "..")) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: bad stream name: '%s'", v->name);
return NGX_ERROR;
}
ctx->name.len = ngx_strlen(v->name);
ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1);
if (ctx->name.data == NULL) {
return NGX_ERROR;
}
*ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0;
len = hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8");
if (hacf->nested) {
len += sizeof("/index") - 1;
}
ctx->playlist.data = ngx_palloc(s->connection->pool, len);
p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len);
if (p[-1] != '/') {
*p++ = '/';
}
p = ngx_cpymem(p, ctx->name.data, ctx->name.len);
/*
* ctx->stream holds initial part of stream file path
* however the space for the whole stream path
* is allocated
*/
ctx->stream.len = p - ctx->playlist.data + 1;
ctx->stream.data = ngx_palloc(s->connection->pool,
ctx->stream.len + NGX_INT64_LEN +
sizeof(".ts"));
ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1);
ctx->stream.data[ctx->stream.len - 1] = (hacf->nested ? '/' : '-');
/* varint playlist path */
if (hacf->variant) {
var = hacf->variant->elts;
for (n = 0; n < hacf->variant->nelts; n++, var++) {
if (ctx->name.len > var->suffix.len &&
ngx_memcmp(var->suffix.data,
ctx->name.data + ctx->name.len - var->suffix.len,
var->suffix.len)
== 0)
{
ctx->var = var;
len = (size_t) (p - ctx->playlist.data);
ctx->var_playlist.len = len - var->suffix.len + sizeof(".m3u8")
- 1;
ctx->var_playlist.data = ngx_palloc(s->connection->pool,
ctx->var_playlist.len + 1);
pp = ngx_cpymem(ctx->var_playlist.data, ctx->playlist.data,
len - var->suffix.len);
pp = ngx_cpymem(pp, ".m3u8", sizeof(".m3u8") - 1);
*pp = 0;
ctx->var_playlist_bak.len = ctx->var_playlist.len +
sizeof(".bak") - 1;
ctx->var_playlist_bak.data = ngx_palloc(s->connection->pool,
ctx->var_playlist_bak.len + 1);
pp = ngx_cpymem(ctx->var_playlist_bak.data,
ctx->var_playlist.data,
ctx->var_playlist.len);
pp = ngx_cpymem(pp, ".bak", sizeof(".bak") - 1);
*pp = 0;
break;
}
}
}
/* playlist path */
if (hacf->nested) {
p = ngx_cpymem(p, "/index.m3u8", sizeof("/index.m3u8") - 1);
} else {
p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1);
}
ctx->playlist.len = p - ctx->playlist.data;
*p = 0;
/* playlist bak (new playlist) path */
ctx->playlist_bak.data = ngx_palloc(s->connection->pool,
ctx->playlist.len + sizeof(".bak"));
p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data,
ctx->playlist.len);
p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1);
ctx->playlist_bak.len = p - ctx->playlist_bak.data;
*p = 0;
/* key path */
if (hacf->keys) {
len = hacf->key_path.len + 1 + ctx->name.len + 1 + NGX_INT64_LEN
+ sizeof(".key");
ctx->keyfile.data = ngx_palloc(s->connection->pool, len);
if (ctx->keyfile.data == NULL) {
return NGX_ERROR;
}
p = ngx_cpymem(ctx->keyfile.data, hacf->key_path.data,
hacf->key_path.len);
if (p[-1] != '/') {
*p++ = '/';
}
p = ngx_cpymem(p, ctx->name.data, ctx->name.len);
*p++ = (hacf->nested ? '/' : '-');
ctx->keyfile.len = p - ctx->keyfile.data;
}
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: playlist='%V' playlist_bak='%V' "
"stream_pattern='%V' keyfile_pattern='%V'",
&ctx->playlist, &ctx->playlist_bak,
&ctx->stream, &ctx->keyfile);
if (hacf->continuous) {
ngx_rtmp_hls_restore_stream(s);
}
next:
return next_publish(s, v);
}
static ngx_int_t
ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (hacf == NULL || !hacf->hls || ctx == NULL) {
goto next;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: close stream");
ngx_rtmp_hls_close_fragment(s);
next:
return next_close_stream(s, v);
}
static ngx_int_t
ngx_rtmp_hls_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype,
ngx_uint_t *srindex, ngx_uint_t *chconf)
{
ngx_rtmp_codec_ctx_t *codec_ctx;
ngx_chain_t *cl;
u_char *p, b0, b1;
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
cl = codec_ctx->aac_header;
p = cl->buf->pos;
if (ngx_rtmp_hls_copy(s, NULL, &p, 2, &cl) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_rtmp_hls_copy(s, &b0, &p, 1, &cl) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_rtmp_hls_copy(s, &b1, &p, 1, &cl) != NGX_OK) {
return NGX_ERROR;
}
*objtype = b0 >> 3;
if (*objtype == 0 || *objtype == 0x1f) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: unsupported adts object type:%ui", *objtype);
return NGX_ERROR;
}
if (*objtype > 4) {
/*
* Mark all extended profiles as LC
* to make Android as happy as possible.
*/
*objtype = 2;
}
*srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7);
if (*srindex == 0x0f) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: unsupported adts sample rate:%ui", *srindex);
return NGX_ERROR;
}
*chconf = (b1 >> 3) & 0x0f;
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: aac object_type:%ui, sample_rate_index:%ui, "
"channel_config:%ui", *objtype, *srindex, *chconf);
return NGX_OK;
}
static void
ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts,
ngx_int_t boundary, ngx_uint_t flush_rate)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_frag_t *f;
ngx_msec_t ts_frag_len;
ngx_int_t same_frag, force,discont;
ngx_buf_t *b;
int64_t d;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
f = NULL;
force = 0;
discont = 1;
if (ctx->opened) {
f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
d = (int64_t) (ts - ctx->frag_ts);
if (d > (int64_t) hacf->max_fraglen * 90 || d < -90000) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: force fragment split: %.3f sec, ", d / 90000.);
force = 1;
} else {
f->duration = (ts - ctx->frag_ts) / 90000.;
discont = 0;
}
}
switch (hacf->slicing) {
case NGX_RTMP_HLS_SLICING_PLAIN:
if (f && f->duration < hacf->fraglen / 1000.) {
boundary = 0;
}
break;
case NGX_RTMP_HLS_SLICING_ALIGNED:
ts_frag_len = hacf->fraglen * 90;
same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len;
if (f && same_frag) {
boundary = 0;
}
if (f == NULL && (ctx->frag_ts == 0 || same_frag)) {
ctx->frag_ts = ts;
boundary = 0;
}
break;
}
if (boundary || force) {
ngx_rtmp_hls_close_fragment(s);
ngx_rtmp_hls_open_fragment(s, ts, discont);
}
b = ctx->aframe;
if (ctx->opened && b && b->last > b->pos &&
ctx->aframe_pts + (uint64_t) hacf->max_audio_delay * 90 / flush_rate
< ts)
{
ngx_rtmp_hls_flush_audio(s);
}
}
static ngx_int_t
ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s)
{
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_mpegts_frame_t frame;
ngx_int_t rc;
ngx_buf_t *b;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
if (ctx == NULL || !ctx->opened) {
return NGX_OK;
}
b = ctx->aframe;
if (b == NULL || b->pos == b->last) {
return NGX_OK;
}
ngx_memzero(&frame, sizeof(frame));
frame.dts = ctx->aframe_pts;
frame.pts = frame.dts;
frame.cc = ctx->audio_cc;
frame.pid = 0x101;
frame.sid = 0xc0;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: flush audio pts=%uL", frame.pts);
rc = ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, b);
if (rc != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: audio flush failed");
}
ctx->audio_cc = frame.cc;
b->pos = b->last = b->start;
return rc;
}
static ngx_int_t
ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
uint64_t pts, est_pts;
int64_t dpts;
size_t bsize;
ngx_buf_t *b;
u_char *p;
ngx_uint_t objtype, srindex, chconf, size;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (hacf == NULL || !hacf->hls || ctx == NULL ||
codec_ctx == NULL || h->mlen < 2)
{
return NGX_OK;
}
if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC ||
codec_ctx->aac_header == NULL || ngx_rtmp_is_codec_header(in))
{
return NGX_OK;
}
b = ctx->aframe;
if (b == NULL) {
b = ngx_pcalloc(s->connection->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_ERROR;
}
ctx->aframe = b;
b->start = ngx_palloc(s->connection->pool, hacf->audio_buffer_size);
if (b->start == NULL) {
return NGX_ERROR;
}
b->end = b->start + hacf->audio_buffer_size;
b->pos = b->last = b->start;
}
size = h->mlen - 2 + 7;
pts = (uint64_t) h->timestamp * 90;
if (b->start + size > b->end) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: too big audio frame");
return NGX_OK;
}
/*
* start new fragment here if
* there's no video at all, otherwise
* do it in video handler
*/
ngx_rtmp_hls_update_fragment(s, pts, codec_ctx->avc_header == NULL, 2);
if (b->last + size > b->end) {
ngx_rtmp_hls_flush_audio(s);
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: audio pts=%uL", pts);
if (b->last + 7 > b->end) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: not enough buffer for audio header");
return NGX_OK;
}
p = b->last;
b->last += 5;
/* copy payload */
for (; in && b->last < b->end; in = in->next) {
bsize = in->buf->last - in->buf->pos;
if (b->last + bsize > b->end) {
bsize = b->end - b->last;
}
b->last = ngx_cpymem(b->last, in->buf->pos, bsize);
}
/* make up ADTS header */
if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf)
!= NGX_OK)
{
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: aac header error");
return NGX_OK;
}
/* we have 5 free bytes + 2 bytes of RTMP frame header */
p[0] = 0xff;
p[1] = 0xf1;
p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) |
((chconf & 0x04) >> 2));
p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03));
p[4] = (u_char) (size >> 3);
p[5] = (u_char) ((size << 5) | 0x1f);
p[6] = 0xfc;
if (p != b->start) {
ctx->aframe_num++;
return NGX_OK;
}
ctx->aframe_pts = pts;
if (!hacf->sync || codec_ctx->sample_rate == 0) {
return NGX_OK;
}
/* align audio frames */
/* TODO: We assume here AAC frame size is 1024
* Need to handle AAC frames with frame size of 960 */
est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 /
codec_ctx->sample_rate;
dpts = (int64_t) (est_pts - pts);
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: audio sync dpts=%L (%.5fs)",
dpts, dpts / 90000.);
if (dpts <= (int64_t) hacf->sync * 90 &&
dpts >= (int64_t) hacf->sync * -90)
{
ctx->aframe_num++;
ctx->aframe_pts = est_pts;
return NGX_OK;
}
ctx->aframe_base = pts;
ctx->aframe_num = 1;
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: audio sync gap dpts=%L (%.5fs)",
dpts, dpts / 90000.);
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
u_char *p;
uint8_t fmt, ftype, htype, nal_type, src_nal_type;
uint32_t len, rlen;
ngx_buf_t out, *b;
uint32_t cts;
ngx_rtmp_mpegts_frame_t frame;
ngx_uint_t nal_bytes;
ngx_int_t aud_sent, sps_pps_sent, boundary;
static u_char buffer[NGX_RTMP_HLS_BUFSIZE];
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL ||
codec_ctx->avc_header == NULL || h->mlen < 1)
{
return NGX_OK;
}
/* Only H264 is supported */
if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {
return NGX_OK;
}
p = in->buf->pos;
if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) {
return NGX_ERROR;
}
/* 1: keyframe (IDR)
* 2: inter frame
* 3: disposable inter frame */
ftype = (fmt & 0xf0) >> 4;
/* H264 HDR/PICT */
if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) {
return NGX_ERROR;
}
/* proceed only with PICT */
if (htype != 1) {
return NGX_OK;
}
/* 3 bytes: decoder delay */
if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) {
return NGX_ERROR;
}
cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) |
(cts & 0x0000FF00);
ngx_memzero(&out, sizeof(out));
out.start = buffer;
out.end = buffer + sizeof(buffer);
out.pos = out.start;
out.last = out.pos;
nal_bytes = codec_ctx->avc_nal_bytes;
aud_sent = 0;
sps_pps_sent = 0;
while (in) {
if (ngx_rtmp_hls_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) {
return NGX_OK;
}
len = 0;
ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes);
if (len == 0) {
continue;
}
if (ngx_rtmp_hls_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {
return NGX_OK;
}
nal_type = src_nal_type & 0x1f;
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: h264 NAL type=%ui, len=%uD",
(ngx_uint_t) nal_type, len);
if (nal_type >= 7 && nal_type <= 9) {
if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) {
return NGX_ERROR;
}
continue;
}
if (!aud_sent) {
switch (nal_type) {
case 1:
case 5:
case 6:
if (ngx_rtmp_hls_append_aud(s, &out) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: error appending AUD NAL");
}
/* fall through */
case 9:
aud_sent = 1;
break;
}
}
switch (nal_type) {
case 1:
sps_pps_sent = 0;
break;
case 5:
if (sps_pps_sent) {
break;
}
if (ngx_rtmp_hls_append_sps_pps(s, &out) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: error appenging SPS/PPS NALs");
}
sps_pps_sent = 1;
break;
}
/* AnnexB prefix */
if (out.end - out.last < 5) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: not enough buffer for AnnexB prefix");
return NGX_OK;
}
/* first AnnexB prefix is long (4 bytes) */
if (out.last == out.pos) {
*out.last++ = 0;
}
*out.last++ = 0;
*out.last++ = 0;
*out.last++ = 1;
*out.last++ = src_nal_type;
/* NAL body */
if (out.end - out.last < (ngx_int_t) len) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: not enough buffer for NAL");
return NGX_OK;
}
if (ngx_rtmp_hls_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
return NGX_ERROR;
}
out.last += (len - 1);
}
ngx_memzero(&frame, sizeof(frame));
frame.cc = ctx->video_cc;
frame.dts = (uint64_t) h->timestamp * 90;
frame.pts = frame.dts + cts * 90;
frame.pid = 0x100;
frame.sid = 0xe0;
frame.key = (ftype == 1);
/*
* start new fragment if
* - we have video key frame AND
* - we have audio buffered or have no audio at all or stream is closed
*/
b = ctx->aframe;
boundary = frame.key && (codec_ctx->aac_header == NULL || !ctx->opened ||
(b && b->last > b->pos));
ngx_rtmp_hls_update_fragment(s, frame.dts, boundary, 1);
if (!ctx->opened) {
return NGX_OK;
}
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: video pts=%uL, dts=%uL", frame.pts, frame.dts);
if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"hls: video frame failed");
}
ctx->video_cc = frame.cc;
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
{
return next_stream_begin(s, v);
}
static ngx_int_t
ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
{
ngx_rtmp_hls_flush_audio(s);
ngx_rtmp_hls_close_fragment(s);
return next_stream_eof(s, v);
}
static ngx_int_t
ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
{
ngx_dir_t dir;
time_t mtime, max_age;
ngx_err_t err;
ngx_str_t name, spath;
u_char *p;
ngx_int_t nentries, nerased;
u_char path[NGX_MAX_PATH + 1];
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"hls: cleanup path='%V' playlen=%M",
ppath, playlen);
if (ngx_open_dir(ppath, &dir) != NGX_OK) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno,
"hls: cleanup open dir failed '%V'", ppath);
return NGX_ERROR;
}
nentries = 0;
nerased = 0;
for ( ;; ) {
ngx_set_errno(0);
if (ngx_read_dir(&dir) == NGX_ERROR) {
err = ngx_errno;
if (ngx_close_dir(&dir) == NGX_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
"hls: cleanup " ngx_close_dir_n " \"%V\" failed",
ppath);
}
if (err == NGX_ENOMOREFILES) {
return nentries - nerased;
}
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err,
"hls: cleanup " ngx_read_dir_n
" '%V' failed", ppath);
return NGX_ERROR;
}
name.data = ngx_de_name(&dir);
if (name.data[0] == '.') {
continue;
}
name.len = ngx_de_namelen(&dir);
p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name);
*p = 0;
spath.data = path;
spath.len = p - path;
nentries++;
if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
"hls: cleanup " ngx_de_info_n " \"%V\" failed",
&spath);
continue;
}
if (ngx_de_is_dir(&dir)) {
if (ngx_rtmp_hls_cleanup_dir(&spath, playlen) == 0) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"hls: cleanup dir '%V'", &name);
/*
* null-termination gets spoiled in win32
* version of ngx_open_dir
*/
*p = 0;
if (ngx_delete_dir(path) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
"hls: cleanup " ngx_delete_dir_n
" failed on '%V'", &spath);
} else {
nerased++;
}
}
continue;
}
if (!ngx_de_is_file(&dir)) {
continue;
}
if (name.len >= 3 && name.data[name.len - 3] == '.' &&
name.data[name.len - 2] == 't' &&
name.data[name.len - 1] == 's')
{
max_age = playlen / 500;
} else if (name.len >= 5 && name.data[name.len - 5] == '.' &&
name.data[name.len - 4] == 'm' &&
name.data[name.len - 3] == '3' &&
name.data[name.len - 2] == 'u' &&
name.data[name.len - 1] == '8')
{
max_age = playlen / 1000;
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
name.data[name.len - 3] == 'k' &&
name.data[name.len - 2] == 'e' &&
name.data[name.len - 1] == 'y')
{
max_age = playlen / 500;
} else {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"hls: cleanup skip unknown file type '%V'", &name);
continue;
}
mtime = ngx_de_mtime(&dir);
if (mtime + max_age > ngx_cached_time->sec) {
continue;
}
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"hls: cleanup '%V' mtime=%T age=%T",
&name, mtime, ngx_cached_time->sec - mtime);
if (ngx_delete_file(path) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
"hls: cleanup " ngx_delete_file_n " failed on '%V'",
&spath);
continue;
}
nerased++;
}
}
#if (nginx_version >= 1011005)
static ngx_msec_t
#else
static time_t
#endif
ngx_rtmp_hls_cleanup(void *data)
{
ngx_rtmp_hls_cleanup_t *cleanup = data;
ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen);
#if (nginx_version >= 1011005)
return cleanup->playlen * 2;
#else
return cleanup->playlen / 500;
#endif
}
static char *
ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_rtmp_hls_app_conf_t *hacf = conf;
ngx_str_t *value, *arg;
ngx_uint_t n;
ngx_rtmp_hls_variant_t *var;
value = cf->args->elts;
if (hacf->variant == NULL) {
hacf->variant = ngx_array_create(cf->pool, 1,
sizeof(ngx_rtmp_hls_variant_t));
if (hacf->variant == NULL) {
return NGX_CONF_ERROR;
}
}
var = ngx_array_push(hacf->variant);
if (var == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(var, sizeof(ngx_rtmp_hls_variant_t));
var->suffix = value[1];
if (cf->args->nelts == 2) {
return NGX_CONF_OK;
}
if (ngx_array_init(&var->args, cf->pool, cf->args->nelts - 2,
sizeof(ngx_str_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
arg = ngx_array_push_n(&var->args, cf->args->nelts - 2);
if (arg == NULL) {
return NGX_CONF_ERROR;
}
for (n = 2; n < cf->args->nelts; n++) {
*arg++ = value[n];
}
return NGX_CONF_OK;
}
static void *
ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
{
ngx_rtmp_hls_app_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_app_conf_t));
if (conf == NULL) {
return NULL;
}
conf->hls = NGX_CONF_UNSET;
conf->fraglen = NGX_CONF_UNSET_MSEC;
conf->max_fraglen = NGX_CONF_UNSET_MSEC;
conf->muxdelay = NGX_CONF_UNSET_MSEC;
conf->sync = NGX_CONF_UNSET_MSEC;
conf->playlen = NGX_CONF_UNSET_MSEC;
conf->continuous = NGX_CONF_UNSET;
conf->nested = NGX_CONF_UNSET;
conf->naming = NGX_CONF_UNSET_UINT;
conf->slicing = NGX_CONF_UNSET_UINT;
conf->type = NGX_CONF_UNSET_UINT;
conf->max_audio_delay = NGX_CONF_UNSET_MSEC;
conf->audio_buffer_size = NGX_CONF_UNSET_SIZE;
conf->cleanup = NGX_CONF_UNSET;
conf->granularity = NGX_CONF_UNSET;
conf->keys = NGX_CONF_UNSET;
conf->frags_per_key = NGX_CONF_UNSET_UINT;
return conf;
}
static char *
ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_rtmp_hls_app_conf_t *prev = parent;
ngx_rtmp_hls_app_conf_t *conf = child;
ngx_rtmp_hls_cleanup_t *cleanup;
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->max_fraglen, prev->max_fraglen,
conf->fraglen * 10);
ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700);
ngx_conf_merge_msec_value(conf->sync, prev->sync, 2);
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
ngx_conf_merge_value(conf->continuous, prev->continuous, 1);
ngx_conf_merge_value(conf->nested, prev->nested, 0);
ngx_conf_merge_uint_value(conf->naming, prev->naming,
NGX_RTMP_HLS_NAMING_SEQUENTIAL);
ngx_conf_merge_uint_value(conf->slicing, prev->slicing,
NGX_RTMP_HLS_SLICING_PLAIN);
ngx_conf_merge_uint_value(conf->type, prev->type,
NGX_RTMP_HLS_TYPE_LIVE);
ngx_conf_merge_msec_value(conf->max_audio_delay, prev->max_audio_delay,
300);
ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size,
NGX_RTMP_HLS_BUFSIZE);
ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);
ngx_conf_merge_str_value(conf->base_url, prev->base_url, "");
ngx_conf_merge_value(conf->granularity, prev->granularity, 0);
ngx_conf_merge_value(conf->keys, prev->keys, 0);
ngx_conf_merge_str_value(conf->key_path, prev->key_path, "");
ngx_conf_merge_str_value(conf->key_url, prev->key_url, "");
ngx_conf_merge_uint_value(conf->frags_per_key, prev->frags_per_key, 0);
if (conf->fraglen) {
conf->winfrags = conf->playlen / conf->fraglen;
}
/* schedule cleanup */
if (conf->hls && conf->path.len && conf->cleanup &&
conf->type != NGX_RTMP_HLS_TYPE_EVENT)
{
if (conf->path.data[conf->path.len - 1] == '/') {
conf->path.len--;
}
cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
if (cleanup == NULL) {
return NGX_CONF_ERROR;
}
cleanup->path = conf->path;
cleanup->playlen = conf->playlen;
conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));
if (conf->slot == NULL) {
return NGX_CONF_ERROR;
}
conf->slot->manager = ngx_rtmp_hls_cleanup;
conf->slot->name = conf->path;
conf->slot->data = cleanup;
conf->slot->conf_file = cf->conf_file->file.name.data;
conf->slot->line = cf->conf_file->line;
if (ngx_add_path(cf, &conf->slot) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
ngx_conf_merge_str_value(conf->path, prev->path, "");
if (conf->keys && conf->cleanup && conf->key_path.len &&
ngx_strcmp(conf->key_path.data, conf->path.data) != 0 &&
conf->type != NGX_RTMP_HLS_TYPE_EVENT)
{
if (conf->key_path.data[conf->key_path.len - 1] == '/') {
conf->key_path.len--;
}
cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
if (cleanup == NULL) {
return NGX_CONF_ERROR;
}
cleanup->path = conf->key_path;
cleanup->playlen = conf->playlen;
conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));
if (conf->slot == NULL) {
return NGX_CONF_ERROR;
}
conf->slot->manager = ngx_rtmp_hls_cleanup;
conf->slot->name = conf->key_path;
conf->slot->data = cleanup;
conf->slot->conf_file = cf->conf_file->file.name.data;
conf->slot->line = cf->conf_file->line;
if (ngx_add_path(cf, &conf->slot) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
ngx_conf_merge_str_value(conf->key_path, prev->key_path, "");
if (conf->key_path.len == 0) {
conf->key_path = conf->path;
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_handler_pt *h;
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
*h = ngx_rtmp_hls_video;
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
*h = ngx_rtmp_hls_audio;
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_hls_publish;
next_close_stream = ngx_rtmp_close_stream;
ngx_rtmp_close_stream = ngx_rtmp_hls_close_stream;
next_stream_begin = ngx_rtmp_stream_begin;
ngx_rtmp_stream_begin = ngx_rtmp_hls_stream_begin;
next_stream_eof = ngx_rtmp_stream_eof;
ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof;
return NGX_OK;
}
================================================
FILE: hls/ngx_rtmp_mpegts.c
================================================
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include "ngx_rtmp_mpegts.h"
static u_char ngx_rtmp_mpegts_header[] = {
/* TS */
0x47, 0x40, 0x00, 0x10, 0x00,
/* PSI */
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
/* PAT */
0x00, 0x01, 0xf0, 0x01,
/* CRC */
0x2e, 0x70, 0x19, 0x05,
/* stuffing 167 bytes */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
/* TS */
0x47, 0x50, 0x01, 0x10, 0x00,
/* PSI */
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
/* PMT */
0xe1, 0x00,
0xf0, 0x00,
0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264 */
0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac */
/*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */
/* CRC */
0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */
/*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */
/* stuffing 157 bytes */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
/* 700 ms PCR delay */
#define NGX_RTMP_HLS_DELAY 63000
static ngx_int_t
ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,
size_t in_size)
{
u_char *out;
size_t out_size, n;
ssize_t rc;
static u_char buf[1024];
if (!file->encrypt) {
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: write %uz bytes", in_size);
rc = ngx_write_fd(file->fd, in, in_size);
if (rc < 0) {
return NGX_ERROR;
}
return NGX_OK;
}
/* encrypt */
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: write %uz encrypted bytes", in_size);
out = buf;
out_size = sizeof(buf);
if (file->size > 0 && file->size + in_size >= 16) {
ngx_memcpy(file->buf + file->size, in, 16 - file->size);
in += 16 - file->size;
in_size -= 16 - file->size;
AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT);
out += 16;
out_size -= 16;
file->size = 0;
}
for ( ;; ) {
n = in_size & ~0x0f;
if (n > 0) {
if (n > out_size) {
n = out_size;
}
AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT);
in += n;
in_size -= n;
} else if (out == buf) {
break;
}
rc = ngx_write_fd(file->fd, buf, out - buf + n);
if (rc < 0) {
return NGX_ERROR;
}
out = buf;
out_size = sizeof(buf);
}
if (in_size) {
ngx_memcpy(file->buf + file->size, in, in_size);
file->size += in_size;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file)
{
return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header,
sizeof(ngx_rtmp_mpegts_header));
}
static u_char *
ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr)
{
*p++ = (u_char) (pcr >> 25);
*p++ = (u_char) (pcr >> 17);
*p++ = (u_char) (pcr >> 9);
*p++ = (u_char) (pcr >> 1);
*p++ = (u_char) (pcr << 7 | 0x7e);
*p++ = 0;
return p;
}
static u_char *
ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts)
{
ngx_uint_t val;
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
*p++ = (u_char) val;
val = (((pts >> 15) & 0x7fff) << 1) | 1;
*p++ = (u_char) (val >> 8);
*p++ = (u_char) val;
val = (((pts) & 0x7fff) << 1) | 1;
*p++ = (u_char) (val >> 8);
*p++ = (u_char) val;
return p;
}
ngx_int_t
ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b)
{
ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags;
u_char packet[188], *p, *base;
ngx_int_t first, rc;
ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: pid=%ui, sid=%ui, pts=%uL, "
"dts=%uL, key=%ui, size=%ui",
f->pid, f->sid, f->pts, f->dts,
(ngx_uint_t) f->key, (size_t) (b->last - b->pos));
first = 1;
while (b->pos < b->last) {
p = packet;
f->cc++;
*p++ = 0x47;
*p++ = (u_char) (f->pid >> 8);
if (first) {
p[-1] |= 0x40;
}
*p++ = (u_char) f->pid;
*p++ = 0x10 | (f->cc & 0x0f); /* payload */
if (first) {
if (f->key) {
packet[3] |= 0x20; /* adaptation */
*p++ = 7; /* size */
*p++ = 0x50; /* random access + PCR */
p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);
}
/* PES header */
*p++ = 0x00;
*p++ = 0x00;
*p++ = 0x01;
*p++ = (u_char) f->sid;
header_size = 5;
flags = 0x80; /* PTS */
if (f->dts != f->pts) {
header_size += 5;
flags |= 0x40; /* DTS */
}
pes_size = (b->last - b->pos) + header_size + 3;
if (pes_size > 0xffff) {
pes_size = 0;
}
*p++ = (u_char) (pes_size >> 8);
*p++ = (u_char) pes_size;
*p++ = 0x80; /* H222 */
*p++ = (u_char) flags;
*p++ = (u_char) header_size;
p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts +
NGX_RTMP_HLS_DELAY);
if (f->dts != f->pts) {
p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts +
NGX_RTMP_HLS_DELAY);
}
first = 0;
}
body_size = (ngx_uint_t) (packet + sizeof(packet) - p);
in_size = (ngx_uint_t) (b->last - b->pos);
if (body_size <= in_size) {
ngx_memcpy(p, b->pos, body_size);
b->pos += body_size;
} else {
stuff_size = (body_size - in_size);
if (packet[3] & 0x20) {
/* has adaptation */
base = &packet[5] + packet[4];
p = ngx_movemem(base + stuff_size, base, p - base);
ngx_memset(base, 0xff, stuff_size);
packet[4] += (u_char) stuff_size;
} else {
/* no adaptation */
packet[3] |= 0x20;
p = ngx_movemem(&packet[4] + stuff_size, &packet[4],
p - &packet[4]);
packet[4] = (u_char) (stuff_size - 1);
if (stuff_size >= 2) {
packet[5] = 0;
ngx_memset(&packet[6], 0xff, stuff_size - 2);
}
}
ngx_memcpy(p, b->pos, in_size);
b->pos = b->last;
}
rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet));
if (rc != NGX_OK) {
return rc;
}
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
u_char *key, size_t key_len, uint64_t iv)
{
if (AES_set_encrypt_key(key, key_len * 8, &file->key)) {
return NGX_ERROR;
}
ngx_memzero(file->iv, 8);
file->iv[8] = (u_char) (iv >> 56);
file->iv[9] = (u_char) (iv >> 48);
file->iv[10] = (u_char) (iv >> 40);
file->iv[11] = (u_char) (iv >> 32);
file->iv[12] = (u_char) (iv >> 24);
file->iv[13] = (u_char) (iv >> 16);
file->iv[14] = (u_char) (iv >> 8);
file->iv[15] = (u_char) (iv);
file->encrypt = 1;
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
ngx_log_t *log)
{
file->log = log;
file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,
NGX_FILE_DEFAULT_ACCESS);
if (file->fd == NGX_INVALID_FILE) {
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
"hls: error creating fragment file");
return NGX_ERROR;
}
file->size = 0;
if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
"hls: error writing fragment header");
ngx_close_file(file->fd);
return NGX_ERROR;
}
return NGX_OK;
}
ngx_int_t
ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file)
{
u_char buf[16];
ssize_t rc;
if (file->encrypt) {
ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size);
AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT);
rc = ngx_write_fd(file->fd, buf, 16);
if (rc < 0) {
return NGX_ERROR;
}
}
ngx_close_file(file->fd);
return NGX_OK;
}
================================================
FILE: hls/ngx_rtmp_mpegts.h
================================================
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_
#define _NGX_RTMP_MPEGTS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <openssl/aes.h>
typedef struct {
ngx_fd_t fd;
ngx_log_t *log;
unsigned encrypt:1;
unsigned size:4;
u_char buf[16];
u_char iv[16];
AES_KEY key;
} ngx_rtmp_mpegts_file_t;
typedef struct {
uint64_t pts;
uint64_t dts;
ngx_uint_t pid;
ngx_uint_t sid;
ngx_uint_t cc;
unsigned key:1;
} ngx_rtmp_mpegts_frame_t;
ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
u_char *key, size_t key_len, uint64_t iv);
ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
ngx_log_t *log);
ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file);
ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b);
#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */
================================================
FILE: ngx_rtmp.c
================================================
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <nginx.h>
#include "ngx_rtmp.h"
static char *ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
ngx_rtmp_listen_t *listen);
static char *ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
static ngx_int_t ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
ngx_rtmp_conf_addr_t *addr);
#if (NGX_HAVE_INET6)
static ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
ngx_rtmp_conf_addr_t *addr);
#endif
static ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two);
static ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf,
ngx_rtmp_core_main_conf_t *cmcf);
static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf,
ngx_rtmp_core_main_conf_t *cmcf);
static char * ngx_rtmp_merge_applications(ngx_conf_t *cf,
ngx_array_t *applications, void **app_conf, ngx_rtmp_module_t *module,
ngx_uint_t ctx_index);
static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle);
#if (nginx_version >= 1007011)
ngx_queue_t ngx_rtmp_init_queue;
#elif (nginx_version >= 1007005)
ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue;
#else
ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue;
#endif
ngx_uint_t ngx_rtmp_max_module;
static ngx_command_t ngx_rtmp_commands[] = {
{ ngx_string("rtmp"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_rtmp_block,
0,
0,
NULL },
ngx_null_command
};
static ngx_core_module_t ngx_rtmp_module_ctx = {
ngx_string("rtmp"),
NULL,
NULL
};
ngx_module_t ngx_rtmp_module = {
NGX_MODULE_V1,
&ngx_rtmp_module_ctx, /* module context */
ngx_rtmp_commands, /* module directives */
NGX_CORE_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
ngx_rtmp_init_process, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static char *
ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
ngx_uint_t i, m, mi, s;
ngx_conf_t pcf;
ngx_array_t ports;
ngx_module_t **modules;
ngx_rtmp_listen_t *listen;
ngx_rtmp_module_t *module;
ngx_rtmp_conf_ctx_t *ctx;
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
ngx_rtmp_core_main_conf_t *cmcf;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
/* count the number of the rtmp modules and set up their indices */
#if (nginx_version >= 1009011)
ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE);
#else
ngx_rtmp_max_module = 0;
for (m = 0; ngx_modules[m]; m++) {
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
}
#endif
/* the rtmp main_conf context, it is the same in the all rtmp contexts */
ctx->main_conf = ngx_pcalloc(cf->pool,
sizeof(void *) * ngx_rtmp_max_module);
if (ctx->main_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the rtmp null srv_conf context, it is used to merge
* the server{}s' srv_conf's
*/
ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (ctx->srv_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* the rtmp null app_conf context, it is used to merge
* the server{}s' app_conf's
*/
ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
if (ctx->app_conf == NULL) {
return NGX_CONF_ERROR;
}
/*
* create the main_conf's, the null srv_conf's, and the null app_conf's
* of the all rtmp modules
*/
#if (nginx_version >= 1009011)
modules = cf->cycle->modules;
#else
modules = ngx_modules;
#endif
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
mi = modules[m]->ctx_index;
if (module->create_main_conf) {
ctx->main_conf[mi] = module->create_main_conf(cf);
if (ctx->main_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_srv_conf) {
ctx->srv_conf[mi] = module->create_srv_conf(cf);
if (ctx->srv_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
if (module->create_app_conf) {
ctx->app_conf[mi] = module->create_app_conf(cf);
if (ctx->app_conf[mi] == NULL) {
return NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx;
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
if (module->preconfiguration) {
if (module->preconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
/* parse inside the rtmp{} block */
cf->module_type = NGX_RTMP_MODULE;
cf->cmd_type = NGX_RTMP_MAIN_CONF;
rv = ngx_conf_parse(cf, NULL);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
/* init rtmp{} main_conf's, merge the server{}s' srv_conf's */
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
cscfp = cmcf->servers.elts;
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
mi = modules[m]->ctx_index;
/* init rtmp{} main_conf's */
cf->ctx = ctx;
if (module->init_main_conf) {
rv = module->init_main_conf(cf, ctx->main_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
for (s = 0; s < cmcf->servers.nelts; s++) {
/* merge the server{}s' srv_conf's */
cf->ctx = cscfp[s]->ctx;
if (module->merge_srv_conf) {
rv = module->merge_srv_conf(cf,
ctx->srv_conf[mi],
cscfp[s]->ctx->srv_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
if (module->merge_app_conf) {
/* merge the server{}'s app_conf */
/*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/
rv = module->merge_app_conf(cf,
ctx->app_conf[mi],
cscfp[s]->ctx->app_conf[mi]);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
/* merge the applications{}' app_conf's */
cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index];
rv = ngx_rtmp_merge_applications(cf, &cscf->applications,
cscfp[s]->ctx->app_conf,
module, mi);
if (rv != NGX_CONF_OK) {
*cf = pcf;
return rv;
}
}
}
}
if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
for (m = 0; modules[m]; m++) {
if (modules[m]->type != NGX_RTMP_MODULE) {
continue;
}
module = modules[m]->ctx;
if (module->postconfiguration) {
if (module->postconfiguration(cf) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
}
*cf = pcf;
if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) {
return NGX_CONF_ERROR;
}
if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
listen = cmcf->listen.elts;
for (i = 0; i < cmcf->listen.nelts; i++) {
if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
return ngx_rtmp_optimize_servers(cf, &ports);
}
static char *
ngx_rtmp_merge_applications(ngx_conf_t *cf, ngx_array_t *applications,
void **app_conf, ngx_rtmp_module_t *module, ngx_uint_t ctx_index)
{
char *rv;
ngx_rtmp_conf_ctx_t *ctx, saved;
ngx_rtmp_core_app_conf_t **cacfp;
ngx_uint_t n;
ngx_rtmp_core_app_conf_t *cacf;
if (applications == NULL) {
return NGX_CONF_OK;
}
ctx = (ngx_rtmp_conf_ctx_t *) cf->ctx;
saved = *ctx;
cacfp = applications->elts;
for (n = 0; n < applications->nelts; ++n, ++cacfp) {
ctx->app_conf = (*cacfp)->app_conf;
rv = module->merge_app_conf(cf, app_conf[ctx_index],
(*cacfp)->app_conf[ctx_index]);
if (rv != NGX_CONF_OK) {
return rv;
}
cacf = (*cacfp)->app_conf[ngx_rtmp_core_module.ctx_index];
rv = ngx_rtmp_merge_applications(cf, &cacf->applications,
(*cacfp)->app_conf,
module, ctx_index);
if (rv != NGX_CONF_OK) {
return rv;
}
}
*ctx = saved;
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_init_events(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
{
size_t n;
for(n = 0; n < NGX_RTMP_MAX_EVENT; ++n) {
if (ngx_array_init(&cmcf->events[n], cf->pool, 1,
sizeof(ngx_rtmp_handler_pt)) != NGX_OK)
{
return NGX_ERROR;
}
}
if (ngx_array_init(&cmcf->amf, cf->pool, 1,
sizeof(ngx_rtmp_amf_handler_t)) != NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
{
ngx_hash_init_t calls_hash;
ngx_rtmp_handler_pt *eh;
ngx_rtmp_amf_handler_t *h;
ngx_hash_key_t *ha;
size_t n, m;
static size_t pm_events[] = {
NGX_RTMP_MSG_CHUNK_SIZE,
NGX_RTMP_MSG_ABORT,
NGX_RTMP_MSG_ACK,
NGX_RTMP_MSG_ACK_SIZE,
NGX_RTMP_MSG_BANDWIDTH
};
static size_t amf_events[] = {
NGX_RTMP_MSG_AMF_CMD,
NGX_RTMP_MSG_AMF_META,
NGX_RTMP_MSG_AMF_SHARED,
NGX_RTMP_MSG_AMF3_CMD,
NGX_RTMP_MSG_AMF3_META,
NGX_RTMP_MSG_AMF3_SHARED
};
/* init standard protocol events */
for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) {
eh = ngx_array_push(&cmcf->events[pm_events[n]]);
*eh = ngx_rtmp_protocol_message_handler;
}
/* init amf events */
for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) {
eh = ngx_array_push(&cmcf->events[amf_events[n]]);
*eh = ngx_rtmp_amf_message_handler;
}
/* init user protocol events */
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]);
*eh = ngx_rtmp_user_message_handler;
/* aggregate to audio/video map */
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]);
*eh = ngx_rtmp_aggregate_message_handler;
/* init amf callbacks */
ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t));
h = cmcf->amf.elts;
for(n = 0; n < cmcf->amf.nelts; ++n, ++h) {
ha = cmcf->amf_arrays.elts;
for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) {
if (h->name.len == ha->key.len
&& !ngx_strncmp(h->name.data, ha->key.data, ha->key.len))
{
break;
}
}
if (m == cmcf->amf_arrays.nelts) {
ha = ngx_array_push(&cmcf->amf_arrays);
ha->key = h->name;
ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len);
ha->value = ngx_array_create(cf->pool, 1,
sizeof(ngx_rtmp_handler_pt));
if (ha->value == NULL) {
return NGX_ERROR;
}
}
eh = ngx_array_push((ngx_array_t*)ha->value);
*eh = h->handler;
}
calls_hash.hash = &cmcf->amf_hash;
calls_hash.key = ngx_hash_key_lc;
calls_hash.max_size = 512;
calls_hash.bucket_size = ngx_cacheline_size;
calls_hash.name = "amf_hash";
calls_hash.pool = cf->pool;
calls_hash.temp_pool = NULL;
if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
ngx_rtmp_listen_t *listen)
{
in_port_t p;
ngx_uint_t i;
struct sockaddr *sa;
struct sockaddr_in *sin;
ngx_rtmp_conf_port_t *port;
ngx_rtmp_conf_addr_t *addr;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
sa = (struct sockaddr *) &listen->sockaddr;
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
p = sin6->sin6_port;
break;
#endif
default: /* AF_INET */
sin = (struct sockaddr_in *) sa;
p = sin->sin_port;
break;
}
port = ports->elts;
for (i = 0; i < ports->nelts; i++) {
if (p == port[i].port && sa->sa_family == port[i].family) {
/* a port is already in the port list */
port = &port[i];
goto found;
}
}
/* add a port to the port list */
port = ngx_array_push(ports);
if (port == NULL) {
return NGX_ERROR;
}
port->family = sa->sa_family;
port->port = p;
if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
sizeof(ngx_rtmp_conf_addr_t))
!= NGX_OK)
{
return NGX_ERROR;
}
found:
addr = ngx_array_push(&port->addrs);
if (addr == NULL) {
return NGX_ERROR;
}
addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
addr->socklen = listen->socklen;
addr->ctx = listen->ctx;
addr->bind = listen->bind;
addr->wildcard = listen->wildcard;
addr->so_keepalive = listen->so_keepalive;
addr->proxy_protocol = listen->proxy_protocol;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
addr->tcp_keepidle = listen->tcp_keepidle;
addr->tcp_keepintvl = listen->tcp_keepintvl;
addr->tcp_keepcnt = listen->tcp_keepcnt;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
addr->ipv6only = listen->ipv6only;
#endif
return NGX_OK;
}
static char *
ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
{
ngx_uint_t i, p, last, bind_wildcard;
ngx_listening_t *ls;
ngx_rtmp_port_t *mport;
ngx_rtmp_conf_port_t *port;
ngx_rtmp_conf_addr_t *addr;
port = ports->elts;
for (p = 0; p < ports->nelts; p++) {
ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs);
addr = port[p].addrs.elts;
last = port[p].addrs.nelts;
/*
* if there is the binding to the "*:port" then we need to bind()
* to the "*:port" only and ignore the other bindings
*/
if (addr[last - 1].wildcard) {
addr[last - 1].bind = 1;
bind_wildcard = 1;
} else {
bind_wildcard = 0;
}
i = 0;
while (i < last) {
if (bind_wildcard && !addr[i].bind) {
i++;
continue;
}
ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
if (ls == NULL) {
return NGX_CONF_ERROR;
}
ls->addr_ntop = 1;
ls->handler = ngx_rtmp_init_connection;
ls->pool_size = 4096;
/* TODO: error_log directive */
ls->logp = &cf->cycle->new_log;
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
ls->keepalive = addr[i].so_keepalive;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
ls->keepidle = addr[i].tcp_keepidle;
ls->keepintvl = addr[i].tcp_keepintvl;
ls->keepcnt = addr[i].tcp_keepcnt;
#endif
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
ls->ipv6only = addr[i].ipv6only;
#endif
ls->wildcard = addr[i].wildcard;
mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t));
if (mport == NULL) {
return NGX_CONF_ERROR;
}
ls->servers = mport;
if (i == last - 1) {
mport->naddrs = last;
} else {
mport->naddrs = 1;
i = 0;
}
switch (ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
#endif
default: /* AF_INET */
if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {
return NGX_CONF_ERROR;
}
break;
}
addr++;
last--;
}
}
return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
ngx_rtmp_conf_addr_t *addr)
{
u_char *p;
size_t len;
ngx_uint_t i;
ngx_rtmp_in_addr_t *addrs;
struct sockaddr_in *sin;
u_char buf[NGX_SOCKADDR_STRLEN];
mport->addrs = ngx_pcalloc(cf->pool,
mport->naddrs * sizeof(ngx_rtmp_in_addr_t));
if (mport->addrs == NULL) {
return NGX_ERROR;
}
addrs = mport->addrs;
for (i = 0; i < mport->naddrs; i++) {
sin = (struct sockaddr_in *) addr[i].sockaddr;
addrs[i].addr = sin->sin_addr.s_addr;
addrs[i].conf.ctx = addr[i].ctx;
len = ngx_sock_ntop(addr[i].sockaddr,
#if (nginx_version >= 1005003)
addr[i].socklen,
#endif
buf, NGX_SOCKADDR_STRLEN, 1);
p = ngx_pnalloc(cf->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, buf, len);
addrs[i].conf.addr_text.len = len;
addrs[i].conf.addr_text.data = p;
addrs[i].conf.proxy_protocol = addr->proxy_protocol;
}
return NGX_OK;
}
#if (NGX_HAVE_INET6)
static ngx_int_t
ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
ngx_rtmp_conf_addr_t *addr)
{
u_char *p;
size_t len;
ngx_uint_t i;
ngx_rtmp_in6_addr_t *addrs6;
struct sockaddr_in6 *sin6;
u_char buf[NGX_SOCKADDR_STRLEN];
mport->addrs = ngx_pcalloc(cf->pool,
mport->naddrs * sizeof(ngx_rtmp_in6_addr_t));
if (mport->addrs == NULL) {
return NGX_ERROR;
}
addrs6 = mport->addrs;
for (i = 0; i < mport->naddrs; i++) {
sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;
addrs6[i].addr6 = sin6->sin6_addr;
addrs6[i].conf.ctx = addr[i].ctx;
len = ngx_sock_ntop(addr[i].sockaddr,
#if (nginx_version >= 1005003)
addr[i].socklen,
#endif
buf, NGX_SOCKADDR_STRLEN, 1);
p = ngx_pnalloc(cf->pool, len);
if (p == NULL) {
return NGX_ERROR;
}
ngx_memcpy(p, buf, len);
addrs6[i].conf.addr_text.len = len;
addrs6[i].conf.addr_text.data = p;
addrs6[i].conf.proxy_protocol = addr->proxy_protocol;
}
return NGX_OK;
}
#endif
static ngx_int_t
ngx_rtmp_cmp_conf_addrs(const void *one, const void *two)
{
ngx_rtmp_conf_addr_t *first, *second;
first = (ngx_rtmp_conf_addr_t *) one;
second = (ngx_rtmp_conf_addr_t *) two;
if (first->wildcard) {
/* a wildcard must be the last resort, shift it to the end */
return 1;
}
if (first->bind && !second->bind) {
/* shift explicit bind()ed addresses to the start */
return -1;
}
if (!first->bind && second->bind) {
/* shift explicit bind()ed addresses to the start */
return 1;
}
/* do not sort by default */
return 0;
}
ngx_int_t
ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,
ngx_rtmp_header_t *h, ngx_chain_t *in)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_array_t *ch;
ngx_rtmp_handler_pt *hh;
size_t n;
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
ch = &cmcf->events[evt];
hh = ch->elts;
for(n = 0; n < ch->nelts; ++n, ++hh) {
if (*hh && (*hh)(s, h, in) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
void *
ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n)
{
u_char *d, *s;
d = dst;
s = (u_char*)src + n - 1;
while(s >= (u_char*)src) {
*d++ = *s--;
}
return dst;
}
static ngx_int_t
ngx_rtmp_init_process(ngx_cycle_t *cycle)
{
#if (nginx_version >= 1007005)
ngx_queue_init(&ngx_rtmp_init_queue);
#endif
return NGX_OK;
}
================================================
FILE: ngx_rtmp.h
================================================
/*
* Copyright (C) Roman Arutyunyan
*/
#ifndef _NGX_RTMP_H_INCLUDED_
#define _NGX_RTMP_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_connect.h>
#include <nginx.h>
#include "ngx_rtmp_amf.h"
#include "ngx_rtmp_bandwidth.h"
#if (NGX_WIN32)
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
#endif
typedef struct {
void **main_conf;
void **srv_conf;
void **app_conf;
} ngx_rtmp_conf_ctx_t;
typedef struct {
u_char sockaddr[NGX_SOCKADDRLEN];
socklen_t socklen;
/* server ctx */
ngx_rtmp_conf_ctx_t *ctx;
unsigned bind:1;
unsigned wildcard:1;
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int tcp_keepidle;
int tcp_keepintvl;
int tcp_keepcnt;
#endif
} ngx_rtmp_listen_t;
typedef struct {
ngx_rtmp_conf_ctx_t *ctx;
ngx_str_t addr_text;
unsigned proxy_protocol:1;
} ngx_rtmp_addr_conf_t;
typedef struct {
in_addr_t addr;
ngx_rtmp_addr_conf_t conf;
} ngx_rtmp_in_addr_t;
#if (NGX_HAVE_INET6)
typedef struct {
struct in6_addr addr6;
ngx_rtmp_addr_conf_t conf;
} ngx_rtmp_in6_addr_t;
#endif
typedef struct {
void *addrs;
ngx_uint_t naddrs;
} ngx_rtmp_port_t;
typedef struct {
int family;
in_port_t port;
ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */
} ngx_rtmp_conf_port_t;
typedef struct {
struct sockaddr *sockaddr;
socklen_t socklen;
ngx_rtmp_conf_ctx_t *ctx;
unsigned bind:1;
unsigned wildcard:1;
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
unsigned ipv6only:2;
#endif
unsigned so_keepalive:2;
unsigned proxy_protocol:1;
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
int tcp_keepidle;
int tcp_keepintvl;
int tcp_keepcnt;
#endif
} ngx_rtmp_conf_addr_t;
#define NGX_RTMP_VERSION 3
#define NGX_LOG_DEBUG_RTMP NGX_LOG_DEBUG_CORE
#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128
/* RTMP message types */
#define NGX_RTMP_MSG_CHUNK_SIZE 1
#define NGX_RTMP_MSG_ABORT 2
#define NGX_RTMP_MSG_ACK 3
#define NGX_RTMP_MSG_USER 4
#define NGX_RTMP_MSG_ACK_SIZE 5
#define NGX_RTMP_MSG_BANDWIDTH 6
#define NGX_RTMP_MSG_EDGE 7
#define NGX_RTMP_MSG_AUDIO 8
#define NGX_RTMP_MSG_VIDEO 9
#define NGX_RTMP_MSG_AMF3_META 15
#define NGX_RTMP_MSG_AMF3_SHARED 16
#define NGX_RTMP_MSG_AMF3_CMD 17
#define NGX_RTMP_MSG_AMF_META 18
#define NGX_RTMP_MSG_AMF_SHARED 19
#define NGX_RTMP_MSG_AMF_CMD 20
#define NGX_RTMP_MSG_AGGREGATE 22
#define NGX_RTMP_MSG_MAX 22
#define NGX_RTMP_MAX_CHUNK_SIZE 10485760
#define NGX_RTMP_CONNECT NGX_RTMP_MSG_MAX + 1
#define NGX_RTMP_DISCONNECT NGX_RTMP_MSG_MAX + 2
#define NGX_RTMP_HANDSHAKE_DONE NGX_RTMP_MSG_MAX + 3
#define NGX_RTMP_MAX_EVENT NGX_RTMP_MSG_MAX + 4
/* RMTP control message types */
#define NGX_RTMP_USER_STREAM_BEGIN 0
#define NGX_RTMP_USER_ST
gitextract_2a2ek2fd/
├── AUTHORS
├── LICENSE
├── README.md
├── config
├── dash/
│ ├── ngx_rtmp_dash_module.c
│ ├── ngx_rtmp_mp4.c
│ └── ngx_rtmp_mp4.h
├── doc/
│ └── README.md
├── hls/
│ ├── ngx_rtmp_hls_module.c
│ ├── ngx_rtmp_mpegts.c
│ └── ngx_rtmp_mpegts.h
├── ngx_rtmp.c
├── ngx_rtmp.h
├── ngx_rtmp_access_module.c
├── ngx_rtmp_amf.c
├── ngx_rtmp_amf.h
├── ngx_rtmp_auto_push_module.c
├── ngx_rtmp_bandwidth.c
├── ngx_rtmp_bandwidth.h
├── ngx_rtmp_bitop.c
├── ngx_rtmp_bitop.h
├── ngx_rtmp_cmd_module.c
├── ngx_rtmp_cmd_module.h
├── ngx_rtmp_codec_module.c
├── ngx_rtmp_codec_module.h
├── ngx_rtmp_control_module.c
├── ngx_rtmp_core_module.c
├── ngx_rtmp_eval.c
├── ngx_rtmp_eval.h
├── ngx_rtmp_exec_module.c
├── ngx_rtmp_flv_module.c
├── ngx_rtmp_handler.c
├── ngx_rtmp_handshake.c
├── ngx_rtmp_init.c
├── ngx_rtmp_limit_module.c
├── ngx_rtmp_live_module.c
├── ngx_rtmp_live_module.h
├── ngx_rtmp_log_module.c
├── ngx_rtmp_mp4_module.c
├── ngx_rtmp_netcall_module.c
├── ngx_rtmp_netcall_module.h
├── ngx_rtmp_notify_module.c
├── ngx_rtmp_play_module.c
├── ngx_rtmp_play_module.h
├── ngx_rtmp_proxy_protocol.c
├── ngx_rtmp_proxy_protocol.h
├── ngx_rtmp_receive.c
├── ngx_rtmp_record_module.c
├── ngx_rtmp_record_module.h
├── ngx_rtmp_relay_module.c
├── ngx_rtmp_relay_module.h
├── ngx_rtmp_send.c
├── ngx_rtmp_shared.c
├── ngx_rtmp_stat_module.c
├── ngx_rtmp_streams.h
├── ngx_rtmp_version.h
├── stat.xsl
└── test/
├── README.md
├── dump.sh
├── ffstream.sh
├── nginx.conf
├── play.sh
├── rtmp-publisher/
│ ├── README.md
│ ├── RtmpPlayer.mxml
│ ├── RtmpPlayer.swf
│ ├── RtmpPlayerLight.mxml
│ ├── RtmpPlayerLight.swf
│ ├── RtmpPublisher.mxml
│ ├── RtmpPublisher.swf
│ ├── player.html
│ ├── publisher.html
│ └── swfobject.js
└── www/
├── index.html
├── jwplayer/
│ ├── jwplayer.flash.swf
│ └── jwplayer.js
├── jwplayer_old/
│ ├── player.swf
│ └── swfobject.js
└── record.html
SYMBOL INDEX (757 symbols across 51 files)
FILE: dash/ngx_rtmp_dash_module.c
type ngx_rtmp_dash_frag_t (line 30) | typedef struct {
type ngx_rtmp_dash_track_t (line 36) | typedef struct {
type ngx_rtmp_dash_ctx_t (line 50) | typedef struct {
type ngx_rtmp_dash_cleanup_t (line 75) | typedef struct {
type ngx_rtmp_dash_app_conf_t (line 81) | typedef struct {
function ngx_rtmp_dash_frag_t (line 172) | static ngx_rtmp_dash_frag_t *
function ngx_rtmp_dash_next_frag (line 185) | static void
function ngx_int_t (line 202) | static ngx_int_t
function ngx_int_t (line 215) | static ngx_int_t
function ngx_int_t (line 447) | static ngx_int_t
function ngx_rtmp_dash_close_fragment (line 523) | static void
function ngx_int_t (line 629) | static ngx_int_t
function ngx_int_t (line 656) | static ngx_int_t
function ngx_int_t (line 704) | static ngx_int_t
function ngx_int_t (line 728) | static ngx_int_t
function ngx_int_t (line 825) | static ngx_int_t
function ngx_int_t (line 956) | static ngx_int_t
function ngx_rtmp_dash_update_fragments (line 980) | static void
function ngx_int_t (line 1043) | static ngx_int_t
function ngx_int_t (line 1104) | static ngx_int_t
function ngx_int_t (line 1152) | static ngx_int_t
function ngx_int_t (line 1210) | static ngx_int_t
function ngx_int_t (line 1217) | static ngx_int_t
function ngx_int_t (line 1226) | static ngx_int_t
function ngx_msec_t (line 1412) | static ngx_msec_t
function ngx_int_t (line 1504) | static ngx_int_t
FILE: dash/ngx_rtmp_mp4.c
function ngx_int_t (line 9) | static ngx_int_t
function ngx_int_t (line 29) | static ngx_int_t
function ngx_int_t (line 48) | static ngx_int_t
function ngx_int_t (line 66) | static ngx_int_t
function ngx_int_t (line 83) | static ngx_int_t
function ngx_int_t (line 93) | static ngx_int_t
function ngx_int_t (line 106) | static ngx_int_t
function u_char (line 119) | static u_char *
function ngx_int_t (line 138) | static ngx_int_t
function ngx_int_t (line 159) | static ngx_int_t
function ngx_int_t (line 185) | ngx_int_t
function ngx_int_t (line 209) | ngx_int_t
function ngx_int_t (line 233) | static ngx_int_t
function ngx_int_t (line 281) | static ngx_int_t
function ngx_int_t (line 340) | static ngx_int_t
function ngx_int_t (line 374) | static ngx_int_t
function ngx_int_t (line 412) | static ngx_int_t
function ngx_int_t (line 431) | static ngx_int_t
function ngx_int_t (line 450) | static ngx_int_t
function ngx_int_t (line 477) | static ngx_int_t
function ngx_int_t (line 492) | static ngx_int_t
function ngx_int_t (line 538) | static ngx_int_t
function ngx_int_t (line 600) | static ngx_int_t
function ngx_int_t (line 680) | static ngx_int_t
function ngx_int_t (line 730) | static ngx_int_t
function ngx_int_t (line 756) | static ngx_int_t
function ngx_int_t (line 772) | static ngx_int_t
function ngx_int_t (line 788) | static ngx_int_t
function ngx_int_t (line 805) | static ngx_int_t
function ngx_int_t (line 821) | static ngx_int_t
function ngx_int_t (line 841) | static ngx_int_t
function ngx_int_t (line 864) | static ngx_int_t
function ngx_int_t (line 881) | static ngx_int_t
function ngx_int_t (line 898) | static ngx_int_t
function ngx_int_t (line 933) | ngx_int_t
function ngx_int_t (line 951) | static ngx_int_t
function ngx_int_t (line 970) | static ngx_int_t
function ngx_int_t (line 987) | static ngx_int_t
function ngx_int_t (line 1052) | static ngx_int_t
function ngx_int_t (line 1071) | static ngx_int_t
function ngx_int_t (line 1090) | ngx_int_t
function ngx_int_t (line 1140) | ngx_int_t
function ngx_uint_t (line 1159) | ngx_uint_t
FILE: dash/ngx_rtmp_mp4.h
type ngx_rtmp_mp4_sample_t (line 18) | typedef struct {
type ngx_rtmp_mp4_file_type_t (line 27) | typedef enum {
type ngx_rtmp_mp4_track_type_t (line 33) | typedef enum {
FILE: hls/ngx_rtmp_hls_module.c
type ngx_rtmp_hls_frag_t (line 36) | typedef struct {
type ngx_rtmp_hls_variant_t (line 45) | typedef struct {
type ngx_rtmp_hls_ctx_t (line 51) | typedef struct {
type ngx_rtmp_hls_cleanup_t (line 85) | typedef struct {
type ngx_rtmp_hls_app_conf_t (line 92) | typedef struct {
function ngx_rtmp_hls_frag_t (line 346) | static ngx_rtmp_hls_frag_t *
function ngx_rtmp_hls_next_frag (line 359) | static void
function ngx_int_t (line 376) | static ngx_int_t
function ngx_int_t (line 389) | static ngx_int_t
function ngx_int_t (line 482) | static ngx_int_t
function ngx_int_t (line 616) | static ngx_int_t
function ngx_int_t (line 668) | static ngx_int_t
function ngx_int_t (line 683) | static ngx_int_t
function ngx_rtmp_hls_get_fragment_id (line 794) | static uint64_t
function ngx_int_t (line 818) | static ngx_int_t
function ngx_int_t (line 843) | static ngx_int_t
function ngx_rtmp_hls_restore_stream (line 974) | static void
function ngx_int_t (line 1169) | static ngx_int_t
function ngx_int_t (line 1273) | static ngx_int_t
function ngx_int_t (line 1477) | static ngx_int_t
function ngx_int_t (line 1501) | static ngx_int_t
function ngx_rtmp_hls_update_fragment (line 1561) | static void
function ngx_int_t (line 1633) | static ngx_int_t
function ngx_int_t (line 1678) | static ngx_int_t
function ngx_int_t (line 1839) | static ngx_int_t
function ngx_int_t (line 2050) | static ngx_int_t
function ngx_int_t (line 2057) | static ngx_int_t
function ngx_int_t (line 2068) | static ngx_int_t
function ngx_msec_t (line 2215) | static ngx_msec_t
function ngx_int_t (line 2432) | static ngx_int_t
FILE: hls/ngx_rtmp_mpegts.c
function ngx_int_t (line 78) | static ngx_int_t
function ngx_int_t (line 157) | static ngx_int_t
function u_char (line 165) | static u_char *
function u_char (line 179) | static u_char *
function ngx_int_t (line 199) | ngx_int_t
function ngx_int_t (line 326) | ngx_int_t
function ngx_int_t (line 351) | ngx_int_t
function ngx_int_t (line 379) | ngx_int_t
FILE: hls/ngx_rtmp_mpegts.h
type ngx_rtmp_mpegts_file_t (line 16) | typedef struct {
type ngx_rtmp_mpegts_frame_t (line 27) | typedef struct {
FILE: ngx_rtmp.c
function ngx_int_t (line 382) | static ngx_int_t
function ngx_int_t (line 405) | static ngx_int_t
function ngx_int_t (line 497) | static ngx_int_t
function ngx_int_t (line 689) | static ngx_int_t
function ngx_int_t (line 739) | static ngx_int_t
function ngx_int_t (line 789) | static ngx_int_t
function ngx_int_t (line 818) | ngx_int_t
function ngx_int_t (line 856) | static ngx_int_t
FILE: ngx_rtmp.h
type __int8 (line 22) | typedef __int8 int8_t;
type ngx_rtmp_conf_ctx_t (line 27) | typedef struct {
type ngx_rtmp_listen_t (line 34) | typedef struct {
type ngx_rtmp_addr_conf_t (line 56) | typedef struct {
type ngx_rtmp_in_addr_t (line 62) | typedef struct {
type ngx_rtmp_in6_addr_t (line 70) | typedef struct {
type ngx_rtmp_port_t (line 78) | typedef struct {
type ngx_rtmp_conf_port_t (line 84) | typedef struct {
type ngx_rtmp_conf_addr_t (line 91) | typedef struct {
type ngx_rtmp_header_t (line 165) | typedef struct {
type ngx_rtmp_stream_t (line 174) | typedef struct {
type ngx_rtmp_session_t (line 191) | typedef struct {
type ngx_int_t (line 284) | typedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s,
type ngx_rtmp_amf_handler_t (line 288) | typedef struct {
type ngx_rtmp_core_main_conf_t (line 294) | typedef struct {
type ngx_rtmp_core_srv_conf_t (line 310) | typedef struct ngx_rtmp_core_srv_conf_s {
type ngx_rtmp_core_app_conf_t (line 337) | typedef struct {
type ngx_rtmp_error_log_ctx_t (line 344) | typedef struct {
type ngx_rtmp_module_t (line 350) | typedef struct {
function ngx_inline (line 426) | static ngx_inline uint16_t
function ngx_inline (line 433) | static ngx_inline uint32_t
function ngx_inline (line 440) | static ngx_inline uint64_t
function ngx_inline (line 593) | static ngx_inline ngx_int_t
function ngx_inline (line 600) | static ngx_inline ngx_int_t
FILE: ngx_rtmp_access_module.c
type ngx_rtmp_access_rule_t (line 29) | typedef struct {
type ngx_rtmp_access_rule6_t (line 39) | typedef struct {
type ngx_rtmp_access_app_conf_t (line 49) | typedef struct {
function ngx_int_t (line 135) | static ngx_int_t
function ngx_int_t (line 180) | static ngx_int_t
function ngx_int_t (line 193) | static ngx_int_t
function ngx_int_t (line 220) | static ngx_int_t
function ngx_int_t (line 269) | static ngx_int_t
function ngx_int_t (line 433) | static ngx_int_t
function ngx_int_t (line 449) | static ngx_int_t
function ngx_int_t (line 460) | static ngx_int_t
FILE: ngx_rtmp_amf.c
function ngx_inline (line 14) | static ngx_inline void*
function ngx_rtmp_amf_debug (line 33) | static void
function ngx_int_t (line 66) | static ngx_int_t
function ngx_int_t (line 116) | static ngx_int_t
function ngx_int_t (line 172) | static ngx_int_t
function ngx_int_t (line 249) | static ngx_int_t
function ngx_int_t (line 272) | static ngx_int_t
function ngx_int_t (line 300) | static ngx_int_t
function ngx_int_t (line 309) | ngx_int_t
function ngx_int_t (line 467) | static ngx_int_t
function ngx_int_t (line 503) | static ngx_int_t
function ngx_int_t (line 529) | ngx_int_t
FILE: ngx_rtmp_amf.h
type ngx_rtmp_amf_elt_t (line 41) | typedef struct {
type ngx_chain_t (line 49) | typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg);
type ngx_rtmp_amf_ctx_t (line 52) | typedef struct {
FILE: ngx_rtmp_auto_push_module.c
type ngx_rtmp_auto_push_ctx_t (line 29) | typedef struct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t;
type ngx_rtmp_auto_push_ctx_s (line 31) | struct ngx_rtmp_auto_push_ctx_s {
type ngx_rtmp_auto_push_conf_t (line 39) | typedef struct {
function ngx_int_t (line 127) | static ngx_int_t
function ngx_rtmp_auto_push_exit_process (line 271) | static void
function ngx_rtmp_auto_push_reconnect (line 328) | static void
function ngx_int_t (line 476) | static ngx_int_t
function ngx_int_t (line 524) | static ngx_int_t
FILE: ngx_rtmp_bandwidth.c
function ngx_rtmp_update_bandwidth (line 12) | void
FILE: ngx_rtmp_bandwidth.h
type ngx_rtmp_bandwidth_t (line 19) | typedef struct {
FILE: ngx_rtmp_bitop.c
function ngx_rtmp_bit_init_reader (line 12) | void
function ngx_rtmp_bit_read (line 22) | uint64_t
function ngx_rtmp_bit_read_golomb (line 55) | uint64_t
FILE: ngx_rtmp_bitop.h
type ngx_rtmp_bit_reader_t (line 15) | typedef struct {
FILE: ngx_rtmp_cmd_module.c
function ngx_rtmp_cmd_fill_args (line 97) | void
function ngx_int_t (line 113) | static ngx_int_t
function ngx_int_t (line 195) | static ngx_int_t
function ngx_int_t (line 327) | static ngx_int_t
function ngx_int_t (line 352) | static ngx_int_t
function ngx_int_t (line 393) | static ngx_int_t
function ngx_int_t (line 418) | static ngx_int_t
function ngx_int_t (line 425) | static ngx_int_t
function ngx_int_t (line 456) | static ngx_int_t
function ngx_int_t (line 469) | static ngx_int_t
function ngx_int_t (line 513) | static ngx_int_t
function ngx_int_t (line 519) | static ngx_int_t
function ngx_int_t (line 574) | static ngx_int_t
function ngx_int_t (line 581) | static ngx_int_t
function ngx_int_t (line 644) | static ngx_int_t
function ngx_int_t (line 685) | static ngx_int_t
function ngx_int_t (line 692) | static ngx_int_t
function ngx_int_t (line 702) | static ngx_int_t
function ngx_int_t (line 709) | static ngx_int_t
function ngx_int_t (line 746) | static ngx_int_t
function ngx_int_t (line 753) | static ngx_int_t
function ngx_int_t (line 760) | static ngx_int_t
function ngx_int_t (line 767) | static ngx_int_t
function ngx_int_t (line 774) | static ngx_int_t
function ngx_int_t (line 782) | static ngx_int_t
function ngx_int_t (line 803) | static ngx_int_t
FILE: ngx_rtmp_cmd_module.h
type ngx_rtmp_connect_t (line 24) | typedef struct {
type ngx_rtmp_create_stream_t (line 38) | typedef struct {
type ngx_rtmp_delete_stream_t (line 44) | typedef struct {
type ngx_rtmp_close_stream_t (line 49) | typedef struct {
type ngx_rtmp_publish_t (line 54) | typedef struct {
type ngx_rtmp_play_t (line 62) | typedef struct {
type ngx_rtmp_seek_t (line 72) | typedef struct {
type ngx_rtmp_pause_t (line 77) | typedef struct {
type ngx_rtmp_msid_t (line 83) | typedef struct {
type ngx_rtmp_msid_t (line 88) | typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t;
type ngx_rtmp_msid_t (line 89) | typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t;
type ngx_rtmp_msid_t (line 90) | typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t;
type ngx_rtmp_msid_t (line 91) | typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t;
type ngx_rtmp_set_buflen_t (line 94) | typedef struct {
type ngx_int_t (line 104) | typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 106) | typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s);
type ngx_int_t (line 107) | typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 109) | typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 111) | typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 113) | typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 115) | typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 117) | typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 119) | typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 122) | typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 124) | typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 126) | typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 128) | typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 130) | typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s,
FILE: ngx_rtmp_codec_module.c
type ngx_rtmp_codec_app_conf_t (line 39) | typedef struct {
function u_char (line 128) | u_char *
function u_char (line 137) | u_char *
function ngx_uint_t (line 146) | static ngx_uint_t
function ngx_int_t (line 160) | static ngx_int_t
function ngx_int_t (line 193) | static ngx_int_t
function ngx_rtmp_codec_parse_aac_header (line 271) | static void
function ngx_rtmp_codec_parse_avc_header (line 357) | static void
function ngx_rtmp_codec_dump_header (line 537) | static void
function ngx_int_t (line 560) | static ngx_int_t
function ngx_int_t (line 683) | static ngx_int_t
function ngx_int_t (line 708) | static ngx_int_t
function ngx_int_t (line 729) | static ngx_int_t
function ngx_int_t (line 921) | static ngx_int_t
FILE: ngx_rtmp_codec_module.h
type ngx_rtmp_codec_ctx_t (line 52) | typedef struct {
FILE: ngx_rtmp_control_module.c
type ngx_rtmp_control_ctx_t (line 38) | typedef struct {
type ngx_rtmp_control_loc_conf_t (line 47) | typedef struct {
function ngx_int_t (line 431) | static ngx_int_t
function ngx_int_t (line 476) | static ngx_int_t
function ngx_int_t (line 550) | static ngx_int_t
function ngx_int_t (line 624) | static ngx_int_t
FILE: ngx_rtmp_core_module.c
type sockaddr (line 506) | struct sockaddr
type sockaddr_in (line 508) | struct sockaddr_in
type sockaddr_in6 (line 511) | struct sockaddr_in6
type sockaddr (line 537) | struct sockaddr
type sockaddr_in6 (line 547) | struct sockaddr_in6
type sockaddr_in6 (line 549) | struct sockaddr_in6
type sockaddr_in (line 555) | struct sockaddr_in
type sockaddr_in (line 557) | struct sockaddr_in
type sockaddr (line 611) | struct sockaddr
type sockaddr (line 614) | struct sockaddr
FILE: ngx_rtmp_eval.c
function ngx_rtmp_eval_session_str (line 15) | static void
function ngx_rtmp_eval_connection_str (line 22) | static void
function ngx_rtmp_eval_append (line 61) | static void
function ngx_rtmp_eval_append_var (line 83) | static void
function ngx_int_t (line 103) | ngx_int_t
function ngx_int_t (line 195) | ngx_int_t
FILE: ngx_rtmp_eval.h
type ngx_rtmp_eval_t (line 16) | typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t;
type ngx_rtmp_eval_s (line 23) | struct ngx_rtmp_eval_s {
FILE: ngx_rtmp_exec_module.c
type ngx_rtmp_exec_conf_t (line 66) | typedef struct {
type ngx_rtmp_exec_t (line 75) | typedef struct {
type ngx_rtmp_exec_main_conf_t (line 93) | typedef struct {
type ngx_rtmp_exec_pull_ctx_t (line 102) | typedef struct ngx_rtmp_exec_pull_ctx_s ngx_rtmp_exec_pull_ctx_t;
type ngx_rtmp_exec_pull_ctx_s (line 104) | struct ngx_rtmp_exec_pull_ctx_s {
type ngx_rtmp_exec_app_conf_t (line 114) | typedef struct {
type ngx_rtmp_exec_ctx_t (line 125) | typedef struct {
function ngx_rtmp_exec_eval_ctx_cstr (line 287) | static void
function ngx_rtmp_exec_eval_ctx_str (line 305) | static void
function ngx_rtmp_exec_eval_pctx_str (line 322) | static void
function ngx_int_t (line 499) | static ngx_int_t
function ngx_int_t (line 562) | static ngx_int_t
function ngx_rtmp_exec_respawn (line 611) | static void
function ngx_rtmp_exec_child_dead (line 618) | static void
function ngx_int_t (line 653) | static ngx_int_t
function ngx_int_t (line 693) | static ngx_int_t
function ngx_int_t (line 893) | static ngx_int_t
function ngx_int_t (line 966) | static ngx_int_t
function ngx_int_t (line 1080) | static ngx_int_t
function ngx_rtmp_exec_unmanaged (line 1107) | static void
function ngx_rtmp_exec_managed (line 1139) | static void
function ngx_int_t (line 1161) | static ngx_int_t
function ngx_int_t (line 1194) | static ngx_int_t
function ngx_int_t (line 1231) | static ngx_int_t
function ngx_int_t (line 1305) | static ngx_int_t
function ngx_int_t (line 1584) | static ngx_int_t
FILE: ngx_rtmp_flv_module.c
type ngx_rtmp_flv_index_t (line 28) | typedef struct {
type ngx_rtmp_flv_ctx_t (line 34) | typedef struct {
function ngx_int_t (line 89) | static ngx_int_t
function ngx_int_t (line 114) | static ngx_int_t
function ngx_rtmp_flv_index_value (line 201) | static double
function ngx_int_t (line 212) | static ngx_int_t
function ngx_rtmp_flv_read_meta (line 311) | static void
function ngx_int_t (line 397) | static ngx_int_t
function ngx_int_t (line 555) | static ngx_int_t
function ngx_int_t (line 579) | static ngx_int_t
function ngx_int_t (line 600) | static ngx_int_t
function ngx_int_t (line 623) | static ngx_int_t
function ngx_int_t (line 641) | static ngx_int_t
FILE: ngx_rtmp_handler.c
function ngx_rtmp_cycle (line 83) | void
function ngx_chain_t (line 101) | static ngx_chain_t *
function ngx_rtmp_reset_ping (line 128) | void
function ngx_rtmp_ping (line 147) | static void
function ngx_rtmp_recv (line 192) | static void
function ngx_rtmp_send (line 491) | static void
function ngx_rtmp_prepare_message (line 567) | void
function ngx_int_t (line 718) | ngx_int_t
function ngx_int_t (line 761) | ngx_int_t
function ngx_int_t (line 822) | ngx_int_t
function ngx_int_t (line 899) | static ngx_int_t
FILE: ngx_rtmp_handshake.c
function ngx_int_t (line 103) | static ngx_int_t
function ngx_int_t (line 143) | static ngx_int_t
function ngx_int_t (line 169) | static ngx_int_t
function ngx_rtmp_fill_random_buffer (line 191) | static void
function ngx_buf_t (line 200) | static ngx_buf_t *
function ngx_rtmp_free_handshake_buffers (line 237) | void
function ngx_int_t (line 258) | static ngx_int_t
function ngx_int_t (line 279) | static ngx_int_t
function ngx_int_t (line 333) | static ngx_int_t
function ngx_rtmp_handshake_done (line 356) | static void
function ngx_rtmp_handshake_recv (line 375) | static void
function ngx_rtmp_handshake_send (line 490) | static void
function ngx_rtmp_handshake (line 579) | void
function ngx_rtmp_client_handshake (line 598) | void
FILE: ngx_rtmp_init.c
function ngx_rtmp_init_connection (line 17) | void
function ngx_rtmp_session_t (line 145) | ngx_rtmp_session_t *
function u_char (line 221) | static u_char *
function ngx_rtmp_close_connection (line 254) | static void
function ngx_rtmp_close_session_handler (line 282) | static void
function ngx_rtmp_finalize_session (line 321) | void
FILE: ngx_rtmp_limit_module.c
type ngx_rtmp_limit_main_conf_t (line 12) | typedef struct {
function ngx_int_t (line 82) | static ngx_int_t
function ngx_int_t (line 120) | static ngx_int_t
function ngx_int_t (line 150) | static ngx_int_t
function ngx_int_t (line 176) | static ngx_int_t
FILE: ngx_rtmp_live_module.c
function ngx_rtmp_live_stream_t (line 221) | static ngx_rtmp_live_stream_t **
function ngx_rtmp_live_idle (line 264) | static void
function ngx_rtmp_live_set_status (line 280) | static void
function ngx_rtmp_live_start (line 363) | static void
function ngx_rtmp_live_stop (line 404) | static void
function ngx_int_t (line 444) | static ngx_int_t
function ngx_int_t (line 465) | static ngx_int_t
function ngx_rtmp_live_join (line 486) | static void
function ngx_int_t (line 566) | static ngx_int_t
function ngx_int_t (line 651) | static ngx_int_t
function ngx_int_t (line 695) | static ngx_int_t
function ngx_int_t (line 1040) | static ngx_int_t
function ngx_int_t (line 1077) | static ngx_int_t
function ngx_int_t (line 1116) | static ngx_int_t
FILE: ngx_rtmp_live_module.h
type ngx_rtmp_live_ctx_t (line 19) | typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t;
type ngx_rtmp_live_stream_t (line 20) | typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t;
type ngx_rtmp_live_chunk_stream_t (line 23) | typedef struct {
type ngx_rtmp_live_ctx_s (line 31) | struct ngx_rtmp_live_ctx_s {
type ngx_rtmp_live_stream_s (line 46) | struct ngx_rtmp_live_stream_s {
type ngx_rtmp_live_app_conf_t (line 60) | typedef struct {
FILE: ngx_rtmp_log_module.c
type ngx_rtmp_log_op_t (line 29) | typedef struct ngx_rtmp_log_op_s ngx_rtmp_log_op_t;
type u_char (line 34) | typedef u_char * (*ngx_rtmp_log_op_getdata_pt)(ngx_rtmp_session_t *s,
type ngx_rtmp_log_op_s (line 38) | struct ngx_rtmp_log_op_s {
type ngx_rtmp_log_var_t (line 46) | typedef struct {
type ngx_rtmp_log_fmt_t (line 54) | typedef struct {
type ngx_rtmp_log_t (line 60) | typedef struct {
type ngx_rtmp_log_app_conf_t (line 68) | typedef struct {
type ngx_rtmp_log_main_conf_t (line 74) | typedef struct {
type ngx_rtmp_log_ctx_t (line 80) | typedef struct {
function ngx_rtmp_log_var_default_getlen (line 146) | static size_t
function u_char (line 153) | static u_char *
function ngx_rtmp_log_var_connection_getlen (line 161) | static size_t
function u_char (line 167) | static u_char *
function ngx_rtmp_log_var_remote_addr_getlen (line 175) | static size_t
function u_char (line 183) | static u_char *
function ngx_rtmp_log_var_msec_getlen (line 192) | static size_t
function u_char (line 200) | static u_char *
function ngx_rtmp_log_var_session_string_getlen (line 212) | static size_t
function u_char (line 220) | static u_char *
function ngx_rtmp_log_var_command_getlen (line 232) | static size_t
function u_char (line 240) | static u_char *
function ngx_rtmp_log_var_context_cstring_getlen (line 265) | static size_t
function u_char (line 273) | static u_char *
function ngx_rtmp_log_var_session_uint32_getlen (line 294) | static size_t
function u_char (line 302) | static u_char *
function ngx_rtmp_log_var_time_local_getlen (line 314) | static size_t
function u_char (line 322) | static u_char *
function ngx_rtmp_log_var_session_time_getlen (line 331) | static size_t
function u_char (line 339) | static u_char *
function ngx_rtmp_log_var_session_readable_time_getlen (line 348) | static size_t
function u_char (line 356) | static u_char *
function ngx_rtmp_log_ctx_t (line 810) | static ngx_rtmp_log_ctx_t *
function ngx_int_t (line 832) | static ngx_int_t
function ngx_int_t (line 853) | static ngx_int_t
function ngx_rtmp_log_write (line 874) | static void
function ngx_int_t (line 916) | static ngx_int_t
function ngx_int_t (line 973) | static ngx_int_t
FILE: ngx_rtmp_mp4_module.c
type ngx_rtmp_mp4_chunk_entry_t (line 41) | typedef struct {
type ngx_rtmp_mp4_chunks_t (line 48) | typedef struct {
type ngx_rtmp_mp4_time_entry_t (line 55) | typedef struct {
type ngx_rtmp_mp4_times_t (line 61) | typedef struct {
type ngx_rtmp_mp4_delay_entry_t (line 68) | typedef struct {
type ngx_rtmp_mp4_delays_t (line 74) | typedef struct {
type ngx_rtmp_mp4_keys_t (line 81) | typedef struct {
type ngx_rtmp_mp4_sizes_t (line 88) | typedef struct {
type ngx_rtmp_mp4_sizes2_t (line 96) | typedef struct {
type ngx_rtmp_mp4_offsets_t (line 104) | typedef struct {
type ngx_rtmp_mp4_offsets64_t (line 111) | typedef struct {
type ngx_rtmp_mp4_cursor_t (line 126) | typedef struct {
type ngx_rtmp_mp4_track_t (line 155) | typedef struct {
type ngx_rtmp_mp4_ctx_t (line 181) | typedef struct {
function ngx_inline (line 209) | static ngx_inline uint32_t
function ngx_inline (line 216) | static ngx_inline uint32_t
function ngx_int_t (line 261) | static ngx_int_t
function ngx_int_t (line 294) | static ngx_int_t
type ngx_int_t (line 347) | typedef ngx_int_t (*ngx_rtmp_mp4_box_pt)(ngx_rtmp_session_t *s, u_char *...
type ngx_rtmp_mp4_box_t (line 350) | typedef struct {
type ngx_int_t (line 394) | typedef ngx_int_t (*ngx_rtmp_mp4_descriptor_pt)(ngx_rtmp_session_t *s,
type ngx_rtmp_mp4_descriptor_t (line 397) | typedef struct {
function ngx_int_t (line 438) | static ngx_int_t
function ngx_int_t (line 503) | static ngx_int_t
function ngx_int_t (line 559) | static ngx_int_t
function ngx_int_t (line 599) | static ngx_int_t
function ngx_int_t (line 641) | static ngx_int_t
function ngx_int_t (line 737) | static ngx_int_t
function ngx_int_t (line 744) | static ngx_int_t
function ngx_int_t (line 751) | static ngx_int_t
function ngx_int_t (line 777) | static ngx_int_t
function ngx_int_t (line 784) | static ngx_int_t
function ngx_int_t (line 808) | static ngx_int_t
function ngx_int_t (line 855) | static ngx_int_t
function ngx_int_t (line 897) | static ngx_int_t
function ngx_int_t (line 956) | static ngx_int_t
function ngx_int_t (line 969) | static ngx_int_t
function ngx_int_t (line 976) | static ngx_int_t
function ngx_int_t (line 983) | static ngx_int_t
function ngx_int_t (line 990) | static ngx_int_t
function ngx_int_t (line 1005) | static ngx_int_t
function ngx_int_t (line 1036) | static ngx_int_t
function ngx_int_t (line 1067) | static ngx_int_t
function ngx_int_t (line 1098) | static ngx_int_t
function ngx_int_t (line 1129) | static ngx_int_t
function ngx_int_t (line 1168) | static ngx_int_t
function ngx_int_t (line 1200) | static ngx_int_t
function ngx_int_t (line 1231) | static ngx_int_t
function ngx_int_t (line 1262) | static ngx_int_t
function ngx_int_t (line 1309) | static ngx_int_t
function ngx_int_t (line 1357) | static ngx_int_t
function ngx_int_t (line 1418) | static ngx_int_t
function ngx_int_t (line 1483) | static ngx_int_t
function ngx_int_t (line 1544) | static ngx_int_t
function ngx_int_t (line 1600) | static ngx_int_t
function ngx_int_t (line 1661) | static ngx_int_t
function ngx_int_t (line 1731) | static ngx_int_t
function ngx_int_t (line 1772) | static ngx_int_t
function ngx_int_t (line 1826) | static ngx_int_t
function ngx_int_t (line 1878) | static ngx_int_t
function ngx_int_t (line 1928) | static ngx_int_t
function ngx_int_t (line 1946) | static ngx_int_t
function ngx_int_t (line 2065) | static ngx_int_t
function ngx_int_t (line 2089) | static ngx_int_t
function ngx_int_t (line 2310) | static ngx_int_t
function ngx_int_t (line 2413) | static ngx_int_t
function ngx_int_t (line 2439) | static ngx_int_t
function ngx_int_t (line 2492) | static ngx_int_t
function ngx_int_t (line 2512) | static ngx_int_t
function ngx_int_t (line 2536) | static ngx_int_t
function ngx_int_t (line 2556) | static ngx_int_t
FILE: ngx_rtmp_netcall_module.c
type ngx_rtmp_netcall_srv_conf_t (line 24) | typedef struct {
type ngx_rtmp_netcall_session_t (line 31) | typedef struct ngx_rtmp_netcall_session_s {
type ngx_rtmp_netcall_ctx_t (line 49) | typedef struct {
function ngx_int_t (line 134) | static ngx_int_t
function ngx_int_t (line 153) | static ngx_int_t
function ngx_rtmp_netcall_free_peer (line 166) | static void
function ngx_int_t (line 173) | ngx_int_t
function ngx_rtmp_netcall_close (line 289) | static void
function ngx_rtmp_netcall_detach (line 336) | static void
function ngx_rtmp_netcall_recv (line 346) | static void
function ngx_rtmp_netcall_send (line 442) | static void
function ngx_chain_t (line 494) | ngx_chain_t *
function ngx_chain_t (line 568) | ngx_chain_t *
function ngx_chain_t (line 635) | ngx_chain_t *
function ngx_chain_t (line 682) | ngx_chain_t *
function ngx_int_t (line 712) | static ngx_int_t
FILE: ngx_rtmp_netcall_module.h
type ngx_chain_t (line 16) | typedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 18) | typedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in);
type ngx_int_t (line 19) | typedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s,
type ngx_int_t (line 21) | typedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s,
type ngx_rtmp_netcall_init_t (line 37) | typedef struct {
FILE: ngx_rtmp_notify_module.c
type ngx_rtmp_notify_app_conf_t (line 69) | typedef struct {
type ngx_rtmp_notify_srv_conf_t (line 79) | typedef struct {
type ngx_rtmp_notify_ctx_t (line 85) | typedef struct {
type ngx_rtmp_notify_done_t (line 94) | typedef struct {
function ngx_chain_t (line 318) | static ngx_chain_t *
function ngx_chain_t (line 351) | static ngx_chain_t *
function ngx_chain_t (line 458) | static ngx_chain_t *
function ngx_chain_t (line 519) | static ngx_chain_t *
function ngx_chain_t (line 570) | static ngx_chain_t *
function ngx_chain_t (line 621) | static ngx_chain_t *
function ngx_chain_t (line 672) | static ngx_chain_t *
function ngx_chain_t (line 740) | static ngx_chain_t *
function ngx_int_t (line 800) | static ngx_int_t
function ngx_int_t (line 849) | static ngx_int_t
function ngx_rtmp_notify_clear_flag (line 946) | static void
function ngx_int_t (line 957) | static ngx_int_t
function ngx_rtmp_notify_set_name (line 989) | static void
function ngx_int_t (line 1005) | static ngx_int_t
function ngx_int_t (line 1084) | static ngx_int_t
function ngx_int_t (line 1163) | static ngx_int_t
function ngx_rtmp_notify_update (line 1196) | static void
function ngx_rtmp_notify_init (line 1231) | static void
function ngx_int_t (line 1287) | static ngx_int_t
function ngx_int_t (line 1323) | static ngx_int_t
function ngx_int_t (line 1356) | static ngx_int_t
function ngx_int_t (line 1398) | static ngx_int_t
function ngx_int_t (line 1440) | static ngx_int_t
function ngx_int_t (line 1486) | static ngx_int_t
function ngx_int_t (line 1519) | static ngx_int_t
function ngx_url_t (line 1550) | static ngx_url_t *
function ngx_int_t (line 1708) | static ngx_int_t
FILE: ngx_rtmp_play_module.c
function ngx_int_t (line 194) | static ngx_int_t
function ngx_int_t (line 229) | static ngx_int_t
function ngx_rtmp_play_send (line 264) | static void
function ngx_int_t (line 318) | static ngx_int_t
function ngx_int_t (line 339) | static ngx_int_t
function ngx_int_t (line 360) | static ngx_int_t
function ngx_int_t (line 388) | static ngx_int_t
function ngx_int_t (line 416) | static ngx_int_t
function u_char (line 457) | static u_char *
function ngx_rtmp_play_copy_local_file (line 477) | static void
function ngx_rtmp_play_cleanup_local_file (line 517) | static void
function ngx_int_t (line 539) | static ngx_int_t
function ngx_int_t (line 577) | static ngx_int_t
function ngx_int_t (line 615) | static ngx_int_t
function ngx_int_t (line 662) | static ngx_int_t
function ngx_int_t (line 689) | static ngx_int_t
function ngx_int_t (line 810) | static ngx_int_t
function ngx_int_t (line 889) | static ngx_int_t
function ngx_chain_t (line 949) | static ngx_chain_t *
function ngx_int_t (line 1007) | static ngx_int_t
function ngx_int_t (line 1035) | static ngx_int_t
function ngx_rtmp_play_entry_t (line 1094) | static ngx_rtmp_play_entry_t *
function ngx_int_t (line 1111) | static ngx_int_t
function ngx_int_t (line 1262) | static ngx_int_t
FILE: ngx_rtmp_play_module.h
type ngx_int_t (line 17) | typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s,
type ngx_int_t (line 19) | typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s,
type ngx_int_t (line 21) | typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s,
type ngx_int_t (line 23) | typedef ngx_int_t (*ngx_rtmp_play_seek_pt) (ngx_rtmp_session_t *s,
type ngx_int_t (line 25) | typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s,
type ngx_int_t (line 27) | typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s,
type ngx_rtmp_play_fmt_t (line 31) | typedef struct {
type ngx_rtmp_play_ctx_t (line 45) | typedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t;
type ngx_rtmp_play_ctx_s (line 48) | struct ngx_rtmp_play_ctx_s {
type ngx_rtmp_play_entry_t (line 70) | typedef struct {
type ngx_rtmp_play_app_conf_t (line 76) | typedef struct {
type ngx_rtmp_play_main_conf_t (line 85) | typedef struct {
FILE: ngx_rtmp_proxy_protocol.c
function ngx_rtmp_proxy_protocol (line 16) | void
function ngx_rtmp_proxy_protocol_recv (line 50) | static void
FILE: ngx_rtmp_receive.c
function ngx_int_t (line 15) | ngx_int_t
function ngx_int_t (line 86) | ngx_int_t
function ngx_int_t (line 216) | static ngx_int_t
function ngx_int_t (line 233) | static ngx_int_t
function ngx_int_t (line 240) | static ngx_int_t
function ngx_int_t (line 259) | ngx_int_t
function ngx_int_t (line 367) | ngx_int_t
function ngx_int_t (line 453) | ngx_int_t
FILE: ngx_rtmp_record_module.c
function ngx_int_t (line 242) | static ngx_int_t
function ngx_rtmp_record_rec_ctx_t (line 267) | static ngx_rtmp_record_rec_ctx_t *
function ngx_int_t (line 289) | ngx_int_t
function ngx_int_t (line 317) | ngx_int_t
function ngx_uint_t (line 345) | ngx_uint_t
function ngx_rtmp_record_make_path (line 368) | static void
function ngx_rtmp_record_notify_error (line 417) | static void
function ngx_int_t (line 434) | static ngx_int_t
function ngx_int_t (line 589) | static ngx_int_t
function ngx_rtmp_record_start (line 643) | static void
function ngx_rtmp_record_stop (line 674) | static void
function ngx_int_t (line 702) | static ngx_int_t
function ngx_int_t (line 750) | static ngx_int_t
function ngx_int_t (line 767) | static ngx_int_t
function ngx_int_t (line 784) | static ngx_int_t
function ngx_int_t (line 853) | static ngx_int_t
function ngx_int_t (line 871) | static ngx_int_t
function ngx_rtmp_record_get_chain_mlen (line 982) | static size_t
function ngx_int_t (line 995) | static ngx_int_t
function ngx_int_t (line 1019) | static ngx_int_t
function ngx_int_t (line 1184) | static ngx_int_t
function ngx_int_t (line 1278) | static ngx_int_t
FILE: ngx_rtmp_record_module.h
type ngx_rtmp_record_app_conf_t (line 23) | typedef struct {
type ngx_rtmp_record_rec_ctx_t (line 42) | typedef struct {
type ngx_rtmp_record_ctx_t (line 59) | typedef struct {
type ngx_rtmp_record_done_t (line 80) | typedef struct {
type ngx_int_t (line 86) | typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s,
FILE: ngx_rtmp_relay_module.c
type ngx_rtmp_relay_app_conf_t (line 47) | typedef struct {
type ngx_rtmp_relay_static_t (line 62) | typedef struct {
function ngx_rtmp_relay_static_pull_reconnect (line 219) | static void
function ngx_rtmp_relay_push_reconnect (line 244) | static void
function ngx_int_t (line 303) | static ngx_int_t
function ngx_rtmp_relay_free_peer (line 310) | static void
type ngx_rtmp_relay_ctx_t (line 317) | typedef ngx_rtmp_relay_ctx_t * (* ngx_rtmp_relay_create_ctx_pt)
function ngx_int_t (line 321) | static ngx_int_t
function ngx_rtmp_relay_ctx_t (line 337) | static ngx_rtmp_relay_ctx_t *
function ngx_rtmp_relay_ctx_t (line 518) | static ngx_rtmp_relay_ctx_t *
function ngx_rtmp_relay_ctx_t (line 532) | static ngx_rtmp_relay_ctx_t *
function ngx_int_t (line 569) | static ngx_int_t
function ngx_int_t (line 623) | ngx_int_t
function ngx_int_t (line 637) | ngx_int_t
function ngx_int_t (line 651) | static ngx_int_t
function ngx_int_t (line 707) | static ngx_int_t
function ngx_int_t (line 755) | static ngx_int_t
function ngx_int_t (line 775) | static ngx_int_t
function ngx_int_t (line 795) | static ngx_int_t
function ngx_int_t (line 928) | static ngx_int_t
function ngx_int_t (line 960) | static ngx_int_t
function ngx_int_t (line 1015) | static ngx_int_t
function ngx_int_t (line 1089) | static ngx_int_t
function ngx_int_t (line 1172) | static ngx_int_t
function ngx_int_t (line 1235) | static ngx_int_t
function ngx_int_t (line 1307) | static ngx_int_t
function ngx_rtmp_relay_close (line 1322) | static void
function ngx_int_t (line 1414) | static ngx_int_t
function ngx_int_t (line 1428) | static ngx_int_t
function ngx_int_t (line 1597) | static ngx_int_t
function ngx_int_t (line 1650) | static ngx_int_t
FILE: ngx_rtmp_relay_module.h
type ngx_rtmp_relay_target_t (line 16) | typedef struct {
type ngx_rtmp_relay_ctx_t (line 35) | typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;
type ngx_rtmp_relay_ctx_s (line 37) | struct ngx_rtmp_relay_ctx_s {
FILE: ngx_rtmp_send.c
function ngx_int_t (line 50) | static ngx_int_t
function ngx_chain_t (line 72) | ngx_chain_t *
function ngx_int_t (line 88) | ngx_int_t
function ngx_chain_t (line 96) | ngx_chain_t *
function ngx_int_t (line 112) | ngx_int_t
function ngx_chain_t (line 120) | ngx_chain_t *
function ngx_int_t (line 136) | ngx_int_t
function ngx_chain_t (line 144) | ngx_chain_t *
function ngx_int_t (line 160) | ngx_int_t
function ngx_chain_t (line 168) | ngx_chain_t *
function ngx_int_t (line 187) | ngx_int_t
function ngx_chain_t (line 198) | ngx_chain_t *
function ngx_int_t (line 214) | ngx_int_t
function ngx_chain_t (line 222) | ngx_chain_t *
function ngx_int_t (line 238) | ngx_int_t
function ngx_chain_t (line 246) | ngx_chain_t *
function ngx_int_t (line 262) | ngx_int_t
function ngx_chain_t (line 270) | ngx_chain_t *
function ngx_int_t (line 289) | ngx_int_t
function ngx_chain_t (line 298) | ngx_chain_t *
function ngx_int_t (line 314) | ngx_int_t
function ngx_chain_t (line 322) | ngx_chain_t *
function ngx_int_t (line 338) | ngx_int_t
function ngx_chain_t (line 346) | ngx_chain_t *
function ngx_int_t (line 362) | ngx_int_t
function ngx_chain_t (line 370) | static ngx_chain_t *
function ngx_int_t (line 380) | ngx_int_t
function ngx_chain_t (line 418) | ngx_chain_t *
function ngx_int_t (line 448) | ngx_int_t
function ngx_chain_t (line 457) | ngx_chain_t *
function ngx_int_t (line 518) | ngx_int_t
function ngx_chain_t (line 526) | ngx_chain_t *
function ngx_int_t (line 588) | ngx_int_t
function ngx_chain_t (line 597) | ngx_chain_t *
function ngx_int_t (line 630) | ngx_int_t
FILE: ngx_rtmp_shared.c
function ngx_chain_t (line 12) | ngx_chain_t *
function ngx_rtmp_free_shared_chain (line 59) | void
function ngx_chain_t (line 78) | ngx_chain_t *
FILE: ngx_rtmp_stat_module.c
type ngx_rtmp_stat_loc_conf_t (line 40) | typedef struct {
function ngx_int_t (line 109) | static ngx_int_t
function ngx_rtmp_stat_output (line 166) | __declspec(noinline)
function ngx_rtmp_stat_bw (line 253) | static void
function ngx_rtmp_stat_get_pool_size (line 285) | static void
function ngx_rtmp_stat_dump_pool (line 307) | static void
function ngx_rtmp_stat_client (line 327) | static void
function ngx_rtmp_stat_live (line 410) | static void
function ngx_rtmp_stat_play (line 604) | static void
function ngx_rtmp_stat_application (line 676) | static void
function ngx_rtmp_stat_server (line 703) | static void
function ngx_int_t (line 725) | static ngx_int_t
function ngx_int_t (line 857) | static ngx_int_t
FILE: test/rtmp-publisher/swfobject.js
function f (line 4) | function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].ap...
function K (line 4) | function K(X){if(J){X()}else{U[U.length]=X}}
function s (line 4) | function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load"...
function h (line 4) | function h(){if(T){V()}else{H()}}
function V (line 4) | function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setA...
function H (line 4) | function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[a...
function z (line 4) | function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typ...
function A (line 4) | function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}
function P (line 4) | function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X...
function p (line 4) | function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNo...
function g (line 4) | function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML...
function u (line 4) | function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(...
function e (line 4) | function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttr...
function y (line 4) | function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.s...
function b (line 4) | function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function...
function c (line 4) | function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}
function C (line 4) | function C(X){return j.createElement(X)}
function i (line 4) | function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}
function F (line 4) | function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=pars...
function v (line 4) | function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagN...
function w (line 4) | function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z...
function L (line 4) | function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof...
FILE: test/www/jwplayer/jwplayer.js
function a (line 1) | function a(b){return function(){return e(b)}}
function c (line 11) | function c(){e=a.ERROR;l.sendEvent(k.ERROR)}
function b (line 11) | function b(){e=a.COMPLETE;l.sendEvent(k.COMPLETE)}
function c (line 27) | function c(){switch(a.getPluginPathType(g)){case a.pluginPathType.ABSOLU...
function b (line 27) | function b(){q=setTimeout(function(){k=a.loaderstatus.COMPLETE;j.sendEve...
function e (line 27) | function e(){k=a.loaderstatus.ERROR;j.sendEvent(h.ERROR)}
function b (line 30) | function b(){m?j.sendEvent(h.ERROR,{message:n}):l||(l=!0,k=a.loaderstatu...
function e (line 30) | function e(){q||b();if(!l&&!m){var e=0,
function e (line 36) | function e(a){l(n,p+a.message)}
function k (line 36) | function k(){l(n,p+"No playable sources found")}
function l (line 36) | function l(b,e){if(m.fallback){var c=b.style;c.backgroundColor="#000";c....
function a (line 39) | function a(a){if(a.playlist)for(var e=0;e<a.playlist.length;e++)a.playli...
function h (line 40) | function h(a,e,c){g.exists(a[c])&&(e[c]=a[c],delete a[c])}
function e (line 41) | function e(a,b){for(var c=h.querySelectorAll(a),e=0;e<c.length;e++)for(v...
function k (line 41) | function k(a,b,c){a=h.createElement(a);
function m (line 45) | function m(a,b,c){var e=document.createElement("param");e.setAttribute("...
function n (line 45) | function n(a,b,c){return function(){try{c&&document.getElementById(l.id+...
function q (line 46) | function q(b){if(!b)return{};var c={},e=[],d;for(d in b){var f=a.getPlug...
function m (line 51) | function m(a,b,d){return function(){try{var e=document.querySelector("#"+
function n (line 52) | function n(a){q.sendEvent(a.type,{message:"HTML5 player not found"})}
function l (line 56) | function l(a,b){return function(c){return b(a,c)}}
function m (line 56) | function m(a,b){p[a]||(p[a]=[],q(g.JWPLAYER_PLAYER_STATE,function(b){var...
function n (line 57) | function n(a,b){try{a.jwAddEventListener(b,'function(dat) { jwplayer("'+...
function q (line 57) | function q(a,b){r[a]||(r[a]=[],s&&u&&n(s,a));r[a].push(b);return f}
function j (line 57) | function j(){if(u){for(var a=arguments[0],b=[],c=1;c<arguments.length;c+...
FILE: test/www/jwplayer_old/swfobject.js
function S (line 5) | function S(){if(J.readyState=="complete"){J.parentNode.removeChild(J);E()}}
function E (line 5) | function E(){if(e){return }if(h.ie&&h.win){var v=a("span");try{var u=K.g...
function f (line 5) | function f(q){if(e){q()}else{o[o.length]=q}}
function R (line 5) | function R(r){if(typeof j.addEventListener!=b){j.addEventListener("load"...
function H (line 5) | function H(){var t=N.length;for(var q=0;q<t;q++){var u=N[q].id;if(h.pv[0...
function Y (line 5) | function Y(t){var q=t.getElementsByTagName(Q)[0];if(q){var w=a("embed"),...
function k (line 5) | function k(w){A=true;var u=C(w.id);if(u){if(w.altContentId){var y=C(w.al...
function O (line 5) | function O(t){if(h.ie&&h.win&&t.readyState!=4){var r=a("div");t.parentNo...
function G (line 5) | function G(v){var u=a("div");if(h.win&&h.ie){u.innerHTML=v.innerHTML}els...
function U (line 5) | function U(AG,AE,t){var q,v=C(t);if(v){if(typeof AG.id==b){AG.id=t}if(h....
function F (line 5) | function F(t,q,r){var u=a("param");u.setAttribute("name",q);u.setAttribu...
function X (line 5) | function X(r){var q=C(r);if(q&&(q.nodeName=="OBJECT"||q.nodeName=="EMBED...
function B (line 5) | function B(t){var r=C(t);if(r){for(var q in r){if(typeof r[q]=="function...
function C (line 5) | function C(t){var q=null;try{q=K.getElementById(t)}catch(r){}return q}
function a (line 5) | function a(q){return K.createElement(q)}
function I (line 5) | function I(t,q,r){t.attachEvent(q,r);d[d.length]=[t,q,r]}
function c (line 5) | function c(t){var r=h.pv,q=t.split(".");q[0]=parseInt(q[0],10);q[1]=pars...
function V (line 5) | function V(v,r){if(h.ie&&h.mac){return }var u=K.getElementsByTagName("he...
function W (line 5) | function W(t,q){var r=q?"visible":"hidden";if(e&&C(t)){C(t).style.visibi...
function g (line 5) | function g(s){var r=/[\\\"<>\.;]/;var q=r.exec(s)!=null;return q?encodeU...
Condensed preview — 78 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (994K chars).
[
{
"path": "AUTHORS",
"chars": 115,
"preview": "Project author:\n\n Roman Arutyunyan\n Moscow, Russia\n\n Contacts:\n arut@qip.ru\n arutyunyan.roman@gmail.com\n"
},
{
"path": "LICENSE",
"chars": 1307,
"preview": "Copyright (c) 2012-2014, Roman Arutyunyan\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with "
},
{
"path": "README.md",
"chars": 9087,
"preview": "# NGINX-based Media Streaming Server\n## nginx-rtmp-module\n\n\n### Project blog\n\n http://nginx-rtmp.blogspot.com\n\n### Wiki"
},
{
"path": "config",
"chars": 5882,
"preview": "ngx_addon_name=\"ngx_rtmp_module\"\n\nRTMP_CORE_MODULES=\" \\\n ngx_rtmp"
},
{
"path": "dash/ngx_rtmp_dash_module.c",
"chars": 47354,
"preview": "\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_rtmp.h>\n#include <ngx_rtmp_codec_module.h>\n#include \"ngx_r"
},
{
"path": "dash/ngx_rtmp_mp4.c",
"chars": 23896,
"preview": "\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_mp4.h\"\n#include <ngx_rtmp_codec_module.h>\n\n\nstatic ng"
},
{
"path": "dash/ngx_rtmp_mp4.h",
"chars": 1357,
"preview": "\n\n#ifndef _NGX_RTMP_MP4_H_INCLUDED_\n#define _NGX_RTMP_MP4_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#i"
},
{
"path": "doc/README.md",
"chars": 80,
"preview": "Documentation is available here:\nhttps://github.com/arut/nginx-rtmp-module/wiki\n"
},
{
"path": "hls/ngx_rtmp_hls_module.c",
"chars": 71543,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_rtmp.h>\n#include"
},
{
"path": "hls/ngx_rtmp_mpegts.c",
"chars": 10750,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_mpegts.h\"\n\n"
},
{
"path": "hls/ngx_rtmp_mpegts.h",
"chars": 1020,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_\n#define _NGX_RTMP_MPEGTS_H_INCLUDED_\n\n\n"
},
{
"path": "ngx_rtmp.c",
"chars": 22297,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#includ"
},
{
"path": "ngx_rtmp.h",
"chars": 19517,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_H_INCLUDED_\n#define _NGX_RTMP_H_INCLUDED_\n\n\n#include <ngx_"
},
{
"path": "ngx_rtmp_access_module.c",
"chars": 11729,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_amf.c",
"chars": 16504,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_amf.h\"\n#inc"
},
{
"path": "ngx_rtmp_amf.h",
"chars": 1933,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_AMF_H_INCLUDED_\n#define _NGX_RTMP_AMF_H_INCLUDED_\n\n\n#inclu"
},
{
"path": "ngx_rtmp_auto_push_module.c",
"chars": 17802,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module."
},
{
"path": "ngx_rtmp_bandwidth.c",
"chars": 589,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_bandwidth.h"
},
{
"path": "ngx_rtmp_bandwidth.h",
"chars": 584,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_\n#define _NGX_RTMP_BANDWIDTH_H_INCLUD"
},
{
"path": "ngx_rtmp_bitop.c",
"chars": 1062,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_bitop.h\"\n\n\n"
},
{
"path": "ngx_rtmp_bitop.h",
"chars": 1162,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_BITOP_H_INCLUDED_\n#define _NGX_RTMP_BITOP_H_INCLUDED_\n\n\n#i"
},
{
"path": "ngx_rtmp_cmd_module.c",
"chars": 22685,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module."
},
{
"path": "ngx_rtmp_cmd_module.h",
"chars": 4817,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_CMD_H_INCLUDED_\n#define _NGX_RTMP_CMD_H_INCLUDED_\n\n\n#inclu"
},
{
"path": "ngx_rtmp_codec_module.c",
"chars": 26153,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_codec_modul"
},
{
"path": "ngx_rtmp_codec_module.h",
"chars": 2640,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_CODEC_H_INCLUDED_\n#define _NGX_RTMP_CODEC_H_INCLUDED_\n\n\n#i"
},
{
"path": "ngx_rtmp_control_module.c",
"chars": 18855,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_http.h>\n#include"
},
{
"path": "ngx_rtmp_core_module.c",
"chars": 21001,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#includ"
},
{
"path": "ngx_rtmp_eval.c",
"chars": 6093,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_eval.h\"\n\n\n#"
},
{
"path": "ngx_rtmp_eval.h",
"chars": 843,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_EVAL_H_INCLUDED_\n#define _NGX_RTMP_EVAL_H_INCLUDED_\n\n\n#inc"
},
{
"path": "ngx_rtmp_exec_module.c",
"chars": 41739,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module."
},
{
"path": "ngx_rtmp_flv_module.c",
"chars": 19445,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_play_module"
},
{
"path": "ngx_rtmp_handler.c",
"chars": 24156,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_handshake.c",
"chars": 16976,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n#includ"
},
{
"path": "ngx_rtmp_init.c",
"chars": 7595,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_limit_module.c",
"chars": 5553,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\ntypede"
},
{
"path": "ngx_rtmp_live_module.c",
"chars": 32748,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_live_module"
},
{
"path": "ngx_rtmp_live_module.h",
"chars": 2709,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_LIVE_H_INCLUDED_\n#define _NGX_RTMP_LIVE_H_INCLUDED_\n\n\n#inc"
},
{
"path": "ngx_rtmp_log_module.c",
"chars": 25282,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module."
},
{
"path": "ngx_rtmp_mp4_module.c",
"chars": 71083,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_play_module"
},
{
"path": "ngx_rtmp_netcall_module.c",
"chars": 19230,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_netcall_mod"
},
{
"path": "ngx_rtmp_netcall_module.h",
"chars": 2138,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_NETCALL_H_INCLUDED_\n#define _NGX_RTMP_NETCALL_H_INCLUDED_\n"
},
{
"path": "ngx_rtmp_notify_module.c",
"chars": 47966,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_md5.h>\n#include "
},
{
"path": "ngx_rtmp_play_module.c",
"chars": 32906,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <nginx.h>\n#include \"n"
},
{
"path": "ngx_rtmp_play_module.h",
"chars": 2536,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_PLAY_H_INCLUDED_\n#define _NGX_RTMP_PLAY_H_INCLUDED_\n\n\n#inc"
},
{
"path": "ngx_rtmp_proxy_protocol.c",
"chars": 3872,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <nginx.h>\n#include \"n"
},
{
"path": "ngx_rtmp_proxy_protocol.h",
"chars": 311,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_\n#define _NGX_RTMP_PROXY_PROTOCO"
},
{
"path": "ngx_rtmp_receive.c",
"chars": 11691,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_record_module.c",
"chars": 36046,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_record_module.h",
"chars": 2992,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_RECORD_H_INCLUDED_\n#define _NGX_RTMP_RECORD_H_INCLUDED_\n\n\n"
},
{
"path": "ngx_rtmp_relay_module.c",
"chars": 46437,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_relay_modul"
},
{
"path": "ngx_rtmp_relay_module.h",
"chars": 2281,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_RELAY_H_INCLUDED_\n#define _NGX_RTMP_RELAY_H_INCLUDED_\n\n\n#i"
},
{
"path": "ngx_rtmp_send.c",
"chars": 15709,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include"
},
{
"path": "ngx_rtmp_shared.c",
"chars": 2629,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\nngx_ch"
},
{
"path": "ngx_rtmp_stat_module.c",
"chars": 25542,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_http.h>\n#include"
},
{
"path": "ngx_rtmp_streams.h",
"chars": 378,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_\n#define _NGX_RTMP_STREAMS_H_INCLUDED_\n"
},
{
"path": "ngx_rtmp_version.h",
"chars": 239,
"preview": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_VERSION_H_INCLUDED_\n#define _NGX_RTMP_VERSION_H_INCLUDED_\n"
},
{
"path": "stat.xsl",
"chars": 11765,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n\n<!--\n Copyright (C) Roman Arutyunyan\n-->\n\n\n<xsl:stylesheet version=\"1.0\" xml"
},
{
"path": "test/README.md",
"chars": 499,
"preview": "# RTMP tests\n\nnginx.conf is sample config for testing nginx-rtmp.\nPlease update paths in it before using.\n\nRTMP port: 19"
},
{
"path": "test/dump.sh",
"chars": 49,
"preview": "rtmpdump -v -r \"rtmp://localhost/myapp/mystream\"\n"
},
{
"path": "test/ffstream.sh",
"chars": 84,
"preview": "ffmpeg -loglevel verbose -re -i ~/movie.avi -f flv rtmp://localhost/myapp/mystream\n"
},
{
"path": "test/nginx.conf",
"chars": 1245,
"preview": "worker_processes 1;\n\nerror_log logs/error.log debug;\n\nevents {\n worker_connections 1024;\n}\n\nrtmp {\n server {\n "
},
{
"path": "test/play.sh",
"chars": 59,
"preview": "ffplay -loglevel verbose \"rtmp://localhost/myapp/mystream\"\n"
},
{
"path": "test/rtmp-publisher/README.md",
"chars": 314,
"preview": "# RTMP Publisher\n\nSimple RTMP publisher.\n\nEdit the following flashvars in publisher.html & player.html to suite your nee"
},
{
"path": "test/rtmp-publisher/RtmpPlayer.mxml",
"chars": 2133,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\txmlns:s=\"library://ns."
},
{
"path": "test/rtmp-publisher/RtmpPlayerLight.mxml",
"chars": 2830,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\txmlns:s=\"library://ns."
},
{
"path": "test/rtmp-publisher/RtmpPublisher.mxml",
"chars": 2741,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\t xmlns:s=\"library://"
},
{
"path": "test/rtmp-publisher/player.html",
"chars": 614,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>RTMP Player</title>\n <script type=\"text/javascript\" src=\"swfobject.js\"></scr"
},
{
"path": "test/rtmp-publisher/publisher.html",
"chars": 505,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>RTMP Publisher</title>\n <script type=\"text/javascript\" src=\"swfobject.js\"></"
},
{
"path": "test/rtmp-publisher/swfobject.js",
"chars": 10220,
"preview": "/*\tSWFObject v2.2 <http://code.google.com/p/swfobject/> \n\tis released under the MIT License <http://www.opensource.org/l"
},
{
"path": "test/www/index.html",
"chars": 505,
"preview": "<b>Play</b> | <a href=\"record.html\">Record</a>\n<br/>\n<script type=\"text/javascript\" src=\"/jwplayer/jwplayer.js\"></script"
},
{
"path": "test/www/jwplayer/jwplayer.js",
"chars": 36524,
"preview": "\"undefined\"==typeof jwplayer&&(jwplayer=function(d){if(jwplayer.api)return jwplayer.api.selectPlayer(d)},jwplayer.versio"
},
{
"path": "test/www/jwplayer_old/swfobject.js",
"chars": 9759,
"preview": "/* SWFObject v2.1 <http://code.google.com/p/swfobject/>\n\tCopyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bo"
},
{
"path": "test/www/record.html",
"chars": 1454,
"preview": "<a href=\"index.html\">Play</a> | <b>Record</b>\n<br/>\n<script src=\"jwplayer_old/swfobject.js\"></script>\n<script type=\"text"
}
]
// ... and 5 more files (download for full content)
About this extraction
This page contains the full source code of the arut/nginx-rtmp-module GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 78 files (927.9 KB), approximately 266.5k tokens, and a symbol index with 757 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.