Commit 8c2b37e6 authored by Karthick J's avatar Karthick J Committed by Steven Liu

avformat/dashenc: Option to generate hls playlist as well

This is to take full advantage of Common Media Application Format(CMAF).
Now server can generate one content and serve both HLS and DASH players.
Reviewed-by: 's avatarSteven Liu <lq@onvideo.cn>
parent 3684b5e5
......@@ -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.
@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.
......
......@@ -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
......
......@@ -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];
double duration = (double) seg->duration / timescale;
if (target_duration <= duration)
target_duration = hls_get_int_from_double(duration);
}
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");
......@@ -662,8 +724,43 @@ static int write_manifest(AVFormatContext *s, int final)
avio_flush(out);
ff_format_io_close(s, &out);
if (use_rename)
return avpriv_io_move(temp_filename, s->filename);
if (use_rename) {
if ((ret = avpriv_io_move(temp_filename, s->filename)) < 0)
return ret;
}
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)
if ((ret = avpriv_io_move(temp_filename, filename_hls)) < 0)
return ret;
c->master_playlist_created = 1;
}
return 0;
}
......@@ -1206,6 +1303,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 },
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment