diff --git a/dash/ngx_rtmp_dash_module.c b/dash/ngx_rtmp_dash_module.c
index 324edce..4fcb59a 100644
--- a/dash/ngx_rtmp_dash_module.c
+++ b/dash/ngx_rtmp_dash_module.c
@@ -81,11 +81,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;
@@ -137,6 +155,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
};
@@ -225,7 +257,10 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
struct tm tm;
ngx_str_t noname, *name;
ngx_uint_t i, frame_rate_num, frame_rate_denom;
- ngx_uint_t depth_msec;
+ 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_rtmp_dash_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
ngx_rtmp_dash_frag_t *f;
@@ -236,7 +271,7 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
static u_char buffer[NGX_RTMP_DASH_BUFSIZE];
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.00S")];
+ 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);
@@ -267,17 +302,21 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
" type=\"dynamic\"\n" \
" xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" \
" availabilityStartTime=\"%s\"\n" \
- " publishTime=\"%s\"\n" \
- " minimumUpdatePeriod=\"PT%uiS\"\n" \
- " minBufferTime=\"PT%uiS\"\n" \
+ " publishTime=\"%s\"\n" \
+ " minimumUpdatePeriod=\"PT%ui.%03uiS\"\n" \
+ " minBufferTime=\"PT%ui.%03uiS\"\n" \
" timeShiftBufferDepth=\"%s\"\n" \
- " suggestedPresentationDelay=\"PT%uiS\"\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_CLOCK \
+ " \n" \
+
+#define NGX_RTMP_DASH_MANIFEST_PERIOD \
" \n"
@@ -285,6 +324,7 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
" \n" \
@@ -296,7 +336,6 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)
" height=\"%ui\"\n" \
" frameRate=\"%s\"\n" \
" sar=\"1:1\"\n" \
- " startWithSAP=\"1\"\n" \
" bandwidth=\"%ui\">\n" \
" \n" \
" \n" \
" \n" \
"\n"
- ngx_libc_localtime(ctx->start_time.sec, &tm);
+/**
+ * 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;
- *ngx_sprintf(avaliable_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;
-
- if (publish_time[0] == '\0'){
- *ngx_sprintf(publish_time, "%s", avaliable_time) = 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);
+ /* Stream publish time */
+ *ngx_sprintf(publish_time, "%s", avaliable_time) = 0;
- depth_msec = (ngx_uint_t) (
+ 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);
- ngx_libc_gmtime((ngx_uint_t) (depth_msec / 1000), &tm);
+ depth_msec = depth_sec % 1000;
+ depth_sec -= depth_msec;
+ depth_sec /= 1000;
- *ngx_sprintf(buffer_depth, "P%dY%02dM%02dDT%dH%02dM%02d.%02dS",
+ 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,
- (ngx_uint_t) ((depth_msec % 1000) / 10)) = 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,
+ // availabilityStartTime
avaliable_time,
+ // publishTime
publish_time,
- (ngx_uint_t) (dacf->fraglen / 1000),
- (ngx_uint_t) (dacf->fraglen / 1000),
+ // minimumUpdatePeriod
+ update_period, update_period_msec,
+ // minBufferTime
+ buffer_time, buffer_time_msec,
+ // timeShiftBufferDepth
buffer_depth,
- (ngx_uint_t) (dacf->fraglen / 500),
- avaliable_time); /* UTCTiming value for shaka-player */
+ // suggestedPresentationDelay
+ presentation_delay, presentation_delay_msec
+ );
+
+ /* UTCTiming value */
+ switch (dacf->clock_compensation) {
+ case NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP:
+ p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
+ "ntp",
+ &dacf->clock_helper_uri
+ );
+ break;
+ case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD:
+ p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
+ "http-head",
+ &dacf->clock_helper_uri
+ );
+ break;
+ case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO:
+ p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_CLOCK,
+ "http-iso",
+ &dacf->clock_helper_uri
+ );
+ break;
+ }
+
+ p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_PERIOD);
n = ngx_write_fd(fd, buffer, p - buffer);
@@ -1520,6 +1621,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;
}
@@ -1537,6 +1639,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;
diff --git a/doc/README.md b/doc/README.md
index 318cca3..f629195 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -24,3 +24,6 @@
* [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)
diff --git a/doc/directives.md b/doc/directives.md
index e80cf13..4ad5436 100644
--- a/doc/directives.md
+++ b/doc/directives.md
@@ -110,6 +110,8 @@ Table of Contents
* [dash_playlist_length](#dash_playlist_length)
* [dash_nested](#dash_nested)
* [dash_cleanup](#dash_cleanup)
+ * [dash_clock_compensation](#dash_clock_compensation)
+ * [dash_clock_helper_uri](#dash_clock_helper_uri)
* [Access log](#access-log)
* [access_log](#access_log)
* [log_format](#log_format)
@@ -1605,6 +1607,45 @@ Init fragments are deleted after stream manifest is deleted.
dash_cleanup off;
```
+#### dash\_clock_compensation
+Syntax: `dash_clock_compensation off|ntp|http_head|http_iso`
+Context: rtmp, server, application
+Default: off
+
+Toggles MPEG-DASH clock compentation element output into MPD.
+In this mode nginx provides `UTCTiming` element for MPEG-DASH manifest.
+Clock compensation provided by DASH-client if possible.
+- ntp - use NTP protocol
+- http_head - client must fetch header `Date` from URI (`dash_clock_helper_uri`)
+- http_iso - client must fetch date in ISO format from URI (`dash_clock_helper_uri`)
+
+Standard section: 4.7.2. Service Provider Requirements and Guidelines
+
+```sh
+dash\_clock_compensation off;
+```
+
+#### dash\_clock_helper_uri
+Syntax: `dash_clock_helper_uri URI`
+Context: rtmp, server, application
+Default: none
+
+URI helper resource for clock compensation for client.
+Clock compensation type:
+- ntp - address of NTP-server
+- http\_head - full HTTP uri
+- http\_iso - full HTTP uri
+
+Standard section: 4.7.2. Service Provider Requirements and Guidelines
+
+```sh
+dash\_clock\_helper_uri http://rtmp-server/static/time.txt;
+
+_or_
+
+dash\_clock\_helper_uri http://rtmp-server/lua/time-iso;
+```
+
## Access log
#### access_log
diff --git a/ngx_rtmp_codec_module.c b/ngx_rtmp_codec_module.c
index 228536d..73d31a1 100644
--- a/ngx_rtmp_codec_module.c
+++ b/ngx_rtmp_codec_module.c
@@ -900,7 +900,7 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
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, s->connection->log, 0,
+ ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"codec: data frame: "
"width=%ui height=%ui duration=%.3f frame_rate=%.3f "
"video=%s (%ui) audio=%s (%ui)",
@@ -910,6 +910,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);