[FFmpeg-devel] [PATCH v6 2/2] avformat/dashenc: Option to generate hls playlist as well
Steven Liu
lingjiujianke at gmail.com
Wed Nov 29 16:10:58 EET 2017
2017-11-29 22:05 GMT+08:00 Steven Liu <lingjiujianke at gmail.com>:
> 2017-11-29 20:48 GMT+08:00 Karthick J <kjeyapal at akamai.com>:
>> This is to take full advantage of Common Media Application Format.
>> Now server can generate one content and serve both HLS and DASH players.
>> ---
>> doc/muxers.texi | 3 ++
>> libavformat/Makefile | 2 +-
>> libavformat/dashenc.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++--
>> 3 files changed, 103 insertions(+), 5 deletions(-)
>>
>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>> index 8ec48c2..3d0c7bf 100644
>> --- a/doc/muxers.texi
>> +++ b/doc/muxers.texi
>> @@ -249,6 +249,9 @@ DASH-templated name to used for the media segments. Default is "chunk-stream$Rep
>> URL of the page that will return the UTC timestamp in ISO format. Example: "https://time.akamai.com/?iso"
>> @item -http_user_agent @var{user_agent}
>> Override User-Agent field in HTTP header. Applicable only for HTTP output.
>> + at item -hls_playlist @var{hls_playlist}
>> +Generate HLS playlist files as well. The master playlist is generated with the filename master.m3u8.
>> +One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc.
>> @item -adaptation_sets @var{adaptation_sets}
>> Assign streams to AdaptationSets. Syntax is "id=x,streams=a,b,c id=y,streams=d,e" with x and y being the IDs
>> of the adaptation sets and a,b,c,d and e are the indices of the mapped streams.
>> diff --git a/libavformat/Makefile b/libavformat/Makefile
>> index fd8b9f9..4bffdf2 100644
>> --- a/libavformat/Makefile
>> +++ b/libavformat/Makefile
>> @@ -135,7 +135,7 @@ OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o
>> OBJS-$(CONFIG_CRC_MUXER) += crcenc.o
>> OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o
>> OBJS-$(CONFIG_DATA_MUXER) += rawenc.o
>> -OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o
>> +OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o hlsplaylist.o
>> OBJS-$(CONFIG_DASH_DEMUXER) += dash.o dashdec.o
>> OBJS-$(CONFIG_DAUD_DEMUXER) += dauddec.o
>> OBJS-$(CONFIG_DAUD_MUXER) += daudenc.o
>> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
>> index 0fee3cd..ee9dc85 100644
>> --- a/libavformat/dashenc.c
>> +++ b/libavformat/dashenc.c
>> @@ -36,6 +36,7 @@
>> #include "avc.h"
>> #include "avformat.h"
>> #include "avio_internal.h"
>> +#include "hlsplaylist.h"
>> #include "internal.h"
>> #include "isom.h"
>> #include "os_support.h"
>> @@ -101,6 +102,8 @@ typedef struct DASHContext {
>> const char *media_seg_name;
>> const char *utc_timing_url;
>> const char *user_agent;
>> + int hls_playlist;
>> + int master_playlist_created;
>> } DASHContext;
>>
>> static struct codec_string {
>> @@ -217,6 +220,14 @@ static void set_http_options(AVDictionary **options, DASHContext *c)
>> av_dict_set(options, "user_agent", c->user_agent, 0);
>> }
>>
>> +static void get_hls_playlist_name(char *playlist_name, int string_size,
>> + const char *base_url, int id) {
>> + if (base_url)
>> + snprintf(playlist_name, string_size, "%smedia_%d.m3u8", base_url, id);
>> + else
>> + snprintf(playlist_name, string_size, "media_%d.m3u8", id);
>> +}
>> +
>> static int flush_init_segment(AVFormatContext *s, OutputStream *os)
>> {
>> DASHContext *c = s->priv_data;
>> @@ -262,7 +273,8 @@ static void dash_free(AVFormatContext *s)
>> av_freep(&c->streams);
>> }
>>
>> -static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c)
>> +static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c,
>> + int representation_id, int final)
>> {
>> int i, start_index = 0, start_number = 1;
>> if (c->window_size) {
>> @@ -322,6 +334,55 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext
>> }
>> avio_printf(out, "\t\t\t\t</SegmentList>\n");
>> }
>> + if (c->hls_playlist && start_index < os->nb_segments)
>> + {
>> + int timescale = os->ctx->streams[0]->time_base.den;
>> + char temp_filename_hls[1024];
>> + char filename_hls[1024];
>> + AVIOContext *out_hls = NULL;
>> + AVDictionary *http_opts = NULL;
>> + int target_duration = 0;
>> + const char *proto = avio_find_protocol_name(c->dirname);
>> + int use_rename = proto && !strcmp(proto, "file");
>> +
>> + get_hls_playlist_name(filename_hls, sizeof(filename_hls),
>> + c->dirname, representation_id);
>> +
>> + snprintf(temp_filename_hls, sizeof(temp_filename_hls), use_rename ? "%s.tmp" : "%s", filename_hls);
>> +
>> + set_http_options(&http_opts, c);
>> + avio_open2(&out_hls, temp_filename_hls, AVIO_FLAG_WRITE, NULL, &http_opts);
>> + av_dict_free(&http_opts);
>> + for (i = start_index; i < os->nb_segments; i++) {
>> + Segment *seg = os->segments[i];
>> + if (target_duration < seg->duration)
>> + target_duration = seg->duration;
>> + }
>> + target_duration = lrint((double) target_duration / timescale);
> What value will set when the target_duration is 1.023 ? I think we
> have talk about the value and the specification describe the standard
> process way.
> You can move the function get_int_from_double to the common file and use that.
>
>> +
>> + ff_hls_write_playlist_header(out_hls, 6, -1, target_duration,
>> + start_number, PLAYLIST_TYPE_NONE);
>> +
>> + ff_hls_write_init_file(out_hls, os->initfile, c->single_file,
>> + os->init_range_length, os->init_start_pos);
>> +
>> + for (i = start_index; i < os->nb_segments; i++) {
>> + Segment *seg = os->segments[i];
>> + ff_hls_write_file_entry(out_hls, 0, c->single_file,
>> + (double) seg->duration / timescale, 0,
>> + seg->range_length, seg->start_pos, NULL,
>> + c->single_file ? os->initfile : seg->file,
>> + NULL);
>> + }
>> +
>> + if (final)
>> + ff_hls_write_end_list(out_hls);
>> +
>> + avio_close(out_hls);
>> + if (use_rename)
>> + avpriv_io_move(temp_filename_hls, filename_hls);
>> + }
>> +
>> }
>>
>> static char *xmlescape(const char *str) {
>> @@ -391,7 +452,8 @@ static void format_date_now(char *buf, int size)
>> }
>> }
>>
>> -static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index)
>> +static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index,
>> + int final)
>> {
>> DASHContext *c = s->priv_data;
>> AdaptationSet *as = &c->as[as_index];
>> @@ -430,7 +492,7 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind
>> avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n",
>> s->streams[i]->codecpar->channels);
>> }
>> - output_segment_list(os, out, c);
>> + output_segment_list(os, out, c, i, final);
>> avio_printf(out, "\t\t\t</Representation>\n");
>> }
>> avio_printf(out, "\t\t</AdaptationSet>\n");
>> @@ -650,7 +712,7 @@ static int write_manifest(AVFormatContext *s, int final)
>> }
>>
>> for (i = 0; i < c->nb_as; i++) {
>> - if ((ret = write_adaptation_set(s, out, i)) < 0)
>> + if ((ret = write_adaptation_set(s, out, i, final)) < 0)
>> return ret;
>> }
>> avio_printf(out, "\t</Period>\n");
>> @@ -665,6 +727,38 @@ static int write_manifest(AVFormatContext *s, int final)
>> if (use_rename)
>> return avpriv_io_move(temp_filename, s->filename);
>>
>> + if (c->hls_playlist && !c->master_playlist_created) {
>> + char filename_hls[1024];
>> +
>> + if (c->dirname)
>> + snprintf(filename_hls, sizeof(filename_hls), "%s/master.m3u8", c->dirname);
>> + else
>> + snprintf(filename_hls, sizeof(filename_hls), "master.m3u8");
>> +
>> + snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", filename_hls);
>> +
>> + set_http_options(&opts, c);
>> + ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, NULL, &opts);
>> + if (ret < 0) {
>> + av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
>> + return ret;
>> + }
>> + av_dict_free(&opts);
>> +
>> + ff_hls_write_playlist_version(out, 6);
>> +
>> + for (i = 0; i < s->nb_streams; i++) {
>> + char playlist_file[64];
>> + AVStream *st = s->streams[i];
>> + get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
>> + ff_hls_write_stream_info(st, out, st->codecpar->bit_rate, playlist_file);
>> + }
>> + avio_close(out);
>> + if (use_rename)
>> + avpriv_io_move(temp_filename, filename_hls);
>> + c->master_playlist_created = 1;
>> + }
>> +
>> return 0;
>> }
>>
>> @@ -1206,6 +1300,7 @@ static const AVOption options[] = {
>> { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name), AV_OPT_TYPE_STRING, {.str = "chunk-stream$RepresentationID$-$Number%05d$.m4s"}, 0, 0, E },
>> { "utc_timing_url", "URL of the page that will return the UTC timestamp in ISO format", OFFSET(utc_timing_url), AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
>> { "http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
>> + { "hls_playlist", "Generate HLS playlist files(master.m3u8, media_%d.m3u8)", OFFSET(hls_playlist), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
>> { NULL },
>> };
>>
>> --
>> 1.9.1
>>
>
>
>
> Thanks
BTW, this patch you can make a new patch, 1/1 not V7 2/2, because i
have pushed the first patch.
Thanks
More information about the ffmpeg-devel
mailing list