2011-10-03 00:05:29 +03:00
/*
* Copyright ( c ) 2011 , Luca Barbato
*
2013-08-16 00:12:51 +03:00
* This file is part of FFmpeg .
2011-10-03 00:05:29 +03:00
*
2013-08-16 00:12:51 +03:00
* FFmpeg is free software ; you can redistribute it and / or
2011-10-03 00:05:29 +03: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 .
*
2013-08-16 00:12:51 +03:00
* FFmpeg is distributed in the hope that it will be useful ,
2011-10-03 00:05:29 +03: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
2013-08-16 00:12:51 +03:00
* License along with FFmpeg ; if not , write to the Free Software
2011-10-03 00:05:29 +03:00
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
2012-08-15 23:20:16 +03:00
/**
* @ file generic segmenter
2012-08-15 12:06:34 +03:00
* M3U8 specification can be find here :
2013-01-22 22:17:00 +03:00
* @ url { http : //tools.ietf.org/id/draft-pantos-http-live-streaming}
2012-08-15 23:20:16 +03:00
*/
2011-10-03 00:05:29 +03:00
# include <float.h>
2014-07-07 22:35:43 +03:00
# include <time.h>
2011-10-03 00:05:29 +03:00
# include "avformat.h"
2016-01-30 03:17:51 +02:00
# include "avio_internal.h"
2011-10-03 00:05:29 +03:00
# include "internal.h"
2012-12-11 02:14:22 +03:00
# include "libavutil/avassert.h"
2015-08-18 02:45:35 +02:00
# include "libavutil/internal.h"
2011-10-03 00:05:29 +03:00
# include "libavutil/log.h"
# include "libavutil/opt.h"
# include "libavutil/avstring.h"
# include "libavutil/parseutils.h"
# include "libavutil/mathematics.h"
2014-07-08 23:51:16 +03:00
# include "libavutil/time.h"
2016-02-28 22:36:42 +02:00
# include "libavutil/timecode.h"
2014-11-02 21:07:34 +02:00
# include "libavutil/time_internal.h"
2012-11-29 15:45:50 +03:00
# include "libavutil/timestamp.h"
2011-10-03 00:05:29 +03:00
2012-12-11 02:14:22 +03:00
typedef struct SegmentListEntry {
int index ;
double start_time , end_time ;
2013-02-06 02:19:52 +03:00
int64_t start_pts ;
2013-07-05 15:28:38 +03:00
int64_t offset_pts ;
2013-11-22 14:49:05 +03:00
char * filename ;
2012-12-11 02:14:22 +03:00
struct SegmentListEntry * next ;
2014-07-17 21:37:55 +03:00
int64_t last_duration ;
2012-12-11 02:14:22 +03:00
} SegmentListEntry ;
2012-01-14 01:29:09 +03:00
typedef enum {
2012-08-16 01:16:28 +03:00
LIST_TYPE_UNDEFINED = - 1 ,
2012-01-14 01:29:09 +03:00
LIST_TYPE_FLAT = 0 ,
2012-08-16 19:13:17 +03:00
LIST_TYPE_CSV ,
2012-08-15 12:06:34 +03:00
LIST_TYPE_M3U8 ,
2012-09-15 00:31:47 +03:00
LIST_TYPE_EXT , ///< deprecated
2013-02-21 21:33:26 +03:00
LIST_TYPE_FFCONCAT ,
2012-01-14 01:29:09 +03:00
LIST_TYPE_NB ,
} ListType ;
2012-09-01 19:01:51 +03:00
# define SEGMENT_LIST_FLAG_CACHE 1
# define SEGMENT_LIST_FLAG_LIVE 2
2014-09-22 10:19:33 +03:00
typedef struct SegmentContext {
2011-10-03 00:05:29 +03:00
const AVClass * class ; /**< Class for private options. */
2012-08-15 23:12:11 +03:00
int segment_idx ; ///< index of the segment file to write, starting from 0
int segment_idx_wrap ; ///< number after which the index wraps
2014-02-05 16:59:00 +03:00
int segment_idx_wrap_nb ; ///< number of time the index has wraped
2012-08-15 23:12:11 +03:00
int segment_count ; ///< number of segment files already written
2012-10-04 00:15:35 +03:00
AVOutputFormat * oformat ;
2011-10-03 00:05:29 +03:00
AVFormatContext * avf ;
2014-09-06 16:43:11 +03:00
char * format ; ///< format to use for output segment files
char * format_options_str ; ///< format options to use for output segment files
AVDictionary * format_options ;
2012-01-14 13:00:16 +03:00
char * list ; ///< filename for the segment list file
2012-09-01 19:01:51 +03:00
int list_flags ; ///< flags affecting list generation
2012-01-14 13:00:16 +03:00
int list_size ; ///< number of entries for the segment list file
2014-07-07 22:35:43 +03:00
int use_clocktime ; ///< flag to cut segments at regular clock time
2016-01-13 02:44:36 +02:00
int64_t clocktime_offset ; //< clock offset for cutting the segments at regular clock time
2016-01-18 02:29:06 +02:00
int64_t clocktime_wrap_duration ; //< wrapping duration considered for starting a new segment
2014-07-07 22:35:43 +03:00
int64_t last_val ; ///< remember last time for wrap around detection
int cut_pending ;
2016-04-08 02:18:45 +02:00
int header_written ; ///< whether we've already called avformat_write_header
2014-07-07 22:35:43 +03:00
2014-04-29 16:42:04 +03:00
char * entry_prefix ; ///< prefix to add to list entry filenames
2015-04-07 23:21:48 +02:00
int list_type ; ///< set the list type
2012-01-17 22:04:14 +03:00
AVIOContext * list_pb ; ///< list file put-byte context
2012-07-08 18:26:33 +03:00
char * time_str ; ///< segment duration specification string
int64_t time ; ///< segment duration
2014-12-28 09:35:34 +02:00
int use_strftime ; ///< flag to expand filename with strftime
2016-02-28 22:36:42 +02:00
int increment_tc ; ///< flag to increment timecode if found
2012-12-09 22:26:30 +03:00
2012-01-29 00:36:38 +03:00
char * times_str ; ///< segment times specification string
int64_t * times ; ///< list of segment interval specification
int nb_times ; ///< number of elments in the times array
2012-12-09 22:26:30 +03:00
char * frames_str ; ///< segment frame numbers specification string
int * frames ; ///< list of frame number specification
int nb_frames ; ///< number of elments in the frames array
2014-07-17 21:28:40 +03:00
int frame_count ; ///< total number of reference frames
int segment_frame_count ; ///< number of reference frames in the segment
2012-12-09 22:26:30 +03:00
2012-01-29 00:21:43 +03:00
int64_t time_delta ;
2012-10-03 00:49:46 +03:00
int individual_header_trailer ; /**< Set by a private option. */
2012-10-03 00:59:35 +03:00
int write_header_trailer ; /**< Set by a private option. */
2015-03-29 03:25:20 +02:00
char * header_filename ; ///< filename to write the output header to
2012-11-29 15:45:50 +03:00
2017-09-22 01:10:56 +02:00
int reset_timestamps ; ///< reset timestamps at the beginning of each segment
2013-07-05 15:28:38 +03:00
int64_t initial_offset ; ///< initial timestamps offset, expressed in microseconds
2012-12-22 23:49:06 +03:00
char * reference_stream_specifier ; ///< reference stream specifier
int reference_stream_index ;
2015-06-09 09:32:18 +02:00
int break_non_keyframes ;
2016-03-07 04:48:57 +02:00
int write_empty ;
2012-12-22 23:49:06 +03:00
2015-08-27 23:40:25 +02:00
int use_rename ;
char temp_list_filename [ 1024 ] ;
2012-12-11 02:14:22 +03:00
SegmentListEntry cur_entry ;
2012-12-20 16:20:19 +03:00
SegmentListEntry * segment_list_entries ;
SegmentListEntry * segment_list_entries_end ;
2011-10-03 00:05:29 +03:00
} SegmentContext ;
2012-09-01 17:12:29 +03:00
static void print_csv_escaped_str ( AVIOContext * ctx , const char * str )
{
2012-09-14 19:26:50 +03:00
int needs_quoting = ! ! str [ strcspn ( str , " \" , \n \r " ) ] ;
2012-09-01 17:12:29 +03:00
2012-09-14 19:26:50 +03:00
if ( needs_quoting )
2012-09-01 17:12:29 +03:00
avio_w8 ( ctx , ' " ' ) ;
2012-09-09 22:27:13 +03:00
for ( ; * str ; str + + ) {
if ( * str = = ' " ' )
2012-09-01 17:12:29 +03:00
avio_w8 ( ctx , ' " ' ) ;
2012-09-09 22:27:13 +03:00
avio_w8 ( ctx , * str ) ;
2012-09-01 17:12:29 +03:00
}
2012-09-14 19:26:50 +03:00
if ( needs_quoting )
2012-09-01 17:12:29 +03:00
avio_w8 ( ctx , ' " ' ) ;
}
2012-10-04 00:15:35 +03:00
static int segment_mux_init ( AVFormatContext * s )
2011-10-03 00:05:29 +03:00
{
2012-01-14 01:27:09 +03:00
SegmentContext * seg = s - > priv_data ;
2012-10-04 00:15:35 +03:00
AVFormatContext * oc ;
int i ;
2014-09-02 13:19:53 +03:00
int ret ;
2012-10-04 00:15:35 +03:00
2014-09-02 13:19:53 +03:00
ret = avformat_alloc_output_context2 ( & seg - > avf , seg - > oformat , NULL , NULL ) ;
if ( ret < 0 )
return ret ;
oc = seg - > avf ;
2012-10-04 00:15:35 +03:00
oc - > interrupt_callback = s - > interrupt_callback ;
2014-10-17 19:28:47 +03:00
oc - > max_delay = s - > max_delay ;
2013-03-18 21:06:45 +03:00
av_dict_copy ( & oc - > metadata , s - > metadata , 0 ) ;
2016-01-16 18:53:43 +02:00
oc - > opaque = s - > opaque ;
oc - > io_close = s - > io_close ;
oc - > io_open = s - > io_open ;
2016-04-11 03:59:12 +02:00
oc - > flags = s - > flags ;
2012-10-04 00:15:35 +03:00
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
AVStream * st ;
2016-04-10 21:58:15 +02:00
AVCodecParameters * ipar , * opar ;
2012-11-17 19:39:51 +03:00
2012-10-04 00:15:35 +03:00
if ( ! ( st = avformat_new_stream ( oc , NULL ) ) )
return AVERROR ( ENOMEM ) ;
2016-04-10 21:58:15 +02:00
ipar = s - > streams [ i ] - > codecpar ;
opar = st - > codecpar ;
avcodec_parameters_copy ( opar , ipar ) ;
2012-11-17 19:39:51 +03:00
if ( ! oc - > oformat - > codec_tag | |
2016-04-10 21:58:15 +02:00
av_codec_get_id ( oc - > oformat - > codec_tag , ipar - > codec_tag ) = = opar - > codec_id | |
av_codec_get_tag ( oc - > oformat - > codec_tag , ipar - > codec_id ) < = 0 ) {
opar - > codec_tag = ipar - > codec_tag ;
2012-11-17 19:39:51 +03:00
} else {
2016-04-10 21:58:15 +02:00
opar - > codec_tag = 0 ;
2012-11-17 19:39:51 +03:00
}
2012-10-04 00:15:35 +03: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 ;
2014-09-01 20:05:44 +03:00
av_dict_copy ( & st - > metadata , s - > streams [ i ] - > metadata , 0 ) ;
2012-10-04 00:15:35 +03:00
}
return 0 ;
}
2012-12-13 13:37:50 +03:00
static int set_segment_filename ( AVFormatContext * s )
{
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
2013-11-22 14:49:05 +03:00
size_t size ;
2015-12-08 13:29:13 +02:00
int ret ;
2017-12-30 00:30:14 +02:00
char buf [ 1024 ] ;
char * new_name ;
2012-12-13 13:37:50 +03:00
if ( seg - > segment_idx_wrap )
seg - > segment_idx % = seg - > segment_idx_wrap ;
2014-12-28 09:35:34 +02:00
if ( seg - > use_strftime ) {
time_t now0 ;
struct tm * tm , tmpbuf ;
time ( & now0 ) ;
tm = localtime_r ( & now0 , & tmpbuf ) ;
2017-12-30 00:30:14 +02:00
if ( ! strftime ( buf , sizeof ( buf ) , s - > url , tm ) ) {
2014-12-28 09:35:34 +02:00
av_log ( oc , AV_LOG_ERROR , " Could not get segment filename with strftime \n " ) ;
return AVERROR ( EINVAL ) ;
}
2017-12-30 00:30:14 +02:00
} else if ( av_get_frame_filename ( buf , sizeof ( buf ) ,
s - > url , seg - > segment_idx ) < 0 ) {
av_log ( oc , AV_LOG_ERROR , " Invalid segment filename template '%s' \n " , s - > url ) ;
2012-12-13 13:37:50 +03:00
return AVERROR ( EINVAL ) ;
}
2017-12-30 00:30:14 +02:00
new_name = av_strdup ( buf ) ;
if ( ! new_name )
return AVERROR ( ENOMEM ) ;
ff_format_set_url ( oc , new_name ) ;
2013-11-22 14:49:05 +03:00
/* copy modified name in list entry */
2017-12-30 00:30:14 +02:00
size = strlen ( av_basename ( oc - > url ) ) + 1 ;
2014-04-29 16:42:04 +03:00
if ( seg - > entry_prefix )
size + = strlen ( seg - > entry_prefix ) ;
2013-11-22 14:49:05 +03:00
2015-12-08 13:29:13 +02:00
if ( ( ret = av_reallocp ( & seg - > cur_entry . filename , size ) ) < 0 )
return ret ;
2013-11-22 14:49:05 +03:00
snprintf ( seg - > cur_entry . filename , size , " %s%s " ,
2014-04-29 16:42:04 +03:00
seg - > entry_prefix ? seg - > entry_prefix : " " ,
2017-12-30 00:30:14 +02:00
av_basename ( oc - > url ) ) ;
2013-11-22 14:49:05 +03:00
2012-12-13 13:37:50 +03:00
return 0 ;
}
2012-10-03 00:49:46 +03:00
static int segment_start ( AVFormatContext * s , int write_header )
2011-10-03 00:05:29 +03:00
{
2012-11-17 18:35:51 +03:00
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
2011-10-03 00:05:29 +03:00
int err = 0 ;
2012-10-03 00:49:46 +03:00
if ( write_header ) {
avformat_free_context ( oc ) ;
2012-11-17 18:35:51 +03:00
seg - > avf = NULL ;
2012-10-03 00:49:46 +03:00
if ( ( err = segment_mux_init ( s ) ) < 0 )
return err ;
2012-11-17 18:35:51 +03:00
oc = seg - > avf ;
2012-10-03 00:49:46 +03:00
}
2012-10-04 00:15:35 +03:00
2012-11-17 19:20:29 +03:00
seg - > segment_idx + + ;
2015-03-31 04:23:19 +02:00
if ( ( seg - > segment_idx_wrap ) & & ( seg - > segment_idx % seg - > segment_idx_wrap = = 0 ) )
2014-02-05 16:59:00 +03:00
seg - > segment_idx_wrap_nb + + ;
2012-12-13 13:37:50 +03:00
if ( ( err = set_segment_filename ( s ) ) < 0 )
return err ;
2011-10-03 00:05:29 +03:00
2017-12-30 00:30:14 +02:00
if ( ( err = s - > io_open ( s , & oc - > pb , oc - > url , AVIO_FLAG_WRITE , NULL ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Failed to open segment '%s' \n " , oc - > url ) ;
2011-10-03 00:05:29 +03:00
return err ;
2013-11-25 21:20:11 +03:00
}
2015-03-29 18:57:09 +02:00
if ( ! seg - > individual_header_trailer )
oc - > pb - > seekable = 0 ;
2011-10-03 00:05:29 +03:00
2012-10-03 00:51:46 +03:00
if ( oc - > oformat - > priv_class & & oc - > priv_data )
2014-09-01 20:05:44 +03:00
av_opt_set ( oc - > priv_data , " mpegts_flags " , " +resend_headers " , 0 ) ;
2012-10-03 00:51:46 +03:00
2012-10-03 00:49:46 +03:00
if ( write_header ) {
2016-03-15 20:19:20 +02:00
AVDictionary * options = NULL ;
av_dict_copy ( & options , seg - > format_options , 0 ) ;
2016-04-08 02:18:45 +02:00
av_dict_set ( & options , " fflags " , " -autobsf " , 0 ) ;
2016-03-15 20:19:20 +02:00
err = avformat_write_header ( oc , & options ) ;
av_dict_free ( & options ) ;
if ( err < 0 )
2012-10-03 00:49:46 +03:00
return err ;
}
2011-10-03 00:05:29 +03:00
2014-07-17 21:28:40 +03:00
seg - > segment_frame_count = 0 ;
2011-10-03 00:05:29 +03:00
return 0 ;
}
2012-08-16 00:21:41 +03:00
static int segment_list_open ( AVFormatContext * s )
{
SegmentContext * seg = s - > priv_data ;
2012-08-16 00:14:34 +03:00
int ret ;
2015-08-27 23:40:25 +02:00
snprintf ( seg - > temp_list_filename , sizeof ( seg - > temp_list_filename ) , seg - > use_rename ? " %s.tmp " : " %s " , seg - > list ) ;
2016-02-10 16:40:32 +02:00
ret = s - > io_open ( s , & seg - > list_pb , seg - > temp_list_filename , AVIO_FLAG_WRITE , NULL ) ;
2013-11-25 21:20:11 +03:00
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , " Failed to open segment list '%s' \n " , seg - > list ) ;
2012-08-16 00:14:34 +03:00
return ret ;
2013-11-25 21:20:11 +03:00
}
2012-08-15 12:06:34 +03:00
2012-12-20 16:20:19 +03:00
if ( seg - > list_type = = LIST_TYPE_M3U8 & & seg - > segment_list_entries ) {
SegmentListEntry * entry ;
double max_duration = 0 ;
2012-08-15 12:06:34 +03:00
avio_printf ( seg - > list_pb , " #EXTM3U \n " ) ;
2012-09-01 18:13:26 +03:00
avio_printf ( seg - > list_pb , " #EXT-X-VERSION:3 \n " ) ;
2012-12-20 16:20:19 +03:00
avio_printf ( seg - > list_pb , " #EXT-X-MEDIA-SEQUENCE:%d \n " , seg - > segment_list_entries - > index ) ;
2013-02-03 13:12:49 +03:00
avio_printf ( seg - > list_pb , " #EXT-X-ALLOW-CACHE:%s \n " ,
seg - > list_flags & SEGMENT_LIST_FLAG_CACHE ? " YES " : " NO " ) ;
2012-12-20 16:20:19 +03:00
2014-04-30 21:12:17 +03:00
av_log ( s , AV_LOG_VERBOSE , " EXT-X-MEDIA-SEQUENCE:%d \n " ,
seg - > segment_list_entries - > index ) ;
2012-12-20 16:20:19 +03:00
for ( entry = seg - > segment_list_entries ; entry ; entry = entry - > next )
max_duration = FFMAX ( max_duration , entry - > end_time - entry - > start_time ) ;
avio_printf ( seg - > list_pb , " #EXT-X-TARGETDURATION:% " PRId64 " \n " , ( int64_t ) ceil ( max_duration ) ) ;
2013-02-21 21:33:26 +03:00
} else if ( seg - > list_type = = LIST_TYPE_FFCONCAT ) {
avio_printf ( seg - > list_pb , " ffconcat version 1.0 \n " ) ;
2012-08-15 12:06:34 +03:00
}
2012-08-16 00:14:34 +03:00
return ret ;
2012-08-16 00:21:41 +03:00
}
2012-12-11 02:14:22 +03:00
static void segment_list_print_entry ( AVIOContext * list_ioctx ,
ListType list_type ,
2013-02-21 21:33:26 +03:00
const SegmentListEntry * list_entry ,
void * log_ctx )
2012-12-11 02:14:22 +03:00
{
switch ( list_type ) {
case LIST_TYPE_FLAT :
avio_printf ( list_ioctx , " %s \n " , list_entry - > filename ) ;
break ;
case LIST_TYPE_CSV :
case LIST_TYPE_EXT :
print_csv_escaped_str ( list_ioctx , list_entry - > filename ) ;
avio_printf ( list_ioctx , " ,%f,%f \n " , list_entry - > start_time , list_entry - > end_time ) ;
break ;
case LIST_TYPE_M3U8 :
avio_printf ( list_ioctx , " #EXTINF:%f, \n %s \n " ,
list_entry - > end_time - list_entry - > start_time , list_entry - > filename ) ;
break ;
2013-02-21 21:33:26 +03:00
case LIST_TYPE_FFCONCAT :
{
char * buf ;
if ( av_escape ( & buf , list_entry - > filename , NULL , AV_ESCAPE_MODE_AUTO , AV_ESCAPE_FLAG_WHITESPACE ) < 0 ) {
av_log ( log_ctx , AV_LOG_WARNING ,
" Error writing list entry '%s' in list file \n " , list_entry - > filename ) ;
return ;
}
avio_printf ( list_ioctx , " file %s \n " , buf ) ;
av_free ( buf ) ;
break ;
}
2012-12-11 02:14:22 +03:00
default :
av_assert0 ( ! " Invalid list type " ) ;
}
}
2013-01-23 02:23:47 +03:00
static int segment_end ( AVFormatContext * s , int write_trailer , int is_last )
2011-10-03 00:05:29 +03:00
{
2012-01-14 01:24:13 +03:00
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
2011-10-03 00:05:29 +03:00
int ret = 0 ;
2016-02-28 22:36:42 +02:00
AVTimecode tc ;
AVRational rate ;
AVDictionaryEntry * tcr ;
char buf [ AV_TIMECODE_STR_SIZE ] ;
int i ;
int err ;
2011-10-03 00:05:29 +03:00
2017-01-21 04:15:03 +02:00
if ( ! oc | | ! oc - > pb )
return AVERROR ( EINVAL ) ;
2012-10-04 15:28:30 +03:00
av_write_frame ( oc , NULL ) ; /* Flush any buffered data (fragmented mp4) */
2012-10-03 00:49:46 +03:00
if ( write_trailer )
2012-10-05 17:14:06 +03:00
ret = av_write_trailer ( oc ) ;
2011-10-03 00:05:29 +03:00
2012-01-13 01:24:27 +03:00
if ( ret < 0 )
2012-01-14 01:24:13 +03:00
av_log ( s , AV_LOG_ERROR , " Failure occurred when ending segment '%s' \n " ,
2017-12-30 00:30:14 +02:00
oc - > url ) ;
2012-01-13 01:24:27 +03:00
2012-01-14 02:16:55 +03:00
if ( seg - > list ) {
2012-12-20 16:20:19 +03:00
if ( seg - > list_size | | seg - > list_type = = LIST_TYPE_M3U8 ) {
SegmentListEntry * entry = av_mallocz ( sizeof ( * entry ) ) ;
if ( ! entry ) {
ret = AVERROR ( ENOMEM ) ;
goto end ;
}
/* append new element */
memcpy ( entry , & seg - > cur_entry , sizeof ( * entry ) ) ;
2015-08-26 03:30:45 +02:00
entry - > filename = av_strdup ( entry - > filename ) ;
2012-12-20 16:20:19 +03:00
if ( ! seg - > segment_list_entries )
seg - > segment_list_entries = seg - > segment_list_entries_end = entry ;
else
seg - > segment_list_entries_end - > next = entry ;
seg - > segment_list_entries_end = entry ;
/* drop first item */
2014-07-09 22:40:43 +03:00
if ( seg - > list_size & & seg - > segment_count > = seg - > list_size ) {
2012-12-20 16:20:19 +03:00
entry = seg - > segment_list_entries ;
seg - > segment_list_entries = seg - > segment_list_entries - > next ;
2014-12-25 13:38:20 +02:00
av_freep ( & entry - > filename ) ;
2012-12-20 16:20:19 +03:00
av_freep ( & entry ) ;
}
2012-08-16 00:21:41 +03:00
if ( ( ret = segment_list_open ( s ) ) < 0 )
2012-01-14 02:16:55 +03:00
goto end ;
2012-12-20 16:20:19 +03:00
for ( entry = seg - > segment_list_entries ; entry ; entry = entry - > next )
2013-02-21 21:33:26 +03:00
segment_list_print_entry ( seg - > list_pb , seg - > list_type , entry , s ) ;
2013-01-23 02:23:47 +03:00
if ( seg - > list_type = = LIST_TYPE_M3U8 & & is_last )
2012-12-20 16:20:19 +03:00
avio_printf ( seg - > list_pb , " #EXT-X-ENDLIST \n " ) ;
2016-02-10 16:40:32 +02:00
ff_format_io_close ( s , & seg - > list_pb ) ;
2015-08-27 23:40:25 +02:00
if ( seg - > use_rename )
ff_rename ( seg - > temp_list_filename , seg - > list , s ) ;
2012-12-20 16:20:19 +03:00
} else {
2013-02-21 21:33:26 +03:00
segment_list_print_entry ( seg - > list_pb , seg - > list_type , & seg - > cur_entry , s ) ;
2015-03-29 03:25:18 +02:00
avio_flush ( seg - > list_pb ) ;
2012-01-14 02:16:55 +03:00
}
}
2013-10-15 13:53:40 +03:00
av_log ( s , AV_LOG_VERBOSE , " segment:'%s' count:%d ended \n " ,
2017-12-30 00:30:14 +02:00
seg - > avf - > url , seg - > segment_count ) ;
2013-10-15 13:53:40 +03:00
seg - > segment_count + + ;
2016-02-28 22:36:42 +02:00
if ( seg - > increment_tc ) {
tcr = av_dict_get ( s - > metadata , " timecode " , NULL , 0 ) ;
if ( tcr ) {
/* search the first video stream */
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
2016-04-10 21:58:15 +02:00
if ( s - > streams [ i ] - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO ) {
2016-02-28 22:36:42 +02:00
rate = s - > streams [ i ] - > avg_frame_rate ; /* Get fps from the video stream */
err = av_timecode_init_from_string ( & tc , rate , tcr - > value , s ) ;
if ( err < 0 ) {
2016-03-17 13:17:44 +02:00
av_log ( s , AV_LOG_WARNING , " Could not increment timecode, error occurred during timecode creation. " ) ;
2016-02-28 22:36:42 +02:00
break ;
}
tc . start + = ( int ) ( ( seg - > cur_entry . end_time - seg - > cur_entry . start_time ) * av_q2d ( rate ) ) ; /* increment timecode */
av_dict_set ( & s - > metadata , " timecode " ,
av_timecode_make_string ( & tc , buf , 0 ) , 0 ) ;
break ;
}
}
} else {
av_log ( s , AV_LOG_WARNING , " Could not increment timecode, no timecode metadata found " ) ;
}
}
2012-01-14 02:16:55 +03:00
end :
2016-01-16 18:53:43 +02:00
ff_format_io_close ( oc , & oc - > pb ) ;
2011-10-03 00:05:29 +03:00
return ret ;
}
2012-01-29 00:36:38 +03:00
static int parse_times ( void * log_ctx , int64_t * * times , int * nb_times ,
const char * times_str )
{
char * p ;
int i , ret = 0 ;
char * times_str1 = av_strdup ( times_str ) ;
char * saveptr = NULL ;
if ( ! times_str1 )
return AVERROR ( ENOMEM ) ;
# define FAIL(err) ret = err; goto end
* nb_times = 1 ;
for ( p = times_str1 ; * p ; p + + )
if ( * p = = ' , ' )
( * nb_times ) + + ;
2014-05-06 00:11:04 +03:00
* times = av_malloc_array ( * nb_times , sizeof ( * * times ) ) ;
2012-01-29 00:36:38 +03:00
if ( ! * times ) {
av_log ( log_ctx , AV_LOG_ERROR , " Could not allocate forced times array \n " ) ;
FAIL ( AVERROR ( ENOMEM ) ) ;
}
p = times_str1 ;
for ( i = 0 ; i < * nb_times ; i + + ) {
int64_t t ;
char * tstr = av_strtok ( p , " , " , & saveptr ) ;
p = NULL ;
2012-12-09 22:39:27 +03:00
if ( ! tstr | | ! tstr [ 0 ] ) {
av_log ( log_ctx , AV_LOG_ERROR , " Empty time specification in times list %s \n " ,
times_str ) ;
FAIL ( AVERROR ( EINVAL ) ) ;
}
2012-01-29 00:36:38 +03:00
ret = av_parse_time ( & t , tstr , 1 ) ;
if ( ret < 0 ) {
av_log ( log_ctx , AV_LOG_ERROR ,
2012-12-09 22:39:27 +03:00
" Invalid time duration specification '%s' in times list %s \n " , tstr , times_str ) ;
2012-01-29 00:36:38 +03:00
FAIL ( AVERROR ( EINVAL ) ) ;
}
( * times ) [ i ] = t ;
/* check on monotonicity */
if ( i & & ( * times ) [ i - 1 ] > ( * times ) [ i ] ) {
av_log ( log_ctx , AV_LOG_ERROR ,
" Specified time %f is greater than the following time %f \n " ,
( float ) ( ( * times ) [ i ] ) / 1000000 , ( float ) ( ( * times ) [ i - 1 ] ) / 1000000 ) ;
FAIL ( AVERROR ( EINVAL ) ) ;
}
}
end :
av_free ( times_str1 ) ;
return ret ;
}
2012-12-09 22:26:30 +03:00
static int parse_frames ( void * log_ctx , int * * frames , int * nb_frames ,
const char * frames_str )
{
char * p ;
int i , ret = 0 ;
char * frames_str1 = av_strdup ( frames_str ) ;
char * saveptr = NULL ;
if ( ! frames_str1 )
return AVERROR ( ENOMEM ) ;
# define FAIL(err) ret = err; goto end
* nb_frames = 1 ;
for ( p = frames_str1 ; * p ; p + + )
if ( * p = = ' , ' )
( * nb_frames ) + + ;
2014-05-06 00:11:04 +03:00
* frames = av_malloc_array ( * nb_frames , sizeof ( * * frames ) ) ;
2012-12-09 22:26:30 +03:00
if ( ! * frames ) {
av_log ( log_ctx , AV_LOG_ERROR , " Could not allocate forced frames array \n " ) ;
FAIL ( AVERROR ( ENOMEM ) ) ;
}
p = frames_str1 ;
for ( i = 0 ; i < * nb_frames ; i + + ) {
long int f ;
char * tailptr ;
char * fstr = av_strtok ( p , " , " , & saveptr ) ;
p = NULL ;
if ( ! fstr ) {
av_log ( log_ctx , AV_LOG_ERROR , " Empty frame specification in frame list %s \n " ,
frames_str ) ;
FAIL ( AVERROR ( EINVAL ) ) ;
}
f = strtol ( fstr , & tailptr , 10 ) ;
if ( * tailptr | | f < = 0 | | f > = INT_MAX ) {
av_log ( log_ctx , AV_LOG_ERROR ,
" Invalid argument '%s', must be a positive integer <= INT64_MAX \n " ,
fstr ) ;
FAIL ( AVERROR ( EINVAL ) ) ;
}
( * frames ) [ i ] = f ;
/* check on monotonicity */
if ( i & & ( * frames ) [ i - 1 ] > ( * frames ) [ i ] ) {
av_log ( log_ctx , AV_LOG_ERROR ,
" Specified frame %d is greater than the following frame %d \n " ,
( * frames ) [ i ] , ( * frames ) [ i - 1 ] ) ;
FAIL ( AVERROR ( EINVAL ) ) ;
}
}
end :
av_free ( frames_str1 ) ;
return ret ;
}
2012-10-03 00:59:35 +03:00
static int open_null_ctx ( AVIOContext * * ctx )
{
int buf_size = 32768 ;
uint8_t * buf = av_malloc ( buf_size ) ;
if ( ! buf )
return AVERROR ( ENOMEM ) ;
* ctx = avio_alloc_context ( buf , buf_size , AVIO_FLAG_WRITE , NULL , NULL , NULL , NULL ) ;
if ( ! * ctx ) {
av_free ( buf ) ;
return AVERROR ( ENOMEM ) ;
}
return 0 ;
}
2014-12-25 13:38:20 +02:00
static void close_null_ctxp ( AVIOContext * * pb )
2012-10-03 00:59:35 +03:00
{
2014-12-25 13:38:20 +02:00
av_freep ( & ( * pb ) - > buffer ) ;
2017-09-01 07:16:33 +02:00
avio_context_free ( pb ) ;
2012-10-03 00:59:35 +03:00
}
2013-01-16 21:52:58 +03:00
static int select_reference_stream ( AVFormatContext * s )
{
SegmentContext * seg = s - > priv_data ;
int ret , i ;
seg - > reference_stream_index = - 1 ;
if ( ! strcmp ( seg - > reference_stream_specifier , " auto " ) ) {
/* select first index of type with highest priority */
int type_index_map [ AVMEDIA_TYPE_NB ] ;
static const enum AVMediaType type_priority_list [ ] = {
AVMEDIA_TYPE_VIDEO ,
AVMEDIA_TYPE_AUDIO ,
AVMEDIA_TYPE_SUBTITLE ,
AVMEDIA_TYPE_DATA ,
AVMEDIA_TYPE_ATTACHMENT
} ;
enum AVMediaType type ;
for ( i = 0 ; i < AVMEDIA_TYPE_NB ; i + + )
type_index_map [ i ] = - 1 ;
/* select first index for each type */
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
2016-04-10 21:58:15 +02:00
type = s - > streams [ i ] - > codecpar - > codec_type ;
2013-01-16 22:10:12 +03:00
if ( ( unsigned ) type < AVMEDIA_TYPE_NB & & type_index_map [ type ] = = - 1
/* ignore attached pictures/cover art streams */
& & ! ( s - > streams [ i ] - > disposition & AV_DISPOSITION_ATTACHED_PIC ) )
2013-01-16 21:52:58 +03:00
type_index_map [ type ] = i ;
}
for ( i = 0 ; i < FF_ARRAY_ELEMS ( type_priority_list ) ; i + + ) {
type = type_priority_list [ i ] ;
if ( ( seg - > reference_stream_index = type_index_map [ type ] ) > = 0 )
break ;
}
} else {
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
ret = avformat_match_stream_specifier ( s , s - > streams [ i ] ,
seg - > reference_stream_specifier ) ;
if ( ret < 0 )
2013-01-23 20:50:21 +03:00
return ret ;
2013-01-16 21:52:58 +03:00
if ( ret > 0 ) {
seg - > reference_stream_index = i ;
break ;
}
}
}
if ( seg - > reference_stream_index < 0 ) {
av_log ( s , AV_LOG_ERROR , " Could not select stream matching identifier '%s' \n " ,
seg - > reference_stream_specifier ) ;
return AVERROR ( EINVAL ) ;
}
return 0 ;
}
2016-04-08 02:18:19 +02:00
static void seg_free ( AVFormatContext * s )
2015-01-05 11:40:41 +02:00
{
2016-04-08 02:18:19 +02:00
SegmentContext * seg = s - > priv_data ;
2016-02-10 16:40:32 +02:00
ff_format_io_close ( seg - > avf , & seg - > list_pb ) ;
2015-01-05 11:40:41 +02:00
avformat_free_context ( seg - > avf ) ;
seg - > avf = NULL ;
}
2016-03-23 16:24:22 +02:00
static int seg_init ( AVFormatContext * s )
2011-10-03 00:05:29 +03:00
{
SegmentContext * seg = s - > priv_data ;
2016-03-23 16:24:22 +02:00
AVFormatContext * oc = seg - > avf ;
2014-09-06 16:43:11 +03:00
AVDictionary * options = NULL ;
2013-01-16 21:52:58 +03:00
int ret ;
2014-11-16 03:49:12 +02:00
int i ;
2011-10-03 00:05:29 +03:00
2012-08-15 23:12:11 +03:00
seg - > segment_count = 0 ;
2012-10-03 00:59:35 +03:00
if ( ! seg - > write_header_trailer )
seg - > individual_header_trailer = 0 ;
2012-07-08 18:26:33 +03:00
2015-03-29 03:25:20 +02:00
if ( seg - > header_filename ) {
seg - > write_header_trailer = 1 ;
seg - > individual_header_trailer = 0 ;
}
2016-09-08 15:56:44 +02:00
if ( seg - > initial_offset > 0 ) {
av_log ( s , AV_LOG_WARNING , " NOTE: the option initial_offset is deprecated, "
" you can use output_ts_offset instead of it \n " ) ;
}
2012-12-09 22:26:30 +03:00
if ( ! ! seg - > time_str + ! ! seg - > times_str + ! ! seg - > frames_str > 1 ) {
2012-07-08 18:26:33 +03:00
av_log ( s , AV_LOG_ERROR ,
2012-12-09 22:26:30 +03:00
" segment_time, segment_times, and segment_frames options "
" are mutually exclusive, select just one of them \n " ) ;
2012-01-29 00:36:38 +03:00
return AVERROR ( EINVAL ) ;
}
if ( seg - > times_str ) {
if ( ( ret = parse_times ( s , & seg - > times , & seg - > nb_times , seg - > times_str ) ) < 0 )
return ret ;
2012-12-09 22:26:30 +03:00
} else if ( seg - > frames_str ) {
if ( ( ret = parse_frames ( s , & seg - > frames , & seg - > nb_frames , seg - > frames_str ) ) < 0 )
return ret ;
2012-01-29 00:36:38 +03:00
} else {
/* set default value if not specified */
if ( ! seg - > time_str )
seg - > time_str = av_strdup ( " 2 " ) ;
if ( ( ret = av_parse_time ( & seg - > time , seg - > time_str , 1 ) ) < 0 ) {
av_log ( s , AV_LOG_ERROR ,
" Invalid time duration specification '%s' for segment_time option \n " ,
seg - > time_str ) ;
return ret ;
}
2016-01-13 02:44:36 +02:00
if ( seg - > use_clocktime ) {
if ( seg - > time < = 0 ) {
av_log ( s , AV_LOG_ERROR , " Invalid negative segment_time with segment_atclocktime option set \n " ) ;
return AVERROR ( EINVAL ) ;
}
seg - > clocktime_offset = seg - > time - ( seg - > clocktime_offset % seg - > time ) ;
}
2012-07-08 18:26:33 +03:00
}
2011-10-03 00:05:29 +03:00
2014-09-06 16:43:11 +03:00
if ( seg - > format_options_str ) {
ret = av_dict_parse_string ( & seg - > format_options , seg - > format_options_str , " = " , " : " , 0 ) ;
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , " Could not parse format options list '%s' \n " ,
seg - > format_options_str ) ;
2016-04-08 02:18:19 +02:00
return ret ;
2014-09-06 16:43:11 +03:00
}
}
2012-08-16 01:16:28 +03:00
if ( seg - > list ) {
if ( seg - > list_type = = LIST_TYPE_UNDEFINED ) {
2012-08-16 19:13:17 +03:00
if ( av_match_ext ( seg - > list , " csv " ) ) seg - > list_type = LIST_TYPE_CSV ;
else if ( av_match_ext ( seg - > list , " ext " ) ) seg - > list_type = LIST_TYPE_EXT ;
2012-08-16 01:16:28 +03:00
else if ( av_match_ext ( seg - > list , " m3u8 " ) ) seg - > list_type = LIST_TYPE_M3U8 ;
2013-02-21 21:33:26 +03:00
else if ( av_match_ext ( seg - > list , " ffcat,ffconcat " ) ) seg - > list_type = LIST_TYPE_FFCONCAT ;
2012-08-16 01:16:28 +03:00
else seg - > list_type = LIST_TYPE_FLAT ;
}
2015-08-27 23:40:25 +02:00
if ( ! seg - > list_size & & seg - > list_type ! = LIST_TYPE_M3U8 ) {
2015-03-29 03:25:18 +02:00
if ( ( ret = segment_list_open ( s ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2015-08-27 23:40:25 +02:00
} else {
2016-10-06 09:00:25 +02:00
const char * proto = avio_find_protocol_name ( seg - > list ) ;
2015-08-27 23:40:25 +02:00
seg - > use_rename = proto & & ! strcmp ( proto , " file " ) ;
}
2012-08-16 01:16:28 +03:00
}
2016-03-23 16:24:22 +02:00
2012-08-16 19:13:17 +03:00
if ( seg - > list_type = = LIST_TYPE_EXT )
av_log ( s , AV_LOG_WARNING , " 'ext' list type option is deprecated in favor of 'csv' \n " ) ;
2011-10-03 00:05:29 +03:00
2013-01-16 21:52:58 +03:00
if ( ( ret = select_reference_stream ( s ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2012-12-22 23:49:06 +03:00
av_log ( s , AV_LOG_VERBOSE , " Selected stream id:%d type:%s \n " ,
seg - > reference_stream_index ,
2016-04-10 21:58:15 +02:00
av_get_media_type_string ( s - > streams [ seg - > reference_stream_index ] - > codecpar - > codec_type ) ) ;
2011-10-03 00:05:29 +03:00
2017-12-30 00:30:14 +02:00
seg - > oformat = av_guess_format ( seg - > format , s - > url , NULL ) ;
2011-10-03 00:05:29 +03:00
2016-04-08 02:18:19 +02:00
if ( ! seg - > oformat )
return AVERROR_MUXER_NOT_FOUND ;
2012-10-04 00:15:35 +03:00
if ( seg - > oformat - > flags & AVFMT_NOFILE ) {
2011-10-03 00:05:29 +03:00
av_log ( s , AV_LOG_ERROR , " format %s not supported. \n " ,
2012-11-05 22:25:25 +03:00
seg - > oformat - > name ) ;
2016-04-08 02:18:19 +02:00
return AVERROR ( EINVAL ) ;
2011-10-03 00:05:29 +03:00
}
2012-10-04 00:15:35 +03:00
if ( ( ret = segment_mux_init ( s ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2011-10-03 00:05:29 +03:00
2012-12-13 13:37:50 +03:00
if ( ( ret = set_segment_filename ( s ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2016-03-23 16:24:22 +02:00
oc = seg - > avf ;
2011-10-03 00:05:29 +03:00
2012-10-03 00:59:35 +03:00
if ( seg - > write_header_trailer ) {
2016-02-10 18:59:58 +02:00
if ( ( ret = s - > io_open ( s , & oc - > pb ,
2017-12-30 00:30:14 +02:00
seg - > header_filename ? seg - > header_filename : oc - > url ,
2016-02-10 18:59:58 +02:00
AVIO_FLAG_WRITE , NULL ) ) < 0 ) {
2017-12-30 00:30:14 +02:00
av_log ( s , AV_LOG_ERROR , " Failed to open segment '%s' \n " , oc - > url ) ;
2016-04-08 02:18:19 +02:00
return ret ;
2013-11-25 21:20:11 +03:00
}
2015-03-29 18:57:09 +02:00
if ( ! seg - > individual_header_trailer )
oc - > pb - > seekable = 0 ;
2012-10-03 00:59:35 +03:00
} else {
if ( ( ret = open_null_ctx ( & oc - > pb ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2012-10-03 00:59:35 +03:00
}
2011-10-03 00:05:29 +03:00
2014-09-06 16:43:11 +03:00
av_dict_copy ( & options , seg - > format_options , 0 ) ;
2016-04-08 02:18:45 +02:00
av_dict_set ( & options , " fflags " , " -autobsf " , 0 ) ;
ret = avformat_init_output ( oc , & options ) ;
2014-09-06 16:43:11 +03:00
if ( av_dict_count ( options ) ) {
av_log ( s , AV_LOG_ERROR ,
" Some of the provided format options in '%s' are not recognized \n " , seg - > format_options_str ) ;
2016-04-08 02:18:19 +02:00
av_dict_free ( & options ) ;
return AVERROR ( EINVAL ) ;
2014-09-06 16:43:11 +03:00
}
2016-04-08 02:18:19 +02:00
av_dict_free ( & options ) ;
2014-09-08 13:47:37 +03:00
2014-09-06 16:43:11 +03:00
if ( ret < 0 ) {
2016-01-16 18:53:43 +02:00
ff_format_io_close ( oc , & oc - > pb ) ;
2016-04-08 02:18:19 +02:00
return ret ;
2011-10-03 00:05:29 +03:00
}
2014-07-17 21:28:40 +03:00
seg - > segment_frame_count = 0 ;
2011-10-03 00:05:29 +03:00
2014-11-16 03:49:12 +02:00
av_assert0 ( s - > nb_streams = = oc - > nb_streams ) ;
2016-04-08 02:18:45 +02:00
if ( ret = = AVSTREAM_INIT_IN_WRITE_HEADER ) {
ret = avformat_write_header ( oc , NULL ) ;
if ( ret < 0 )
return ret ;
seg - > header_written = 1 ;
}
2014-11-16 03:49:12 +02:00
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
AVStream * inner_st = oc - > streams [ i ] ;
AVStream * outer_st = s - > streams [ i ] ;
avpriv_set_pts_info ( outer_st , inner_st - > pts_wrap_bits , inner_st - > time_base . num , inner_st - > time_base . den ) ;
}
2012-10-06 13:07:26 +03:00
if ( oc - > avoid_negative_ts > 0 & & s - > avoid_negative_ts < 0 )
s - > avoid_negative_ts = 1 ;
2016-04-08 02:18:45 +02:00
return ret ;
}
static int seg_write_header ( AVFormatContext * s )
{
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
2016-10-27 05:03:02 +02:00
int ret , i ;
2016-04-08 02:18:45 +02:00
if ( ! seg - > header_written ) {
2016-10-27 05:03:02 +02:00
for ( i = 0 ; i < s - > nb_streams ; i + + ) {
AVStream * st = oc - > streams [ i ] ;
AVCodecParameters * ipar , * opar ;
ipar = s - > streams [ i ] - > codecpar ;
opar = oc - > streams [ i ] - > codecpar ;
avcodec_parameters_copy ( opar , ipar ) ;
if ( ! oc - > oformat - > codec_tag | |
av_codec_get_id ( oc - > oformat - > codec_tag , ipar - > codec_tag ) = = opar - > codec_id | |
av_codec_get_tag ( oc - > oformat - > codec_tag , ipar - > codec_id ) < = 0 ) {
opar - > codec_tag = ipar - > codec_tag ;
} else {
opar - > codec_tag = 0 ;
}
st - > sample_aspect_ratio = s - > streams [ i ] - > sample_aspect_ratio ;
st - > time_base = s - > streams [ i ] - > time_base ;
}
2016-04-08 02:18:45 +02:00
ret = avformat_write_header ( oc , NULL ) ;
if ( ret < 0 )
return ret ;
}
2015-03-29 03:25:20 +02:00
if ( ! seg - > write_header_trailer | | seg - > header_filename ) {
if ( seg - > header_filename ) {
av_write_frame ( oc , NULL ) ;
2016-02-10 16:40:32 +02:00
ff_format_io_close ( oc , & oc - > pb ) ;
2015-03-29 03:25:20 +02:00
} else {
close_null_ctxp ( & oc - > pb ) ;
}
2017-12-30 00:30:14 +02:00
if ( ( ret = oc - > io_open ( oc , & oc - > pb , oc - > url , AVIO_FLAG_WRITE , NULL ) ) < 0 )
2016-04-08 02:18:19 +02:00
return ret ;
2015-03-29 18:57:09 +02:00
if ( ! seg - > individual_header_trailer )
oc - > pb - > seekable = 0 ;
2012-10-03 00:59:35 +03:00
}
2016-04-08 02:18:19 +02:00
return 0 ;
2011-10-03 00:05:29 +03:00
}
static int seg_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
SegmentContext * seg = s - > priv_data ;
2012-02-26 02:39:32 +03:00
AVStream * st = s - > streams [ pkt - > stream_index ] ;
2013-07-05 15:28:38 +03:00
int64_t end_pts = INT64_MAX , offset ;
2012-12-09 22:26:30 +03:00
int start_frame = INT_MAX ;
2011-10-03 00:05:29 +03:00
int ret ;
2014-07-07 22:35:43 +03:00
struct tm ti ;
int64_t usecs ;
int64_t wrapped_val ;
2011-10-03 00:05:29 +03:00
2017-01-21 04:15:03 +02:00
if ( ! seg - > avf | | ! seg - > avf - > pb )
2015-01-05 11:40:41 +02:00
return AVERROR ( EINVAL ) ;
2016-03-07 04:48:57 +02:00
calc_times :
2012-01-29 00:36:38 +03:00
if ( seg - > times ) {
2013-10-15 15:54:25 +03:00
end_pts = seg - > segment_count < seg - > nb_times ?
seg - > times [ seg - > segment_count ] : INT64_MAX ;
2012-12-09 22:26:30 +03:00
} else if ( seg - > frames ) {
2014-09-01 20:10:03 +03:00
start_frame = seg - > segment_count < seg - > nb_frames ?
2013-10-15 15:54:25 +03:00
seg - > frames [ seg - > segment_count ] : INT_MAX ;
2012-01-29 00:36:38 +03:00
} else {
2014-07-07 22:35:43 +03:00
if ( seg - > use_clocktime ) {
2014-07-08 23:51:16 +03:00
int64_t avgt = av_gettime ( ) ;
time_t sec = avgt / 1000000 ;
localtime_r ( & sec , & ti ) ;
2015-03-31 04:23:19 +02:00
usecs = ( int64_t ) ( ti . tm_hour * 3600 + ti . tm_min * 60 + ti . tm_sec ) * 1000000 + ( avgt % 1000000 ) ;
2016-01-13 02:44:36 +02:00
wrapped_val = ( usecs + seg - > clocktime_offset ) % seg - > time ;
2017-01-26 04:04:57 +02:00
if ( wrapped_val < seg - > last_val & & wrapped_val < seg - > clocktime_wrap_duration )
2014-07-07 22:35:43 +03:00
seg - > cut_pending = 1 ;
seg - > last_val = wrapped_val ;
} else {
2015-03-31 04:23:19 +02:00
end_pts = seg - > time * ( seg - > segment_count + 1 ) ;
2014-07-07 22:35:43 +03:00
}
2012-01-29 00:36:38 +03:00
}
2015-08-18 02:45:35 +02:00
ff_dlog ( s , " packet stream:%d pts:%s pts_time:%s duration_time:%s is_key:%d frame:%d \n " ,
2014-07-17 17:36:11 +03:00
pkt - > stream_index , av_ts2str ( pkt - > pts ) , av_ts2timestr ( pkt - > pts , & st - > time_base ) ,
av_ts2timestr ( pkt - > duration , & st - > time_base ) ,
pkt - > flags & AV_PKT_FLAG_KEY ,
pkt - > stream_index = = seg - > reference_stream_index ? seg - > frame_count : - 1 ) ;
2012-12-09 22:26:30 +03:00
2012-12-22 23:49:06 +03:00
if ( pkt - > stream_index = = seg - > reference_stream_index & &
2015-06-09 09:32:18 +02:00
( pkt - > flags & AV_PKT_FLAG_KEY | | seg - > break_non_keyframes ) & &
2016-03-07 04:48:57 +02:00
( seg - > segment_frame_count > 0 | | seg - > write_empty ) & &
2014-07-07 22:35:43 +03:00
( seg - > cut_pending | | seg - > frame_count > = start_frame | |
2012-12-09 22:26:30 +03:00
( pkt - > pts ! = AV_NOPTS_VALUE & &
av_compare_ts ( pkt - > pts , st - > time_base ,
2016-03-07 03:38:39 +02:00
end_pts - seg - > time_delta , AV_TIME_BASE_Q ) > = 0 ) ) ) {
2014-07-17 21:37:55 +03:00
/* sanitize end time in case last packet didn't have a defined duration */
if ( seg - > cur_entry . last_duration = = 0 )
seg - > cur_entry . end_time = ( double ) pkt - > pts * av_q2d ( st - > time_base ) ;
2013-10-15 16:17:22 +03:00
if ( ( ret = segment_end ( s , seg - > individual_header_trailer , 0 ) ) < 0 )
goto fail ;
2011-10-03 00:05:29 +03:00
2013-10-15 16:17:22 +03:00
if ( ( ret = segment_start ( s , seg - > individual_header_trailer ) ) < 0 )
2011-10-03 00:05:29 +03:00
goto fail ;
2014-07-07 22:35:43 +03:00
seg - > cut_pending = 0 ;
2015-03-31 04:23:19 +02:00
seg - > cur_entry . index = seg - > segment_idx + seg - > segment_idx_wrap * seg - > segment_idx_wrap_nb ;
2012-12-11 02:14:22 +03:00
seg - > cur_entry . start_time = ( double ) pkt - > pts * av_q2d ( st - > time_base ) ;
seg - > cur_entry . start_pts = av_rescale_q ( pkt - > pts , st - > time_base , AV_TIME_BASE_Q ) ;
2016-03-07 04:43:37 +02:00
seg - > cur_entry . end_time = seg - > cur_entry . start_time ;
2016-03-07 04:48:57 +02:00
if ( seg - > times | | ( ! seg - > frames & & ! seg - > use_clocktime ) & & seg - > write_empty )
goto calc_times ;
2016-03-07 04:43:37 +02:00
}
if ( pkt - > stream_index = = seg - > reference_stream_index ) {
if ( pkt - > pts ! = AV_NOPTS_VALUE )
seg - > cur_entry . end_time =
FFMAX ( seg - > cur_entry . end_time , ( double ) ( pkt - > pts + pkt - > duration ) * av_q2d ( st - > time_base ) ) ;
2014-07-17 21:37:55 +03:00
seg - > cur_entry . last_duration = pkt - > duration ;
2011-10-03 00:05:29 +03:00
}
2014-07-17 21:28:40 +03:00
if ( seg - > segment_frame_count = = 0 ) {
2013-12-17 12:43:32 +03:00
av_log ( s , AV_LOG_VERBOSE , " segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d \n " ,
2017-12-30 00:30:14 +02:00
seg - > avf - > url , pkt - > stream_index ,
2012-12-09 22:26:30 +03:00
av_ts2str ( pkt - > pts ) , av_ts2timestr ( pkt - > pts , & st - > time_base ) , seg - > frame_count ) ;
2012-12-13 23:19:25 +03:00
}
2013-07-05 15:31:29 +03:00
av_log ( s , AV_LOG_DEBUG , " stream:%d start_pts_time:%s pts:%s pts_time:%s dts:%s dts_time:%s " ,
pkt - > stream_index ,
av_ts2timestr ( seg - > cur_entry . start_pts , & AV_TIME_BASE_Q ) ,
av_ts2str ( pkt - > pts ) , av_ts2timestr ( pkt - > pts , & st - > time_base ) ,
av_ts2str ( pkt - > dts ) , av_ts2timestr ( pkt - > dts , & st - > time_base ) ) ;
/* compute new timestamps */
offset = av_rescale_q ( seg - > initial_offset - ( seg - > reset_timestamps ? seg - > cur_entry . start_pts : 0 ) ,
AV_TIME_BASE_Q , st - > time_base ) ;
if ( pkt - > pts ! = AV_NOPTS_VALUE )
pkt - > pts + = offset ;
if ( pkt - > dts ! = AV_NOPTS_VALUE )
pkt - > dts + = offset ;
av_log ( s , AV_LOG_DEBUG , " -> pts:%s pts_time:%s dts:%s dts_time:%s \n " ,
av_ts2str ( pkt - > pts ) , av_ts2timestr ( pkt - > pts , & st - > time_base ) ,
av_ts2str ( pkt - > dts ) , av_ts2timestr ( pkt - > dts , & st - > time_base ) ) ;
2012-11-29 15:45:50 +03:00
2014-07-25 00:22:33 +03:00
ret = ff_write_chained ( seg - > avf , pkt - > stream_index , pkt , s , seg - > initial_offset | | seg - > reset_timestamps ) ;
2011-10-03 00:05:29 +03:00
fail :
2014-07-17 21:28:40 +03:00
if ( pkt - > stream_index = = seg - > reference_stream_index ) {
2012-12-09 22:26:30 +03:00
seg - > frame_count + + ;
2014-07-17 21:28:40 +03:00
seg - > segment_frame_count + + ;
}
2012-12-09 22:26:30 +03:00
2011-10-03 00:05:29 +03:00
return ret ;
}
static int seg_write_trailer ( struct AVFormatContext * s )
{
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
2012-12-20 16:20:19 +03:00
SegmentListEntry * cur , * next ;
2015-01-05 11:40:41 +02:00
int ret = 0 ;
if ( ! oc )
goto fail ;
2012-12-20 16:20:19 +03:00
2012-10-03 00:59:35 +03:00
if ( ! seg - > write_header_trailer ) {
2013-01-23 02:23:47 +03:00
if ( ( ret = segment_end ( s , 0 , 1 ) ) < 0 )
2012-10-09 03:49:42 +03:00
goto fail ;
2015-06-12 14:39:16 +02:00
if ( ( ret = open_null_ctx ( & oc - > pb ) ) < 0 )
goto fail ;
2012-10-09 03:49:42 +03:00
ret = av_write_trailer ( oc ) ;
2014-12-25 13:38:20 +02:00
close_null_ctxp ( & oc - > pb ) ;
2012-10-03 00:59:35 +03:00
} else {
2013-01-23 02:23:47 +03:00
ret = segment_end ( s , 1 , 1 ) ;
2012-10-03 00:59:35 +03:00
}
2012-10-11 15:28:18 +03:00
fail :
2011-10-03 00:05:29 +03:00
if ( seg - > list )
2016-02-10 16:40:32 +02:00
ff_format_io_close ( s , & seg - > list_pb ) ;
2012-07-08 18:26:33 +03:00
2014-09-06 16:43:11 +03:00
av_dict_free ( & seg - > format_options ) ;
2012-07-08 18:26:33 +03:00
av_opt_free ( seg ) ;
2012-01-29 00:36:38 +03:00
av_freep ( & seg - > times ) ;
2012-12-09 22:26:30 +03:00
av_freep ( & seg - > frames ) ;
2015-08-25 13:36:24 +02:00
av_freep ( & seg - > cur_entry . filename ) ;
2012-07-08 18:26:33 +03:00
2012-12-20 16:20:19 +03:00
cur = seg - > segment_list_entries ;
while ( cur ) {
next = cur - > next ;
2014-12-25 13:38:20 +02:00
av_freep ( & cur - > filename ) ;
2012-12-20 16:20:19 +03:00
av_free ( cur ) ;
cur = next ;
}
2011-10-03 00:05:29 +03:00
avformat_free_context ( oc ) ;
2015-01-07 23:29:16 +02:00
seg - > avf = NULL ;
2011-10-03 00:05:29 +03:00
return ret ;
}
2016-10-27 05:03:02 +02:00
static int seg_check_bitstream ( struct AVFormatContext * s , const AVPacket * pkt )
{
SegmentContext * seg = s - > priv_data ;
AVFormatContext * oc = seg - > avf ;
if ( oc - > oformat - > check_bitstream ) {
int ret = oc - > oformat - > check_bitstream ( oc , pkt ) ;
if ( ret = = 1 ) {
AVStream * st = s - > streams [ pkt - > stream_index ] ;
AVStream * ost = oc - > streams [ pkt - > stream_index ] ;
st - > internal - > bsfcs = ost - > internal - > bsfcs ;
st - > internal - > nb_bsfcs = ost - > internal - > nb_bsfcs ;
ost - > internal - > bsfcs = NULL ;
ost - > internal - > nb_bsfcs = 0 ;
}
return ret ;
}
return 1 ;
}
2011-10-03 00:05:29 +03:00
# define OFFSET(x) offsetof(SegmentContext, x)
# define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [ ] = {
2012-12-22 23:49:06 +03:00
{ " reference_stream " , " set reference stream " , OFFSET ( reference_stream_specifier ) , AV_OPT_TYPE_STRING , { . str = " auto " } , CHAR_MIN , CHAR_MAX , E } ,
2012-01-14 13:00:16 +03:00
{ " segment_format " , " set container format used for the segments " , OFFSET ( format ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2014-09-06 16:43:11 +03:00
{ " segment_format_options " , " set list of options for the container format used for the segments " , OFFSET ( format_options_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-01-14 13:00:16 +03:00
{ " segment_list " , " set the segment list filename " , OFFSET ( list ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2015-03-29 03:25:20 +02:00
{ " segment_header_filename " , " write a single file containing the header " , OFFSET ( header_filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-09-01 19:01:51 +03:00
{ " segment_list_flags " , " set flags affecting segment list generation " , OFFSET ( list_flags ) , AV_OPT_TYPE_FLAGS , { . i64 = SEGMENT_LIST_FLAG_CACHE } , 0 , UINT_MAX , E , " list_flags " } ,
{ " cache " , " allow list caching " , 0 , AV_OPT_TYPE_CONST , { . i64 = SEGMENT_LIST_FLAG_CACHE } , INT_MIN , INT_MAX , E , " list_flags " } ,
{ " live " , " enable live-friendly list generation (useful for HLS) " , 0 , AV_OPT_TYPE_CONST , { . i64 = SEGMENT_LIST_FLAG_LIVE } , INT_MIN , INT_MAX , E , " list_flags " } ,
2012-09-05 15:26:01 +03:00
{ " segment_list_size " , " set the maximum number of playlist entries " , OFFSET ( list_size ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2012-12-14 13:44:31 +03:00
2012-09-05 15:26:01 +03:00
{ " segment_list_type " , " set the segment list type " , OFFSET ( list_type ) , AV_OPT_TYPE_INT , { . i64 = LIST_TYPE_UNDEFINED } , - 1 , LIST_TYPE_NB - 1 , E , " list_type " } ,
2012-12-14 13:44:31 +03:00
{ " flat " , " flat format " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_FLAT } , INT_MIN , INT_MAX , E , " list_type " } ,
{ " csv " , " csv format " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_CSV } , INT_MIN , INT_MAX , E , " list_type " } ,
{ " ext " , " extended format " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_EXT } , INT_MIN , INT_MAX , E , " list_type " } ,
2013-02-21 21:33:26 +03:00
{ " ffconcat " , " ffconcat format " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_FFCONCAT } , INT_MIN , INT_MAX , E , " list_type " } ,
2012-12-14 13:44:31 +03:00
{ " m3u8 " , " M3U8 format " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_M3U8 } , INT_MIN , INT_MAX , E , " list_type " } ,
{ " hls " , " Apple HTTP Live Streaming compatible " , 0 , AV_OPT_TYPE_CONST , { . i64 = LIST_TYPE_M3U8 } , INT_MIN , INT_MAX , E , " list_type " } ,
2015-11-21 23:05:07 +02:00
{ " segment_atclocktime " , " set segment to be cut at clocktime " , OFFSET ( use_clocktime ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2016-01-13 02:44:36 +02:00
{ " segment_clocktime_offset " , " set segment clocktime offset " , OFFSET ( clocktime_offset ) , AV_OPT_TYPE_DURATION , { . i64 = 0 } , 0 , 86400000000LL , E } ,
2016-01-18 02:29:06 +02:00
{ " segment_clocktime_wrap_duration " , " set segment clocktime wrapping duration " , OFFSET ( clocktime_wrap_duration ) , AV_OPT_TYPE_DURATION , { . i64 = INT64_MAX } , 0 , INT64_MAX , E } ,
2012-01-29 00:36:38 +03:00
{ " segment_time " , " set segment duration " , OFFSET ( time_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2018-09-30 21:23:55 +02:00
{ " segment_time_delta " , " set approximation value used for the segment times " , OFFSET ( time_delta ) , AV_OPT_TYPE_DURATION , { . i64 = 0 } , 0 , INT64_MAX , E } ,
2012-01-29 00:36:38 +03:00
{ " segment_times " , " set segment split time points " , OFFSET ( times_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-12-09 22:26:30 +03:00
{ " segment_frames " , " set segment split frame numbers " , OFFSET ( frames_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-09-05 15:26:01 +03:00
{ " segment_wrap " , " set number after which the index wraps " , OFFSET ( segment_idx_wrap ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2014-04-29 16:42:04 +03:00
{ " segment_list_entry_prefix " , " set base url prefix for segments " , OFFSET ( entry_prefix ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-12-09 20:21:00 +03:00
{ " segment_start_number " , " set the sequence number of the first segment " , OFFSET ( segment_idx ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2014-02-05 16:59:00 +03:00
{ " segment_wrap_number " , " set the number of wrap before the first segment " , OFFSET ( segment_idx_wrap_nb ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2015-11-21 23:05:07 +02:00
{ " strftime " , " set filename expansion with strftime at segment creation " , OFFSET ( use_strftime ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2016-03-15 18:50:00 +02:00
{ " increment_tc " , " increment timecode between each segment " , OFFSET ( increment_tc ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2015-11-21 23:05:07 +02:00
{ " break_non_keyframes " , " allow breaking segments on non-keyframes " , OFFSET ( break_non_keyframes ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2012-11-29 15:45:50 +03:00
2015-11-21 23:05:07 +02:00
{ " individual_header_trailer " , " write header/trailer to each segment " , OFFSET ( individual_header_trailer ) , AV_OPT_TYPE_BOOL , { . i64 = 1 } , 0 , 1 , E } ,
{ " write_header_trailer " , " write a header to the first segment and a trailer to the last one " , OFFSET ( write_header_trailer ) , AV_OPT_TYPE_BOOL , { . i64 = 1 } , 0 , 1 , E } ,
2017-09-22 01:10:56 +02:00
{ " reset_timestamps " , " reset timestamps at the beginning of each segment " , OFFSET ( reset_timestamps ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2013-07-05 15:28:38 +03:00
{ " initial_offset " , " set initial timestamp offset " , OFFSET ( initial_offset ) , AV_OPT_TYPE_DURATION , { . i64 = 0 } , - INT64_MAX , INT64_MAX , E } ,
2016-03-07 04:48:57 +02:00
{ " write_empty_segments " , " allow writing empty 'filler' segments " , OFFSET ( write_empty ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , E } ,
2011-10-03 00:05:29 +03:00
{ NULL } ,
} ;
2018-02-07 21:42:11 +02:00
# if CONFIG_SEGMENT_MUXER
2011-10-03 00:05:29 +03:00
static const AVClass seg_class = {
. class_name = " segment muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
AVOutputFormat ff_segment_muxer = {
. name = " segment " ,
2012-07-24 04:23:48 +03:00
. long_name = NULL_IF_CONFIG_SMALL ( " segment " ) ,
2011-10-03 00:05:29 +03:00
. priv_data_size = sizeof ( SegmentContext ) ,
2012-11-17 19:12:43 +03:00
. flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER ,
2016-03-23 16:24:22 +02:00
. init = seg_init ,
2016-04-08 02:18:45 +02:00
. write_header = seg_write_header ,
2011-10-03 00:05:29 +03:00
. write_packet = seg_write_packet ,
. write_trailer = seg_write_trailer ,
2016-04-08 02:18:19 +02:00
. deinit = seg_free ,
2016-10-27 05:03:02 +02:00
. check_bitstream = seg_check_bitstream ,
2011-10-03 00:05:29 +03:00
. priv_class = & seg_class ,
} ;
2018-02-07 21:42:11 +02:00
# endif
2012-01-13 17:38:13 +03:00
2018-02-07 21:42:11 +02:00
# if CONFIG_STREAM_SEGMENT_MUXER
2012-01-13 17:38:13 +03:00
static const AVClass sseg_class = {
. class_name = " stream_segment muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
AVOutputFormat ff_stream_segment_muxer = {
. name = " stream_segment,ssegment " ,
. long_name = NULL_IF_CONFIG_SMALL ( " streaming segment muxer " ) ,
. priv_data_size = sizeof ( SegmentContext ) ,
. flags = AVFMT_NOFILE ,
2016-03-23 16:24:22 +02:00
. init = seg_init ,
2016-04-08 02:18:45 +02:00
. write_header = seg_write_header ,
2012-01-13 17:38:13 +03:00
. write_packet = seg_write_packet ,
. write_trailer = seg_write_trailer ,
2016-04-08 02:18:19 +02:00
. deinit = seg_free ,
2016-10-27 05:03:02 +02:00
. check_bitstream = seg_check_bitstream ,
2012-01-13 17:38:13 +03:00
. priv_class = & sseg_class ,
} ;
2018-02-07 21:42:11 +02:00
# endif