[FFmpeg-devel,v2,1/1] avformat/hlsenc: closed caption tags in the master playlist

Submitted by Dixit, Vishwanath on Jan. 9, 2018, 8:45 a.m.

Details

Message ID 1515487541-13767-1-git-send-email-vdixit@akamai.com
State New
Headers show

Commit Message

Dixit, Vishwanath Jan. 9, 2018, 8:45 a.m.
From: Vishwanath Dixit <vdixit@akamai.com>

---
 doc/muxers.texi           |  37 +++++++++++
 libavformat/dashenc.c     |   2 +-
 libavformat/hlsenc.c      | 155 +++++++++++++++++++++++++++++++++++++++++++++-
 libavformat/hlsplaylist.c |   5 +-
 libavformat/hlsplaylist.h |   3 +-
 5 files changed, 197 insertions(+), 5 deletions(-)

Comments

Steven Liu Jan. 9, 2018, 9:06 a.m.
> On 9 Jan 2018, at 16:45, vdixit@akamai.com wrote:
> 
> From: Vishwanath Dixit <vdixit@akamai.com>
> 
> ---
> doc/muxers.texi           |  37 +++++++++++
> libavformat/dashenc.c     |   2 +-
> libavformat/hlsenc.c      | 155 +++++++++++++++++++++++++++++++++++++++++++++-
> libavformat/hlsplaylist.c |   5 +-
> libavformat/hlsplaylist.h |   3 +-
> 5 files changed, 197 insertions(+), 5 deletions(-)
> 
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index b060c4f..d9a5cc0 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -901,6 +901,43 @@ and they are mapped to the two video only variant streams with audio group names
> 
> By default, a single hls variant containing all the encoded streams is created.
> 
> +@item cc_stream_map
> +Map string which specifies different closed captions groups and their
> +attributes. The closed captions stream groups are separated by space.
> +Expected string format is like this
> +"ccgroup:<group name>,instreamid:<INSTREAM-ID>,language:<language code> ....".
> +'ccgroup' and 'instreamid' are mandatory attributes. 'language' is an optional
> +attribute.
> +The closed captions groups configured using this option are mapped to different
> +variant streams by providing the same 'ccgroup' name in the
> +@code{var_stream_map} string. If @code{var_stream_map} is not set, then the
> +first available ccgroup in @code{cc_stream_map} is mapped to the output variant
> +stream. The examples for these two use cases are given below.
> +
> +@example
> +ffmpeg -re -i in.ts -b:v 1000k -b:a 64k -a53cc 1 -f hls \
> +  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en" \
> +  -master_pl_name master.m3u8 \
> +  http://example.com/live/out.m3u8
> +@end example
> +This example adds @code{#EXT-X-MEDIA} tag with @code{TYPE=CLOSED-CAPTIONS} in
> +the master playlist with group name 'cc', langauge 'en' (english) and
> +INSTREAM-ID 'CC1'. Also, it adds @code{CLOSED-CAPTIONS} attribute with group
> +name 'cc' for the output variant stream.
> +@example
> +ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k -b:a:1 32k \
> +  -a53cc:0 1 -a53cc:1 1\
> +  -map 0:v -map 0:a -map 0:v -map 0:a -f hls \
> +  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en ccgroup:cc,instreamid:CC2,language:sp" \
> +  -var_stream_map "v:0,a:0,ccgroup:cc v:1,a:1,ccgroup:cc" \
> +  -master_pl_name master.m3u8 \
> +  http://example.com/live/out_%v.m3u8
> +@end example
> +This example adds two @code{#EXT-X-MEDIA} tags with @code{TYPE=CLOSED-CAPTIONS} in
> +the master playlist for the INSTREAM-IDs 'CC1' and 'CC2'. Also, it adds
> +@code{CLOSED-CAPTIONS} attribute with group name 'cc' for the two output variant
> +streams.
> +
> @item master_pl_name
> Create HLS master playlist with the given name.
> 
> diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
> index 3345b89..39d0afe 100644
> --- a/libavformat/dashenc.c
> +++ b/libavformat/dashenc.c
> @@ -820,7 +820,7 @@ static int write_manifest(AVFormatContext *s, int final)
>                 stream_bitrate += max_audio_bitrate;
>             }
>             get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
> -            ff_hls_write_stream_info(st, out, stream_bitrate, playlist_file, agroup);
> +            ff_hls_write_stream_info(st, out, stream_bitrate, playlist_file, agroup, NULL);
>         }
>         avio_close(out);
>         if (use_rename)
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index e36120c..4e4b287 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -145,9 +145,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;
> @@ -196,11 +203,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;
> @@ -1115,7 +1125,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) {
> @@ -1142,6 +1153,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]);
> @@ -1226,8 +1247,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);
> +                aud_st ? vs->agroup : NULL, ccgroup);
> 
>         av_freep(&m3u8_rel_name);
>     }
> @@ -1714,6 +1750,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)) {
> @@ -1744,9 +1785,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);
> @@ -1764,6 +1890,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];
>     }
> @@ -2127,13 +2260,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;
> }
> @@ -2470,13 +2612,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);
>     }
> 
> @@ -2536,6 +2686,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 },
> diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
> index 098dc89..e797f14 100644
> --- a/libavformat/hlsplaylist.c
> +++ b/libavformat/hlsplaylist.c
> @@ -46,7 +46,8 @@ void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
> }
> 
> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
> -                              int bandwidth, char *filename, char *agroup) {
> +                              int bandwidth, char *filename, char *agroup,
> +                              char *ccgroup) {
>     if (!out || !filename)
>         return;
> 
> @@ -62,6 +63,8 @@ void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
>                 st->codecpar->height);
>     if (agroup && strlen(agroup) > 0)
>         avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
> +    if (ccgroup && strlen(ccgroup) > 0)
> +        avio_printf(out, ",CLOSED-CAPTIONS=\"%s\"", ccgroup);
>     avio_printf(out, "\n%s\n\n", filename);
> }
> 
> diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
> index 9969315..29c5123 100644
> --- a/libavformat/hlsplaylist.h
> +++ b/libavformat/hlsplaylist.h
> @@ -40,7 +40,8 @@ void ff_hls_write_playlist_version(AVIOContext *out, int version);
> void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
>                                   char *filename, int name_id, int is_default);
> void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
> -                              int bandwidth, char *filename, char *agroup);
> +                              int bandwidth, char *filename, char *agroup,
> +                              char *ccgroup);
> void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,
>                                   int target_duration, int64_t sequence,
>                                   uint32_t playlist_type);
> -- 
> 1.9.1
> 

If only copy command of the document, it’s ok,
If input string by myself, It’s have problem, cannot be used.


liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers
  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox
  libavutil      56.  7.100 / 56.  7.100
  libavcodec     58.  9.100 / 58.  9.100
  libavformat    58.  3.100 / 58.  3.100
  libavdevice    58.  0.100 / 58.  0.100
  libavfilter     7. 11.101 /  7. 11.101
  libswscale      5.  0.101 /  5.  0.101
  libswresample   3.  0.101 /  3.  0.101
  libpostproc    55.  0.100 / 55.  0.100
Input #0, lavfi, from 'color=red':
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
Press [q] to stop, [?] for help
[libx264 @ 0x7fadae801600] using SAR=1/1
[libx264 @ 0x7fadae801600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
[libx264 @ 0x7fadae801600] profile High, level 2.0
[libx264 @ 0x7fadae801600] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
[hls @ 0x7fadae81ec00] Opening 'live/out0.ts' for writing
Output #0, hls, to 'live/out.m3u8':
  Metadata:
    encoder         : Lavf58.3.100
    Stream #0:0: Video: h264 (libx264), yuv420p, 320x240 [SAR 1:1 DAR 4:3], q=-1--1, 1000 kb/s, 25 fps, 90k tbn, 25 tbc
    Metadata:
      encoder         : Lavc58.9.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/1000000 buffer size: 0 vbv_delay: -1
[hls @ 0x7fadae81ec00] Opening 'live/out1.ts' for writingte=N/A speed=0.514x
[hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
[hls @ 0x7fadae81ec00] Opening 'live/master.m3u8' for writing
[hls @ 0x7fadae81ec00] Opening 'live/out2.ts' for writingte=N/A speed=0.71x
[hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
[hls @ 0x7fadae81ec00] Opening 'live/out3.ts' for writingte=N/A speed=0.794x
[hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
[hls @ 0x7fadae81ec00] Opening 'live/out4.ts' for writingte=N/A speed=0.809x
[hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
[hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
frame=  202 fps= 25 q=-1.0 Lsize=N/A time=00:00:08.00 bitrate=N/A speed=0.99x
video:5kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
[libx264 @ 0x7fadae801600] frame I:9     Avg QP: 0.44  size:    72
[libx264 @ 0x7fadae801600] frame P:49    Avg QP: 0.14  size:    22
[libx264 @ 0x7fadae801600] frame B:144   Avg QP: 0.36  size:    16
[libx264 @ 0x7fadae801600] consecutive B-frames:  5.0%  0.0%  0.0% 95.0%
[libx264 @ 0x7fadae801600] mb I  I16..4: 100.0%  0.0%  0.0%
[libx264 @ 0x7fadae801600] mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  0.0%  0.0%  0.0%  0.0%  0.0%    skip:100.0%
[libx264 @ 0x7fadae801600] mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  0.0%  0.0%  0.0%  direct: 0.0%  skip:100.0%
[libx264 @ 0x7fadae801600] final ratefactor: -27.54
[libx264 @ 0x7fadae801600] 8x8 transform intra:0.0%
[libx264 @ 0x7fadae801600] coded y,uvDC,uvAC intra: 0.0% 0.3% 0.0% inter: 0.0% 0.0% 0.0%
[libx264 @ 0x7fadae801600] i16 v,h,dc,p: 93%  0%  7%  0%
[libx264 @ 0x7fadae801600] i8c dc,h,v,p: 100%  0%  0%  0%
[libx264 @ 0x7fadae801600] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0x7fadae801600] kb/s:4.05



liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "closecgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers
  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox
  libavutil      56.  7.100 / 56.  7.100
  libavcodec     58.  9.100 / 58.  9.100
  libavformat    58.  3.100 / 58.  3.100
  libavdevice    58.  0.100 / 58.  0.100
  libavfilter     7. 11.101 /  7. 11.101
  libswscale      5.  0.101 /  5.  0.101
  libswresample   3.  0.101 /  3.  0.101
  libpostproc    55.  0.100 / 55.  0.100
Input #0, lavfi, from 'color=red':
  Duration: N/A, start: 0.000000, bitrate: N/A
    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
Press [q] to stop, [?] for help
[libx264 @ 0x7fb463005800] using SAR=1/1
[libx264 @ 0x7fb463005800] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
[libx264 @ 0x7fb463005800] profile High, level 2.0
[libx264 @ 0x7fb463005800] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
[hls @ 0x7fb46383bc00] Invalid keyval closecgroup:cc
[hls @ 0x7fb46383bc00] Variant stream info update failed with status ffffffea
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Error initializing output stream 0:0 --
[libx264 @ 0x7fb463005800] final ratefactor: 17.61
Conversion failed!
liuqideMacBook-Pro:xxx liuqi$




Thanks

Steven
Steven Liu Jan. 9, 2018, 9:11 a.m.
>> 
> 
> If only copy command of the document, it’s ok,
> If input string by myself, It’s have problem, cannot be used.
> 
> 
> liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
> ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers
>  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
>  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox
>  libavutil      56.  7.100 / 56.  7.100
>  libavcodec     58.  9.100 / 58.  9.100
>  libavformat    58.  3.100 / 58.  3.100
>  libavdevice    58.  0.100 / 58.  0.100
>  libavfilter     7. 11.101 /  7. 11.101
>  libswscale      5.  0.101 /  5.  0.101
>  libswresample   3.  0.101 /  3.  0.101
>  libpostproc    55.  0.100 / 55.  0.100
> Input #0, lavfi, from 'color=red':
>  Duration: N/A, start: 0.000000, bitrate: N/A
>    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
> Stream mapping:
>  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
> Press [q] to stop, [?] for help
> [libx264 @ 0x7fadae801600] using SAR=1/1
> [libx264 @ 0x7fadae801600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
> [libx264 @ 0x7fadae801600] profile High, level 2.0
> [libx264 @ 0x7fadae801600] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
> [hls @ 0x7fadae81ec00] Opening 'live/out0.ts' for writing
> Output #0, hls, to 'live/out.m3u8':
>  Metadata:
>    encoder         : Lavf58.3.100
>    Stream #0:0: Video: h264 (libx264), yuv420p, 320x240 [SAR 1:1 DAR 4:3], q=-1--1, 1000 kb/s, 25 fps, 90k tbn, 25 tbc
>    Metadata:
>      encoder         : Lavc58.9.100 libx264
>    Side data:
>      cpb: bitrate max/min/avg: 0/0/1000000 buffer size: 0 vbv_delay: -1
> [hls @ 0x7fadae81ec00] Opening 'live/out1.ts' for writingte=N/A speed=0.514x
> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
> [hls @ 0x7fadae81ec00] Opening 'live/master.m3u8' for writing
> [hls @ 0x7fadae81ec00] Opening 'live/out2.ts' for writingte=N/A speed=0.71x
> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
> [hls @ 0x7fadae81ec00] Opening 'live/out3.ts' for writingte=N/A speed=0.794x
> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
> [hls @ 0x7fadae81ec00] Opening 'live/out4.ts' for writingte=N/A speed=0.809x
> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing
> frame=  202 fps= 25 q=-1.0 Lsize=N/A time=00:00:08.00 bitrate=N/A speed=0.99x
> video:5kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
> [libx264 @ 0x7fadae801600] frame I:9     Avg QP: 0.44  size:    72
> [libx264 @ 0x7fadae801600] frame P:49    Avg QP: 0.14  size:    22
> [libx264 @ 0x7fadae801600] frame B:144   Avg QP: 0.36  size:    16
> [libx264 @ 0x7fadae801600] consecutive B-frames:  5.0%  0.0%  0.0% 95.0%
> [libx264 @ 0x7fadae801600] mb I  I16..4: 100.0%  0.0%  0.0%
> [libx264 @ 0x7fadae801600] mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  0.0%  0.0%  0.0%  0.0%  0.0%    skip:100.0%
> [libx264 @ 0x7fadae801600] mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  0.0%  0.0%  0.0%  direct: 0.0%  skip:100.0%
> [libx264 @ 0x7fadae801600] final ratefactor: -27.54
> [libx264 @ 0x7fadae801600] 8x8 transform intra:0.0%
> [libx264 @ 0x7fadae801600] coded y,uvDC,uvAC intra: 0.0% 0.3% 0.0% inter: 0.0% 0.0% 0.0%
> [libx264 @ 0x7fadae801600] i16 v,h,dc,p: 93%  0%  7%  0%
> [libx264 @ 0x7fadae801600] i8c dc,h,v,p: 100%  0%  0%  0%
> [libx264 @ 0x7fadae801600] Weighted P-Frames: Y:0.0% UV:0.0%
> [libx264 @ 0x7fadae801600] kb/s:4.05
> 
> 
> 
> liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "closecgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
> ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers
>  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)
>  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox
>  libavutil      56.  7.100 / 56.  7.100
>  libavcodec     58.  9.100 / 58.  9.100
>  libavformat    58.  3.100 / 58.  3.100
>  libavdevice    58.  0.100 / 58.  0.100
>  libavfilter     7. 11.101 /  7. 11.101
>  libswscale      5.  0.101 /  5.  0.101
>  libswresample   3.  0.101 /  3.  0.101
>  libpostproc    55.  0.100 / 55.  0.100
> Input #0, lavfi, from 'color=red':
>  Duration: N/A, start: 0.000000, bitrate: N/A
>    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc
> Stream mapping:
>  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))
> Press [q] to stop, [?] for help
> [libx264 @ 0x7fb463005800] using SAR=1/1
> [libx264 @ 0x7fb463005800] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
> [libx264 @ 0x7fb463005800] profile High, level 2.0
> [libx264 @ 0x7fb463005800] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
> [hls @ 0x7fb46383bc00] Invalid keyval closecgroup:cc
> [hls @ 0x7fb46383bc00] Variant stream info update failed with status ffffffea
> Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
> Error initializing output stream 0:0 --
> [libx264 @ 0x7fb463005800] final ratefactor: 17.61
> Conversion failed!
> liuqideMacBook-Pro:xxx liuqi$
> 
> 


I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.
Maybe two ways:
1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.
2. parse the closed captions group string by KeyValue way? maybe this is better.

Thanks

Steven
Dixit, Vishwanath Jan. 9, 2018, 9:28 a.m.
On 1/9/18 2:41 PM, 刘歧 wrote:
>>>

>>

>> If only copy command of the document, it’s ok,

>> If input string by myself, It’s have problem, cannot be used.

>>

>>

>> liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8

>> ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers

>>  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)

>>  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox

>>  libavutil      56.  7.100 / 56.  7.100

>>  libavcodec     58.  9.100 / 58.  9.100

>>  libavformat    58.  3.100 / 58.  3.100

>>  libavdevice    58.  0.100 / 58.  0.100

>>  libavfilter     7. 11.101 /  7. 11.101

>>  libswscale      5.  0.101 /  5.  0.101

>>  libswresample   3.  0.101 /  3.  0.101

>>  libpostproc    55.  0.100 / 55.  0.100

>> Input #0, lavfi, from 'color=red':

>>  Duration: N/A, start: 0.000000, bitrate: N/A

>>    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc

>> Stream mapping:

>>  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))

>> Press [q] to stop, [?] for help

>> [libx264 @ 0x7fadae801600] using SAR=1/1

>> [libx264 @ 0x7fadae801600] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX

>> [libx264 @ 0x7fadae801600] profile High, level 2.0

>> [libx264 @ 0x7fadae801600] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00

>> [hls @ 0x7fadae81ec00] Opening 'live/out0.ts' for writing

>> Output #0, hls, to 'live/out.m3u8':

>>  Metadata:

>>    encoder         : Lavf58.3.100

>>    Stream #0:0: Video: h264 (libx264), yuv420p, 320x240 [SAR 1:1 DAR 4:3], q=-1--1, 1000 kb/s, 25 fps, 90k tbn, 25 tbc

>>    Metadata:

>>      encoder         : Lavc58.9.100 libx264

>>    Side data:

>>      cpb: bitrate max/min/avg: 0/0/1000000 buffer size: 0 vbv_delay: -1

>> [hls @ 0x7fadae81ec00] Opening 'live/out1.ts' for writingte=N/A speed=0.514x

>> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing

>> [hls @ 0x7fadae81ec00] Opening 'live/master.m3u8' for writing

>> [hls @ 0x7fadae81ec00] Opening 'live/out2.ts' for writingte=N/A speed=0.71x

>> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing

>> [hls @ 0x7fadae81ec00] Opening 'live/out3.ts' for writingte=N/A speed=0.794x

>> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing

>> [hls @ 0x7fadae81ec00] Opening 'live/out4.ts' for writingte=N/A speed=0.809x

>> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing

>> [hls @ 0x7fadae81ec00] Opening 'live/out.m3u8.tmp' for writing

>> frame=  202 fps= 25 q=-1.0 Lsize=N/A time=00:00:08.00 bitrate=N/A speed=0.99x

>> video:5kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

>> [libx264 @ 0x7fadae801600] frame I:9     Avg QP: 0.44  size:    72

>> [libx264 @ 0x7fadae801600] frame P:49    Avg QP: 0.14  size:    22

>> [libx264 @ 0x7fadae801600] frame B:144   Avg QP: 0.36  size:    16

>> [libx264 @ 0x7fadae801600] consecutive B-frames:  5.0%  0.0%  0.0% 95.0%

>> [libx264 @ 0x7fadae801600] mb I  I16..4: 100.0%  0.0%  0.0%

>> [libx264 @ 0x7fadae801600] mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  0.0%  0.0%  0.0%  0.0%  0.0%    skip:100.0%

>> [libx264 @ 0x7fadae801600] mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  0.0%  0.0%  0.0%  direct: 0.0%  skip:100.0%

>> [libx264 @ 0x7fadae801600] final ratefactor: -27.54

>> [libx264 @ 0x7fadae801600] 8x8 transform intra:0.0%

>> [libx264 @ 0x7fadae801600] coded y,uvDC,uvAC intra: 0.0% 0.3% 0.0% inter: 0.0% 0.0% 0.0%

>> [libx264 @ 0x7fadae801600] i16 v,h,dc,p: 93%  0%  7%  0%

>> [libx264 @ 0x7fadae801600] i8c dc,h,v,p: 100%  0%  0%  0%

>> [libx264 @ 0x7fadae801600] Weighted P-Frames: Y:0.0% UV:0.0%

>> [libx264 @ 0x7fadae801600] kb/s:4.05

>>

>>

>>

>> liuqideMacBook-Pro:xxx liuqi$ ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "closecgroup:cc,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8

>> ffmpeg version N-89757-g42a5fe340f Copyright (c) 2000-2018 the FFmpeg developers

>>  built with Apple LLVM version 9.0.0 (clang-900.0.39.2)

>>  configuration: --enable-fontconfig --enable-gpl --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libspeex --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-version3 --cc='ccache gcc' --enable-nonfree --enable-videotoolbox

>>  libavutil      56.  7.100 / 56.  7.100

>>  libavcodec     58.  9.100 / 58.  9.100

>>  libavformat    58.  3.100 / 58.  3.100

>>  libavdevice    58.  0.100 / 58.  0.100

>>  libavfilter     7. 11.101 /  7. 11.101

>>  libswscale      5.  0.101 /  5.  0.101

>>  libswresample   3.  0.101 /  3.  0.101

>>  libpostproc    55.  0.100 / 55.  0.100

>> Input #0, lavfi, from 'color=red':

>>  Duration: N/A, start: 0.000000, bitrate: N/A

>>    Stream #0:0: Video: rawvideo (I420 / 0x30323449), yuv420p, 320x240 [SAR 1:1 DAR 4:3], 25 tbr, 25 tbn, 25 tbc

>> Stream mapping:

>>  Stream #0:0 -> #0:0 (rawvideo (native) -> h264 (libx264))

>> Press [q] to stop, [?] for help

>> [libx264 @ 0x7fb463005800] using SAR=1/1

>> [libx264 @ 0x7fb463005800] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX

>> [libx264 @ 0x7fb463005800] profile High, level 2.0

>> [libx264 @ 0x7fb463005800] 264 - core 148 r2694 3b70645 - H.264/MPEG-4 AVC codec - Copyleft 2003-2016 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=6 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=25 keyint_min=2 scenecut=40 intra_refresh=0 rc_lookahead=25 rc=abr mbtree=1 bitrate=1000 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00

>> [hls @ 0x7fb46383bc00] Invalid keyval closecgroup:cc

>> [hls @ 0x7fb46383bc00] Variant stream info update failed with status ffffffea

>> Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument

>> Error initializing output stream 0:0 --

>> [libx264 @ 0x7fb463005800] final ratefactor: 17.61

>> Conversion failed!

>> liuqideMacBook-Pro:xxx liuqi$

>>

>>

>

>

> I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.

> Maybe two ways:

> 1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.

> 2. parse the closed captions group string by KeyValue way? maybe this is better.


Actually, these requirements have been already handled. The parsing is happening based on key value pairs. The keys are ‘ccgroup’, ‘instreamid’, ‘language’. The values for these keys can be set after ‘:’ as given the examples in the patch. 
I am assuming you are trying to set ccgroup name as closecgroup. In that case, please modify the command as below. Because, the string ‘ccgroup’ is a key value, whatever string that comes after ‘ccgroup:’ is the cc group name.  
./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:closecgroup,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8

To clarify further, consider user wants to set cc group name as ‘mycaptions’, instream id as ‘SERVICE60’ language as Spanish, in that case, the map string would be
-cc_stream_map "ccgroup:mycaptions,instreamid:SERVICE60,language:sp"  

>

> Thanks

>

> Steven

>

>

>
Steven Liu Jan. 9, 2018, 9:35 a.m.
>>> 
>> 
>> 
>> I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.
>> Maybe two ways:
>> 1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.
>> 2. parse the closed captions group string by KeyValue way? maybe this is better.
> 
> Actually, these requirements have been already handled. The parsing is happening based on key value pairs. The keys are ‘ccgroup’, ‘instreamid’, ‘language’. The values for these keys can be set after ‘:’ as given the examples in the patch. 
> I am assuming you are trying to set ccgroup name as closecgroup. In that case, please modify the command as below. Because, the string ‘ccgroup’ is a key value, whatever string that comes after ‘ccgroup:’ is the cc group name.  
> ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:closecgroup,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
> 
> To clarify further, consider user wants to set cc group name as ‘mycaptions’, instream id as ‘SERVICE60’ language as Spanish, in that case, the map string would be
> -cc_stream_map "ccgroup:mycaptions,instreamid:SERVICE60,language:sp”  
I understand and i got the point, 
just one question, do you want force the user use ffmpeg must input a string “ccgroup” ? if yes, maybe don’t let the user input it use string, maybe use -cc_stream_map_ccgroup is better, What do you think about it?


Thanks

Steven
Dixit, Vishwanath Jan. 9, 2018, 9:54 a.m.
On 1/9/18 3:05 PM, 刘歧 wrote:
>>>>

>>>

>>>

>>> I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.

>>> Maybe two ways:

>>> 1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.

>>> 2. parse the closed captions group string by KeyValue way? maybe this is better.

>>

>> Actually, these requirements have been already handled. The parsing is happening based on key value pairs. The keys are ‘ccgroup’, ‘instreamid’, ‘language’. The values for these keys can be set after ‘:’ as given the examples in the patch. 

>> I am assuming you are trying to set ccgroup name as closecgroup. In that case, please modify the command as below. Because, the string ‘ccgroup’ is a key value, whatever string that comes after ‘ccgroup:’ is the cc group name.  

>> ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:closecgroup,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8

>>

>> To clarify further, consider user wants to set cc group name as ‘mycaptions’, instream id as ‘SERVICE60’ language as Spanish, in that case, the map string would be

>> -cc_stream_map "ccgroup:mycaptions,instreamid:SERVICE60,language:sp”  

> I understand and i got the point, 

> just one question, do you want force the user use ffmpeg must input a string “ccgroup” ? if yes, maybe don’t let the user input it use string, maybe use -cc_stream_map_ccgroup is better, What do you think about it?

Are you suggesting to change the string from ‘–cc_stream_map’ to ‘-cc_stream_map_ccgroup’ and remove the ‘ccgroup’ key from the value string? Could you please clarify?

Apart from this, I would like to highlight some advanced use cases where current implementation will be more meaningful. It is possible that that there could be multiple instream ids under the same closed caption group or there can be different closed captions groups. Example:
-cc_stream_map "ccgroup:group1,instreamid:CC1,language:en ccgroup:group1,instreamid:CC2,language:sp”
-cc_stream_map "ccgroup:eng_group,instreamid:CC1,language:en ccgroup:span_group,instreamid:CC1,language:sp”

In these cases, the same ccgroup is mentioned in the –var_stream_map string as well.
-var_stream_map "v:0,a:0,ccgroup:group1 v:1,a:1,ccgroup:group1" or
-var_stream_map "v:0,a:0,ccgroup:eng_group v:1,a:1,ccgroup:span_group"

Here, ‘ccgroup’ is a common key between –cc_stream_map and –var_stream_map. So, it is better to keep this key so that it will more readable and easily configurable for the user.

>

>

> Thanks

>

> Steven

>

> _______________________________________________

> ffmpeg-devel mailing list

> ffmpeg-devel@ffmpeg.org

> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Steven Liu Jan. 14, 2018, 3:08 p.m.
> 在 2018年1月9日,下午5:54,Dixit, Vishwanath <vdixit@akamai.com> 写道:
> 
> 
> 
> On 1/9/18 3:05 PM, 刘歧 wrote:
>>>>> 
>>>> 
>>>> 
>>>> I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.
>>>> Maybe two ways:
>>>> 1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.
>>>> 2. parse the closed captions group string by KeyValue way? maybe this is better.
>>> 
>>> Actually, these requirements have been already handled. The parsing is happening based on key value pairs. The keys are ‘ccgroup’, ‘instreamid’, ‘language’. The values for these keys can be set after ‘:’ as given the examples in the patch. 
>>> I am assuming you are trying to set ccgroup name as closecgroup. In that case, please modify the command as below. Because, the string ‘ccgroup’ is a key value, whatever string that comes after ‘ccgroup:’ is the cc group name.  
>>> ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:closecgroup,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8
>>> 
>>> To clarify further, consider user wants to set cc group name as ‘mycaptions’, instream id as ‘SERVICE60’ language as Spanish, in that case, the map string would be
>>> -cc_stream_map "ccgroup:mycaptions,instreamid:SERVICE60,language:sp”  
>> I understand and i got the point, 
>> just one question, do you want force the user use ffmpeg must input a string “ccgroup” ? if yes, maybe don’t let the user input it use string, maybe use -cc_stream_map_ccgroup is better, What do you think about it?
> Are you suggesting to change the string from ‘–cc_stream_map’ to ‘-cc_stream_map_ccgroup’ and remove the ‘ccgroup’ key from the value string? Could you please clarify?
> 
> Apart from this, I would like to highlight some advanced use cases where current implementation will be more meaningful. It is possible that that there could be multiple instream ids under the same closed caption group or there can be different closed captions groups. Example:
> -cc_stream_map "ccgroup:group1,instreamid:CC1,language:en ccgroup:group1,instreamid:CC2,language:sp”
> -cc_stream_map "ccgroup:eng_group,instreamid:CC1,language:en ccgroup:span_group,instreamid:CC1,language:sp”
> 
> In these cases, the same ccgroup is mentioned in the –var_stream_map string as well.
> -var_stream_map "v:0,a:0,ccgroup:group1 v:1,a:1,ccgroup:group1" or
> -var_stream_map "v:0,a:0,ccgroup:eng_group v:1,a:1,ccgroup:span_group"
> 
> Here, ‘ccgroup’ is a common key between –cc_stream_map and –var_stream_map. So, it is better to keep this key so that it will more readable and easily configurable for the user.

Hi Dixit,

    I cannot found “ccgroup” string in rfc8216, or dose it in some other specifications? Or user MUST use “ccgroup” in the var_stream_map?
> 
>> 
>> 
>> Thanks
>> 
>> Steven
>> 
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 
> 
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Dixit, Vishwanath Jan. 15, 2018, 5:57 a.m.
On 1/14/18 8:38 PM, Liu Steven wrote:
>

>> 在 2018年1月9日,下午5:54,Dixit, Vishwanath <vdixit@akamai.com> 写道:

>>

>>

>>

>> On 1/9/18 3:05 PM, 刘歧 wrote:

>>>>>>

>>>>>

>>>>>

>>>>> I cannot sure use -cc_stream_map_ccgroup option is ok, because the ccgroup string is not standard, maybe it can be defined bu user.

>>>>> Maybe two ways:

>>>>> 1. use -cc_stream_map_ccgroup ? this way is defined the name by ffmpeg, cannot modify.

>>>>> 2. parse the closed captions group string by KeyValue way? maybe this is better.

>>>>

>>>> Actually, these requirements have been already handled. The parsing is happening based on key value pairs. The keys are ‘ccgroup’, ‘instreamid’, ‘language’. The values for these keys can be set after ‘:’ as given the examples in the patch. 

>>>> I am assuming you are trying to set ccgroup name as closecgroup. In that case, please modify the command as below. Because, the string ‘ccgroup’ is a key value, whatever string that comes after ‘ccgroup:’ is the cc group name.  

>>>> ./ffmpeg -re -f lavfi -i color=red  -g 25  -b:v 1000k -b:a 64k -a53cc 1 -f hls  -cc_stream_map "ccgroup:closecgroup,instreamid:CC1,language:en"   -master_pl_name master.m3u8  live/out.m3u8

>>>>

>>>> To clarify further, consider user wants to set cc group name as ‘mycaptions’, instream id as ‘SERVICE60’ language as Spanish, in that case, the map string would be

>>>> -cc_stream_map "ccgroup:mycaptions,instreamid:SERVICE60,language:sp”  

>>> I understand and i got the point, 

>>> just one question, do you want force the user use ffmpeg must input a string “ccgroup” ? if yes, maybe don’t let the user input it use string, maybe use -cc_stream_map_ccgroup is better, What do you think about it?

>> Are you suggesting to change the string from ‘–cc_stream_map’ to ‘-cc_stream_map_ccgroup’ and remove the ‘ccgroup’ key from the value string? Could you please clarify?

>>

>> Apart from this, I would like to highlight some advanced use cases where current implementation will be more meaningful. It is possible that that there could be multiple instream ids under the same closed caption group or there can be different closed captions groups. Example:

>> -cc_stream_map "ccgroup:group1,instreamid:CC1,language:en ccgroup:group1,instreamid:CC2,language:sp”

>> -cc_stream_map "ccgroup:eng_group,instreamid:CC1,language:en ccgroup:span_group,instreamid:CC1,language:sp”

>>

>> In these cases, the same ccgroup is mentioned in the –var_stream_map string as well.

>> -var_stream_map "v:0,a:0,ccgroup:group1 v:1,a:1,ccgroup:group1" or

>> -var_stream_map "v:0,a:0,ccgroup:eng_group v:1,a:1,ccgroup:span_group"

>>

>> Here, ‘ccgroup’ is a common key between –cc_stream_map and –var_stream_map. So, it is better to keep this key so that it will more readable and easily configurable for the user.

>

> Hi Dixit,

>

>     I cannot found “ccgroup” string in rfc8216, or dose it in some other specifications? Or user MUST use “ccgroup” in the var_stream_map?

Hi Steven,

The string “ccgroup” is defined by me in both ‘var_stream_map’ and ‘cc_stream_map’ just for convenience. In the list of key value attribute pairs that I have defined, “ccgroup” is a key to which user can set a GROUP-ID (as defined in section 4.3.4.1.  EXT-X-MEDIA of the RFC) for the closed captions group. This is a mandatory attribute in ‘cc_stream_map’ and optional attribute in ‘var_stream_map’.

Regards,
Vishwanath
>>

>>>

>>>

>>> Thanks

>>>

>>> Steven

>>>

Patch hide | download patch | download mbox

diff --git a/doc/muxers.texi b/doc/muxers.texi
index b060c4f..d9a5cc0 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -901,6 +901,43 @@  and they are mapped to the two video only variant streams with audio group names
 
 By default, a single hls variant containing all the encoded streams is created.
 
+@item cc_stream_map
+Map string which specifies different closed captions groups and their
+attributes. The closed captions stream groups are separated by space.
+Expected string format is like this
+"ccgroup:<group name>,instreamid:<INSTREAM-ID>,language:<language code> ....".
+'ccgroup' and 'instreamid' are mandatory attributes. 'language' is an optional
+attribute.
+The closed captions groups configured using this option are mapped to different
+variant streams by providing the same 'ccgroup' name in the
+@code{var_stream_map} string. If @code{var_stream_map} is not set, then the
+first available ccgroup in @code{cc_stream_map} is mapped to the output variant
+stream. The examples for these two use cases are given below.
+
+@example
+ffmpeg -re -i in.ts -b:v 1000k -b:a 64k -a53cc 1 -f hls \
+  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en" \
+  -master_pl_name master.m3u8 \
+  http://example.com/live/out.m3u8
+@end example
+This example adds @code{#EXT-X-MEDIA} tag with @code{TYPE=CLOSED-CAPTIONS} in
+the master playlist with group name 'cc', langauge 'en' (english) and
+INSTREAM-ID 'CC1'. Also, it adds @code{CLOSED-CAPTIONS} attribute with group
+name 'cc' for the output variant stream.
+@example
+ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k -b:a:1 32k \
+  -a53cc:0 1 -a53cc:1 1\
+  -map 0:v -map 0:a -map 0:v -map 0:a -f hls \
+  -cc_stream_map "ccgroup:cc,instreamid:CC1,language:en ccgroup:cc,instreamid:CC2,language:sp" \
+  -var_stream_map "v:0,a:0,ccgroup:cc v:1,a:1,ccgroup:cc" \
+  -master_pl_name master.m3u8 \
+  http://example.com/live/out_%v.m3u8
+@end example
+This example adds two @code{#EXT-X-MEDIA} tags with @code{TYPE=CLOSED-CAPTIONS} in
+the master playlist for the INSTREAM-IDs 'CC1' and 'CC2'. Also, it adds
+@code{CLOSED-CAPTIONS} attribute with group name 'cc' for the two output variant
+streams.
+
 @item master_pl_name
 Create HLS master playlist with the given name.
 
diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c
index 3345b89..39d0afe 100644
--- a/libavformat/dashenc.c
+++ b/libavformat/dashenc.c
@@ -820,7 +820,7 @@  static int write_manifest(AVFormatContext *s, int final)
                 stream_bitrate += max_audio_bitrate;
             }
             get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
-            ff_hls_write_stream_info(st, out, stream_bitrate, playlist_file, agroup);
+            ff_hls_write_stream_info(st, out, stream_bitrate, playlist_file, agroup, NULL);
         }
         avio_close(out);
         if (use_rename)
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index e36120c..4e4b287 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -145,9 +145,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;
@@ -196,11 +203,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;
@@ -1115,7 +1125,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) {
@@ -1142,6 +1153,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]);
@@ -1226,8 +1247,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);
+                aud_st ? vs->agroup : NULL, ccgroup);
 
         av_freep(&m3u8_rel_name);
     }
@@ -1714,6 +1750,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)) {
@@ -1744,9 +1785,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);
@@ -1764,6 +1890,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];
     }
@@ -2127,13 +2260,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;
 }
@@ -2470,13 +2612,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);
     }
 
@@ -2536,6 +2686,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 },
diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c
index 098dc89..e797f14 100644
--- a/libavformat/hlsplaylist.c
+++ b/libavformat/hlsplaylist.c
@@ -46,7 +46,8 @@  void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
 }
 
 void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
-                              int bandwidth, char *filename, char *agroup) {
+                              int bandwidth, char *filename, char *agroup,
+                              char *ccgroup) {
     if (!out || !filename)
         return;
 
@@ -62,6 +63,8 @@  void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
                 st->codecpar->height);
     if (agroup && strlen(agroup) > 0)
         avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
+    if (ccgroup && strlen(ccgroup) > 0)
+        avio_printf(out, ",CLOSED-CAPTIONS=\"%s\"", ccgroup);
     avio_printf(out, "\n%s\n\n", filename);
 }
 
diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h
index 9969315..29c5123 100644
--- a/libavformat/hlsplaylist.h
+++ b/libavformat/hlsplaylist.h
@@ -40,7 +40,8 @@  void ff_hls_write_playlist_version(AVIOContext *out, int version);
 void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
                                   char *filename, int name_id, int is_default);
 void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
-                              int bandwidth, char *filename, char *agroup);
+                              int bandwidth, char *filename, char *agroup,
+                              char *ccgroup);
 void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,
                                   int target_duration, int64_t sequence,
                                   uint32_t playlist_type);