From 4e85202bcc26b09f45d358da8f4d43aca0d27c0e Mon Sep 17 00:00:00 2001 From: Anssi Hannula Date: Mon, 30 Dec 2013 11:27:36 +0200 Subject: [PATCH] avformat/hls: improve segment selection when restarting list reception Improve selection of the segment sequence number when restarting the reception of a playlist after it was suspended due to being unneeded (due to discard flags). The current code assumes that each playlist contains matching data with the same sequence number, while spec 3.4.3 specifically says that that is not the case. Often subtitle playlists also have longer target durations as well, causing the selection to be completely wrong. Instead prefer using the playlist segment duration information for non-live playlists, and other means if that is not possible. Signed-off-by: Anssi Hannula --- libavformat/hls.c | 85 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index 4e037c1b9a..dfeb520a30 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -167,6 +167,7 @@ typedef struct HLSContext { int end_of_segment; int first_packet; int64_t first_timestamp; + int64_t cur_timestamp; AVIOInterruptCB *interrupt_callback; char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context @@ -948,6 +949,13 @@ cleanup: return ret; } +static int64_t default_reload_interval(struct playlist *pls) +{ + return pls->n_segments > 0 ? + pls->segments[pls->n_segments - 1]->duration : + pls->target_duration; +} + static int read_data(void *opaque, uint8_t *buf, int buf_size) { struct playlist *v = opaque; @@ -962,9 +970,7 @@ restart: if (!v->input) { /* If this is a live stream and the reload interval has elapsed since * the last playlist reload, reload the playlists now. */ - int64_t reload_interval = v->n_segments > 0 ? - v->segments[v->n_segments - 1]->duration : - v->target_duration; + int64_t reload_interval = default_reload_interval(v); reload: if (!v->finished && @@ -1141,6 +1147,43 @@ static int find_timestamp_in_playlist(HLSContext *c, struct playlist *pls, return 0; } +static int select_cur_seq_no(HLSContext *c, struct playlist *pls) +{ + int seq_no; + + if (!pls->finished && !c->first_packet && + av_gettime() - pls->last_load_time >= default_reload_interval(pls)) + /* reload the playlist since it was suspended */ + parse_playlist(c, pls->url, pls, NULL); + + /* If playback is already in progress (we are just selecting a new + * playlist) and this is a complete file, find the matching segment + * by counting durations. */ + if (pls->finished && c->cur_timestamp != AV_NOPTS_VALUE) { + find_timestamp_in_playlist(c, pls, c->cur_timestamp, &seq_no); + return seq_no; + } + + if (!pls->finished) { + if (!c->first_packet && /* we are doing a segment selection during playback */ + c->cur_seq_no >= pls->start_seq_no && + c->cur_seq_no < pls->start_seq_no + pls->n_segments) + /* While spec 3.4.3 says that we cannot assume anything about the + * content at the same sequence number on different playlists, + * in practice this seems to work and doing it otherwise would + * require us to download a segment to inspect its timestamps. */ + return c->cur_seq_no; + + /* If this is a live stream with more than 3 segments, start at the + * third last segment. */ + if (pls->n_segments > 3) + return pls->start_seq_no + pls->n_segments - 3; + } + + /* Otherwise just start on the first segment. */ + return pls->start_seq_no; +} + static int hls_read_header(AVFormatContext *s) { URLContext *u = (s->flags & AVFMT_FLAG_CUSTOM_IO) ? NULL : s->pb->opaque; @@ -1149,6 +1192,10 @@ static int hls_read_header(AVFormatContext *s) c->interrupt_callback = &s->interrupt_callback; + c->first_packet = 1; + c->first_timestamp = AV_NOPTS_VALUE; + c->cur_timestamp = AV_NOPTS_VALUE; + // if the URL context is good, read important options we must broker later if (u && u->prot->priv_data_class) { // get the previous user agent & set back to null if string size is zero @@ -1231,12 +1278,7 @@ static int hls_read_header(AVFormatContext *s) pls->index = i; pls->needed = 1; pls->parent = s; - - /* If this is a live stream with more than 3 segments, start at the - * third last segment. */ - pls->cur_seq_no = pls->start_seq_no; - if (!pls->finished && pls->n_segments > 3) - pls->cur_seq_no = pls->start_seq_no + pls->n_segments - 3; + pls->cur_seq_no = select_cur_seq_no(c, pls); pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls, @@ -1330,9 +1372,6 @@ static int hls_read_header(AVFormatContext *s) } } - c->first_packet = 1; - c->first_timestamp = AV_NOPTS_VALUE; - return 0; fail: free_playlist_list(c); @@ -1361,9 +1400,14 @@ static int recheck_discard_flags(AVFormatContext *s, int first) if (pls->cur_needed && !pls->needed) { pls->needed = 1; changed = 1; - pls->cur_seq_no = c->cur_seq_no; + pls->cur_seq_no = select_cur_seq_no(c, pls); pls->pb.eof_reached = 0; - av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i); + if (c->cur_timestamp != AV_NOPTS_VALUE) { + /* catch up */ + pls->seek_timestamp = c->cur_timestamp; + pls->seek_flags = AVSEEK_FLAG_ANY; + } + av_log(s, AV_LOG_INFO, "Now receiving playlist %d, segment %d\n", i, pls->cur_seq_no); } else if (first && !pls->cur_needed && pls->needed) { if (pls->input) ffurl_close(pls->input); @@ -1505,9 +1549,16 @@ start: } /* If we got a packet, return it */ if (minplaylist >= 0) { - *pkt = c->playlists[minplaylist]->pkt; - pkt->stream_index += c->playlists[minplaylist]->stream_offset; + struct playlist *pls = c->playlists[minplaylist]; + *pkt = pls->pkt; + pkt->stream_index += pls->stream_offset; reset_packet(&c->playlists[minplaylist]->pkt); + + if (pkt->dts != AV_NOPTS_VALUE) + c->cur_timestamp = av_rescale_q(pkt->dts, + pls->ctx->streams[pls->pkt.stream_index]->time_base, + AV_TIME_BASE_Q); + return 0; } return AVERROR_EOF; @@ -1581,6 +1632,8 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, find_timestamp_in_playlist(c, pls, seek_timestamp, &pls->cur_seq_no); } + c->cur_timestamp = seek_timestamp; + return 0; }