2014-07-14 19:52:23 +03:00
/*
* WebM DASH Manifest XML muxer
* Copyright ( c ) 2014 Vignesh Venkatasubramanian
*
* This file is part of FFmpeg .
*
* FFmpeg is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* FFmpeg is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/*
* WebM DASH Specification :
* https : //sites.google.com/a/webmproject.org/wiki/adaptive-streaming/webm-dash-specification
2015-04-01 04:40:01 +02:00
* ISO DASH Specification :
* http : //standards.iso.org/ittf/PubliclyAvailableStandards/c065274_ISO_IEC_23009-1_2014.zip
2014-07-14 19:52:23 +03:00
*/
2015-04-01 04:40:01 +02:00
# include <float.h>
2014-07-14 19:52:23 +03:00
# include <stdint.h>
# include <string.h>
# include "avformat.h"
# include "matroska.h"
# include "libavutil/avstring.h"
# include "libavutil/dict.h"
# include "libavutil/opt.h"
2015-04-01 04:40:01 +02:00
# include "libavutil/time_internal.h"
2014-07-14 19:52:23 +03:00
typedef struct AdaptationSet {
char id [ 10 ] ;
int * streams ;
int nb_streams ;
} AdaptationSet ;
typedef struct WebMDashMuxContext {
const AVClass * class ;
char * adaptation_sets ;
AdaptationSet * as ;
int nb_as ;
2014-11-11 20:02:05 +02:00
int representation_id ;
2015-04-01 04:40:01 +02:00
int is_live ;
int chunk_start_index ;
int chunk_duration ;
char * utc_timing_url ;
double time_shift_buffer_depth ;
2015-04-22 02:35:29 +02:00
int minimum_update_period ;
2014-07-14 19:52:23 +03:00
} WebMDashMuxContext ;
static const char * get_codec_name ( int codec_id )
{
2020-03-30 03:09:41 +02:00
return avcodec_descriptor_get ( codec_id ) - > name ;
2014-07-14 19:52:23 +03:00
}
static double get_duration ( AVFormatContext * s )
{
int i = 0 ;
double max = 0.0 ;
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
AVDictionaryEntry * duration = av_dict_get ( s - > streams [ i ] - > metadata ,
DURATION , NULL , 0 ) ;
2014-08-15 21:33:21 +03:00
if ( ! duration | | atof ( duration - > value ) < 0 ) continue ;
2014-07-14 19:52:23 +03:00
if ( atof ( duration - > value ) > max ) max = atof ( duration - > value ) ;
}
return max / 1000 ;
}
2015-05-14 19:32:24 +02:00
static int write_header ( AVFormatContext * s )
2014-07-14 19:52:23 +03:00
{
2015-04-01 04:40:01 +02:00
WebMDashMuxContext * w = s - > priv_data ;
2014-07-14 19:52:23 +03:00
double min_buffer_time = 1.0 ;
avio_printf ( s - > pb , " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ) ;
avio_printf ( s - > pb , " <MPD \n " ) ;
avio_printf ( s - > pb , " xmlns:xsi= \" http://www.w3.org/2001/XMLSchema-instance \" \n " ) ;
avio_printf ( s - > pb , " xmlns= \" urn:mpeg:DASH:schema:MPD:2011 \" \n " ) ;
avio_printf ( s - > pb , " xsi:schemaLocation= \" urn:mpeg:DASH:schema:MPD:2011 \" \n " ) ;
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " type= \" %s \" \n " , w - > is_live ? " dynamic " : " static " ) ;
if ( ! w - > is_live ) {
avio_printf ( s - > pb , " mediaPresentationDuration= \" PT%gS \" \n " ,
get_duration ( s ) ) ;
}
avio_printf ( s - > pb , " minBufferTime= \" PT%gS \" \n " , min_buffer_time ) ;
avio_printf ( s - > pb , " profiles= \" %s \" %s " ,
w - > is_live ? " urn:mpeg:dash:profile:isoff-live:2011 " : " urn:webm:dash:profile:webm-on-demand:2012 " ,
w - > is_live ? " \n " : " > \n " ) ;
if ( w - > is_live ) {
2015-04-07 21:21:02 +02:00
time_t local_time = time ( NULL ) ;
struct tm gmt_buffer ;
struct tm * gmt = gmtime_r ( & local_time , & gmt_buffer ) ;
2015-04-14 19:54:23 +02:00
char gmt_iso [ 21 ] ;
2015-05-14 19:32:24 +02:00
if ( ! strftime ( gmt_iso , 21 , " %Y-%m-%dT%H:%M:%SZ " , gmt ) ) {
return AVERROR_UNKNOWN ;
}
2020-03-18 00:13:05 +02:00
if ( s - > flags & AVFMT_FLAG_BITEXACT ) {
2015-04-07 21:21:02 +02:00
av_strlcpy ( gmt_iso , " " , 1 ) ;
}
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " availabilityStartTime= \" %s \" \n " , gmt_iso ) ;
2015-04-22 01:36:52 +02:00
avio_printf ( s - > pb , " timeShiftBufferDepth= \" PT%gS \" \n " , w - > time_shift_buffer_depth ) ;
2015-04-22 02:35:29 +02:00
avio_printf ( s - > pb , " minimumUpdatePeriod= \" PT%dS \" " , w - > minimum_update_period ) ;
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " > \n " ) ;
2015-04-28 01:26:34 +02:00
if ( w - > utc_timing_url ) {
avio_printf ( s - > pb , " <UTCTiming \n " ) ;
avio_printf ( s - > pb , " schemeIdUri= \" urn:mpeg:dash:utc:http-iso:2014 \" \n " ) ;
avio_printf ( s - > pb , " value= \" %s \" /> \n " , w - > utc_timing_url ) ;
}
2015-04-01 04:40:01 +02:00
}
2015-05-14 19:32:24 +02:00
return 0 ;
2014-07-14 19:52:23 +03:00
}
static void write_footer ( AVFormatContext * s )
{
2014-09-16 18:24:56 +03:00
avio_printf ( s - > pb , " </MPD> \n " ) ;
2014-07-14 19:52:23 +03:00
}
static int subsegment_alignment ( AVFormatContext * s , AdaptationSet * as ) {
int i ;
AVDictionaryEntry * gold = av_dict_get ( s - > streams [ as - > streams [ 0 ] ] - > metadata ,
CUE_TIMESTAMPS , NULL , 0 ) ;
2014-08-15 21:33:21 +03:00
if ( ! gold ) return 0 ;
2014-07-14 19:52:23 +03:00
for ( i = 1 ; i < as - > nb_streams ; i + + ) {
AVDictionaryEntry * ts = av_dict_get ( s - > streams [ as - > streams [ i ] ] - > metadata ,
CUE_TIMESTAMPS , NULL , 0 ) ;
2014-08-15 21:33:21 +03:00
if ( ! ts | | strncmp ( gold - > value , ts - > value , strlen ( gold - > value ) ) ) return 0 ;
2014-07-14 19:52:23 +03:00
}
return 1 ;
}
static int bitstream_switching ( AVFormatContext * s , AdaptationSet * as ) {
int i ;
AVDictionaryEntry * gold_track_num = av_dict_get ( s - > streams [ as - > streams [ 0 ] ] - > metadata ,
TRACK_NUMBER , NULL , 0 ) ;
2016-04-10 21:58:15 +02:00
AVCodecParameters * gold_par = s - > streams [ as - > streams [ 0 ] ] - > codecpar ;
2014-08-15 21:33:21 +03:00
if ( ! gold_track_num ) return 0 ;
2014-07-14 19:52:23 +03:00
for ( i = 1 ; i < as - > nb_streams ; i + + ) {
AVDictionaryEntry * track_num = av_dict_get ( s - > streams [ as - > streams [ i ] ] - > metadata ,
TRACK_NUMBER , NULL , 0 ) ;
2016-04-10 21:58:15 +02:00
AVCodecParameters * par = s - > streams [ as - > streams [ i ] ] - > codecpar ;
2014-08-15 21:33:21 +03:00
if ( ! track_num | |
2014-07-14 19:52:23 +03:00
strncmp ( gold_track_num - > value , track_num - > value , strlen ( gold_track_num - > value ) ) | |
2016-04-10 21:58:15 +02:00
gold_par - > codec_id ! = par - > codec_id | |
gold_par - > extradata_size ! = par - > extradata_size | |
memcmp ( gold_par - > extradata , par - > extradata , par - > extradata_size ) ) {
2014-07-14 19:52:23 +03:00
return 0 ;
}
}
return 1 ;
}
2014-10-01 20:13:32 +03:00
/*
* Writes a Representation within an Adaptation Set . Returns 0 on success and
* < 0 on failure .
*/
2015-04-01 04:40:01 +02:00
static int write_representation ( AVFormatContext * s , AVStream * stream , char * id ,
2014-10-01 20:13:32 +03:00
int output_width , int output_height ,
int output_sample_rate ) {
2015-04-01 04:40:01 +02:00
WebMDashMuxContext * w = s - > priv_data ;
2014-10-01 20:13:32 +03:00
AVDictionaryEntry * irange = av_dict_get ( stream - > metadata , INITIALIZATION_RANGE , NULL , 0 ) ;
AVDictionaryEntry * cues_start = av_dict_get ( stream - > metadata , CUES_START , NULL , 0 ) ;
AVDictionaryEntry * cues_end = av_dict_get ( stream - > metadata , CUES_END , NULL , 0 ) ;
AVDictionaryEntry * filename = av_dict_get ( stream - > metadata , FILENAME , NULL , 0 ) ;
AVDictionaryEntry * bandwidth = av_dict_get ( stream - > metadata , BANDWIDTH , NULL , 0 ) ;
2017-04-12 06:33:28 +02:00
const char * bandwidth_str ;
2015-04-01 04:40:01 +02:00
if ( ( w - > is_live & & ( ! filename ) ) | |
( ! w - > is_live & & ( ! irange | | ! cues_start | | ! cues_end | | ! filename | | ! bandwidth ) ) ) {
2015-04-17 02:32:21 +02:00
return AVERROR_INVALIDDATA ;
2014-10-01 20:13:32 +03:00
}
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " <Representation id= \" %s \" " , id ) ;
2017-04-12 06:33:28 +02:00
// if bandwidth for live was not provided, use a default
if ( w - > is_live & & ! bandwidth ) {
bandwidth_str = ( stream - > codecpar - > codec_type = = AVMEDIA_TYPE_AUDIO ) ? " 128000 " : " 1000000 " ;
} else {
bandwidth_str = bandwidth - > value ;
}
avio_printf ( s - > pb , " bandwidth= \" %s \" " , bandwidth_str ) ;
2016-04-10 21:58:15 +02:00
if ( stream - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO & & output_width )
avio_printf ( s - > pb , " width= \" %d \" " , stream - > codecpar - > width ) ;
if ( stream - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO & & output_height )
avio_printf ( s - > pb , " height= \" %d \" " , stream - > codecpar - > height ) ;
if ( stream - > codecpar - > codec_type = = AVMEDIA_TYPE_AUDIO & & output_sample_rate )
avio_printf ( s - > pb , " audioSamplingRate= \" %d \" " , stream - > codecpar - > sample_rate ) ;
2015-04-01 04:40:01 +02:00
if ( w - > is_live ) {
// For live streams, Codec and Mime Type always go in the Representation tag.
2016-04-10 21:58:15 +02:00
avio_printf ( s - > pb , " codecs= \" %s \" " , get_codec_name ( stream - > codecpar - > codec_id ) ) ;
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " mimeType= \" %s/webm \" " ,
2016-04-10 21:58:15 +02:00
stream - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO ? " video " : " audio " ) ;
2015-04-01 04:40:01 +02:00
// For live streams, subsegments always start with key frames. So this
// is always 1.
avio_printf ( s - > pb , " startsWithSAP= \" 1 \" " ) ;
avio_printf ( s - > pb , " > " ) ;
} else {
avio_printf ( s - > pb , " > \n " ) ;
avio_printf ( s - > pb , " <BaseURL>%s</BaseURL> \n " , filename - > value ) ;
avio_printf ( s - > pb , " <SegmentBase \n " ) ;
avio_printf ( s - > pb , " indexRange= \" %s-%s \" > \n " , cues_start - > value , cues_end - > value ) ;
avio_printf ( s - > pb , " <Initialization \n " ) ;
avio_printf ( s - > pb , " range= \" 0-%s \" /> \n " , irange - > value ) ;
avio_printf ( s - > pb , " </SegmentBase> \n " ) ;
}
2014-10-01 20:13:32 +03:00
avio_printf ( s - > pb , " </Representation> \n " ) ;
return 0 ;
}
/*
* Checks if width of all streams are the same . Returns 1 if true , 0 otherwise .
*/
static int check_matching_width ( AVFormatContext * s , AdaptationSet * as ) {
int first_width , i ;
if ( as - > nb_streams < 2 ) return 1 ;
2016-04-10 21:58:15 +02:00
first_width = s - > streams [ as - > streams [ 0 ] ] - > codecpar - > width ;
2014-10-01 20:13:32 +03:00
for ( i = 1 ; i < as - > nb_streams ; i + + )
2016-04-10 21:58:15 +02:00
if ( first_width ! = s - > streams [ as - > streams [ i ] ] - > codecpar - > width )
2014-10-01 20:13:32 +03:00
return 0 ;
return 1 ;
}
/*
* Checks if height of all streams are the same . Returns 1 if true , 0 otherwise .
*/
static int check_matching_height ( AVFormatContext * s , AdaptationSet * as ) {
int first_height , i ;
if ( as - > nb_streams < 2 ) return 1 ;
2016-04-10 21:58:15 +02:00
first_height = s - > streams [ as - > streams [ 0 ] ] - > codecpar - > height ;
2014-10-01 20:13:32 +03:00
for ( i = 1 ; i < as - > nb_streams ; i + + )
2016-04-10 21:58:15 +02:00
if ( first_height ! = s - > streams [ as - > streams [ i ] ] - > codecpar - > height )
2014-10-01 20:13:32 +03:00
return 0 ;
return 1 ;
}
/*
* Checks if sample rate of all streams are the same . Returns 1 if true , 0 otherwise .
*/
static int check_matching_sample_rate ( AVFormatContext * s , AdaptationSet * as ) {
int first_sample_rate , i ;
if ( as - > nb_streams < 2 ) return 1 ;
2016-04-10 21:58:15 +02:00
first_sample_rate = s - > streams [ as - > streams [ 0 ] ] - > codecpar - > sample_rate ;
2014-10-01 20:13:32 +03:00
for ( i = 1 ; i < as - > nb_streams ; i + + )
2016-04-10 21:58:15 +02:00
if ( first_sample_rate ! = s - > streams [ as - > streams [ i ] ] - > codecpar - > sample_rate )
2014-10-01 20:13:32 +03:00
return 0 ;
return 1 ;
}
2015-04-17 02:32:21 +02:00
static void free_adaptation_sets ( AVFormatContext * s ) {
WebMDashMuxContext * w = s - > priv_data ;
int i ;
for ( i = 0 ; i < w - > nb_as ; i + + ) {
av_freep ( & w - > as [ i ] . streams ) ;
}
av_freep ( & w - > as ) ;
w - > nb_as = 0 ;
}
2015-04-01 04:40:01 +02:00
/*
* Parses a live header filename and computes the representation id ,
* initialization pattern and the media pattern . Pass NULL if you don ' t want to
* compute any of those 3. Returns 0 on success and non - zero on failure .
*
* Name of the header file should conform to the following pattern :
* < file_description > _ < representation_id > . hdr where < file_description > can be
* anything . The chunks should be named according to the following pattern :
* < file_description > _ < representation_id > _ < chunk_number > . chk
*/
static int parse_filename ( char * filename , char * * representation_id ,
char * * initialization_pattern , char * * media_pattern ) {
char * underscore_pos = NULL ;
char * period_pos = NULL ;
char * temp_pos = NULL ;
char * filename_str = av_strdup ( filename ) ;
2017-07-14 16:32:42 +02:00
int ret = 0 ;
if ( ! filename_str ) {
ret = AVERROR ( ENOMEM ) ;
goto end ;
}
2015-04-01 04:40:01 +02:00
temp_pos = av_stristr ( filename_str , " _ " ) ;
while ( temp_pos ) {
underscore_pos = temp_pos + 1 ;
temp_pos = av_stristr ( temp_pos + 1 , " _ " ) ;
}
2017-07-14 16:32:42 +02:00
if ( ! underscore_pos ) {
ret = AVERROR_INVALIDDATA ;
goto end ;
}
2015-04-01 04:40:01 +02:00
period_pos = av_stristr ( underscore_pos , " . " ) ;
2017-07-14 16:32:42 +02:00
if ( ! period_pos ) {
ret = AVERROR_INVALIDDATA ;
goto end ;
}
2015-04-01 04:40:01 +02:00
* ( underscore_pos - 1 ) = 0 ;
if ( representation_id ) {
* representation_id = av_malloc ( period_pos - underscore_pos + 1 ) ;
2017-07-14 16:32:42 +02:00
if ( ! ( * representation_id ) ) {
ret = AVERROR ( ENOMEM ) ;
goto end ;
}
2015-04-01 04:40:01 +02:00
av_strlcpy ( * representation_id , underscore_pos , period_pos - underscore_pos + 1 ) ;
}
if ( initialization_pattern ) {
* initialization_pattern = av_asprintf ( " %s_$RepresentationID$.hdr " ,
filename_str ) ;
2017-07-14 16:32:42 +02:00
if ( ! ( * initialization_pattern ) ) {
ret = AVERROR ( ENOMEM ) ;
goto end ;
}
2015-04-01 04:40:01 +02:00
}
if ( media_pattern ) {
* media_pattern = av_asprintf ( " %s_$RepresentationID$_$Number$.chk " ,
filename_str ) ;
2017-07-14 16:32:42 +02:00
if ( ! ( * media_pattern ) ) {
ret = AVERROR ( ENOMEM ) ;
goto end ;
}
2015-04-01 04:40:01 +02:00
}
2017-07-14 16:32:42 +02:00
end :
av_freep ( & filename_str ) ;
return ret ;
2015-04-01 04:40:01 +02:00
}
2014-07-14 19:52:23 +03:00
/*
* Writes an Adaptation Set . Returns 0 on success and < 0 on failure .
*/
static int write_adaptation_set ( AVFormatContext * s , int as_index )
{
WebMDashMuxContext * w = s - > priv_data ;
AdaptationSet * as = & w - > as [ as_index ] ;
2016-04-10 21:58:15 +02:00
AVCodecParameters * par = s - > streams [ as - > streams [ 0 ] ] - > codecpar ;
2014-10-01 20:13:32 +03:00
AVDictionaryEntry * lang ;
2014-07-14 19:52:23 +03:00
int i ;
static const char boolean [ 2 ] [ 6 ] = { " false " , " true " } ;
int subsegmentStartsWithSAP = 1 ;
2014-10-01 20:13:32 +03:00
// Width, Height and Sample Rate will go in the AdaptationSet tag if they
// are the same for all contained Representations. otherwise, they will go
2015-04-01 04:40:01 +02:00
// on their respective Representation tag. For live streams, they always go
// in the Representation tag.
2014-10-01 20:13:32 +03:00
int width_in_as = 1 , height_in_as = 1 , sample_rate_in_as = 1 ;
2016-04-10 21:58:15 +02:00
if ( par - > codec_type = = AVMEDIA_TYPE_VIDEO ) {
2015-04-01 04:40:01 +02:00
width_in_as = ! w - > is_live & & check_matching_width ( s , as ) ;
height_in_as = ! w - > is_live & & check_matching_height ( s , as ) ;
2014-10-01 20:13:32 +03:00
} else {
2015-04-01 04:40:01 +02:00
sample_rate_in_as = ! w - > is_live & & check_matching_sample_rate ( s , as ) ;
2014-10-01 20:13:32 +03:00
}
2014-07-14 19:52:23 +03:00
avio_printf ( s - > pb , " <AdaptationSet id= \" %s \" " , as - > id ) ;
avio_printf ( s - > pb , " mimeType= \" %s/webm \" " ,
2016-04-10 21:58:15 +02:00
par - > codec_type = = AVMEDIA_TYPE_VIDEO ? " video " : " audio " ) ;
avio_printf ( s - > pb , " codecs= \" %s \" " , get_codec_name ( par - > codec_id ) ) ;
2014-07-14 19:52:23 +03:00
lang = av_dict_get ( s - > streams [ as - > streams [ 0 ] ] - > metadata , " language " , NULL , 0 ) ;
2014-08-15 22:31:59 +03:00
if ( lang ) avio_printf ( s - > pb , " lang= \" %s \" " , lang - > value ) ;
2014-07-14 19:52:23 +03:00
2016-04-10 21:58:15 +02:00
if ( par - > codec_type = = AVMEDIA_TYPE_VIDEO & & width_in_as )
avio_printf ( s - > pb , " width= \" %d \" " , par - > width ) ;
if ( par - > codec_type = = AVMEDIA_TYPE_VIDEO & & height_in_as )
avio_printf ( s - > pb , " height= \" %d \" " , par - > height ) ;
if ( par - > codec_type = = AVMEDIA_TYPE_AUDIO & & sample_rate_in_as )
avio_printf ( s - > pb , " audioSamplingRate= \" %d \" " , par - > sample_rate ) ;
2014-07-14 19:52:23 +03:00
avio_printf ( s - > pb , " bitstreamSwitching= \" %s \" " ,
boolean [ bitstream_switching ( s , as ) ] ) ;
avio_printf ( s - > pb , " subsegmentAlignment= \" %s \" " ,
2015-04-01 04:40:01 +02:00
boolean [ w - > is_live | | subsegment_alignment ( s , as ) ] ) ;
2014-07-14 19:52:23 +03:00
for ( i = 0 ; i < as - > nb_streams ; i + + ) {
AVDictionaryEntry * kf = av_dict_get ( s - > streams [ as - > streams [ i ] ] - > metadata ,
CLUSTER_KEYFRAME , NULL , 0 ) ;
2015-04-01 04:40:01 +02:00
if ( ! w - > is_live & & ( ! kf | | ! strncmp ( kf - > value , " 0 " , 1 ) ) ) subsegmentStartsWithSAP = 0 ;
2014-07-14 19:52:23 +03:00
}
avio_printf ( s - > pb , " subsegmentStartsWithSAP= \" %d \" " , subsegmentStartsWithSAP ) ;
avio_printf ( s - > pb , " > \n " ) ;
2015-04-01 04:40:01 +02:00
if ( w - > is_live ) {
AVDictionaryEntry * filename =
av_dict_get ( s - > streams [ as - > streams [ 0 ] ] - > metadata , FILENAME , NULL , 0 ) ;
char * initialization_pattern = NULL ;
char * media_pattern = NULL ;
int ret = parse_filename ( filename - > value , NULL , & initialization_pattern ,
& media_pattern ) ;
if ( ret ) return ret ;
avio_printf ( s - > pb , " <ContentComponent id= \" 1 \" type= \" %s \" /> \n " ,
2016-04-10 21:58:15 +02:00
par - > codec_type = = AVMEDIA_TYPE_VIDEO ? " video " : " audio " ) ;
2015-04-01 04:40:01 +02:00
avio_printf ( s - > pb , " <SegmentTemplate " ) ;
avio_printf ( s - > pb , " timescale= \" 1000 \" " ) ;
avio_printf ( s - > pb , " duration= \" %d \" " , w - > chunk_duration ) ;
avio_printf ( s - > pb , " media= \" %s \" " , media_pattern ) ;
avio_printf ( s - > pb , " startNumber= \" %d \" " , w - > chunk_start_index ) ;
avio_printf ( s - > pb , " initialization= \" %s \" " , initialization_pattern ) ;
avio_printf ( s - > pb , " /> \n " ) ;
av_free ( initialization_pattern ) ;
av_free ( media_pattern ) ;
}
2014-07-14 19:52:23 +03:00
for ( i = 0 ; i < as - > nb_streams ; i + + ) {
2015-04-01 04:40:01 +02:00
char * representation_id = NULL ;
2015-04-17 02:32:21 +02:00
int ret ;
2015-04-01 04:40:01 +02:00
if ( w - > is_live ) {
AVDictionaryEntry * filename =
av_dict_get ( s - > streams [ as - > streams [ i ] ] - > metadata , FILENAME , NULL , 0 ) ;
2015-08-20 17:18:14 +02:00
if ( ! filename )
return AVERROR ( EINVAL ) ;
if ( ret = parse_filename ( filename - > value , & representation_id , NULL , NULL ) )
2015-04-17 02:32:21 +02:00
return ret ;
2015-04-01 04:40:01 +02:00
} else {
representation_id = av_asprintf ( " %d " , w - > representation_id + + ) ;
2015-04-17 02:32:21 +02:00
if ( ! representation_id ) return AVERROR ( ENOMEM ) ;
2015-04-01 04:40:01 +02:00
}
2015-04-17 02:32:21 +02:00
ret = write_representation ( s , s - > streams [ as - > streams [ i ] ] ,
representation_id , ! width_in_as ,
! height_in_as , ! sample_rate_in_as ) ;
2015-04-01 04:40:01 +02:00
av_free ( representation_id ) ;
2015-04-20 20:20:41 +02:00
if ( ret ) return ret ;
2014-07-14 19:52:23 +03:00
}
avio_printf ( s - > pb , " </AdaptationSet> \n " ) ;
return 0 ;
}
static int to_integer ( char * p , int len )
{
int ret ;
2020-04-04 01:30:14 +02:00
char * q = av_malloc ( len ) ;
2015-04-01 18:11:48 +02:00
if ( ! q )
return AVERROR ( ENOMEM ) ;
2014-08-29 14:02:34 +03:00
av_strlcpy ( q , p , len ) ;
2014-07-14 19:52:23 +03:00
ret = atoi ( q ) ;
av_free ( q ) ;
return ret ;
}
static int parse_adaptation_sets ( AVFormatContext * s )
{
WebMDashMuxContext * w = s - > priv_data ;
char * p = w - > adaptation_sets ;
char * q ;
enum { new_set , parsed_id , parsing_streams } state ;
2017-04-20 14:14:42 +02:00
if ( ! w - > adaptation_sets ) {
av_log ( s , AV_LOG_ERROR , " The 'adaptation_sets' option must be set. \n " ) ;
return AVERROR ( EINVAL ) ;
}
2014-07-14 19:52:23 +03:00
// syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
state = new_set ;
while ( p < w - > adaptation_sets + strlen ( w - > adaptation_sets ) ) {
if ( * p = = ' ' )
continue ;
else if ( state = = new_set & & ! strncmp ( p , " id= " , 3 ) ) {
2015-04-17 02:32:21 +02:00
void * mem = av_realloc ( w - > as , sizeof ( * w - > as ) * ( w - > nb_as + 1 ) ) ;
2019-02-13 11:15:04 +02:00
const char * comma ;
2015-04-17 02:32:21 +02:00
if ( mem = = NULL )
2015-04-01 18:11:48 +02:00
return AVERROR ( ENOMEM ) ;
2015-04-17 02:32:21 +02:00
w - > as = mem ;
+ + w - > nb_as ;
2014-07-14 19:52:23 +03:00
w - > as [ w - > nb_as - 1 ] . nb_streams = 0 ;
w - > as [ w - > nb_as - 1 ] . streams = NULL ;
p + = 3 ; // consume "id="
q = w - > as [ w - > nb_as - 1 ] . id ;
2019-02-13 11:15:04 +02:00
comma = strchr ( p , ' , ' ) ;
if ( ! comma | | comma - p > = sizeof ( w - > as [ w - > nb_as - 1 ] . id ) ) {
av_log ( s , AV_LOG_ERROR , " 'id' in 'adaptation_sets' is malformed. \n " ) ;
return AVERROR ( EINVAL ) ;
}
2014-07-14 19:52:23 +03:00
while ( * p ! = ' , ' ) * q + + = * p + + ;
* q = 0 ;
p + + ;
state = parsed_id ;
} else if ( state = = parsed_id & & ! strncmp ( p , " streams= " , 8 ) ) {
p + = 8 ; // consume "streams="
state = parsing_streams ;
} else if ( state = = parsing_streams ) {
struct AdaptationSet * as = & w - > as [ w - > nb_as - 1 ] ;
2019-12-20 22:21:59 +02:00
int ret = av_reallocp_array ( & as - > streams , + + as - > nb_streams ,
sizeof ( * as - > streams ) ) ;
if ( ret < 0 )
return ret ;
2014-07-14 19:52:23 +03:00
q = p ;
while ( * q ! = ' \0 ' & & * q ! = ' , ' & & * q ! = ' ' ) q + + ;
2014-08-29 14:02:34 +03:00
as - > streams [ as - > nb_streams - 1 ] = to_integer ( p , q - p + 1 ) ;
2017-04-20 17:17:44 +02:00
if ( as - > streams [ as - > nb_streams - 1 ] < 0 | |
as - > streams [ as - > nb_streams - 1 ] > = s - > nb_streams ) {
av_log ( s , AV_LOG_ERROR , " Invalid value for 'streams' in adapation_sets. \n " ) ;
return AVERROR ( EINVAL ) ;
}
2014-07-14 19:52:23 +03:00
if ( * q = = ' \0 ' ) break ;
if ( * q = = ' ' ) state = new_set ;
p = + + q ;
} else {
return - 1 ;
}
}
return 0 ;
}
static int webm_dash_manifest_write_header ( AVFormatContext * s )
{
int i ;
double start = 0.0 ;
2015-04-17 02:32:21 +02:00
int ret ;
2014-07-14 19:52:23 +03:00
WebMDashMuxContext * w = s - > priv_data ;
2020-03-30 02:50:02 +02:00
for ( unsigned i = 0 ; i < s - > nb_streams ; i + + ) {
enum AVCodecID codec_id = s - > streams [ i ] - > codecpar - > codec_id ;
if ( codec_id ! = AV_CODEC_ID_VP8 & & codec_id ! = AV_CODEC_ID_VP9 & &
codec_id ! = AV_CODEC_ID_VORBIS & & codec_id ! = AV_CODEC_ID_OPUS )
return AVERROR ( EINVAL ) ;
}
2015-04-17 02:32:21 +02:00
ret = parse_adaptation_sets ( s ) ;
if ( ret < 0 ) {
2015-05-14 19:32:24 +02:00
goto fail ;
}
ret = write_header ( s ) ;
if ( ret < 0 ) {
goto fail ;
2015-04-17 02:32:21 +02:00
}
2014-07-14 19:52:23 +03:00
avio_printf ( s - > pb , " <Period id= \" 0 \" " ) ;
avio_printf ( s - > pb , " start= \" PT%gS \" " , start ) ;
2015-04-01 04:40:01 +02:00
if ( ! w - > is_live ) {
avio_printf ( s - > pb , " duration= \" PT%gS \" " , get_duration ( s ) ) ;
}
2014-07-14 19:52:23 +03:00
avio_printf ( s - > pb , " > \n " ) ;
for ( i = 0 ; i < w - > nb_as ; i + + ) {
2015-04-17 02:32:21 +02:00
ret = write_adaptation_set ( s , i ) ;
if ( ret < 0 ) {
2015-05-14 19:32:24 +02:00
goto fail ;
2015-04-17 02:32:21 +02:00
}
2014-07-14 19:52:23 +03:00
}
avio_printf ( s - > pb , " </Period> \n " ) ;
write_footer ( s ) ;
2015-05-14 19:32:24 +02:00
fail :
free_adaptation_sets ( s ) ;
return ret < 0 ? ret : 0 ;
2014-07-14 19:52:23 +03:00
}
static int webm_dash_manifest_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
return AVERROR_EOF ;
}
# define OFFSET(x) offsetof(WebMDashMuxContext, x)
static const AVOption options [ ] = {
{ " adaptation_sets " , " Adaptation sets. Syntax: id=0,streams=0,1,2 id=1,streams=3,4 and so on " , OFFSET ( adaptation_sets ) , AV_OPT_TYPE_STRING , { 0 } , 0 , 0 , AV_OPT_FLAG_ENCODING_PARAM } ,
2015-11-21 23:05:07 +02:00
{ " live " , " create a live stream manifest " , OFFSET ( is_live ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , AV_OPT_FLAG_ENCODING_PARAM } ,
2015-04-01 04:40:01 +02:00
{ " chunk_start_index " , " start index of the chunk " , OFFSET ( chunk_start_index ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
{ " chunk_duration_ms " , " duration of each chunk (in milliseconds) " , OFFSET ( chunk_duration ) , AV_OPT_TYPE_INT , { . i64 = 1000 } , 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
{ " utc_timing_url " , " URL of the page that will return the UTC timestamp in ISO format " , OFFSET ( utc_timing_url ) , AV_OPT_TYPE_STRING , { 0 } , 0 , 0 , AV_OPT_FLAG_ENCODING_PARAM } ,
{ " time_shift_buffer_depth " , " Smallest time (in seconds) shifting buffer for which any Representation is guaranteed to be available. " , OFFSET ( time_shift_buffer_depth ) , AV_OPT_TYPE_DOUBLE , { . dbl = 60.0 } , 1.0 , DBL_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
2015-04-22 02:35:29 +02:00
{ " minimum_update_period " , " Minimum Update Period (in seconds) of the manifest. " , OFFSET ( minimum_update_period ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
2014-07-14 19:52:23 +03:00
{ NULL } ,
} ;
static const AVClass webm_dash_class = {
. class_name = " WebM DASH Manifest muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
AVOutputFormat ff_webm_dash_manifest_muxer = {
. name = " webm_dash_manifest " ,
. long_name = NULL_IF_CONFIG_SMALL ( " WebM DASH Manifest " ) ,
. mime_type = " application/xml " ,
. extensions = " xml " ,
. priv_data_size = sizeof ( WebMDashMuxContext ) ,
. write_header = webm_dash_manifest_write_header ,
. write_packet = webm_dash_manifest_write_packet ,
. priv_class = & webm_dash_class ,
} ;