From: Vladimir Kuznetsov <v...@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel