[FFmpeg-devel] [PATCH 09/19] avformat/hls: demux each subtitle segment separately

Anssi Hannula anssi.hannula at iki.fi
Fri Jan 3 15:21:33 CET 2014


Subtitle demuxers (e.g. WebVTT used in HLS) read the entire file in
read_header(), so only provide them a single segment at a time, and open
the demuxer again for a following segment.

v2: After seeking the subtitle subdemuxer has to be reopened before
    reading any frames, since the frames are buffered by the
    subdemuxer.

Signed-off-by: Anssi Hannula <anssi.hannula at iki.fi>
---

Alternative to this hackishness would be to handle subtitle streams
completely differently by opening the demuxer, reading all the
AVPackets to a local buffer and closing the demuxer, and instead of
av_read_frame() read packets from the local buffer. However, that
sounds to me like it might add quite a lot of code/complexity since
it differs from the handling of other streams a lot more than this
patch... I'll think about it a bit more, though.

TODO: check if after_segment_switch() needs to avoid changing
REOPEN_BEFORE_READ => REOPEN_AFTER_CONSUMED.

 libavformat/hls.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 61 insertions(+), 2 deletions(-)

diff --git a/libavformat/hls.c b/libavformat/hls.c
index a211941..32785ba 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -71,6 +71,12 @@ struct segment {
 
 struct rendition;
 
+enum Reopen {
+    NO_REOPEN,
+    REOPEN_AFTER_CONSUMED,
+    REOPEN_BEFORE_READ,
+};
+
 /*
  * Each playlist has its own demuxer. If it currently is active,
  * it has an open AVIOContext too, and potentially an AVPacket
@@ -101,6 +107,9 @@ struct playlist {
     char key_url[MAX_URL_SIZE];
     uint8_t key[16];
 
+    int is_subtitle;
+    enum Reopen reopen_subtitle;
+
     /* Renditions associated with this playlist, if any.
      * Alternative rendition playlists have a single rendition associated
      * with them, and variant main Media Playlists may have
@@ -366,9 +375,12 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf
     /* add the playlist if this is an external rendition */
     if (info->uri[0]) {
         rend->playlist = new_playlist(c, info->uri, url_base);
-        if (rend->playlist)
+        if (rend->playlist) {
             dynarray_add(&rend->playlist->renditions,
                          &rend->playlist->n_renditions, rend);
+            if (type == AVMEDIA_TYPE_SUBTITLE)
+                rend->playlist->is_subtitle = 1;
+        }
     }
 
     if (info->assoc_language[0]) {
@@ -709,6 +721,14 @@ cleanup:
     return ret;
 }
 
+static void after_segment_switch(struct playlist *pls)
+{
+    /* subtitle demuxers may try to consume the entire stream, so report
+     * EOF to them on segment switches and reopen on next request */
+    if (pls->is_subtitle && pls->ctx)
+        pls->reopen_subtitle = REOPEN_AFTER_CONSUMED;
+}
+
 static int read_data(void *opaque, uint8_t *buf, int buf_size)
 {
     struct playlist *v = opaque;
@@ -717,7 +737,7 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
     int actual_read_size;
     struct segment *seg;
 
-    if (!v->needed)
+    if (!v->needed || v->reopen_subtitle)
         return AVERROR_EOF;
 
 restart:
@@ -784,6 +804,7 @@ reload:
 
     c->end_of_segment = 1;
     c->cur_seq_no = v->cur_seq_no;
+    after_segment_switch(v);
 
     if (v->ctx && v->ctx->nb_streams &&
         v->parent->nb_streams >= v->stream_offset + v->ctx->nb_streams) {
@@ -1088,6 +1109,7 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
             changed = 1;
             pls->cur_seq_no = c->cur_seq_no;
             pls->pb.eof_reached = 0;
+            after_segment_switch(pls);
             av_log(s, AV_LOG_INFO, "Now receiving playlist %d\n", i);
         } else if (first && !pls->cur_needed && pls->needed) {
             if (pls->input)
@@ -1101,6 +1123,28 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
     return changed;
 }
 
+static int reopen_subtitle_playlist(HLSContext *c, struct playlist *pls)
+{
+    /* each subtitle segment is demuxed separately */
+    struct AVFormatContext *newctx;
+    int ret;
+
+    pls->reopen_subtitle = NO_REOPEN;
+
+    if (!(newctx = avformat_alloc_context()))
+        return AVERROR(ENOMEM);
+
+    newctx->pb = &pls->pb;
+
+    ret = avformat_open_input(&newctx, NULL, pls->ctx->iformat, NULL);
+    if (ret == 0) {
+        avformat_close_input(&pls->ctx);
+        pls->ctx = newctx;
+    }
+
+    return ret;
+}
+
 static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
 {
     HLSContext *c = s->priv_data;
@@ -1121,11 +1165,21 @@ start:
             while (1) {
                 int64_t ts_diff;
                 AVRational tb;
+
+                if (pls->reopen_subtitle == REOPEN_BEFORE_READ)
+                    reopen_subtitle_playlist(c, pls);
+
                 ret = av_read_frame(pls->ctx, &pls->pkt);
+
                 if (ret < 0) {
                     if (!url_feof(&pls->pb) && ret != AVERROR_EOF)
                         return ret;
                     reset_packet(&pls->pkt);
+
+                    if (pls->reopen_subtitle &&
+                        reopen_subtitle_playlist(c, pls) == 0)
+                        continue;
+
                     break;
                 } else {
 
@@ -1256,6 +1310,11 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
         pls->next_seg_bytepos = 0;
         set_audio_id3_tag_offset(pls);
 
+        after_segment_switch(pls);
+        /* there are old subtitle packets in the demuxer buffer */
+        if (pls->reopen_subtitle == REOPEN_AFTER_CONSUMED)
+            pls->reopen_subtitle = REOPEN_BEFORE_READ;
+
         /* Locate the segment that contains the target timestamp */
         for (j = 0; j < pls->n_segments; j++) {
             if (timestamp >= pos &&
-- 
1.8.1.5



More information about the ffmpeg-devel mailing list