[FFmpeg-devel] [PATCH 15/18] avformat/hls: improve segment selection when restarting list reception

Anssi Hannula anssi.hannula at iki.fi
Mon Dec 30 12:14:29 CET 2013


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 <anssi.hannula at iki.fi>
---
 libavformat/hls.c | 83 ++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 67 insertions(+), 16 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 7055294..bb75071 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -158,6 +158,7 @@ typedef struct HLSContext {
     int end_of_segment;
     int first_packet;
     int64_t first_timestamp;
+    int64_t cur_timestamp;
     AVIOInterruptCB *interrupt_callback;
     AVDictionary *demuxer_options;
     char *user_agent;                    ///< holds HTTP user agent set as an AVOption to the HTTP protocol context
@@ -744,6 +745,13 @@ static void after_segment_switch(struct playlist *pls)
         pls->reopen_subtitle = 1;
 }
 
+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;
@@ -760,9 +768,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 &&
@@ -960,6 +966,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;
@@ -968,6 +1011,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
@@ -1053,12 +1100,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,
@@ -1150,9 +1192,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);
@@ -1182,10 +1221,15 @@ 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;
+            if (c->cur_timestamp != AV_NOPTS_VALUE) {
+                /* catch up */
+                pls->seek_timestamp = c->cur_timestamp;
+                pls->seek_flags = AVSEEK_FLAG_ANY;
+            }
             after_segment_switch(pls);
-            av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i);
+            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);
@@ -1338,9 +1382,14 @@ 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, get_timebase(pls, pls->pkt.stream_index), AV_TIME_BASE_Q);
+
         return 0;
     }
     return AVERROR_EOF;
@@ -1416,6 +1465,8 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
         after_segment_switch(pls);
     }
 
+    c->cur_timestamp = seek_timestamp;
+
     return 0;
 }
 
-- 
1.8.1.5



More information about the ffmpeg-devel mailing list