Skip to content

Commit e22ca28

Browse files
committed
seamless hls stream switch
1 parent b96bcfc commit e22ca28

File tree

1 file changed

+77
-51
lines changed

1 file changed

+77
-51
lines changed

hls/ngx_rtmp_hls_module.c

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ typedef struct {
2929

3030
unsigned publishing:1;
3131
unsigned opened:1;
32-
unsigned epoch_valid:1;
3332

3433
ngx_file_t file;
3534

@@ -38,17 +37,15 @@ typedef struct {
3837
ngx_str_t stream;
3938
ngx_str_t name;
4039

41-
ngx_int_t frag;
40+
uint64_t frag;
4241

4342
ngx_uint_t audio_cc;
4443
ngx_uint_t video_cc;
4544

4645
int64_t aframe_base;
4746
int64_t aframe_num;
4847

49-
uint32_t epoch;
50-
uint64_t basetime;
51-
uint64_t timestamp;
48+
uint64_t offset;
5249
} ngx_rtmp_hls_ctx_t;
5350

5451

@@ -62,6 +59,7 @@ typedef struct {
6259
ngx_uint_t winfrags;
6360
ngx_uint_t nfrags;
6461
ngx_flag_t continuous;
62+
ngx_flag_t nodelete;
6563
ngx_rtmp_hls_ctx_t **ctx;
6664
ngx_uint_t nbuckets;
6765
ngx_str_t path;
@@ -119,6 +117,13 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
119117
offsetof(ngx_rtmp_hls_app_conf_t, continuous),
120118
NULL },
121119

120+
{ ngx_string("hls_nodelete"),
121+
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
122+
ngx_conf_set_flag_slot,
123+
NGX_RTMP_APP_CONF_OFFSET,
124+
offsetof(ngx_rtmp_hls_app_conf_t, nodelete),
125+
NULL },
126+
122127
{ ngx_string("hls_playlist_factor"),
123128
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
124129
ngx_conf_set_num_slot,
@@ -161,6 +166,9 @@ ngx_module_t ngx_rtmp_hls_module = {
161166
};
162167

163168

169+
#define ngx_rtmp_hls_frag(hacf, f) (hacf->nfrags ? (f) % hacf->nfrags : (f))
170+
171+
164172
static void
165173
ngx_rtmp_hls_chain2buffer(ngx_buf_t *out, ngx_chain_t *in, size_t skip)
166174
{
@@ -190,7 +198,7 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
190198
u_char *p;
191199
ngx_rtmp_hls_ctx_t *ctx;
192200
ssize_t n;
193-
ngx_int_t ffrag;
201+
uint64_t ffrag;
194202
ngx_rtmp_hls_app_conf_t *hacf;
195203
ngx_int_t nretry;
196204

@@ -223,17 +231,15 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
223231
return NGX_ERROR;
224232
}
225233

226-
ffrag = ctx->frag - hacf->winfrags;
227-
if (ffrag < 1) {
228-
ffrag = 1;
229-
}
234+
ffrag = ctx->frag > hacf->winfrags ?
235+
ctx->frag - (uint64_t) hacf->winfrags : 1;
230236

231237
p = ngx_snprintf(buffer, sizeof(buffer),
232238
"#EXTM3U\r\n"
233-
"#EXT-X-MEDIA-SEQUENCE:%i\r\n"
234-
"#EXT-X-TARGETDURATION:%i\r\n"
239+
"#EXT-X-MEDIA-SEQUENCE:%uL\r\n"
240+
"#EXT-X-TARGETDURATION:%ui\r\n"
235241
"#EXT-X-ALLOW-CACHE:NO\r\n\r\n",
236-
ctx->frag, (ngx_int_t) (hacf->fraglen / 1000));
242+
ctx->frag, (ngx_uint_t) (hacf->fraglen / 1000));
237243

238244
n = write(fd, buffer, p - buffer);
239245
if (n < 0) {
@@ -246,9 +252,9 @@ ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
246252
for (; ffrag < ctx->frag; ++ffrag) {
247253
p = ngx_snprintf(buffer, sizeof(buffer),
248254
"#EXTINF:%i,\r\n"
249-
"%V-%i.ts\r\n",
250-
(ngx_int_t) (hacf->fraglen / 1000),
251-
&ctx->name, ffrag % hacf->nfrags);
255+
"%V-%uL.ts\r\n",
256+
(ngx_int_t) (hacf->fraglen / 1000), &ctx->name,
257+
ngx_rtmp_hls_frag(hacf, ffrag));
252258

253259
n = write(fd, buffer, p - buffer);
254260
if (n < 0) {
@@ -469,13 +475,28 @@ ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)
469475
ctx->opened = 0;
470476
}
471477

478+
if (hacf->nfrags == 0 && ctx->frag > 2 * hacf->winfrags &&
479+
!hacf->nodelete)
480+
{
481+
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%uL.ts",
482+
ngx_rtmp_hls_frag(hacf, ctx->frag - 2 * hacf->winfrags))
483+
= 0;
484+
485+
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
486+
"hls: delete frag '%s'", ctx->stream.data);
487+
488+
ngx_delete_file(ctx->stream.data);
489+
490+
}
491+
472492
ctx->frag++;
473493

474-
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%i.ts",
475-
ctx->frag % hacf->nfrags) = 0;
494+
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%uL.ts",
495+
ngx_rtmp_hls_frag(hacf, ctx->frag)) = 0;
476496

477-
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
478-
"hls: next frag='%s'", ctx->stream.data);
497+
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
498+
"hls: open frag '%s', frag=%uL",
499+
ctx->stream.data, ctx->frag);
479500

480501
ngx_memzero(&ctx->file, sizeof(ctx->file));
481502

@@ -505,16 +526,17 @@ ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)
505526
}
506527

507528

529+
#define NGX_RTMP_HLS_RESTORE_PREFIX "#EXTM3U\r\n#EXT-X-MEDIA-SEQUENCE:"
530+
531+
508532
static void
509533
ngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s)
510534
{
511-
ngx_rtmp_hls_app_conf_t *hacf;
512535
ngx_rtmp_hls_ctx_t *ctx;
513536
ngx_file_t file;
514537
ssize_t ret;
515-
u_char buffer[31 + NGX_OFF_T_LEN];
516-
517-
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
538+
u_char buffer[sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) -
539+
1 + NGX_OFF_T_LEN];
518540

519541
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
520542

@@ -535,20 +557,25 @@ ngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s)
535557
goto done;
536558
}
537559

538-
/* read media sequence number */
560+
if ((size_t) ret < sizeof(NGX_RTMP_HLS_RESTORE_PREFIX)) {
561+
goto done;
562+
}
539563

540-
if (ret <= 31) {
564+
if (ngx_strncmp(buffer, NGX_RTMP_HLS_RESTORE_PREFIX,
565+
sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) - 1))
566+
{
567+
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
568+
"hls: failed to restore stream");
541569
goto done;
542570
}
543571

544572
buffer[ret] = 0;
545573

546-
ctx->frag = atoi((const char *) &buffer[31]);
547-
ctx->basetime = (uint64_t) ctx->frag * hacf->fraglen * 90;
574+
ctx->frag = strtod((const char *)
575+
&buffer[sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) - 1], NULL);
548576

549-
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
550-
"hls: restored frag=%i, basetime=%uL",
551-
ctx->frag, ctx-> basetime);
577+
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
578+
"hls: restored frag=%uL", ctx->frag);
552579

553580
done:
554581
ngx_close_file(file.fd);
@@ -629,6 +656,7 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
629656
p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1);
630657

631658
ctx->playlist_bak.len = p - ctx->playlist_bak.data;
659+
632660
*p = 0;
633661

634662
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
@@ -728,21 +756,29 @@ ngx_rtmp_hls_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype,
728756

729757

730758
static void
731-
ngx_rtmp_hls_switch_frag(ngx_rtmp_session_t *s, uint64_t ts)
759+
ngx_rtmp_hls_set_frag(ngx_rtmp_session_t *s, uint64_t ts)
732760
{
733761
ngx_rtmp_hls_app_conf_t *hacf;
734762
ngx_rtmp_hls_ctx_t *ctx;
735-
ngx_uint_t frag;
763+
uint64_t frag;
736764

737765
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
738766

739767
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
740768

741769
frag = ts / hacf->fraglen / 90;
742770

743-
while ((ctx->frag - frag) % hacf->nfrags) {
744-
ngx_rtmp_hls_next_frag(s);
771+
if (frag == ctx->frag && ctx->opened) {
772+
return;
745773
}
774+
775+
if (frag != ctx->frag + 1) {
776+
ctx->offset = (ctx->frag + 1) * (uint64_t) hacf->fraglen * 90 - ts;
777+
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
778+
"hls: time gap offset=%uL", ctx->offset);
779+
}
780+
781+
ngx_rtmp_hls_next_frag(s);
746782
}
747783

748784

@@ -778,14 +814,9 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
778814
return NGX_OK;
779815
}
780816

781-
if (!ctx->epoch_valid) {
782-
ctx->epoch = h->timestamp;
783-
ctx->epoch_valid = 1;
784-
}
785-
786817
ngx_memzero(&frame, sizeof(frame));
787818

788-
frame.dts = (h->timestamp - ctx->epoch)* 90L + ctx->basetime;
819+
frame.dts = (uint64_t) h->timestamp * 90L + ctx->offset;
789820
frame.cc = ctx->audio_cc;
790821
frame.pid = 0x101;
791822
frame.sid = 0xc0;
@@ -855,7 +886,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
855886
* However if video stream is missing then do it here */
856887

857888
if (codec_ctx->avc_header == NULL) {
858-
ngx_rtmp_hls_switch_frag(s, frame.dts);
889+
ngx_rtmp_hls_set_frag(s, frame.dts);
859890
}
860891

861892
if (!ctx->opened) {
@@ -871,7 +902,6 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
871902
}
872903

873904
ctx->audio_cc = frame.cc;
874-
ctx->timestamp = frame.dts;
875905

876906
return NGX_OK;
877907
}
@@ -1081,22 +1111,17 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
10811111
out.last += (len - 1);
10821112
}
10831113

1084-
if (!ctx->epoch_valid) {
1085-
ctx->epoch = h->timestamp;
1086-
ctx->epoch_valid = 1;
1087-
}
1088-
10891114
ngx_memzero(&frame, sizeof(frame));
10901115

10911116
frame.cc = ctx->video_cc;
1092-
frame.dts = (h->timestamp - ctx->epoch) * 90L + ctx->basetime;
1117+
frame.dts = (uint64_t) h->timestamp * 90L + ctx->offset;
10931118
frame.pts = frame.dts + cts * 90;
10941119
frame.pid = 0x100;
10951120
frame.sid = 0xe0;
10961121
frame.key = (ftype == 1);
10971122

10981123
if (frame.key) {
1099-
ngx_rtmp_hls_switch_frag(s, frame.dts);
1124+
ngx_rtmp_hls_set_frag(s, frame.dts);
11001125
}
11011126

11021127
if (!ctx->opened) {
@@ -1112,7 +1137,6 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
11121137
}
11131138

11141139
ctx->video_cc = frame.cc;
1115-
ctx->timestamp = frame.dts;
11161140

11171141
return NGX_OK;
11181142
}
@@ -1134,6 +1158,7 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
11341158
conf->sync = NGX_CONF_UNSET;
11351159
conf->playlen = NGX_CONF_UNSET;
11361160
conf->continuous = NGX_CONF_UNSET;
1161+
conf->nodelete = NGX_CONF_UNSET;
11371162
conf->factor = NGX_CONF_UNSET;
11381163
conf->nbuckets = 1024;
11391164

@@ -1154,6 +1179,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
11541179
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
11551180
ngx_conf_merge_str_value(conf->path, prev->path, "");
11561181
ngx_conf_merge_value(conf->continuous, prev->continuous, 1);
1182+
ngx_conf_merge_value(conf->nodelete, prev->nodelete, 0);
11571183
ngx_conf_merge_value(conf->factor, prev->factor, 2);
11581184

11591185
conf->ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_ctx_t *) *

0 commit comments

Comments
 (0)