diff --git a/TODO b/TODO index 8194b0f..19de8f7 100644 --- a/TODO +++ b/TODO @@ -3,11 +3,11 @@ - add HTTP callbacks for all calls +- support client chunk size change + - check compilation with IPv6 enabled - remove macros hell from ngx_rtmp_send.c - fix broken data_frame(?) -- fix time wrapping problem (% 0x00ffffff) - diff --git a/doc/video_file_format_spec_v10.pdf b/doc/video_file_format_spec_v10.pdf new file mode 100644 index 0000000..69ecf03 Binary files /dev/null and b/doc/video_file_format_spec_v10.pdf differ diff --git a/ngx_rtmp.h b/ngx_rtmp.h index dcf4baa..1fec6f3 100644 --- a/ngx_rtmp.h +++ b/ngx_rtmp.h @@ -245,6 +245,8 @@ typedef struct ngx_rtmp_core_srv_conf_s { size_t max_buf; size_t max_message; ngx_flag_t wait_key_frame; + ngx_flag_t play_time_fix; + ngx_flag_t publish_time_fix; ngx_rtmp_conf_ctx_t *ctx; } ngx_rtmp_core_srv_conf_t; diff --git a/ngx_rtmp_broadcast_module.c b/ngx_rtmp_broadcast_module.c index 91a8af3..586f41c 100644 --- a/ngx_rtmp_broadcast_module.c +++ b/ngx_rtmp_broadcast_module.c @@ -33,6 +33,8 @@ static ngx_int_t ngx_rtmp_broadcast_set_data_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in); static ngx_int_t ngx_rtmp_broadcast_ok(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_broadcast_stream_length(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); static ngx_rtmp_amf0_handler_t ngx_rtmp_broadcast_map[] = { @@ -42,6 +44,7 @@ static ngx_rtmp_amf0_handler_t ngx_rtmp_broadcast_map[] = { { ngx_string("play"), ngx_rtmp_broadcast_play }, { ngx_string("-@setDataFrame"), ngx_rtmp_broadcast_set_data_frame }, { ngx_string("releaseStream"), ngx_rtmp_broadcast_ok }, + { ngx_string("getStreamLength"), ngx_rtmp_broadcast_stream_length }, { ngx_string("FCPublish"), ngx_rtmp_broadcast_ok }, { ngx_string("FCSubscribe"), ngx_rtmp_broadcast_ok }, }; @@ -265,7 +268,7 @@ ngx_rtmp_broadcast_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module); memset(&sh, 0, sizeof(sh)); - sh.timestamp = h->timestamp + s->epoch; + sh.timestamp = (h->timestamp + s->epoch);/* & 0x00ffffff*/; /*FIXME*/ sh.msid = NGX_RTMP_BROADCAST_MSID; sh.type = h->type; @@ -755,6 +758,43 @@ ngx_rtmp_broadcast_ok(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, } +static ngx_int_t +ngx_rtmp_broadcast_stream_length(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_header_t sh; + + static double trans; + static double length; + + static ngx_rtmp_amf0_elt_t in_elts[] = { + { NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) }, + }; + + static ngx_rtmp_amf0_elt_t out_elts[] = { + { NGX_RTMP_AMF0_STRING, NULL, "_result", 0 }, + { NGX_RTMP_AMF0_NUMBER, NULL, &trans, 0 }, + { NGX_RTMP_AMF0_NUMBER, NULL, &length, 0 }, + }; + + /* parse input */ + if (ngx_rtmp_receive_amf0(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + memset(&sh, 0, sizeof(sh)); + sh.csid = h->csid; + sh.type = NGX_RTMP_MSG_AMF0_CMD; + sh.msid = 0; + + /* send simple _result */ + return ngx_rtmp_send_amf0(s, &sh, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + static ngx_int_t ngx_rtmp_broadcast_postconfiguration(ngx_conf_t *cf) { diff --git a/ngx_rtmp_core_module.c b/ngx_rtmp_core_module.c index cfc0e2b..b014383 100644 --- a/ngx_rtmp_core_module.c +++ b/ngx_rtmp_core_module.c @@ -98,6 +98,21 @@ static ngx_command_t ngx_rtmp_core_commands[] = { offsetof(ngx_rtmp_core_srv_conf_t, wait_key_frame), NULL }, + /* time fixes are needed for flash clients */ + { ngx_string("play_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix), + NULL }, + + { ngx_string("publish_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix), + NULL }, + ngx_null_command }; @@ -173,6 +188,8 @@ ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf) conf->max_buf = NGX_CONF_UNSET; conf->max_message = NGX_CONF_UNSET; conf->wait_key_frame = NGX_CONF_UNSET; + conf->play_time_fix = NGX_CONF_UNSET; + conf->publish_time_fix = NGX_CONF_UNSET; return conf; } @@ -193,6 +210,8 @@ ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->max_buf, prev->max_buf, 128 * 1024); ngx_conf_merge_size_value(conf->max_message, prev->max_message, 1024 * 1024); ngx_conf_merge_value(conf->wait_key_frame, prev->wait_key_frame, 1); + ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1); + ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1); if (prev->pool == NULL) { prev->pool = ngx_create_pool(8192, cf->log); diff --git a/ngx_rtmp_handler.c b/ngx_rtmp_handler.c index 918f479..0d524ed 100644 --- a/ngx_rtmp_handler.c +++ b/ngx_rtmp_handler.c @@ -281,7 +281,7 @@ ngx_rtmp_get_timestamp() tod = ngx_timeofday(); /* FIXME: divisor */ - return (uint32_t)(tod->sec * 1000 + tod->msec) % 0x00ffffff; + return (uint32_t)(tod->sec * 1000 + tod->msec) /*& 0x00ffffff*/; } @@ -471,8 +471,6 @@ restart: ngx_rtmp_handshake_recv(c->read); } -ngx_chain_t * tmp; - void ngx_rtmp_recv(ngx_event_t *rev) @@ -681,9 +679,16 @@ ngx_rtmp_recv(ngx_event_t *rev) pp[3] = *p++; } } + } - /* extended header */ - if (timestamp == 0x00ffffff) { + /* extended header */ + if (timestamp >= 0x00ffffff) { + /* Messages with type=3 should + * never have ext timestamp field + * according to standard. + * However that's not always the case + * in real life */ + if (fmt <= 2 || cscf->publish_time_fix) { if (b->last - p < 4) continue; pp = (u_char*)×tamp; @@ -692,11 +697,12 @@ ngx_rtmp_recv(ngx_event_t *rev) pp[1] = *p++; pp[0] = *p++; } - if (fmt) { - h->timestamp += timestamp; - } else { - h->timestamp = timestamp; - } + } + + if (fmt == 1 || fmt == 2) { + h->timestamp += timestamp; + } else { + h->timestamp = timestamp; } ngx_log_debug6(NGX_LOG_DEBUG_RTMP, c->log, 0, @@ -705,11 +711,6 @@ ngx_rtmp_recv(ngx_event_t *rev) ngx_rtmp_message_type(h->type), (int)h->type, h->timestamp, h->mlen, st->len, h->msid); - if (h->mlen==51441 && st->len==20864) { - /*asm("int $0x03");*/ - tmp = in; - } - /* header done */ b->pos = p; @@ -876,7 +877,7 @@ ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_int_t hsize, thsize, nbufs; uint32_t mlen, timestamp, ext_timestamp; static uint8_t hdrsize[] = { 12, 8, 4, 1 }; - u_char th[3]; + u_char th[7]; ngx_rtmp_core_srv_conf_t *cscf; uint8_t fmt; ngx_connection_t *c; @@ -992,6 +993,15 @@ ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, *p++ = pp[2]; *p++ = pp[1]; *p++ = pp[0]; + + /* This CONTRADICTS the standard + * but that's the way flash client + * wants data to be encoded; + * ffmpeg complains */ + if (cscf->play_time_fix) { + ngx_memcpy(&th[thsize], p - 4, 4); + thsize += 4; + } } /* append headers to successive fragments */ diff --git a/test/nginx.conf b/test/nginx.conf index ba4b368..fc88159 100644 --- a/test/nginx.conf +++ b/test/nginx.conf @@ -20,13 +20,15 @@ rtmp { listen 1935; -# wait_key_frame on; + #wait_key_frame on; chunk_size 128; max_buf 1000000; -#allow play all; + #timestamp_fix on; + + #allow play all; allow publish 127.0.0.1;