2012-10-26 16:36:56 +02:00
/*
* Apple HTTP Live Streaming segmenter
* Copyright (c) 2012, Luca Barbato
*
2012-11-14 11:22:40 +01:00
* This file is part of FFmpeg.
2012-10-26 16:36:56 +02:00
*
2012-11-14 11:22:40 +01:00
* FFmpeg is free software; you can redistribute it and/or
2012-10-26 16:36:56 +02:00
* 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.
*
2012-11-14 11:22:40 +01:00
* FFmpeg is distributed in the hope that it will be useful,
2012-10-26 16:36:56 +02:00
* 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
2012-11-14 11:22:40 +01:00
* License along with FFmpeg; if not, write to the Free Software
2012-10-26 16:36:56 +02:00
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
2014-12-08 10:25:05 -06:00
# include "config.h"
2012-10-26 16:36:56 +02:00
# include <float.h>
2013-11-23 21:32:55 +01:00
# include <stdint.h>
2014-12-08 10:25:05 -06:00
# if HAVE_UNISTD_H
# include <unistd.h>
# endif
2012-10-26 16:36:56 +02:00
2017-04-14 23:30:29 +08:00
# if CONFIG_GCRYPT
# include <gcrypt.h>
# elif CONFIG_OPENSSL
# include <openssl/rand.h>
# endif
2014-09-28 22:27:33 +02:00
# include "libavutil/avassert.h"
2012-10-26 16:36:56 +02:00
# include "libavutil/mathematics.h"
# include "libavutil/parseutils.h"
# include "libavutil/avstring.h"
2017-04-14 23:30:29 +08:00
# include "libavutil/intreadwrite.h"
# include "libavutil/random_seed.h"
2012-10-26 16:36:56 +02:00
# include "libavutil/opt.h"
# include "libavutil/log.h"
2015-09-01 04:10:15 +08:00
# include "libavutil/time_internal.h"
2012-10-26 16:36:56 +02:00
# include "avformat.h"
2016-01-30 02:17:51 +01:00
# include "avio_internal.h"
2012-10-26 16:36:56 +02:00
# include "internal.h"
2014-12-08 10:25:05 -06:00
# include "os_support.h"
2012-10-26 16:36:56 +02:00
2017-01-12 20:43:10 +08:00
typedef enum {
HLS_START_SEQUENCE_AS_START_NUMBER = 0 ,
HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH = 1 ,
HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2 , // YYYYMMDDhhmmss
} StartSequenceSourceType ;
2015-06-15 10:58:07 -08:00
# define KEYSIZE 16
# define LINE_BUFFER_SIZE 1024
2017-01-24 12:29:01 +08:00
# define HLS_MICROSECOND_UNIT 1000000
2015-06-15 10:58:07 -08:00
2014-07-18 10:57:42 +02:00
typedef struct HLSSegment {
char filename [ 1024 ] ;
2015-06-04 13:25:52 +05:30
char sub_filename [ 1024 ] ;
2014-07-18 10:57:42 +02:00
double duration ; /* in seconds */
2016-12-05 17:55:33 +08:00
int discont ;
2014-09-15 16:26:57 +03:00
int64_t pos ;
int64_t size ;
2014-07-18 10:57:42 +02:00
2015-06-15 10:58:07 -08:00
char key_uri [ LINE_BUFFER_SIZE + 1 ] ;
char iv_string [ KEYSIZE * 2 + 1 ] ;
2014-07-18 10:57:42 +02:00
struct HLSSegment * next ;
} HLSSegment ;
2012-10-26 16:36:56 +02:00
2014-09-15 16:26:57 +03:00
typedef enum HLSFlags {
// Generate a single media file and use byte ranges in the playlist.
HLS_SINGLE_FILE = ( 1 < < 0 ) ,
2014-12-08 10:25:05 -06:00
HLS_DELETE_SEGMENTS = ( 1 < < 1 ) ,
2015-04-21 15:46:17 -06:00
HLS_ROUND_DURATIONS = ( 1 < < 2 ) ,
2015-04-21 15:52:03 -06:00
HLS_DISCONT_START = ( 1 < < 3 ) ,
2015-04-21 15:53:57 -06:00
HLS_OMIT_ENDLIST = ( 1 < < 4 ) ,
2016-07-07 06:51:20 +08:00
HLS_SPLIT_BY_TIME = ( 1 < < 5 ) ,
2016-08-21 12:55:45 +08:00
HLS_APPEND_LIST = ( 1 < < 6 ) ,
2016-09-07 21:34:44 +02:00
HLS_PROGRAM_DATE_TIME = ( 1 < < 7 ) ,
2016-12-27 10:40:35 +08:00
HLS_SECOND_LEVEL_SEGMENT_INDEX = ( 1 < < 8 ) , // include segment index in segment filenames when use_localtime e.g.: %%03d
2017-01-03 22:57:51 +08:00
HLS_SECOND_LEVEL_SEGMENT_DURATION = ( 1 < < 9 ) , // include segment duration (microsec) in segment filenames when use_localtime e.g.: %%09t
HLS_SECOND_LEVEL_SEGMENT_SIZE = ( 1 < < 10 ) , // include segment size (bytes) in segment filenames when use_localtime e.g.: %%014s
2017-02-07 11:58:43 +08:00
HLS_TEMP_FILE = ( 1 < < 11 ) ,
2017-08-06 15:10:35 +08:00
HLS_PERIODIC_REKEY = ( 1 < < 12 ) ,
2014-09-15 16:26:57 +03:00
} HLSFlags ;
2017-07-03 20:20:44 +08:00
typedef enum {
SEGMENT_TYPE_MPEGTS ,
SEGMENT_TYPE_FMP4 ,
} SegmentType ;
2015-12-17 03:16:45 +00:00
typedef enum {
PLAYLIST_TYPE_NONE ,
PLAYLIST_TYPE_EVENT ,
PLAYLIST_TYPE_VOD ,
PLAYLIST_TYPE_NB ,
} PlaylistType ;
2012-10-26 16:36:56 +02:00
typedef struct HLSContext {
const AVClass * class ; // Class for private options.
2012-12-29 12:31:01 +01:00
unsigned number ;
2012-12-29 12:09:17 +01:00
int64_t sequence ;
2014-04-29 10:13:34 +02:00
int64_t start_sequence ;
2017-01-12 20:43:10 +08:00
uint32_t start_sequence_source_type ; // enum StartSequenceSourceType
2012-10-26 16:36:56 +02:00
AVOutputFormat * oformat ;
2015-06-04 13:25:52 +05:30
AVOutputFormat * vtt_oformat ;
2014-07-18 10:57:42 +02:00
2012-10-26 16:36:56 +02:00
AVFormatContext * avf ;
2015-06-04 13:25:52 +05:30
AVFormatContext * vtt_avf ;
2014-07-18 10:57:42 +02:00
2012-10-26 16:36:56 +02:00
float time ; // Set by a private option.
2016-08-27 19:11:14 +08:00
float init_time ; // Set by a private option.
2014-07-18 10:57:42 +02:00
int max_nb_segments ; // Set by a private option.
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
2012-10-26 16:36:56 +02:00
int wrap ; // Set by a private option.
2017-02-11 12:32:31 +08:00
# endif
2014-09-15 16:26:57 +03:00
uint32_t flags ; // enum HLSFlags
2015-12-17 03:16:45 +00:00
uint32_t pl_type ; // enum PlaylistType
2014-12-05 21:20:15 +00:00
char * segment_filename ;
2017-07-03 20:20:44 +08:00
char * fmp4_init_filename ;
int segment_type ;
int fmp4_init_mode ;
2014-07-18 10:57:42 +02:00
2015-09-01 04:10:15 +08:00
int use_localtime ; ///< flag to expand filename with localtime
2016-02-03 22:20:07 +01:00
int use_localtime_mkdir ; ///< flag to mkdir dirname in timebased filename
2014-09-22 10:17:01 +02:00
int allowcache ;
2012-10-26 16:36:56 +02:00
int64_t recording_time ;
int has_video ;
2015-06-04 13:25:52 +05:30
int has_subtitle ;
2017-01-11 04:33:13 +08:00
int new_start ;
double dpp ; // duration per packet
2012-10-26 16:36:56 +02:00
int64_t start_pts ;
int64_t end_pts ;
2014-04-03 00:47:13 +05:30
double duration ; // last segment duration computed so far, in seconds
2014-09-15 16:26:57 +03:00
int64_t start_pos ; // last segment starting position
int64_t size ; // last segment size
2016-09-19 07:00:42 +08:00
int64_t max_seg_size ; // every segment file max size
2012-12-25 10:05:42 +01:00
int nb_entries ;
2015-04-21 15:52:03 -06:00
int discontinuity_set ;
2016-12-05 17:55:33 +08:00
int discontinuity ;
2014-07-18 10:57:42 +02:00
2014-07-18 10:57:42 +02:00
HLSSegment * segments ;
HLSSegment * last_segment ;
2014-12-08 10:25:05 -06:00
HLSSegment * old_segments ;
2014-07-18 10:57:42 +02:00
2012-10-26 16:36:56 +02:00
char * basename ;
2017-07-24 19:55:19 +08:00
char * base_output_dirname ;
2015-06-04 13:25:52 +05:30
char * vtt_basename ;
char * vtt_m3u8_name ;
2014-03-18 21:19:00 +01:00
char * baseurl ;
2014-09-12 18:04:52 +08:00
char * format_options_str ;
2015-06-04 13:25:52 +05:30
char * vtt_format_options_str ;
char * subtitle_filename ;
2014-09-12 18:04:52 +08:00
AVDictionary * format_options ;
2015-06-15 10:58:07 -08:00
2017-04-14 23:30:29 +08:00
int encrypt ;
char * key ;
char * key_url ;
char * iv ;
char * key_basename ;
2015-06-15 10:58:07 -08:00
char * key_info_file ;
char key_file [ LINE_BUFFER_SIZE + 1 ] ;
char key_uri [ LINE_BUFFER_SIZE + 1 ] ;
char key_string [ KEYSIZE * 2 + 1 ] ;
char iv_string [ KEYSIZE * 2 + 1 ] ;
2015-06-04 13:25:52 +05:30
AVDictionary * vtt_format_options ;
2015-09-14 21:07:37 +02:00
char * method ;
2016-09-07 21:34:44 +02:00
double initial_prog_date_time ;
2017-01-03 22:57:51 +08:00
char current_segment_final_filename_fmt [ 1024 ] ; // when renaming segments
2012-10-26 16:36:56 +02:00
} HLSContext ;
2017-01-11 04:38:34 +08:00
static int get_int_from_double ( double val )
{
return ( int ) ( ( val - ( int ) val ) > = 0.001 ) ? ( int ) ( val + 1 ) : ( int ) val ;
}
2016-09-28 16:09:05 +08:00
static int mkdir_p ( const char * path ) {
int ret = 0 ;
char * temp = av_strdup ( path ) ;
char * pos = temp ;
char tmp_ch = ' \0 ' ;
if ( ! path | | ! temp ) {
return - 1 ;
}
if ( ! strncmp ( temp , " / " , 1 ) | | ! strncmp ( temp , " \\ " , 1 ) ) {
pos + + ;
} else if ( ! strncmp ( temp , " ./ " , 2 ) | | ! strncmp ( temp , " . \\ " , 2 ) ) {
pos + = 2 ;
}
for ( ; * pos ! = ' \0 ' ; + + pos ) {
if ( * pos = = ' / ' | | * pos = = ' \\ ' ) {
tmp_ch = * pos ;
* pos = ' \0 ' ;
ret = mkdir ( temp , 0755 ) ;
* pos = tmp_ch ;
}
}
if ( ( * ( pos - 1 ) ! = ' / ' ) | | ( * ( pos - 1 ) ! = ' \\ ' ) ) {
ret = mkdir ( temp , 0755 ) ;
}
av_free ( temp ) ;
return ret ;
}
2017-01-03 22:57:51 +08:00
static int replace_int_data_in_filename ( char * buf , int buf_size , const char * filename , char placeholder , int64_t number )
{
const char * p ;
char * q , buf1 [ 20 ] , c ;
int nd , len , addchar_count ;
int found_count = 0 ;
q = buf ;
p = filename ;
for ( ; ; ) {
c = * p ;
if ( c = = ' \0 ' )
break ;
if ( c = = ' % ' & & * ( p + 1 ) = = ' % ' ) // %%
addchar_count = 2 ;
else if ( c = = ' % ' & & ( av_isdigit ( * ( p + 1 ) ) | | * ( p + 1 ) = = placeholder ) ) {
nd = 0 ;
addchar_count = 1 ;
while ( av_isdigit ( * ( p + addchar_count ) ) ) {
nd = nd * 10 + * ( p + addchar_count ) - ' 0 ' ;
addchar_count + + ;
}
if ( * ( p + addchar_count ) = = placeholder ) {
len = snprintf ( buf1 , sizeof ( buf1 ) , " %0* " PRId64 , ( number < 0 ) ? nd : nd + + , number ) ;
if ( len < 1 ) // returned error or empty buf1
goto fail ;
if ( ( q - buf + len ) > buf_size - 1 )
goto fail ;
memcpy ( q , buf1 , len ) ;
q + = len ;
p + = ( addchar_count + 1 ) ;
addchar_count = 0 ;
found_count + + ;
}
} else
addchar_count = 1 ;
while ( addchar_count - - )
if ( ( q - buf ) < buf_size - 1 )
* q + + = * p + + ;
else
goto fail ;
}
* q = ' \0 ' ;
return found_count ;
fail :
* q = ' \0 ' ;
return - 1 ;
}
2017-07-03 20:20:44 +08:00
static void write_styp ( AVIOContext * pb )
{
avio_wb32 ( pb , 24 ) ;
ffio_wfourcc ( pb , " styp " ) ;
ffio_wfourcc ( pb , " msdh " ) ;
avio_wb32 ( pb , 0 ) ; /* minor */
ffio_wfourcc ( pb , " msdh " ) ;
ffio_wfourcc ( pb , " msix " ) ;
}
2017-02-18 09:42:51 +08:00
static int hls_delete_old_segments ( AVFormatContext * s , HLSContext * hls ) {
2014-12-08 10:25:05 -06:00
HLSSegment * segment , * previous_segment = NULL ;
float playlist_duration = 0.0f ;
2015-06-04 13:25:52 +05:30
int ret = 0 , path_size , sub_path_size ;
2015-09-03 11:18:10 +02:00
char * dirname = NULL , * p , * sub_path ;
char * path = NULL ;
2017-01-19 07:06:50 +08:00
AVDictionary * options = NULL ;
AVIOContext * out = NULL ;
2017-02-18 09:42:51 +08:00
const char * proto = NULL ;
2014-12-08 10:25:05 -06:00
segment = hls - > segments ;
while ( segment ) {
playlist_duration + = segment - > duration ;
segment = segment - > next ;
}
segment = hls - > old_segments ;
while ( segment ) {
playlist_duration - = segment - > duration ;
previous_segment = segment ;
segment = previous_segment - > next ;
if ( playlist_duration < = - previous_segment - > duration ) {
previous_segment - > next = NULL ;
break ;
}
}
2016-12-29 12:00:20 +08:00
if ( segment & & ! hls - > use_localtime_mkdir ) {
2014-12-08 10:25:05 -06:00
if ( hls - > segment_filename ) {
dirname = av_strdup ( hls - > segment_filename ) ;
} else {
dirname = av_strdup ( hls - > avf - > filename ) ;
}
if ( ! dirname ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
p = ( char * ) av_basename ( dirname ) ;
* p = ' \0 ' ;
}
while ( segment ) {
av_log ( hls , AV_LOG_DEBUG , " deleting old segment %s \n " ,
segment - > filename ) ;
2016-12-29 12:00:20 +08:00
path_size = ( hls - > use_localtime_mkdir ? 0 : strlen ( dirname ) ) + strlen ( segment - > filename ) + 1 ;
2014-12-08 10:25:05 -06:00
path = av_malloc ( path_size ) ;
if ( ! path ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2015-06-04 13:25:52 +05:30
2016-12-29 12:00:20 +08:00
if ( hls - > use_localtime_mkdir )
av_strlcpy ( path , segment - > filename , path_size ) ;
else { // segment->filename contains basename only
av_strlcpy ( path , dirname , path_size ) ;
av_strlcat ( path , segment - > filename , path_size ) ;
}
2017-02-18 09:42:51 +08:00
proto = avio_find_protocol_name ( s - > filename ) ;
if ( hls - > method | | ( proto & & ! av_strcasecmp ( proto , " http " ) ) ) {
2017-01-19 07:06:50 +08:00
av_dict_set ( & options , " method " , " DELETE " , 0 ) ;
if ( ( ret = hls - > avf - > io_open ( hls - > avf , & out , path , AVIO_FLAG_WRITE , & options ) ) < 0 )
goto fail ;
2017-01-20 12:12:02 +08:00
ff_format_io_close ( hls - > avf , & out ) ;
2017-01-19 07:06:50 +08:00
} else if ( unlink ( path ) < 0 ) {
2014-12-08 10:25:05 -06:00
av_log ( hls , AV_LOG_ERROR , " failed to delete old segment %s: %s \n " ,
path , strerror ( errno ) ) ;
}
2015-06-04 13:25:52 +05:30
2017-01-06 06:29:12 +08:00
if ( ( segment - > sub_filename [ 0 ] ! = ' \0 ' ) ) {
sub_path_size = strlen ( segment - > sub_filename ) + 1 + ( dirname ? strlen ( dirname ) : 0 ) ;
2015-10-01 19:21:33 -04:00
sub_path = av_malloc ( sub_path_size ) ;
if ( ! sub_path ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
av_strlcpy ( sub_path , dirname , sub_path_size ) ;
av_strlcat ( sub_path , segment - > sub_filename , sub_path_size ) ;
2017-01-19 07:06:50 +08:00
2017-02-18 09:42:51 +08:00
if ( hls - > method | | ( proto & & ! av_strcasecmp ( proto , " http " ) ) ) {
2017-01-19 07:06:50 +08:00
av_dict_set ( & options , " method " , " DELETE " , 0 ) ;
if ( ( ret = hls - > avf - > io_open ( hls - > avf , & out , sub_path , AVIO_FLAG_WRITE , & options ) ) < 0 ) {
av_free ( sub_path ) ;
goto fail ;
}
2017-01-20 12:12:02 +08:00
ff_format_io_close ( hls - > avf , & out ) ;
2017-01-19 07:06:50 +08:00
} else if ( unlink ( sub_path ) < 0 ) {
2015-10-01 19:21:33 -04:00
av_log ( hls , AV_LOG_ERROR , " failed to delete old segment %s: %s \n " ,
sub_path , strerror ( errno ) ) ;
}
av_free ( sub_path ) ;
2015-06-04 13:25:52 +05:30
}
2015-09-03 11:18:10 +02:00
av_freep ( & path ) ;
2014-12-08 10:25:05 -06:00
previous_segment = segment ;
segment = previous_segment - > next ;
av_free ( previous_segment ) ;
}
fail :
2015-09-03 11:18:10 +02:00
av_free ( path ) ;
2014-12-08 10:25:05 -06:00
av_free ( dirname ) ;
return ret ;
}
2017-04-14 23:30:29 +08:00
static int randomize ( uint8_t * buf , int len )
{
# if CONFIG_GCRYPT
gcry_randomize ( buf , len , GCRY_VERY_STRONG_RANDOM ) ;
return 0 ;
# elif CONFIG_OPENSSL
if ( RAND_bytes ( buf , len ) )
return 0 ;
# else
return AVERROR ( ENOSYS ) ;
# endif
return AVERROR ( EINVAL ) ;
}
static int do_encrypt ( AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
int ret ;
int len ;
AVIOContext * pb ;
uint8_t key [ KEYSIZE ] ;
len = strlen ( hls - > basename ) + 4 + 1 ;
hls - > key_basename = av_mallocz ( len ) ;
if ( ! hls - > key_basename )
return AVERROR ( ENOMEM ) ;
av_strlcpy ( hls - > key_basename , s - > filename , len ) ;
av_strlcat ( hls - > key_basename , " .key " , len ) ;
if ( hls - > key_url ) {
2017-04-28 10:18:44 +08:00
av_strlcpy ( hls - > key_file , hls - > key_url , sizeof ( hls - > key_file ) ) ;
av_strlcpy ( hls - > key_uri , hls - > key_url , sizeof ( hls - > key_uri ) ) ;
2017-04-14 23:30:29 +08:00
} else {
2017-04-28 10:18:44 +08:00
av_strlcpy ( hls - > key_file , hls - > key_basename , sizeof ( hls - > key_file ) ) ;
av_strlcpy ( hls - > key_uri , hls - > key_basename , sizeof ( hls - > key_uri ) ) ;
2017-04-14 23:30:29 +08:00
}
if ( ! * hls - > iv_string ) {
uint8_t iv [ 16 ] = { 0 } ;
char buf [ 33 ] ;
if ( ! hls - > iv ) {
AV_WB64 ( iv + 8 , hls - > sequence ) ;
} else {
memcpy ( iv , hls - > iv , sizeof ( iv ) ) ;
}
ff_data_to_hex ( buf , iv , sizeof ( iv ) , 0 ) ;
buf [ 32 ] = ' \0 ' ;
memcpy ( hls - > iv_string , buf , sizeof ( hls - > iv_string ) ) ;
}
if ( ! * hls - > key_uri ) {
av_log ( hls , AV_LOG_ERROR , " no key URI specified in key info file \n " ) ;
return AVERROR ( EINVAL ) ;
}
if ( ! * hls - > key_file ) {
av_log ( hls , AV_LOG_ERROR , " no key file specified in key info file \n " ) ;
return AVERROR ( EINVAL ) ;
}
if ( ! * hls - > key_string ) {
if ( ! hls - > key ) {
if ( ( ret = randomize ( key , sizeof ( key ) ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Cannot generate a strong random key \n " ) ;
return ret ;
}
} else {
memcpy ( key , hls - > key , sizeof ( key ) ) ;
}
ff_data_to_hex ( hls - > key_string , key , sizeof ( key ) , 0 ) ;
if ( ( ret = s - > io_open ( s , & pb , hls - > key_file , AVIO_FLAG_WRITE , NULL ) ) < 0 )
return ret ;
avio_seek ( pb , 0 , SEEK_CUR ) ;
avio_write ( pb , key , KEYSIZE ) ;
avio_close ( pb ) ;
}
return 0 ;
}
2015-06-15 10:58:07 -08:00
static int hls_encryption_start ( AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
int ret ;
AVIOContext * pb ;
uint8_t key [ KEYSIZE ] ;
2016-02-10 14:40:32 +00:00
if ( ( ret = s - > io_open ( s , & pb , hls - > key_info_file , AVIO_FLAG_READ , NULL ) ) < 0 ) {
2015-06-15 10:58:07 -08:00
av_log ( hls , AV_LOG_ERROR ,
" error opening key info file %s \n " , hls - > key_info_file ) ;
return ret ;
}
ff_get_line ( pb , hls - > key_uri , sizeof ( hls - > key_uri ) ) ;
hls - > key_uri [ strcspn ( hls - > key_uri , " \r \n " ) ] = ' \0 ' ;
ff_get_line ( pb , hls - > key_file , sizeof ( hls - > key_file ) ) ;
hls - > key_file [ strcspn ( hls - > key_file , " \r \n " ) ] = ' \0 ' ;
ff_get_line ( pb , hls - > iv_string , sizeof ( hls - > iv_string ) ) ;
hls - > iv_string [ strcspn ( hls - > iv_string , " \r \n " ) ] = ' \0 ' ;
2016-02-10 14:40:32 +00:00
ff_format_io_close ( s , & pb ) ;
2015-06-15 10:58:07 -08:00
if ( ! * hls - > key_uri ) {
av_log ( hls , AV_LOG_ERROR , " no key URI specified in key info file \n " ) ;
return AVERROR ( EINVAL ) ;
}
if ( ! * hls - > key_file ) {
av_log ( hls , AV_LOG_ERROR , " no key file specified in key info file \n " ) ;
return AVERROR ( EINVAL ) ;
}
2016-02-10 14:40:32 +00:00
if ( ( ret = s - > io_open ( s , & pb , hls - > key_file , AVIO_FLAG_READ , NULL ) ) < 0 ) {
2015-06-15 10:58:07 -08:00
av_log ( hls , AV_LOG_ERROR , " error opening key file %s \n " , hls - > key_file ) ;
return ret ;
}
ret = avio_read ( pb , key , sizeof ( key ) ) ;
2016-02-10 14:40:32 +00:00
ff_format_io_close ( s , & pb ) ;
2015-06-15 10:58:07 -08:00
if ( ret ! = sizeof ( key ) ) {
av_log ( hls , AV_LOG_ERROR , " error reading key file %s \n " , hls - > key_file ) ;
if ( ret > = 0 | | ret = = AVERROR_EOF )
ret = AVERROR ( EINVAL ) ;
return ret ;
}
ff_data_to_hex ( hls - > key_string , key , sizeof ( key ) , 0 ) ;
return 0 ;
}
2016-08-21 12:55:45 +08:00
static int read_chomp_line ( AVIOContext * s , char * buf , int maxlen )
{
int len = ff_get_line ( s , buf , maxlen ) ;
while ( len > 0 & & av_isspace ( buf [ len - 1 ] ) )
buf [ - - len ] = ' \0 ' ;
return len ;
}
2012-10-26 16:36:56 +02:00
static int hls_mux_init ( AVFormatContext * s )
{
2017-07-03 20:20:44 +08:00
AVDictionary * options = NULL ;
2012-10-26 16:36:56 +02:00
HLSContext * hls = s - > priv_data ;
AVFormatContext * oc ;
2015-09-03 11:14:48 +02:00
AVFormatContext * vtt_oc = NULL ;
2017-08-04 21:38:55 +08:00
int byterange_mode = ( hls - > flags & HLS_SINGLE_FILE ) | | ( hls - > max_seg_size > 0 ) ;
2014-10-17 19:28:47 +03:00
int i , ret ;
2012-10-26 16:36:56 +02:00
2014-10-17 19:28:47 +03:00
ret = avformat_alloc_output_context2 ( & hls - > avf , hls - > oformat , NULL , NULL ) ;
if ( ret < 0 )
return ret ;
oc = hls - > avf ;
2012-10-26 16:36:56 +02:00
2017-02-07 11:58:43 +08:00
oc - > filename [ 0 ] = ' \0 ' ;
2012-10-26 16:36:56 +02:00
oc - > oformat = hls - > oformat ;
oc - > interrupt_callback = s - > interrupt_callback ;
2014-10-17 19:28:47 +03:00
oc - > max_delay = s - > max_delay ;
2016-01-16 17:53:43 +01:00
oc - > opaque = s - > opaque ;
oc - > io_open = s - > io_open ;
oc - > io_close = s - > io_close ;
2013-12-12 18:41:39 +01:00
av_dict_copy ( & oc - > metadata , s - > metadata , 0 ) ;
2012-10-26 16:36:56 +02:00
2015-06-04 13:25:52 +05:30
if ( hls - > vtt_oformat ) {
ret = avformat_alloc_output_context2 ( & hls - > vtt_avf , hls - > vtt_oformat , NULL , NULL ) ;
if ( ret < 0 )
return ret ;
vtt_oc = hls - > vtt_avf ;
vtt_oc - > oformat = hls - > vtt_oformat ;
av_dict_copy ( & vtt_oc - > metadata , s - > metadata , 0 ) ;
}
2012-10-26 16:36:56 +02:00
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
AVStream * st ;
2015-06-04 13:25:52 +05:30
AVFormatContext * loc ;
2016-04-10 20:58:15 +01:00
if ( s - > streams [ i ] - > codecpar - > codec_type = = AVMEDIA_TYPE_SUBTITLE )
2015-06-04 13:25:52 +05:30
loc = vtt_oc ;
else
loc = oc ;
if ( ! ( st = avformat_new_stream ( loc , NULL ) ) )
2012-10-26 16:36:56 +02:00
return AVERROR ( ENOMEM ) ;
2014-06-18 20:42:52 +02:00
avcodec_parameters_copy ( st - > codecpar , s - > streams [ i ] - > codecpar ) ;
2017-07-05 11:20:21 +08:00
if ( ! oc - > oformat - > codec_tag | |
av_codec_get_id ( oc - > oformat - > codec_tag , s - > streams [ i ] - > codecpar - > codec_tag ) = = st - > codecpar - > codec_id | |
av_codec_get_tag ( oc - > oformat - > codec_tag , s - > streams [ i ] - > codecpar - > codec_id ) < = 0 ) {
st - > codecpar - > codec_tag = s - > streams [ i ] - > codecpar - > codec_tag ;
} else {
st - > codecpar - > codec_tag = 0 ;
}
2012-10-26 16:36:56 +02:00
st - > sample_aspect_ratio = s - > streams [ i ] - > sample_aspect_ratio ;
2014-10-06 11:41:33 +03:00
st - > time_base = s - > streams [ i ] - > time_base ;
2017-02-13 07:33:48 +08:00
av_dict_copy ( & st - > metadata , s - > streams [ i ] - > metadata , 0 ) ;
2012-10-26 16:36:56 +02:00
}
2014-09-15 16:26:57 +03:00
hls - > start_pos = 0 ;
2017-01-11 04:33:13 +08:00
hls - > new_start = 1 ;
2017-07-03 20:20:44 +08:00
hls - > fmp4_init_mode = 0 ;
2012-10-26 16:36:56 +02:00
2017-07-03 20:20:44 +08:00
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
2017-08-04 21:38:55 +08:00
if ( hls - > max_seg_size > 0 ) {
av_log ( s , AV_LOG_WARNING , " Multi-file byterange mode is currently unsupported in the HLS muxer. \n " ) ;
return AVERROR_PATCHWELCOME ;
}
hls - > fmp4_init_mode = ! byterange_mode ;
2017-07-24 19:55:19 +08:00
if ( ( ret = s - > io_open ( s , & oc - > pb , hls - > base_output_dirname , AVIO_FLAG_WRITE , NULL ) ) < 0 ) {
2017-07-03 20:20:44 +08:00
av_log ( s , AV_LOG_ERROR , " Failed to open segment '%s' \n " , hls - > fmp4_init_filename ) ;
return ret ;
}
if ( hls - > format_options_str ) {
ret = av_dict_parse_string ( & hls - > format_options , hls - > format_options_str , " = " , " : " , 0 ) ;
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , " Could not parse format options list '%s' \n " ,
hls - > format_options_str ) ;
return ret ;
}
}
av_dict_copy ( & options , hls - > format_options , 0 ) ;
av_dict_set ( & options , " fflags " , " -autobsf " , 0 ) ;
av_dict_set ( & options , " movflags " , " frag_custom+dash+delay_moov " , 0 ) ;
ret = avformat_init_output ( oc , & options ) ;
2017-07-07 10:08:18 +08:00
if ( ret < 0 )
return ret ;
2017-07-03 20:20:44 +08:00
if ( av_dict_count ( options ) ) {
av_log ( s , AV_LOG_ERROR , " Some of the provided format options in '%s' are not recognized \n " , hls - > format_options_str ) ;
av_dict_free ( & options ) ;
return AVERROR ( EINVAL ) ;
}
av_dict_free ( & options ) ;
}
2012-10-26 16:36:56 +02:00
return 0 ;
}
2017-01-01 20:22:34 +08:00
static HLSSegment * find_segment_by_filename ( HLSSegment * segment , const char * filename )
{
while ( segment ) {
if ( ! av_strcasecmp ( segment - > filename , filename ) )
return segment ;
segment = segment - > next ;
}
return ( HLSSegment * ) NULL ;
}
2017-03-13 11:58:34 +08:00
static int sls_flags_filename_process ( struct AVFormatContext * s , HLSContext * hls , HLSSegment * en , double duration ,
int64_t pos , int64_t size )
2012-10-26 16:36:56 +02:00
{
2017-01-03 22:57:51 +08:00
if ( ( hls - > flags & ( HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION ) ) & &
strlen ( hls - > current_segment_final_filename_fmt ) ) {
av_strlcpy ( hls - > avf - > filename , hls - > current_segment_final_filename_fmt , sizeof ( hls - > avf - > filename ) ) ;
if ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_SIZE ) {
char * filename = av_strdup ( hls - > avf - > filename ) ; // %%s will be %s after strftime
2017-01-05 17:08:09 +08:00
if ( ! filename ) {
av_free ( en ) ;
2017-01-03 22:57:51 +08:00
return AVERROR ( ENOMEM ) ;
2017-01-05 17:08:09 +08:00
}
2017-01-03 22:57:51 +08:00
if ( replace_int_data_in_filename ( hls - > avf - > filename , sizeof ( hls - > avf - > filename ) ,
filename , ' s ' , pos + size ) < 1 ) {
av_log ( hls , AV_LOG_ERROR ,
" Invalid second level segment filename template '%s', "
" you can try to remove second_level_segment_size flag \n " ,
filename ) ;
av_free ( filename ) ;
2017-01-05 17:08:09 +08:00
av_free ( en ) ;
2017-01-03 22:57:51 +08:00
return AVERROR ( EINVAL ) ;
}
av_free ( filename ) ;
}
if ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_DURATION ) {
char * filename = av_strdup ( hls - > avf - > filename ) ; // %%t will be %t after strftime
2017-01-05 17:08:09 +08:00
if ( ! filename ) {
av_free ( en ) ;
2017-01-03 22:57:51 +08:00
return AVERROR ( ENOMEM ) ;
2017-01-05 17:08:09 +08:00
}
2017-01-03 22:57:51 +08:00
if ( replace_int_data_in_filename ( hls - > avf - > filename , sizeof ( hls - > avf - > filename ) ,
2017-01-24 12:29:01 +08:00
filename , ' t ' , ( int64_t ) round ( duration * HLS_MICROSECOND_UNIT ) ) < 1 ) {
2017-01-03 22:57:51 +08:00
av_log ( hls , AV_LOG_ERROR ,
" Invalid second level segment filename template '%s', "
" you can try to remove second_level_segment_time flag \n " ,
filename ) ;
av_free ( filename ) ;
2017-01-05 17:08:09 +08:00
av_free ( en ) ;
2017-01-03 22:57:51 +08:00
return AVERROR ( EINVAL ) ;
}
av_free ( filename ) ;
}
}
2017-03-13 11:58:34 +08:00
return 0 ;
}
static int sls_flag_check_duration_size_index ( HLSContext * hls )
{
int ret = 0 ;
if ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_DURATION ) {
av_log ( hls , AV_LOG_ERROR ,
" second_level_segment_duration hls_flag requires use_localtime to be true \n " ) ;
ret = AVERROR ( EINVAL ) ;
}
if ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_SIZE ) {
av_log ( hls , AV_LOG_ERROR ,
" second_level_segment_size hls_flag requires use_localtime to be true \n " ) ;
ret = AVERROR ( EINVAL ) ;
}
if ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_INDEX ) {
av_log ( hls , AV_LOG_ERROR ,
" second_level_segment_index hls_flag requires use_localtime to be true \n " ) ;
ret = AVERROR ( EINVAL ) ;
}
return ret ;
}
static int sls_flag_check_duration_size ( HLSContext * hls )
{
const char * proto = avio_find_protocol_name ( hls - > basename ) ;
int segment_renaming_ok = proto & & ! strcmp ( proto , " file " ) ;
int ret = 0 ;
if ( ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_DURATION ) & & ! segment_renaming_ok ) {
av_log ( hls , AV_LOG_ERROR ,
" second_level_segment_duration hls_flag works only with file protocol segment names \n " ) ;
ret = AVERROR ( EINVAL ) ;
}
if ( ( hls - > flags & HLS_SECOND_LEVEL_SEGMENT_SIZE ) & & ! segment_renaming_ok ) {
av_log ( hls , AV_LOG_ERROR ,
" second_level_segment_size hls_flag works only with file protocol segment names \n " ) ;
ret = AVERROR ( EINVAL ) ;
}
return ret ;
}
static void sls_flag_file_rename ( HLSContext * hls , char * old_filename ) {
if ( ( hls - > flags & ( HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION ) ) & &
strlen ( hls - > current_segment_final_filename_fmt ) ) {
ff_rename ( old_filename , hls - > avf - > filename , hls ) ;
}
}
static int sls_flag_use_localtime_filename ( AVFormatContext * oc , HLSContext * c )
{
if ( c - > flags & HLS_SECOND_LEVEL_SEGMENT_INDEX ) {
char * filename = av_strdup ( oc - > filename ) ; // %%d will be %d after strftime
if ( ! filename )
return AVERROR ( ENOMEM ) ;
if ( replace_int_data_in_filename ( oc - > filename , sizeof ( oc - > filename ) ,
# if FF_API_HLS_WRAP
filename , ' d ' , c - > wrap ? c - > sequence % c - > wrap : c - > sequence ) < 1 ) {
# else
filename , ' d ' , c - > sequence ) < 1 ) {
# endif
av_log ( c , AV_LOG_ERROR , " Invalid second level segment filename template '%s', "
" you can try to remove second_level_segment_index flag \n " ,
filename ) ;
av_free ( filename ) ;
return AVERROR ( EINVAL ) ;
}
av_free ( filename ) ;
}
if ( c - > flags & ( HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION ) ) {
av_strlcpy ( c - > current_segment_final_filename_fmt , oc - > filename ,
sizeof ( c - > current_segment_final_filename_fmt ) ) ;
if ( c - > flags & HLS_SECOND_LEVEL_SEGMENT_SIZE ) {
char * filename = av_strdup ( oc - > filename ) ; // %%s will be %s after strftime
if ( ! filename )
return AVERROR ( ENOMEM ) ;
if ( replace_int_data_in_filename ( oc - > filename , sizeof ( oc - > filename ) , filename , ' s ' , 0 ) < 1 ) {
av_log ( c , AV_LOG_ERROR , " Invalid second level segment filename template '%s', "
" you can try to remove second_level_segment_size flag \n " ,
filename ) ;
av_free ( filename ) ;
return AVERROR ( EINVAL ) ;
}
av_free ( filename ) ;
}
if ( c - > flags & HLS_SECOND_LEVEL_SEGMENT_DURATION ) {
char * filename = av_strdup ( oc - > filename ) ; // %%t will be %t after strftime
if ( ! filename )
return AVERROR ( ENOMEM ) ;
if ( replace_int_data_in_filename ( oc - > filename , sizeof ( oc - > filename ) , filename , ' t ' , 0 ) < 1 ) {
av_log ( c , AV_LOG_ERROR , " Invalid second level segment filename template '%s', "
" you can try to remove second_level_segment_time flag \n " ,
filename ) ;
av_free ( filename ) ;
return AVERROR ( EINVAL ) ;
}
av_free ( filename ) ;
}
}
return 0 ;
}
/* Create a new segment and append it to the segment list */
static int hls_append_segment ( struct AVFormatContext * s , HLSContext * hls , double duration ,
int64_t pos , int64_t size )
{
HLSSegment * en = av_malloc ( sizeof ( * en ) ) ;
const char * filename ;
2017-06-19 10:35:41 +08:00
int byterange_mode = ( hls - > flags & HLS_SINGLE_FILE ) | | ( hls - > max_seg_size > 0 ) ;
2017-03-13 11:58:34 +08:00
int ret ;
2017-01-03 22:57:51 +08:00
2017-03-13 11:58:34 +08:00
if ( ! en )
return AVERROR ( ENOMEM ) ;
ret = sls_flags_filename_process ( s , hls , en , duration , pos , size ) ;
if ( ret < 0 ) {
return ret ;
}
2017-01-03 22:57:51 +08:00
2016-02-03 22:20:07 +01:00
filename = av_basename ( hls - > avf - > filename ) ;
if ( hls - > use_localtime_mkdir ) {
2016-09-26 15:51:02 +08:00
filename = hls - > avf - > filename ;
2016-02-03 22:20:07 +01:00
}
2017-06-19 10:35:41 +08:00
if ( ( find_segment_by_filename ( hls - > segments , filename ) | | find_segment_by_filename ( hls - > old_segments , filename ) )
& & ! byterange_mode ) {
2017-01-01 20:22:34 +08:00
av_log ( hls , AV_LOG_WARNING , " Duplicated segment filename detected: %s \n " , filename ) ;
}
2016-02-03 22:20:07 +01:00
av_strlcpy ( en - > filename , filename , sizeof ( en - > filename ) ) ;
2012-10-26 16:36:56 +02:00
2015-06-04 13:25:52 +05:30
if ( hls - > has_subtitle )
av_strlcpy ( en - > sub_filename , av_basename ( hls - > vtt_avf - > filename ) , sizeof ( en - > sub_filename ) ) ;
2015-10-01 19:21:33 -04:00
else
en - > sub_filename [ 0 ] = ' \0 ' ;
2015-06-04 13:25:52 +05:30
2012-10-26 16:36:56 +02:00
en - > duration = duration ;
2014-09-15 16:26:57 +03:00
en - > pos = pos ;
en - > size = size ;
2012-10-26 16:36:56 +02:00
en - > next = NULL ;
2016-12-05 17:55:33 +08:00
en - > discont = 0 ;
if ( hls - > discontinuity ) {
en - > discont = 1 ;
hls - > discontinuity = 0 ;
}
2012-10-26 16:36:56 +02:00
2017-04-14 23:30:29 +08:00
if ( hls - > key_info_file | | hls - > encrypt ) {
2015-06-15 10:58:07 -08:00
av_strlcpy ( en - > key_uri , hls - > key_uri , sizeof ( en - > key_uri ) ) ;
av_strlcpy ( en - > iv_string , hls - > iv_string , sizeof ( en - > iv_string ) ) ;
}
2014-07-18 10:57:42 +02:00
if ( ! hls - > segments )
hls - > segments = en ;
2012-10-26 16:36:56 +02:00
else
2014-07-18 10:57:42 +02:00
hls - > last_segment - > next = en ;
2012-10-26 16:36:56 +02:00
2014-07-18 10:57:42 +02:00
hls - > last_segment = en ;
2012-10-26 16:36:56 +02:00
2015-12-17 03:16:45 +00:00
// EVENT or VOD playlists imply sliding window cannot be used
if ( hls - > pl_type ! = PLAYLIST_TYPE_NONE )
hls - > max_nb_segments = 0 ;
2014-07-18 10:57:42 +02:00
if ( hls - > max_nb_segments & & hls - > nb_entries > = hls - > max_nb_segments ) {
en = hls - > segments ;
2016-12-27 10:10:44 +08:00
hls - > initial_prog_date_time + = en - > duration ;
2014-07-18 10:57:42 +02:00
hls - > segments = en - > next ;
2014-12-08 10:25:05 -06:00
if ( en & & hls - > flags & HLS_DELETE_SEGMENTS & &
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
2014-12-08 10:25:05 -06:00
! ( hls - > flags & HLS_SINGLE_FILE | | hls - > wrap ) ) {
2017-02-11 12:32:31 +08:00
# else
! ( hls - > flags & HLS_SINGLE_FILE ) ) {
# endif
2014-12-08 10:25:05 -06:00
en - > next = hls - > old_segments ;
hls - > old_segments = en ;
2017-02-18 09:42:51 +08:00
if ( ( ret = hls_delete_old_segments ( s , hls ) ) < 0 )
2014-12-08 10:25:05 -06:00
return ret ;
} else
av_free ( en ) ;
2012-12-25 10:05:42 +01:00
} else
hls - > nb_entries + + ;
2012-10-26 16:36:56 +02:00
2016-09-19 07:00:42 +08:00
if ( hls - > max_seg_size > 0 ) {
return 0 ;
}
2012-12-29 12:09:17 +01:00
hls - > sequence + + ;
2012-10-26 16:36:56 +02:00
return 0 ;
}
2016-08-21 12:55:45 +08:00
static int parse_playlist ( AVFormatContext * s , const char * url )
{
HLSContext * hls = s - > priv_data ;
AVIOContext * in ;
int ret = 0 , is_segment = 0 ;
int64_t new_start_pos ;
char line [ 1024 ] ;
const char * ptr ;
2017-04-28 10:17:40 +08:00
const char * end ;
2016-08-21 12:55:45 +08:00
if ( ( ret = ffio_open_whitelist ( & in , url , AVIO_FLAG_READ ,
& s - > interrupt_callback , NULL ,
s - > protocol_whitelist , s - > protocol_blacklist ) ) < 0 )
return ret ;
read_chomp_line ( in , line , sizeof ( line ) ) ;
if ( strcmp ( line , " #EXTM3U " ) ) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
2016-12-05 17:55:33 +08:00
hls - > discontinuity = 0 ;
2016-08-21 12:55:45 +08:00
while ( ! avio_feof ( in ) ) {
read_chomp_line ( in , line , sizeof ( line ) ) ;
if ( av_strstart ( line , " #EXT-X-MEDIA-SEQUENCE: " , & ptr ) ) {
2017-01-12 20:43:10 +08:00
int64_t tmp_sequence = strtoll ( ptr , NULL , 10 ) ;
if ( tmp_sequence < hls - > sequence )
av_log ( hls , AV_LOG_VERBOSE ,
" Found playlist sequence number was smaller " " "
" than specified start sequence number: % " PRId64 " < % " PRId64 " , "
" omitting \n " , tmp_sequence , hls - > start_sequence ) ;
else {
av_log ( hls , AV_LOG_DEBUG , " Found playlist sequence number: % " PRId64 " \n " , tmp_sequence ) ;
hls - > sequence = tmp_sequence ;
}
2016-12-07 07:43:36 +08:00
} else if ( av_strstart ( line , " #EXT-X-DISCONTINUITY " , & ptr ) ) {
is_segment = 1 ;
hls - > discontinuity = 1 ;
2016-08-21 12:55:45 +08:00
} else if ( av_strstart ( line , " #EXTINF: " , & ptr ) ) {
is_segment = 1 ;
hls - > duration = atof ( ptr ) ;
2017-04-28 10:17:40 +08:00
} else if ( av_stristart ( line , " #EXT-X-KEY: " , & ptr ) ) {
ptr = av_stristr ( line , " URI= \" " ) ;
if ( ptr ) {
ptr + = strlen ( " URI= \" " ) ;
end = av_stristr ( ptr , " , " ) ;
if ( end ) {
av_strlcpy ( hls - > key_uri , ptr , end - ptr ) ;
} else {
av_strlcpy ( hls - > key_uri , ptr , sizeof ( hls - > key_uri ) ) ;
}
}
ptr = av_stristr ( line , " IV=0x " ) ;
if ( ptr ) {
ptr + = strlen ( " IV=0x " ) ;
end = av_stristr ( ptr , " , " ) ;
if ( end ) {
av_strlcpy ( hls - > iv_string , ptr , end - ptr ) ;
} else {
av_strlcpy ( hls - > iv_string , ptr , sizeof ( hls - > iv_string ) ) ;
}
}
2016-08-21 12:55:45 +08:00
} else if ( av_strstart ( line , " # " , NULL ) ) {
continue ;
} else if ( line [ 0 ] ) {
if ( is_segment ) {
is_segment = 0 ;
new_start_pos = avio_tell ( hls - > avf - > pb ) ;
hls - > size = new_start_pos - hls - > start_pos ;
av_strlcpy ( hls - > avf - > filename , line , sizeof ( line ) ) ;
ret = hls_append_segment ( s , hls , hls - > duration , hls - > start_pos , hls - > size ) ;
if ( ret < 0 )
goto fail ;
hls - > start_pos = new_start_pos ;
}
}
}
fail :
avio_close ( in ) ;
return ret ;
}
2014-12-08 10:25:05 -06:00
static void hls_free_segments ( HLSSegment * p )
2012-10-26 16:36:56 +02:00
{
2014-12-08 10:25:05 -06:00
HLSSegment * en ;
2012-10-26 16:36:56 +02:00
while ( p ) {
en = p ;
p = p - > next ;
av_free ( en ) ;
}
}
2017-02-18 09:42:51 +08:00
static void set_http_options ( AVFormatContext * s , AVDictionary * * options , HLSContext * c )
2015-09-14 21:07:37 +02:00
{
2017-02-18 09:42:51 +08:00
const char * proto = avio_find_protocol_name ( s - > filename ) ;
2017-02-21 23:38:39 +08:00
int http_base_proto = proto ? ( ! av_strcasecmp ( proto , " http " ) | | ! av_strcasecmp ( proto , " https " ) ) : 0 ;
2017-02-18 09:42:51 +08:00
if ( c - > method ) {
2015-09-14 21:07:37 +02:00
av_dict_set ( options , " method " , c - > method , 0 ) ;
2017-02-21 23:38:39 +08:00
} else if ( http_base_proto ) {
2017-02-18 09:42:51 +08:00
av_log ( c , AV_LOG_WARNING , " No HTTP method set, hls muxer defaulting to method PUT. \n " ) ;
av_dict_set ( options , " method " , " PUT " , 0 ) ;
}
2015-09-14 21:07:37 +02:00
}
2017-01-24 22:25:29 +08:00
static void write_m3u8_head_block ( HLSContext * hls , AVIOContext * out , int version ,
int target_duration , int64_t sequence )
{
avio_printf ( out , " #EXTM3U \n " ) ;
avio_printf ( out , " #EXT-X-VERSION:%d \n " , version ) ;
if ( hls - > allowcache = = 0 | | hls - > allowcache = = 1 ) {
avio_printf ( out , " #EXT-X-ALLOW-CACHE:%s \n " , hls - > allowcache = = 0 ? " NO " : " YES " ) ;
}
avio_printf ( out , " #EXT-X-TARGETDURATION:%d \n " , target_duration ) ;
avio_printf ( out , " #EXT-X-MEDIA-SEQUENCE:% " PRId64 " \n " , sequence ) ;
av_log ( hls , AV_LOG_VERBOSE , " EXT-X-MEDIA-SEQUENCE:% " PRId64 " \n " , sequence ) ;
}
2017-02-25 10:59:05 +08:00
static void hls_rename_temp_file ( AVFormatContext * s , AVFormatContext * oc )
{
size_t len = strlen ( oc - > filename ) ;
char final_filename [ sizeof ( oc - > filename ) ] ;
av_strlcpy ( final_filename , oc - > filename , len ) ;
final_filename [ len - 4 ] = ' \0 ' ;
ff_rename ( oc - > filename , final_filename , s ) ;
oc - > filename [ len - 4 ] = ' \0 ' ;
}
2012-10-26 16:36:56 +02:00
static int hls_window ( AVFormatContext * s , int last )
{
HLSContext * hls = s - > priv_data ;
2014-07-18 10:57:42 +02:00
HLSSegment * en ;
2012-12-21 12:05:46 +01:00
int target_duration = 0 ;
2012-10-26 16:36:56 +02:00
int ret = 0 ;
2015-02-20 12:55:13 +01:00
AVIOContext * out = NULL ;
2015-06-04 13:25:52 +05:30
AVIOContext * sub_out = NULL ;
2015-02-20 12:55:14 +01:00
char temp_filename [ 1024 ] ;
2014-04-30 21:14:56 +02:00
int64_t sequence = FFMAX ( hls - > start_sequence , hls - > sequence - hls - > nb_entries ) ;
2016-09-19 07:00:42 +08:00
int version = 3 ;
2015-02-21 01:21:53 +01:00
const char * proto = avio_find_protocol_name ( s - > filename ) ;
int use_rename = proto & & ! strcmp ( proto , " file " ) ;
2015-02-21 19:27:06 +01:00
static unsigned warned_non_file ;
2015-06-15 10:58:07 -08:00
char * key_uri = NULL ;
char * iv_string = NULL ;
2015-09-14 21:07:37 +02:00
AVDictionary * options = NULL ;
2016-09-07 21:34:44 +02:00
double prog_date_time = hls - > initial_prog_date_time ;
2016-09-19 07:00:42 +08:00
int byterange_mode = ( hls - > flags & HLS_SINGLE_FILE ) | | ( hls - > max_seg_size > 0 ) ;
if ( byterange_mode ) {
version = 4 ;
sequence = 0 ;
}
2015-02-21 19:27:06 +01:00
2017-07-03 20:20:44 +08:00
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
version = 7 ;
}
2015-02-21 19:27:06 +01:00
if ( ! use_rename & & ! warned_non_file + + )
2016-10-09 12:57:01 +02:00
av_log ( s , AV_LOG_ERROR , " Cannot use rename on non file protocol, this may lead to races and temporary partial files \n " ) ;
2012-10-26 16:36:56 +02:00
2017-02-18 09:42:51 +08:00
set_http_options ( s , & options , hls ) ;
2015-02-21 01:21:53 +01:00
snprintf ( temp_filename , sizeof ( temp_filename ) , use_rename ? " %s.tmp " : " %s " , s - > filename ) ;
2016-03-11 00:29:59 +01:00
if ( ( ret = s - > io_open ( s , & out , temp_filename , AVIO_FLAG_WRITE , & options ) ) < 0 )
2012-10-26 16:36:56 +02:00
goto fail ;
2014-07-18 10:57:42 +02:00
for ( en = hls - > segments ; en ; en = en - > next ) {
2017-01-11 04:38:34 +08:00
if ( target_duration < = en - > duration )
target_duration = get_int_from_double ( en - > duration ) ;
2012-12-21 12:05:46 +01:00
}
2015-04-21 15:52:03 -06:00
hls - > discontinuity_set = 0 ;
2017-01-24 22:25:29 +08:00
write_m3u8_head_block ( hls , out , version , target_duration , sequence ) ;
2015-12-17 03:16:45 +00:00
if ( hls - > pl_type = = PLAYLIST_TYPE_EVENT ) {
avio_printf ( out , " #EXT-X-PLAYLIST-TYPE:EVENT \n " ) ;
} else if ( hls - > pl_type = = PLAYLIST_TYPE_VOD ) {
avio_printf ( out , " #EXT-X-PLAYLIST-TYPE:VOD \n " ) ;
}
2012-10-26 16:36:56 +02:00
2015-04-21 15:52:03 -06:00
if ( ( hls - > flags & HLS_DISCONT_START ) & & sequence = = hls - > start_sequence & & hls - > discontinuity_set = = 0 ) {
avio_printf ( out , " #EXT-X-DISCONTINUITY \n " ) ;
hls - > discontinuity_set = 1 ;
}
2014-07-18 10:57:42 +02:00
for ( en = hls - > segments ; en ; en = en - > next ) {
2017-04-14 23:30:29 +08:00
if ( ( hls - > encrypt | | hls - > key_info_file ) & & ( ! key_uri | | strcmp ( en - > key_uri , key_uri ) | |
2015-06-15 10:58:07 -08:00
av_strcasecmp ( en - > iv_string , iv_string ) ) ) {
avio_printf ( out , " #EXT-X-KEY:METHOD=AES-128,URI= \" %s \" " , en - > key_uri ) ;
if ( * en - > iv_string )
avio_printf ( out , " ,IV=0x%s " , en - > iv_string ) ;
avio_printf ( out , " \n " ) ;
key_uri = en - > key_uri ;
iv_string = en - > iv_string ;
}
2016-12-05 17:55:33 +08:00
if ( en - > discont ) {
avio_printf ( out , " #EXT-X-DISCONTINUITY \n " ) ;
}
2017-08-04 21:38:55 +08:00
if ( ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) & & ( en = = hls - > segments ) ) {
avio_printf ( out , " #EXT-X-MAP:URI= \" %s \" " , hls - > fmp4_init_filename ) ;
if ( hls - > flags & HLS_SINGLE_FILE ) {
avio_printf ( out , " ,BYTERANGE= \" % " PRId64 " @% " PRId64 " \" " , en - > size , en - > pos ) ;
}
avio_printf ( out , " \n " ) ;
} else {
if ( hls - > flags & HLS_ROUND_DURATIONS )
avio_printf ( out , " #EXTINF:%ld, \n " , lrint ( en - > duration ) ) ;
else
avio_printf ( out , " #EXTINF:%f, \n " , en - > duration ) ;
if ( byterange_mode )
avio_printf ( out , " #EXT-X-BYTERANGE:% " PRId64 " @% " PRId64 " \n " ,
en - > size , en - > pos ) ;
}
2016-09-07 21:34:44 +02:00
if ( hls - > flags & HLS_PROGRAM_DATE_TIME ) {
2016-09-15 01:01:51 +02:00
time_t tt , wrongsecs ;
2016-09-07 21:34:44 +02:00
int milli ;
struct tm * tm , tmpbuf ;
char buf0 [ 128 ] , buf1 [ 128 ] ;
tt = ( int64_t ) prog_date_time ;
milli = av_clip ( lrint ( 1000 * ( prog_date_time - tt ) ) , 0 , 999 ) ;
tm = localtime_r ( & tt , & tmpbuf ) ;
2016-09-13 22:55:12 +02:00
strftime ( buf0 , sizeof ( buf0 ) , " %Y-%m-%dT%H:%M:%S " , tm ) ;
2016-09-15 01:01:51 +02:00
if ( ! strftime ( buf1 , sizeof ( buf1 ) , " %z " , tm ) | | buf1 [ 1 ] < ' 0 ' | | buf1 [ 1 ] > ' 2 ' ) {
int tz_min , dst = tm - > tm_isdst ;
tm = gmtime_r ( & tt , & tmpbuf ) ;
tm - > tm_isdst = dst ;
wrongsecs = mktime ( tm ) ;
tz_min = ( abs ( wrongsecs - tt ) + 30 ) / 60 ;
snprintf ( buf1 , sizeof ( buf1 ) ,
" %c%02d%02d " ,
wrongsecs < = tt ? ' + ' : ' - ' ,
tz_min / 60 ,
tz_min % 60 ) ;
}
2016-09-07 21:34:44 +02:00
avio_printf ( out , " #EXT-X-PROGRAM-DATE-TIME:%s.%03d%s \n " , buf0 , milli , buf1 ) ;
prog_date_time + = en - > duration ;
}
2017-08-04 21:38:55 +08:00
if ( ! ( ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) & & ( en = = hls - > segments ) ) ) {
if ( hls - > baseurl )
avio_printf ( out , " %s " , hls - > baseurl ) ;
avio_printf ( out , " %s \n " , en - > filename ) ;
}
2012-10-26 16:36:56 +02:00
}
2015-04-21 15:53:57 -06:00
if ( last & & ( hls - > flags & HLS_OMIT_ENDLIST ) = = 0 )
2015-02-20 12:55:13 +01:00
avio_printf ( out , " #EXT-X-ENDLIST \n " ) ;
2012-10-26 16:36:56 +02:00
2015-06-04 13:25:52 +05:30
if ( hls - > vtt_m3u8_name ) {
2016-02-10 14:40:32 +00:00
if ( ( ret = s - > io_open ( s , & sub_out , hls - > vtt_m3u8_name , AVIO_FLAG_WRITE , & options ) ) < 0 )
2015-06-04 13:25:52 +05:30
goto fail ;
2017-01-24 22:25:29 +08:00
write_m3u8_head_block ( hls , sub_out , version , target_duration , sequence ) ;
2015-06-04 13:25:52 +05:30
for ( en = hls - > segments ; en ; en = en - > next ) {
avio_printf ( sub_out , " #EXTINF:%f, \n " , en - > duration ) ;
2016-09-19 07:00:42 +08:00
if ( byterange_mode )
2015-06-04 13:25:52 +05:30
avio_printf ( sub_out , " #EXT-X-BYTERANGE:% " PRIi64 " @% " PRIi64 " \n " ,
en - > size , en - > pos ) ;
if ( hls - > baseurl )
avio_printf ( sub_out , " %s " , hls - > baseurl ) ;
avio_printf ( sub_out , " %s \n " , en - > sub_filename ) ;
}
if ( last )
avio_printf ( sub_out , " #EXT-X-ENDLIST \n " ) ;
}
2012-10-26 16:36:56 +02:00
fail :
2015-09-14 21:07:37 +02:00
av_dict_free ( & options ) ;
2016-01-16 17:53:43 +01:00
ff_format_io_close ( s , & out ) ;
2016-02-10 14:40:32 +00:00
ff_format_io_close ( s , & sub_out ) ;
2015-02-21 01:21:53 +01:00
if ( ret > = 0 & & use_rename )
2015-02-20 12:55:14 +01:00
ff_rename ( temp_filename , s - > filename , s ) ;
2012-10-26 16:36:56 +02:00
return ret ;
}
static int hls_start ( AVFormatContext * s )
{
HLSContext * c = s - > priv_data ;
AVFormatContext * oc = c - > avf ;
2015-06-04 13:25:52 +05:30
AVFormatContext * vtt_oc = c - > vtt_avf ;
2015-06-15 10:58:07 -08:00
AVDictionary * options = NULL ;
char * filename , iv_string [ KEYSIZE * 2 + 1 ] ;
2012-10-26 16:36:56 +02:00
int err = 0 ;
2015-06-04 13:25:52 +05:30
if ( c - > flags & HLS_SINGLE_FILE ) {
2014-09-15 16:26:57 +03:00
av_strlcpy ( oc - > filename , c - > basename ,
sizeof ( oc - > filename ) ) ;
2015-06-04 13:25:52 +05:30
if ( c - > vtt_basename )
av_strlcpy ( vtt_oc - > filename , c - > vtt_basename ,
sizeof ( vtt_oc - > filename ) ) ;
2016-09-19 07:00:42 +08:00
} else if ( c - > max_seg_size > 0 ) {
2017-01-12 20:43:10 +08:00
if ( replace_int_data_in_filename ( oc - > filename , sizeof ( oc - > filename ) ,
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
2017-01-12 20:43:10 +08:00
c - > basename , ' d ' , c - > wrap ? c - > sequence % c - > wrap : c - > sequence ) < 1 ) {
2017-02-11 12:32:31 +08:00
# else
c - > basename , ' d ' , c - > sequence ) < 1 ) {
# endif
2016-09-19 07:00:42 +08:00
av_log ( oc , AV_LOG_ERROR , " Invalid segment filename template '%s', you can try to use -use_localtime 1 with it \n " , c - > basename ) ;
return AVERROR ( EINVAL ) ;
}
2015-06-04 13:25:52 +05:30
} else {
2015-09-01 04:10:15 +08:00
if ( c - > use_localtime ) {
time_t now0 ;
struct tm * tm , tmpbuf ;
time ( & now0 ) ;
tm = localtime_r ( & now0 , & tmpbuf ) ;
if ( ! strftime ( oc - > filename , sizeof ( oc - > filename ) , c - > basename , tm ) ) {
av_log ( oc , AV_LOG_ERROR , " Could not get segment filename with use_localtime \n " ) ;
return AVERROR ( EINVAL ) ;
}
2017-03-13 11:58:34 +08:00
err = sls_flag_use_localtime_filename ( oc , c ) ;
if ( err < 0 ) {
return AVERROR ( ENOMEM ) ;
2017-01-03 22:57:51 +08:00
}
2017-03-13 11:58:34 +08:00
2016-02-03 22:20:07 +01:00
if ( c - > use_localtime_mkdir ) {
const char * dir ;
char * fn_copy = av_strdup ( oc - > filename ) ;
if ( ! fn_copy ) {
return AVERROR ( ENOMEM ) ;
}
dir = av_dirname ( fn_copy ) ;
2016-09-28 16:09:05 +08:00
if ( mkdir_p ( dir ) = = - 1 & & errno ! = EEXIST ) {
2016-02-03 22:20:07 +01:00
av_log ( oc , AV_LOG_ERROR , " Could not create directory %s with use_localtime_mkdir \n " , dir ) ;
av_free ( fn_copy ) ;
return AVERROR ( errno ) ;
}
av_free ( fn_copy ) ;
}
2017-01-12 20:43:10 +08:00
} else if ( replace_int_data_in_filename ( oc - > filename , sizeof ( oc - > filename ) ,
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
2017-01-12 20:43:10 +08:00
c - > basename , ' d ' , c - > wrap ? c - > sequence % c - > wrap : c - > sequence ) < 1 ) {
2017-02-11 12:32:31 +08:00
# else
c - > basename , ' d ' , c - > sequence ) < 1 ) {
# endif
2016-10-09 12:57:01 +02:00
av_log ( oc , AV_LOG_ERROR , " Invalid segment filename template '%s' you can try to use -use_localtime 1 with it \n " , c - > basename ) ;
2014-09-15 16:26:57 +03:00
return AVERROR ( EINVAL ) ;
}
2015-06-04 13:25:52 +05:30
if ( c - > vtt_basename ) {
2017-01-12 20:43:10 +08:00
if ( replace_int_data_in_filename ( vtt_oc - > filename , sizeof ( vtt_oc - > filename ) ,
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
2017-01-12 20:43:10 +08:00
c - > vtt_basename , ' d ' , c - > wrap ? c - > sequence % c - > wrap : c - > sequence ) < 1 ) {
2017-02-11 12:32:31 +08:00
# else
c - > vtt_basename , ' d ' , c - > sequence ) < 1 ) {
# endif
2015-06-04 13:25:52 +05:30
av_log ( vtt_oc , AV_LOG_ERROR , " Invalid segment filename template '%s' \n " , c - > vtt_basename ) ;
return AVERROR ( EINVAL ) ;
}
}
}
2013-06-06 12:09:38 +02:00
c - > number + + ;
2012-10-26 16:36:56 +02:00
2017-02-18 09:42:51 +08:00
set_http_options ( s , & options , c ) ;
2015-09-14 21:07:37 +02:00
2017-02-07 11:58:43 +08:00
if ( c - > flags & HLS_TEMP_FILE ) {
av_strlcat ( oc - > filename , " .tmp " , sizeof ( oc - > filename ) ) ;
}
2017-04-14 23:30:29 +08:00
if ( c - > key_info_file | | c - > encrypt ) {
if ( c - > key_info_file & & c - > encrypt ) {
av_log ( s , AV_LOG_WARNING , " Cannot use both -hls_key_info_file and -hls_enc, "
" will use -hls_key_info_file priority \n " ) ;
}
2017-07-25 10:31:29 +08:00
2017-08-06 15:10:35 +08:00
if ( c - > number < = 1 | | ( c - > flags & HLS_PERIODIC_REKEY ) ) {
2017-07-25 10:31:29 +08:00
if ( c - > key_info_file ) {
if ( ( err = hls_encryption_start ( s ) ) < 0 )
goto fail ;
} else {
if ( ( err = do_encrypt ( s ) ) < 0 )
goto fail ;
}
2017-04-14 23:30:29 +08:00
}
2015-06-15 10:58:07 -08:00
if ( ( err = av_dict_set ( & options , " encryption_key " , c - > key_string , 0 ) )
< 0 )
2015-09-14 21:07:37 +02:00
goto fail ;
2015-06-15 10:58:07 -08:00
err = av_strlcpy ( iv_string , c - > iv_string , sizeof ( iv_string ) ) ;
if ( ! err )
snprintf ( iv_string , sizeof ( iv_string ) , " %032 " PRIx64 , c - > sequence ) ;
if ( ( err = av_dict_set ( & options , " encryption_iv " , iv_string , 0 ) ) < 0 )
2015-09-14 21:07:37 +02:00
goto fail ;
2015-06-15 10:58:07 -08:00
filename = av_asprintf ( " crypto:%s " , oc - > filename ) ;
if ( ! filename ) {
2015-09-14 21:07:37 +02:00
err = AVERROR ( ENOMEM ) ;
goto fail ;
2015-06-15 10:58:07 -08:00
}
2016-02-10 19:02:48 +01:00
err = s - > io_open ( s , & oc - > pb , filename , AVIO_FLAG_WRITE , & options ) ;
2015-06-15 10:58:07 -08:00
av_free ( filename ) ;
av_dict_free ( & options ) ;
if ( err < 0 )
return err ;
} else
2016-02-10 14:40:32 +00:00
if ( ( err = s - > io_open ( s , & oc - > pb , oc - > filename , AVIO_FLAG_WRITE , & options ) ) < 0 )
2015-09-14 21:07:37 +02:00
goto fail ;
2015-06-04 13:25:52 +05:30
if ( c - > vtt_basename ) {
2017-02-18 09:42:51 +08:00
set_http_options ( s , & options , c ) ;
2016-02-10 14:40:32 +00:00
if ( ( err = s - > io_open ( s , & vtt_oc - > pb , vtt_oc - > filename , AVIO_FLAG_WRITE , & options ) ) < 0 )
2015-09-14 21:07:37 +02:00
goto fail ;
2015-06-04 13:25:52 +05:30
}
2015-09-14 21:07:37 +02:00
av_dict_free ( & options ) ;
2012-10-26 16:36:56 +02:00
2017-08-04 21:38:55 +08:00
if ( c - > segment_type = = SEGMENT_TYPE_FMP4 & & ! ( c - > flags & HLS_SINGLE_FILE ) ) {
2017-07-03 20:20:44 +08:00
write_styp ( oc - > pb ) ;
} else {
/* We only require one PAT/PMT per segment. */
if ( oc - > oformat - > priv_class & & oc - > priv_data ) {
char period [ 21 ] ;
2015-04-05 14:28:38 -04:00
2017-07-03 20:20:44 +08:00
snprintf ( period , sizeof ( period ) , " %d " , ( INT_MAX / 2 ) - 1 ) ;
2015-04-05 14:28:38 -04:00
2017-07-03 20:20:44 +08:00
av_opt_set ( oc - > priv_data , " mpegts_flags " , " resend_headers " , 0 ) ;
av_opt_set ( oc - > priv_data , " sdt_period " , period , 0 ) ;
av_opt_set ( oc - > priv_data , " pat_period " , period , 0 ) ;
}
2015-04-05 14:28:38 -04:00
}
2012-10-26 16:36:56 +02:00
2015-12-13 02:28:13 +01:00
if ( c - > vtt_basename ) {
err = avformat_write_header ( vtt_oc , NULL ) ;
if ( err < 0 )
return err ;
}
2015-06-04 13:25:52 +05:30
2012-10-26 16:36:56 +02:00
return 0 ;
2015-09-14 21:07:37 +02:00
fail :
av_dict_free ( & options ) ;
return err ;
2012-10-26 16:36:56 +02:00
}
2017-07-24 19:55:19 +08:00
static const char * get_default_pattern_localtime_fmt ( AVFormatContext * s )
2017-01-01 11:00:45 +08:00
{
char b [ 21 ] ;
time_t t = time ( NULL ) ;
struct tm * p , tmbuf ;
2017-07-24 19:55:19 +08:00
HLSContext * hls = s - > priv_data ;
2017-01-01 11:00:45 +08:00
p = localtime_r ( & t , & tmbuf ) ;
// no %s support when strftime returned error or left format string unchanged
2017-02-25 11:23:50 +08:00
// also no %s support on MSVC, which invokes the invalid parameter handler on unsupported format strings, instead of returning an error
2017-07-24 19:55:19 +08:00
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
return ( HAVE_LIBC_MSVCRT | | ! strftime ( b , sizeof ( b ) , " %s " , p ) | | ! strcmp ( b , " %s " ) ) ? " -%Y%m%d%H%M%S.m4s " : " -%s.m4s " ;
}
2017-02-25 11:23:50 +08:00
return ( HAVE_LIBC_MSVCRT | | ! strftime ( b , sizeof ( b ) , " %s " , p ) | | ! strcmp ( b , " %s " ) ) ? " -%Y%m%d%H%M%S.ts " : " -%s.ts " ;
2017-01-01 11:00:45 +08:00
}
2012-10-26 16:36:56 +02:00
static int hls_write_header ( AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
int ret , i ;
char * p ;
const char * pattern = " %d.ts " ;
2017-07-24 19:55:19 +08:00
const char * pattern_localtime_fmt = get_default_pattern_localtime_fmt ( s ) ;
2015-06-04 13:25:52 +05:30
const char * vtt_pattern = " %d.vtt " ;
2014-09-12 18:04:52 +08:00
AVDictionary * options = NULL ;
2014-12-05 21:20:15 +00:00
int basename_size ;
2015-06-04 13:25:52 +05:30
int vtt_basename_size ;
2012-10-26 16:36:56 +02:00
2017-07-24 19:55:19 +08:00
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
pattern = " %d.m4s " ;
2017-07-07 10:05:54 +08:00
}
2017-03-13 11:58:34 +08:00
if ( ( hls - > start_sequence_source_type = = HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH ) | |
( hls - > start_sequence_source_type = = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME ) ) {
2017-01-12 20:43:10 +08:00
time_t t = time ( NULL ) ; // we will need it in either case
2017-01-12 14:01:22 +01:00
if ( hls - > start_sequence_source_type = = HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH ) {
2017-01-12 20:43:10 +08:00
hls - > start_sequence = ( int64_t ) t ;
2017-01-12 14:01:22 +01:00
} else if ( hls - > start_sequence_source_type = = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME ) {
2017-01-12 20:43:10 +08:00
char b [ 15 ] ;
struct tm * p , tmbuf ;
if ( ! ( p = localtime_r ( & t , & tmbuf ) ) )
return AVERROR ( ENOMEM ) ;
if ( ! strftime ( b , sizeof ( b ) , " %Y%m%d%H%M%S " , p ) )
return AVERROR ( ENOMEM ) ;
hls - > start_sequence = strtoll ( b , NULL , 10 ) ;
}
av_log ( hls , AV_LOG_DEBUG , " start_number evaluated to % " PRId64 " \n " , hls - > start_sequence ) ;
}
2014-04-29 10:13:34 +02:00
hls - > sequence = hls - > start_sequence ;
2016-08-27 19:11:14 +08:00
hls - > recording_time = ( hls - > init_time ? hls - > init_time : hls - > time ) * AV_TIME_BASE ;
2012-10-26 16:36:56 +02:00
hls - > start_pts = AV_NOPTS_VALUE ;
2017-01-03 22:57:51 +08:00
hls - > current_segment_final_filename_fmt [ 0 ] = ' \0 ' ;
2012-10-26 16:36:56 +02:00
2016-09-07 21:34:44 +02:00
if ( hls - > flags & HLS_PROGRAM_DATE_TIME ) {
time_t now0 ;
time ( & now0 ) ;
hls - > initial_prog_date_time = now0 ;
}
2014-09-12 18:04:52 +08:00
if ( hls - > format_options_str ) {
ret = av_dict_parse_string ( & hls - > format_options , hls - > format_options_str , " = " , " : " , 0 ) ;
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , " Could not parse format options list '%s' \n " , hls - > format_options_str ) ;
goto fail ;
}
}
2015-06-04 13:25:52 +05:30
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
2012-10-26 16:36:56 +02:00
hls - > has_video + =
2014-06-18 20:42:52 +02:00
s - > streams [ i ] - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO ;
2015-06-04 13:25:52 +05:30
hls - > has_subtitle + =
2016-04-10 20:58:15 +01:00
s - > streams [ i ] - > codecpar - > codec_type = = AVMEDIA_TYPE_SUBTITLE ;
2015-06-04 13:25:52 +05:30
}
2012-10-26 16:36:56 +02:00
if ( hls - > has_video > 1 )
av_log ( s , AV_LOG_WARNING ,
" More than a single video stream present, "
" expect issues decoding it. \n " ) ;
2017-07-03 20:20:44 +08:00
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
hls - > oformat = av_guess_format ( " mp4 " , NULL , NULL ) ;
} else {
hls - > oformat = av_guess_format ( " mpegts " , NULL , NULL ) ;
}
2012-10-26 16:36:56 +02:00
if ( ! hls - > oformat ) {
ret = AVERROR_MUXER_NOT_FOUND ;
goto fail ;
}
2015-06-04 13:25:52 +05:30
if ( hls - > has_subtitle ) {
hls - > vtt_oformat = av_guess_format ( " webvtt " , NULL , NULL ) ;
if ( ! hls - > oformat ) {
ret = AVERROR_MUXER_NOT_FOUND ;
goto fail ;
}
}
2014-12-05 21:20:15 +00:00
if ( hls - > segment_filename ) {
hls - > basename = av_strdup ( hls - > segment_filename ) ;
if ( ! hls - > basename ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
} else {
2017-08-04 21:38:55 +08:00
if ( hls - > flags & HLS_SINGLE_FILE ) {
if ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) {
pattern = " .m4s " ;
} else {
pattern = " .ts " ;
}
}
2012-10-26 16:36:56 +02:00
2015-09-01 04:10:15 +08:00
if ( hls - > use_localtime ) {
basename_size = strlen ( s - > filename ) + strlen ( pattern_localtime_fmt ) + 1 ;
} else {
basename_size = strlen ( s - > filename ) + strlen ( pattern ) + 1 ;
}
2014-12-05 21:20:15 +00:00
hls - > basename = av_malloc ( basename_size ) ;
if ( ! hls - > basename ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2012-10-26 16:36:56 +02:00
2014-12-05 21:20:15 +00:00
av_strlcpy ( hls - > basename , s - > filename , basename_size ) ;
2012-10-26 16:36:56 +02:00
2014-12-05 21:20:15 +00:00
p = strrchr ( hls - > basename , ' . ' ) ;
if ( p )
* p = ' \0 ' ;
2015-09-01 04:10:15 +08:00
if ( hls - > use_localtime ) {
av_strlcat ( hls - > basename , pattern_localtime_fmt , basename_size ) ;
} else {
av_strlcat ( hls - > basename , pattern , basename_size ) ;
}
2017-07-24 19:55:19 +08:00
if ( av_strcasecmp ( hls - > fmp4_init_filename , " init.mp4 " ) ) {
int fmp4_init_filename_len = strlen ( hls - > fmp4_init_filename ) + 1 ;
hls - > base_output_dirname = av_malloc ( fmp4_init_filename_len ) ;
if ( ! hls - > base_output_dirname ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
av_strlcpy ( hls - > base_output_dirname , hls - > fmp4_init_filename , fmp4_init_filename_len ) ;
} else {
hls - > base_output_dirname = av_malloc ( basename_size ) ;
if ( ! hls - > base_output_dirname ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
av_strlcpy ( hls - > base_output_dirname , s - > filename , basename_size ) ;
p = strrchr ( hls - > base_output_dirname , ' / ' ) ;
if ( p ) {
* ( p + 1 ) = ' \0 ' ;
av_strlcat ( hls - > base_output_dirname , hls - > fmp4_init_filename , basename_size ) ;
} else {
av_strlcpy ( hls - > base_output_dirname , hls - > fmp4_init_filename , basename_size ) ;
}
}
2014-12-05 21:20:15 +00:00
}
2017-01-03 22:57:51 +08:00
if ( ! hls - > use_localtime ) {
2017-03-13 11:58:34 +08:00
ret = sls_flag_check_duration_size_index ( hls ) ;
if ( ret < 0 ) {
2017-01-03 22:57:51 +08:00
goto fail ;
}
} else {
2017-03-13 11:58:34 +08:00
ret = sls_flag_check_duration_size ( hls ) ;
if ( ret < 0 ) {
2017-01-03 22:57:51 +08:00
goto fail ;
}
2016-12-27 10:40:35 +08:00
}
2015-06-04 13:25:52 +05:30
if ( hls - > has_subtitle ) {
if ( hls - > flags & HLS_SINGLE_FILE )
vtt_pattern = " .vtt " ;
vtt_basename_size = strlen ( s - > filename ) + strlen ( vtt_pattern ) + 1 ;
hls - > vtt_basename = av_malloc ( vtt_basename_size ) ;
if ( ! hls - > vtt_basename ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
hls - > vtt_m3u8_name = av_malloc ( vtt_basename_size ) ;
if ( ! hls - > vtt_m3u8_name ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
av_strlcpy ( hls - > vtt_basename , s - > filename , vtt_basename_size ) ;
p = strrchr ( hls - > vtt_basename , ' . ' ) ;
if ( p )
* p = ' \0 ' ;
if ( hls - > subtitle_filename ) {
strcpy ( hls - > vtt_m3u8_name , hls - > subtitle_filename ) ;
} else {
strcpy ( hls - > vtt_m3u8_name , hls - > vtt_basename ) ;
av_strlcat ( hls - > vtt_m3u8_name , " _vtt.m3u8 " , vtt_basename_size ) ;
}
av_strlcat ( hls - > vtt_basename , vtt_pattern , vtt_basename_size ) ;
}
2017-08-04 21:38:55 +08:00
if ( ( hls - > flags & HLS_SINGLE_FILE ) & & ( hls - > segment_type = = SEGMENT_TYPE_FMP4 ) ) {
hls - > fmp4_init_filename = av_strdup ( hls - > basename ) ;
if ( ! hls - > fmp4_init_filename ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
}
2012-10-26 16:36:56 +02:00
if ( ( ret = hls_mux_init ( s ) ) < 0 )
goto fail ;
2016-08-21 12:55:45 +08:00
if ( hls - > flags & HLS_APPEND_LIST ) {
parse_playlist ( s , s - > filename ) ;
2016-12-05 17:55:33 +08:00
hls - > discontinuity = 1 ;
2016-08-30 17:26:54 +08:00
if ( hls - > init_time > 0 ) {
av_log ( s , AV_LOG_WARNING , " append_list mode does not support hls_init_time, "
" hls_init_time value will have no effect \n " ) ;
hls - > init_time = 0 ;
hls - > recording_time = hls - > time * AV_TIME_BASE ;
}
2016-08-21 12:55:45 +08:00
}
2017-08-04 21:38:55 +08:00
if ( hls - > segment_type ! = SEGMENT_TYPE_FMP4 | | hls - > flags & HLS_SINGLE_FILE ) {
2017-07-03 20:20:44 +08:00
if ( ( ret = hls_start ( s ) ) < 0 )
goto fail ;
}
2012-10-26 16:36:56 +02:00
2014-09-12 18:04:52 +08:00
av_dict_copy ( & options , hls - > format_options , 0 ) ;
ret = avformat_write_header ( hls - > avf , & options ) ;
if ( av_dict_count ( options ) ) {
av_log ( s , AV_LOG_ERROR , " Some of provided format options in '%s' are not recognized \n " , hls - > format_options_str ) ;
ret = AVERROR ( EINVAL ) ;
2014-07-29 16:24:11 +02:00
goto fail ;
2014-09-12 18:04:52 +08:00
}
2015-06-04 13:25:52 +05:30
//av_assert0(s->nb_streams == hls->avf->nb_streams);
2014-09-28 22:27:33 +02:00
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
2015-06-04 13:25:52 +05:30
AVStream * inner_st ;
2014-09-29 00:19:43 +02:00
AVStream * outer_st = s - > streams [ i ] ;
2016-09-19 07:00:42 +08:00
if ( hls - > max_seg_size > 0 ) {
if ( ( outer_st - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO ) & &
( outer_st - > codecpar - > bit_rate > hls - > max_seg_size ) ) {
av_log ( s , AV_LOG_WARNING , " Your video bitrate is bigger than hls_segment_size, "
" (% " PRId64 " > % " PRId64 " ), the result maybe not be what you want. " ,
outer_st - > codecpar - > bit_rate , hls - > max_seg_size ) ;
}
}
2016-04-10 20:58:15 +01:00
if ( outer_st - > codecpar - > codec_type ! = AVMEDIA_TYPE_SUBTITLE )
2015-06-04 13:25:52 +05:30
inner_st = hls - > avf - > streams [ i ] ;
else if ( hls - > vtt_avf )
inner_st = hls - > vtt_avf - > streams [ 0 ] ;
else {
2015-07-26 22:06:19 +02:00
/* We have a subtitle stream, when the user does not want one */
2015-06-04 13:25:52 +05:30
inner_st = NULL ;
continue ;
}
2014-09-29 00:19:43 +02:00
avpriv_set_pts_info ( outer_st , inner_st - > pts_wrap_bits , inner_st - > time_base . num , inner_st - > time_base . den ) ;
2014-09-28 22:27:33 +02:00
}
2012-10-26 16:36:56 +02:00
fail :
2014-09-12 18:04:52 +08:00
av_dict_free ( & options ) ;
2015-01-11 04:58:29 +01:00
if ( ret < 0 ) {
2017-08-04 21:38:55 +08:00
av_freep ( & hls - > fmp4_init_filename ) ;
2014-12-16 15:04:41 +01:00
av_freep ( & hls - > basename ) ;
2015-06-04 13:25:52 +05:30
av_freep ( & hls - > vtt_basename ) ;
2017-04-14 23:30:29 +08:00
av_freep ( & hls - > key_basename ) ;
2012-10-26 16:36:56 +02:00
if ( hls - > avf )
avformat_free_context ( hls - > avf ) ;
2015-06-04 13:25:52 +05:30
if ( hls - > vtt_avf )
avformat_free_context ( hls - > vtt_avf ) ;
2012-10-26 16:36:56 +02:00
}
return ret ;
}
static int hls_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
HLSContext * hls = s - > priv_data ;
2015-06-04 13:25:52 +05:30
AVFormatContext * oc = NULL ;
2012-10-26 16:36:56 +02:00
AVStream * st = s - > streams [ pkt - > stream_index ] ;
int64_t end_pts = hls - > recording_time * hls - > number ;
2013-04-28 11:48:40 +02:00
int is_ref_pkt = 1 ;
2017-07-03 20:20:44 +08:00
int ret = 0 , can_split = 1 ;
2015-06-04 13:25:52 +05:30
int stream_index = 0 ;
2012-10-26 16:36:56 +02:00
2016-08-27 19:11:14 +08:00
if ( hls - > sequence - hls - > nb_entries > hls - > start_sequence & & hls - > init_time > 0 ) {
/* reset end_pts, hls->recording_time at end of the init hls list */
int init_list_dur = hls - > init_time * hls - > nb_entries * AV_TIME_BASE ;
int after_init_list_dur = ( hls - > sequence - hls - > nb_entries ) * hls - > time * AV_TIME_BASE ;
hls - > recording_time = hls - > time * AV_TIME_BASE ;
end_pts = init_list_dur + after_init_list_dur ;
}
2016-04-10 20:58:15 +01:00
if ( st - > codecpar - > codec_type = = AVMEDIA_TYPE_SUBTITLE ) {
2015-06-04 13:25:52 +05:30
oc = hls - > vtt_avf ;
stream_index = 0 ;
} else {
oc = hls - > avf ;
stream_index = pkt - > stream_index ;
}
2012-10-26 16:36:56 +02:00
if ( hls - > start_pts = = AV_NOPTS_VALUE ) {
hls - > start_pts = pkt - > pts ;
hls - > end_pts = pkt - > pts ;
}
2013-04-26 09:54:59 +02:00
if ( hls - > has_video ) {
2014-06-18 20:42:52 +02:00
can_split = st - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO & &
2016-07-07 06:51:20 +08:00
( ( pkt - > flags & AV_PKT_FLAG_KEY ) | | ( hls - > flags & HLS_SPLIT_BY_TIME ) ) ;
2016-04-10 20:58:15 +01:00
is_ref_pkt = st - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO ;
2013-01-13 17:22:59 +01:00
}
2013-04-28 11:48:40 +02:00
if ( pkt - > pts = = AV_NOPTS_VALUE )
is_ref_pkt = can_split = 0 ;
2013-01-13 17:22:59 +01:00
2017-01-11 04:33:13 +08:00
if ( is_ref_pkt ) {
if ( hls - > new_start ) {
hls - > new_start = 0 ;
hls - > duration = ( double ) ( pkt - > pts - hls - > end_pts )
* st - > time_base . num / st - > time_base . den ;
hls - > dpp = ( double ) ( pkt - > duration ) * st - > time_base . num / st - > time_base . den ;
} else {
2017-03-15 07:37:11 +08:00
if ( pkt - > duration ) {
hls - > duration + = ( double ) ( pkt - > duration ) * st - > time_base . num / st - > time_base . den ;
} else {
av_log ( s , AV_LOG_WARNING , " pkt->duration = 0, maybe the hls segment duration will not precise \n " ) ;
hls - > duration = ( double ) ( pkt - > pts - hls - > end_pts ) * st - > time_base . num / st - > time_base . den ;
}
2017-01-11 04:33:13 +08:00
}
2012-10-26 16:36:56 +02:00
2017-01-11 04:33:13 +08:00
}
2017-07-03 20:20:44 +08:00
if ( hls - > fmp4_init_mode | | can_split & & av_compare_ts ( pkt - > pts - hls - > start_pts , st - > time_base ,
2013-04-26 09:54:59 +02:00
end_pts , AV_TIME_BASE_Q ) > = 0 ) {
2014-09-15 16:26:57 +03:00
int64_t new_start_pos ;
2017-01-13 07:57:22 +08:00
char * old_filename = av_strdup ( hls - > avf - > filename ) ;
2017-03-14 10:16:00 +08:00
int byterange_mode = ( hls - > flags & HLS_SINGLE_FILE ) | | ( hls - > max_seg_size > 0 ) ;
2017-01-13 07:57:22 +08:00
if ( ! old_filename ) {
return AVERROR ( ENOMEM ) ;
}
2017-07-03 20:20:44 +08:00
av_write_frame ( hls - > avf , NULL ) ; /* Flush any buffered data */
2014-09-15 16:26:57 +03:00
new_start_pos = avio_tell ( hls - > avf - > pb ) ;
hls - > size = new_start_pos - hls - > start_pos ;
2017-02-25 10:59:05 +08:00
2017-03-14 10:16:00 +08:00
if ( ! byterange_mode ) {
ff_format_io_close ( s , & oc - > pb ) ;
if ( hls - > vtt_avf ) {
ff_format_io_close ( s , & hls - > vtt_avf - > pb ) ;
}
2017-03-04 09:27:56 +08:00
}
2017-02-25 10:59:05 +08:00
if ( ( hls - > flags & HLS_TEMP_FILE ) & & oc - > filename [ 0 ] ) {
if ( ! ( hls - > flags & HLS_SINGLE_FILE ) | | ( hls - > max_seg_size < = 0 ) )
2017-07-03 20:20:44 +08:00
if ( ( hls - > avf - > oformat - > priv_class & & hls - > avf - > priv_data ) & & hls - > segment_type ! = SEGMENT_TYPE_FMP4 )
2017-02-25 10:59:05 +08:00
av_opt_set ( hls - > avf - > priv_data , " mpegts_flags " , " resend_headers " , 0 ) ;
hls_rename_temp_file ( s , oc ) ;
}
2017-07-03 20:20:44 +08:00
if ( hls - > fmp4_init_mode ) {
hls - > number - - ;
}
2017-08-04 21:38:55 +08:00
if ( ! hls - > fmp4_init_mode | | byterange_mode )
2017-07-03 20:20:44 +08:00
ret = hls_append_segment ( s , hls , hls - > duration , hls - > start_pos , hls - > size ) ;
2014-09-15 16:26:57 +03:00
hls - > start_pos = new_start_pos ;
2017-01-13 07:57:22 +08:00
if ( ret < 0 ) {
av_free ( old_filename ) ;
2012-12-25 08:59:38 +01:00
return ret ;
2017-01-13 07:57:22 +08:00
}
2012-12-25 08:59:38 +01:00
2012-10-26 16:36:56 +02:00
hls - > end_pts = pkt - > pts ;
2013-01-13 17:22:59 +01:00
hls - > duration = 0 ;
2012-10-26 16:36:56 +02:00
2017-07-03 20:20:44 +08:00
hls - > fmp4_init_mode = 0 ;
2014-09-15 16:26:57 +03:00
if ( hls - > flags & HLS_SINGLE_FILE ) {
hls - > number + + ;
2016-09-19 07:00:42 +08:00
} else if ( hls - > max_seg_size > 0 ) {
if ( hls - > start_pos > = hls - > max_seg_size ) {
hls - > sequence + + ;
2017-03-13 11:58:34 +08:00
sls_flag_file_rename ( hls , old_filename ) ;
2016-09-19 07:00:42 +08:00
ret = hls_start ( s ) ;
hls - > start_pos = 0 ;
/* When split segment by byte, the duration is short than hls_time,
* so it is not enough one segment duration as hls_time, */
hls - > number - - ;
}
hls - > number + + ;
2014-09-15 16:26:57 +03:00
} else {
2017-03-13 11:58:34 +08:00
sls_flag_file_rename ( hls , old_filename ) ;
2014-09-15 16:26:57 +03:00
ret = hls_start ( s ) ;
}
2017-05-11 10:15:08 +08:00
av_free ( old_filename ) ;
2012-10-26 16:36:56 +02:00
2017-01-13 07:57:22 +08:00
if ( ret < 0 ) {
2012-10-26 16:36:56 +02:00
return ret ;
2017-01-13 07:57:22 +08:00
}
2012-10-26 16:36:56 +02:00
2017-08-04 21:38:55 +08:00
if ( ! hls - > fmp4_init_mode | | byterange_mode )
if ( ( ret = hls_window ( s , 0 ) ) < 0 ) {
return ret ;
}
2012-10-26 16:36:56 +02:00
}
2015-06-04 13:25:52 +05:30
ret = ff_write_chained ( oc , stream_index , pkt , s , 0 ) ;
2012-10-26 16:36:56 +02:00
return ret ;
}
static int hls_write_trailer ( struct AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
AVFormatContext * oc = hls - > avf ;
2015-06-04 13:25:52 +05:30
AVFormatContext * vtt_oc = hls - > vtt_avf ;
2017-01-13 07:57:22 +08:00
char * old_filename = av_strdup ( hls - > avf - > filename ) ;
if ( ! old_filename ) {
return AVERROR ( ENOMEM ) ;
}
2012-10-26 16:36:56 +02:00
av_write_trailer ( oc ) ;
2014-12-15 18:10:46 -06:00
if ( oc - > pb ) {
hls - > size = avio_tell ( hls - > avf - > pb ) - hls - > start_pos ;
2016-02-10 14:40:32 +00:00
ff_format_io_close ( s , & oc - > pb ) ;
2017-02-25 10:59:05 +08:00
if ( ( hls - > flags & HLS_TEMP_FILE ) & & oc - > filename [ 0 ] ) {
hls_rename_temp_file ( s , oc ) ;
}
2017-01-11 04:33:13 +08:00
/* after av_write_trailer, then duration + 1 duration per packet */
hls_append_segment ( s , hls , hls - > duration + hls - > dpp , hls - > start_pos , hls - > size ) ;
2014-12-15 18:10:46 -06:00
}
2015-06-04 13:25:52 +05:30
2017-03-13 11:58:34 +08:00
sls_flag_file_rename ( hls , old_filename ) ;
2017-02-07 11:58:43 +08:00
2015-06-04 13:25:52 +05:30
if ( vtt_oc ) {
if ( vtt_oc - > pb )
av_write_trailer ( vtt_oc ) ;
hls - > size = avio_tell ( hls - > vtt_avf - > pb ) - hls - > start_pos ;
2016-02-10 14:40:32 +00:00
ff_format_io_close ( s , & vtt_oc - > pb ) ;
2015-06-04 13:25:52 +05:30
}
2014-12-16 15:04:41 +01:00
av_freep ( & hls - > basename ) ;
2017-07-24 19:55:19 +08:00
av_freep ( & hls - > base_output_dirname ) ;
2017-04-14 23:30:29 +08:00
av_freep ( & hls - > key_basename ) ;
2014-11-16 04:02:56 +01:00
avformat_free_context ( oc ) ;
2015-06-04 13:25:52 +05:30
2016-09-02 20:20:21 +01:00
hls - > avf = NULL ;
hls_window ( s , 1 ) ;
2017-08-22 10:21:42 +08:00
av_freep ( & hls - > fmp4_init_filename ) ;
2015-06-04 13:25:52 +05:30
if ( vtt_oc ) {
av_freep ( & hls - > vtt_basename ) ;
av_freep ( & hls - > vtt_m3u8_name ) ;
avformat_free_context ( vtt_oc ) ;
}
2014-12-08 10:25:05 -06:00
hls_free_segments ( hls - > segments ) ;
hls_free_segments ( hls - > old_segments ) ;
2017-01-13 07:57:22 +08:00
av_free ( old_filename ) ;
2012-10-26 16:36:56 +02:00
return 0 ;
}
# define OFFSET(x) offsetof(HLSContext, x)
# define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [ ] = {
2014-04-30 20:52:58 +02:00
{ " start_number " , " set first number in the sequence " , OFFSET ( start_sequence ) , AV_OPT_TYPE_INT64 , { . i64 = 0 } , 0 , INT64_MAX , E } ,
2013-01-09 11:26:31 +01:00
{ " hls_time " , " set segment length in seconds " , OFFSET ( time ) , AV_OPT_TYPE_FLOAT , { . dbl = 2 } , 0 , FLT_MAX , E } ,
2016-08-27 19:11:14 +08:00
{ " hls_init_time " , " set segment length in seconds at init list " , OFFSET ( init_time ) , AV_OPT_TYPE_FLOAT , { . dbl = 0 } , 0 , FLT_MAX , E } ,
2014-07-18 10:57:42 +02:00
{ " hls_list_size " , " set maximum number of playlist entries " , OFFSET ( max_nb_segments ) , AV_OPT_TYPE_INT , { . i64 = 5 } , 0 , INT_MAX , E } ,
2014-09-12 18:04:52 +08:00
{ " hls_ts_options " , " set hls mpegts list of options for the container format used for hls " , OFFSET ( format_options_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2015-06-04 13:25:52 +05:30
{ " hls_vtt_options " , " set hls vtt list of options for the container format used for hls " , OFFSET ( vtt_format_options_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2017-02-11 12:32:31 +08:00
# if FF_API_HLS_WRAP
{ " hls_wrap " , " set number after which the index wraps (will be deprecated) " , OFFSET ( wrap ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
# endif
2014-09-22 10:17:01 +02:00
{ " hls_allow_cache " , " explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments " , OFFSET ( allowcache ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , INT_MIN , INT_MAX , E } ,
2014-03-18 21:19:00 +01:00
{ " hls_base_url " , " url to prepend to each playlist entry " , OFFSET ( baseurl ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2014-12-05 21:20:15 +00:00
{ " hls_segment_filename " , " filename template for segment files " , OFFSET ( segment_filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2016-09-19 07:00:42 +08:00
{ " hls_segment_size " , " maximum size per segment file, (in bytes) " , OFFSET ( max_seg_size ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2015-06-15 10:58:07 -08:00
{ " hls_key_info_file " , " file with key URI and key file path " , OFFSET ( key_info_file ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2017-04-14 23:30:29 +08:00
{ " hls_enc " , " enable AES128 encryption support " , OFFSET ( encrypt ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
{ " hls_enc_key " , " hex-coded 16 byte key to encrypt the segments " , OFFSET ( key ) , AV_OPT_TYPE_STRING , . flags = E } ,
{ " hls_enc_key_url " , " url to access the key to decrypt the segments " , OFFSET ( key_url ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
{ " hls_enc_iv " , " hex-coded 16 byte initialization vector " , OFFSET ( iv ) , AV_OPT_TYPE_STRING , . flags = E } ,
2015-06-04 13:25:52 +05:30
{ " hls_subtitle_path " , " set path of hls subtitles " , OFFSET ( subtitle_filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2017-07-05 11:15:20 +08:00
{ " hls_segment_type " , " set hls segment files type " , OFFSET ( segment_type ) , AV_OPT_TYPE_INT , { . i64 = SEGMENT_TYPE_MPEGTS } , 0 , SEGMENT_TYPE_FMP4 , E , " segment_type " } ,
2017-07-03 20:20:44 +08:00
{ " mpegts " , " make segment file to mpegts files in m3u8 " , 0 , AV_OPT_TYPE_CONST , { . i64 = SEGMENT_TYPE_MPEGTS } , 0 , UINT_MAX , E , " segment_type " } ,
2017-07-05 11:15:20 +08:00
{ " fmp4 " , " make segment file to fragment mp4 files in m3u8 " , 0 , AV_OPT_TYPE_CONST , { . i64 = SEGMENT_TYPE_FMP4 } , 0 , UINT_MAX , E , " segment_type " } ,
2017-07-03 20:20:44 +08:00
{ " hls_fmp4_init_filename " , " set fragment mp4 file init filename " , OFFSET ( fmp4_init_filename ) , AV_OPT_TYPE_STRING , { . str = " init.mp4 " } , 0 , 0 , E } ,
2014-09-15 16:26:57 +03:00
{ " hls_flags " , " set flags affecting HLS playlist and media file generation " , OFFSET ( flags ) , AV_OPT_TYPE_FLAGS , { . i64 = 0 } , 0 , UINT_MAX , E , " flags " } ,
{ " single_file " , " generate a single media file indexed with byte ranges " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_SINGLE_FILE } , 0 , UINT_MAX , E , " flags " } ,
2017-02-07 11:58:43 +08:00
{ " temp_file " , " write segment to temporary file and rename when complete " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_TEMP_FILE } , 0 , UINT_MAX , E , " flags " } ,
2014-12-08 10:25:05 -06:00
{ " delete_segments " , " delete segment files that are no longer part of the playlist " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_DELETE_SEGMENTS } , 0 , UINT_MAX , E , " flags " } ,
2015-04-21 15:46:17 -06:00
{ " round_durations " , " round durations in m3u8 to whole numbers " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_ROUND_DURATIONS } , 0 , UINT_MAX , E , " flags " } ,
2015-04-21 15:52:03 -06:00
{ " discont_start " , " start the playlist with a discontinuity tag " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_DISCONT_START } , 0 , UINT_MAX , E , " flags " } ,
2015-04-21 15:53:57 -06:00
{ " omit_endlist " , " Do not append an endlist when ending stream " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_OMIT_ENDLIST } , 0 , UINT_MAX , E , " flags " } ,
2016-07-07 06:51:20 +08:00
{ " split_by_time " , " split the hls segment by time which user set by hls_time " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_SPLIT_BY_TIME } , 0 , UINT_MAX , E , " flags " } ,
2016-08-21 12:55:45 +08:00
{ " append_list " , " append the new segments into old hls segment list " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_APPEND_LIST } , 0 , UINT_MAX , E , " flags " } ,
2016-09-07 21:34:44 +02:00
{ " program_date_time " , " add EXT-X-PROGRAM-DATE-TIME " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_PROGRAM_DATE_TIME } , 0 , UINT_MAX , E , " flags " } ,
2016-12-27 10:40:35 +08:00
{ " second_level_segment_index " , " include segment index in segment filenames when use_localtime " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_SECOND_LEVEL_SEGMENT_INDEX } , 0 , UINT_MAX , E , " flags " } ,
2017-01-03 22:57:51 +08:00
{ " second_level_segment_duration " , " include segment duration in segment filenames when use_localtime " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_SECOND_LEVEL_SEGMENT_DURATION } , 0 , UINT_MAX , E , " flags " } ,
{ " second_level_segment_size " , " include segment size in segment filenames when use_localtime " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_SECOND_LEVEL_SEGMENT_SIZE } , 0 , UINT_MAX , E , " flags " } ,
2017-08-06 15:10:35 +08:00
{ " periodic_rekey " , " reload keyinfo file periodically for re-keying " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_PERIODIC_REKEY } , 0 , UINT_MAX , E , " flags " } ,
2016-02-03 22:20:07 +01:00
{ " use_localtime " , " set filename expansion with strftime at segment creation " , OFFSET ( use_localtime ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
{ " use_localtime_mkdir " , " create last directory component in strftime-generated filename " , OFFSET ( use_localtime_mkdir ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2015-12-17 03:16:45 +00:00
{ " hls_playlist_type " , " set the HLS playlist type " , OFFSET ( pl_type ) , AV_OPT_TYPE_INT , { . i64 = PLAYLIST_TYPE_NONE } , 0 , PLAYLIST_TYPE_NB - 1 , E , " pl_type " } ,
{ " event " , " EVENT playlist " , 0 , AV_OPT_TYPE_CONST , { . i64 = PLAYLIST_TYPE_EVENT } , INT_MIN , INT_MAX , E , " pl_type " } ,
{ " vod " , " VOD playlist " , 0 , AV_OPT_TYPE_CONST , { . i64 = PLAYLIST_TYPE_VOD } , INT_MIN , INT_MAX , E , " pl_type " } ,
2017-02-18 09:42:51 +08:00
{ " method " , " set the HTTP method(default: PUT) " , OFFSET ( method ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2017-01-12 14:01:22 +01:00
{ " hls_start_number_source " , " set source of first number in sequence " , OFFSET ( start_sequence_source_type ) , AV_OPT_TYPE_INT , { . i64 = HLS_START_SEQUENCE_AS_START_NUMBER } , 0 , HLS_START_SEQUENCE_AS_FORMATTED_DATETIME , E , " start_sequence_source_type " } ,
{ " generic " , " start_number value (default) " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_START_SEQUENCE_AS_START_NUMBER } , INT_MIN , INT_MAX , E , " start_sequence_source_type " } ,
{ " epoch " , " seconds since epoch " , 0 , AV_OPT_TYPE_CONST , { . i64 = HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH } , INT_MIN , INT_MAX , E , " start_sequence_source_type " } ,
{ " 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 " } ,
2012-10-26 16:36:56 +02:00
{ NULL } ,
} ;
static const AVClass hls_class = {
. class_name = " hls muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
AVOutputFormat ff_hls_muxer = {
. name = " hls " ,
2012-12-08 06:00:46 +01:00
. long_name = NULL_IF_CONFIG_SMALL ( " Apple HTTP Live Streaming " ) ,
2012-10-26 16:36:56 +02:00
. extensions = " m3u8 " ,
. priv_data_size = sizeof ( HLSContext ) ,
2014-05-15 21:43:46 +03:00
. audio_codec = AV_CODEC_ID_AAC ,
. video_codec = AV_CODEC_ID_H264 ,
2015-06-04 13:25:52 +05:30
. subtitle_codec = AV_CODEC_ID_WEBVTT ,
2017-07-03 20:20:44 +08:00
. flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH ,
2012-10-26 16:36:56 +02:00
. write_header = hls_write_header ,
. write_packet = hls_write_packet ,
. write_trailer = hls_write_trailer ,
. priv_class = & hls_class ,
} ;