@ -38,6 +38,16 @@
# include "os_support.h"
# include "url.h"
// See ISO/IEC 23009-1:2014 5.3.9.4.4
typedef enum {
DASH_TMPL_ID_UNDEFINED = - 1 ,
DASH_TMPL_ID_ESCAPE ,
DASH_TMPL_ID_REP_ID ,
DASH_TMPL_ID_NUMBER ,
DASH_TMPL_ID_BANDWIDTH ,
DASH_TMPL_ID_TIME ,
} DASHTmplId ;
typedef struct Segment {
char file [ 1024 ] ;
int64_t start_pos ;
@ -59,6 +69,7 @@ typedef struct OutputStream {
int nb_segments , segments_size , segment_index ;
Segment * * segments ;
int64_t first_dts , start_dts , end_dts ;
int bit_rate ;
char bandwidth_str [ 64 ] ;
char codec_str [ 100 ] ;
@ -79,6 +90,9 @@ typedef struct DASHContext {
int total_duration ;
char availability_start_time [ 100 ] ;
char dirname [ 1024 ] ;
const char * single_file_name ;
const char * init_seg_name ;
const char * media_seg_name ;
} DASHContext ;
static int dash_write ( void * opaque , uint8_t * buf , int buf_size )
@ -192,7 +206,7 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext
avio_printf ( out , " \t \t \t \t <SegmentTemplate timescale= \" %d \" " , timescale ) ;
if ( ! c - > use_timeline )
avio_printf ( out , " duration= \" %d \" " , c - > last_duration ) ;
avio_printf ( out , " initialization= \" init-stream$RepresentationID$.m4 s\" media= \" chunk-stream$RepresentationID$-$Number %%05d$.m4 s\" startNumber= \" %d \" > \n " , c - > use_timeline ? start_number : 1 ) ;
avio_printf ( out , " initialization= \" % s\" media= \" %s \" startNumber= \" %d \" > \n " , c - > init_seg_name , c - > media_seg_name , c - > use_timeline ? start_number : 1 ) ;
if ( c - > use_timeline ) {
avio_printf ( out , " \t \t \t \t \t <SegmentTimeline> \n " ) ;
for ( i = start_index ; i < os - > nb_segments ; ) {
@ -235,6 +249,120 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext
}
}
static DASHTmplId dash_read_tmpl_id ( const char * identifier , char * format_tag ,
size_t format_tag_size , const char * * ptr ) {
const char * next_ptr ;
DASHTmplId id_type = DASH_TMPL_ID_UNDEFINED ;
if ( av_strstart ( identifier , " $$ " , & next_ptr ) ) {
id_type = DASH_TMPL_ID_ESCAPE ;
* ptr = next_ptr ;
} else if ( av_strstart ( identifier , " $RepresentationID$ " , & next_ptr ) ) {
id_type = DASH_TMPL_ID_REP_ID ;
// default to basic format, as $RepresentationID$ identifiers
// are not allowed to have custom format-tags.
av_strlcpy ( format_tag , " %d " , format_tag_size ) ;
* ptr = next_ptr ;
} else { // the following identifiers may have an explicit format_tag
if ( av_strstart ( identifier , " $Number " , & next_ptr ) )
id_type = DASH_TMPL_ID_NUMBER ;
else if ( av_strstart ( identifier , " $Bandwidth " , & next_ptr ) )
id_type = DASH_TMPL_ID_BANDWIDTH ;
else if ( av_strstart ( identifier , " $Time " , & next_ptr ) )
id_type = DASH_TMPL_ID_TIME ;
else
id_type = DASH_TMPL_ID_UNDEFINED ;
// next parse the dash format-tag and generate a c-string format tag
// (next_ptr now points at the first '%' at the beginning of the format-tag)
if ( id_type ! = DASH_TMPL_ID_UNDEFINED ) {
const char * number_format = DASH_TMPL_ID_TIME ? " lld " : " d " ;
if ( next_ptr [ 0 ] = = ' $ ' ) { // no dash format-tag
snprintf ( format_tag , format_tag_size , " %%%s " , number_format ) ;
* ptr = & next_ptr [ 1 ] ;
} else {
const char * width_ptr ;
// only tolerate single-digit width-field (i.e. up to 9-digit width)
if ( av_strstart ( next_ptr , " %0 " , & width_ptr ) & &
av_isdigit ( width_ptr [ 0 ] ) & &
av_strstart ( & width_ptr [ 1 ] , " d$ " , & next_ptr ) ) {
// yes, we're using a format tag to build format_tag.
snprintf ( format_tag , format_tag_size , " %s%c%s " , " %0 " , width_ptr [ 0 ] , number_format ) ;
* ptr = next_ptr ;
} else {
av_log ( NULL , AV_LOG_WARNING , " Failed to parse format-tag beginning with %s. Expected either a "
" closing '$' character or a format-string like '%%0[width]d', "
" where width must be a single digit \n " , next_ptr ) ;
id_type = DASH_TMPL_ID_UNDEFINED ;
}
}
}
}
return id_type ;
}
static void dash_fill_tmpl_params ( char * dst , size_t buffer_size ,
const char * template , int rep_id ,
int number , int bit_rate ,
int64_t time ) {
int dst_pos = 0 ;
const char * t_cur = template ;
while ( dst_pos < buffer_size - 1 & & * t_cur ) {
int format_tag_size = 7 ;
char format_tag [ format_tag_size ] ; // May be "%d", "%0Xd", or "%0Xlld" (for $Time$), where X is in [0-9]
int n = 0 ;
DASHTmplId id_type ;
const char * t_next = strchr ( t_cur , ' $ ' ) ; // copy over everything up to the first '$' character
if ( t_next ) {
int num_copy_bytes = FFMIN ( t_next - t_cur , buffer_size - dst_pos - 1 ) ;
av_strlcpy ( & dst [ dst_pos ] , t_cur , num_copy_bytes + 1 ) ;
// advance
dst_pos + = num_copy_bytes ;
t_cur = t_next ;
} else { // no more DASH identifiers to substitute - just copy the rest over and break
av_strlcpy ( & dst [ dst_pos ] , t_cur , buffer_size - dst_pos ) ;
break ;
}
if ( dst_pos > = buffer_size - 1 | | ! * t_cur )
break ;
// t_cur is now pointing to a '$' character
id_type = dash_read_tmpl_id ( t_cur , format_tag , format_tag_size , & t_next ) ;
switch ( id_type ) {
case DASH_TMPL_ID_ESCAPE :
av_strlcpy ( & dst [ dst_pos ] , " $ " , 2 ) ;
n = 1 ;
break ;
case DASH_TMPL_ID_REP_ID :
n = snprintf ( & dst [ dst_pos ] , buffer_size - dst_pos , format_tag , rep_id ) ;
break ;
case DASH_TMPL_ID_NUMBER :
n = snprintf ( & dst [ dst_pos ] , buffer_size - dst_pos , format_tag , number ) ;
break ;
case DASH_TMPL_ID_BANDWIDTH :
n = snprintf ( & dst [ dst_pos ] , buffer_size - dst_pos , format_tag , bit_rate ) ;
break ;
case DASH_TMPL_ID_TIME :
n = snprintf ( & dst [ dst_pos ] , buffer_size - dst_pos , format_tag , time ) ;
break ;
case DASH_TMPL_ID_UNDEFINED :
// copy over one byte and advance
av_strlcpy ( & dst [ dst_pos ] , t_cur , 2 ) ;
n = 1 ;
t_next = & t_cur [ 1 ] ;
break ;
}
// t_next points just past the processed identifier
// n is the number of bytes that were attempted to be written to dst
// (may have failed to write all because buffer_size).
// advance
dst_pos + = FFMIN ( n , buffer_size - dst_pos - 1 ) ;
t_cur = t_next ;
}
}
static char * xmlescape ( const char * str ) {
int outlen = strlen ( str ) * 3 / 2 + 6 ;
char * out = av_realloc ( NULL , outlen + 1 ) ;
@ -403,6 +531,8 @@ static int dash_write_header(AVFormatContext *s)
char * ptr ;
char basename [ 1024 ] ;
if ( c - > single_file_name )
c - > single_file = 1 ;
if ( c - > single_file )
c - > use_template = 0 ;
@ -439,12 +569,12 @@ static int dash_write_header(AVFormatContext *s)
AVDictionary * opts = NULL ;
char filename [ 1024 ] ;
int bit_rate = s - > streams [ i ] - > codec - > bit_rate ?
os - > bit_rate = s - > streams [ i ] - > codec - > bit_rate ?
s - > streams [ i ] - > codec - > bit_rate :
s - > streams [ i ] - > codec - > rc_max_rate ;
if ( bit_rate ) {
if ( os - > bit_rate ) {
snprintf ( os - > bandwidth_str , sizeof ( os - > bandwidth_str ) ,
" bandwidth= \" %d \" " , bit_rate ) ;
" bandwidth= \" %d \" " , os - > bit_rate ) ;
} else {
int level = s - > strict_std_compliance > = FF_COMPLIANCE_STRICT ?
AV_LOG_ERROR : AV_LOG_WARNING ;
@ -479,10 +609,14 @@ static int dash_write_header(AVFormatContext *s)
goto fail ;
}
if ( c - > single_file )
snprintf ( os - > initfile , sizeof ( os - > initfile ) , " %s-stream%d.m4s " , basename , i ) ;
else
snprintf ( os - > initfile , sizeof ( os - > initfile ) , " init-stream%d.m4s " , i ) ;
if ( c - > single_file ) {
if ( c - > single_file_name )
dash_fill_tmpl_params ( os - > initfile , sizeof ( os - > initfile ) , c - > single_file_name , i , 0 , os - > bit_rate , 0 ) ;
else
snprintf ( os - > initfile , sizeof ( os - > initfile ) , " %s-stream%d.m4s " , basename , i ) ;
} else {
dash_fill_tmpl_params ( os - > initfile , sizeof ( os - > initfile ) , c - > init_seg_name , i , 0 , os - > bit_rate , 0 ) ;
}
snprintf ( filename , sizeof ( filename ) , " %s%s " , c - > dirname , os - > initfile ) ;
ret = ffurl_open ( & os - > out , filename , AVIO_FLAG_WRITE , & s - > interrupt_callback , NULL ) ;
if ( ret < 0 )
@ -627,7 +761,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream)
}
if ( ! c - > single_file ) {
snprintf ( filename , sizeof ( filename ) , " chunk-stream%d-%05d.m4s " , i , os - > segment_index ) ;
dash_fill_tmpl_params ( filename , sizeof ( filename ) , c - > media_seg_name , i , os - > segment_index , os - > bit_rate , os - > start_dts ) ;
snprintf ( full_path , sizeof ( full_path ) , " %s%s " , c - > dirname , filename ) ;
snprintf ( temp_path , sizeof ( temp_path ) , " %s.tmp " , full_path ) ;
ret = ffurl_open ( & os - > out , temp_path , AVIO_FLAG_WRITE , & s - > interrupt_callback , NULL ) ;
@ -772,6 +906,9 @@ static const AVOption options[] = {
{ " use_template " , " Use SegmentTemplate instead of SegmentList " , OFFSET ( use_template ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 1 , E } ,
{ " use_timeline " , " Use SegmentTimeline in SegmentTemplate " , OFFSET ( use_timeline ) , AV_OPT_TYPE_INT , { . i64 = 1 } , 0 , 1 , E } ,
{ " single_file " , " Store all segments in one file, accessed using byte ranges " , OFFSET ( single_file ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , 1 , E } ,
{ " single_file_name " , " DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges " , OFFSET ( single_file_name ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
{ " init_seg_name " , " DASH-templated name to used for the initialization segment " , OFFSET ( init_seg_name ) , AV_OPT_TYPE_STRING , { . str = " init-stream$RepresentationID$.m4s " } , 0 , 0 , E } ,
{ " 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 } ,
{ NULL } ,
} ;