2011-10-02 23:05:29 +02:00
/*
* Generic segmenter
* Copyright (c) 2011, Luca Barbato
*
* This file is part of Libav.
*
* Libav is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Libav is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <float.h>
#include "avformat.h"
#include "internal.h"
#include "libavutil/log.h"
#include "libavutil/opt.h"
#include "libavutil/avstring.h"
#include "libavutil/parseutils.h"
#include "libavutil/mathematics.h"
typedef struct {
const AVClass * class ; /**< Class for private options. */
int number ;
2012-10-04 00:15:35 +03:00
AVOutputFormat * oformat ;
2011-10-02 23:05:29 +02:00
AVFormatContext * avf ;
char * format ; /**< Set by a private option. */
char * list ; /**< Set by a private option. */
2012-10-09 02:49:42 +02:00
int list_type ; /**< Set by a private option. */
2011-10-02 23:05:29 +02:00
float time ; /**< Set by a private option. */
int size ; /**< Set by a private option. */
2012-02-25 21:45:23 +01:00
int wrap ; /**< Set by a private option. */
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. */
2011-10-02 23:05:29 +02:00
int64_t offset_time ;
int64_t recording_time ;
int has_video ;
AVIOContext * pb ;
} SegmentContext ;
2012-10-09 02:49:42 +02:00
enum {
LIST_FLAT ,
LIST_HLS
};
2012-10-04 00:15:35 +03:00
static int segment_mux_init ( AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc ;
int i ;
seg -> avf = oc = avformat_alloc_context ();
if ( ! oc )
return AVERROR ( ENOMEM );
oc -> oformat = seg -> oformat ;
oc -> interrupt_callback = s -> interrupt_callback ;
for ( i = 0 ; i < s -> nb_streams ; i ++ ) {
AVStream * st ;
if ( ! ( st = avformat_new_stream ( oc , NULL )))
return AVERROR ( ENOMEM );
avcodec_copy_context ( st -> codec , s -> streams [ i ] -> codec );
st -> sample_aspect_ratio = s -> streams [ i ] -> sample_aspect_ratio ;
}
return 0 ;
}
2012-10-09 02:49:42 +02:00
static int segment_hls_window ( AVFormatContext * s , int last )
{
SegmentContext * seg = s -> priv_data ;
int i , ret = 0 ;
char buf [ 1024 ];
if (( ret = avio_open2 ( & seg -> pb , seg -> list , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
goto fail ;
avio_printf ( seg -> pb , "#EXTM3U \n " );
avio_printf ( seg -> pb , "#EXT-X-VERSION:3 \n " );
avio_printf ( seg -> pb , "#EXT-X-TARGETDURATION:%d \n " , ( int ) seg -> time );
avio_printf ( seg -> pb , "#EXT-X-MEDIA-SEQUENCE:%d \n " ,
FFMAX ( 0 , seg -> number - seg -> size ));
for ( i = FFMAX ( 0 , seg -> number - seg -> size );
i < seg -> number ; i ++ ) {
avio_printf ( seg -> pb , "#EXTINF:%d, \n " , ( int ) seg -> time );
av_get_frame_filename ( buf , sizeof ( buf ), s -> filename , i );
avio_printf ( seg -> pb , "%s \n " , buf );
}
if ( last )
avio_printf ( seg -> pb , "#EXT-X-ENDLIST \n " );
fail :
avio_closep ( & seg -> pb );
return ret ;
}
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
{
SegmentContext * c = s -> priv_data ;
AVFormatContext * oc = c -> avf ;
int err = 0 ;
2012-10-02 23:49:46 +02:00
if ( write_header ) {
avformat_free_context ( oc );
c -> avf = NULL ;
if (( err = segment_mux_init ( s )) < 0 )
return err ;
oc = c -> avf ;
}
2012-10-04 00:15:35 +03:00
2012-02-25 21:45:23 +01:00
if ( c -> wrap )
c -> number %= c -> wrap ;
2011-10-02 23:05:29 +02:00
if ( av_get_frame_filename ( oc -> filename , sizeof ( oc -> filename ),
s -> filename , c -> number ++ ) < 0 )
return AVERROR ( EINVAL );
if (( err = avio_open2 ( & oc -> pb , oc -> filename , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
return err ;
2012-10-02 23:51:46 +02:00
if ( oc -> oformat -> priv_class && oc -> priv_data )
2012-10-04 15:28:30 +03:00
av_opt_set ( oc -> priv_data , "resend_headers" , "1" , 0 ); /* mpegts specific */
2012-10-02 23:51:46 +02:00
2012-10-02 23:49:46 +02:00
if ( write_header ) {
if (( err = avformat_write_header ( oc , NULL )) < 0 )
return err ;
}
2011-10-02 23:05:29 +02:00
return 0 ;
}
2012-10-02 23:49:46 +02:00
static int segment_end ( AVFormatContext * oc , int write_trailer )
2011-10-02 23:05:29 +02:00
{
int ret = 0 ;
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 )
av_write_trailer ( oc );
2011-10-02 23:05:29 +02:00
avio_close ( oc -> pb );
return ret ;
}
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 );
* ctx = avio_alloc_context ( buf , buf_size , AVIO_FLAG_WRITE , NULL , NULL , NULL , NULL );
if ( !* ctx ) {
av_free ( buf );
return AVERROR ( ENOMEM );
}
return 0 ;
}
static void close_null_ctx ( AVIOContext * pb )
{
av_free ( pb -> buffer );
av_free ( pb );
}
2011-10-02 23:05:29 +02:00
static int seg_write_header ( AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
2012-10-04 00:15:35 +03:00
AVFormatContext * oc = NULL ;
2011-10-02 23:05:29 +02:00
int ret , i ;
seg -> number = 0 ;
seg -> offset_time = 0 ;
seg -> recording_time = seg -> time * 1000000 ;
2012-10-02 23:59:35 +02:00
if ( ! seg -> write_header_trailer )
seg -> individual_header_trailer = 0 ;
2011-10-02 23:05:29 +02:00
2012-10-09 02:49:42 +02:00
if ( seg -> list && seg -> list_type != LIST_HLS )
2011-10-02 23:05:29 +02:00
if (( ret = avio_open2 ( & seg -> pb , seg -> list , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
2012-04-27 11:09:30 -07:00
goto fail ;
2011-10-02 23:05:29 +02:00
2012-10-04 00:03:45 +03:00
for ( i = 0 ; i < s -> nb_streams ; i ++ )
2011-10-02 23:05:29 +02:00
seg -> has_video +=
( s -> streams [ i ] -> codec -> codec_type == AVMEDIA_TYPE_VIDEO );
if ( seg -> has_video > 1 )
av_log ( s , AV_LOG_WARNING ,
"More than a single video stream present, "
"expect issues decoding it. \n " );
2012-10-04 00:15:35 +03:00
seg -> oformat = av_guess_format ( seg -> format , s -> filename , NULL );
2011-10-02 23:05:29 +02:00
2012-10-04 00:15:35 +03:00
if ( ! seg -> oformat ) {
2011-10-02 23:05:29 +02:00
ret = AVERROR_MUXER_NOT_FOUND ;
goto fail ;
}
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 " ,
oc -> oformat -> name );
ret = AVERROR ( EINVAL );
goto fail ;
}
2012-10-04 00:15:35 +03:00
if (( ret = segment_mux_init ( s )) < 0 )
goto fail ;
oc = seg -> avf ;
2011-10-02 23:05:29 +02:00
if ( av_get_frame_filename ( oc -> filename , sizeof ( oc -> filename ),
s -> filename , seg -> number ++ ) < 0 ) {
ret = AVERROR ( EINVAL );
goto fail ;
}
2012-10-02 23:59:35 +02:00
if ( seg -> write_header_trailer ) {
if (( ret = avio_open2 ( & oc -> pb , oc -> filename , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
goto fail ;
} else {
if (( ret = open_null_ctx ( & oc -> pb )) < 0 )
goto fail ;
}
2011-10-02 23:05:29 +02:00
if (( ret = avformat_write_header ( oc , NULL )) < 0 ) {
avio_close ( oc -> pb );
goto fail ;
}
2012-10-02 23:59:35 +02:00
if ( ! seg -> write_header_trailer ) {
close_null_ctx ( oc -> pb );
if (( ret = avio_open2 ( & oc -> pb , oc -> filename , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
goto fail ;
}
2011-10-02 23:05:29 +02:00
if ( seg -> list ) {
2012-10-09 02:49:42 +02:00
if ( seg -> list_type == LIST_HLS ) {
if (( ret = segment_hls_window ( s , 0 )) < 0 )
goto fail ;
} else {
avio_printf ( seg -> pb , "%s \n " , oc -> filename );
avio_flush ( seg -> pb );
}
2011-10-02 23:05:29 +02:00
}
fail :
if ( ret ) {
if ( seg -> list )
avio_close ( seg -> pb );
2012-10-04 00:15:35 +03:00
if ( seg -> avf )
avformat_free_context ( seg -> avf );
2011-10-02 23:05:29 +02:00
}
return ret ;
}
static int seg_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2012-02-26 01:39:32 +02:00
AVStream * st = s -> streams [ pkt -> stream_index ];
2011-10-02 23:05:29 +02:00
int64_t end_pts = seg -> recording_time * seg -> number ;
int ret ;
if (( seg -> has_video && st -> codec -> codec_type == AVMEDIA_TYPE_VIDEO ) &&
av_compare_ts ( pkt -> pts , st -> time_base ,
end_pts , AV_TIME_BASE_Q ) >= 0 &&
pkt -> flags & AV_PKT_FLAG_KEY ) {
av_log ( s , AV_LOG_DEBUG , "Next segment starts at %d %" PRId64 " \n " ,
pkt -> stream_index , pkt -> pts );
2012-10-02 23:49:46 +02:00
ret = segment_end ( oc , seg -> individual_header_trailer );
2011-10-02 23:05:29 +02:00
if ( ! ret )
2012-10-02 23:49:46 +02:00
ret = segment_start ( s , seg -> individual_header_trailer );
2011-10-02 23:05:29 +02:00
if ( ret )
goto fail ;
2012-10-04 00:15:35 +03:00
oc = seg -> avf ;
2011-10-02 23:05:29 +02:00
if ( seg -> list ) {
2012-10-09 02:49:42 +02:00
if ( seg -> list_type == LIST_HLS ) {
if (( ret = segment_hls_window ( s , 0 )) < 0 )
2011-10-02 23:05:29 +02:00
goto fail ;
2012-10-09 02:49:42 +02:00
} else {
avio_printf ( seg -> pb , "%s \n " , oc -> filename );
avio_flush ( seg -> pb );
if ( seg -> size && ! ( seg -> number % seg -> size )) {
avio_closep ( & seg -> pb );
if (( ret = avio_open2 ( & seg -> pb , seg -> list , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL )) < 0 )
goto fail ;
}
2011-10-02 23:05:29 +02:00
}
}
}
2012-02-26 01:39:32 +02:00
ret = ff_write_chained ( oc , pkt -> stream_index , pkt , s );
2011-10-02 23:05:29 +02:00
fail :
if ( ret < 0 ) {
if ( seg -> list )
avio_close ( seg -> pb );
avformat_free_context ( oc );
}
return ret ;
}
static int seg_write_trailer ( struct AVFormatContext * s )
{
SegmentContext * seg = s -> priv_data ;
AVFormatContext * oc = seg -> avf ;
2012-10-02 23:59:35 +02:00
int ret ;
if ( ! seg -> write_header_trailer ) {
2012-10-09 02:49:42 +02:00
if (( ret = segment_end ( oc , 0 )) < 0 )
goto fail ;
2012-10-02 23:59:35 +02:00
open_null_ctx ( & oc -> pb );
2012-10-09 02:49:42 +02:00
ret = av_write_trailer ( oc );
2012-10-02 23:59:35 +02:00
close_null_ctx ( oc -> pb );
} else {
ret = segment_end ( oc , 1 );
}
2012-10-09 02:49:42 +02:00
if ( ret < 0 )
goto fail ;
if ( seg -> list && seg -> list_type == LIST_HLS ) {
if (( ret = segment_hls_window ( s , 1 ) < 0 ))
goto fail ;
}
fail :
avio_close ( seg -> pb );
2011-10-02 23:05:29 +02:00
avformat_free_context ( oc );
return ret ;
}
#define OFFSET(x) offsetof(SegmentContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [] = {
{ "segment_format" , "container format used for the segments" , OFFSET ( format ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , E },
{ "segment_time" , "segment length in seconds" , OFFSET ( time ), AV_OPT_TYPE_FLOAT , {. dbl = 2 }, 0 , FLT_MAX , E },
{ "segment_list" , "output the segment list" , OFFSET ( list ), AV_OPT_TYPE_STRING , {. str = NULL }, 0 , 0 , E },
2012-08-31 13:22:31 +03:00
{ "segment_list_size" , "maximum number of playlist entries" , OFFSET ( size ), AV_OPT_TYPE_INT , {. i64 = 5 }, 0 , INT_MAX , E },
2012-10-09 02:49:42 +02:00
{ "segment_list_type" , "segment list format" , OFFSET ( list_type ), AV_OPT_TYPE_INT , {. i64 = LIST_FLAT }, 0 , 2 , E , "list_type" },
{ "flat" , "plain list (default)" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_FLAT }, 0 , 0 , E , "list_type" },
{ "hls" , "Apple HTTP Live Streaming compatible" , 0 , AV_OPT_TYPE_CONST , {. i64 = LIST_HLS }, 0 , 0 , E , "list_type" },
2012-08-31 13:22:31 +03:00
{ "segment_wrap" , "number after which the index wraps" , OFFSET ( wrap ), AV_OPT_TYPE_INT , {. i64 = 0 }, 0 , INT_MAX , E },
2012-10-02 23:49:46 +02:00
{ "individual_header_trailer" , "write header/trailer to each segment" , OFFSET ( individual_header_trailer ), AV_OPT_TYPE_INT , {. i64 = 1 }, 0 , 1 , E },
2012-10-02 23:59:35 +02:00
{ "write_header_trailer" , "write a header to the first segment and a trailer to the last one" , OFFSET ( write_header_trailer ), AV_OPT_TYPE_INT , {. i64 = 1 }, 0 , 1 , E },
2011-10-02 23:05:29 +02:00
{ NULL },
};
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 03:23:48 +02:00
. long_name = NULL_IF_CONFIG_SMALL ( "segment" ),
2011-10-02 23:05:29 +02:00
. priv_data_size = sizeof ( SegmentContext ),
2012-10-05 05:07:18 +02:00
. flags = AVFMT_NOFILE ,
2011-10-02 23:05:29 +02:00
. write_header = seg_write_header ,
. write_packet = seg_write_packet ,
. write_trailer = seg_write_trailer ,
. priv_class = & seg_class ,
};