X-Git-Url: https://git.piment-noir.org/?p=deb_ffmpeg.git;a=blobdiff_plain;f=ffmpeg%2Flibavformat%2Fhlsenc.c;fp=ffmpeg%2Flibavformat%2Fhlsenc.c;h=e13f438a2f5df6f60dfbfd8bf53c884235f32ba6;hp=cc142fa47452d53be56b40e4330f4454d1245651;hb=f6fa7814ccfe3e76514b36cf04f5cd3cb657c8cf;hpb=2ba45a602cbfa7b771effba9b11bb4245c21bc00 diff --git a/ffmpeg/libavformat/hlsenc.c b/ffmpeg/libavformat/hlsenc.c index cc142fa..e13f438 100644 --- a/ffmpeg/libavformat/hlsenc.c +++ b/ffmpeg/libavformat/hlsenc.c @@ -35,10 +35,17 @@ typedef struct HLSSegment { char filename[1024]; double duration; /* in seconds */ + int64_t pos; + int64_t size; struct HLSSegment *next; } HLSSegment; +typedef enum HLSFlags { + // Generate a single media file and use byte ranges in the playlist. + HLS_SINGLE_FILE = (1 << 0), +} HLSFlags; + typedef struct HLSContext { const AVClass *class; // Class for private options. unsigned number; @@ -51,12 +58,16 @@ typedef struct HLSContext { float time; // Set by a private option. int max_nb_segments; // Set by a private option. int wrap; // Set by a private option. + uint32_t flags; // enum HLSFlags + int allowcache; int64_t recording_time; int has_video; int64_t start_pts; int64_t end_pts; double duration; // last segment duration computed so far, in seconds + int64_t start_pos; // last segment starting position + int64_t size; // last segment size int nb_entries; HLSSegment *segments; @@ -64,6 +75,8 @@ typedef struct HLSContext { char *basename; char *baseurl; + char *format_options_str; + AVDictionary *format_options; AVIOContext *pb; } HLSContext; @@ -72,14 +85,16 @@ static int hls_mux_init(AVFormatContext *s) { HLSContext *hls = s->priv_data; AVFormatContext *oc; - int i; + int i, ret; - hls->avf = oc = avformat_alloc_context(); - if (!oc) - return AVERROR(ENOMEM); + ret = avformat_alloc_output_context2(&hls->avf, hls->oformat, NULL, NULL); + if (ret < 0) + return ret; + oc = hls->avf; oc->oformat = hls->oformat; 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++) { @@ -88,13 +103,16 @@ static int hls_mux_init(AVFormatContext *s) return AVERROR(ENOMEM); avcodec_copy_context(st->codec, s->streams[i]->codec); st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; + st->time_base = s->streams[i]->time_base; } + hls->start_pos = 0; return 0; } /* Create a new segment and append it to the segment list */ -static int hls_append_segment(HLSContext *hls, double duration) +static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, + int64_t size) { HLSSegment *en = av_malloc(sizeof(*en)); @@ -104,6 +122,8 @@ static int hls_append_segment(HLSContext *hls, double duration) av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); en->duration = duration; + en->pos = pos; + en->size = size; en->next = NULL; if (!hls->segments) @@ -143,6 +163,7 @@ static int hls_window(AVFormatContext *s, int last) int target_duration = 0; int ret = 0; int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); + int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3; if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) @@ -154,7 +175,10 @@ static int hls_window(AVFormatContext *s, int last) } avio_printf(hls->pb, "#EXTM3U\n"); - avio_printf(hls->pb, "#EXT-X-VERSION:3\n"); + avio_printf(hls->pb, "#EXT-X-VERSION:%d\n", version); + if (hls->allowcache == 0 || hls->allowcache == 1) { + avio_printf(hls->pb, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); + } avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration); avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); @@ -163,6 +187,9 @@ static int hls_window(AVFormatContext *s, int last) for (en = hls->segments; en; en = en->next) { avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration); + if (hls->flags & HLS_SINGLE_FILE) + avio_printf(hls->pb, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", + en->size, en->pos); if (hls->baseurl) avio_printf(hls->pb, "%s", hls->baseurl); avio_printf(hls->pb, "%s\n", en->filename); @@ -182,11 +209,15 @@ static int hls_start(AVFormatContext *s) AVFormatContext *oc = c->avf; int err = 0; - if (av_get_frame_filename(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { - av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename); - return AVERROR(EINVAL); - } + if (c->flags & HLS_SINGLE_FILE) + av_strlcpy(oc->filename, c->basename, + sizeof(oc->filename)); + else + if (av_get_frame_filename(oc->filename, sizeof(oc->filename), + c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename); + return AVERROR(EINVAL); + } c->number++; if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, @@ -205,12 +236,24 @@ static int hls_write_header(AVFormatContext *s) int ret, i; char *p; const char *pattern = "%d.ts"; + AVDictionary *options = NULL; int basename_size = strlen(s->filename) + strlen(pattern) + 1; hls->sequence = hls->start_sequence; hls->recording_time = hls->time * AV_TIME_BASE; hls->start_pts = AV_NOPTS_VALUE; + if (hls->flags & HLS_SINGLE_FILE) + pattern = ".ts"; + + if (hls->format_options_str) { + ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", hls->format_options_str); + goto fail; + } + } + for (i = 0; i < s->nb_streams; i++) hls->has_video += s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; @@ -249,17 +292,22 @@ static int hls_write_header(AVFormatContext *s) if ((ret = hls_start(s)) < 0) goto fail; - if ((ret = avformat_write_header(hls->avf, NULL)) < 0) + av_dict_copy(&options, hls->format_options, 0); + ret = avformat_write_header(hls->avf, &options); + if (av_dict_count(options)) { + av_log(s, AV_LOG_ERROR, "Some of provided format options in '%s' are not recognized\n", hls->format_options_str); + ret = AVERROR(EINVAL); goto fail; - + } av_assert0(s->nb_streams == hls->avf->nb_streams); for (i = 0; i < s->nb_streams; i++) { AVStream *inner_st = hls->avf->streams[i]; - AVStream *outter_st = s->streams[i]; - avpriv_set_pts_info(outter_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); + 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); } - fail: + + av_dict_free(&options); if (ret) { av_free(hls->basename); if (hls->avf) @@ -296,17 +344,28 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, end_pts, AV_TIME_BASE_Q) >= 0) { - ret = hls_append_segment(hls, hls->duration); + int64_t new_start_pos; + av_write_frame(oc, NULL); /* Flush any buffered data */ + + new_start_pos = avio_tell(hls->avf->pb); + hls->size = new_start_pos - hls->start_pos; + ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + hls->start_pos = new_start_pos; if (ret) return ret; hls->end_pts = pkt->pts; hls->duration = 0; - av_write_frame(oc, NULL); /* Flush any buffered data */ - avio_close(oc->pb); + if (hls->flags & HLS_SINGLE_FILE) { + if (hls->avf->oformat->priv_class && hls->avf->priv_data) + av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); + hls->number++; + } else { + avio_close(oc->pb); - ret = hls_start(s); + ret = hls_start(s); + } if (ret) return ret; @@ -328,10 +387,12 @@ static int hls_write_trailer(struct AVFormatContext *s) AVFormatContext *oc = hls->avf; av_write_trailer(oc); + hls->size = avio_tell(hls->avf->pb) - hls->start_pos; avio_closep(&oc->pb); - avformat_free_context(oc); av_free(hls->basename); - hls_append_segment(hls, hls->duration); + hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + avformat_free_context(oc); + hls->avf = NULL; hls_window(s, 1); hls_free_segments(hls); @@ -345,8 +406,13 @@ static const AVOption options[] = { {"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, + {"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, + {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E}, {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"}, + {"single_file", "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX, E, "flags"}, + { NULL }, };