@ -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 } ,