2015-03-30 23:46:10 +02:00
/*
* Copyright ( c ) 2015 , Vignesh Venkatasubramanian
*
* This file is part of FFmpeg .
*
* FFmpeg 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 .
*
* FFmpeg 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 FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file WebM Chunk Muxer
* The chunk muxer enables writing WebM Live chunks where there is a header
* chunk , followed by data chunks where each Cluster is written out as a Chunk .
*/
# include "avformat.h"
# include "avio.h"
2016-01-30 03:17:51 +02:00
# include "avio_internal.h"
2015-03-30 23:46:10 +02:00
# include "internal.h"
2022-05-06 17:13:55 +02:00
# include "mux.h"
2015-03-30 23:46:10 +02:00
# include "libavutil/log.h"
# include "libavutil/opt.h"
# include "libavutil/mathematics.h"
2015-04-30 20:34:44 +02:00
# define MAX_FILENAME_SIZE 1024
2015-03-30 23:46:10 +02:00
typedef struct WebMChunkContext {
const AVClass * class ;
char * header_filename ;
int chunk_duration ;
int chunk_index ;
2016-09-15 23:29:24 +02:00
char * http_method ;
2015-03-30 23:46:10 +02:00
uint64_t duration_written ;
2019-04-20 00:03:16 +02:00
int64_t prev_pts ;
2015-03-30 23:46:10 +02:00
AVFormatContext * avf ;
2020-05-17 21:37:42 +02:00
int header_written ;
2015-03-30 23:46:10 +02:00
} WebMChunkContext ;
2020-03-01 01:33:18 +02:00
static int webm_chunk_init ( AVFormatContext * s )
2015-03-30 23:46:10 +02:00
{
WebMChunkContext * wc = s - > priv_data ;
2021-02-25 04:11:32 +02:00
const AVOutputFormat * oformat ;
2015-03-30 23:46:10 +02:00
AVFormatContext * oc ;
2020-02-29 23:47:26 +02:00
AVStream * st , * ost = s - > streams [ 0 ] ;
AVDictionary * dict = NULL ;
2015-03-30 23:46:10 +02:00
int ret ;
2020-03-01 01:33:18 +02:00
// DASH Streams can only have one track per file.
if ( s - > nb_streams ! = 1 )
return AVERROR ( EINVAL ) ;
2020-03-01 01:13:07 +02:00
if ( ! wc - > header_filename ) {
av_log ( s , AV_LOG_ERROR , " No header filename provided \n " ) ;
return AVERROR ( EINVAL ) ;
}
2020-03-01 01:33:18 +02:00
wc - > prev_pts = AV_NOPTS_VALUE ;
2020-03-01 00:49:19 +02:00
oformat = av_guess_format ( " webm " , s - > url , " video/webm " ) ;
if ( ! oformat )
return AVERROR_MUXER_NOT_FOUND ;
ret = avformat_alloc_output_context2 ( & wc - > avf , oformat , NULL , NULL ) ;
2015-03-30 23:46:10 +02:00
if ( ret < 0 )
return ret ;
oc = wc - > avf ;
2020-03-01 01:13:07 +02:00
ff_format_set_url ( oc , wc - > header_filename ) ;
wc - > header_filename = NULL ;
2020-03-01 06:32:42 +02:00
oc - > interrupt_callback = s - > interrupt_callback ;
oc - > max_delay = s - > max_delay ;
2020-03-01 05:56:07 +02:00
oc - > flags = s - > flags & ~ AVFMT_FLAG_FLUSH_PACKETS ;
2020-02-29 22:09:29 +02:00
oc - > strict_std_compliance = s - > strict_std_compliance ;
2020-02-29 23:47:26 +02:00
oc - > avoid_negative_ts = s - > avoid_negative_ts ;
2020-02-29 22:09:29 +02:00
2020-03-01 05:56:07 +02:00
oc - > flush_packets = 0 ;
2020-03-01 02:29:57 +02:00
if ( ( ret = av_dict_copy ( & oc - > metadata , s - > metadata , 0 ) ) < 0 )
return ret ;
2015-03-30 23:46:10 +02:00
2022-08-07 01:35:19 +02:00
st = ff_stream_clone ( oc , ost ) ;
if ( ! st )
2020-02-29 23:47:26 +02:00
return AVERROR ( ENOMEM ) ;
2020-05-17 21:37:42 +02:00
if ( wc - > http_method )
if ( ( ret = av_dict_set ( & dict , " method " , wc - > http_method , 0 ) ) < 0 )
return ret ;
ret = s - > io_open ( s , & oc - > pb , oc - > url , AVIO_FLAG_WRITE , & dict ) ;
av_dict_free ( & dict ) ;
if ( ret < 0 )
return ret ;
oc - > pb - > seekable = 0 ;
2020-03-01 02:29:57 +02:00
if ( ( ret = av_dict_set_int ( & dict , " dash " , 1 , 0 ) ) < 0 | |
( ret = av_dict_set_int ( & dict , " cluster_time_limit " ,
wc - > chunk_duration , 0 ) ) < 0 | |
( ret = av_dict_set_int ( & dict , " live " , 1 , 0 ) ) < 0 )
goto fail ;
2020-02-29 23:47:26 +02:00
ret = avformat_init_output ( oc , & dict ) ;
2020-03-01 02:29:57 +02:00
fail :
2020-02-29 23:47:26 +02:00
av_dict_free ( & dict ) ;
if ( ret < 0 )
return ret ;
// Copy the timing info back to the original stream
// so that the timestamps of the packets are directly usable
avpriv_set_pts_info ( ost , st - > pts_wrap_bits , st - > time_base . num ,
st - > time_base . den ) ;
2015-03-30 23:46:10 +02:00
2020-02-29 23:47:26 +02:00
// This ensures that the timestamps will already be properly shifted
// when the packets arrive here, so we don't need to shift again.
s - > avoid_negative_ts = oc - > avoid_negative_ts ;
2021-08-24 14:58:07 +02:00
ffformatcontext ( s ) - > avoid_negative_ts_use_pts =
ffformatcontext ( oc ) - > avoid_negative_ts_use_pts ;
2022-01-18 17:17:09 +02:00
oc - > avoid_negative_ts = AVFMT_AVOID_NEG_TS_DISABLED ;
2022-09-01 14:19:07 +02:00
ffformatcontext ( oc ) - > avoid_negative_ts_status = AVOID_NEGATIVE_TS_DISABLED ;
2015-03-30 23:46:10 +02:00
return 0 ;
}
2020-03-01 01:13:07 +02:00
static int get_chunk_filename ( AVFormatContext * s , char filename [ MAX_FILENAME_SIZE ] )
2015-03-30 23:46:10 +02:00
{
WebMChunkContext * wc = s - > priv_data ;
2015-04-30 20:34:44 +02:00
if ( ! filename ) {
return AVERROR ( EINVAL ) ;
}
2020-03-01 06:32:42 +02:00
if ( av_get_frame_filename ( filename , MAX_FILENAME_SIZE ,
s - > url , wc - > chunk_index - 1 ) < 0 ) {
av_log ( s , AV_LOG_ERROR , " Invalid chunk filename template '%s' \n " , s - > url ) ;
return AVERROR ( EINVAL ) ;
}
2015-03-30 23:46:10 +02:00
return 0 ;
}
static int webm_chunk_write_header ( AVFormatContext * s )
{
WebMChunkContext * wc = s - > priv_data ;
2020-03-01 01:33:18 +02:00
AVFormatContext * oc = wc - > avf ;
2022-09-01 14:19:07 +02:00
AVStream * st = s - > streams [ 0 ] , * ost = oc - > streams [ 0 ] ;
2015-03-30 23:46:10 +02:00
int ret ;
2020-02-29 23:47:26 +02:00
ret = avformat_write_header ( oc , NULL ) ;
2020-02-29 22:00:17 +02:00
ff_format_io_close ( s , & oc - > pb ) ;
2022-09-01 14:19:07 +02:00
ffstream ( st ) - > lowest_ts_allowed = ffstream ( ost ) - > lowest_ts_allowed ;
ffstream ( ost ) - > lowest_ts_allowed = 0 ;
2020-05-17 21:37:42 +02:00
wc - > header_written = 1 ;
2015-03-30 23:46:10 +02:00
if ( ret < 0 )
return ret ;
return 0 ;
}
static int chunk_start ( AVFormatContext * s )
{
WebMChunkContext * wc = s - > priv_data ;
AVFormatContext * oc = wc - > avf ;
int ret ;
2015-04-30 20:34:44 +02:00
ret = avio_open_dyn_buf ( & oc - > pb ) ;
2015-03-30 23:46:10 +02:00
if ( ret < 0 )
return ret ;
wc - > chunk_index + + ;
return 0 ;
}
2019-04-20 00:03:15 +02:00
static int chunk_end ( AVFormatContext * s , int flush )
2015-03-30 23:46:10 +02:00
{
WebMChunkContext * wc = s - > priv_data ;
AVFormatContext * oc = wc - > avf ;
int ret ;
2015-04-30 20:34:44 +02:00
int buffer_size ;
uint8_t * buffer ;
AVIOContext * pb ;
char filename [ MAX_FILENAME_SIZE ] ;
2016-09-15 23:29:24 +02:00
AVDictionary * options = NULL ;
2015-03-30 23:46:10 +02:00
2019-04-20 00:03:15 +02:00
if ( ! oc - > pb )
2015-03-30 23:46:10 +02:00
return 0 ;
2019-04-20 00:03:15 +02:00
if ( flush )
// Flush the cluster in WebM muxer.
2020-02-29 23:47:26 +02:00
av_write_frame ( oc , NULL ) ;
2015-04-30 20:34:44 +02:00
buffer_size = avio_close_dyn_buf ( oc - > pb , & buffer ) ;
2019-04-20 00:03:15 +02:00
oc - > pb = NULL ;
2020-03-01 01:13:07 +02:00
ret = get_chunk_filename ( s , filename ) ;
2015-03-30 23:46:10 +02:00
if ( ret < 0 )
2015-04-30 20:34:44 +02:00
goto fail ;
2016-09-15 23:29:24 +02:00
if ( wc - > http_method )
2020-03-01 02:29:57 +02:00
if ( ( ret = av_dict_set ( & options , " method " , wc - > http_method , 0 ) ) < 0 )
goto fail ;
2016-09-15 23:29:24 +02:00
ret = s - > io_open ( s , & pb , filename , AVIO_FLAG_WRITE , & options ) ;
2020-03-01 02:29:57 +02:00
av_dict_free ( & options ) ;
2015-04-30 20:34:44 +02:00
if ( ret < 0 )
goto fail ;
avio_write ( pb , buffer , buffer_size ) ;
2016-02-10 16:40:32 +02:00
ff_format_io_close ( s , & pb ) ;
2015-04-30 20:34:44 +02:00
fail :
av_free ( buffer ) ;
return ( ret < 0 ) ? ret : 0 ;
2015-03-30 23:46:10 +02:00
}
static int webm_chunk_write_packet ( AVFormatContext * s , AVPacket * pkt )
{
WebMChunkContext * wc = s - > priv_data ;
AVFormatContext * oc = wc - > avf ;
AVStream * st = s - > streams [ pkt - > stream_index ] ;
int ret ;
2016-04-10 21:58:15 +02:00
if ( st - > codecpar - > codec_type = = AVMEDIA_TYPE_AUDIO ) {
2019-04-20 00:03:16 +02:00
if ( wc - > prev_pts ! = AV_NOPTS_VALUE )
wc - > duration_written + = av_rescale_q ( pkt - > pts - wc - > prev_pts ,
st - > time_base ,
( AVRational ) { 1 , 1000 } ) ;
2015-03-30 23:46:10 +02:00
wc - > prev_pts = pkt - > pts ;
}
// For video, a new chunk is started only on key frames. For audio, a new
2019-04-20 00:03:15 +02:00
// chunk is started based on chunk_duration. Also, a new chunk is started
// unconditionally if there is no currently open chunk.
if ( ! oc - > pb | | ( st - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO & &
2015-03-30 23:46:10 +02:00
( pkt - > flags & AV_PKT_FLAG_KEY ) ) | |
2016-04-10 21:58:15 +02:00
( st - > codecpar - > codec_type = = AVMEDIA_TYPE_AUDIO & &
2019-04-20 00:03:15 +02:00
wc - > duration_written > = wc - > chunk_duration ) ) {
2015-03-30 23:46:10 +02:00
wc - > duration_written = 0 ;
2019-04-20 00:03:15 +02:00
if ( ( ret = chunk_end ( s , 1 ) ) < 0 | | ( ret = chunk_start ( s ) ) < 0 ) {
return ret ;
2015-03-30 23:46:10 +02:00
}
}
2020-02-29 23:47:26 +02:00
// We only have one stream, so use the non-interleaving av_write_frame.
return av_write_frame ( oc , pkt ) ;
2015-03-30 23:46:10 +02:00
}
static int webm_chunk_write_trailer ( AVFormatContext * s )
{
WebMChunkContext * wc = s - > priv_data ;
AVFormatContext * oc = wc - > avf ;
2019-04-20 00:03:15 +02:00
int ret ;
if ( ! oc - > pb ) {
ret = chunk_start ( s ) ;
if ( ret < 0 )
2020-03-01 01:50:15 +02:00
return ret ;
2019-04-20 00:03:15 +02:00
}
2020-03-01 02:29:57 +02:00
ret = av_write_trailer ( oc ) ;
if ( ret < 0 )
return ret ;
2020-03-01 01:50:15 +02:00
return chunk_end ( s , 0 ) ;
}
static void webm_chunk_deinit ( AVFormatContext * s )
{
WebMChunkContext * wc = s - > priv_data ;
if ( ! wc - > avf )
return ;
2020-05-17 21:37:42 +02:00
if ( wc - > header_written )
ffio_free_dyn_buf ( & wc - > avf - > pb ) ;
else
ff_format_io_close ( s , & wc - > avf - > pb ) ;
2020-03-01 01:50:15 +02:00
avformat_free_context ( wc - > avf ) ;
wc - > avf = NULL ;
2015-03-30 23:46:10 +02:00
}
# define OFFSET(x) offsetof(WebMChunkContext, x)
static const AVOption options [ ] = {
2020-03-01 01:23:38 +02:00
{ " chunk_start_index " , " start index of the chunk " , OFFSET ( chunk_index ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
2020-02-29 22:52:08 +02:00
{ " header " , " filename of the header where the initialization data will be written " , OFFSET ( header_filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , AV_OPT_FLAG_ENCODING_PARAM } ,
2015-03-30 23:46:10 +02:00
{ " audio_chunk_duration " , " duration of each chunk in milliseconds " , OFFSET ( chunk_duration ) , AV_OPT_TYPE_INT , { . i64 = 5000 } , 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM } ,
2016-09-15 23:29:24 +02:00
{ " method " , " set the HTTP method " , OFFSET ( http_method ) , AV_OPT_TYPE_STRING , { . str = NULL } , 0 , 0 , AV_OPT_FLAG_ENCODING_PARAM } ,
2015-03-30 23:46:10 +02:00
{ NULL } ,
} ;
static const AVClass webm_chunk_class = {
. class_name = " WebM Chunk Muxer " ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
2021-04-19 19:45:24 +02:00
const AVOutputFormat ff_webm_chunk_muxer = {
2015-03-30 23:46:10 +02:00
. name = " webm_chunk " ,
. long_name = NULL_IF_CONFIG_SMALL ( " WebM Chunk Muxer " ) ,
. mime_type = " video/webm " ,
. extensions = " chk " ,
. flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_NEEDNUMBER |
2015-06-09 19:02:56 +02:00
AVFMT_TS_NONSTRICT ,
2015-03-30 23:46:10 +02:00
. priv_data_size = sizeof ( WebMChunkContext ) ,
2020-03-01 01:33:18 +02:00
. init = webm_chunk_init ,
2015-03-30 23:46:10 +02:00
. write_header = webm_chunk_write_header ,
. write_packet = webm_chunk_write_packet ,
. write_trailer = webm_chunk_write_trailer ,
2020-03-01 01:50:15 +02:00
. deinit = webm_chunk_deinit ,
2015-03-30 23:46:10 +02:00
. priv_class = & webm_chunk_class ,
} ;