mirror of
https://github.com/zotanmew/nginx-rtmp-module.git
synced 2024-05-23 10:49:33 +02:00
Compare commits
302 commits
Author | SHA1 | Date | |
---|---|---|---|
Laura Hausmann | a5f44b6d54 | ||
Laura Hausmann | cfb277dea7 | ||
Laura Hausmann | d2d55348d6 | ||
3b7ada6677 | |||
22861b746d | |||
6e40dbe805 | |||
5f8a96f505 | |||
9102003adf | |||
ca9fd4a380 | |||
0668f512b6 | |||
e57666bcf4 | |||
Laura Hausmann | 2fee90d89f | ||
Laura Hausmann | e73d44f0fb | ||
9eefedac83 | |||
c4dcf60e63 | |||
e8ff79dfb9 | |||
8e344d7994 | |||
4d15e2c0f1 | |||
d4d762e917 | |||
5cb0be7f09 | |||
1688a23f0a | |||
a2895a03d9 | |||
649d220306 | |||
ca1f3eeaa2 | |||
a4a1343bb8 | |||
14221340d3 | |||
f7254ae5e8 | |||
fe35e3e98b | |||
cdbb1d4dc1 | |||
d743c36996 | |||
59c2454b23 | |||
6c4be06423 | |||
23ec4ce2d7 | |||
882ef5ca1e | |||
eee3c5eb15 | |||
a0a55be887 | |||
ce5a10a0d1 | |||
3bf7523267 | |||
b2049f3c39 | |||
a5ac72c274 | |||
00fd6cfa53 | |||
b4ee055393 | |||
e2a626ac04 | |||
7b7d30f36c | |||
e5b78f2de7 | |||
a65297410e | |||
669059f41b | |||
ff3536996c | |||
15cc5d0226 | |||
504b9ee29d | |||
23d67822b2 | |||
21db986d97 | |||
a01cc448ee | |||
a898a09d87 | |||
916f3f8374 | |||
1c3dc989ef | |||
d25c56fa69 | |||
e65f2d099b | |||
f31e27fbaf | |||
bb4190e248 | |||
542106e4de | |||
9121b34bdc | |||
ff86f5c3fd | |||
f23323a51a | |||
f8992e572f | |||
4975784d46 | |||
07912c5cd1 | |||
95d81573c9 | |||
bc81475b6b | |||
6b8155cf3b | |||
9c71ce6761 | |||
d86287fe3c | |||
ebe697b601 | |||
dc76eb2641 | |||
18b228a01d | |||
4bf6852a28 | |||
4809496d78 | |||
315e8aa497 | |||
dbcb7aa966 | |||
2fd45d4114 | |||
c47cb2370f | |||
a037181c59 | |||
7381b66e13 | |||
a88bc39141 | |||
998de2937a | |||
a2d65b4251 | |||
26d6107307 | |||
e38fcac9c9 | |||
e4799c633a | |||
7db5ef0ea5 | |||
a9e0056d5b | |||
1d5a20ea2b | |||
eca3fa3b04 | |||
2b0596051e | |||
77ba897d2f | |||
6d9a85e061 | |||
b4ecd58544 | |||
aee81e3c8f | |||
45a02da89e | |||
51396cdebb | |||
358806e915 | |||
14b56c4a5b | |||
0df743179d | |||
965523f397 | |||
fe122c1597 | |||
a48dadfbc1 | |||
341b07409d | |||
970da5673d | |||
570204bdeb | |||
8b97be9593 | |||
c3237ae747 | |||
62748fe56d | |||
1e6ae8d94d | |||
281d2226d9 | |||
89dd74e666 | |||
4f96ff087d | |||
16851c4512 | |||
28f75cb86d | |||
2a6b426247 | |||
d171a0a9b0 | |||
0d94bb2c84 | |||
0bd7d6b375 | |||
307c8d969a | |||
93e9377dc6 | |||
5376bd3432 | |||
98f700a090 | |||
0bfbd6b39f | |||
f15596b8d1 | |||
64c0529fde | |||
01825510f7 | |||
93cf3b69f1 | |||
bfaccfd738 | |||
7eb100a306 | |||
a3924dce67 | |||
66b3bcf096 | |||
65e24b3fee | |||
182566fe93 | |||
e8304c9852 | |||
7e68afde6f | |||
d13e665e56 | |||
2855a9ffc1 | |||
86cfd20b28 | |||
cfadbd7779 | |||
c11797815d | |||
6666d789b5 | |||
ede4b5f0f4 | |||
9f75cc2c6e | |||
2d4613c906 | |||
fc013040b6 | |||
12595a21aa | |||
f9d89634ad | |||
f344f4ae92 | |||
d28e52b32b | |||
c0b592a57c | |||
5e179d7296 | |||
4ce7ea8b9d | |||
292a6c1ca8 | |||
f89d8c1973 | |||
96b69327fa | |||
298697a4da | |||
bf332f3794 | |||
a194707ea9 | |||
cd0d9f73a5 | |||
b9bdd89676 | |||
07a83d8750 | |||
b937376041 | |||
1b7d6148e5 | |||
63c87e8070 | |||
8c5139650d | |||
94343ad786 | |||
3352af8b73 | |||
118b808207 | |||
dc5add30a8 | |||
c206cd7978 | |||
9b25e901f8 | |||
f8da609671 | |||
06e49e05fd | |||
4c7dd6ed00 | |||
c80342e0ab | |||
b6687d6cab | |||
d1e9d8682e | |||
2bc42d90f2 | |||
9ec1c78749 | |||
cd416d5fd2 | |||
74fdeef568 | |||
0f73f4e05b | |||
f9aa396689 | |||
fd23b27f3f | |||
c078a7c3e0 | |||
17d5c4678d | |||
c9442e9aa3 | |||
f9c89a2e21 | |||
f1cde7d4a4 | |||
9faaa99829 | |||
509a9e2f75 | |||
f8097173ab | |||
bacdd48aac | |||
b666598e3a | |||
a344ee6607 | |||
7abda68ef7 | |||
754cca2a58 | |||
0d0882b29d | |||
83cb5a6f90 | |||
5b62a17516 | |||
498ff9a468 | |||
091936ea2d | |||
b4bbfff24b | |||
5d6ea2314f | |||
100ecc4fe8 | |||
8fda94268c | |||
414053862c | |||
f29fbc89a4 | |||
bd154e391b | |||
f0c9b595bc | |||
c39589c5ea | |||
854d5142df | |||
5504e4636c | |||
a92851011a | |||
b1e28ead5b | |||
335bf07021 | |||
24be2c72cd | |||
7f24b5f6cd | |||
bcbef42a7c | |||
b3f0fb0460 | |||
5ed7825c0c | |||
bb67548636 | |||
3497601b6b | |||
38f98aa81c | |||
139c8c2773 | |||
8409e4c499 | |||
cb439496eb | |||
ec3684f9f9 | |||
0327cbf651 | |||
ae7974bc2d | |||
f455876a9c | |||
4e17054a6d | |||
dec648681d | |||
46c0fe6d5a | |||
3207278846 | |||
2a68430ae2 | |||
70d056bf92 | |||
658f26fc2c | |||
4da120a125 | |||
fb80463ba9 | |||
21683a4408 | |||
25146e40f9 | |||
e9f58daf81 | |||
3170037e74 | |||
dc0b3df1b7 | |||
52b09f5300 | |||
ac4197b871 | |||
b49c7fb6ea | |||
5d4dfaee43 | |||
1e5b135b2b | |||
889e6afccb | |||
99cf8a1a10 | |||
1055727de8 | |||
de8faa55a7 | |||
c9e70d5994 | |||
593368e9e6 | |||
a05b3babfe | |||
961fde23f7 | |||
75fe5406b4 | |||
1616a378ef | |||
4c1b69b5bb | |||
038c3bbc67 | |||
0d77210815 | |||
c4ee944870 | |||
69d79ccda6 | |||
859540db60 | |||
087e5358e2 | |||
d797d269a4 | |||
95471ae880 | |||
a079875fd7 | |||
8341644121 | |||
098ded3e87 | |||
70c90bbac5 | |||
a4907e80ce | |||
48983747d3 | |||
6ce821727d | |||
809f094124 | |||
2c2e90b706 | |||
b053eca1f9 | |||
b92a262f4c | |||
955c7e6b64 | |||
cb36f9c23c | |||
1d1d4fcac6 | |||
459e730d34 | |||
8f0f2e75c9 | |||
17159755e5 | |||
de42f3801d | |||
1d21d68c3c | |||
2a54c8e089 | |||
d069f36f86 | |||
99433754df | |||
2362acf45c | |||
ad6c63c149 | |||
c54b413b43 | |||
d99c069e8e | |||
2262649c1f | |||
9ebfcc1978 | |||
80d7b1c905 |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/.idea
|
||||
/.settings
|
||||
/.project
|
||||
/.cproject
|
||||
/.vscode
|
7
AUTHORS
7
AUTHORS
|
@ -6,3 +6,10 @@ Project author:
|
|||
Contacts:
|
||||
arut@qip.ru
|
||||
arutyunyan.roman@gmail.com
|
||||
|
||||
Fork author:
|
||||
Sergey Dryanzhinsky
|
||||
Moscow, Russia
|
||||
|
||||
Contacts:
|
||||
sergey.dryabzhinsky@gmail.com
|
||||
|
|
50
README.md
50
README.md
|
@ -1,14 +1,29 @@
|
|||
# NGINX-based Media Streaming Server
|
||||
## nginx-rtmp-module
|
||||
|
||||
## nginx-rtmp-module
|
||||
|
||||
### Project blog
|
||||
|
||||
http://nginx-rtmp.blogspot.com
|
||||
|
||||
### Wiki manual
|
||||
### Documentation
|
||||
|
||||
https://github.com/arut/nginx-rtmp-module/wiki/Directives
|
||||
* [Home](doc/README.md)
|
||||
* [Control module](doc/control_modul.md)
|
||||
* [Debug log](doc/debug_log.md)
|
||||
* [Directives](doc/directives.md)
|
||||
* [Examples](doc/examples.md)
|
||||
* [Exec wrapper in bash](doc/exec_wrapper_in_bash.md)
|
||||
* [FAQ](doc/faq.md)
|
||||
* [Getting number of subscribers](doc/getting_number_of_subscribers.md)
|
||||
* [Getting started with nginx rtmp](doc/getting_started.md)
|
||||
* [Installing in Gentoo](doc/installing_in_gentoo.md)
|
||||
* [Installing on Ubuntu using PPAs](doc/installing_ubuntu_using_ppas.md)
|
||||
* [Tutorial](doc/tutorial.md)
|
||||
|
||||
*Source: https://github.com/arut/nginx-rtmp-module/wiki*
|
||||
|
||||
* [Latest updates](doc/README.md#updates)
|
||||
|
||||
### Google group
|
||||
|
||||
|
@ -76,6 +91,12 @@ For building debug version of nginx add `--with-debug`
|
|||
|
||||
[Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log)
|
||||
|
||||
### Contributing and Branch Policy
|
||||
|
||||
The "dev" branch is the one where all contributions will be merged before reaching "master".
|
||||
If you plan to propose a patch, please commit into the "dev" branch or its own feature branch.
|
||||
Direct commit to "master" are not permitted.
|
||||
|
||||
### Windows limitations
|
||||
|
||||
Windows support is limited. These features are not supported
|
||||
|
@ -97,10 +118,8 @@ name - interpreted by each application
|
|||
|
||||
### 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.
|
||||
This NGINX-RTMP module does not support multi-worker live
|
||||
streaming. While this feature can be enabled through rtmp_auto_push on|off directive, it is ill advised because it is incompatible with NGINX versions starting 1.7.2 and up, there for it should not be used.
|
||||
|
||||
|
||||
### Example nginx.conf
|
||||
|
@ -138,7 +157,7 @@ rtmp_auto_push directive.
|
|||
application big {
|
||||
live on;
|
||||
|
||||
# On every pusblished stream run this command (ffmpeg)
|
||||
# On every published stream run this command (ffmpeg)
|
||||
# with substitutions: $app/${app}, $name/${name} for application & stream name.
|
||||
#
|
||||
# This ffmpeg call receives stream from this application &
|
||||
|
@ -315,18 +334,3 @@ rtmp_auto_push directive.
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
### Multi-worker streaming example
|
||||
|
||||
rtmp_auto_push on;
|
||||
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
|
||||
application mytv {
|
||||
live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
config
58
config
|
@ -1,6 +1,5 @@
|
|||
ngx_addon_name="ngx_rtmp_module"
|
||||
|
||||
CORE_MODULES="$CORE_MODULES
|
||||
RTMP_CORE_MODULES=" \
|
||||
ngx_rtmp_module \
|
||||
ngx_rtmp_core_module \
|
||||
ngx_rtmp_cmd_module \
|
||||
|
@ -15,21 +14,18 @@ CORE_MODULES="$CORE_MODULES
|
|||
ngx_rtmp_relay_module \
|
||||
ngx_rtmp_exec_module \
|
||||
ngx_rtmp_auto_push_module \
|
||||
ngx_rtmp_notify_module \
|
||||
ngx_rtmp_auto_push_index_module \
|
||||
ngx_rtmp_log_module \
|
||||
ngx_rtmp_limit_module \
|
||||
ngx_rtmp_hls_module \
|
||||
ngx_rtmp_dash_module \
|
||||
ngx_rtmp_notify_module \
|
||||
"
|
||||
|
||||
|
||||
HTTP_MODULES="$HTTP_MODULES \
|
||||
RTMP_HTTP_MODULES=" \
|
||||
ngx_rtmp_stat_module \
|
||||
ngx_rtmp_control_module \
|
||||
"
|
||||
|
||||
|
||||
NGX_ADDON_DEPS="$NGX_ADDON_DEPS \
|
||||
RTMP_DEPS=" \
|
||||
$ngx_addon_dir/ngx_rtmp_amf.h \
|
||||
$ngx_addon_dir/ngx_rtmp_bandwidth.h \
|
||||
$ngx_addon_dir/ngx_rtmp_cmd_module.h \
|
||||
|
@ -48,9 +44,7 @@ NGX_ADDON_DEPS="$NGX_ADDON_DEPS \
|
|||
$ngx_addon_dir/hls/ngx_rtmp_mpegts.h \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.h \
|
||||
"
|
||||
|
||||
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
|
||||
RTMP_CORE_SRCS=" \
|
||||
$ngx_addon_dir/ngx_rtmp.c \
|
||||
$ngx_addon_dir/ngx_rtmp_init.c \
|
||||
$ngx_addon_dir/ngx_rtmp_handshake.c \
|
||||
|
@ -70,13 +64,10 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
|
|||
$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_stat_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_control_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 \
|
||||
|
@ -84,9 +75,44 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
|
|||
$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/hls/ngx_rtmp_mpegts_crc.c \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.c \
|
||||
$ngx_addon_dir/ngx_rtmp_notify_module.c \
|
||||
"
|
||||
CFLAGS="$CFLAGS -I$ngx_addon_dir"
|
||||
RTMP_HTTP_SRCS=" \
|
||||
$ngx_addon_dir/ngx_rtmp_stat_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_control_module.c \
|
||||
"
|
||||
ngx_module_incs=$ngx_addon_dir
|
||||
ngx_module_deps=$RTMP_DEPS
|
||||
|
||||
if [ "$ngx_module_link" = "" ] ; then
|
||||
# Old nginx version
|
||||
ngx_module_link=NONE
|
||||
|
||||
EVENT_MODULES="$EVENT_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"
|
||||
fi
|
||||
|
||||
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
|
||||
elif [ $ngx_module_link = ADDON ] ; then
|
||||
ngx_module_type=EVENT
|
||||
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_srcs=$RTMP_HTTP_SRCS
|
||||
. auto/module
|
||||
fi
|
||||
|
||||
USE_OPENSSL=YES
|
||||
|
||||
CFLAGS="$CFLAGS -I$ngx_addon_dir"
|
||||
# Debug build with all warnings as errors
|
||||
# CFLAGS="$CFLAGS -I$ngx_addon_dir -Wall -Wpointer-arith -Wno-unused-parameter -Werror"
|
||||
|
|
|
@ -12,6 +12,7 @@ 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_rtmp_playlist_pt next_playlist;
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf);
|
||||
|
@ -19,13 +20,18 @@ 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);
|
||||
static ngx_int_t ngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_BUFSIZE (1024*1024)
|
||||
#define NGX_RTMP_DASH_MAX_MDAT (10*1024*1024)
|
||||
/* Big buffer for 8k (QHD) cameras */
|
||||
#ifndef NGX_RTMP_DASH_BUFSIZE
|
||||
#define NGX_RTMP_DASH_BUFSIZE (16*1024*1024)
|
||||
#endif
|
||||
#define NGX_RTMP_DASH_MAX_MDAT (16*1024*1024)
|
||||
#define NGX_RTMP_DASH_MAX_SAMPLES 1024
|
||||
#define NGX_RTMP_DASH_DIR_ACCESS 0744
|
||||
/* Allow access to www-data (web-server) and others too */
|
||||
#define NGX_RTMP_DASH_DIR_ACCESS 0755
|
||||
|
||||
#define NGX_RTMP_DASH_GMT_LENGTH sizeof("1970-09-28T12:00:00+06:00")
|
||||
|
||||
typedef struct {
|
||||
uint32_t timestamp;
|
||||
|
@ -78,11 +84,29 @@ typedef struct {
|
|||
} ngx_rtmp_dash_cleanup_t;
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF 1
|
||||
#define NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP 2
|
||||
#define NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD 3
|
||||
#define NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO 4
|
||||
|
||||
static ngx_conf_enum_t ngx_rtmp_dash_clock_compensation_type_slots[] = {
|
||||
{ ngx_string("off"), NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF },
|
||||
{ ngx_string("ntp"), NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP },
|
||||
{ ngx_string("http_head"), NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD },
|
||||
{ ngx_string("http_iso"), NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO },
|
||||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ngx_flag_t dash;
|
||||
ngx_msec_t fraglen;
|
||||
ngx_msec_t playlen;
|
||||
ngx_flag_t nested;
|
||||
ngx_uint_t clock_compensation; // Try to compensate clock drift
|
||||
// between client and server (on client side)
|
||||
ngx_str_t clock_helper_uri; // Use uri to static file on HTTP server
|
||||
// - same machine as RTMP/DASH)
|
||||
// - or NTP server address
|
||||
ngx_str_t path;
|
||||
ngx_uint_t winfrags;
|
||||
ngx_flag_t cleanup;
|
||||
|
@ -134,6 +158,20 @@ static ngx_command_t ngx_rtmp_dash_commands[] = {
|
|||
offsetof(ngx_rtmp_dash_app_conf_t, nested),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("dash_clock_compensation"),
|
||||
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_dash_app_conf_t, clock_compensation),
|
||||
&ngx_rtmp_dash_clock_compensation_type_slots },
|
||||
|
||||
{ ngx_string("dash_clock_helper_uri"),
|
||||
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, clock_helper_uri),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
@ -212,6 +250,22 @@ ngx_rtmp_dash_rename_file(u_char *src, u_char *dst)
|
|||
}
|
||||
|
||||
|
||||
static ngx_uint_t
|
||||
ngx_rtmp_dash_gcd(ngx_uint_t m, ngx_uint_t n)
|
||||
{
|
||||
/* greatest common divisor */
|
||||
|
||||
ngx_uint_t temp;
|
||||
|
||||
while (n) {
|
||||
temp=n;
|
||||
n=m % n;
|
||||
m=temp;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
||||
{
|
||||
|
@ -221,15 +275,24 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
ngx_fd_t fd;
|
||||
struct tm tm;
|
||||
ngx_str_t noname, *name;
|
||||
ngx_uint_t i;
|
||||
ngx_uint_t i, frame_rate_num, frame_rate_denom;
|
||||
ngx_uint_t depth_msec, depth_sec;
|
||||
ngx_uint_t update_period, update_period_msec;
|
||||
ngx_uint_t buffer_time, buffer_time_msec;
|
||||
ngx_uint_t presentation_delay, presentation_delay_msec;
|
||||
ngx_uint_t gcd, par_x, par_y;
|
||||
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;
|
||||
|
||||
ngx_rtmp_playlist_t v;
|
||||
|
||||
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
|
||||
static u_char start_time[sizeof("1970-09-28T12:00:00+06:00")];
|
||||
static u_char end_time[sizeof("1970-09-28T12:00:00+06:00")];
|
||||
static u_char avaliable_time[NGX_RTMP_DASH_GMT_LENGTH];
|
||||
static u_char publish_time[NGX_RTMP_DASH_GMT_LENGTH];
|
||||
static u_char buffer_depth[sizeof("P00Y00M00DT00H00M00.000S")];
|
||||
static u_char frame_rate[(NGX_INT_T_LEN * 2) + 2];
|
||||
|
||||
dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
|
||||
|
@ -259,34 +322,37 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
" type=\"dynamic\"\n" \
|
||||
" xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" \
|
||||
" availabilityStartTime=\"%s\"\n" \
|
||||
" availabilityEndTime=\"%s\"\n" \
|
||||
" minimumUpdatePeriod=\"PT%uiS\"\n" \
|
||||
" minBufferTime=\"PT%uiS\"\n" \
|
||||
" timeShiftBufferDepth=\"PT0H0M0.00S\"\n" \
|
||||
" suggestedPresentationDelay=\"PT%uiS\"\n" \
|
||||
" publishTime=\"%s\"\n" \
|
||||
" minimumUpdatePeriod=\"PT%ui.%03uiS\"\n" \
|
||||
" minBufferTime=\"PT%ui.%03uiS\"\n" \
|
||||
" timeShiftBufferDepth=\"%s\"\n" \
|
||||
" suggestedPresentationDelay=\"PT%ui.%03uiS\"\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" \
|
||||
" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\">\n"
|
||||
|
||||
#define NGX_RTMP_DASH_MANIFEST_PERIOD \
|
||||
" <Period start=\"PT0S\" id=\"dash\">\n"
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_MANIFEST_VIDEO \
|
||||
" <AdaptationSet\n" \
|
||||
" id=\"1\"\n" \
|
||||
" startWithSAP=\"1\"\n" \
|
||||
" segmentAlignment=\"true\"\n" \
|
||||
" maxWidth=\"%ui\"\n" \
|
||||
" maxHeight=\"%ui\"\n" \
|
||||
" maxFrameRate=\"%ui\">\n" \
|
||||
" maxFrameRate=\"%s\"\n" \
|
||||
" par=\"%ui:%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" \
|
||||
" frameRate=\"%s\"\n" \
|
||||
" sar=\"1:1\"\n" \
|
||||
" startWithSAP=\"1\"\n" \
|
||||
" bandwidth=\"%ui\">\n" \
|
||||
" <SegmentTemplate\n" \
|
||||
" presentationTimeOffset=\"0\"\n" \
|
||||
|
@ -310,6 +376,7 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
#define NGX_RTMP_DASH_MANIFEST_AUDIO \
|
||||
" <AdaptationSet\n" \
|
||||
" id=\"2\"\n" \
|
||||
" startWithSAP=\"1\"\n" \
|
||||
" segmentAlignment=\"true\">\n" \
|
||||
" <AudioChannelConfiguration\n" \
|
||||
" schemeIdUri=\"urn:mpeg:dash:" \
|
||||
|
@ -320,7 +387,6 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
" mimeType=\"audio/mp4\"\n" \
|
||||
" codecs=\"mp4a.%s\"\n" \
|
||||
" audioSamplingRate=\"%ui\"\n" \
|
||||
" startWithSAP=\"1\"\n" \
|
||||
" bandwidth=\"%ui\">\n" \
|
||||
" <SegmentTemplate\n" \
|
||||
" presentationTimeOffset=\"0\"\n" \
|
||||
|
@ -337,42 +403,101 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
" </AdaptationSet>\n"
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_PERIOD_FOOTER \
|
||||
" </Period>\n"
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_MANIFEST_CLOCK \
|
||||
" <UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:%s:2014\"\n" \
|
||||
" value=\"%V\" />\n"
|
||||
|
||||
|
||||
#define NGX_RTMP_DASH_MANIFEST_FOOTER \
|
||||
" </Period>\n" \
|
||||
"</MPD>\n"
|
||||
|
||||
ngx_libc_localtime(ctx->start_time.sec +
|
||||
ngx_rtmp_dash_get_frag(s, 0)->timestamp / 1000, &tm);
|
||||
|
||||
*ngx_sprintf(start_time, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1,
|
||||
tm.tm_mday, tm.tm_hour,
|
||||
/**
|
||||
* Availability time must be equal stream start time
|
||||
* Cos segments time counting from it
|
||||
*/
|
||||
ngx_libc_gmtime(ctx->start_time.sec, &tm);
|
||||
*ngx_sprintf(avaliable_time, "%4d-%02d-%02dT%02d:%02d:%02dZ",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1,
|
||||
tm.tm_mday, tm.tm_hour,
|
||||
tm.tm_min, tm.tm_sec
|
||||
) = 0;
|
||||
|
||||
/* Stream publish time */
|
||||
*ngx_sprintf(publish_time, "%s", avaliable_time) = 0;
|
||||
|
||||
depth_sec = (ngx_uint_t) (
|
||||
ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->timestamp +
|
||||
ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->duration -
|
||||
ngx_rtmp_dash_get_frag(s, 0)->timestamp);
|
||||
|
||||
depth_msec = depth_sec % 1000;
|
||||
depth_sec -= depth_msec;
|
||||
depth_sec /= 1000;
|
||||
|
||||
ngx_libc_gmtime(depth_sec, &tm);
|
||||
|
||||
*ngx_sprintf(buffer_depth, "P%dY%02dM%02dDT%dH%02dM%02d.%03dS",
|
||||
tm.tm_year - 70, tm.tm_mon,
|
||||
tm.tm_mday - 1, tm.tm_hour,
|
||||
tm.tm_min, tm.tm_sec,
|
||||
ctx->start_time.gmtoff < 0 ? '-' : '+',
|
||||
ngx_abs(ctx->start_time.gmtoff / 60),
|
||||
ngx_abs(ctx->start_time.gmtoff % 60)) = 0;
|
||||
|
||||
ngx_libc_localtime(ctx->start_time.sec +
|
||||
(ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->timestamp +
|
||||
ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->duration) /
|
||||
1000, &tm);
|
||||
|
||||
*ngx_sprintf(end_time, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1,
|
||||
tm.tm_mday, tm.tm_hour,
|
||||
tm.tm_min, tm.tm_sec,
|
||||
ctx->start_time.gmtoff < 0 ? '-' : '+',
|
||||
ngx_abs(ctx->start_time.gmtoff / 60),
|
||||
ngx_abs(ctx->start_time.gmtoff % 60)) = 0;
|
||||
depth_msec) = 0;
|
||||
|
||||
last = buffer + sizeof(buffer);
|
||||
|
||||
/**
|
||||
* Calculate playlist minimal update period
|
||||
* This should be more than biggest segment duration
|
||||
* Cos segments rounded by keyframe/GOP.
|
||||
* And that time not always equals to fragment length.
|
||||
*/
|
||||
update_period = dacf->fraglen;
|
||||
|
||||
for (i = 0; i < ctx->nfrags; i++) {
|
||||
f = ngx_rtmp_dash_get_frag(s, i);
|
||||
if (f->duration > update_period) {
|
||||
update_period = f->duration;
|
||||
}
|
||||
}
|
||||
|
||||
// Reasonable delay for streaming
|
||||
presentation_delay = update_period * 2 + 1000;
|
||||
presentation_delay_msec = presentation_delay % 1000;
|
||||
presentation_delay -= presentation_delay_msec;
|
||||
presentation_delay /= 1000;
|
||||
|
||||
// Calculate msec part and seconds
|
||||
update_period_msec = update_period % 1000;
|
||||
update_period -= update_period_msec;
|
||||
update_period /= 1000;
|
||||
|
||||
// Buffer length by default fragment length
|
||||
buffer_time = dacf->fraglen;
|
||||
buffer_time_msec = buffer_time % 1000;
|
||||
buffer_time -= buffer_time_msec;
|
||||
buffer_time /= 1000;
|
||||
|
||||
// Fill DASH header
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER,
|
||||
start_time,
|
||||
end_time,
|
||||
(ngx_uint_t) (dacf->fraglen / 1000),
|
||||
(ngx_uint_t) (dacf->fraglen / 1000),
|
||||
(ngx_uint_t) (dacf->fraglen / 500));
|
||||
// availabilityStartTime
|
||||
avaliable_time,
|
||||
// publishTime
|
||||
publish_time,
|
||||
// minimumUpdatePeriod
|
||||
update_period, update_period_msec,
|
||||
// minBufferTime
|
||||
buffer_time, buffer_time_msec,
|
||||
// timeShiftBufferDepth
|
||||
buffer_depth,
|
||||
// suggestedPresentationDelay
|
||||
presentation_delay, presentation_delay_msec
|
||||
);
|
||||
|
||||
p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_PERIOD);
|
||||
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
|
||||
|
@ -382,17 +507,46 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
sep = (dacf->nested ? "" : "-");
|
||||
|
||||
if (ctx->has_video) {
|
||||
frame_rate_num = (ngx_uint_t) (codec_ctx->frame_rate * 1000.);
|
||||
|
||||
if (frame_rate_num % 1000 == 0) {
|
||||
*ngx_sprintf(frame_rate, "%ui", frame_rate_num / 1000) = 0;
|
||||
} else {
|
||||
frame_rate_denom = 1000;
|
||||
switch (frame_rate_num) {
|
||||
case 23976:
|
||||
frame_rate_num = 24000;
|
||||
frame_rate_denom = 1001;
|
||||
break;
|
||||
case 29970:
|
||||
frame_rate_num = 30000;
|
||||
frame_rate_denom = 1001;
|
||||
break;
|
||||
case 59940:
|
||||
frame_rate_num = 60000;
|
||||
frame_rate_denom = 1001;
|
||||
break;
|
||||
}
|
||||
|
||||
*ngx_sprintf(frame_rate, "%ui/%ui", frame_rate_num, frame_rate_denom) = 0;
|
||||
}
|
||||
|
||||
gcd = ngx_rtmp_dash_gcd(codec_ctx->width, codec_ctx->height);
|
||||
par_x = codec_ctx->width / gcd;
|
||||
par_y = codec_ctx->height / gcd;
|
||||
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO,
|
||||
codec_ctx->width,
|
||||
codec_ctx->height,
|
||||
codec_ctx->frame_rate,
|
||||
frame_rate,
|
||||
par_x, par_y,
|
||||
&ctx->name,
|
||||
codec_ctx->avc_profile,
|
||||
codec_ctx->avc_compat,
|
||||
codec_ctx->avc_level,
|
||||
codec_ctx->width,
|
||||
codec_ctx->height,
|
||||
codec_ctx->frame_rate,
|
||||
frame_rate,
|
||||
(ngx_uint_t) (codec_ctx->video_data_rate * 1000),
|
||||
name, sep,
|
||||
name, sep);
|
||||
|
@ -429,6 +583,34 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
}
|
||||
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_PERIOD_FOOTER);
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
|
||||
/* UTCTiming value */
|
||||
switch (dacf->clock_compensation) {
|
||||
case NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP:
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
|
||||
"ntp",
|
||||
&dacf->clock_helper_uri
|
||||
);
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
break;
|
||||
case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD:
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
|
||||
"http-head",
|
||||
&dacf->clock_helper_uri
|
||||
);
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
break;
|
||||
case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO:
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
|
||||
"http-iso",
|
||||
&dacf->clock_helper_uri
|
||||
);
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER);
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
|
||||
|
@ -450,7 +632,11 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
|
|||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
ngx_str_set(&(v.module), "dash");
|
||||
v.playlist.data = ctx->playlist.data;
|
||||
v.playlist.len = ctx->playlist.len;
|
||||
return next_playlist(s, &v);
|
||||
}
|
||||
|
||||
|
||||
|
@ -725,6 +911,10 @@ ngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s)
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v');
|
||||
|
||||
ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a');
|
||||
|
@ -1001,6 +1191,10 @@ ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary,
|
|||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);
|
||||
f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: timestamp=%ui, f-timestamp=%ui, boundary=%i, dacf-fraglen=%ui",
|
||||
timestamp, f->timestamp, boundary, dacf->fraglen);
|
||||
|
||||
d = (int32_t) (timestamp - f->timestamp);
|
||||
|
||||
if (d >= 0) {
|
||||
|
@ -1015,26 +1209,44 @@ ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary,
|
|||
hit = (-d > 1000);
|
||||
}
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: d=%i, f-duration=%ui, hit=%i",
|
||||
d, f->duration, hit);
|
||||
|
||||
if (ctx->has_video && !hit) {
|
||||
boundary = 0;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=0 cos has_video && !hit");
|
||||
}
|
||||
|
||||
if (!ctx->has_video && ctx->has_audio) {
|
||||
boundary = hit;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=hit cos !has_video && has_audio");
|
||||
}
|
||||
|
||||
if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {
|
||||
boundary = 1;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=1 cos audio max mdat");
|
||||
}
|
||||
|
||||
if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {
|
||||
boundary = 1;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=1 cos video max mdat");
|
||||
}
|
||||
|
||||
if (!ctx->opened) {
|
||||
boundary = 1;
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=1 cos !opened");
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"dash: update_fragments: boundary=%i",
|
||||
boundary);
|
||||
|
||||
if (boundary) {
|
||||
ngx_rtmp_dash_close_fragments(s);
|
||||
ngx_rtmp_dash_open_fragments(s);
|
||||
|
@ -1356,7 +1568,7 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
|
|||
"dash: cleanup '%V' allowed, mpd missing '%s'",
|
||||
&name, mpd_path);
|
||||
|
||||
max_age = 0;
|
||||
max_age = playlen / 500;
|
||||
|
||||
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
|
||||
name.data[name.len - 3] == 'm' &&
|
||||
|
@ -1384,7 +1596,7 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
|
|||
name.data[name.len - 2] == 'a' &&
|
||||
name.data[name.len - 1] == 'w')
|
||||
{
|
||||
max_age = playlen / 1000;
|
||||
max_age = playlen / 500;
|
||||
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
|
||||
|
@ -1412,17 +1624,31 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#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);
|
||||
|
||||
// Next callback in doubled playlist length time to make sure what all
|
||||
// players read all segments
|
||||
#if (nginx_version >= 1011005)
|
||||
return cleanup->playlen * 2;
|
||||
#else
|
||||
return cleanup->playlen / 500;
|
||||
#endif
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_dash_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v)
|
||||
{
|
||||
return next_playlist(s, v);
|
||||
}
|
||||
|
||||
static void *
|
||||
ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf)
|
||||
|
@ -1439,6 +1665,7 @@ ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf)
|
|||
conf->playlen = NGX_CONF_UNSET_MSEC;
|
||||
conf->cleanup = NGX_CONF_UNSET;
|
||||
conf->nested = NGX_CONF_UNSET;
|
||||
conf->clock_compensation = NGX_CONF_UNSET;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
@ -1456,6 +1683,9 @@ ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
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);
|
||||
ngx_conf_merge_uint_value(conf->clock_compensation, prev->clock_compensation,
|
||||
NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF);
|
||||
ngx_conf_merge_str_value(conf->clock_helper_uri, prev->clock_helper_uri, "");
|
||||
|
||||
if (conf->fraglen) {
|
||||
conf->winfrags = conf->playlen / conf->fraglen;
|
||||
|
@ -1524,5 +1754,8 @@ ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf)
|
|||
next_stream_eof = ngx_rtmp_stream_eof;
|
||||
ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof;
|
||||
|
||||
next_playlist = ngx_rtmp_playlist;
|
||||
ngx_rtmp_playlist = ngx_rtmp_dash_playlist;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
|
@ -1,2 +1,29 @@
|
|||
Documentation is available here:
|
||||
https://github.com/arut/nginx-rtmp-module/wiki
|
||||
# Welcome to the nginx-rtmp-module documentation!
|
||||
|
||||
## Pages
|
||||
|
||||
* [Control module](control_modul.md)
|
||||
* [Debug log](debug_log.md)
|
||||
* [Directives](directives.md)
|
||||
* [Examples](examples.md)
|
||||
* [Exec wrapper in bash](exec_wrapper_in_bash.md)
|
||||
* [FAQ](faq.md)
|
||||
* [Getting number of subscribers](getting_number_of_subscribers.md)
|
||||
* [Getting started with nginx rtmp](getting_started.md)
|
||||
* [Installing in Gentoo](Installing_in_gentoo.md)
|
||||
* [Installing on Ubuntu using PPAs](installing_ubuntu_using_ppas.md)
|
||||
* [Tutorial](tutorial.md)
|
||||
|
||||
*Source: https://github.com/arut/nginx-rtmp-module/wiki*
|
||||
|
||||
## Updates
|
||||
|
||||
* Directives
|
||||
* Notify
|
||||
* [on_playlist](directives.md#on_playlist)
|
||||
* [notify_send_redirect](directives.md#notify_send_redirect)
|
||||
* Client Caching
|
||||
* [hls_allow_client_cache](directives.md#hls_allow_client_cache)
|
||||
* Dash MPD generation
|
||||
* [dash_clock_compensation](directives.md#dash_clock_compensation)
|
||||
* [dash_clock_helper_uri](directives.md#dash_clock_helper_uri)
|
||||
|
|
94
doc/control_modul.md
Normal file
94
doc/control_modul.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# Control module
|
||||
|
||||
Control module is HTTP module which makes it possible to control rtmp module from outside using HTTP protocol. Here's an example of how to enable control.
|
||||
```sh
|
||||
http {
|
||||
...
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
....
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are several sub-modules within control module each controlling a different feature.
|
||||
|
||||
# Record
|
||||
This sub-module starts and stops recordings created with _manual_ flag.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/record/start|stop?srv=SRV&app=APP&name=NAME&rec=REC
|
||||
```
|
||||
|
||||
* srv=SRV - optional server{} block number within rtmp{} block, default to first server{} block
|
||||
* app=APP - required application name
|
||||
* name=NAME - required stream name
|
||||
* rec=REC - optional recorder name, defaults to root (unnamed) recorder
|
||||
|
||||
Example
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application myapp {
|
||||
live on;
|
||||
recorder rec1 {
|
||||
record all manual;
|
||||
record_suffix all.flv;
|
||||
record_path /tmp/rec;
|
||||
record_unique on;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Publish the stream with the following command
|
||||
```sh
|
||||
$ ffmpeg -i http://someserver.com/mychannel.ts -c:v copy -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Use the following commands to start and stop recording
|
||||
```sh
|
||||
$ curl "http://localhost:8080/control/record/start?app=myapp&name=mystream&rec=rec1"
|
||||
§ curl "http://localhost:8080/control/record/stop?app=myapp&name=mystream&rec=rec1"
|
||||
```
|
||||
|
||||
if the record start/stop request returns nothing sometimes, you should check if you use multi workers. one worker works great.
|
||||
|
||||
# Drop
|
||||
This sub-module provides a simple way to drop client connection.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/drop/publisher|subscriber|client?
|
||||
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID
|
||||
```
|
||||
|
||||
* srv, app, name - the same as above
|
||||
* addr - optional client address (the same as returned by rtmp_stat)
|
||||
* clientid - optional nginx client id (displayed in log and stat)
|
||||
|
||||
The first method ```drop/publisher``` drops publisher connection. The second ```drop/client``` drops every connection matching ```addr``` argument or all clients (including publisher) if ```addr``` is not specified.
|
||||
|
||||
Examples
|
||||
```sh
|
||||
$ curl http://localhost:8080/control/drop/publisher?app=myapp&name=mystream
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&addr=192.168.0.1
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&clientid=1
|
||||
```
|
||||
|
||||
# Redirect
|
||||
Redirect play/publish client to a new stream.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/redirect/publisher|subscriber|client?
|
||||
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID&newname=NEWNAME
|
||||
```
|
||||
|
||||
* srv, app, name, addr, clients - the same as above
|
||||
* newname - new stream name to redirect to
|
16
doc/debug_log.md
Normal file
16
doc/debug_log.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Debug log
|
||||
|
||||
In case you need to solve a streaming problem you might need to watch debug log.
|
||||
For that configure nginx with *--with-debug* flag.
|
||||
```sh
|
||||
$ cd nginx-X.Y.Z
|
||||
$ ./configure --add-module=/path/to/nginx-rtmp-module --with-debug ...
|
||||
```
|
||||
|
||||
After compiling set nginx error.log level to *debug* in nginx.conf
|
||||
```sh
|
||||
error_log logs/error.log debug;
|
||||
```
|
||||
|
||||
After that you will have _a lot_ of debug info in error.log. Please grep
|
||||
what your problem relates to (exec, notify etc) and post to [nginx-rtmp google group](https://groups.google.com/group/nginx-rtmp) to help with solving it.
|
1824
doc/directives.md
Normal file
1824
doc/directives.md
Normal file
File diff suppressed because it is too large
Load diff
69
doc/examples.md
Normal file
69
doc/examples.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Exampled
|
||||
|
||||
### Simple Video-on-Demand
|
||||
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application vod {
|
||||
play /var/flvs;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Simple live broadcast service
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application live {
|
||||
live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Re-translate remote stream
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application tv {
|
||||
live on;
|
||||
pull rtmp://cdn.example.com:443/programs/main pageUrl=http://www.example.com/index.html name=maintv;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Re-translate remote stream with HLS support
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application tv {
|
||||
live on;
|
||||
hls on;
|
||||
hls_path /tmp/tv2;
|
||||
hls_fragment 15s;
|
||||
|
||||
pull rtmp://tv2.example.com:443/root/new name=tv2;
|
||||
}
|
||||
}
|
||||
}
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
location /tv2 {
|
||||
alias /tmp/tv2;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stream your X screen through RTMP
|
||||
```sh
|
||||
$ ffmpeg -f x11grab -follow_mouse centered -r 25 -s cif -i :0.0 -f flv rtmp://localhost/myapp/screen
|
||||
```
|
33
doc/exec_wrapper_in_bash.md
Normal file
33
doc/exec_wrapper_in_bash.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Exec wrapper in bash
|
||||
|
||||
You can write exec wrapper in any language. However you should pay attention to termination process. When publisher closes the stream all executed processed get terminated. If you specify wrapper in ```exec``` directive instead of real ffmpeg then you might end up with your ffmpeg still alive and orphaned until it times out reading input data.
|
||||
|
||||
The solution is using signal traps. Here's an example of such wrapper in bash.
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
on_die ()
|
||||
{
|
||||
# kill all children
|
||||
pkill -KILL -P $$
|
||||
}
|
||||
|
||||
trap 'on_die' TERM
|
||||
ffmpeg -i rtmp://localhost/myapp/$1 -c copy -f flv rtmp://localhost/myapp2/$1 &
|
||||
wait
|
||||
```
|
||||
|
||||
The script registers SIGTERM handler which terminates child ffmpeg. Default signal sent by nginx-rtmp is SIGKILL which cannot be caught. For the above script to behave as expected you need to change exec kill signal with ```exec_kill_signal``` directive. It accept numeric or symbolic signal name (for POSIX.1-1990 signals). Here's example application.
|
||||
```sh
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
exec /var/scripts/exec_wrapper.sh $name;
|
||||
exec_kill_signal term;
|
||||
}
|
||||
|
||||
application myapp2 {
|
||||
live on;
|
||||
}
|
||||
```
|
26
doc/faq.md
Normal file
26
doc/faq.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# FAQ
|
||||
|
||||
#### RTMP stream is not played normally in IE, stream stops after several seconds.
|
||||
|
||||
Add this directive to fix the problem
|
||||
```sh
|
||||
wait_video on;
|
||||
```
|
||||
|
||||
#### I use `pull` directive to get stream from remote location. That works for RTMP clients but does not work for HLS.
|
||||
|
||||
Currently HLS clients do not trigger any events. You cannot pull or exec when HLS client connects to server. However you can use static directives `exec_static`, `pull ... static` to pull the stream always.
|
||||
|
||||
#### Seek does not work with flv files recorded by the module.
|
||||
|
||||
To make the files seekable add flv metadata with external software like yamdi, flvmeta or ffmpeg.
|
||||
```sh
|
||||
exec_record_done yamdi -i $path -o /var/videos/$basename;
|
||||
```
|
||||
|
||||
#### Published stream is missing from stats page after some time and clients fail to connect
|
||||
|
||||
Check if you use multiple workers in nginx (`worker_processes`). In such case you have to enable:
|
||||
```sh
|
||||
rtmp_auto_push on;
|
||||
```
|
38
doc/getting_number_of_subscribers.md
Normal file
38
doc/getting_number_of_subscribers.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Getting number of subscribers
|
||||
|
||||
There's an easy way to display number of clients watching the stream. You need to
|
||||
|
||||
Set up statistics page at location `/stat`
|
||||
```sh
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
allow 127.0.0.1;
|
||||
}
|
||||
```
|
||||
|
||||
Create a simple xsl stylesheet `nclients.xsl` extracting number of stream subscribers
|
||||
```xsl
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<xsl:output method="html"/>
|
||||
|
||||
<xsl:param name="app"/>
|
||||
<xsl:param name="name"/>
|
||||
|
||||
<xsl:template match="/">
|
||||
<xsl:value-of select="count(//application[name=$app]/live/stream[name=$name]/client[not(publishing) and flashver])"/>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
```
|
||||
|
||||
Set up a location returning number of suscribers
|
||||
```sh
|
||||
location /nclients {
|
||||
proxy_pass http://127.0.0.1/stat;
|
||||
xslt_stylesheet /www/nclients.xsl app='$arg_app' name='$arg_name';
|
||||
add_header Refresh "3; $request_uri";
|
||||
}
|
||||
```
|
||||
|
||||
Use HTTP request `http://myserver.com/nclients?app=myapp&name=mystream` to get the number of stream subscribers. This number will be automatically refreshed every 3 seconds when opened in browser or iframe.
|
188
doc/getting_started.md
Normal file
188
doc/getting_started.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
# Getting started with nginx rtmp
|
||||
|
||||
## Download, build and install
|
||||
|
||||
CD to build directory (home)
|
||||
```sh
|
||||
$ cd /usr/build
|
||||
```
|
||||
|
||||
Download & unpack latest nginx-rtmp (you can also use http)
|
||||
```sh
|
||||
$ git clone git://github.com/sergey-dryabzhinsky/nginx-rtmp-module
|
||||
```
|
||||
|
||||
Download & unpack nginx (you can also use svn)
|
||||
|
||||
```sh
|
||||
$ wget http://nginx.org/download/nginx-1.2.4.tar.gz
|
||||
$ tar xzf nginx-1.2.4.tar.gz
|
||||
$ cd nginx-1.2.4
|
||||
```
|
||||
|
||||
Build nginx with nginx-rtmp
|
||||
```sh
|
||||
$ ./configure --add-module=/usr/build/nginx-rtmp-module
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
For nginx 1.3.4-1.5.0 more options are needed
|
||||
```sh
|
||||
$ ./configure --add-module=/usr/build/nginx-rtmp-module --with-http_ssl_module
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
## Set up live streaming
|
||||
|
||||
To set up RTMP support you need to add `rtmp{}` section to `nginx.conf` (can be found in PREFIX/conf/nginx.conf). Stock `nginx.conf` contains only `http{}` section.
|
||||
|
||||
Use this `nginx.conf` instead of stock config:
|
||||
```sh
|
||||
#user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
error_log logs/error.log debug;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
# sample handlers
|
||||
#location /on_play {
|
||||
# if ($arg_pageUrl ~* localhost) {
|
||||
# return 201;
|
||||
# }
|
||||
# return 202;
|
||||
#}
|
||||
#location /on_publish {
|
||||
# return 201;
|
||||
#}
|
||||
|
||||
#location /vod {
|
||||
# alias /var/myvideos;
|
||||
#}
|
||||
|
||||
# rtmp stat
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
location /stat.xsl {
|
||||
# you can move stat.xsl to a different location
|
||||
root /usr/build/nginx-rtmp-module;
|
||||
}
|
||||
|
||||
# rtmp control
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
ping 30s;
|
||||
notify_method get;
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
# sample play/publish handlers
|
||||
#on_play http://localhost:8080/on_play;
|
||||
#on_publish http://localhost:8080/on_publish;
|
||||
|
||||
# sample recorder
|
||||
#recorder rec1 {
|
||||
# record all;
|
||||
# record_interval 30s;
|
||||
# record_path /tmp;
|
||||
# record_unique on;
|
||||
#}
|
||||
|
||||
# sample HLS
|
||||
#hls on;
|
||||
#hls_path /tmp/hls;
|
||||
#hls_sync 100ms;
|
||||
}
|
||||
|
||||
# Video on demand
|
||||
#application vod {
|
||||
# play /var/Videos;
|
||||
#}
|
||||
|
||||
# Video on demand over HTTP
|
||||
#application vod_http {
|
||||
# play http://localhost:8080/vod/;
|
||||
#}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
Navigate your browser to `http://localhost:8080/stat` to see current
|
||||
streaming statistics, connected clients, bandwidth etc.
|
||||
|
||||
## Publishing with ffmpeg
|
||||
|
||||
The easiest way to publish live video stream is using ffmpeg (or avconv).
|
||||
It's already installed on most systems and easy to install on others.
|
||||
|
||||
RTMP supports only a limited number of codecs. The most popular RTMP video
|
||||
codecs are H264, Sorenson-H263 (aka flv) and audio codecs AAC, MP3,
|
||||
Nellymoser, Speex. If your video is encoded with these codecs
|
||||
(the most common pair is H264/AAC) then you do not need any conversion.
|
||||
Otherwise you need to convert video to one of supported codecs.
|
||||
|
||||
We'll stream test file `/var/videos/test.mp4` to server with ffmpeg.
|
||||
|
||||
Streaming without conversion (given `test.mp4` codecs are compatible with RTMP)
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c copy -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (AAC) and video (H264), need `libx264` and `libfaac`
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libfaac -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (MP3) and video (H264), need `libx264` and `libmp3lame`
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (Nellymoser) and video (Sorenson H263)
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v flv -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Publishing video from webcam
|
||||
```sh
|
||||
$ ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Playing with ffplay
|
||||
```sh
|
||||
$ ffplay rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Publishing and playing with flash
|
||||
|
||||
See `test/rtmp-publisher` directory for test flash applets and html.
|
24
doc/installing_in_gentoo.md
Normal file
24
doc/installing_in_gentoo.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Installing in Gentoo
|
||||
|
||||
## Download module source code
|
||||
You have many options:
|
||||
* Get the zip at https://github.com/arut/nginx-rtmp-module/archive/master.zip
|
||||
* Or much better, do a git clone (see options in top of https://github.com/arut/nginx-rtmp-module)
|
||||
* Or get an ebuild from [fem-overlay](http://subversion.fem.tu-ilmenau.de/repository/fem-overlay/trunk/www-servers/nginx/nginx-1.2.5-r1.ebuild). And set the USE flag "nginx_modules_rtmp" or "nginx_modules_rtmp_hls".
|
||||
|
||||
## Emerge nginx with nginx-rtmp-module
|
||||
> NGINX_ADD_MODULES="/path/to/nginx-rtmp-module" emerge -va nginx
|
||||
|
||||
Replace `/path/to/` with the actual module's source path.
|
||||
You can add with this method any number of custom modules.
|
||||
|
||||
To make this change permanent see:
|
||||
http://wiki.gentoo.org/wiki/Knowledge_Base:Overriding_environment_variables_per_package
|
||||
|
||||
## Configure nginx
|
||||
Don't forget to include a rtmp section inside your nginx configuration file located at `/etc/nginx/nginx.conf`.
|
||||
|
||||
See:
|
||||
* [Getting started](getting_started.md) We already have done _Download, build and install_ Gentoo style ;-)
|
||||
* [More Examples](examples.md)
|
||||
* [Reference of all directives](directives.md)
|
30
doc/installing_ubuntu_using_ppas.md
Normal file
30
doc/installing_ubuntu_using_ppas.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Installing on Ubuntu using PPAs
|
||||
```sh
|
||||
$ sudo apt-get install dpkg-dev
|
||||
$ sudo apt-get source nginx
|
||||
$ cd /usr/src/nginx
|
||||
$ sudo git clone https://github.com/arut/nginx-rtmp-module.git
|
||||
$ cd nginx-[version-number]
|
||||
$ sudo vi debian/rules
|
||||
```
|
||||
|
||||
Edit the rules and at then end of the add-modules configuration string add
|
||||
```sh
|
||||
--add-module=/usr/src/nginx/nginx-rtmp-module \
|
||||
```
|
||||
|
||||
If installing for the first time build nginx dependancies.
|
||||
```sh
|
||||
$ sudo apt-get build-dep nginx
|
||||
$ dpkg-buildpackage -b
|
||||
```
|
||||
|
||||
(wait for a while while it builds... a really long while... like you might want to go grab a meal)
|
||||
|
||||
```sh
|
||||
$ cd .. && sudo dpkg --install nginx-common_1.3.13-1chl1~quantal1_all.deb nginx-full_1.3.13-1chl1~quantal1_amd64.deb
|
||||
$ sudo service nginx status
|
||||
$ sudo service nginx start (if nginx isn't running)
|
||||
```
|
||||
|
||||
[Source](http://serverfault.com/questions/227480/installing-optional-nginx-modules-with-apt-get)
|
114
doc/tutorial.md
Normal file
114
doc/tutorial.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Tutorial
|
||||
|
||||
[This article is not finished yet]
|
||||
|
||||
## RTMP
|
||||
RTMP is a proprietary protocol developed by Adobe (Macromedia) for use
|
||||
in flash player. Until 2009 it had no public specification.
|
||||
A number of third-party RTMP-related products started in that period
|
||||
were based on the results of reverse engineering. In 2009
|
||||
[RTMP specification](http://www.adobe.com/devnet/rtmp.html) has been
|
||||
published which made developing such applications easier. However
|
||||
the spec is not full and misses significant issues concerning streaming H264.
|
||||
|
||||
## System requirements
|
||||
The module has been tested on Linux x86-family platforms.
|
||||
However it should work on FreeBSD too.
|
||||
|
||||
## Licence
|
||||
The module is distributed under BSD license.
|
||||
|
||||
## Building NGINX with the module
|
||||
Building is pretty obvious. Just cd to nginx source directory
|
||||
and configure nginx this way:
|
||||
```sh
|
||||
$ ./configure --add-module=/path/to/nginx-rtmp-module
|
||||
```
|
||||
|
||||
Then `make` and `make install`.
|
||||
|
||||
## Configuration
|
||||
|
||||
## Simple live application
|
||||
Simple live application configuration:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
You can add access list control:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
allow play all;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And you can add record support for live streams:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
allow play all;
|
||||
|
||||
record all;
|
||||
record_path /path/to/record/dir;
|
||||
record_max_size 100M;
|
||||
record_unique off;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## HLS (HTTP Live Streaming)
|
||||
|
||||
## Choosing flash player
|
||||
To watch RTMP stream in browser one should either develop
|
||||
flash application for that or use one of available flash
|
||||
players. The most popular players which are proved to have
|
||||
no problems with the module are:
|
||||
|
||||
* [JWPlayer](http://www.longtailvideo.com/)
|
||||
* [FlowPlayer](http://flowplayer.org/)
|
||||
* [Strobe Media Playback](http://www.osmf.org/strobe_mediaplayback.html)
|
||||
* [Clappr](https://github.com/globocom/clappr)
|
||||
|
||||
Old versions of JWPlayer (<=4.4) supported capturing video
|
||||
from webcam. You can find that version in test/ subdirectory.
|
||||
However audio is not captured by this version of player.
|
||||
Recent free versions of JWPlayer have no capture capability at
|
||||
all.
|
||||
|
||||
## Transcoding streams
|
||||
You can use exec directive and ffmpeg for transcoding streams. For example:
|
||||
```sh
|
||||
application big {
|
||||
live on;
|
||||
exec /usr/bin/ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 -f flv rtmp://localhost:1935/small/${name};
|
||||
}
|
||||
application small {
|
||||
live on;
|
||||
}
|
||||
```
|
||||
|
||||
## Video on demand
|
||||
|
||||
## Distributed streaming
|
||||
|
||||
## Notifications & access control
|
||||
|
||||
## Statistics
|
||||
|
||||
## Verifying session
|
||||
|
||||
## Utilizing multi-core CPUs
|
|
@ -16,6 +16,7 @@ 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_rtmp_playlist_pt next_playlist;
|
||||
|
||||
|
||||
static char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
|
@ -29,16 +30,21 @@ 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
|
||||
/* Big buffer for 8k (QHD) cameras */
|
||||
#ifndef NGX_RTMP_HLS_BUFSIZE
|
||||
#define NGX_RTMP_HLS_BUFSIZE (16*1024*1024)
|
||||
#endif
|
||||
/* Allow access to www-data (web-server) and others too */
|
||||
#define NGX_RTMP_HLS_DIR_ACCESS 0755
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t id;
|
||||
uint64_t key_id;
|
||||
ngx_str_t *datetime;
|
||||
double duration;
|
||||
unsigned active:1;
|
||||
unsigned discont:1; /* before */
|
||||
u_char active; /* small int, 0/1 */
|
||||
u_char discont; /* small int, 0/1 */
|
||||
} ngx_rtmp_hls_frag_t;
|
||||
|
||||
|
||||
|
@ -49,7 +55,7 @@ typedef struct {
|
|||
|
||||
|
||||
typedef struct {
|
||||
unsigned opened:1;
|
||||
u_char opened; /* small int, 0/1 */
|
||||
|
||||
ngx_rtmp_mpegts_file_t file;
|
||||
|
||||
|
@ -101,12 +107,14 @@ typedef struct {
|
|||
ngx_flag_t nested;
|
||||
ngx_str_t path;
|
||||
ngx_uint_t naming;
|
||||
ngx_uint_t datetime;
|
||||
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_uint_t allow_client_cache;
|
||||
ngx_array_t *variant;
|
||||
ngx_str_t base_url;
|
||||
ngx_int_t granularity;
|
||||
|
@ -122,6 +130,11 @@ typedef struct {
|
|||
#define NGX_RTMP_HLS_NAMING_SYSTEM 3
|
||||
|
||||
|
||||
#define NGX_RTMP_HLS_DATETIME_NONE 1
|
||||
#define NGX_RTMP_HLS_DATETIME_SYSTEM 2
|
||||
#define NGX_RTMP_HLS_DATETIME_TIMESTAMP 3
|
||||
|
||||
|
||||
#define NGX_RTMP_HLS_SLICING_PLAIN 1
|
||||
#define NGX_RTMP_HLS_SLICING_ALIGNED 2
|
||||
|
||||
|
@ -129,6 +142,9 @@ typedef struct {
|
|||
#define NGX_RTMP_HLS_TYPE_LIVE 1
|
||||
#define NGX_RTMP_HLS_TYPE_EVENT 2
|
||||
|
||||
#define NGX_RTMP_HLS_CACHE_DISABLED 1
|
||||
#define NGX_RTMP_HLS_CACHE_ENABLED 2
|
||||
|
||||
|
||||
static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = {
|
||||
{ ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL },
|
||||
|
@ -138,6 +154,14 @@ static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = {
|
|||
};
|
||||
|
||||
|
||||
static ngx_conf_enum_t ngx_rtmp_hls_datetime_slots[] = {
|
||||
{ ngx_string("none"), NGX_RTMP_HLS_DATETIME_NONE },
|
||||
{ ngx_string("system"), NGX_RTMP_HLS_DATETIME_SYSTEM },
|
||||
{ ngx_string("timestamp"), NGX_RTMP_HLS_DATETIME_TIMESTAMP },
|
||||
{ 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 },
|
||||
|
@ -151,6 +175,11 @@ static ngx_conf_enum_t ngx_rtmp_hls_type_slots[] = {
|
|||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
static ngx_conf_enum_t ngx_rtmp_hls_cache[] = {
|
||||
{ ngx_string("enabled"), NGX_RTMP_HLS_CACHE_ENABLED },
|
||||
{ ngx_string("disabled"), NGX_RTMP_HLS_CACHE_DISABLED },
|
||||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
static ngx_command_t ngx_rtmp_hls_commands[] = {
|
||||
|
||||
|
@ -224,6 +253,13 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
|
|||
offsetof(ngx_rtmp_hls_app_conf_t, naming),
|
||||
&ngx_rtmp_hls_naming_slots },
|
||||
|
||||
{ ngx_string("hls_datetime"),
|
||||
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, datetime),
|
||||
&ngx_rtmp_hls_datetime_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,
|
||||
|
@ -259,6 +295,13 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
|
|||
offsetof(ngx_rtmp_hls_app_conf_t, cleanup),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("hls_allow_client_cache"),
|
||||
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, allow_client_cache),
|
||||
&ngx_rtmp_hls_cache },
|
||||
|
||||
{ ngx_string("hls_variant"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,
|
||||
ngx_rtmp_hls_variant,
|
||||
|
@ -400,6 +443,8 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s)
|
|||
ngx_rtmp_hls_variant_t *var;
|
||||
ngx_rtmp_hls_app_conf_t *hacf;
|
||||
|
||||
ngx_rtmp_playlist_t v;
|
||||
|
||||
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
|
||||
|
||||
|
@ -432,7 +477,7 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s)
|
|||
p = buffer;
|
||||
last = buffer + sizeof(buffer);
|
||||
|
||||
p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1");
|
||||
p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1,CLOSED-CAPTIONS=NONE");
|
||||
|
||||
arg = var->args.elts;
|
||||
for (k = 0; k < var->args.nelts; k++, arg++) {
|
||||
|
@ -475,12 +520,16 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s)
|
|||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
ngx_str_set(&(v.module), "hls");
|
||||
v.playlist.data = ctx->playlist.data;
|
||||
v.playlist.len = ctx->playlist.len;
|
||||
return next_playlist(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
||||
ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s, int final)
|
||||
{
|
||||
static u_char buffer[1024];
|
||||
ngx_fd_t fd;
|
||||
|
@ -489,11 +538,17 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
ssize_t n;
|
||||
ngx_rtmp_hls_app_conf_t *hacf;
|
||||
ngx_rtmp_hls_frag_t *f;
|
||||
ngx_uint_t i, max_frag;
|
||||
ngx_int_t i, start_i;
|
||||
ngx_uint_t max_frag;
|
||||
double fragments_length;
|
||||
ngx_str_t name_part, key_name_part;
|
||||
uint64_t prev_key_id;
|
||||
const char *sep, *key_sep;
|
||||
|
||||
ngx_rtmp_playlist_t v;
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"hls: write playlist");
|
||||
|
||||
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
|
||||
|
@ -510,7 +565,32 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
|
||||
max_frag = hacf->fraglen / 1000;
|
||||
|
||||
for (i = 0; i < ctx->nfrags; i++) {
|
||||
/**
|
||||
* Need to check fragments length sum and playlist max length
|
||||
* Do backward search
|
||||
*/
|
||||
start_i = 0;
|
||||
fragments_length = 0.;
|
||||
for (i = ctx->nfrags-1; i >= 0; i--) {
|
||||
f = ngx_rtmp_hls_get_frag(s, i);
|
||||
if (f->duration) {
|
||||
fragments_length += f->duration;
|
||||
}
|
||||
/**
|
||||
* Think that sum of frag length is more than playlist desired length - half minimal frag length
|
||||
* XXX: sometimes sum of frag lengths are almost playlist length
|
||||
* but key-frames come at random rate...
|
||||
*/
|
||||
if (fragments_length >= hacf->playlen/1000. - max_frag/2) {
|
||||
start_i = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"hls: found starting fragment=%i", start_i);
|
||||
|
||||
for (i = start_i; i < (ngx_int_t)ctx->nfrags; i++) {
|
||||
f = ngx_rtmp_hls_get_frag(s, i);
|
||||
if (f->duration > max_frag) {
|
||||
max_frag = (ngx_uint_t) (f->duration + .5);
|
||||
|
@ -525,19 +605,21 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%uL\n"
|
||||
"#EXT-X-TARGETDURATION:%ui\n",
|
||||
ctx->frag, max_frag);
|
||||
ctx->frag + start_i, max_frag);
|
||||
|
||||
if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) {
|
||||
p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n");
|
||||
p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE:EVENT\n");
|
||||
}
|
||||
|
||||
if (hacf->allow_client_cache == NGX_RTMP_HLS_CACHE_ENABLED) {
|
||||
p = ngx_slprintf(p, end, "#EXT-X-ALLOW-CACHE:YES\n");
|
||||
} else if (hacf->allow_client_cache == NGX_RTMP_HLS_CACHE_DISABLED) {
|
||||
p = ngx_slprintf(p, end, "#EXT-X-ALLOW-CACHE:NO\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;
|
||||
goto write_err;
|
||||
}
|
||||
|
||||
sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-";
|
||||
|
@ -555,8 +637,23 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
|
||||
prev_key_id = 0;
|
||||
|
||||
for (i = 0; i < ctx->nfrags; i++) {
|
||||
for (i = start_i; i < (ngx_int_t)ctx->nfrags; i++) {
|
||||
f = ngx_rtmp_hls_get_frag(s, i);
|
||||
if ((i == start_i || f->discont) && f->datetime && f->datetime->len > 0) {
|
||||
p = ngx_snprintf(buffer, sizeof(buffer), "#EXT-X-PROGRAM-DATE-TIME:");
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
if (n < 0) {
|
||||
goto write_err;
|
||||
}
|
||||
n = ngx_write_fd(fd, f->datetime->data, f->datetime->len);
|
||||
if (n < 0) {
|
||||
goto write_err;
|
||||
}
|
||||
n = ngx_write_fd(fd, "\n", 1);
|
||||
if (n < 0) {
|
||||
goto write_err;
|
||||
}
|
||||
}
|
||||
|
||||
p = buffer;
|
||||
end = p + sizeof(buffer);
|
||||
|
@ -582,15 +679,20 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
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);
|
||||
ctx->frag, i + 1, ctx->nfrags, f->duration, (ngx_int_t)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;
|
||||
goto write_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (final)
|
||||
{
|
||||
p = ngx_slprintf(p, end, "#EXT-X-ENDLIST\n");
|
||||
n = ngx_write_fd(fd, buffer, p - buffer);
|
||||
if (n < 0) {
|
||||
goto write_err;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -609,7 +711,18 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
|
|||
return ngx_rtmp_hls_write_variant_playlist(s);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
ngx_str_set(&(v.module), "hls");
|
||||
v.playlist.data = ctx->playlist.data;
|
||||
v.playlist.len = ctx->playlist.len;
|
||||
return next_playlist(s, &v);
|
||||
|
||||
write_err:
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -815,8 +928,55 @@ ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts)
|
|||
}
|
||||
|
||||
|
||||
static ngx_str_t *
|
||||
ngx_rtmp_hls_get_fragment_datetime(ngx_rtmp_session_t *s, uint64_t ts)
|
||||
{
|
||||
ngx_rtmp_hls_app_conf_t *hacf;
|
||||
ngx_str_t *datetime;
|
||||
ngx_tm_t tm;
|
||||
uint64_t msec;
|
||||
|
||||
datetime = (ngx_str_t *) ngx_pcalloc(s->connection->pool, sizeof(ngx_str_t));
|
||||
datetime->data = NULL;
|
||||
datetime->len = 0;
|
||||
|
||||
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
|
||||
|
||||
switch (hacf->datetime) {
|
||||
|
||||
case NGX_RTMP_HLS_DATETIME_TIMESTAMP:
|
||||
/* Timestamps in RTMP are given as an integer number of milliseconds
|
||||
* relative to an unspecified epoch, so we clear the last 32 bits
|
||||
* from system time, and add the timestamp from RTMP. */
|
||||
msec = ngx_cached_time->sec * 1000 + ngx_cached_time->msec;
|
||||
msec /= 4294967296; //2**32
|
||||
msec *= 4294967296;
|
||||
msec += (ts / 90);
|
||||
ngx_gmtime(msec / 1000, &tm);
|
||||
|
||||
datetime->len = sizeof("1970-01-01T00:00:00.000-00:00") - 1;
|
||||
datetime->data = (u_char *) ngx_pcalloc(s->connection->pool, datetime->len * sizeof(u_char));
|
||||
(void) ngx_sprintf(datetime->data, "%4d-%02d-%02dT%02d:%02d:%02d.%03d-00:00",
|
||||
tm.ngx_tm_year, tm.ngx_tm_mon,
|
||||
tm.ngx_tm_mday, tm.ngx_tm_hour,
|
||||
tm.ngx_tm_min, tm.ngx_tm_sec,
|
||||
msec % 1000);
|
||||
return datetime;
|
||||
|
||||
case NGX_RTMP_HLS_DATETIME_SYSTEM:
|
||||
datetime->data = (u_char *) ngx_pcalloc(s->connection->pool, ngx_cached_http_log_iso8601.len * sizeof(u_char));
|
||||
ngx_memcpy(datetime->data, ngx_cached_http_log_iso8601.data, ngx_cached_http_log_iso8601.len);
|
||||
datetime->len = ngx_cached_http_log_iso8601.len;
|
||||
return datetime;
|
||||
|
||||
default: /* NGX_RTMP_HLS_DATETIME_NONE */
|
||||
return datetime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)
|
||||
ngx_rtmp_hls_close_final_fragment(ngx_rtmp_session_t *s, int final)
|
||||
{
|
||||
ngx_rtmp_hls_ctx_t *ctx;
|
||||
|
||||
|
@ -834,20 +994,29 @@ ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)
|
|||
|
||||
ngx_rtmp_hls_next_frag(s);
|
||||
|
||||
ngx_rtmp_hls_write_playlist(s);
|
||||
ngx_rtmp_hls_write_playlist(s, final);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)
|
||||
{
|
||||
return ngx_rtmp_hls_close_final_fragment(s, 0);
|
||||
}
|
||||
|
||||
|
||||
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_str_t *datetime;
|
||||
ngx_uint_t g, mpegts_cc;
|
||||
ngx_rtmp_hls_ctx_t *ctx;
|
||||
ngx_rtmp_codec_ctx_t *codec_ctx;
|
||||
ngx_rtmp_hls_frag_t *f;
|
||||
ngx_rtmp_hls_app_conf_t *hacf;
|
||||
|
||||
|
@ -870,6 +1039,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
|
|||
}
|
||||
|
||||
id = ngx_rtmp_hls_get_fragment_id(s, ts);
|
||||
datetime = ngx_rtmp_hls_get_fragment_datetime(s, ts);
|
||||
|
||||
if (hacf->granularity) {
|
||||
g = (ngx_uint_t) hacf->granularity;
|
||||
|
@ -927,12 +1097,15 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
|
|||
}
|
||||
}
|
||||
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
// This is continuity counter for TS header
|
||||
mpegts_cc = (ngx_uint_t)(ctx->nfrags + ctx->frag);
|
||||
|
||||
ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"hls: open fragment file='%s', keyfile='%s', "
|
||||
"frag=%uL, n=%ui, time=%uL, discont=%i",
|
||||
"frag=%uL, n=%ui, time=%uL, discont=%i, tscc=%ui",
|
||||
ctx->stream.data,
|
||||
ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "",
|
||||
ctx->frag, ctx->nfrags, ts, discont);
|
||||
ctx->frag, ctx->nfrags, ts, discont, mpegts_cc);
|
||||
|
||||
if (hacf->keys &&
|
||||
ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id)
|
||||
|
@ -943,8 +1116,10 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
|
|||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
||||
|
||||
if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data,
|
||||
s->connection->log)
|
||||
s->connection->log, codec_ctx, mpegts_cc)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
|
@ -960,6 +1135,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
|
|||
f->discont = discont;
|
||||
f->id = id;
|
||||
f->key_id = ctx->key_id;
|
||||
f->datetime = datetime;
|
||||
|
||||
ctx->frag_ts = ts;
|
||||
|
||||
|
@ -1291,7 +1467,7 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
|||
goto next;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"hls: publish: name='%s' type='%s'",
|
||||
v->name, v->type);
|
||||
|
||||
|
@ -1491,7 +1667,7 @@ ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
|
|||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"hls: close stream");
|
||||
|
||||
ngx_rtmp_hls_close_fragment(s);
|
||||
ngx_rtmp_hls_close_final_fragment(s, 1);
|
||||
|
||||
next:
|
||||
return next_close_stream(s, v);
|
||||
|
@ -1570,6 +1746,9 @@ ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts,
|
|||
ngx_buf_t *b;
|
||||
int64_t d;
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"hls: update fragment");
|
||||
|
||||
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;
|
||||
|
@ -1687,7 +1866,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
size_t bsize;
|
||||
ngx_buf_t *b;
|
||||
u_char *p;
|
||||
ngx_uint_t objtype, srindex, chconf, size;
|
||||
ngx_uint_t objtype, srindex, chconf, size, samples_per_frame;
|
||||
|
||||
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
|
||||
|
||||
|
@ -1701,8 +1880,14 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC ||
|
||||
codec_ctx->aac_header == NULL || ngx_rtmp_is_codec_header(in))
|
||||
if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC &&
|
||||
codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_MP3)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -1727,7 +1912,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
b->pos = b->last = b->start;
|
||||
}
|
||||
|
||||
size = h->mlen - 2 + 7;
|
||||
size = codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3 ? h->mlen - 1 : h->mlen - 2 + 7;
|
||||
pts = (uint64_t) h->timestamp * 90;
|
||||
|
||||
if (b->start + size > b->end) {
|
||||
|
@ -1751,14 +1936,22 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
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;
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
|
||||
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;
|
||||
}
|
||||
b->last += 5;
|
||||
}
|
||||
else {
|
||||
/* For some reason the pointer is already incremented past the rest
|
||||
of the RTMP frame header. I'm not sure where in the code this is
|
||||
being done. Regardless, there's an extra byte that needs to be skipped
|
||||
for MP3. */
|
||||
in->buf->pos += 1;
|
||||
}
|
||||
|
||||
/* copy payload */
|
||||
|
||||
|
@ -1774,28 +1967,30 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
|
||||
/* 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;
|
||||
}
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
|
||||
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 */
|
||||
/* 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;
|
||||
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;
|
||||
if (p != b->start) {
|
||||
ctx->aframe_num++;
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->aframe_pts = pts;
|
||||
|
@ -1809,7 +2004,9 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
/* 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 /
|
||||
samples_per_frame = codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ? 1024 : 1152;
|
||||
|
||||
est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * samples_per_frame /
|
||||
codec_ctx->sample_rate;
|
||||
dpts = (int64_t) (est_pts - pts);
|
||||
|
||||
|
@ -1822,6 +2019,10 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
{
|
||||
ctx->aframe_num++;
|
||||
ctx->aframe_pts = est_pts;
|
||||
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3) {
|
||||
ngx_rtmp_hls_flush_audio(s);
|
||||
}
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
@ -1951,6 +2152,7 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"hls: error appending AUD NAL");
|
||||
}
|
||||
/* fall through */
|
||||
case 9:
|
||||
aud_sent = 1;
|
||||
break;
|
||||
|
@ -2056,6 +2258,9 @@ ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
|
|||
static ngx_int_t
|
||||
ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"hls: stream eof");
|
||||
|
||||
ngx_rtmp_hls_flush_audio(s);
|
||||
|
||||
ngx_rtmp_hls_close_fragment(s);
|
||||
|
@ -2174,7 +2379,7 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
|
|||
name.data[name.len - 2] == 'u' &&
|
||||
name.data[name.len - 1] == '8')
|
||||
{
|
||||
max_age = playlen / 1000;
|
||||
max_age = playlen / 500;
|
||||
|
||||
} else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
|
||||
name.data[name.len - 3] == 'k' &&
|
||||
|
@ -2209,15 +2414,23 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#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);
|
||||
|
||||
return cleanup->playlen / 500;
|
||||
// Next callback in half of playlist length time
|
||||
#if (nginx_version >= 1011005)
|
||||
return cleanup->playlen / 2;
|
||||
#else
|
||||
return cleanup->playlen / 2000;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -2273,6 +2486,13 @@ ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_hls_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v)
|
||||
{
|
||||
return next_playlist(s, v);
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
|
||||
{
|
||||
|
@ -2292,11 +2512,13 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
|
|||
conf->continuous = NGX_CONF_UNSET;
|
||||
conf->nested = NGX_CONF_UNSET;
|
||||
conf->naming = NGX_CONF_UNSET_UINT;
|
||||
conf->datetime = 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->allow_client_cache = NGX_CONF_UNSET_UINT;
|
||||
conf->granularity = NGX_CONF_UNSET;
|
||||
conf->keys = NGX_CONF_UNSET;
|
||||
conf->frags_per_key = NGX_CONF_UNSET_UINT;
|
||||
|
@ -2315,7 +2537,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
ngx_conf_merge_value(conf->hls, prev->hls, 0);
|
||||
ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);
|
||||
ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen,
|
||||
conf->fraglen * 10);
|
||||
conf->fraglen * 2);
|
||||
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);
|
||||
|
@ -2323,6 +2545,8 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
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->datetime, prev->datetime,
|
||||
NGX_RTMP_HLS_DATETIME_NONE);
|
||||
ngx_conf_merge_uint_value(conf->slicing, prev->slicing,
|
||||
NGX_RTMP_HLS_SLICING_PLAIN);
|
||||
ngx_conf_merge_uint_value(conf->type, prev->type,
|
||||
|
@ -2446,5 +2670,8 @@ ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf)
|
|||
next_stream_eof = ngx_rtmp_stream_eof;
|
||||
ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof;
|
||||
|
||||
next_playlist = ngx_rtmp_playlist;
|
||||
ngx_rtmp_playlist = ngx_rtmp_hls_playlist;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
|
@ -7,18 +7,30 @@
|
|||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_mpegts.h"
|
||||
#include "ngx_rtmp_mpegts_crc.h"
|
||||
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header[] = {
|
||||
|
||||
/* TS */
|
||||
0x47, 0x40, 0x00, 0x10, 0x00,
|
||||
/* PSI */
|
||||
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
||||
/* https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet */
|
||||
|
||||
/* TS Header */
|
||||
0x47, // Sync byte
|
||||
0x40, 0x00, // TEI(1) + PUS(1) + TP(1) + PID(13)
|
||||
0x10, // TSC(2) + AFF(1) + PF(1) + CC(4)
|
||||
0x00, // adaption_field_length(8)
|
||||
|
||||
/* PAT */
|
||||
0x00, 0x01, 0xf0, 0x01,
|
||||
/* CRC */
|
||||
0x2e, 0x70, 0x19, 0x05,
|
||||
0x00, // table_id(8)
|
||||
0xb0, 0x0d, // 1011b(4) + section_length(12)
|
||||
0x00, 0x01, // transport_stream_id(16)
|
||||
0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8)
|
||||
/* PAT program loop */
|
||||
0x00, 0x01, 0xef, 0xff, // program_number(16), reserved(3) + program_map_pid(13)
|
||||
/* PAT crc (CRC-32-MPEG2) */
|
||||
0x36, 0x90, 0xe2, 0x3d, // !!! Needs to be recalculated each time any bit in PAT is modified (which we dont do at the moment) !!!
|
||||
|
||||
/* stuffing 167 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
@ -38,19 +50,27 @@ static u_char ngx_rtmp_mpegts_header[] = {
|
|||
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,
|
||||
/* TS Header */
|
||||
0x47, // Sync byte
|
||||
0x4f, 0xff, // TEI(1) + PUS(1) + TP(1) + PID(13)
|
||||
0x10, // TSC(2) + AFF(1) + PF(1) + CC(4)
|
||||
0x00, // adaption_field_length(8)
|
||||
|
||||
/* 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 */
|
||||
0x02, // table_id(8)
|
||||
0xb0, 0x12, // 1011b(4) + section_length(12) (section length set below. Ignore this value in here)
|
||||
0x00, 0x01, // program_number(16)
|
||||
0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8)
|
||||
0xe1, 0x00, // reserved(3) + PCR_PID(13)
|
||||
0xf0, 0x00, // reserved(4) + program_info_length(12)
|
||||
|
||||
/* PMT component loop, looped through when writing header */
|
||||
/* Max size of 14 bytes */
|
||||
/* Also includes the PMT CRC, calculated dynamically */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
/* stuffing 157 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
@ -70,6 +90,32 @@ static u_char ngx_rtmp_mpegts_header[] = {
|
|||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_h264[] = {
|
||||
//H.264 Video, PID 0x100
|
||||
0x1b, // stream_type(8)
|
||||
0xe1, 0x00, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_mp3[] = {
|
||||
//MP3 Audio, PID 0x101
|
||||
0x03, // stream_type(8)
|
||||
0xe1, 0x01, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_aac[] = {
|
||||
//ADTS AAC Audio, PID 0x101
|
||||
0x0f, // stream_type(8)
|
||||
0xe1, 0x01, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET 193
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH 12
|
||||
#define NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET 195
|
||||
#define NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET 205
|
||||
#define NGX_RTMP_MPEGTS_PID_SIZE 5
|
||||
|
||||
/* 700 ms PCR delay */
|
||||
#define NGX_RTMP_HLS_DELAY 63000
|
||||
|
@ -155,10 +201,53 @@ ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,
|
|||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file)
|
||||
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc)
|
||||
{
|
||||
return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header,
|
||||
sizeof(ngx_rtmp_mpegts_header));
|
||||
ngx_int_t next_pid_offset = 0; //Used to track the number of PIDs we have and the offset in 5-byte chunks
|
||||
|
||||
//MPEG-TS CC is 4 bits long. Need to truncate it here.
|
||||
mpegts_cc %= 0x0f;
|
||||
// And then put it in the headers
|
||||
ngx_rtmp_mpegts_header[3] = (ngx_rtmp_mpegts_header[3] & 0xf0) + (u_char)mpegts_cc;
|
||||
ngx_rtmp_mpegts_header[191] = (ngx_rtmp_mpegts_header[191] & 0xf0) + (u_char)mpegts_cc;
|
||||
|
||||
//ngx_rtmp_mpegts_header
|
||||
|
||||
if (codec_ctx->video_codec_id)
|
||||
{
|
||||
//Put h264 PID in the PMT
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_h264, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
|
||||
next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE;
|
||||
}
|
||||
|
||||
if (codec_ctx->audio_codec_id){
|
||||
//Put Audio PID in the PMT
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_aac, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_mp3, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
}
|
||||
next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE;
|
||||
}
|
||||
|
||||
//Set section length of PMT
|
||||
//PMT is 13 bytes long without any programs in it. Add this in
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET] = 13 + next_pid_offset;
|
||||
|
||||
//Calculate CRC
|
||||
ngx_rtmp_mpegts_crc_t crc = ngx_rtmp_mpegts_crc_init();
|
||||
crc = ngx_rtmp_mpegts_crc_update(crc, ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET, NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH+next_pid_offset);
|
||||
crc = ngx_rtmp_mpegts_crc_finalize(crc);
|
||||
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset] = (crc >> 24) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+1] = (crc >> 16) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+2] = (crc >> 8) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+3] = crc & 0xff;
|
||||
|
||||
return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, sizeof(ngx_rtmp_mpegts_header));
|
||||
}
|
||||
|
||||
|
||||
|
@ -229,14 +318,12 @@ ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
|
|||
|
||||
if (first) {
|
||||
|
||||
if (f->key) {
|
||||
packet[3] |= 0x20; /* adaptation */
|
||||
packet[3] |= 0x20; /* adaptation */
|
||||
|
||||
*p++ = 7; /* size */
|
||||
*p++ = 0x50; /* random access + PCR */
|
||||
*p++ = 7; /* size */
|
||||
*p++ = 0x50; /* random access + PCR */
|
||||
|
||||
p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);
|
||||
}
|
||||
p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);
|
||||
|
||||
/* PES header */
|
||||
|
||||
|
@ -350,7 +437,7 @@ ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
|
|||
|
||||
ngx_int_t
|
||||
ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
|
||||
ngx_log_t *log)
|
||||
ngx_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc)
|
||||
{
|
||||
file->log = log;
|
||||
|
||||
|
@ -365,7 +452,7 @@ ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
|
|||
|
||||
file->size = 0;
|
||||
|
||||
if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) {
|
||||
if (ngx_rtmp_mpegts_write_header(file, codec_ctx, mpegts_cc) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
|
||||
"hls: error writing fragment header");
|
||||
ngx_close_file(file->fd);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <ngx_core.h>
|
||||
#include <openssl/aes.h>
|
||||
|
||||
#include <ngx_rtmp_codec_module.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_fd_t fd;
|
||||
|
@ -37,7 +39,7 @@ typedef struct {
|
|||
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_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc);
|
||||
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);
|
||||
|
|
80
hls/ngx_rtmp_mpegts_crc.c
Normal file
80
hls/ngx_rtmp_mpegts_crc.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* \file crc.c
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:31 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#include "ngx_rtmp_mpegts_crc.h" /* include the header file generated with pycrc */
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Static table used for the table_driven implementation.
|
||||
*****************************************************************************/
|
||||
static const ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_table[256] = {
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len)
|
||||
{
|
||||
const unsigned char *d = (const unsigned char *)data;
|
||||
unsigned int tbl_idx;
|
||||
|
||||
while (data_len--) {
|
||||
tbl_idx = ((crc >> 24) ^ *d) & 0xff;
|
||||
crc = (ngx_rtmp_mpegts_crc_table[tbl_idx] ^ (crc << 8)) & 0xffffffff;
|
||||
|
||||
d++;
|
||||
}
|
||||
return crc & 0xffffffff;
|
||||
}
|
||||
|
||||
|
83
hls/ngx_rtmp_mpegts_crc.h
Normal file
83
hls/ngx_rtmp_mpegts_crc.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* \file crc.h
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:22 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#ifndef _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
#define _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* The definition of the used algorithm.
|
||||
*
|
||||
* This is not used anywhere in the generated code, but it may be used by the
|
||||
* application code to call algoritm-specific code, is desired.
|
||||
*****************************************************************************/
|
||||
#define CRC_ALGO_TABLE_DRIVEN 1
|
||||
|
||||
|
||||
/**
|
||||
* The type of the CRC values.
|
||||
*
|
||||
* This type must be big enough to contain at least 32 bits.
|
||||
*****************************************************************************/
|
||||
typedef uint_fast32_t ngx_rtmp_mpegts_crc_t;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the initial crc value.
|
||||
*
|
||||
* \return The initial crc value.
|
||||
*****************************************************************************/
|
||||
static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_init(void)
|
||||
{
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len);
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the final crc value.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \return The final crc value.
|
||||
*****************************************************************************/
|
||||
static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_finalize(ngx_rtmp_mpegts_crc_t crc)
|
||||
{
|
||||
return crc ^ 0x00000000;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* closing brace for extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ */
|
43
ngx_rtmp.c
43
ngx_rtmp.c
|
@ -87,6 +87,7 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
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;
|
||||
|
@ -101,14 +102,18 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
|
||||
|
||||
/* count the number of the rtmp modules and set up their indices */
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
ngx_rtmp_max_module = 0;
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
|
||||
modules[m]->ctx_index = ngx_rtmp_max_module++;
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,13 +153,13 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
* of the all rtmp modules
|
||||
*/
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
mi = ngx_modules[m]->ctx_index;
|
||||
module = modules[m]->ctx;
|
||||
mi = modules[m]->ctx_index;
|
||||
|
||||
if (module->create_main_conf) {
|
||||
ctx->main_conf[mi] = module->create_main_conf(cf);
|
||||
|
@ -181,12 +186,12 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
pcf = *cf;
|
||||
cf->ctx = ctx;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->preconfiguration) {
|
||||
if (module->preconfiguration(cf) != NGX_OK) {
|
||||
|
@ -212,13 +217,13 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
|
||||
cscfp = cmcf->servers.elts;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
mi = ngx_modules[m]->ctx_index;
|
||||
module = modules[m]->ctx;
|
||||
mi = modules[m]->ctx_index;
|
||||
|
||||
/* init rtmp{} main_conf's */
|
||||
|
||||
|
@ -283,12 +288,12 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->postconfiguration) {
|
||||
if (module->postconfiguration(cf) != NGX_OK) {
|
||||
|
@ -840,7 +845,7 @@ static ngx_int_t
|
|||
ngx_rtmp_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (nginx_version >= 1007005)
|
||||
ngx_queue_init(&ngx_rtmp_init_queue);
|
||||
ngx_queue_init((ngx_queue_t*) &ngx_rtmp_init_queue);
|
||||
#endif
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
13
ngx_rtmp.h
13
ngx_rtmp.h
|
@ -60,16 +60,16 @@ typedef struct {
|
|||
} ngx_rtmp_addr_conf_t;
|
||||
|
||||
typedef struct {
|
||||
in_addr_t addr;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
in_addr_t addr;
|
||||
} ngx_rtmp_in_addr_t;
|
||||
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
typedef struct {
|
||||
struct in6_addr addr6;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
struct in6_addr addr6;
|
||||
} ngx_rtmp_in6_addr_t;
|
||||
|
||||
#endif
|
||||
|
@ -231,6 +231,9 @@ typedef struct {
|
|||
ngx_msec_t base_time;
|
||||
uint32_t current_time;
|
||||
|
||||
/* ready for publishing? */
|
||||
unsigned ready_for_publish:1;
|
||||
|
||||
/* ping */
|
||||
ngx_event_t ping_evt;
|
||||
unsigned ping_active:1;
|
||||
|
@ -580,6 +583,12 @@ ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code,
|
|||
ngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code,
|
||||
char* level, ngx_uint_t duration, ngx_uint_t bytes);
|
||||
ngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_send_redirect_status(ngx_rtmp_session_t *s,
|
||||
char *callMethod, char *desc, ngx_str_t to_url);
|
||||
ngx_int_t ngx_rtmp_send_close_method(ngx_rtmp_session_t *s, char *methodName);
|
||||
ngx_int_t ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
ngx_int_t ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
ngx_int_t ngx_rtmp_send_fi(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
/* Frame types */
|
||||
|
|
|
@ -409,9 +409,9 @@ ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
if (!all) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* "all" passes through */
|
||||
#endif
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
|
||||
|
@ -449,10 +449,17 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play");
|
||||
|
||||
if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play: error");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play: next");
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len)
|
|||
return dst;
|
||||
}
|
||||
|
||||
#define NGX_RTMP_AMF_DEBUG_SIZE 16
|
||||
#define NGX_RTMP_AMF_DEBUG_SIZE 72
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
static void
|
||||
|
@ -328,9 +328,10 @@ ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
|||
} else {
|
||||
switch (ngx_rtmp_amf_get(ctx, &type8, 1)) {
|
||||
case NGX_DONE:
|
||||
if (elts->type & NGX_RTMP_AMF_OPTIONAL) {
|
||||
if (elts && elts->type & NGX_RTMP_AMF_OPTIONAL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
/* fall through */
|
||||
case NGX_ERROR:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
@ -398,6 +399,7 @@ ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
|||
if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case NGX_RTMP_AMF_OBJECT:
|
||||
if (ngx_rtmp_amf_read_object(ctx, data,
|
||||
|
@ -592,6 +594,7 @@ ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
|
|||
if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case NGX_RTMP_AMF_OBJECT:
|
||||
type8 = NGX_RTMP_AMF_END;
|
||||
|
|
|
@ -93,6 +93,34 @@ ngx_module_t ngx_rtmp_auto_push_module = {
|
|||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_auto_push_index_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_auto_push_index_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_auto_push_index_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-rtmp"
|
||||
|
||||
|
||||
|
@ -324,7 +352,7 @@ ngx_rtmp_auto_push_reconnect(ngx_event_t *ev)
|
|||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
@ -461,14 +489,14 @@ ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
|||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_palloc(s->connection->pool,
|
||||
sizeof(ngx_rtmp_auto_push_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_module);
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);
|
||||
|
||||
}
|
||||
ngx_memzero(ctx, sizeof(*ctx));
|
||||
|
@ -508,7 +536,7 @@ ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
|||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx) {
|
||||
if (ctx->push_evt.timer_set) {
|
||||
ngx_del_timer(&ctx->push_evt);
|
||||
|
@ -532,7 +560,7 @@ ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
|||
slot, &rctx->app, &rctx->name);
|
||||
|
||||
pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,
|
||||
ngx_rtmp_auto_push_module);
|
||||
ngx_rtmp_auto_push_index_module);
|
||||
if (pctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,
|
|||
static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_set_buflen_t *v);
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v);
|
||||
|
||||
ngx_rtmp_connect_pt ngx_rtmp_connect;
|
||||
ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
|
||||
|
@ -62,6 +63,7 @@ ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
|
|||
ngx_rtmp_recorded_pt ngx_rtmp_recorded;
|
||||
ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
|
||||
|
||||
ngx_rtmp_playlist_pt ngx_rtmp_playlist;
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf);
|
||||
|
||||
|
@ -574,6 +576,8 @@ ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"cmd: ngx_rtmp_cmd_play");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
@ -786,6 +790,14 @@ ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v)
|
|||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = {
|
||||
{ ngx_string("connect"), ngx_rtmp_cmd_connect_init },
|
||||
{ ngx_string("createStream"), ngx_rtmp_cmd_create_stream_init },
|
||||
|
@ -852,5 +864,7 @@ ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf)
|
|||
ngx_rtmp_recorded = ngx_rtmp_cmd_recorded;
|
||||
ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen;
|
||||
|
||||
ngx_rtmp_playlist = ngx_rtmp_cmd_playlist;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_MAX_NAME 256
|
||||
#define NGX_RTMP_MAX_URL 256
|
||||
#define NGX_RTMP_MAX_NAME 2048
|
||||
#define NGX_RTMP_MAX_URL 4096
|
||||
#define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ typedef struct {
|
|||
double trans;
|
||||
u_char app[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
u_char flashver[32];
|
||||
u_char flashver[64];
|
||||
u_char swf_url[NGX_RTMP_MAX_URL];
|
||||
u_char tc_url[NGX_RTMP_MAX_URL];
|
||||
double acodecs;
|
||||
|
@ -59,6 +59,12 @@ typedef struct {
|
|||
} ngx_rtmp_publish_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t playlist;
|
||||
ngx_str_t module;
|
||||
} ngx_rtmp_playlist_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
|
@ -130,6 +136,7 @@ typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s,
|
|||
typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_set_buflen_t *v);
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_playlist_pt)(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v);
|
||||
|
||||
extern ngx_rtmp_connect_pt ngx_rtmp_connect;
|
||||
extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
|
||||
|
@ -147,5 +154,6 @@ extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
|
|||
extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
|
||||
extern ngx_rtmp_recorded_pt ngx_rtmp_recorded;
|
||||
|
||||
extern ngx_rtmp_playlist_pt ngx_rtmp_playlist;
|
||||
|
||||
#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */
|
||||
|
|
|
@ -306,7 +306,7 @@ ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
|||
ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);
|
||||
|
||||
if (ctx->aac_profile == 5 || ctx->aac_profile == 29) {
|
||||
|
||||
|
||||
if (ctx->aac_profile == 29) {
|
||||
ctx->aac_ps = 1;
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
|||
5 bits: object type
|
||||
if (object type == 31)
|
||||
6 bits + 32: object type
|
||||
|
||||
|
||||
var bits: AOT Specific Config
|
||||
*/
|
||||
|
||||
|
@ -358,8 +358,10 @@ static void
|
|||
ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
||||
{
|
||||
ngx_uint_t profile_idc, width, height, crop_left, crop_right,
|
||||
crop_top, crop_bottom, frame_mbs_only, n, cf_idc,
|
||||
num_ref_frames;
|
||||
crop_top, crop_bottom, frame_mbs_only, n, cf_n, cf_idc,
|
||||
// num_ref_frames;
|
||||
num_ref_frames, sl_size, sl_index, sl_udelta;
|
||||
ngx_int_t sl_last, sl_next, sl_delta;
|
||||
ngx_rtmp_codec_ctx_t *ctx;
|
||||
ngx_rtmp_bit_reader_t br;
|
||||
|
||||
|
@ -413,7 +415,7 @@ ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
|||
{
|
||||
/* chroma format idc */
|
||||
cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);
|
||||
|
||||
|
||||
if (cf_idc == 3) {
|
||||
|
||||
/* separate color plane */
|
||||
|
@ -432,16 +434,39 @@ ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
|||
/* seq scaling matrix present */
|
||||
if (ngx_rtmp_bit_read(&br, 1)) {
|
||||
|
||||
for (n = 0; n < (cf_idc != 3 ? 8u : 12u); n++) {
|
||||
for (n = 0, cf_n = (cf_idc != 3 ? 8u : 12u); n < cf_n; n++) {
|
||||
|
||||
/* seq scaling list present */
|
||||
if (ngx_rtmp_bit_read(&br, 1)) {
|
||||
|
||||
/* TODO: scaling_list()
|
||||
/* scaling list */
|
||||
if (n < 6) {
|
||||
sl_size = 16;
|
||||
} else {
|
||||
sl_size = 64;
|
||||
}
|
||||
|
||||
sl_last = 8;
|
||||
sl_next = 8;
|
||||
|
||||
for (sl_index = 0; sl_index < sl_size; sl_index++) {
|
||||
|
||||
if (sl_next != 0) {
|
||||
|
||||
/* convert to signed: (-1)**k+1 * ceil(k/2) */
|
||||
sl_udelta = (ngx_uint_t)ngx_rtmp_bit_read_golomb(&br);
|
||||
sl_delta = (sl_udelta + 1) >> 1;
|
||||
if ((sl_udelta & 1) == 0) {
|
||||
sl_delta = -sl_delta;
|
||||
}
|
||||
|
||||
sl_next = (sl_last + sl_delta + 256) % 256;
|
||||
|
||||
if (sl_next != 0) {
|
||||
sl_last = sl_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -570,6 +595,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
|
|||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
double video_keyframe_frequency;
|
||||
double video_codec_id;
|
||||
double audio_data_rate;
|
||||
double audio_codec_id;
|
||||
|
@ -581,7 +607,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
|
|||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("Server"),
|
||||
"NGINX RTMP (github.com/arut/nginx-rtmp-module)", 0 },
|
||||
"NGINX RTMP (github.com/sergey-dryabzhinsky/nginx-rtmp-module)", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("width"),
|
||||
|
@ -615,6 +641,10 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
|
|||
ngx_string("videodatarate"),
|
||||
&v.video_data_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videokeyframe_frequency"),
|
||||
&v.video_keyframe_frequency, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videocodecid"),
|
||||
&v.video_codec_id, 0 },
|
||||
|
@ -664,6 +694,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)
|
|||
v.duration = ctx->duration;
|
||||
v.frame_rate = ctx->frame_rate;
|
||||
v.video_data_rate = ctx->video_data_rate;
|
||||
v.video_keyframe_frequency = ctx->video_keyframe_frequency;
|
||||
v.video_codec_id = ctx->video_codec_id;
|
||||
v.audio_data_rate = ctx->audio_data_rate;
|
||||
v.audio_codec_id = ctx->audio_codec_id;
|
||||
|
@ -740,6 +771,7 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
double video_keyframe_frequency;
|
||||
double video_codec_id_n;
|
||||
u_char video_codec_id_s[32];
|
||||
double audio_data_rate;
|
||||
|
@ -797,6 +829,10 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
ngx_string("videodatarate"),
|
||||
&v.video_data_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videokeyframe_frequency"),
|
||||
&v.video_keyframe_frequency, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_VARIANT,
|
||||
ngx_string("videocodecid"),
|
||||
in_video_codec_id, sizeof(in_video_codec_id) },
|
||||
|
@ -820,7 +856,8 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
/* That string is passed by FFmpeg and possibly others (librtmp). It's skipped after at #880 */
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
|
@ -839,11 +876,20 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
/* use -1 as a sign of unchanged data;
|
||||
* 0 is a valid value for uncompressed audio */
|
||||
/* use -1 as a sign of unchanged data */
|
||||
v.width = -1;
|
||||
v.height = -1;
|
||||
v.duration = -1;
|
||||
v.frame_rate = -1;
|
||||
v.video_data_rate = -1;
|
||||
v.video_keyframe_frequency = -1;
|
||||
v.video_codec_id_n = -1;
|
||||
v.audio_data_rate = -1;
|
||||
v.audio_codec_id_n = -1;
|
||||
v.profile[0] = '\0';
|
||||
v.level[0] = '\0';
|
||||
|
||||
/* FFmpeg sends a string in front of actal metadata; ignore it */
|
||||
/* FFmpeg sends a string in front of actual metadata; ignore it */
|
||||
skip = !(in->buf->last > in->buf->pos
|
||||
&& *in->buf->pos == NGX_RTMP_AMF_STRING);
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts + skip,
|
||||
|
@ -854,22 +900,22 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
ctx->width = (ngx_uint_t) v.width;
|
||||
ctx->height = (ngx_uint_t) v.height;
|
||||
ctx->duration = (ngx_uint_t) v.duration;
|
||||
ctx->frame_rate = (ngx_uint_t) v.frame_rate;
|
||||
ctx->video_data_rate = (ngx_uint_t) v.video_data_rate;
|
||||
ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n;
|
||||
ctx->audio_data_rate = (ngx_uint_t) v.audio_data_rate;
|
||||
ctx->audio_codec_id = (v.audio_codec_id_n == -1
|
||||
? 0 : v.audio_codec_id_n == 0
|
||||
if (v.width != -1) ctx->width = (ngx_uint_t) v.width;
|
||||
if (v.height != -1) ctx->height = (ngx_uint_t) v.height;
|
||||
if (v.duration != -1) ctx->duration = (double) v.duration;
|
||||
if (v.frame_rate != -1) ctx->frame_rate = (double) v.frame_rate;
|
||||
if (v.video_data_rate != -1) ctx->video_data_rate = v.video_data_rate;
|
||||
if (v.video_codec_id_n != -1) ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n;
|
||||
if (v.audio_data_rate != -1) ctx->audio_data_rate = v.audio_data_rate;
|
||||
if (v.video_keyframe_frequency != -1) ctx->video_keyframe_frequency = v.video_keyframe_frequency;
|
||||
if (v.audio_codec_id_n != -1) ctx->audio_codec_id = (v.audio_codec_id_n == 0
|
||||
? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n);
|
||||
ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));
|
||||
ngx_memcpy(ctx->level, v.level, sizeof(v.level));
|
||||
if (v.profile[0] != '\0') ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));
|
||||
if (v.level[0] != '\0') ngx_memcpy(ctx->level, v.level, sizeof(v.level));
|
||||
|
||||
ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"codec: data frame: "
|
||||
"width=%ui height=%ui duration=%ui frame_rate=%ui "
|
||||
"width=%ui height=%ui duration=%.3f frame_rate=%.3f "
|
||||
"video=%s (%ui) audio=%s (%ui)",
|
||||
ctx->width, ctx->height, ctx->duration, ctx->frame_rate,
|
||||
ngx_rtmp_get_video_codec_name(ctx->video_codec_id),
|
||||
|
@ -877,6 +923,12 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id),
|
||||
ctx->audio_codec_id);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"codec: data frame: "
|
||||
"video_rate=%.3f audio_rate=%.3f ",
|
||||
ctx->video_data_rate, ctx->audio_data_rate
|
||||
);
|
||||
|
||||
switch (cacf->meta) {
|
||||
case NGX_RTMP_CODEC_META_ON:
|
||||
return ngx_rtmp_codec_reconstruct_meta(s);
|
||||
|
@ -944,6 +996,14 @@ ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf)
|
|||
ngx_str_set(&ch->name, "@setDataFrame");
|
||||
ch->handler = ngx_rtmp_codec_meta_data;
|
||||
|
||||
// some encoders send setDataFrame instead of @setDataFrame
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
if (ch == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_str_set(&ch->name, "setDataFrame");
|
||||
ch->handler = ngx_rtmp_codec_meta_data;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
if (ch == NULL) {
|
||||
return NGX_ERROR;
|
||||
|
|
|
@ -52,11 +52,12 @@ u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id);
|
|||
typedef struct {
|
||||
ngx_uint_t width;
|
||||
ngx_uint_t height;
|
||||
ngx_uint_t duration;
|
||||
ngx_uint_t frame_rate;
|
||||
ngx_uint_t video_data_rate;
|
||||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
double video_keyframe_frequency;
|
||||
ngx_uint_t video_codec_id;
|
||||
ngx_uint_t audio_data_rate;
|
||||
double audio_data_rate;
|
||||
ngx_uint_t audio_codec_id;
|
||||
ngx_uint_t aac_profile;
|
||||
ngx_uint_t aac_chan_conf;
|
||||
|
|
|
@ -45,7 +45,7 @@ static ngx_command_t ngx_rtmp_core_commands[] = {
|
|||
NULL },
|
||||
|
||||
{ ngx_string("listen"),
|
||||
NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12,
|
||||
NGX_RTMP_SRV_CONF|NGX_CONF_1MORE,
|
||||
ngx_rtmp_core_listen,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
0,
|
||||
|
@ -332,6 +332,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
void *mconf;
|
||||
ngx_uint_t m;
|
||||
ngx_conf_t pcf;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx;
|
||||
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
|
||||
|
@ -357,12 +358,17 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
#if defined(nginx_version) && 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 = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->create_srv_conf) {
|
||||
mconf = module->create_srv_conf(cf);
|
||||
|
@ -370,7 +376,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
|
||||
ctx->srv_conf[modules[m]->ctx_index] = mconf;
|
||||
}
|
||||
|
||||
if (module->create_app_conf) {
|
||||
|
@ -379,7 +385,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ctx->app_conf[ngx_modules[m]->ctx_index] = mconf;
|
||||
ctx->app_conf[modules[m]->ctx_index] = mconf;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,6 +425,7 @@ ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
ngx_int_t i;
|
||||
ngx_str_t *value;
|
||||
ngx_conf_t save;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_conf_ctx_t *ctx, *pctx;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
@ -438,17 +445,21 @@ ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; ngx_modules[i]; i++) {
|
||||
if (ngx_modules[i]->type != NGX_RTMP_MODULE) {
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
for (i = 0; modules[i]; i++) {
|
||||
if (modules[i]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[i]->ctx;
|
||||
module = modules[i]->ctx;
|
||||
|
||||
if (module->create_app_conf) {
|
||||
ctx->app_conf[ngx_modules[i]->ctx_index] =
|
||||
module->create_app_conf(cf);
|
||||
if (ctx->app_conf[ngx_modules[i]->ctx_index] == NULL) {
|
||||
ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf);
|
||||
if (ctx->app_conf[modules[i]->ctx_index] == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +499,7 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
in_port_t port;
|
||||
ngx_str_t *value;
|
||||
ngx_url_t u;
|
||||
ngx_uint_t i, m;
|
||||
ngx_uint_t i;
|
||||
struct sockaddr *sa;
|
||||
ngx_rtmp_listen_t *ls;
|
||||
struct sockaddr_in *sin;
|
||||
|
@ -545,7 +556,11 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
break;
|
||||
}
|
||||
|
||||
#if (nginx_version >= 1011000)
|
||||
if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len) != 0) {
|
||||
#else
|
||||
if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) {
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -565,18 +580,16 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
ngx_memzero(ls, sizeof(ngx_rtmp_listen_t));
|
||||
|
||||
#if (nginx_version >= 1011000)
|
||||
ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen);
|
||||
#else
|
||||
ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen);
|
||||
#endif
|
||||
|
||||
ls->socklen = u.socklen;
|
||||
ls->wildcard = u.wildcard;
|
||||
ls->ctx = cf->ctx;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 2; i < cf->args->nelts; i++) {
|
||||
|
||||
if (ngx_strcmp(value[i].data, "bind") == 0) {
|
||||
|
@ -586,7 +599,6 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
|
||||
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||
struct sockaddr *sa;
|
||||
u_char buf[NGX_SOCKADDR_STRLEN];
|
||||
|
||||
sa = (struct sockaddr *) ls->sockaddr;
|
||||
|
|
|
@ -154,6 +154,7 @@ ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,
|
|||
|
||||
name.len = p - name.data;
|
||||
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
|
||||
/* fall through */
|
||||
|
||||
case NORMAL:
|
||||
switch (c) {
|
||||
|
@ -164,7 +165,11 @@ ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,
|
|||
case '\\':
|
||||
state = ESCAPE;
|
||||
continue;
|
||||
/* fall through */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case ESCAPE:
|
||||
ngx_rtmp_eval_append(&b, &c, 1, log);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
static ngx_rtmp_publish_pt next_publish;
|
||||
static ngx_rtmp_play_pt next_play;
|
||||
static ngx_rtmp_close_stream_pt next_close_stream;
|
||||
static ngx_rtmp_record_started_pt next_record_started;
|
||||
static ngx_rtmp_record_done_pt next_record_done;
|
||||
#endif
|
||||
|
||||
|
@ -55,6 +56,7 @@ enum {
|
|||
NGX_RTMP_EXEC_PUBLISH_DONE,
|
||||
NGX_RTMP_EXEC_PLAY,
|
||||
NGX_RTMP_EXEC_PLAY_DONE,
|
||||
NGX_RTMP_EXEC_RECORD_STARTED,
|
||||
NGX_RTMP_EXEC_RECORD_DONE,
|
||||
|
||||
NGX_RTMP_EXEC_MAX,
|
||||
|
@ -208,6 +210,15 @@ static ngx_command_t ngx_rtmp_exec_commands[] = {
|
|||
NGX_RTMP_EXEC_PLAY_DONE * sizeof(ngx_array_t),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("exec_record_started"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF|
|
||||
NGX_CONF_1MORE,
|
||||
ngx_rtmp_exec_conf,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_exec_app_conf_t, conf) +
|
||||
NGX_RTMP_EXEC_RECORD_STARTED * sizeof(ngx_array_t),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("exec_record_done"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF|
|
||||
NGX_CONF_1MORE,
|
||||
|
@ -1194,6 +1205,9 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"exec: ngx_rtmp_exec_play");
|
||||
|
||||
ngx_rtmp_exec_ctx_t *ctx;
|
||||
ngx_rtmp_exec_pull_ctx_t *pctx;
|
||||
ngx_rtmp_exec_app_conf_t *eacf;
|
||||
|
@ -1224,6 +1238,8 @@ ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
}
|
||||
|
||||
next:
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"exec: ngx_rtmp_exec_play: next");
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
@ -1302,6 +1318,31 @@ next:
|
|||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_exec_record_started(ngx_rtmp_session_t *s, ngx_rtmp_record_started_t *v)
|
||||
{
|
||||
ngx_rtmp_exec_app_conf_t *eacf;
|
||||
|
||||
if (s->auto_pushed) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);
|
||||
if (eacf == NULL || !eacf->active) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_RECORD_STARTED],
|
||||
"record_started");
|
||||
|
||||
ngx_str_null(&v->recorder);
|
||||
ngx_str_null(&v->path);
|
||||
|
||||
next:
|
||||
return next_record_started(s, v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)
|
||||
{
|
||||
|
@ -1359,6 +1400,7 @@ ngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)
|
|||
next:
|
||||
return next_record_done(s, v);
|
||||
}
|
||||
|
||||
#endif /* NGX_WIN32 */
|
||||
|
||||
|
||||
|
@ -1598,6 +1640,9 @@ ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf)
|
|||
next_record_done = ngx_rtmp_record_done;
|
||||
ngx_rtmp_record_done = ngx_rtmp_exec_record_done;
|
||||
|
||||
next_record_started = ngx_rtmp_record_started;
|
||||
ngx_rtmp_record_started = ngx_rtmp_exec_record_started;
|
||||
|
||||
#endif /* NGX_WIN32 */
|
||||
|
||||
return NGX_OK;
|
||||
|
|
|
@ -241,7 +241,9 @@ ngx_rtmp_recv(ngx_event_t *rev)
|
|||
"reusing formerly read data: %d", old_size);
|
||||
|
||||
b->pos = b->start;
|
||||
b->last = ngx_movemem(b->pos, old_pos, old_size);
|
||||
|
||||
size = ngx_min((size_t) (b->end - b->start), old_size);
|
||||
b->last = ngx_movemem(b->pos, old_pos, size);
|
||||
|
||||
if (s->in_chunk_size_changing) {
|
||||
ngx_rtmp_finalize_set_chunk_size(s);
|
||||
|
@ -558,7 +560,11 @@ ngx_rtmp_send(ngx_event_t *wev)
|
|||
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
|
||||
}
|
||||
|
||||
#if (nginx_version >= 1007012)
|
||||
ngx_event_process_posted((ngx_cycle_t *) ngx_cycle,(ngx_queue_t *) &s->posted_dry_events);
|
||||
#else
|
||||
ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -868,6 +874,7 @@ ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size)
|
|||
|
||||
bi->pos += (ngx_cpymem(bo->last, bi->pos,
|
||||
bo->end - bo->last) - bo->last);
|
||||
bo->last = bo->end;
|
||||
lo->next = ngx_rtmp_alloc_in_buf(s);
|
||||
lo = lo->next;
|
||||
if (lo == NULL) {
|
||||
|
|
|
@ -104,30 +104,37 @@ static ngx_int_t
|
|||
ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src,
|
||||
u_char *skip, u_char *dst, ngx_log_t *log)
|
||||
{
|
||||
static HMAC_CTX hmac;
|
||||
static unsigned hmac_initialized;
|
||||
static HMAC_CTX *hmac;
|
||||
unsigned int len;
|
||||
|
||||
if (!hmac_initialized) {
|
||||
HMAC_CTX_init(&hmac);
|
||||
hmac_initialized = 1;
|
||||
if (hmac == NULL) {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
static HMAC_CTX shmac;
|
||||
hmac = &shmac;
|
||||
HMAC_CTX_init(hmac);
|
||||
#else
|
||||
hmac = HMAC_CTX_new();
|
||||
if (hmac == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
HMAC_Init_ex(&hmac, key->data, key->len, EVP_sha256(), NULL);
|
||||
HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL);
|
||||
|
||||
if (skip && src->pos <= skip && skip <= src->last) {
|
||||
if (skip != src->pos) {
|
||||
HMAC_Update(&hmac, src->pos, skip - src->pos);
|
||||
HMAC_Update(hmac, src->pos, skip - src->pos);
|
||||
}
|
||||
if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) {
|
||||
HMAC_Update(&hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,
|
||||
HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,
|
||||
src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN);
|
||||
}
|
||||
} else {
|
||||
HMAC_Update(&hmac, src->pos, src->last - src->pos);
|
||||
HMAC_Update(hmac, src->pos, src->last - src->pos);
|
||||
}
|
||||
|
||||
HMAC_Final(&hmac, dst, &len);
|
||||
HMAC_Final(hmac, dst, &len);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
|
@ -76,9 +76,9 @@ ngx_rtmp_init_connection(ngx_connection_t *c)
|
|||
|
||||
break;
|
||||
#endif
|
||||
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) sa;
|
||||
|
@ -110,6 +110,7 @@ ngx_rtmp_init_connection(ngx_connection_t *c)
|
|||
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
addr = port->addrs;
|
||||
|
|
|
@ -40,16 +40,16 @@ static ngx_command_t ngx_rtmp_live_commands[] = {
|
|||
|
||||
{ ngx_string("stream_buckets"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_live_app_conf_t, nbuckets),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("buffer"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_live_app_conf_t, buflen),
|
||||
offsetof(ngx_rtmp_live_app_conf_t, buffer),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("sync"),
|
||||
|
@ -152,7 +152,7 @@ ngx_rtmp_live_create_app_conf(ngx_conf_t *cf)
|
|||
|
||||
lacf->live = NGX_CONF_UNSET;
|
||||
lacf->nbuckets = NGX_CONF_UNSET;
|
||||
lacf->buflen = NGX_CONF_UNSET_MSEC;
|
||||
lacf->buffer = NGX_CONF_UNSET;
|
||||
lacf->sync = NGX_CONF_UNSET_MSEC;
|
||||
lacf->idle_timeout = NGX_CONF_UNSET_MSEC;
|
||||
lacf->interleave = NGX_CONF_UNSET;
|
||||
|
@ -174,11 +174,11 @@ ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
|
||||
ngx_conf_merge_value(conf->live, prev->live, 0);
|
||||
ngx_conf_merge_value(conf->nbuckets, prev->nbuckets, 1024);
|
||||
ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 0);
|
||||
ngx_conf_merge_value(conf->buffer, prev->buffer, 0);
|
||||
ngx_conf_merge_msec_value(conf->sync, prev->sync, 300);
|
||||
ngx_conf_merge_msec_value(conf->idle_timeout, prev->idle_timeout, 0);
|
||||
ngx_conf_merge_value(conf->interleave, prev->interleave, 0);
|
||||
ngx_conf_merge_value(conf->wait_key, prev->wait_key, 1);
|
||||
ngx_conf_merge_value(conf->wait_key, prev->wait_key, 0);
|
||||
ngx_conf_merge_value(conf->wait_video, prev->wait_video, 0);
|
||||
ngx_conf_merge_value(conf->publish_notify, prev->publish_notify, 0);
|
||||
ngx_conf_merge_value(conf->play_restart, prev->play_restart, 0);
|
||||
|
@ -357,6 +357,9 @@ ngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_chain_t *control,
|
|||
|
||||
ctx->cs[1].active = 0;
|
||||
ctx->cs[1].dropped = 0;
|
||||
|
||||
ctx->cs[2].active = 0;
|
||||
ctx->cs[2].dropped = 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -550,12 +553,13 @@ ngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher)
|
|||
|
||||
(*stream)->ctx = ctx;
|
||||
|
||||
if (lacf->buflen) {
|
||||
if (lacf->buffer) {
|
||||
s->out_buffer = 1;
|
||||
}
|
||||
|
||||
ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO;
|
||||
ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO;
|
||||
ctx->cs[2].csid = NGX_RTMP_CSID_AMF;
|
||||
|
||||
if (!ctx->publishing && ctx->stream->active) {
|
||||
ngx_rtmp_live_start(s);
|
||||
|
@ -1036,6 +1040,351 @@ ngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in, ngx_rtmp_amf_elt_t *out_elts, ngx_uint_t out_elts_size)
|
||||
{
|
||||
ngx_rtmp_live_ctx_t *ctx, *pctx;
|
||||
ngx_chain_t *data, *rpkt;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
ngx_rtmp_session_t *ss;
|
||||
ngx_rtmp_header_t ch;
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t prio;
|
||||
ngx_uint_t peers;
|
||||
uint32_t delta;
|
||||
ngx_rtmp_live_chunk_stream_t *cs;
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
u_char *msg_type;
|
||||
|
||||
msg_type = (u_char *)out_elts[0].data;
|
||||
#endif
|
||||
|
||||
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
|
||||
if (lacf == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (!lacf->live || in == NULL || in->buf == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
|
||||
if (ctx == NULL || ctx->stream == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ctx->publishing == 0) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"live: %s from non-publisher", msg_type);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* drop the data packet if the stream is not active */
|
||||
if (!ctx->stream->active) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"live: %s packet timestamp=%uD",
|
||||
msg_type, h->timestamp);
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
cs = &ctx->cs[2];
|
||||
cs->active = 1;
|
||||
|
||||
peers = 0;
|
||||
prio = 0;
|
||||
data = NULL;
|
||||
rc = ngx_rtmp_append_amf(s, &data, NULL, out_elts, out_elts_size);
|
||||
if (rc != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: data - can't append amf!");
|
||||
if (data) {
|
||||
ngx_rtmp_free_shared_chain(cscf, data);
|
||||
}
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memzero(&ch, sizeof(ch));
|
||||
ch.timestamp = h->timestamp;
|
||||
ch.msid = NGX_RTMP_MSID;
|
||||
ch.csid = h->csid;
|
||||
ch.type = NGX_RTMP_MSG_AMF_META;
|
||||
|
||||
delta = ch.timestamp - cs->timestamp;
|
||||
|
||||
rpkt = ngx_rtmp_append_shared_bufs(cscf, data, in);
|
||||
ngx_rtmp_prepare_message(s, &ch, NULL, rpkt);
|
||||
|
||||
for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
|
||||
if (pctx == ctx || pctx->paused) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ss = pctx->session;
|
||||
|
||||
if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) {
|
||||
++pctx->ndropped;
|
||||
cs->dropped += delta;
|
||||
continue;
|
||||
}
|
||||
|
||||
cs->timestamp += delta;
|
||||
++peers;
|
||||
ss->current_time = cs->timestamp;
|
||||
}
|
||||
|
||||
if (rpkt) {
|
||||
ngx_rtmp_free_shared_chain(cscf, rpkt);
|
||||
}
|
||||
|
||||
ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen);
|
||||
ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, h->mlen * peers);
|
||||
ngx_rtmp_update_bandwidth(&ctx->stream->bw_in_data, h->mlen);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_on_cue_point(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onCuePoint", 0 }
|
||||
};
|
||||
|
||||
return ngx_rtmp_live_data(s, h, in, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_on_text_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onTextData", 0 }
|
||||
};
|
||||
|
||||
return ngx_rtmp_live_data(s, h, in, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_on_fi(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
ngx_int_t res;
|
||||
|
||||
static struct {
|
||||
u_char time[NGX_TIME_T_LEN + 1];
|
||||
u_char date[NGX_TIME_T_LEN + 1];
|
||||
} v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_dt_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("sd"),
|
||||
&v.date, sizeof(v.date) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("st"),
|
||||
&v.time, sizeof(v.time) },
|
||||
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_MIXED_ARRAY,
|
||||
ngx_null_string,
|
||||
in_dt_elts, sizeof(in_dt_elts) },
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_dt_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("sd"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("st"),
|
||||
NULL, 0 },
|
||||
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFI", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_MIXED_ARRAY,
|
||||
ngx_null_string,
|
||||
out_dt_elts, sizeof(out_dt_elts) },
|
||||
};
|
||||
|
||||
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
|
||||
if (lacf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: Fi - no live config!");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (!lacf->live || in == NULL || in->buf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: Fi - no live or no buffer!");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
res = ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0]));
|
||||
|
||||
if (res == NGX_OK) {
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: onFI: date='%s', time='%s'",
|
||||
v.date, v.time);
|
||||
|
||||
out_dt_elts[0].data = v.date;
|
||||
out_dt_elts[1].data = v.time;
|
||||
|
||||
// Pass through datetime from publisher
|
||||
return ngx_rtmp_live_data(s, h, in, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
|
||||
} else {
|
||||
// Send our server datetime
|
||||
return ngx_rtmp_send_fi(s);
|
||||
}
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_on_fcpublish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
|
||||
static struct {
|
||||
double trans;
|
||||
u_char action[128];
|
||||
u_char stream[1024];
|
||||
} v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
// Already readed
|
||||
/* { NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.action, sizeof(v.action) },
|
||||
*/
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.stream, sizeof(v.stream) },
|
||||
};
|
||||
|
||||
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
|
||||
if (lacf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: FCPublish - no live config!");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (!lacf->live || in == NULL || in->buf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: FCPublish - no live or no buffer!");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0]));
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: onFCPublish: stream='%s'",
|
||||
v.stream);
|
||||
|
||||
return ngx_rtmp_send_fcpublish(s, v.stream);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_on_fcunpublish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
|
||||
static struct {
|
||||
double trans;
|
||||
u_char action[128];
|
||||
u_char stream[1024];
|
||||
} v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
// Already readed
|
||||
/* { NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.action, sizeof(v.action) },
|
||||
*/
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.stream, sizeof(v.stream) },
|
||||
};
|
||||
|
||||
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
|
||||
if (lacf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: FCUnpublish - no live config!");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (!lacf->live || in == NULL || in->buf == NULL) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: FCUnpublish - no live or no buffer!");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0]));
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: onFCUnpublish: stream='%s'",
|
||||
v.stream);
|
||||
|
||||
return ngx_rtmp_send_fcunpublish(s, v.stream);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
|
@ -1077,6 +1426,9 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: ngx_rtmp_live_play");
|
||||
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
ngx_rtmp_live_ctx_t *ctx;
|
||||
|
||||
|
@ -1109,6 +1461,8 @@ ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
}
|
||||
|
||||
next:
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"live: ngx_rtmp_live_play: next");
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
@ -1118,6 +1472,7 @@ ngx_rtmp_live_postconfiguration(ngx_conf_t *cf)
|
|||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
ngx_rtmp_amf_handler_t *ch;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
|
@ -1149,5 +1504,25 @@ ngx_rtmp_live_postconfiguration(ngx_conf_t *cf)
|
|||
next_stream_eof = ngx_rtmp_stream_eof;
|
||||
ngx_rtmp_stream_eof = ngx_rtmp_live_stream_eof;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "onTextData");
|
||||
ch->handler = ngx_rtmp_live_on_text_data;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "onCuePoint");
|
||||
ch->handler = ngx_rtmp_live_on_cue_point;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "onFI");
|
||||
ch->handler = ngx_rtmp_live_on_fi;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "FCPublish");
|
||||
ch->handler = ngx_rtmp_live_on_fcpublish;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "FCUnpublish");
|
||||
ch->handler = ngx_rtmp_live_on_fcunpublish;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ struct ngx_rtmp_live_ctx_s {
|
|||
ngx_rtmp_live_stream_t *stream;
|
||||
ngx_rtmp_live_ctx_t *next;
|
||||
ngx_uint_t ndropped;
|
||||
ngx_rtmp_live_chunk_stream_t cs[2];
|
||||
ngx_rtmp_live_chunk_stream_t cs[3];
|
||||
ngx_uint_t meta_version;
|
||||
ngx_event_t idle_evt;
|
||||
unsigned active:1;
|
||||
|
@ -50,6 +50,7 @@ struct ngx_rtmp_live_stream_s {
|
|||
ngx_rtmp_bandwidth_t bw_in;
|
||||
ngx_rtmp_bandwidth_t bw_in_audio;
|
||||
ngx_rtmp_bandwidth_t bw_in_video;
|
||||
ngx_rtmp_bandwidth_t bw_in_data;
|
||||
ngx_rtmp_bandwidth_t bw_out;
|
||||
ngx_msec_t epoch;
|
||||
unsigned active:1;
|
||||
|
@ -71,7 +72,7 @@ typedef struct {
|
|||
ngx_flag_t publish_notify;
|
||||
ngx_flag_t play_restart;
|
||||
ngx_flag_t idle_streams;
|
||||
ngx_msec_t buflen;
|
||||
ngx_flag_t buffer;
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_live_stream_t *free_streams;
|
||||
} ngx_rtmp_live_app_conf_t;
|
||||
|
|
|
@ -853,6 +853,9 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"log: ngx_rtmp_log_play");
|
||||
|
||||
ngx_rtmp_log_ctx_t *ctx;
|
||||
|
||||
if (s->auto_pushed || s->relay) {
|
||||
|
@ -867,6 +870,8 @@ ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
ctx->play = 1;
|
||||
|
||||
next:
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"log: ngx_rtmp_log_play: next");
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
|
|
@ -571,6 +571,7 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
|||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
ngx_str_t *addr_text;
|
||||
size_t bsize;
|
||||
|
||||
addr_text = &s->connection->addr_text;
|
||||
|
||||
|
@ -579,16 +580,34 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool,
|
||||
sizeof("app=") - 1 + s->app.len * 3 +
|
||||
sizeof("&flashver=") - 1 + s->flashver.len * 3 +
|
||||
sizeof("&swfurl=") - 1 + s->swf_url.len * 3 +
|
||||
sizeof("&tcurl=") - 1 + s->tc_url.len * 3 +
|
||||
sizeof("&pageurl=") - 1 + s->page_url.len * 3 +
|
||||
sizeof("&addr=") - 1 + addr_text->len * 3 +
|
||||
sizeof("&clientid=") - 1 + NGX_INT_T_LEN
|
||||
);
|
||||
/**
|
||||
* @2016-04-20 sergey-dryabzhinsky
|
||||
* Not all params may be filled in session
|
||||
* So not override them with empty values
|
||||
*/
|
||||
|
||||
bsize = sizeof("addr=") - 1 + addr_text->len * 3 +
|
||||
sizeof("&clientid=") - 1 + NGX_INT_T_LEN;
|
||||
|
||||
// Indicator of additional vars from session
|
||||
// Event `connect` don't have them, for example
|
||||
if (s->app.len) {
|
||||
bsize += sizeof("&app=") - 1 + s->app.len * 3;
|
||||
}
|
||||
if (s->flashver.len) {
|
||||
bsize += sizeof("&flashver=") - 1 + s->flashver.len * 3;
|
||||
}
|
||||
if (s->swf_url.len) {
|
||||
bsize += sizeof("&swfurl=") - 1 + s->swf_url.len * 3;
|
||||
}
|
||||
if (s->tc_url.len) {
|
||||
bsize += sizeof("&tcurl=") - 1 + s->tc_url.len * 3;
|
||||
}
|
||||
if (s->page_url.len) {
|
||||
bsize += sizeof("&pageurl=") - 1 + s->page_url.len * 3;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, bsize);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -596,31 +615,7 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
|||
cl->buf = b;
|
||||
cl->next = NULL;
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "app=", sizeof("app=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,
|
||||
NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&flashver=",
|
||||
sizeof("&flashver=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data,
|
||||
s->flashver.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=",
|
||||
sizeof("&swfurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data,
|
||||
s->swf_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=",
|
||||
sizeof("&tcurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data,
|
||||
s->tc_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=",
|
||||
sizeof("&pageurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data,
|
||||
s->page_url.len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&addr=", sizeof("&addr=") - 1);
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "addr=", sizeof("addr=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data,
|
||||
addr_text->len, NGX_ESCAPE_ARGS);
|
||||
|
||||
|
@ -628,6 +623,36 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
|||
sizeof("&clientid=") - 1);
|
||||
b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number);
|
||||
|
||||
if (s->app.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&app=", sizeof("&app=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,
|
||||
NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->flashver.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&flashver=",
|
||||
sizeof("&flashver=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data,
|
||||
s->flashver.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->swf_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=",
|
||||
sizeof("&swfurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data,
|
||||
s->swf_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->tc_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=",
|
||||
sizeof("&tcurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data,
|
||||
s->tc_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->page_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=",
|
||||
sizeof("&pageurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data,
|
||||
s->page_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -291,7 +291,11 @@ ngx_rtmp_play_send(ngx_event_t *e)
|
|||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"play: send buffer full");
|
||||
|
||||
#if (nginx_version >= 1007012)
|
||||
ngx_post_event(e, (ngx_queue_t *) &s->posted_dry_events);
|
||||
#else
|
||||
ngx_post_event(e, &s->posted_dry_events);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -481,6 +485,9 @@ ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name)
|
|||
ngx_rtmp_play_ctx_t *ctx;
|
||||
u_char *path, *p;
|
||||
static u_char dpath[NGX_MAX_PATH + 1];
|
||||
u_char *d;
|
||||
static u_char dir[NGX_MAX_PATH + 1];
|
||||
u_int l;
|
||||
|
||||
pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);
|
||||
if (pacf == NULL) {
|
||||
|
@ -496,6 +503,26 @@ ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name)
|
|||
|
||||
p = ngx_snprintf(dpath, NGX_MAX_PATH, "%V/%s%V", &pacf->local_path,
|
||||
name + ctx->pfx_size, &ctx->sfx);
|
||||
|
||||
d = name + ctx->pfx_size;
|
||||
while (*d != '\0') {
|
||||
if (*d == '/') {
|
||||
p = ngx_snprintf(dir, NGX_MAX_PATH, "%V/%s", &pacf->local_path, name + ctx->pfx_size, &ctx->sfx);
|
||||
l = ngx_strlen(dir) - ngx_strlen(d);
|
||||
dir[l]='\0';
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"play: create dir '%s' for '%s'", dir, dpath);
|
||||
if (ngx_create_dir(dir, 0700) == NGX_FILE_ERROR) {
|
||||
if (ngx_errno != NGX_EEXIST) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
|
||||
"play: error creating dir '%s' for '%s'", dir, dpath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
d++;
|
||||
}
|
||||
|
||||
*p = 0;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
|
@ -689,6 +716,9 @@ ngx_rtmp_play_parse_index(char type, u_char *args)
|
|||
static ngx_int_t
|
||||
ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_play");
|
||||
|
||||
ngx_rtmp_play_main_conf_t *pmcf;
|
||||
ngx_rtmp_play_app_conf_t *pacf;
|
||||
ngx_rtmp_play_ctx_t *ctx;
|
||||
|
@ -800,9 +830,15 @@ ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"play: fmt=%V", &ctx->fmt->name);
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_play: next_entry");
|
||||
|
||||
return ngx_rtmp_play_next_entry(s, v);
|
||||
|
||||
next:
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_play: next");
|
||||
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
@ -810,6 +846,9 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_next_entry");
|
||||
|
||||
ngx_rtmp_play_app_conf_t *pacf;
|
||||
ngx_rtmp_play_ctx_t *ctx;
|
||||
ngx_rtmp_play_entry_t *pe;
|
||||
|
@ -854,6 +893,9 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
/* open remote */
|
||||
|
||||
if (pe->url) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_next_entry: open remote");
|
||||
|
||||
return ngx_rtmp_play_open_remote(s, v);
|
||||
}
|
||||
|
||||
|
@ -866,9 +908,23 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN,
|
||||
NGX_FILE_DEFAULT_ACCESS);
|
||||
|
||||
/* try unsuffixed file name as fallback if adding suffix didn't work */
|
||||
if (ctx->file.fd == NGX_INVALID_FILE) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno,
|
||||
"play: error opening file '%s'", path);
|
||||
"play: error opening file '%s', trying without suffix",
|
||||
path);
|
||||
|
||||
p = ngx_snprintf(path, NGX_MAX_PATH, "%V/%s",
|
||||
pe->root, v->name + ctx->pfx_size);
|
||||
*p = 0;
|
||||
|
||||
ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN,
|
||||
NGX_FILE_DEFAULT_ACCESS);
|
||||
}
|
||||
|
||||
if (ctx->file.fd == NGX_INVALID_FILE) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno,
|
||||
"play: error opening fallback file '%s'", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -876,12 +932,19 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
"play: open local file '%s'", path);
|
||||
|
||||
if (ngx_rtmp_play_open(s, v->start) != NGX_OK) {
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_next_entry: error open");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_next_entry: next");
|
||||
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
@ -1007,6 +1070,9 @@ ngx_rtmp_play_remote_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool)
|
|||
static ngx_int_t
|
||||
ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_remote_handle");
|
||||
|
||||
ngx_rtmp_play_t *v = arg;
|
||||
|
||||
ngx_rtmp_play_ctx_t *ctx;
|
||||
|
@ -1014,6 +1080,8 @@ ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in)
|
|||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
|
||||
|
||||
if (ctx->nbody == 0) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_remote_handle: next_entry");
|
||||
return ngx_rtmp_play_next_entry(s, v);
|
||||
}
|
||||
|
||||
|
@ -1025,9 +1093,16 @@ ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in)
|
|||
"play: open remote file");
|
||||
|
||||
if (ngx_rtmp_play_open(s, v->start) != NGX_OK) {
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_remote_handle: error open");
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"play: ngx_rtmp_play_remote_handle: next");
|
||||
|
||||
return next_play(s, (ngx_rtmp_play_t *)arg);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "ngx_rtmp_record_module.h"
|
||||
|
||||
|
||||
ngx_rtmp_record_started_pt ngx_rtmp_record_started;
|
||||
ngx_rtmp_record_done_pt ngx_rtmp_record_done;
|
||||
|
||||
|
||||
|
@ -31,9 +32,9 @@ static char * ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf,
|
|||
static ngx_int_t ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_rec_ctx_t *rctx,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_int_t inc_nframes);
|
||||
static ngx_int_t ngx_rtmp_record_av(ngx_rtmp_session_t *s,
|
||||
static ngx_int_t ngx_rtmp_record_avd(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_record_node_av(ngx_rtmp_session_t *s,
|
||||
static ngx_int_t ngx_rtmp_record_node_avd(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_record_node_open(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_rec_ctx_t *rctx);
|
||||
|
@ -47,9 +48,13 @@ static ngx_int_t ngx_rtmp_record_init(ngx_rtmp_session_t *s);
|
|||
static ngx_conf_bitmask_t ngx_rtmp_record_mask[] = {
|
||||
{ ngx_string("off"), NGX_RTMP_RECORD_OFF },
|
||||
{ ngx_string("all"), NGX_RTMP_RECORD_AUDIO |
|
||||
NGX_RTMP_RECORD_VIDEO |
|
||||
NGX_RTMP_RECORD_DATA },
|
||||
{ ngx_string("av"), NGX_RTMP_RECORD_AUDIO |
|
||||
NGX_RTMP_RECORD_VIDEO },
|
||||
{ ngx_string("audio"), NGX_RTMP_RECORD_AUDIO },
|
||||
{ ngx_string("video"), NGX_RTMP_RECORD_VIDEO },
|
||||
{ ngx_string("data"), NGX_RTMP_RECORD_DATA },
|
||||
{ ngx_string("keyframes"), NGX_RTMP_RECORD_KEYFRAMES },
|
||||
{ ngx_string("manual"), NGX_RTMP_RECORD_MANUAL },
|
||||
{ ngx_null_string, 0 }
|
||||
|
@ -106,6 +111,14 @@ static ngx_command_t ngx_rtmp_record_commands[] = {
|
|||
offsetof(ngx_rtmp_record_app_conf_t, lock_file),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("record_interval_size"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|
|
||||
NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_record_app_conf_t, interval_size),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("record_max_size"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|
|
||||
NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,
|
||||
|
@ -190,6 +203,7 @@ ngx_rtmp_record_create_app_conf(ngx_conf_t *cf)
|
|||
}
|
||||
|
||||
racf->max_size = NGX_CONF_UNSET_SIZE;
|
||||
racf->interval_size = NGX_CONF_UNSET_SIZE;
|
||||
racf->max_frames = NGX_CONF_UNSET_SIZE;
|
||||
racf->interval = NGX_CONF_UNSET_MSEC;
|
||||
racf->unique = NGX_CONF_UNSET;
|
||||
|
@ -216,6 +230,7 @@ ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
ngx_conf_merge_str_value(conf->path, prev->path, "");
|
||||
ngx_conf_merge_str_value(conf->suffix, prev->suffix, ".flv");
|
||||
ngx_conf_merge_size_value(conf->max_size, prev->max_size, 0);
|
||||
ngx_conf_merge_size_value(conf->interval_size, prev->interval_size, 0);
|
||||
ngx_conf_merge_size_value(conf->max_frames, prev->max_frames, 0);
|
||||
ngx_conf_merge_value(conf->unique, prev->unique, 0);
|
||||
ngx_conf_merge_value(conf->append, prev->append, 0);
|
||||
|
@ -842,6 +857,8 @@ ngx_rtmp_record_node_close(ngx_rtmp_session_t *s,
|
|||
v.recorder = rracf->id;
|
||||
ngx_rtmp_record_make_path(s, rctx, &v.path);
|
||||
|
||||
rctx->record_started = 0;
|
||||
|
||||
rc = ngx_rtmp_record_done(s, &v);
|
||||
|
||||
s->app_conf = app_conf;
|
||||
|
@ -886,10 +903,26 @@ ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s,
|
|||
|
||||
if (h->type == NGX_RTMP_MSG_VIDEO) {
|
||||
rctx->video = 1;
|
||||
} else {
|
||||
}
|
||||
if (h->type == NGX_RTMP_MSG_AUDIO) {
|
||||
rctx->audio = 1;
|
||||
}
|
||||
|
||||
if (rctx->record_started == 0)
|
||||
{
|
||||
rctx->record_started = 1;
|
||||
|
||||
ngx_rtmp_record_started_t v;
|
||||
ngx_rtmp_record_app_conf_t *racf;
|
||||
racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);
|
||||
|
||||
if (racf != NULL && racf->rec.nelts != 0) {
|
||||
v.recorder = racf->id;
|
||||
v.path = racf->path;
|
||||
ngx_rtmp_record_started(s, &v);
|
||||
}
|
||||
}
|
||||
|
||||
timestamp = h->timestamp - rctx->epoch;
|
||||
|
||||
if ((int32_t) timestamp < 0) {
|
||||
|
@ -993,7 +1026,7 @@ ngx_rtmp_record_get_chain_mlen(ngx_chain_t *in)
|
|||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_record_avd(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_record_ctx_t *ctx;
|
||||
|
@ -1009,7 +1042,7 @@ ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
rctx = ctx->rec.elts;
|
||||
|
||||
for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) {
|
||||
ngx_rtmp_record_node_av(s, rctx, h, in);
|
||||
ngx_rtmp_record_node_avd(s, rctx, h, in);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
@ -1017,7 +1050,7 @@ ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
||||
ngx_rtmp_record_node_avd(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_time_t next;
|
||||
|
@ -1041,37 +1074,15 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
|||
? keyframe
|
||||
: (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0;
|
||||
|
||||
if (brkframe && (rracf->flags & NGX_RTMP_RECORD_MANUAL) == 0) {
|
||||
|
||||
if (rracf->interval != (ngx_msec_t) NGX_CONF_UNSET) {
|
||||
|
||||
next = rctx->last;
|
||||
next.msec += rracf->interval;
|
||||
next.sec += (next.msec / 1000);
|
||||
next.msec %= 1000;
|
||||
|
||||
if (ngx_cached_time->sec > next.sec ||
|
||||
(ngx_cached_time->sec == next.sec &&
|
||||
ngx_cached_time->msec > next.msec))
|
||||
{
|
||||
ngx_rtmp_record_node_close(s, rctx);
|
||||
ngx_rtmp_record_node_open(s, rctx);
|
||||
}
|
||||
|
||||
} else if (!rctx->failed) {
|
||||
ngx_rtmp_record_node_open(s, rctx);
|
||||
}
|
||||
}
|
||||
|
||||
if ((rracf->flags & NGX_RTMP_RECORD_MANUAL) &&
|
||||
!brkframe && rctx->nframes == 0)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rctx->file.fd == NGX_INVALID_FILE) {
|
||||
/*if (rctx->file.fd == NGX_INVALID_FILE) {
|
||||
return NGX_OK;
|
||||
}
|
||||
}*/
|
||||
|
||||
if (h->type == NGX_RTMP_MSG_AUDIO &&
|
||||
(rracf->flags & NGX_RTMP_RECORD_AUDIO) == 0)
|
||||
|
@ -1079,6 +1090,12 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (h->type == NGX_RTMP_MSG_AMF_META &&
|
||||
(rracf->flags & NGX_RTMP_RECORD_DATA) == 0)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (h->type == NGX_RTMP_MSG_VIDEO &&
|
||||
(rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0 &&
|
||||
((rracf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe))
|
||||
|
@ -1086,6 +1103,27 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (brkframe && rracf->interval != NGX_CONF_UNSET_MSEC)
|
||||
{
|
||||
// record interval should work if set, manual mode or not
|
||||
next = rctx->last;
|
||||
next.msec += rracf->interval;
|
||||
next.sec += (next.msec / 1000);
|
||||
next.msec %= 1000;
|
||||
|
||||
if (ngx_cached_time->sec > next.sec ||
|
||||
(ngx_cached_time->sec == next.sec &&
|
||||
ngx_cached_time->msec > next.msec))
|
||||
{
|
||||
ngx_rtmp_record_node_close(s, rctx);
|
||||
ngx_rtmp_record_node_open(s, rctx);
|
||||
}
|
||||
}
|
||||
else if (!rctx->failed)
|
||||
{
|
||||
ngx_rtmp_record_node_open(s, rctx);
|
||||
}
|
||||
|
||||
if (!rctx->initialized) {
|
||||
|
||||
rctx->initialized = 1;
|
||||
|
@ -1181,6 +1219,12 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,
|
|||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_record_started_init(ngx_rtmp_session_t *s, ngx_rtmp_record_started_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_record_done_init(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)
|
||||
{
|
||||
|
@ -1195,6 +1239,7 @@ ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
ngx_int_t i;
|
||||
ngx_str_t *value;
|
||||
ngx_conf_t save;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_core_app_conf_t *cacf, **pcacf, *rcacf;
|
||||
ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf;
|
||||
|
@ -1221,17 +1266,22 @@ ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (i = 0; ngx_modules[i]; i++) {
|
||||
if (ngx_modules[i]->type != NGX_RTMP_MODULE) {
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
for (i = 0; modules[i]; i++) {
|
||||
if (modules[i]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[i]->ctx;
|
||||
module = modules[i]->ctx;
|
||||
|
||||
if (module->create_app_conf) {
|
||||
ctx->app_conf[ngx_modules[i]->ctx_index] =
|
||||
ctx->app_conf[modules[i]->ctx_index] =
|
||||
module->create_app_conf(cf);
|
||||
if (ctx->app_conf[ngx_modules[i]->ctx_index] == NULL) {
|
||||
if (ctx->app_conf[modules[i]->ctx_index] == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
@ -1275,15 +1325,20 @@ ngx_rtmp_record_postconfiguration(ngx_conf_t *cf)
|
|||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
|
||||
ngx_rtmp_record_started = ngx_rtmp_record_started_init;
|
||||
|
||||
ngx_rtmp_record_done = ngx_rtmp_record_done_init;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
|
||||
*h = ngx_rtmp_record_av;
|
||||
*h = ngx_rtmp_record_avd;
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
|
||||
*h = ngx_rtmp_record_av;
|
||||
*h = ngx_rtmp_record_avd;
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AMF_META]);
|
||||
*h = ngx_rtmp_record_avd;
|
||||
|
||||
next_publish = ngx_rtmp_publish;
|
||||
ngx_rtmp_publish = ngx_rtmp_record_publish;
|
||||
|
|
|
@ -16,15 +16,16 @@
|
|||
#define NGX_RTMP_RECORD_OFF 0x01
|
||||
#define NGX_RTMP_RECORD_AUDIO 0x02
|
||||
#define NGX_RTMP_RECORD_VIDEO 0x04
|
||||
#define NGX_RTMP_RECORD_KEYFRAMES 0x08
|
||||
#define NGX_RTMP_RECORD_MANUAL 0x10
|
||||
|
||||
#define NGX_RTMP_RECORD_DATA 0x08
|
||||
#define NGX_RTMP_RECORD_KEYFRAMES 0x10
|
||||
#define NGX_RTMP_RECORD_MANUAL 0x20
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t id;
|
||||
ngx_uint_t flags;
|
||||
ngx_str_t path;
|
||||
size_t max_size;
|
||||
size_t interval_size;
|
||||
size_t max_frames;
|
||||
ngx_msec_t interval;
|
||||
ngx_str_t suffix;
|
||||
|
@ -53,6 +54,7 @@ typedef struct {
|
|||
unsigned video_key_sent:1;
|
||||
unsigned audio:1;
|
||||
unsigned video:1;
|
||||
unsigned record_started:1;
|
||||
} ngx_rtmp_record_rec_ctx_t;
|
||||
|
||||
|
||||
|
@ -83,10 +85,23 @@ typedef struct {
|
|||
} ngx_rtmp_record_done_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t recorder;
|
||||
ngx_str_t path;
|
||||
} ngx_rtmp_record_started_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_record_started_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_started_t *v);
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_done_t *v);
|
||||
|
||||
|
||||
extern ngx_rtmp_record_started_pt ngx_rtmp_record_started;
|
||||
|
||||
|
||||
extern ngx_rtmp_record_done_pt ngx_rtmp_record_done;
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_relay_module.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
|
||||
|
||||
static ngx_rtmp_publish_pt next_publish;
|
||||
|
@ -496,9 +497,11 @@ ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name,
|
|||
}
|
||||
rs->app_conf = cctx->app_conf;
|
||||
rs->relay = 1;
|
||||
rs->ready_for_publish = 0;
|
||||
rctx->session = rs;
|
||||
ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module);
|
||||
ngx_str_set(&rs->flashver, "ngx-local-relay");
|
||||
ngx_memcpy(&rs->app, &rctx->app, sizeof(rctx->app));
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, 1);
|
||||
|
@ -707,6 +710,9 @@ next:
|
|||
static ngx_int_t
|
||||
ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"relay: ngx_rtmp_relay_play");
|
||||
|
||||
ngx_rtmp_relay_app_conf_t *racf;
|
||||
ngx_rtmp_relay_target_t *target, **t;
|
||||
ngx_str_t name;
|
||||
|
@ -748,6 +754,9 @@ ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|||
}
|
||||
|
||||
next:
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"relay: ngx_rtmp_relay_play: next");
|
||||
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
@ -1231,12 +1240,160 @@ ngx_rtmp_relay_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_relay_send_set_data_frame(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_relay_ctx_t *ctx;
|
||||
ngx_rtmp_codec_ctx_t *codec_ctx;
|
||||
ngx_rtmp_header_t hdr;
|
||||
|
||||
static struct {
|
||||
double width;
|
||||
double height;
|
||||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
double video_codec_id;
|
||||
double audio_data_rate;
|
||||
double audio_codec_id;
|
||||
u_char profile[32];
|
||||
u_char level[32];
|
||||
} v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("Server"),
|
||||
"NGINX RTMP (github.com/arut/nginx-rtmp-module)", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("width"),
|
||||
&v.width, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("height"),
|
||||
&v.height, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("displayWidth"),
|
||||
&v.width, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("displayHeight"),
|
||||
&v.height, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("duration"),
|
||||
&v.duration, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("framerate"),
|
||||
&v.frame_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("fps"),
|
||||
&v.frame_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videodatarate"),
|
||||
&v.video_data_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videocodecid"),
|
||||
&v.video_codec_id, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("audiodatarate"),
|
||||
&v.audio_data_rate, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("audiocodecid"),
|
||||
&v.audio_codec_id, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("profile"),
|
||||
&v.profile, sizeof(v.profile) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
&v.level, sizeof(v.level) }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"@setDataFrame", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onMetaData", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf, sizeof(out_inf) }
|
||||
};
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
|
||||
if (ctx == NULL || !s->relay) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"relay: couldn't get relay context");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* we need to get the codec context from the incoming publisher in order to
|
||||
* send the metadata along */
|
||||
codec_ctx = ngx_rtmp_get_module_ctx(ctx->publish->session,
|
||||
ngx_rtmp_codec_module);
|
||||
if (codec_ctx == NULL) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"relay: couldn't get codec context");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"relay: data frame from codec context: "
|
||||
"width=%ui height=%ui duration=%ui frame_rate=%ui "
|
||||
"video_codec_id=%ui audio_codec_id=%ui",
|
||||
codec_ctx->width, codec_ctx->height, codec_ctx->duration,
|
||||
codec_ctx->frame_rate, codec_ctx->video_codec_id,
|
||||
codec_ctx->audio_codec_id);
|
||||
|
||||
/* we only want to send the metadata if the codec module has already
|
||||
* parsed it -- is there a better way to check this? */
|
||||
if (codec_ctx->width > 0 && codec_ctx->height > 0) {
|
||||
v.width = codec_ctx->width;
|
||||
v.height = codec_ctx->height;
|
||||
v.duration = codec_ctx->duration;
|
||||
v.frame_rate = codec_ctx->frame_rate;
|
||||
v.video_data_rate = codec_ctx->video_data_rate;
|
||||
v.video_codec_id = codec_ctx->video_codec_id;
|
||||
v.audio_data_rate = codec_ctx->audio_data_rate;
|
||||
v.audio_codec_id = codec_ctx->audio_codec_id;
|
||||
ngx_memcpy(v.profile, codec_ctx->profile, sizeof(codec_ctx->profile));
|
||||
ngx_memcpy(v.level, codec_ctx->level, sizeof(codec_ctx->level));
|
||||
|
||||
ngx_memzero(&hdr, sizeof(hdr));
|
||||
hdr.csid = NGX_RTMP_RELAY_CSID_AMF_INI;
|
||||
hdr.msid = NGX_RTMP_RELAY_MSID;
|
||||
hdr.type = NGX_RTMP_MSG_AMF_META;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"relay: sending @setDataFrame");
|
||||
|
||||
return ngx_rtmp_send_amf(s, &hdr, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_relay_ctx_t *ctx;
|
||||
|
||||
static struct {
|
||||
double trans;
|
||||
u_char level[32];
|
||||
|
@ -1300,9 +1457,57 @@ ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|||
"relay: onStatus: level='%s' code='%s' description='%s'",
|
||||
v.level, v.code, v.desc);
|
||||
|
||||
/* when doing a push to Adobe Media Server, we have to use the
|
||||
* @setDataFrame command to send the metadata
|
||||
* see: http://help.adobe.com/en_US/adobemediaserver/devguide/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6Dev.2.3.html
|
||||
*/
|
||||
if (!ngx_strncasecmp(v.code, (u_char *)"NetStream.Publish.Start",
|
||||
ngx_strlen("NetStream.Publish.Start"))) {
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"relay: sending metadata from NetStream.Publish.Start from player");
|
||||
|
||||
s->ready_for_publish = 1;
|
||||
|
||||
if (ngx_rtmp_relay_send_set_data_frame(s) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"relay: unable to send metadata via @setDataFrame");
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_relay_on_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
/* when we receive onMetaData, the session (s) is our incoming publisher's
|
||||
* session, so we need to send the @setDataFrame to our ctx->play->session */
|
||||
ngx_rtmp_relay_ctx_t *ctx;
|
||||
ngx_rtmp_relay_ctx_t *pctx;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"relay: got metadata from @setDataFrame invocation from publisher.");
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
for (pctx = ctx->play; pctx; pctx = pctx->next) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"relay: %ssending metadata from @setDataFrame invocation from publisher to %V/%V/%V",
|
||||
(pctx->session->relay && pctx->session->ready_for_publish) ? "" : "not ", &pctx->url, &pctx->app, &pctx->play_path);
|
||||
if (!pctx->session->relay || !pctx->session->ready_for_publish) continue;
|
||||
if (ngx_rtmp_relay_send_set_data_frame(pctx->session) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"relay: unable to send @setDataFrame to %V/%V", &pctx->url, &pctx->play_path);
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
|
@ -1686,5 +1891,9 @@ ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf)
|
|||
ngx_str_set(&ch->name, "onStatus");
|
||||
ch->handler = ngx_rtmp_relay_on_status;
|
||||
|
||||
ch = ngx_array_push(&cmcf->amf);
|
||||
ngx_str_set(&ch->name, "@setDataFrame");
|
||||
ch->handler = ngx_rtmp_relay_on_meta_data;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
356
ngx_rtmp_send.c
356
ngx_rtmp_send.c
|
@ -3,7 +3,6 @@
|
|||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
@ -100,7 +99,7 @@ ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid)
|
|||
"create: abort csid=%uD", csid);
|
||||
|
||||
{
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE);
|
||||
NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ABORT);
|
||||
|
||||
NGX_RTMP_USER_OUT4(csid);
|
||||
|
||||
|
@ -503,6 +502,7 @@ ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level,
|
|||
out_inf[0].data = level;
|
||||
out_inf[1].data = code;
|
||||
out_inf[2].data = desc;
|
||||
trans = 0;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
|
@ -594,6 +594,358 @@ ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level,
|
|||
}
|
||||
|
||||
|
||||
// ----------- Based on Adobe FMS 3 application.redirectConnection description --------- //
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_redirect_status(ngx_rtmp_session_t *s, char *callMethod, char *desc, ngx_str_t to_url)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double dtrans;
|
||||
static double dcode;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create redirect status: got data");
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create redirect status: method='%s', status code='%s' level='%s' "
|
||||
"ex.code=%ui ex.redirect='%s'", callMethod,
|
||||
"NetConnection.Connect.Rejected", "error", 302, to_url.data);
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf_ex_data[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("code"),
|
||||
&dcode, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("redirect"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"error", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetConnection.Connect.Rejected", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_string("ex"),
|
||||
out_inf_ex_data,
|
||||
sizeof(out_inf_ex_data) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&dtrans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create redirect status: set structure data");
|
||||
|
||||
out_elts[0].data = callMethod;
|
||||
out_inf[2].data = desc;
|
||||
dcode = 302;
|
||||
dtrans = 0;
|
||||
out_inf_ex_data[1].data = to_url.data;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_redirect_status(ngx_rtmp_session_t *s,
|
||||
char *callMethod, char *desc, ngx_str_t to_url)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_redirect_status(s, callMethod, desc, to_url));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_close_method(ngx_rtmp_session_t *s, char *methodName)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double dtrans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&dtrans, 0 },
|
||||
};
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create close method: set structure data");
|
||||
|
||||
out_elts[0].data = methodName;
|
||||
dtrans = 0;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_close_method(ngx_rtmp_session_t *s, char *methodName)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_close_method(s, methodName));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_fcpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetStream.Publish.Start", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFCPublish", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create: fcpublish - set structure data");
|
||||
|
||||
out_inf[2].data = desc;
|
||||
// trans = 3.0; // magick from ffmpeg
|
||||
trans = 0;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_fcpublish(s, desc));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_fcunpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetStream.Unpublish.Success", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFCUnpublish", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"create: fcunpublish - set structure data");
|
||||
|
||||
out_inf[2].data = desc;
|
||||
// trans = 5.0; // magick from ffmpeg
|
||||
trans = 0;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_fcunpublish(s, desc));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_fi(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_header_t h;
|
||||
static double trans;
|
||||
|
||||
struct tm tm;
|
||||
struct timeval tv;
|
||||
|
||||
static u_char buf_time[NGX_TIME_T_LEN*2 + 1];
|
||||
static u_char buf_date[NGX_TIME_T_LEN + 1];
|
||||
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("st"),
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("sd"),
|
||||
NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"onFI", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf,
|
||||
sizeof(out_inf) },
|
||||
};
|
||||
|
||||
trans = 0;
|
||||
|
||||
ngx_gettimeofday(&tv);
|
||||
|
||||
ngx_libc_localtime((time_t)tv.tv_sec, &tm);
|
||||
|
||||
ngx_memzero(buf_time, sizeof(buf_time));
|
||||
ngx_memzero(buf_date, sizeof(buf_date));
|
||||
|
||||
ngx_sprintf(buf_time, "%02d:%02d:%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, (int)tv.tv_usec);
|
||||
// Strange order, but FMLE send like this
|
||||
ngx_sprintf(buf_date, "%02d-%02d-%04d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
|
||||
|
||||
out_inf[0].data = buf_time;
|
||||
out_inf[1].data = buf_date;
|
||||
|
||||
memset(&h, 0, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
|
||||
return ngx_rtmp_create_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_send_fi(ngx_rtmp_session_t *s)
|
||||
{
|
||||
return ngx_rtmp_send_shared_packet(s,
|
||||
ngx_rtmp_create_fi(s));
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s)
|
||||
{
|
||||
|
|
|
@ -66,7 +66,8 @@ ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in)
|
|||
}
|
||||
|
||||
for (cl = in; ; cl = cl->next) {
|
||||
if (cl->next == NULL) {
|
||||
/* FIXME: Don't create circular chains in the first place */
|
||||
if (cl->next == NULL || cl->next == in) {
|
||||
cl->next = cscf->free;
|
||||
cscf->free = in;
|
||||
return;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_play_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
#include "ngx_rtmp_record_module.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle);
|
||||
|
@ -115,7 +116,7 @@ ngx_rtmp_stat_init_process(ngx_cycle_t *cycle)
|
|||
* so we can run posted events here
|
||||
*/
|
||||
|
||||
ngx_event_process_posted(cycle, &ngx_rtmp_init_queue);
|
||||
ngx_event_process_posted(cycle, (ngx_queue_t*) &ngx_rtmp_init_queue);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
@ -329,6 +330,7 @@ ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
ngx_rtmp_session_t *s)
|
||||
{
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
struct sockaddr_in *sa;
|
||||
|
||||
#ifdef NGX_RTMP_POOL_DEBUG
|
||||
ngx_rtmp_stat_dump_pool(r, lll, s->connection->pool);
|
||||
|
@ -342,6 +344,20 @@ ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
NGX_RTMP_STAT_ES(&s->connection->addr_text);
|
||||
NGX_RTMP_STAT_L("</address>");
|
||||
|
||||
/*
|
||||
** Displays socket port number
|
||||
*/
|
||||
NGX_RTMP_STAT_L("<port>");
|
||||
sa = (struct sockaddr_in *) s->connection->sockaddr;
|
||||
if (sa) {
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui",
|
||||
(ngx_uint_t) ntohs(sa->sin_port)) - buf);
|
||||
} else {
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", 0) - buf);
|
||||
}
|
||||
NGX_RTMP_STAT_L("</port>");
|
||||
|
||||
|
||||
NGX_RTMP_STAT_L("<time>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%i",
|
||||
(ngx_int_t) (ngx_current_msec - s->epoch)) - buf);
|
||||
|
@ -364,6 +380,14 @@ ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
NGX_RTMP_STAT_ES(&s->swf_url);
|
||||
NGX_RTMP_STAT_L("</swfurl>");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<bytes_in>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", (ngx_uint_t) s->in_bytes) - buf);
|
||||
NGX_RTMP_STAT_L("</bytes_in>");
|
||||
|
||||
NGX_RTMP_STAT_L("<bytes_out>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", (ngx_uint_t) s->out_bytes) - buf);
|
||||
NGX_RTMP_STAT_L("</bytes_out>");
|
||||
}
|
||||
|
||||
|
||||
|
@ -414,14 +438,19 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
ngx_rtmp_live_stream_t *stream;
|
||||
ngx_rtmp_codec_ctx_t *codec;
|
||||
ngx_rtmp_live_ctx_t *ctx;
|
||||
ngx_rtmp_record_ctx_t *rctx;
|
||||
ngx_rtmp_record_rec_ctx_t *recctx;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_int_t n;
|
||||
ngx_uint_t nclients, total_nclients;
|
||||
ngx_uint_t nclients, total_nclients, rn;
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
u_char bbuf[NGX_INT32_LEN];
|
||||
ngx_rtmp_stat_loc_conf_t *slcf;
|
||||
u_char *cname;
|
||||
|
||||
// Is any of stream clients (publisher) recording now
|
||||
u_char is_recording = 0;
|
||||
|
||||
if (!lacf->live) {
|
||||
return;
|
||||
}
|
||||
|
@ -435,6 +464,8 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
for (stream = lacf->streams[n]; stream; stream = stream->next) {
|
||||
NGX_RTMP_STAT_L("<stream>\r\n");
|
||||
|
||||
is_recording = 0;
|
||||
|
||||
NGX_RTMP_STAT_L("<name>");
|
||||
NGX_RTMP_STAT_ECS(stream->name);
|
||||
NGX_RTMP_STAT_L("</name>\r\n");
|
||||
|
@ -453,6 +484,8 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
NGX_RTMP_STAT_BW);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, "video",
|
||||
NGX_RTMP_STAT_BW);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_data, "data",
|
||||
NGX_RTMP_STAT_BW);
|
||||
|
||||
nclients = 0;
|
||||
codec = NULL;
|
||||
|
@ -489,6 +522,18 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
NGX_RTMP_STAT_L("<active/>");
|
||||
}
|
||||
|
||||
rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);
|
||||
if (rctx) {
|
||||
recctx = rctx->rec.elts;
|
||||
for (rn = 0; rn < rctx->rec.nelts; ++rn, ++recctx) {
|
||||
if (recctx->initialized && recctx->file.fd != NGX_INVALID_FILE) {
|
||||
NGX_RTMP_STAT_L("<recording/>");
|
||||
is_recording = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</client>\r\n");
|
||||
}
|
||||
if (ctx->publishing) {
|
||||
|
@ -509,8 +554,18 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
"%ui", codec->height) - buf);
|
||||
NGX_RTMP_STAT_L("</height><frame_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->frame_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</frame_rate>");
|
||||
"%.3f", codec->frame_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</frame_rate><data_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.0f", codec->video_data_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</data_rate>");
|
||||
|
||||
if(codec->video_keyframe_frequency) {
|
||||
NGX_RTMP_STAT_L("<keyframe_frequency>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.0f", codec->video_keyframe_frequency) - buf);
|
||||
NGX_RTMP_STAT_L("</keyframe_frequency>");
|
||||
}
|
||||
|
||||
cname = ngx_rtmp_get_video_codec_name(codec->video_codec_id);
|
||||
if (*cname) {
|
||||
|
@ -570,6 +625,12 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
"%ui", codec->sample_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</sample_rate>");
|
||||
}
|
||||
if (codec->audio_data_rate) {
|
||||
NGX_RTMP_STAT_L("<data_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%0.0f", codec->audio_data_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</data_rate>");
|
||||
}
|
||||
NGX_RTMP_STAT_L("</audio>");
|
||||
|
||||
NGX_RTMP_STAT_L("</meta>\r\n");
|
||||
|
@ -588,6 +649,10 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
|||
NGX_RTMP_STAT_L("<active/>\r\n");
|
||||
}
|
||||
|
||||
if (is_recording) {
|
||||
NGX_RTMP_STAT_L("<recording/>\r\n");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</stream>\r\n");
|
||||
}
|
||||
}
|
||||
|
@ -768,7 +833,10 @@ ngx_rtmp_stat_handler(ngx_http_request_t *r)
|
|||
#ifdef NGX_COMPILER
|
||||
NGX_RTMP_STAT_L("<compiler>" NGX_COMPILER "</compiler>\r\n");
|
||||
#endif
|
||||
/* This may prevent reproducible builds. If you need that info - pass `-DNGX_BUILD_DATEITIME=1` to CFLAGS */
|
||||
#ifdef NGX_BUILD_DATEITIME
|
||||
NGX_RTMP_STAT_L("<built>" __DATE__ " " __TIME__ "</built>\r\n");
|
||||
#endif
|
||||
|
||||
NGX_RTMP_STAT_L("<pid>");
|
||||
NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
#define _NGX_RTMP_VERSION_H_INCLUDED_
|
||||
|
||||
|
||||
#define nginx_rtmp_version 1001004
|
||||
#define NGINX_RTMP_VERSION "1.1.4"
|
||||
#define nginx_rtmp_version 1002002
|
||||
#define NGINX_RTMP_VERSION "1.2.2-r1"
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */
|
||||
|
|
18
stat.xsl
18
stat.xsl
|
@ -38,6 +38,7 @@
|
|||
<th>In bits/s</th>
|
||||
<th>Out bits/s</th>
|
||||
<th>State</th>
|
||||
<th>Record</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -75,6 +76,7 @@
|
|||
</xsl:call-template>
|
||||
</td>
|
||||
<td/>
|
||||
<td/>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="/rtmp/uptime * 1000"/>
|
||||
|
@ -202,6 +204,7 @@
|
|||
</xsl:call-template>
|
||||
</td>
|
||||
<td><xsl:call-template name="streamstate"/></td>
|
||||
<td><xsl:call-template name="recordstate"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time"/>
|
||||
|
@ -217,6 +220,7 @@
|
|||
<tr>
|
||||
<th>Id</th>
|
||||
<th>State</th>
|
||||
<th>Recording</th>
|
||||
<th>Address</th>
|
||||
<th>Flash version</th>
|
||||
<th>Page URL</th>
|
||||
|
@ -299,6 +303,13 @@
|
|||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="recordstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="recording">yes</xsl:when>
|
||||
<xsl:otherwise>no</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="client">
|
||||
<tr>
|
||||
|
@ -310,13 +321,14 @@
|
|||
</xsl:attribute>
|
||||
<td><xsl:value-of select="id"/></td>
|
||||
<td><xsl:call-template name="clientstate"/></td>
|
||||
<td><xsl:call-template name="recordstate"/></td>
|
||||
<td>
|
||||
<a target="_blank">
|
||||
<xsl:attribute name="href">
|
||||
http://apps.db.ripe.net/search/query.html?searchtext=<xsl:value-of select="address"/>
|
||||
</xsl:attribute>
|
||||
<xsl:attribute name="title">whois</xsl:attribute>
|
||||
<xsl:value-of select="address"/>
|
||||
<xsl:value-of select="address"/>:<xsl:value-of select="port"/>
|
||||
</a>
|
||||
</td>
|
||||
<td><xsl:value-of select="flashver"/></td>
|
||||
|
@ -348,6 +360,10 @@
|
|||
active
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="recording">
|
||||
recording
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="width">
|
||||
<xsl:value-of select="."/>x<xsl:value-of select="../height"/>
|
||||
</xsl:template>
|
||||
|
|
Loading…
Reference in a new issue