2012-10-26 16:36:56 +02:00
/*
* Apple HTTP Live Streaming segmenter
* Copyright ( c ) 2012 , 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>
2013-11-23 21:32:55 +01:00
# include <stdint.h>
2012-10-26 16:36:56 +02:00
# include "libavutil/mathematics.h"
# include "libavutil/parseutils.h"
# include "libavutil/avstring.h"
# include "libavutil/opt.h"
# include "libavutil/log.h"
# include "avformat.h"
# include "internal.h"
typedef struct ListEntry {
char name [ 1024 ] ;
int duration ;
struct ListEntry * next ;
} ListEntry ;
typedef struct HLSContext {
const AVClass * class ; // Class for private options.
2012-12-29 12:31:01 +01:00
unsigned number ;
2012-12-29 12:09:17 +01:00
int64_t sequence ;
2014-04-29 10:13:34 +02:00
int64_t start_sequence ;
2012-10-26 16:36:56 +02:00
AVOutputFormat * oformat ;
AVFormatContext * avf ;
float time ; // Set by a private option.
int size ; // Set by a private option.
int wrap ; // Set by a private option.
2014-09-19 10:01:28 +02:00
int allowcache ;
2012-10-26 16:36:56 +02:00
int64_t recording_time ;
int has_video ;
int64_t start_pts ;
int64_t end_pts ;
2013-08-15 11:33:20 +02:00
int64_t duration ; // last segment duration computed so far, in seconds
2012-12-25 10:05:42 +01:00
int nb_entries ;
2012-10-26 16:36:56 +02:00
ListEntry * list ;
ListEntry * end_list ;
char * basename ;
2014-03-18 21:19:00 +01:00
char * baseurl ;
2012-10-26 16:36:56 +02:00
} HLSContext ;
static int hls_mux_init ( AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
AVFormatContext * oc ;
int i ;
hls - > avf = oc = avformat_alloc_context ( ) ;
if ( ! oc )
return AVERROR ( ENOMEM ) ;
oc - > oformat = hls - > 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 ;
2014-10-06 10:41:33 +02:00
st - > time_base = s - > streams [ i ] - > time_base ;
2012-10-26 16:36:56 +02:00
}
return 0 ;
}
static int append_entry ( HLSContext * hls , uint64_t duration )
{
ListEntry * en = av_malloc ( sizeof ( * en ) ) ;
if ( ! en )
return AVERROR ( ENOMEM ) ;
2012-12-25 09:14:59 +01:00
av_strlcpy ( en - > name , av_basename ( hls - > avf - > filename ) , sizeof ( en - > name ) ) ;
2012-10-26 16:36:56 +02:00
en - > duration = duration ;
en - > next = NULL ;
if ( ! hls - > list )
hls - > list = en ;
else
hls - > end_list - > next = en ;
hls - > end_list = en ;
2012-12-25 10:05:42 +01:00
if ( hls - > nb_entries > = hls - > size ) {
2012-10-26 16:36:56 +02:00
en = hls - > list ;
hls - > list = en - > next ;
av_free ( en ) ;
2012-12-25 10:05:42 +01:00
} else
hls - > nb_entries + + ;
2012-10-26 16:36:56 +02:00
2012-12-29 12:09:17 +01:00
hls - > sequence + + ;
2012-10-26 16:36:56 +02:00
return 0 ;
}
static void free_entries ( HLSContext * hls )
{
ListEntry * p = hls - > list , * en ;
while ( p ) {
en = p ;
p = p - > next ;
av_free ( en ) ;
}
}
static int hls_window ( AVFormatContext * s , int last )
{
HLSContext * hls = s - > priv_data ;
ListEntry * en ;
2012-12-21 12:05:46 +01:00
int target_duration = 0 ;
2012-10-26 16:36:56 +02:00
int ret = 0 ;
2015-02-20 12:54:57 +01:00
AVIOContext * out = NULL ;
2015-02-20 12:54:58 +01:00
char temp_filename [ 1024 ] ;
2014-04-29 18:37:01 +02:00
int64_t sequence = FFMAX ( hls - > start_sequence , hls - > sequence - hls - > size ) ;
2012-10-26 16:36:56 +02:00
2015-02-20 12:54:58 +01:00
snprintf ( temp_filename , sizeof ( temp_filename ) , " %s.tmp " , s - > filename ) ;
if ( ( ret = avio_open2 ( & out , temp_filename , AVIO_FLAG_WRITE ,
2012-10-26 16:36:56 +02:00
& s - > interrupt_callback , NULL ) ) < 0 )
goto fail ;
2012-12-21 12:05:46 +01:00
for ( en = hls - > list ; en ; en = en - > next ) {
if ( target_duration < en - > duration )
target_duration = en - > duration ;
}
2015-02-20 12:54:57 +01:00
avio_printf ( out , " #EXTM3U \n " ) ;
avio_printf ( out , " #EXT-X-VERSION:3 \n " ) ;
2014-09-19 10:01:28 +02:00
if ( hls - > allowcache = = 0 | | hls - > allowcache = = 1 ) {
2015-02-20 12:54:57 +01:00
avio_printf ( out , " #EXT-X-ALLOW-CACHE:%s \n " , hls - > allowcache = = 0 ? " NO " : " YES " ) ;
2014-09-19 10:01:28 +02:00
}
2015-02-20 12:54:57 +01:00
avio_printf ( out , " #EXT-X-TARGETDURATION:%d \n " , target_duration ) ;
avio_printf ( out , " #EXT-X-MEDIA-SEQUENCE:% " PRId64 " \n " , sequence ) ;
2012-10-26 16:36:56 +02:00
2014-04-29 10:07:03 +02:00
av_log ( s , AV_LOG_VERBOSE , " EXT-X-MEDIA-SEQUENCE:% " PRId64 " \n " ,
2014-04-29 18:37:01 +02:00
sequence ) ;
2014-04-29 10:07:03 +02:00
2012-10-26 16:36:56 +02:00
for ( en = hls - > list ; en ; en = en - > next ) {
2015-02-20 12:54:57 +01:00
avio_printf ( out , " #EXTINF:%d, \n " , en - > duration ) ;
2014-03-18 21:19:00 +01:00
if ( hls - > baseurl )
2015-02-20 12:54:57 +01:00
avio_printf ( out , " %s " , hls - > baseurl ) ;
avio_printf ( out , " %s \n " , en - > name ) ;
2012-10-26 16:36:56 +02:00
}
if ( last )
2015-02-20 12:54:57 +01:00
avio_printf ( out , " #EXT-X-ENDLIST \n " ) ;
2012-10-26 16:36:56 +02:00
fail :
2015-02-20 12:54:57 +01:00
avio_closep ( & out ) ;
2015-02-20 12:54:58 +01:00
if ( ret > = 0 )
ff_rename ( temp_filename , s - > filename ) ;
2012-10-26 16:36:56 +02:00
return ret ;
}
static int hls_start ( AVFormatContext * s )
{
HLSContext * c = s - > priv_data ;
AVFormatContext * oc = c - > avf ;
int err = 0 ;
if ( av_get_frame_filename ( oc - > filename , sizeof ( oc - > filename ) ,
2014-04-29 18:20:17 +02:00
c - > basename , c - > wrap ? c - > sequence % c - > wrap : c - > sequence ) < 0 )
2012-10-26 16:36:56 +02:00
return AVERROR ( EINVAL ) ;
2013-06-06 12:09:38 +02:00
c - > number + + ;
2012-10-26 16:36:56 +02:00
if ( ( err = avio_open2 ( & oc - > pb , oc - > filename , AVIO_FLAG_WRITE ,
& s - > interrupt_callback , NULL ) ) < 0 )
return err ;
if ( oc - > oformat - > priv_class & & oc - > priv_data )
av_opt_set ( oc - > priv_data , " mpegts_flags " , " resend_headers " , 0 ) ;
return 0 ;
}
static int hls_write_header ( AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
int ret , i ;
char * p ;
const char * pattern = " %d.ts " ;
2012-12-21 00:27:00 +01:00
int basename_size = strlen ( s - > filename ) + strlen ( pattern ) + 1 ;
2012-10-26 16:36:56 +02:00
2014-04-29 10:13:34 +02:00
hls - > sequence = hls - > start_sequence ;
2012-12-29 11:44:33 +01:00
hls - > recording_time = hls - > time * AV_TIME_BASE ;
2012-10-26 16:36:56 +02:00
hls - > start_pts = AV_NOPTS_VALUE ;
for ( i = 0 ; i < s - > nb_streams ; i + + )
hls - > has_video + =
s - > streams [ i ] - > codec - > codec_type = = AVMEDIA_TYPE_VIDEO ;
if ( hls - > has_video > 1 )
av_log ( s , AV_LOG_WARNING ,
" More than a single video stream present, "
" expect issues decoding it. \n " ) ;
hls - > oformat = av_guess_format ( " mpegts " , NULL , NULL ) ;
if ( ! hls - > oformat ) {
ret = AVERROR_MUXER_NOT_FOUND ;
goto fail ;
}
hls - > basename = av_malloc ( basename_size ) ;
if ( ! hls - > basename ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
strcpy ( hls - > basename , s - > filename ) ;
p = strrchr ( hls - > basename , ' . ' ) ;
if ( p )
* p = ' \0 ' ;
2012-12-23 20:34:06 +01:00
av_strlcat ( hls - > basename , pattern , basename_size ) ;
2012-10-26 16:36:56 +02:00
if ( ( ret = hls_mux_init ( s ) ) < 0 )
goto fail ;
if ( ( ret = hls_start ( s ) ) < 0 )
goto fail ;
if ( ( ret = avformat_write_header ( hls - > avf , NULL ) ) < 0 )
return ret ;
fail :
if ( ret ) {
av_free ( hls - > basename ) ;
if ( hls - > avf )
avformat_free_context ( hls - > avf ) ;
}
return ret ;
}
static int hls_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
HLSContext * hls = s - > priv_data ;
AVFormatContext * oc = hls - > avf ;
AVStream * st = s - > streams [ pkt - > stream_index ] ;
int64_t end_pts = hls - > recording_time * hls - > number ;
2013-04-26 09:54:59 +02:00
int ret , can_split = 1 ;
2012-10-26 16:36:56 +02:00
if ( hls - > start_pts = = AV_NOPTS_VALUE ) {
hls - > start_pts = pkt - > pts ;
hls - > end_pts = pkt - > pts ;
}
2013-04-26 09:54:59 +02:00
if ( hls - > has_video ) {
can_split = st - > codec - > codec_type = = AVMEDIA_TYPE_VIDEO & &
pkt - > flags & AV_PKT_FLAG_KEY ;
}
2013-08-15 11:33:20 +02:00
if ( pkt - > pts = = AV_NOPTS_VALUE )
can_split = 0 ;
else
hls - > duration = av_rescale ( pkt - > pts - hls - > end_pts ,
st - > time_base . num , st - > time_base . den ) ;
2012-10-26 16:36:56 +02:00
2013-04-26 09:54:59 +02:00
if ( can_split & & av_compare_ts ( pkt - > pts - hls - > start_pts , st - > time_base ,
end_pts , AV_TIME_BASE_Q ) > = 0 ) {
2013-08-15 11:33:20 +02:00
ret = append_entry ( hls , hls - > duration ) ;
2012-12-25 08:59:38 +01:00
if ( ret )
return ret ;
2012-10-26 16:36:56 +02:00
hls - > end_pts = pkt - > pts ;
2013-08-15 11:33:20 +02:00
hls - > duration = 0 ;
2012-10-26 16:36:56 +02:00
av_write_frame ( oc , NULL ) ; /* Flush any buffered data */
avio_close ( oc - > pb ) ;
ret = hls_start ( s ) ;
if ( ret )
return ret ;
oc = hls - > avf ;
if ( ( ret = hls_window ( s , 0 ) ) < 0 )
return ret ;
}
ret = ff_write_chained ( oc , pkt - > stream_index , pkt , s ) ;
return ret ;
}
static int hls_write_trailer ( struct AVFormatContext * s )
{
HLSContext * hls = s - > priv_data ;
AVFormatContext * oc = hls - > avf ;
av_write_trailer ( oc ) ;
avio_closep ( & oc - > pb ) ;
avformat_free_context ( oc ) ;
av_free ( hls - > basename ) ;
2013-08-15 11:33:20 +02:00
append_entry ( hls , hls - > duration ) ;
2012-10-26 16:36:56 +02:00
hls_window ( s , 1 ) ;
free_entries ( hls ) ;
return 0 ;
}
# define OFFSET(x) offsetof(HLSContext, x)
# define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options [ ] = {
2014-04-29 10:13:34 +02:00
{ " start_number " , " first number in the sequence " , OFFSET ( start_sequence ) , AV_OPT_TYPE_INT64 , { . i64 = 0 } , 0 , INT64_MAX , E } ,
2012-10-26 16:36:56 +02:00
{ " hls_time " , " segment length in seconds " , OFFSET ( time ) , AV_OPT_TYPE_FLOAT , { . dbl = 2 } , 0 , FLT_MAX , E } ,
{ " hls_list_size " , " maximum number of playlist entries " , OFFSET ( size ) , AV_OPT_TYPE_INT , { . i64 = 5 } , 0 , INT_MAX , E } ,
{ " hls_wrap " , " number after which the index wraps " , OFFSET ( wrap ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , E } ,
2014-09-19 10:01:28 +02:00
{ " hls_allow_cache " , " explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments " , OFFSET ( allowcache ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , INT_MIN , INT_MAX , E } ,
2014-03-18 21:19:00 +01:00
{ " hls_base_url " , " url to prepend to each playlist entry " , OFFSET ( baseurl ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , E } ,
2012-10-26 16:36:56 +02:00
{ NULL } ,
} ;
static const AVClass hls_class = {
. class_name = " hls muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
AVOutputFormat ff_hls_muxer = {
. name = " hls " ,
2012-12-08 06:00:46 +01:00
. long_name = NULL_IF_CONFIG_SMALL ( " Apple HTTP Live Streaming " ) ,
2012-10-26 16:36:56 +02:00
. extensions = " m3u8 " ,
. priv_data_size = sizeof ( HLSContext ) ,
2014-05-15 20:43:46 +02:00
. audio_codec = AV_CODEC_ID_AAC ,
. video_codec = AV_CODEC_ID_H264 ,
2012-10-26 16:36:56 +02:00
. flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH ,
. write_header = hls_write_header ,
. write_packet = hls_write_packet ,
. write_trailer = hls_write_trailer ,
. priv_class = & hls_class ,
} ;