aboutsummaryrefslogtreecommitdiffstats
path: root/libavformat/hlsenc.c
diff options
context:
space:
mode:
authorVishwanath Dixit <vdixit@akamai.com>2018-01-24 11:42:57 +0800
committerSteven Liu <lq@chinaffmpeg.org>2018-01-24 11:42:57 +0800
commit1948b76a1beabbcf36480c4b2c2af891886ead88 (patch)
treed85253c6e3a52a5e08c3982ff2f173e35cf81642 /libavformat/hlsenc.c
parent8a4cc0a2567fa8418709f75af5539cdf76fefb99 (diff)
downloadffmpeg-1948b76a1beabbcf36480c4b2c2af891886ead88.tar.gz
avformat/hlsenc: closed caption tags in the master playlist
Diffstat (limited to 'libavformat/hlsenc.c')
-rw-r--r--libavformat/hlsenc.c155
1 files changed, 153 insertions, 2 deletions
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 42e437f5d1..aab21f2f5e 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -152,9 +152,16 @@ typedef struct VariantStream {
unsigned int nb_streams;
int m3u8_created; /* status of media play-list creation */
char *agroup; /* audio group name */
+ char *ccgroup; /* closed caption group name */
char *baseurl;
} VariantStream;
+typedef struct ClosedCaptionsStream {
+ char *ccgroup; /* closed caption group name */
+ char *instreamid; /* closed captions INSTREAM-ID */
+ char *language; /* closed captions langauge */
+} ClosedCaptionsStream;
+
typedef struct HLSContext {
const AVClass *class; // Class for private options.
int64_t start_sequence;
@@ -203,11 +210,14 @@ typedef struct HLSContext {
VariantStream *var_streams;
unsigned int nb_varstreams;
+ ClosedCaptionsStream *cc_streams;
+ unsigned int nb_ccstreams;
int master_m3u8_created; /* status of master play-list creation */
char *master_m3u8_url; /* URL of the master m3u8 file */
int version; /* HLS version */
char *var_stream_map; /* user specified variant stream map string */
+ char *cc_stream_map; /* user specified closed caption streams map string */
char *master_pl_name;
unsigned int master_publish_rate;
int http_persistent;
@@ -1167,7 +1177,8 @@ static int create_master_playlist(AVFormatContext *s,
AVDictionary *options = NULL;
unsigned int i, j;
int m3u8_name_size, ret, bandwidth;
- char *m3u8_rel_name;
+ char *m3u8_rel_name, *ccgroup;
+ ClosedCaptionsStream *ccs;
input_vs->m3u8_created = 1;
if (!hls->master_m3u8_created) {
@@ -1194,6 +1205,16 @@ static int create_master_playlist(AVFormatContext *s,
ff_hls_write_playlist_version(hls->m3u8_out, hls->version);
+ for (i = 0; i < hls->nb_ccstreams; i++) {
+ ccs = &(hls->cc_streams[i]);
+ avio_printf(hls->m3u8_out, "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS");
+ avio_printf(hls->m3u8_out, ",GROUP-ID=\"%s\"", ccs->ccgroup);
+ avio_printf(hls->m3u8_out, ",NAME=\"%s\"", ccs->instreamid);
+ if (ccs->language)
+ avio_printf(hls->m3u8_out, ",LANGUAGE=\"%s\"", ccs->language);
+ avio_printf(hls->m3u8_out, ",INSTREAM-ID=\"%s\"\n", ccs->instreamid);
+ }
+
/* For audio only variant streams add #EXT-X-MEDIA tag with attributes*/
for (i = 0; i < hls->nb_varstreams; i++) {
vs = &(hls->var_streams[i]);
@@ -1278,8 +1299,23 @@ static int create_master_playlist(AVFormatContext *s,
bandwidth += aud_st->codecpar->bit_rate;
bandwidth += bandwidth / 10;
+ ccgroup = NULL;
+ if (vid_st && vs->ccgroup) {
+ /* check if this group name is available in the cc map string */
+ for (j = 0; j < hls->nb_ccstreams; j++) {
+ ccs = &(hls->cc_streams[j]);
+ if (!av_strcasecmp(ccs->ccgroup, vs->ccgroup)) {
+ ccgroup = vs->ccgroup;
+ break;
+ }
+ }
+ if (j == hls->nb_ccstreams)
+ av_log(NULL, AV_LOG_WARNING, "mapping ccgroup %s not found\n",
+ vs->ccgroup);
+ }
+
ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,
- aud_st ? vs->agroup : NULL, vs->codec_attr);
+ aud_st ? vs->agroup : NULL, vs->codec_attr, ccgroup);
av_freep(&m3u8_rel_name);
}
@@ -1766,6 +1802,11 @@ static int parse_variant_stream_mapstring(AVFormatContext *s)
if (!vs->agroup)
return AVERROR(ENOMEM);
continue;
+ } else if (av_strstart(keyval, "ccgroup:", &val)) {
+ vs->ccgroup = av_strdup(val);
+ if (!vs->ccgroup)
+ return AVERROR(ENOMEM);
+ continue;
} else if (av_strstart(keyval, "v:", &val)) {
codec_type = AVMEDIA_TYPE_VIDEO;
} else if (av_strstart(keyval, "a:", &val)) {
@@ -1796,9 +1837,94 @@ static int parse_variant_stream_mapstring(AVFormatContext *s)
return 0;
}
+static int parse_cc_stream_mapstring(AVFormatContext *s)
+{
+ HLSContext *hls = s->priv_data;
+ int nb_ccstreams;
+ char *p, *q, *saveptr1, *saveptr2, *ccstr, *keyval;
+ const char *val;
+ ClosedCaptionsStream *ccs;
+
+ p = av_strdup(hls->cc_stream_map);
+ q = p;
+ while(av_strtok(q, " \t", &saveptr1)) {
+ q = NULL;
+ hls->nb_ccstreams++;
+ }
+ av_freep(&p);
+
+ hls->cc_streams = av_mallocz(sizeof(*hls->cc_streams) * hls->nb_ccstreams);
+ if (!hls->cc_streams)
+ return AVERROR(ENOMEM);
+
+ p = hls->cc_stream_map;
+ nb_ccstreams = 0;
+ while (ccstr = av_strtok(p, " \t", &saveptr1)) {
+ p = NULL;
+
+ if (nb_ccstreams < hls->nb_ccstreams)
+ ccs = &(hls->cc_streams[nb_ccstreams++]);
+ else
+ return AVERROR(EINVAL);
+
+ while (keyval = av_strtok(ccstr, ",", &saveptr2)) {
+ ccstr = NULL;
+
+ if (av_strstart(keyval, "ccgroup:", &val)) {
+ ccs->ccgroup = av_strdup(val);
+ if (!ccs->ccgroup)
+ return AVERROR(ENOMEM);
+ } else if (av_strstart(keyval, "instreamid:", &val)) {
+ ccs->instreamid = av_strdup(val);
+ if (!ccs->instreamid)
+ return AVERROR(ENOMEM);
+ } else if (av_strstart(keyval, "language:", &val)) {
+ ccs->language = av_strdup(val);
+ if (!ccs->language)
+ return AVERROR(ENOMEM);
+ } else {
+ av_log(s, AV_LOG_ERROR, "Invalid keyval %s\n", keyval);
+ return AVERROR(EINVAL);
+ }
+ }
+
+ if (!ccs->ccgroup || !ccs->instreamid) {
+ av_log(s, AV_LOG_ERROR, "Insufficient parameters in cc stream map string\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (av_strstart(ccs->instreamid, "CC", &val)) {
+ if(atoi(val) < 1 || atoi(val) > 4) {
+ av_log(s, AV_LOG_ERROR, "Invalid instream ID CC index %d in %s, range 1-4\n",
+ atoi(val), ccs->instreamid);
+ return AVERROR(EINVAL);
+ }
+ } else if (av_strstart(ccs->instreamid, "SERVICE", &val)) {
+ if(atoi(val) < 1 || atoi(val) > 63) {
+ av_log(s, AV_LOG_ERROR, "Invalid instream ID SERVICE index %d in %s, range 1-63 \n",
+ atoi(val), ccs->instreamid);
+ return AVERROR(EINVAL);
+ }
+ } else {
+ av_log(s, AV_LOG_ERROR, "Invalid instream ID %s, supported are CCn or SERIVICEn\n",
+ ccs->instreamid);
+ return AVERROR(EINVAL);
+ }
+ }
+
+ return 0;
+}
+
static int update_variant_stream_info(AVFormatContext *s) {
HLSContext *hls = s->priv_data;
unsigned int i;
+ int ret = 0;
+
+ if (hls->cc_stream_map) {
+ ret = parse_cc_stream_mapstring(s);
+ if (ret < 0)
+ return ret;
+ }
if (hls->var_stream_map) {
return parse_variant_stream_mapstring(s);
@@ -1816,6 +1942,13 @@ static int update_variant_stream_info(AVFormatContext *s) {
if (!hls->var_streams[0].streams)
return AVERROR(ENOMEM);
+ //by default, the first available ccgroup is mapped to the variant stream
+ if (hls->nb_ccstreams) {
+ hls->var_streams[0].ccgroup = av_strdup(hls->cc_streams[0].ccgroup);
+ if (!hls->var_streams[0].ccgroup)
+ return AVERROR(ENOMEM);
+ }
+
for (i = 0; i < s->nb_streams; i++)
hls->var_streams[0].streams[i] = s->streams[i];
}
@@ -2192,13 +2325,22 @@ failed:
av_freep(&vs->m3u8_name);
av_freep(&vs->streams);
av_freep(&vs->agroup);
+ av_freep(&vs->ccgroup);
av_freep(&vs->baseurl);
}
+ for (i = 0; i < hls->nb_ccstreams; i++) {
+ ClosedCaptionsStream *ccs = &hls->cc_streams[i];
+ av_freep(&ccs->ccgroup);
+ av_freep(&ccs->instreamid);
+ av_freep(&ccs->language);
+ }
+
ff_format_io_close(s, &hls->m3u8_out);
ff_format_io_close(s, &hls->sub_m3u8_out);
av_freep(&hls->key_basename);
av_freep(&hls->var_streams);
+ av_freep(&hls->cc_streams);
av_freep(&hls->master_m3u8_url);
return 0;
}
@@ -2535,13 +2677,21 @@ fail:
av_freep(&vs->vtt_m3u8_name);
av_freep(&vs->streams);
av_freep(&vs->agroup);
+ av_freep(&vs->ccgroup);
av_freep(&vs->baseurl);
if (vs->avf)
avformat_free_context(vs->avf);
if (vs->vtt_avf)
avformat_free_context(vs->vtt_avf);
}
+ for (i = 0; i < hls->nb_ccstreams; i++) {
+ ClosedCaptionsStream *ccs = &hls->cc_streams[i];
+ av_freep(&ccs->ccgroup);
+ av_freep(&ccs->instreamid);
+ av_freep(&ccs->language);
+ }
av_freep(&hls->var_streams);
+ av_freep(&hls->cc_streams);
av_freep(&hls->master_m3u8_url);
}
@@ -2601,6 +2751,7 @@ static const AVOption options[] = {
{"datetime", "current datetime as YYYYMMDDhhmmss", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME }, INT_MIN, INT_MAX, E, "start_sequence_source_type" },
{"http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
{"var_stream_map", "Variant stream map string", OFFSET(var_stream_map), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
+ {"cc_stream_map", "Closed captions stream map string", OFFSET(cc_stream_map), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
{"master_pl_name", "Create HLS master playlist with this name", OFFSET(master_pl_name), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
{"master_pl_publish_rate", "Publish master play list every after this many segment intervals", OFFSET(master_publish_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, UINT_MAX, E},
{"http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },