[FFmpeg-devel] [PATCH] Progressive HLS support. When switched on 2 HLS streams will be generated: full + audio only. Also will be generated root m3u8 file for both streams. That functionality tested on spreeacst.com platform within last few mounths and work well on iOS devices and desktop safari.

Vlad Kuznetsov <> vlad at spreecast.com
Wed Dec 31 19:33:34 CET 2014


From: Vladimir Kuznetsov <vlad at spreecast.com>

Client (mobile or desctop) will automatically choose the stream (full or adio only) depends on current network conditions.
---
 libavformat/segment.c | 630 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 418 insertions(+), 212 deletions(-)

diff --git a/libavformat/segment.c b/libavformat/segment.c
index 97a0db2..485cdc6 100644
--- a/libavformat/segment.c
+++ b/libavformat/segment.c
@@ -52,6 +52,14 @@ typedef struct SegmentListEntry {
     int64_t last_duration;
 } SegmentListEntry;
 
+typedef struct SegmentEntryWrapper
+{
+    double start_time;
+    SegmentListEntry cur_entry;
+    SegmentListEntry *segment_list_entries;
+    SegmentListEntry *segment_list_entries_end; 
+} SegmentEntryWrapper;
+
 typedef enum {
     LIST_TYPE_UNDEFINED = -1,
     LIST_TYPE_FLAT = 0,
@@ -65,6 +73,9 @@ typedef enum {
 #define SEGMENT_LIST_FLAG_CACHE 1
 #define SEGMENT_LIST_FLAG_LIVE  2
 
+#define SEGMENT_ENTRY_FULL 0
+#define SEGMENT_ENTRY_AUDIO_ONLY 1
+
 typedef struct {
     const AVClass *class;  /**< Class for private options. */
     int segment_idx;       ///< index of the segment file to write, starting from 0
@@ -72,7 +83,8 @@ typedef struct {
     int segment_idx_wrap_nb;  ///< number of time the index has wraped
     int segment_count;     ///< number of segment files already written
     AVOutputFormat *oformat;
-    AVFormatContext *avf;
+    AVFormatContext *avf[2];
+    int64_t stream_size[2];
     char *format;              ///< format to use for output segment files
     char *format_options_str;  ///< format options to use for output segment files
     AVDictionary *format_options;
@@ -87,7 +99,7 @@ typedef struct {
 
     char *entry_prefix;    ///< prefix to add to list entry filenames
     ListType list_type;    ///< set the list type
-    AVIOContext *list_pb;  ///< list file put-byte context
+    AVIOContext *list_pb[2];  ///< list file put-byte context
     char *time_str;        ///< segment duration specification string
     int64_t time;          ///< segment duration
     int use_strftime;      ///< flag to expand filename with strftime
@@ -111,9 +123,15 @@ typedef struct {
     char *reference_stream_specifier; ///< reference stream specifier
     int   reference_stream_index;
 
-    SegmentListEntry cur_entry;
-    SegmentListEntry *segment_list_entries;
-    SegmentListEntry *segment_list_entries_end;
+    int progressive;
+    char *audio_list;
+    char *root_m3u8;
+    char *vc_tag;
+    char *ac_tag;
+
+    SegmentEntryWrapper entry_wrapper[2];
+
+    int is_first_pkt;      ///< tells if it is the first packet in the segment
 } SegmentContext;
 
 static void print_csv_escaped_str(AVIOContext *ctx, const char *str)
@@ -136,37 +154,43 @@ static int segment_mux_init(AVFormatContext *s)
 {
     SegmentContext *seg = s->priv_data;
     AVFormatContext *oc;
-    int i;
-    int ret;
-
-    ret = avformat_alloc_output_context2(&seg->avf, seg->oformat, NULL, NULL);
-    if (ret < 0)
-        return ret;
-    oc = seg->avf;
+    AVStream *st;
+    AVCodecContext *icodec, *ocodec;
+    int iCount = seg->progressive?2:1;
+    for (int formats_num = 0; formats_num < iCount; ++formats_num) {
+        int i;
+        int ret;
+
+        ret = avformat_alloc_output_context2(&seg->avf[formats_num], seg->oformat, NULL, NULL);
+        if (ret < 0)
+            return ret;
+        oc = seg->avf[formats_num];
 
-    oc->interrupt_callback = s->interrupt_callback;
-    oc->max_delay          = s->max_delay;
-    av_dict_copy(&oc->metadata, s->metadata, 0);
+        oc->interrupt_callback = s->interrupt_callback;
+        oc->max_delay          = s->max_delay;
+        av_dict_copy(&oc->metadata, s->metadata, 0);
 
-    for (i = 0; i < s->nb_streams; i++) {
-        AVStream *st;
-        AVCodecContext *icodec, *ocodec;
+        for (i = 0; i < s->nb_streams; i++) {
+            if (formats_num == SEGMENT_ENTRY_AUDIO_ONLY && s->streams[i]->codec->codec_type != AVMEDIA_TYPE_AUDIO) {
+                continue;
+            }
 
-        if (!(st = avformat_new_stream(oc, NULL)))
-            return AVERROR(ENOMEM);
-        icodec = s->streams[i]->codec;
-        ocodec = st->codec;
-        avcodec_copy_context(ocodec, icodec);
-        if (!oc->oformat->codec_tag ||
-            av_codec_get_id (oc->oformat->codec_tag, icodec->codec_tag) == ocodec->codec_id ||
-            av_codec_get_tag(oc->oformat->codec_tag, icodec->codec_id) <= 0) {
-            ocodec->codec_tag = icodec->codec_tag;
-        } else {
-            ocodec->codec_tag = 0;
+            if (!(st = avformat_new_stream(oc, NULL)))
+                return AVERROR(ENOMEM);
+            icodec = s->streams[i]->codec;
+            ocodec = st->codec;
+            avcodec_copy_context(ocodec, icodec);
+            if (!oc->oformat->codec_tag ||
+                av_codec_get_id (oc->oformat->codec_tag, icodec->codec_tag) == ocodec->codec_id ||
+                av_codec_get_tag(oc->oformat->codec_tag, icodec->codec_id) <= 0) {
+                ocodec->codec_tag = icodec->codec_tag;
+            } else {
+                ocodec->codec_tag = 0;
+            }
+            st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
+            st->time_base = s->streams[i]->time_base;
+            av_dict_copy(&st->metadata, s->streams[i]->metadata, 0);
         }
-        st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
-        st->time_base = s->streams[i]->time_base;
-        av_dict_copy(&st->metadata, s->streams[i]->metadata, 0);
     }
 
     return 0;
@@ -175,110 +199,197 @@ static int segment_mux_init(AVFormatContext *s)
 static int set_segment_filename(AVFormatContext *s)
 {
     SegmentContext *seg = s->priv_data;
-    AVFormatContext *oc = seg->avf;
-    size_t size;
-
+    AVFormatContext *oc;
+    int iCount = seg->progressive?2:1;
     if (seg->segment_idx_wrap)
         seg->segment_idx %= seg->segment_idx_wrap;
-    if (seg->use_strftime) {
-        time_t now0;
-        struct tm *tm, tmpbuf;
-        time(&now0);
-        tm = localtime_r(&now0, &tmpbuf);
-        if (!strftime(oc->filename, sizeof(oc->filename), s->filename, tm)) {
-            av_log(oc, AV_LOG_ERROR, "Could not get segment filename with strftime\n");
+
+    for (int cnt = 0; cnt < iCount; ++cnt) {
+        oc = seg->avf[cnt];
+        size_t size;
+
+        if (seg->segment_idx_wrap)
+            seg->segment_idx %= seg->segment_idx_wrap;
+        if (seg->use_strftime) {
+            time_t now0;
+            struct tm *tm, tmpbuf;
+            time(&now0);
+            tm = localtime_r(&now0, &tmpbuf);
+            if (!strftime(oc->filename, sizeof(oc->filename), s->filename, tm)) {
+                av_log(oc, AV_LOG_ERROR, "Could not get segment filename with strftime\n");
+                return AVERROR(EINVAL);
+            }
+        } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
+                                         s->filename, seg->segment_idx) < 0) {
+            av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename);
             return AVERROR(EINVAL);
         }
-    } else if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
-                                     s->filename, seg->segment_idx) < 0) {
-        av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename);
-        return AVERROR(EINVAL);
-    }
+        if (seg->progressive) {
+            int dir_sz;
+            char fname[1024];
+            char* bname = (char*)av_basename(oc->filename);
+            dir_sz = strlen(oc->filename) - strlen(bname) + 1;
+            if (cnt==SEGMENT_ENTRY_AUDIO_ONLY) {
+                snprintf(fname, 1024, "audio-%s", bname);
+            } else {
+                snprintf(fname, 1024, "video-%s", bname);
+            }
+            snprintf(bname, 1024 - dir_sz, "/%s", fname);
+        }
 
-    /* copy modified name in list entry */
-    size = strlen(av_basename(oc->filename)) + 1;
-    if (seg->entry_prefix)
-        size += strlen(seg->entry_prefix);
 
-    seg->cur_entry.filename = av_mallocz(size);
-    if (!seg->cur_entry.filename)
-        return AVERROR(ENOMEM);
-    snprintf(seg->cur_entry.filename, size, "%s%s",
-             seg->entry_prefix ? seg->entry_prefix : "",
-             av_basename(oc->filename));
+        /* copy modified name in list entry */
+        size = strlen(av_basename(oc->filename)) + 1;
+        if (seg->entry_prefix)
+            size += strlen(seg->entry_prefix);
 
+        seg->entry_wrapper[cnt].cur_entry.filename = av_mallocz(size);
+        if (!seg->entry_wrapper[cnt].cur_entry.filename)
+            return AVERROR(ENOMEM);
+        snprintf(seg->entry_wrapper[cnt].cur_entry.filename, size, "%s%s",
+                 seg->entry_prefix ? seg->entry_prefix : "",
+                 av_basename(oc->filename));
+    }
     return 0;
 }
 
 static int segment_start(AVFormatContext *s, int write_header)
 {
     SegmentContext *seg = s->priv_data;
-    AVFormatContext *oc = seg->avf;
+    AVFormatContext *oc;
+    int iCount = seg->progressive?2:1;
     int err = 0;
-
-    if (write_header) {
-        avformat_free_context(oc);
-        seg->avf = NULL;
-        if ((err = segment_mux_init(s)) < 0)
-            return err;
-        oc = seg->avf;
+    for (int cnt = 0; cnt < iCount; ++cnt) {
+        oc = seg->avf[cnt];
+        if (write_header) {
+            avformat_free_context(oc);
+            seg->avf[cnt] = NULL;
+            if ((err = segment_mux_init(s)) < 0)
+                return err;
+            oc = seg->avf[cnt];
+        }
     }
 
-    seg->segment_idx++;
     if ((seg->segment_idx_wrap) && (seg->segment_idx%seg->segment_idx_wrap == 0))
         seg->segment_idx_wrap_nb++;
 
     if ((err = set_segment_filename(s)) < 0)
         return err;
 
-    if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
-                          &s->interrupt_callback, NULL)) < 0) {
-        av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
-        return err;
-    }
+    seg->segment_idx++;
 
-    if (oc->oformat->priv_class && oc->priv_data)
-        av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0);
+    for (int cnt = 0; cnt < iCount; ++cnt) {
+        oc = seg->avf[cnt];
 
-    if (write_header) {
-        if ((err = avformat_write_header(oc, NULL)) < 0)
+        if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
+                              &s->interrupt_callback, NULL)) < 0) {
+            av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
             return err;
-    }
+        }
 
-    seg->segment_frame_count = 0;
+        if (oc->oformat->priv_class && oc->priv_data)
+            av_opt_set(oc->priv_data, "mpegts_flags", "+resend_headers", 0);
+
+        if (write_header) {
+            if ((err = avformat_write_header(oc, NULL)) < 0)
+                return err;
+        }
+    
+        seg->is_first_pkt = 1;
+        seg->segment_frame_count = 0;
+    }
     return 0;
 }
 
-static int segment_list_open(AVFormatContext *s)
+static int root_m3u8_create(AVFormatContext *s)
 {
     SegmentContext *seg = s->priv_data;
+    AVStream *video = 0;
+    AVStream *audio = 0;
+    for (int i = 0; i < s->nb_streams; i++) {
+        if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
+            video = s->streams[i];
+        } else if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+            audio = s->streams[i];
+        }
+    }
+    if (seg->list_type != LIST_TYPE_M3U8 || seg->root_m3u8 == 0) {
+        return 0;
+    }
     int ret;
+    AVIOContext *pb = 0;
+    ret = avio_open2(&pb, seg->root_m3u8, AVIO_FLAG_WRITE,
+                     &s->interrupt_callback, NULL);
+    if (ret < 0) {
+        av_log(s, AV_LOG_ERROR, "Failed to open root segment list '%s'\n", seg->root_m3u8);
+        return ret;
+    }
+    double full_duration = (seg->entry_wrapper[SEGMENT_ENTRY_FULL].cur_entry.end_time - seg->entry_wrapper[SEGMENT_ENTRY_FULL].start_time);
+    int full_br = seg->stream_size[SEGMENT_ENTRY_FULL] * 8 / full_duration;
+    int w = video ? video->codec->width : 0;
+    int h = video ? video->codec->height : 0;
+     
+    avio_printf(pb, "#EXTM3U\n");
+    avio_printf(pb, "#EXT-X-VERSION:3\n");
+    avio_printf(pb, "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%d,CODECS=\"%s, %s\",RESOLUTION=%dx%d\n", full_br, seg->vc_tag, seg->ac_tag, w, h);
+    avio_printf(pb, "%s\n", av_basename(seg->list));
+
+    if (seg->progressive) {
+        double a_duration = (seg->entry_wrapper[SEGMENT_ENTRY_AUDIO_ONLY].cur_entry.end_time - seg->entry_wrapper[SEGMENT_ENTRY_AUDIO_ONLY].start_time);
+        int a_br = seg->stream_size[SEGMENT_ENTRY_AUDIO_ONLY] * 8 / a_duration;
+        avio_printf(pb, "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=%d,CODECS=\"%s\"\n", a_br, seg->ac_tag);
+        avio_printf(pb, "%s\n", av_basename(seg->audio_list));
+    }
+    
+    avio_close(pb);
+    return ret;
+}
 
-    ret = avio_open2(&seg->list_pb, seg->list, AVIO_FLAG_WRITE,
+static int segment_list_open_num(AVFormatContext *s, int num)
+{
+    SegmentContext *seg = s->priv_data;
+    SegmentListEntry *entry;
+    int ret;
+    char* list = num==SEGMENT_ENTRY_FULL?seg->list:seg->audio_list;
+    ret = avio_open2(&seg->list_pb[num], list, AVIO_FLAG_WRITE,
                      &s->interrupt_callback, NULL);
     if (ret < 0) {
-        av_log(s, AV_LOG_ERROR, "Failed to open segment list '%s'\n", seg->list);
+        av_log(s, AV_LOG_ERROR, "Failed to open segment list '%s'\n", list);
         return ret;
     }
 
-    if (seg->list_type == LIST_TYPE_M3U8 && seg->segment_list_entries) {
-        SegmentListEntry *entry;
+    if (seg->list_type == LIST_TYPE_M3U8 && seg->entry_wrapper[num].segment_list_entries) {
         double max_duration = 0;
 
-        avio_printf(seg->list_pb, "#EXTM3U\n");
-        avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n");
-        avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->segment_list_entries->index);
-        avio_printf(seg->list_pb, "#EXT-X-ALLOW-CACHE:%s\n",
+        avio_printf(seg->list_pb[num], "#EXTM3U\n");
+        avio_printf(seg->list_pb[num], "#EXT-X-VERSION:3\n");
+        avio_printf(seg->list_pb[num], "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->entry_wrapper[num].segment_list_entries->index);
+        avio_printf(seg->list_pb[num], "#EXT-X-ALLOW-CACHE:%s\n",
                     seg->list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO");
 
         av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%d\n",
-               seg->segment_list_entries->index);
+               seg->entry_wrapper[num].segment_list_entries->index);
 
-        for (entry = seg->segment_list_entries; entry; entry = entry->next)
+        for (entry = seg->entry_wrapper[num].segment_list_entries; entry; entry = entry->next)
             max_duration = FFMAX(max_duration, entry->end_time - entry->start_time);
-        avio_printf(seg->list_pb, "#EXT-X-TARGETDURATION:%"PRId64"\n", (int64_t)ceil(max_duration));
+        avio_printf(seg->list_pb[num], "#EXT-X-TARGETDURATION:%"PRId64"\n", (int64_t)ceil(max_duration));
     } else if (seg->list_type == LIST_TYPE_FFCONCAT) {
-        avio_printf(seg->list_pb, "ffconcat version 1.0\n");
+        avio_printf(seg->list_pb[num], "ffconcat version 1.0\n");
+    }
+
+    return ret;
+}
+
+static int segment_list_open(AVFormatContext *s)
+{
+    SegmentContext *seg = s->priv_data;
+    int ret;
+
+    int iCount = seg->progressive?2:1;
+    for (int cnt = 0; cnt < iCount; ++cnt) {
+        ret = segment_list_open_num(s, cnt);
+        if (ret < 0)
+            break;
     }
 
     return ret;
@@ -322,61 +433,90 @@ static void segment_list_print_entry(AVIOContext      *list_ioctx,
 static int segment_end(AVFormatContext *s, int write_trailer, int is_last)
 {
     SegmentContext *seg = s->priv_data;
-    AVFormatContext *oc = seg->avf;
+    AVFormatContext *oc;
+    int iCount = seg->progressive?2:1;
     int ret = 0;
-
-    av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */
-    if (write_trailer)
-        ret = av_write_trailer(oc);
-
-    if (ret < 0)
-        av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
-               oc->filename);
+    int cnt;
+    for (cnt = 0; cnt < iCount; ++cnt) {
+        if (seg->list) {
+            avio_close(seg->list_pb[cnt]);
+        }
+    }
 
     if (seg->list) {
-        if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) {
-            SegmentListEntry *entry = av_mallocz(sizeof(*entry));
-            if (!entry) {
-                ret = AVERROR(ENOMEM);
-                goto end;
+        if ((ret = segment_list_open(s)) < 0) {
+            for (cnt = 0; cnt < iCount; ++cnt) {
+                oc = seg->avf[cnt];
+                avio_close(oc->pb);
             }
+        }
+    }
 
-            /* append new element */
-            memcpy(entry, &seg->cur_entry, sizeof(*entry));
-            if (!seg->segment_list_entries)
-                seg->segment_list_entries = seg->segment_list_entries_end = entry;
-            else
-                seg->segment_list_entries_end->next = entry;
-            seg->segment_list_entries_end = entry;
-
-            /* drop first item */
-            if (seg->list_size && seg->segment_count >= seg->list_size) {
-                entry = seg->segment_list_entries;
-                seg->segment_list_entries = seg->segment_list_entries->next;
-                av_freep(&entry->filename);
-                av_freep(&entry);
+    for (cnt = 0; cnt < iCount; ++cnt) {
+        oc = seg->avf[cnt];
+
+        av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */
+        if (write_trailer)
+            ret = av_write_trailer(oc);
+
+        if (ret < 0)
+            av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
+                   oc->filename);
+
+        if (seg->list) {
+            if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) {
+                SegmentListEntry *entry = av_mallocz(sizeof(*entry));
+                if (!entry) {
+                    ret = AVERROR(ENOMEM);
+                    goto end;
+                }
+
+                /* append new element */
+                memcpy(entry, &seg->entry_wrapper[cnt].cur_entry, sizeof(*entry));
+                if (!seg->entry_wrapper[cnt].segment_list_entries) {
+                    seg->entry_wrapper[cnt].segment_list_entries = seg->entry_wrapper[cnt].segment_list_entries_end = entry;
+                    segment_list_open_num(s, cnt);
+                } else {
+                    seg->entry_wrapper[cnt].segment_list_entries_end->next = entry;
+                }
+                seg->entry_wrapper[cnt].segment_list_entries_end = entry;
+
+                /* drop first item */
+                if (seg->list_size && seg->segment_count >= seg->list_size) {
+                    entry = seg->entry_wrapper[cnt].segment_list_entries;
+                    seg->entry_wrapper[cnt].segment_list_entries = seg->entry_wrapper[cnt].segment_list_entries->next;
+                    av_freep(&entry->filename);
+                    av_freep(&entry);
+                }
+
+                avio_close(seg->list_pb[cnt]);
+                if ((ret = segment_list_open_num(s,cnt)) < 0)
+                    goto end;
+                for (entry = seg->entry_wrapper[cnt].segment_list_entries; entry; entry = entry->next)
+                    segment_list_print_entry(seg->list_pb[cnt], seg->list_type, entry, s);
+                if (seg->list_type == LIST_TYPE_M3U8 && is_last)
+                    avio_printf(seg->list_pb[cnt], "#EXT-X-ENDLIST\n");
+            } else {
+                segment_list_print_entry(seg->list_pb[cnt], seg->list_type, &seg->entry_wrapper[cnt].cur_entry, s);
             }
+            avio_flush(seg->list_pb[cnt]);
+        }
 
-            avio_close(seg->list_pb);
-            if ((ret = segment_list_open(s)) < 0)
-                goto end;
-            for (entry = seg->segment_list_entries; entry; entry = entry->next)
-                segment_list_print_entry(seg->list_pb, seg->list_type, entry, s);
-            if (seg->list_type == LIST_TYPE_M3U8 && is_last)
-                avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n");
-        } else {
-            segment_list_print_entry(seg->list_pb, seg->list_type, &seg->cur_entry, s);
+        av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n",
+               seg->avf[cnt]->filename, seg->segment_count);
+    end:
+        seg->stream_size[cnt] += avio_size(oc->pb);
+        if (seg->entry_wrapper[cnt].start_time == -1) {
+            seg->entry_wrapper[cnt].start_time = seg->entry_wrapper[cnt].cur_entry.start_time;
         }
-        avio_flush(seg->list_pb);
+        avio_close(oc->pb);
     }
-
-    av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n",
-           seg->avf->filename, seg->segment_count);
     seg->segment_count++;
 
-end:
-    avio_close(oc->pb);
-
+    if ( (seg->list_type == LIST_TYPE_M3U8 && is_last)
+        || (seg->list_type == LIST_TYPE_M3U8 && seg->list && seg->list_size)) {
+        ret = root_m3u8_create(s);
+    }
     return ret;
 }
 
@@ -583,6 +723,8 @@ static int seg_write_header(AVFormatContext *s)
     int i;
 
     seg->segment_count = 0;
+    seg->entry_wrapper[0].start_time = -1;
+    seg->entry_wrapper[1].start_time = -1;
     if (!seg->write_header_trailer)
         seg->individual_header_trailer = 0;
 
@@ -630,6 +772,8 @@ static int seg_write_header(AVFormatContext *s)
         }
         if ((ret = segment_list_open(s)) < 0)
             goto fail;
+        seg->stream_size[0] = 0;
+        seg->stream_size[1] = 0;
     }
     if (seg->list_type == LIST_TYPE_EXT)
         av_log(s, AV_LOG_WARNING, "'ext' list type option is deprecated in favor of 'csv'\n");
@@ -655,61 +799,88 @@ static int seg_write_header(AVFormatContext *s)
 
     if ((ret = segment_mux_init(s)) < 0)
         goto fail;
-    oc = seg->avf;
 
     if ((ret = set_segment_filename(s)) < 0)
         goto fail;
 
-    if (seg->write_header_trailer) {
-        if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
-                              &s->interrupt_callback, NULL)) < 0) {
-            av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
-            goto fail;
+    seg->segment_idx++;
+    seg->is_first_pkt = 1;
+    
+    int iCount = seg->progressive?2:1;
+    int cnt;
+    for (cnt = 0; cnt < iCount; ++cnt) {
+        oc = seg->avf[cnt];
+
+        if (seg->write_header_trailer) {
+            if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
+                                  &s->interrupt_callback, NULL)) < 0) {
+                av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", oc->filename);
+                goto fail;
+            }
+        } else {
+            if ((ret = open_null_ctx(&oc->pb)) < 0)
+                goto fail;
         }
-    } else {
-        if ((ret = open_null_ctx(&oc->pb)) < 0)
-            goto fail;
-    }
 
-    av_dict_copy(&options, seg->format_options, 0);
-    ret = avformat_write_header(oc, &options);
-    if (av_dict_count(options)) {
-        av_log(s, AV_LOG_ERROR,
-               "Some of the provided format options in '%s' are not recognized\n", seg->format_options_str);
-        ret = AVERROR(EINVAL);
-        goto fail;
-    }
+        av_dict_copy(&options, seg->format_options, 0);
+        ret = avformat_write_header(oc, &options);
+        if (av_dict_count(options)) {
+            av_log(s, AV_LOG_ERROR,
+                   "Some of the provided format options in '%s' are not recognized\n", seg->format_options_str);
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
 
-    if (ret < 0) {
-        avio_close(oc->pb);
-        goto fail;
-    }
-    seg->segment_frame_count = 0;
+        if (ret < 0) {
+            avio_close(oc->pb);
+            goto fail;
+        }
+        seg->segment_frame_count = 0;
+
+        if (cnt != SEGMENT_ENTRY_AUDIO_ONLY) {
+            av_assert0(s->nb_streams == oc->nb_streams);
+            for (i = 0; i < s->nb_streams; i++) {
+                AVStream *inner_st  = oc->streams[i];
+                AVStream *outer_st = s->streams[i];
+                avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
+            }
+        } else {
+            for (i = 0; i < s->nb_streams; i++) {
+                AVStream *outer_st = s->streams[i];
+                if (outer_st->codec->codec_type!=AVMEDIA_TYPE_AUDIO) {
+                    continue;
+                }
+                AVStream *inner_st  = oc->streams[0];
+                avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
+            }
+        }
 
-    av_assert0(s->nb_streams == oc->nb_streams);
-    for (i = 0; i < s->nb_streams; i++) {
-        AVStream *inner_st  = oc->streams[i];
-        AVStream *outer_st = s->streams[i];
-        avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den);
-    }
+        if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0)
+            s->avoid_negative_ts = 1;
 
-    if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0)
-        s->avoid_negative_ts = 1;
+        if (!seg->write_header_trailer) {
+            close_null_ctxp(&oc->pb);
+            if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
+                                  &s->interrupt_callback, NULL)) < 0)
+                goto fail;
+        }
 
-    if (!seg->write_header_trailer) {
-        close_null_ctxp(&oc->pb);
-        if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
-                              &s->interrupt_callback, NULL)) < 0)
-            goto fail;
+        if (oc->avoid_negative_ts > 0 && s->avoid_negative_ts < 0)
+            s->avoid_negative_ts = 1;
     }
-
 fail:
     av_dict_free(&options);
     if (ret) {
-        if (seg->list)
-            avio_close(seg->list_pb);
-        if (seg->avf)
-            avformat_free_context(seg->avf);
+        if (seg->list) {
+            if (seg->list_pb[0])
+                avio_close(seg->list_pb[0]);
+            if (seg->list_pb[1])
+                avio_close(seg->list_pb[1]);
+        }
+        if (seg->avf[0])
+            avformat_free_context(seg->avf[0]);
+        if (seg->avf[1])
+            avformat_free_context(seg->avf[1]);
     }
     return ret;
 }
@@ -724,6 +895,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
     struct tm ti;
     int64_t usecs;
     int64_t wrapped_val;
+    int iCount = seg->progressive?2:1;
+    int cnt;
+
 
     if (seg->times) {
         end_pts = seg->segment_count < seg->nb_times ?
@@ -762,8 +936,10 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
           av_compare_ts(pkt->pts, st->time_base,
                         end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0))) {
         /* sanitize end time in case last packet didn't have a defined duration */
-        if (seg->cur_entry.last_duration == 0)
-            seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base);
+        for (cnt = 0; cnt < iCount; ++cnt) {
+            if (seg->entry_wrapper[cnt].cur_entry.last_duration == 0)
+                seg->entry_wrapper[cnt].cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base);
+        }
 
         if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0)
             goto fail;
@@ -772,31 +948,39 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
             goto fail;
 
         seg->cut_pending = 0;
-        seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap*seg->segment_idx_wrap_nb;
-        seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base);
-        seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
-        seg->cur_entry.end_time = seg->cur_entry.start_time +
-            pkt->pts != AV_NOPTS_VALUE ? (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base) : 0;
+        for (cnt = 0; cnt < iCount; ++cnt) {
+            seg->entry_wrapper[cnt].cur_entry.index = seg->segment_idx + seg->segment_idx_wrap*seg->segment_idx_wrap_nb;
+            seg->entry_wrapper[cnt].cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base);
+            seg->entry_wrapper[cnt].cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
+            seg->entry_wrapper[cnt].cur_entry.end_time = seg->entry_wrapper[cnt].cur_entry.start_time +
+                pkt->pts != AV_NOPTS_VALUE ? (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base) : 0;
+        }
     } else if (pkt->pts != AV_NOPTS_VALUE && pkt->stream_index == seg->reference_stream_index) {
-        seg->cur_entry.end_time =
-            FFMAX(seg->cur_entry.end_time, (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base));
-        seg->cur_entry.last_duration = pkt->duration;
+        for (cnt = 0; cnt < iCount; ++cnt) {
+            seg->entry_wrapper[cnt].cur_entry.end_time =
+            FFMAX(seg->entry_wrapper[cnt].cur_entry.end_time, (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base));
+            seg->entry_wrapper[cnt].cur_entry.last_duration = pkt->duration;
+        }
     }
 
     if (seg->segment_frame_count == 0) {
-        av_log(s, AV_LOG_VERBOSE, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d\n",
-               seg->avf->filename, pkt->stream_index,
-               av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), seg->frame_count);
+        for (cnt = 0; cnt < iCount; ++cnt) {
+            av_log(s, AV_LOG_VERBOSE, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d\n",
+                   seg->avf[cnt]->filename, pkt->stream_index,
+                   av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), seg->frame_count);
+        }
     }
 
-    av_log(s, AV_LOG_DEBUG, "stream:%d start_pts_time:%s pts:%s pts_time:%s dts:%s dts_time:%s",
-           pkt->stream_index,
-           av_ts2timestr(seg->cur_entry.start_pts, &AV_TIME_BASE_Q),
-           av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
-           av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
+    for (cnt = 0; cnt < iCount; ++cnt) {
+        av_log(s, AV_LOG_DEBUG, "stream:%d start_pts_time:%s pts:%s pts_time:%s dts:%s dts_time:%s",
+               pkt->stream_index,
+               av_ts2timestr(seg->entry_wrapper[cnt].cur_entry.start_pts, &AV_TIME_BASE_Q),
+               av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
+               av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
+    }
 
     /* compute new timestamps */
-    offset = av_rescale_q(seg->initial_offset - (seg->reset_timestamps ? seg->cur_entry.start_pts : 0),
+    offset = av_rescale_q(seg->initial_offset - (seg->reset_timestamps ? seg->entry_wrapper[0].cur_entry.start_pts : 0),
                           AV_TIME_BASE_Q, st->time_base);
     if (pkt->pts != AV_NOPTS_VALUE)
         pkt->pts += offset;
@@ -807,8 +991,16 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
            av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
            av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
 
-    ret = ff_write_chained(seg->avf, pkt->stream_index, pkt, s, seg->initial_offset || seg->reset_timestamps);
-
+    int stream_idx = pkt->stream_index;
+    for (cnt = 0; cnt < iCount; ++cnt) {
+        if (cnt == SEGMENT_ENTRY_AUDIO_ONLY && st->codec->codec_type!=AVMEDIA_TYPE_AUDIO) {
+            continue;
+        } else if (cnt == SEGMENT_ENTRY_AUDIO_ONLY) {
+            pkt->stream_index = 0;
+        }
+        ret = ff_write_chained(seg->avf[cnt], pkt->stream_index, pkt, s, seg->initial_offset || seg->reset_timestamps);
+        pkt->stream_index = stream_idx;
+    }
 fail:
     if (pkt->stream_index == seg->reference_stream_index) {
         seg->frame_count++;
@@ -821,37 +1013,46 @@ fail:
 static int seg_write_trailer(struct AVFormatContext *s)
 {
     SegmentContext *seg = s->priv_data;
-    AVFormatContext *oc = seg->avf;
+    int iCount = seg->progressive?2:1;
     SegmentListEntry *cur, *next;
 
     int ret;
     if (!seg->write_header_trailer) {
         if ((ret = segment_end(s, 0, 1)) < 0)
             goto fail;
-        open_null_ctx(&oc->pb);
-        ret = av_write_trailer(oc);
-        close_null_ctxp(&oc->pb);
+        for (int cnt = 0; cnt < iCount; ++cnt) {
+            AVFormatContext *oc = seg->avf[cnt];
+            open_null_ctx(&oc->pb);
+            ret = av_write_trailer(oc);
+            close_null_ctxp(&oc->pb);
+        }
     } else {
         ret = segment_end(s, 1, 1);
     }
 fail:
-    if (seg->list)
-        avio_close(seg->list_pb);
-
-    av_dict_free(&seg->format_options);
+    if (seg->list) {
+        if (seg->list_pb[0])
+            avio_close(seg->list_pb[0]);
+        if (seg->list_pb[1])
+            avio_close(seg->list_pb[1]);
+    }
+    
     av_opt_free(seg);
     av_freep(&seg->times);
     av_freep(&seg->frames);
 
-    cur = seg->segment_list_entries;
-    while (cur) {
-        next = cur->next;
-        av_freep(&cur->filename);
-        av_free(cur);
-        cur = next;
-    }
+    for (int cnt = 0; cnt < iCount; ++cnt) {
+        AVFormatContext *oc = seg->avf[cnt];
+        cur = seg->entry_wrapper[cnt].segment_list_entries;
+        while (cur) {
+            next = cur->next;
+            av_freep(&cur->filename);
+            av_free(cur);
+            cur = next;
+        }
 
-    avformat_free_context(oc);
+        avformat_free_context(oc);
+    }
     return ret;
 }
 
@@ -862,6 +1063,7 @@ static const AVOption options[] = {
     { "segment_format",    "set container format used for the segments", OFFSET(format),  AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
     { "segment_format_options", "set list of options for the container format used for the segments", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
     { "segment_list",      "set the segment list filename",              OFFSET(list),    AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
+    { "a_segment_list",      "set the audio only segment list filename",              OFFSET(audio_list),    AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
 
     { "segment_list_flags","set flags affecting segment list generation", OFFSET(list_flags), AV_OPT_TYPE_FLAGS, {.i64 = SEGMENT_LIST_FLAG_CACHE }, 0, UINT_MAX, E, "list_flags"},
     { "cache",             "allow list caching",                                    0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX,   E, "list_flags"},
@@ -892,6 +1094,10 @@ static const AVOption options[] = {
     { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E },
     { "reset_timestamps", "reset timestamps at the begin of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
     { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E },
+    { "progressive_hls", "set progressive hls support", OFFSET(progressive), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, E },
+    { "root_m3u8", "root m3u8 file", OFFSET(root_m3u8), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
+    { "vc_tag", "video codec tag", OFFSET(vc_tag), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
+    { "ac_tag", "audio codec tag", OFFSET(ac_tag), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
     { NULL },
 };
 
-- 
2.2.1



More information about the ffmpeg-devel mailing list