2011-10-02 23:05:29 +02:00
/*
* Copyright (c) 2011, Luca Barbato
*
2013-08-15 23:12:51 +02:00
* This file is part of FFmpeg.
2011-10-02 23:05:29 +02:00
*
2013-08-15 23:12:51 +02:00
* FFmpeg is free software; you can redistribute it and/or
2011-10-02 23:05:29 +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.
*
2013-08-15 23:12:51 +02:00
* FFmpeg is distributed in the hope that it will be useful,
2011-10-02 23:05:29 +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
2013-08-15 23:12:51 +02:00
* License along with FFmpeg; if not, write to the Free Software
2011-10-02 23:05:29 +02:00
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
2012-08-15 22:20:16 +02:00
/**
* @file generic segmenter
2012-08-15 11:06:34 +02:00
* M3U8 specification can be find here:
2013-01-22 20:17:00 +01:00
* @url{http://tools.ietf.org/id/draft-pantos-http-live-streaming}
2012-08-15 22:20:16 +02:00
*/
2022-02-23 14:56:49 +02:00
#include "config_components.h"
2014-07-07 21:35:43 +02:00
#include <time.h>
2011-10-02 23:05:29 +02:00
#include "avformat.h"
#include "internal.h"
2022-05-06 14:39:31 +02:00
#include "mux.h"
2011-10-02 23:05:29 +02:00
2012-12-11 00:14:22 +01:00
#include "libavutil/avassert.h"
2015-08-17 20:45:35 -04:00
#include "libavutil/internal.h"
2011-10-02 23:05:29 +02:00
#include "libavutil/log.h"
2024-03-25 01:30:37 +01:00
#include "libavutil/mem.h"
2011-10-02 23:05:29 +02:00
#include "libavutil/opt.h"
#include "libavutil/avstring.h"
2025-08-17 20:39:09 +02:00
#include "libavutil/bprint.h"
2011-10-02 23:05:29 +02:00
#include "libavutil/parseutils.h"
#include "libavutil/mathematics.h"
2014-07-08 22:51:16 +02:00
#include "libavutil/time.h"
2016-02-28 21:36:42 +01:00
#include "libavutil/timecode.h"
2014-11-02 20:07:34 +01:00
#include "libavutil/time_internal.h"
2012-11-29 13:45:50 +01:00
#include "libavutil/timestamp.h"
2011-10-02 23:05:29 +02:00
2012-12-11 00:14:22 +01:00
typedef struct SegmentListEntry {
int index ;
double start_time , end_time ;
2013-02-06 00:19:52 +01:00
int64_t start_pts ;
2013-07-05 14:28:38 +02:00
int64_t offset_pts ;
2013-11-22 12:49:05 +01:00
char * filename ;
2012-12-11 00:14:22 +01:00
struct SegmentListEntry * next ;
2014-07-17 20:37:55 +02:00
int64_t last_duration ;
2012-12-11 00:14:22 +01:00
} SegmentListEntry ;
2012-01-13 23:29:09 +01:00
typedef enum {
2012-08-16 00:16:28 +02:00
LIST_TYPE_UNDEFINED = - 1 ,
2012-01-13 23:29:09 +01:00
LIST_TYPE_FLAT = 0 ,
2012-08-16 18:13:17 +02:00
LIST_TYPE_CSV ,
2012-08-15 11:06:34 +02:00
LIST_TYPE_M3U8 ,
2012-09-14 23:31:47 +02:00
LIST_TYPE_EXT , ///< deprecated
2013-02-21 19:33:26 +01:00
LIST_TYPE_FFCONCAT ,
2012-01-13 23:29:09 +01:00
LIST_TYPE_NB ,
} ListType ;
2012-09-01 18:01:51 +02:00
#define SEGMENT_LIST_FLAG_CACHE 1
#define SEGMENT_LIST_FLAG_LIVE 2
2014-09-22 09:19:33 +02:00
typedef struct SegmentContext {
2011-10-02 23:05:29 +02:00
const AVClass * class ; /**< Class for private options. */
2012-08-15 22:12:11 +02:00
int segment_idx ; ///< index of the segment file to write, starting from 0
int segment_idx_wrap ; ///< number after which the index wraps
2025-08-01 22:43:23 +02:00
int segment_idx_wrap_nb ; ///< number of time the index has wrapped
2012-08-15 22:12:11 +02:00
int segment_count ; ///< number of segment files already written
2021-02-25 03:11:32 +01:00
const AVOutputFormat * oformat ;
2011-10-02 23:05:29 +02:00
AVFormatContext * avf ;
2014-09-06 15:43:11 +02:00
char * format ; ///< format to use for output segment files
AVDictionary * format_options ;
2012-01-14 11:00:16 +01:00
char * list ; ///< filename for the segment list file
2012-09-01 18:01:51 +02:00
int list_flags ; ///< flags affecting list generation
2012-01-14 11:00:16 +01:00
int list_size ; ///< number of entries for the segment list file
2014-07-07 21:35:43 +02:00
2020-09-06 00:29:33 +02:00
int is_nullctx ; ///< whether avf->pb is a nullctx
2014-07-07 21:35:43 +02:00
int use_clocktime ; ///< flag to cut segments at regular clock time
2016-01-13 01:44:36 +01:00
int64_t clocktime_offset ; //< clock offset for cutting the segments at regular clock time
2016-01-18 01:29:06 +01:00
int64_t clocktime_wrap_duration ; //< wrapping duration considered for starting a new segment
2014-07-07 21:35:43 +02:00
int64_t last_val ; ///< remember last time for wrap around detection
int cut_pending ;
2016-04-07 19:18:45 -05:00
int header_written ; ///< whether we've already called avformat_write_header
2014-07-07 21:35:43 +02:00
2014-04-29 15:42:04 +02: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 20:04:14 +01:00
AVIOContext * list_pb ; ///< list file put-byte context
2012-07-08 17:26:33 +02:00
int64_t time ; ///< segment duration
2022-12-21 19:41:58 +05:30
int64_t min_seg_duration ; ///< minimum segment duration
2014-12-28 05:35:34 -02:00
int use_strftime ; ///< flag to expand filename with strftime
2016-02-28 21:36:42 +01:00
int increment_tc ; ///< flag to increment timecode if found
2012-12-09 20:26:30 +01:00
2012-01-28 22:36:38 +01:00
char * times_str ; ///< segment times specification string
int64_t * times ; ///< list of segment interval specification
2025-08-01 22:43:23 +02:00
int nb_times ; ///< number of elements in the times array
2012-12-09 20:26:30 +01:00
char * frames_str ; ///< segment frame numbers specification string
int * frames ; ///< list of frame number specification
2025-08-01 22:43:23 +02:00
int nb_frames ; ///< number of elements in the frames array
2014-07-17 20:28:40 +02:00
int frame_count ; ///< total number of reference frames
int segment_frame_count ; ///< number of reference frames in the segment
2012-12-09 20:26:30 +01:00
2012-01-28 22:21:43 +01:00
int64_t time_delta ;
2012-10-02 23:49:46 +02:00
int individual_header_trailer ; /**< Set by a private option. */
2012-10-02 23:59:35 +02:00
int write_header_trailer ; /**< Set by a private option. */
2015-03-28 19:25:20 -06:00
char * header_filename ; ///< filename to write the output header to
2012-11-29 13:45:50 +01:00
2017-09-21 15:10:56 -08:00
int reset_timestamps ; ///< reset timestamps at the beginning of each segment
2013-07-05 14:28:38 +02:00
int64_t initial_offset ; ///< initial timestamps offset, expressed in microseconds
2012-12-22 21:49:06 +01:00
char * reference_stream_specifier ; ///< reference stream specifier
int reference_stream_index ;
2023-01-07 15:10:52 +05:30
int64_t reference_stream_first_pts ; ///< initial timestamp, expressed in microseconds
2015-06-09 02:32:18 -05:00
int break_non_keyframes ;
2016-03-06 20:48:57 -06:00
int write_empty ;
2012-12-22 21:49:06 +01:00
2015-08-27 23:40:25 +02:00
int use_rename ;
2025-08-17 20:39:09 +02:00
char * temp_list_filename ;
2015-08-27 23:40:25 +02:00
2012-12-11 00:14:22 +01:00
SegmentListEntry cur_entry ;
2012-12-20 14:20:19 +01:00
SegmentListEntry * segment_list_entries ;
SegmentListEntry * segment_list_entries_end ;
2011-10-02 23:05:29 +02:00
} SegmentContext ;
2012-09-01 16:12:29 +02:00
static void print_csv_escaped_str ( AVIOContext * ctx , const char * str )
{
2012-09-14 18:26:50 +02:00
int needs_quoting = !! str [ strcspn ( str , " \" , \n\r " )];
2012-09-01 16:12:29 +02:00
2012-09-14 18:26:50 +02:00
if ( needs_quoting )
2012-09-01 16:12:29 +02:00
avio_w8 ( ctx , '"' );
2012-09-09 21:27:13 +02:00
for (; * str ; str ++ ) {
if ( * str == '"' )
2012-09-01 16:12:29 +02:00
avio_w8 ( ctx , '"' );
2012-09-09 21:27:13 +02:00
avio_w8 ( ctx , * str );
2012-09-01 16:12:29 +02:00
}
2012-09-14 18:26:50 +02:00
if ( needs_quoting )
2012-09-01 16:12:29 +02:00
avio_w8 ( ctx , '"' );
}
2012-10-04 00:15:35 +03:00
static int segment_mux_init ( AVFormatContext * s )
2011-10-02 23:05:29 +02:00
{
2012-01-13 23:27:09 +01:00
SegmentContext * seg = s -> priv_data ;
2012-10-04 00:15:35 +03:00
AVFormatContext * oc ;
int i ;
2014-09-02 12:19:53 +02:00
int ret ;
2012-10-04 00:15:35 +03:00
2014-09-02 12:19:53 +02: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 19:06:45 +01:00
av_dict_copy ( & oc -> metadata , s -> metadata , 0 );
2016-01-16 17:53:43 +01:00
oc -> opaque = s -> opaque ;
2021-11-30 00:38:30 +01:00
oc -> io_close2 = s -> io_close2 ;
2016-01-16 17:53:43 +01:00
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 ++ ) {
2020-05-17 02:36:46 +02:00
AVStream * st , * ist = s -> streams [ i ];
AVCodecParameters * ipar = ist -> codecpar , * opar ;
2012-11-17 17:39:51 +01:00
2022-08-06 16:35:19 -07:00
st = ff_stream_clone ( oc , ist );
if ( ! st )
2012-10-04 00:15:35 +03:00
return AVERROR ( ENOMEM );
2016-04-10 20:58:15 +01:00
opar = st -> codecpar ;
2012-11-17 17:39:51 +01:00
if ( ! oc -> oformat -> codec_tag ||
2016-04-10 20:58:15 +01: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 17:39:51 +01:00
} else {
2016-04-10 20:58:15 +01:00
opar -> codec_tag = 0 ;
2012-11-17 17:39:51 +01:00
}
2012-10-04 00:15:35 +03:00
}
return 0 ;
}
2012-12-13 11:37:50 +01:00
static int set_segment_filename ( AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2013-11-22 12:49:05 +01:00
size_t size ;
2015-12-08 19:29:13 +08:00
int ret ;
2025-08-17 20:39:09 +02:00
AVBPrint filename ;
2017-12-29 23:30:14 +01:00
char * new_name ;
2012-12-13 11:37:50 +01:00
2025-08-17 20:39:09 +02:00
av_bprint_init ( & filename , 0 , AV_BPRINT_SIZE_UNLIMITED );
2012-12-13 11:37:50 +01:00
if ( seg -> segment_idx_wrap )
seg -> segment_idx %= seg -> segment_idx_wrap ;
2014-12-28 05:35:34 -02:00
if ( seg -> use_strftime ) {
time_t now0 ;
struct tm * tm , tmpbuf ;
time ( & now0 );
tm = localtime_r ( & now0 , & tmpbuf );
2025-08-17 20:39:09 +02:00
av_bprint_strftime ( & filename , s -> url , tm );
if ( ! av_bprint_is_complete ( & filename )) {
av_bprint_finalize ( & filename , NULL );
return AVERROR ( ENOMEM );
}
} else {
ret = ff_bprint_get_frame_filename ( & filename , s -> url , seg -> segment_idx , 0 );
if ( ret < 0 ) {
av_bprint_finalize ( & filename , NULL );
av_log ( oc , AV_LOG_ERROR , "Invalid segment filename template '%s' \n " , s -> url );
return ret ;
2014-12-28 05:35:34 -02:00
}
2012-12-13 11:37:50 +01:00
}
2025-08-17 20:39:09 +02:00
ret = av_bprint_finalize ( & filename , & new_name );
if ( ret < 0 )
return ret ;
2017-12-29 23:30:14 +01:00
ff_format_set_url ( oc , new_name );
2013-11-22 12:49:05 +01:00
/* copy modified name in list entry */
2017-12-29 23:30:14 +01:00
size = strlen ( av_basename ( oc -> url )) + 1 ;
2014-04-29 15:42:04 +02:00
if ( seg -> entry_prefix )
size += strlen ( seg -> entry_prefix );
2013-11-22 12:49:05 +01:00
2015-12-08 19:29:13 +08:00
if (( ret = av_reallocp ( & seg -> cur_entry . filename , size )) < 0 )
return ret ;
2013-11-22 12:49:05 +01:00
snprintf ( seg -> cur_entry . filename , size , "%s%s" ,
2014-04-29 15:42:04 +02:00
seg -> entry_prefix ? seg -> entry_prefix : "" ,
2017-12-29 23:30:14 +01:00
av_basename ( oc -> url ));
2013-11-22 12:49:05 +01:00
2012-12-13 11:37:50 +01:00
return 0 ;
}
2012-10-02 23:49:46 +02:00
static int segment_start ( AVFormatContext * s , int write_header )
2011-10-02 23:05:29 +02:00
{
2012-11-17 16:35:51 +01:00
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2011-10-02 23:05:29 +02:00
int err = 0 ;
2012-10-02 23:49:46 +02:00
if ( write_header ) {
avformat_free_context ( oc );
2012-11-17 16:35:51 +01:00
seg -> avf = NULL ;
2012-10-02 23:49:46 +02:00
if (( err = segment_mux_init ( s )) < 0 )
return err ;
2012-11-17 16:35:51 +01:00
oc = seg -> avf ;
2012-10-02 23:49:46 +02:00
}
2012-10-04 00:15:35 +03:00
2012-11-17 17:20:29 +01:00
seg -> segment_idx ++ ;
2015-03-30 20:23:19 -06:00
if (( seg -> segment_idx_wrap ) && ( seg -> segment_idx % seg -> segment_idx_wrap == 0 ))
2014-02-05 14:59:00 +01:00
seg -> segment_idx_wrap_nb ++ ;
2012-12-13 11:37:50 +01:00
if (( err = set_segment_filename ( s )) < 0 )
return err ;
2011-10-02 23:05:29 +02:00
2017-12-29 23:30:14 +01: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-02 23:05:29 +02:00
return err ;
2013-11-25 19:20:11 +01:00
}
2015-03-29 10:57:09 -06:00
if ( ! seg -> individual_header_trailer )
oc -> pb -> seekable = 0 ;
2011-10-02 23:05:29 +02:00
2012-10-02 23:51:46 +02: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-02 23:51:46 +02:00
2012-10-02 23:49:46 +02:00
if ( write_header ) {
2016-03-15 19:19:20 +01:00
AVDictionary * options = NULL ;
av_dict_copy ( & options , seg -> format_options , 0 );
2016-04-07 19:18:45 -05:00
av_dict_set ( & options , "fflags" , "-autobsf" , 0 );
2016-03-15 19:19:20 +01:00
err = avformat_write_header ( oc , & options );
av_dict_free ( & options );
if ( err < 0 )
2012-10-02 23:49:46 +02:00
return err ;
}
2011-10-02 23:05:29 +02:00
2014-07-17 20:28:40 +02:00
seg -> segment_frame_count = 0 ;
2011-10-02 23:05:29 +02:00
return 0 ;
}
2012-08-15 23:21:41 +02:00
static int segment_list_open ( AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
2012-08-15 23:14:34 +02:00
int ret ;
2025-08-17 20:39:09 +02:00
av_freep ( & seg -> temp_list_filename );
seg -> temp_list_filename = av_asprintf ( seg -> use_rename ? "%s.tmp" : "%s" , seg -> list );
if ( ! seg -> temp_list_filename )
return AVERROR ( ENOMEM );
2016-02-10 14:40:32 +00:00
ret = s -> io_open ( s , & seg -> list_pb , seg -> temp_list_filename , AVIO_FLAG_WRITE , NULL );
2013-11-25 19:20:11 +01:00
if ( ret < 0 ) {
av_log ( s , AV_LOG_ERROR , "Failed to open segment list '%s' \n " , seg -> list );
2012-08-15 23:14:34 +02:00
return ret ;
2013-11-25 19:20:11 +01:00
}
2012-08-15 11:06:34 +02:00
2012-12-20 14:20:19 +01:00
if ( seg -> list_type == LIST_TYPE_M3U8 && seg -> segment_list_entries ) {
SegmentListEntry * entry ;
double max_duration = 0 ;
2012-08-15 11:06:34 +02:00
avio_printf ( seg -> list_pb , "#EXTM3U \n " );
2012-09-01 17:13:26 +02:00
avio_printf ( seg -> list_pb , "#EXT-X-VERSION:3 \n " );
2012-12-20 14:20:19 +01:00
avio_printf ( seg -> list_pb , "#EXT-X-MEDIA-SEQUENCE:%d \n " , seg -> segment_list_entries -> index );
2013-02-03 11:12:49 +01:00
avio_printf ( seg -> list_pb , "#EXT-X-ALLOW-CACHE:%s \n " ,
seg -> list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO" );
2012-12-20 14:20:19 +01:00
2014-04-30 20:12:17 +02:00
av_log ( s , AV_LOG_VERBOSE , "EXT-X-MEDIA-SEQUENCE:%d \n " ,
seg -> segment_list_entries -> index );
2012-12-20 14:20:19 +01: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 19:33:26 +01:00
} else if ( seg -> list_type == LIST_TYPE_FFCONCAT ) {
avio_printf ( seg -> list_pb , "ffconcat version 1.0 \n " );
2012-08-15 11:06:34 +02:00
}
2012-08-15 23:14:34 +02:00
return ret ;
2012-08-15 23:21:41 +02:00
}
2012-12-11 00:14:22 +01:00
static void segment_list_print_entry ( AVIOContext * list_ioctx ,
ListType list_type ,
2013-02-21 19:33:26 +01:00
const SegmentListEntry * list_entry ,
void * log_ctx )
2012-12-11 00:14:22 +01: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 19:33:26 +01: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 00:14:22 +01:00
default :
av_assert0 ( ! "Invalid list type" );
}
}
2013-01-23 00:23:47 +01:00
static int segment_end ( AVFormatContext * s , int write_trailer , int is_last )
2011-10-02 23:05:29 +02:00
{
2012-01-13 23:24:13 +01:00
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2011-10-02 23:05:29 +02:00
int ret = 0 ;
2016-02-28 21:36:42 +01:00
AVTimecode tc ;
AVRational rate ;
AVDictionaryEntry * tcr ;
char buf [ AV_TIMECODE_STR_SIZE ];
int i ;
int err ;
2011-10-02 23:05:29 +02:00
2017-01-20 20:15:03 -06: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-02 23:49:46 +02:00
if ( write_trailer )
2012-10-05 16:14:06 +02:00
ret = av_write_trailer ( oc );
2011-10-02 23:05:29 +02:00
2012-01-12 23:24:27 +01:00
if ( ret < 0 )
2012-01-13 23:24:13 +01:00
av_log ( s , AV_LOG_ERROR , "Failure occurred when ending segment '%s' \n " ,
2017-12-29 23:30:14 +01:00
oc -> url );
2012-01-12 23:24:27 +01:00
2012-01-14 00:16:55 +01:00
if ( seg -> list ) {
2012-12-20 14:20:19 +01: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 14:20:19 +01: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 21:40:43 +02:00
if ( seg -> list_size && seg -> segment_count >= seg -> list_size ) {
2012-12-20 14:20:19 +01:00
entry = seg -> segment_list_entries ;
seg -> segment_list_entries = seg -> segment_list_entries -> next ;
2014-12-25 12:38:20 +01:00
av_freep ( & entry -> filename );
2012-12-20 14:20:19 +01:00
av_freep ( & entry );
}
2012-08-15 23:21:41 +02:00
if (( ret = segment_list_open ( s )) < 0 )
2012-01-14 00:16:55 +01:00
goto end ;
2012-12-20 14:20:19 +01:00
for ( entry = seg -> segment_list_entries ; entry ; entry = entry -> next )
2013-02-21 19:33:26 +01:00
segment_list_print_entry ( seg -> list_pb , seg -> list_type , entry , s );
2013-01-23 00:23:47 +01:00
if ( seg -> list_type == LIST_TYPE_M3U8 && is_last )
2012-12-20 14:20:19 +01:00
avio_printf ( seg -> list_pb , "#EXT-X-ENDLIST \n " );
2016-02-10 14:40:32 +00: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 14:20:19 +01:00
} else {
2013-02-21 19:33:26 +01:00
segment_list_print_entry ( seg -> list_pb , seg -> list_type , & seg -> cur_entry , s );
2015-03-28 19:25:18 -06:00
avio_flush ( seg -> list_pb );
2012-01-14 00:16:55 +01:00
}
}
2013-10-15 11:53:40 +01:00
av_log ( s , AV_LOG_VERBOSE , "segment:'%s' count:%d ended \n " ,
2017-12-29 23:30:14 +01:00
seg -> avf -> url , seg -> segment_count );
2013-10-15 11:53:40 +01:00
seg -> segment_count ++ ;
2016-02-28 21:36:42 +01: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 20:58:15 +01:00
if ( s -> streams [ i ] -> codecpar -> codec_type == AVMEDIA_TYPE_VIDEO ) {
2016-02-28 21:36:42 +01: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 ) {
2019-06-14 22:36:27 +05:30
av_log ( s , AV_LOG_WARNING , "Could not increment global timecode, error occurred during timecode creation. \n " );
2016-02-28 21:36:42 +01: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 {
2019-06-14 22:36:27 +05:30
av_log ( s , AV_LOG_WARNING , "Could not increment global timecode, no global timecode metadata found. \n " );
}
for ( i = 0 ; i < s -> nb_streams ; i ++ ) {
if ( s -> streams [ i ] -> codecpar -> codec_type == AVMEDIA_TYPE_VIDEO ) {
char st_buf [ AV_TIMECODE_STR_SIZE ];
AVTimecode st_tc ;
AVRational st_rate = s -> streams [ i ] -> avg_frame_rate ;
AVDictionaryEntry * st_tcr = av_dict_get ( s -> streams [ i ] -> metadata , "timecode" , NULL , 0 );
if ( st_tcr ) {
if (( av_timecode_init_from_string ( & st_tc , st_rate , st_tcr -> value , s ) < 0 )) {
av_log ( s , AV_LOG_WARNING , "Could not increment stream %d timecode, error occurred during timecode creation. \n " , i );
continue ;
}
st_tc . start += ( int )(( seg -> cur_entry . end_time - seg -> cur_entry . start_time ) * av_q2d ( st_rate )); // increment timecode
av_dict_set ( & s -> streams [ i ] -> metadata , "timecode" , av_timecode_make_string ( & st_tc , st_buf , 0 ), 0 );
}
}
2016-02-28 21:36:42 +01:00
}
}
2012-01-14 00:16:55 +01:00
end :
2016-01-16 17:53:43 +01:00
ff_format_io_close ( oc , & oc -> pb );
2011-10-02 23:05:29 +02:00
return ret ;
}
2012-01-28 22:36:38 +01: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-05 23:11:04 +02:00
* times = av_malloc_array ( * nb_times , sizeof ( ** times ));
2012-01-28 22:36:38 +01: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 20:39:27 +01: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-28 22:36:38 +01:00
ret = av_parse_time ( & t , tstr , 1 );
if ( ret < 0 ) {
av_log ( log_ctx , AV_LOG_ERROR ,
2012-12-09 20:39:27 +01:00
"Invalid time duration specification '%s' in times list %s \n " , tstr , times_str );
2012-01-28 22:36:38 +01:00
FAIL ( AVERROR ( EINVAL ));
}
( * times )[ i ] = t ;
/* check on monotonicity */
if ( i && ( * times )[ i - 1 ] > ( * times )[ i ]) {
av_log ( log_ctx , AV_LOG_ERROR ,
2020-09-06 11:24:22 +02:00
"Specified time %f is smaller than the last time %f \n " ,
2012-01-28 22:36:38 +01:00
( float )(( * times )[ i ]) / 1000000 , ( float )(( * times )[ i - 1 ]) / 1000000 );
FAIL ( AVERROR ( EINVAL ));
}
}
end :
av_free ( times_str1 );
return ret ;
}
2012-12-09 20:26:30 +01:00
static int parse_frames ( void * log_ctx , int ** frames , int * nb_frames ,
const char * frames_str )
{
2020-09-06 12:07:39 +02:00
const char * p ;
int i ;
2012-12-09 20:26:30 +01:00
* nb_frames = 1 ;
2020-09-06 12:07:39 +02:00
for ( p = frames_str ; * p ; p ++ )
2012-12-09 20:26:30 +01:00
if ( * p == ',' )
( * nb_frames ) ++ ;
2014-05-05 23:11:04 +02:00
* frames = av_malloc_array ( * nb_frames , sizeof ( ** frames ));
2012-12-09 20:26:30 +01:00
if ( !* frames ) {
av_log ( log_ctx , AV_LOG_ERROR , "Could not allocate forced frames array \n " );
2020-09-06 12:07:39 +02:00
return AVERROR ( ENOMEM );
2012-12-09 20:26:30 +01:00
}
2020-09-06 12:07:39 +02:00
p = frames_str ;
2012-12-09 20:26:30 +01:00
for ( i = 0 ; i < * nb_frames ; i ++ ) {
long int f ;
char * tailptr ;
2020-09-06 12:07:39 +02:00
if ( * p == '\0' || * p == ',' ) {
2012-12-09 20:26:30 +01:00
av_log ( log_ctx , AV_LOG_ERROR , "Empty frame specification in frame list %s \n " ,
frames_str );
2020-09-06 12:07:39 +02:00
return AVERROR ( EINVAL );
2012-12-09 20:26:30 +01:00
}
2020-09-06 12:07:39 +02:00
f = strtol ( p , & tailptr , 10 );
if ( * tailptr != '\0' && * tailptr != ',' || f <= 0 || f >= INT_MAX ) {
2012-12-09 20:26:30 +01:00
av_log ( log_ctx , AV_LOG_ERROR ,
2020-09-06 11:24:22 +02:00
"Invalid argument '%s', must be a positive integer < INT_MAX \n " ,
2020-09-06 12:07:39 +02:00
p );
return AVERROR ( EINVAL );
2012-12-09 20:26:30 +01:00
}
2020-09-06 12:07:39 +02:00
if ( * tailptr == ',' )
tailptr ++ ;
p = tailptr ;
2012-12-09 20:26:30 +01:00
( * frames )[ i ] = f ;
/* check on monotonicity */
if ( i && ( * frames )[ i - 1 ] > ( * frames )[ i ]) {
av_log ( log_ctx , AV_LOG_ERROR ,
2020-09-06 11:24:22 +02:00
"Specified frame %d is smaller than the last frame %d \n " ,
2012-12-09 20:26:30 +01:00
( * frames )[ i ], ( * frames )[ i - 1 ]);
2020-09-06 12:07:39 +02:00
return AVERROR ( EINVAL );
2012-12-09 20:26:30 +01:00
}
}
2020-09-06 12:07:39 +02:00
return 0 ;
2012-12-09 20:26:30 +01:00
}
2012-10-02 23:59:35 +02:00
static int open_null_ctx ( AVIOContext ** ctx )
{
int buf_size = 32768 ;
uint8_t * buf = av_malloc ( buf_size );
if ( ! buf )
return AVERROR ( ENOMEM );
2021-11-22 00:25:16 +01:00
* ctx = avio_alloc_context ( buf , buf_size , 1 , NULL , NULL , NULL , NULL );
2012-10-02 23:59:35 +02:00
if ( !* ctx ) {
av_free ( buf );
return AVERROR ( ENOMEM );
}
return 0 ;
}
2014-12-25 12:38:20 +01:00
static void close_null_ctxp ( AVIOContext ** pb )
2012-10-02 23:59:35 +02:00
{
2014-12-25 12:38:20 +01:00
av_freep ( & ( * pb ) -> buffer );
2017-09-01 02:16:33 -03:00
avio_context_free ( pb );
2012-10-02 23:59:35 +02:00
}
2013-01-16 19:52:58 +01: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 20:58:15 +01:00
type = s -> streams [ i ] -> codecpar -> codec_type ;
2013-01-16 20:10:12 +01: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 19:52:58 +01: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 18:50:21 +01:00
return ret ;
2013-01-16 19:52:58 +01: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-07 19:18:19 -05:00
static void seg_free ( AVFormatContext * s )
2015-01-05 10:40:41 +01:00
{
2016-04-07 19:18:19 -05:00
SegmentContext * seg = s -> priv_data ;
2020-09-05 23:36:03 +02:00
SegmentListEntry * cur ;
2020-09-05 18:12:27 +02:00
ff_format_io_close ( s , & seg -> list_pb );
2020-09-06 00:29:33 +02:00
if ( seg -> avf ) {
if ( seg -> is_nullctx )
close_null_ctxp ( & seg -> avf -> pb );
else
ff_format_io_close ( s , & seg -> avf -> pb );
avformat_free_context ( seg -> avf );
seg -> avf = NULL ;
}
2020-09-05 19:36:31 +02:00
av_freep ( & seg -> times );
av_freep ( & seg -> frames );
2020-09-05 21:22:21 +02:00
av_freep ( & seg -> cur_entry . filename );
2025-08-17 20:39:09 +02:00
av_freep ( & seg -> temp_list_filename );
2020-09-05 23:36:03 +02:00
cur = seg -> segment_list_entries ;
while ( cur ) {
SegmentListEntry * next = cur -> next ;
av_freep ( & cur -> filename );
av_free ( cur );
cur = next ;
}
2015-01-05 10:40:41 +01:00
}
2016-03-23 09:24:22 -05:00
static int seg_init ( AVFormatContext * s )
2011-10-02 23:05:29 +02:00
{
SegmentContext * seg = s -> priv_data ;
2016-03-23 09:24:22 -05:00
AVFormatContext * oc = seg -> avf ;
2014-09-06 15:43:11 +02:00
AVDictionary * options = NULL ;
2013-01-16 19:52:58 +01:00
int ret ;
2014-11-16 02:49:12 +01:00
int i ;
2011-10-02 23:05:29 +02:00
2012-08-15 22:12:11 +02:00
seg -> segment_count = 0 ;
2012-10-02 23:59:35 +02:00
if ( ! seg -> write_header_trailer )
seg -> individual_header_trailer = 0 ;
2012-07-08 17:26:33 +02:00
2015-03-28 19:25:20 -06:00
if ( seg -> header_filename ) {
seg -> write_header_trailer = 1 ;
seg -> individual_header_trailer = 0 ;
}
2016-09-08 21:56:44 +08: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 " );
}
2020-09-06 12:34:58 +02:00
if (( seg -> time != 2000000 ) + !! seg -> times_str + !! seg -> frames_str > 1 ) {
2012-07-08 17:26:33 +02:00
av_log ( s , AV_LOG_ERROR ,
2012-12-09 20:26:30 +01:00
"segment_time, segment_times, and segment_frames options "
"are mutually exclusive, select just one of them \n " );
2012-01-28 22:36:38 +01:00
return AVERROR ( EINVAL );
}
2022-12-21 19:41:58 +05:30
if ( seg -> times_str || seg -> frames_str )
seg -> min_seg_duration = 0 ;
2012-01-28 22:36:38 +01:00
if ( seg -> times_str ) {
if (( ret = parse_times ( s , & seg -> times , & seg -> nb_times , seg -> times_str )) < 0 )
return ret ;
2012-12-09 20:26:30 +01:00
} else if ( seg -> frames_str ) {
if (( ret = parse_frames ( s , & seg -> frames , & seg -> nb_frames , seg -> frames_str )) < 0 )
return ret ;
2012-01-28 22:36:38 +01:00
} else {
2016-01-13 01:44:36 +01: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 );
}
2022-12-21 19:41:58 +05:30
if ( seg -> min_seg_duration > seg -> time ) {
av_log ( s , AV_LOG_ERROR , "min_seg_duration cannot be greater than segment_time \n " );
return AVERROR ( EINVAL );
}
2012-07-08 17:26:33 +02:00
}
2011-10-02 23:05:29 +02:00
2012-08-16 00:16:28 +02:00
if ( seg -> list ) {
if ( seg -> list_type == LIST_TYPE_UNDEFINED ) {
2012-08-16 18:13:17 +02: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 00:16:28 +02:00
else if ( av_match_ext ( seg -> list , "m3u8" )) seg -> list_type = LIST_TYPE_M3U8 ;
2013-02-21 19:33:26 +01:00
else if ( av_match_ext ( seg -> list , "ffcat,ffconcat" )) seg -> list_type = LIST_TYPE_FFCONCAT ;
2012-08-16 00:16:28 +02: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-28 19:25:18 -06:00
if (( ret = segment_list_open ( s )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2015-08-27 23:40:25 +02:00
} else {
2016-10-06 02:00:25 -05: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 00:16:28 +02:00
}
2016-03-23 09:24:22 -05:00
2012-08-16 18:13:17 +02: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-02 23:05:29 +02:00
2013-01-16 19:52:58 +01:00
if (( ret = select_reference_stream ( s )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2012-12-22 21:49:06 +01:00
av_log ( s , AV_LOG_VERBOSE , "Selected stream id:%d type:%s \n " ,
seg -> reference_stream_index ,
2016-04-10 20:58:15 +01:00
av_get_media_type_string ( s -> streams [ seg -> reference_stream_index ] -> codecpar -> codec_type ));
2011-10-02 23:05:29 +02:00
2023-01-07 15:10:52 +05:30
seg -> reference_stream_first_pts = AV_NOPTS_VALUE ;
2017-12-29 23:30:14 +01:00
seg -> oformat = av_guess_format ( seg -> format , s -> url , NULL );
2011-10-02 23:05:29 +02:00
2016-04-07 19:18:19 -05: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-02 23:05:29 +02:00
av_log ( s , AV_LOG_ERROR , "format %s not supported. \n " ,
2012-11-05 20:25:25 +01:00
seg -> oformat -> name );
2016-04-07 19:18:19 -05:00
return AVERROR ( EINVAL );
2011-10-02 23:05:29 +02:00
}
2012-10-04 00:15:35 +03:00
if (( ret = segment_mux_init ( s )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2011-10-02 23:05:29 +02:00
2012-12-13 11:37:50 +01:00
if (( ret = set_segment_filename ( s )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2016-03-23 09:24:22 -05:00
oc = seg -> avf ;
2011-10-02 23:05:29 +02:00
2012-10-02 23:59:35 +02:00
if ( seg -> write_header_trailer ) {
2016-02-10 17:59:58 +01:00
if (( ret = s -> io_open ( s , & oc -> pb ,
2017-12-29 23:30:14 +01:00
seg -> header_filename ? seg -> header_filename : oc -> url ,
2016-02-10 17:59:58 +01:00
AVIO_FLAG_WRITE , NULL )) < 0 ) {
2017-12-29 23:30:14 +01:00
av_log ( s , AV_LOG_ERROR , "Failed to open segment '%s' \n " , oc -> url );
2016-04-07 19:18:19 -05:00
return ret ;
2013-11-25 19:20:11 +01:00
}
2015-03-29 10:57:09 -06:00
if ( ! seg -> individual_header_trailer )
oc -> pb -> seekable = 0 ;
2012-10-02 23:59:35 +02:00
} else {
if (( ret = open_null_ctx ( & oc -> pb )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2020-09-06 00:29:33 +02:00
seg -> is_nullctx = 1 ;
2012-10-02 23:59:35 +02:00
}
2011-10-02 23:05:29 +02:00
2014-09-06 15:43:11 +02:00
av_dict_copy ( & options , seg -> format_options , 0 );
2016-04-07 19:18:45 -05:00
av_dict_set ( & options , "fflags" , "-autobsf" , 0 );
ret = avformat_init_output ( oc , & options );
2014-09-06 15:43:11 +02:00
if ( av_dict_count ( options )) {
av_log ( s , AV_LOG_ERROR ,
2019-12-25 00:48:00 +01:00
"Some of the provided format options are not recognized \n " );
2016-04-07 19:18:19 -05:00
av_dict_free ( & options );
return AVERROR ( EINVAL );
2014-09-06 15:43:11 +02:00
}
2016-04-07 19:18:19 -05:00
av_dict_free ( & options );
2014-09-08 12:47:37 +02:00
2014-09-06 15:43:11 +02:00
if ( ret < 0 ) {
2016-04-07 19:18:19 -05:00
return ret ;
2011-10-02 23:05:29 +02:00
}
2014-07-17 20:28:40 +02:00
seg -> segment_frame_count = 0 ;
2011-10-02 23:05:29 +02:00
2014-11-16 02:49:12 +01:00
av_assert0 ( s -> nb_streams == oc -> nb_streams );
2016-04-07 19:18:45 -05: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 02:49:12 +01: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 12:07:26 +02:00
if ( oc -> avoid_negative_ts > 0 && s -> avoid_negative_ts < 0 )
s -> avoid_negative_ts = 1 ;
2016-04-07 19:18:45 -05:00
return ret ;
}
static int seg_write_header ( AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2020-09-06 13:24:03 +02:00
int ret ;
2016-04-07 19:18:45 -05:00
if ( ! seg -> header_written ) {
ret = avformat_write_header ( oc , NULL );
if ( ret < 0 )
return ret ;
}
2015-03-28 19:25:20 -06:00
if ( ! seg -> write_header_trailer || seg -> header_filename ) {
if ( seg -> header_filename ) {
av_write_frame ( oc , NULL );
2016-02-10 14:40:32 +00:00
ff_format_io_close ( oc , & oc -> pb );
2015-03-28 19:25:20 -06:00
} else {
close_null_ctxp ( & oc -> pb );
2020-09-06 00:29:33 +02:00
seg -> is_nullctx = 0 ;
2015-03-28 19:25:20 -06:00
}
2017-12-29 23:30:14 +01:00
if (( ret = oc -> io_open ( oc , & oc -> pb , oc -> url , AVIO_FLAG_WRITE , NULL )) < 0 )
2016-04-07 19:18:19 -05:00
return ret ;
2015-03-29 10:57:09 -06:00
if ( ! seg -> individual_header_trailer )
oc -> pb -> seekable = 0 ;
2012-10-02 23:59:35 +02:00
}
2016-04-07 19:18:19 -05:00
return 0 ;
2011-10-02 23:05:29 +02:00
}
static int seg_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
SegmentContext * seg = s -> priv_data ;
2012-02-26 01:39:32 +02:00
AVStream * st = s -> streams [ pkt -> stream_index ];
2022-12-21 19:41:58 +05:30
int64_t end_pts = INT64_MAX , offset , pkt_pts_avtb ;
2012-12-09 20:26:30 +01:00
int start_frame = INT_MAX ;
2011-10-02 23:05:29 +02:00
int ret ;
2014-07-07 21:35:43 +02:00
struct tm ti ;
int64_t usecs ;
int64_t wrapped_val ;
2011-10-02 23:05:29 +02:00
2017-01-20 20:15:03 -06:00
if ( ! seg -> avf || ! seg -> avf -> pb )
2015-01-05 10:40:41 +01:00
return AVERROR ( EINVAL );
2019-05-21 17:15:54 +05:30
if ( ! st -> codecpar -> extradata_size ) {
2021-04-14 14:59:32 +02:00
size_t pkt_extradata_size ;
2019-05-21 17:15:54 +05:30
uint8_t * pkt_extradata = av_packet_get_side_data ( pkt , AV_PKT_DATA_NEW_EXTRADATA , & pkt_extradata_size );
if ( pkt_extradata && pkt_extradata_size > 0 ) {
ret = ff_alloc_extradata ( st -> codecpar , pkt_extradata_size );
if ( ret < 0 ) {
av_log ( s , AV_LOG_WARNING , "Unable to add extradata to stream. Output segments may be invalid. \n " );
goto calc_times ;
}
memcpy ( st -> codecpar -> extradata , pkt_extradata , pkt_extradata_size );
}
}
2016-03-06 20:48:57 -06:00
calc_times :
2012-01-28 22:36:38 +01:00
if ( seg -> times ) {
2013-10-15 14:54:25 +02:00
end_pts = seg -> segment_count < seg -> nb_times ?
seg -> times [ seg -> segment_count ] : INT64_MAX ;
2012-12-09 20:26:30 +01:00
} else if ( seg -> frames ) {
2014-09-01 20:10:03 +03:00
start_frame = seg -> segment_count < seg -> nb_frames ?
2013-10-15 14:54:25 +02:00
seg -> frames [ seg -> segment_count ] : INT_MAX ;
2012-01-28 22:36:38 +01:00
} else {
2014-07-07 21:35:43 +02:00
if ( seg -> use_clocktime ) {
2014-07-08 22:51:16 +02:00
int64_t avgt = av_gettime ();
time_t sec = avgt / 1000000 ;
localtime_r ( & sec , & ti );
2015-03-30 20:23:19 -06:00
usecs = ( int64_t )( ti . tm_hour * 3600 + ti . tm_min * 60 + ti . tm_sec ) * 1000000 + ( avgt % 1000000 );
2016-01-13 01:44:36 +01:00
wrapped_val = ( usecs + seg -> clocktime_offset ) % seg -> time ;
2017-01-26 03:04:57 +01:00
if ( wrapped_val < seg -> last_val && wrapped_val < seg -> clocktime_wrap_duration )
2014-07-07 21:35:43 +02:00
seg -> cut_pending = 1 ;
seg -> last_val = wrapped_val ;
} else {
2015-03-30 20:23:19 -06:00
end_pts = seg -> time * ( seg -> segment_count + 1 );
2014-07-07 21:35:43 +02:00
}
2012-01-28 22:36:38 +01:00
}
2015-08-17 20:45:35 -04:00
ff_dlog ( s , "packet stream:%d pts:%s pts_time:%s duration_time:%s is_key:%d frame:%d \n " ,
2014-07-17 16:36:11 +02: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 20:26:30 +01:00
2023-01-07 15:10:52 +05:30
if ( seg -> reference_stream_first_pts == AV_NOPTS_VALUE &&
pkt -> stream_index == seg -> reference_stream_index &&
pkt -> pts != AV_NOPTS_VALUE ) {
seg -> reference_stream_first_pts = av_rescale_q ( pkt -> pts , st -> time_base , AV_TIME_BASE_Q );
}
if ( seg -> reference_stream_first_pts != AV_NOPTS_VALUE ) {
end_pts += ( INT64_MAX - end_pts >= seg -> reference_stream_first_pts ) ?
seg -> reference_stream_first_pts :
INT64_MAX - end_pts ;
}
2022-12-21 19:41:58 +05:30
if ( pkt -> pts != AV_NOPTS_VALUE )
pkt_pts_avtb = av_rescale_q ( pkt -> pts , st -> time_base , AV_TIME_BASE_Q );
2012-12-22 21:49:06 +01:00
if ( pkt -> stream_index == seg -> reference_stream_index &&
2015-06-09 02:32:18 -05:00
( pkt -> flags & AV_PKT_FLAG_KEY || seg -> break_non_keyframes ) &&
2016-03-06 20:48:57 -06:00
( seg -> segment_frame_count > 0 || seg -> write_empty ) &&
2014-07-07 21:35:43 +02:00
( seg -> cut_pending || seg -> frame_count >= start_frame ||
2012-12-09 20:26:30 +01:00
( pkt -> pts != AV_NOPTS_VALUE &&
2022-12-21 19:41:58 +05:30
pkt_pts_avtb - seg -> cur_entry . start_pts >= seg -> min_seg_duration &&
2012-12-09 20:26:30 +01:00
av_compare_ts ( pkt -> pts , st -> time_base ,
2016-03-06 19:38:39 -06:00
end_pts - seg -> time_delta , AV_TIME_BASE_Q ) >= 0 ))) {
2014-07-17 20:37:55 +02: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 15:17:22 +02:00
if (( ret = segment_end ( s , seg -> individual_header_trailer , 0 )) < 0 )
goto fail ;
2011-10-02 23:05:29 +02:00
2013-10-15 15:17:22 +02:00
if (( ret = segment_start ( s , seg -> individual_header_trailer )) < 0 )
2011-10-02 23:05:29 +02:00
goto fail ;
2014-07-07 21:35:43 +02:00
seg -> cut_pending = 0 ;
2015-03-30 20:23:19 -06:00
seg -> cur_entry . index = seg -> segment_idx + seg -> segment_idx_wrap * seg -> segment_idx_wrap_nb ;
2012-12-11 00:14:22 +01: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-06 20:43:37 -06:00
seg -> cur_entry . end_time = seg -> cur_entry . start_time ;
2016-03-06 20:48:57 -06:00
if ( seg -> times || ( ! seg -> frames && ! seg -> use_clocktime ) && seg -> write_empty )
goto calc_times ;
2016-03-06 20:43:37 -06: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 20:37:55 +02:00
seg -> cur_entry . last_duration = pkt -> duration ;
2011-10-02 23:05:29 +02:00
}
2014-07-17 20:28:40 +02:00
if ( seg -> segment_frame_count == 0 ) {
2013-12-17 10:43:32 +01:00
av_log ( s , AV_LOG_VERBOSE , "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d \n " ,
2017-12-29 23:30:14 +01:00
seg -> avf -> url , pkt -> stream_index ,
2012-12-09 20:26:30 +01:00
av_ts2str ( pkt -> pts ), av_ts2timestr ( pkt -> pts , & st -> time_base ), seg -> frame_count );
2012-12-13 21:19:25 +01:00
}
2013-07-05 14:31:29 +02: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 ));
2012-11-29 13:45:50 +01:00
2013-07-05 14:31:29 +02:00
/* 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 ;
2012-11-29 13:45:50 +01:00
2013-07-05 14:31:29 +02:00
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 13:45:50 +01:00
2020-04-01 01:03:29 +02:00
ret = ff_write_chained ( seg -> avf , pkt -> stream_index , pkt , s ,
2023-01-27 15:06:00 +01:00
seg -> initial_offset || seg -> reset_timestamps ||
ffofmt ( seg -> avf -> oformat ) -> interleave_packet );
2011-10-02 23:05:29 +02:00
fail :
2021-09-01 21:14:41 +02:00
/* Use st->index here as the packet returned from ff_write_chained()
* is blank if interleaving has been used. */
if ( st -> index == seg -> reference_stream_index ) {
2012-12-09 20:26:30 +01:00
seg -> frame_count ++ ;
2014-07-17 20:28:40 +02:00
seg -> segment_frame_count ++ ;
}
2012-12-09 20:26:30 +01:00
2011-10-02 23:05:29 +02:00
return ret ;
}
static int seg_write_trailer ( struct AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2020-09-05 23:45:22 +02:00
int ret ;
2015-01-05 10:40:41 +01:00
if ( ! oc )
2020-09-05 23:45:22 +02:00
return 0 ;
2012-12-20 14:20:19 +01:00
2012-10-02 23:59:35 +02:00
if ( ! seg -> write_header_trailer ) {
2013-01-23 00:23:47 +01:00
if (( ret = segment_end ( s , 0 , 1 )) < 0 )
2020-09-05 23:45:22 +02:00
return ret ;
2015-06-12 13:39:16 +01:00
if (( ret = open_null_ctx ( & oc -> pb )) < 0 )
2020-09-05 23:45:22 +02:00
return ret ;
2020-09-06 00:29:33 +02:00
seg -> is_nullctx = 1 ;
2012-10-09 02:49:42 +02:00
ret = av_write_trailer ( oc );
2012-10-02 23:59:35 +02:00
} else {
2013-01-23 00:23:47 +01:00
ret = segment_end ( s , 1 , 1 );
2012-10-02 23:59:35 +02:00
}
2011-10-02 23:05:29 +02:00
return ret ;
}
2021-11-18 21:48:49 +01:00
static int seg_check_bitstream ( AVFormatContext * s , AVStream * st ,
const AVPacket * pkt )
2016-10-26 22:03:02 -05:00
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2023-01-27 15:06:00 +01:00
if ( ffofmt ( oc -> oformat ) -> check_bitstream ) {
2021-11-18 21:48:49 +01:00
AVStream * const ost = oc -> streams [ st -> index ];
2023-01-27 15:06:00 +01:00
int ret = ffofmt ( oc -> oformat ) -> check_bitstream ( oc , ost , pkt );
2016-10-26 22:03:02 -05:00
if ( ret == 1 ) {
2021-11-18 21:48:49 +01:00
FFStream * const sti = ffstream ( st );
FFStream * const osti = ffstream ( ost );
2021-08-24 19:41:16 +02:00
sti -> bsfc = osti -> bsfc ;
osti -> bsfc = NULL ;
2016-10-26 22:03:02 -05:00
}
return ret ;
}
return 1 ;
}
2011-10-02 23:05:29 +02:00
#define OFFSET(x) offsetof(SegmentContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [] = {
2020-03-17 22:46:36 +01:00
{ "reference_stream" , "set reference stream" , OFFSET ( reference_stream_specifier ), AV_OPT_TYPE_STRING , {. str = "auto" }, 0 , 0 , E },
2012-01-14 11:00:16 +01:00
{ "segment_format" , "set container format used for the segments" , OFFSET ( format ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , E },
2019-12-25 00:48:00 +01:00
{ "segment_format_options" , "set list of options for the container format used for the segments" , OFFSET ( format_options ), AV_OPT_TYPE_DICT , {. str = NULL }, 0 , 0 , E },
2012-01-14 11:00:16 +01:00
{ "segment_list" , "set the segment list filename" , OFFSET ( list ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , E },
2015-03-28 19:25:20 -06: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 18:01:51 +02:00
2024-02-11 15:41:05 +01: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 , . unit = "list_flags" },
{ "cache" , "allow list caching" , 0 , AV_OPT_TYPE_CONST , {. i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN , INT_MAX , E , . unit = "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 , . unit = "list_flags" },
2012-09-01 18:01:51 +02:00
2012-09-05 14:26:01 +02: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 11:44:31 +01:00
2024-02-11 15:41:05 +01: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 , . unit = "list_type" },
{ "flat" , "flat format" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_FLAT }, INT_MIN , INT_MAX , E , . unit = "list_type" },
{ "csv" , "csv format" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_CSV }, INT_MIN , INT_MAX , E , . unit = "list_type" },
{ "ext" , "extended format" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_EXT }, INT_MIN , INT_MAX , E , . unit = "list_type" },
{ "ffconcat" , "ffconcat format" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_FFCONCAT }, INT_MIN , INT_MAX , E , . unit = "list_type" },
{ "m3u8" , "M3U8 format" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_M3U8 }, INT_MIN , INT_MAX , E , . unit = "list_type" },
{ "hls" , "Apple HTTP Live Streaming compatible" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_TYPE_M3U8 }, INT_MIN , INT_MAX , E , . unit = "list_type" },
2012-12-14 11:44:31 +01:00
2015-11-21 22:05:07 +01: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 01:44:36 +01:00
{ "segment_clocktime_offset" , "set segment clocktime offset" , OFFSET ( clocktime_offset ), AV_OPT_TYPE_DURATION , {. i64 = 0 }, 0 , 86400000000LL , E },
2016-01-18 01:29:06 +01: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 },
2020-09-06 12:34:58 +02:00
{ "segment_time" , "set segment duration" , OFFSET ( time ), AV_OPT_TYPE_DURATION , {. i64 = 2000000 }, INT64_MIN , INT64_MAX , 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 },
2022-12-21 19:41:58 +05:30
{ "min_seg_duration" , "set minimum segment duration" , OFFSET ( min_seg_duration ), AV_OPT_TYPE_DURATION , {. i64 = 0 }, 0 , INT64_MAX , E },
2012-01-28 22:36:38 +01:00
{ "segment_times" , "set segment split time points" , OFFSET ( times_str ), AV_OPT_TYPE_STRING ,{. str = NULL }, 0 , 0 , E },
2012-12-09 20:26:30 +01:00
{ "segment_frames" , "set segment split frame numbers" , OFFSET ( frames_str ), AV_OPT_TYPE_STRING ,{. str = NULL }, 0 , 0 , E },
2012-09-05 14:26:01 +02: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 15:42:04 +02: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 18:21:00 +01: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 14:59:00 +01: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 22:05:07 +01: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 17:50:00 +01:00
{ "increment_tc" , "increment timecode between each segment" , OFFSET ( increment_tc ), AV_OPT_TYPE_BOOL , {. i64 = 0 }, 0 , 1 , E },
2015-11-21 22:05:07 +01: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 13:45:50 +01:00
2015-11-21 22:05:07 +01: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-21 15:10:56 -08: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 14:28:38 +02:00
{ "initial_offset" , "set initial timestamp offset" , OFFSET ( initial_offset ), AV_OPT_TYPE_DURATION , {. i64 = 0 }, - INT64_MAX , INT64_MAX , E },
2016-03-06 20:48:57 -06:00
{ "write_empty_segments" , "allow writing empty 'filler' segments" , OFFSET ( write_empty ), AV_OPT_TYPE_BOOL , {. i64 = 0 }, 0 , 1 , E },
2011-10-02 23:05:29 +02:00
{ NULL },
};
static const AVClass seg_class = {
2021-06-07 16:13:33 +02:00
. class_name = "(stream) segment muxer" ,
2024-01-19 13:33:28 +01:00
. item_name = av_default_item_name ,
2011-10-02 23:05:29 +02:00
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
};
2021-06-07 16:13:33 +02:00
#if CONFIG_SEGMENT_MUXER
2023-01-27 15:06:00 +01:00
const FFOutputFormat ff_segment_muxer = {
. p . name = "segment" ,
. p . long_name = NULL_IF_CONFIG_SMALL ( "segment" ),
. p . flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER ,
. p . priv_class = & seg_class ,
2011-10-02 23:05:29 +02:00
. priv_data_size = sizeof ( SegmentContext ),
2016-03-23 09:24:22 -05:00
. init = seg_init ,
2016-04-07 19:18:45 -05:00
. write_header = seg_write_header ,
2011-10-02 23:05:29 +02:00
. write_packet = seg_write_packet ,
. write_trailer = seg_write_trailer ,
2016-04-07 19:18:19 -05:00
. deinit = seg_free ,
2016-10-26 22:03:02 -05:00
. check_bitstream = seg_check_bitstream ,
2011-10-02 23:05:29 +02:00
};
2018-02-07 16:42:11 -03:00
#endif
2012-01-13 15:38:13 +01:00
2018-02-07 16:42:11 -03:00
#if CONFIG_STREAM_SEGMENT_MUXER
2023-01-27 15:06:00 +01:00
const FFOutputFormat ff_stream_segment_muxer = {
. p . name = "stream_segment,ssegment" ,
. p . long_name = NULL_IF_CONFIG_SMALL ( "streaming segment muxer" ),
. p . flags = AVFMT_NOFILE ,
. p . priv_class = & seg_class ,
. priv_data_size = sizeof ( SegmentContext ),
2016-03-23 09:24:22 -05:00
. init = seg_init ,
2016-04-07 19:18:45 -05:00
. write_header = seg_write_header ,
2012-01-13 15:38:13 +01:00
. write_packet = seg_write_packet ,
. write_trailer = seg_write_trailer ,
2016-04-07 19:18:19 -05:00
. deinit = seg_free ,
2016-10-26 22:03:02 -05:00
. check_bitstream = seg_check_bitstream ,
2012-01-13 15:38:13 +01:00
};
2018-02-07 16:42:11 -03:00
#endif