2015-03-30 14:46:10 -07: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 <float.h>
#include <time.h>
#include "avformat.h"
#include "avio.h"
2016-01-30 02:17:51 +01:00
#include "avio_internal.h"
2015-03-30 14:46:10 -07:00
#include "internal.h"
#include "libavutil/avassert.h"
#include "libavutil/log.h"
#include "libavutil/opt.h"
#include "libavutil/avstring.h"
#include "libavutil/parseutils.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavutil/time_internal.h"
#include "libavutil/timestamp.h"
2015-04-30 11:34:44 -07:00
#define MAX_FILENAME_SIZE 1024
2015-03-30 14:46:10 -07:00
typedef struct WebMChunkContext {
const AVClass * class ;
int chunk_start_index ;
char * header_filename ;
int chunk_duration ;
int chunk_index ;
uint64_t duration_written ;
int prev_pts ;
AVOutputFormat * oformat ;
AVFormatContext * avf ;
} WebMChunkContext ;
static int chunk_mux_init ( AVFormatContext * s )
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc ;
int ret ;
ret = avformat_alloc_output_context2 ( & wc -> avf , wc -> oformat , NULL , NULL );
if ( ret < 0 )
return ret ;
oc = wc -> avf ;
oc -> interrupt_callback = s -> interrupt_callback ;
oc -> max_delay = s -> max_delay ;
av_dict_copy ( & oc -> metadata , s -> metadata , 0 );
* ( const AVClass ** ) oc -> priv_data = oc -> oformat -> priv_class ;
av_opt_set_defaults ( oc -> priv_data );
av_opt_set_int ( oc -> priv_data , "dash" , 1 , 0 );
av_opt_set_int ( oc -> priv_data , "cluster_time_limit" , wc -> chunk_duration , 0 );
av_opt_set_int ( oc -> priv_data , "live" , 1 , 0 );
oc -> streams = s -> streams ;
oc -> nb_streams = s -> nb_streams ;
return 0 ;
}
2015-04-30 11:34:44 -07:00
static int get_chunk_filename ( AVFormatContext * s , int is_header , char * filename )
2015-03-30 14:46:10 -07:00
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc = wc -> avf ;
2015-04-30 11:34:44 -07:00
if ( ! filename ) {
return AVERROR ( EINVAL );
}
2015-03-30 14:46:10 -07:00
if ( is_header ) {
if ( ! wc -> header_filename ) {
return AVERROR ( EINVAL );
}
2015-04-30 11:34:44 -07:00
av_strlcpy ( filename , wc -> header_filename , strlen ( wc -> header_filename ) + 1 );
2015-03-30 14:46:10 -07:00
} else {
2015-04-30 11:34:44 -07:00
if ( av_get_frame_filename ( filename , MAX_FILENAME_SIZE ,
s -> filename , wc -> chunk_index - 1 ) < 0 ) {
2015-03-30 14:46:10 -07:00
av_log ( oc , AV_LOG_ERROR , "Invalid chunk filename template '%s' \n " , s -> filename );
return AVERROR ( EINVAL );
}
}
return 0 ;
}
static int webm_chunk_write_header ( AVFormatContext * s )
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc = NULL ;
int ret ;
// DASH Streams can only have either one track per file.
if ( s -> nb_streams != 1 ) { return AVERROR_INVALIDDATA ; }
wc -> chunk_index = wc -> chunk_start_index ;
wc -> oformat = av_guess_format ( "webm" , s -> filename , "video/webm" );
if ( ! wc -> oformat )
return AVERROR_MUXER_NOT_FOUND ;
ret = chunk_mux_init ( s );
if ( ret < 0 )
return ret ;
oc = wc -> avf ;
2015-04-30 11:34:44 -07:00
ret = get_chunk_filename ( s , 1 , oc -> filename );
if ( ret < 0 )
2015-03-30 14:46:10 -07:00
return ret ;
2016-01-30 02:17:51 +01:00
ret = ffio_open_whitelist ( & oc -> pb , oc -> filename , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL , s -> protocol_whitelist );
2015-03-30 14:46:10 -07:00
if ( ret < 0 )
return ret ;
oc -> pb -> seekable = 0 ;
ret = oc -> oformat -> write_header ( oc );
if ( ret < 0 )
return ret ;
avio_close ( oc -> pb );
return 0 ;
}
static int chunk_start ( AVFormatContext * s )
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc = wc -> avf ;
int ret ;
2015-04-30 11:34:44 -07:00
ret = avio_open_dyn_buf ( & oc -> pb );
2015-03-30 14:46:10 -07:00
if ( ret < 0 )
return ret ;
wc -> chunk_index ++ ;
return 0 ;
}
static int chunk_end ( AVFormatContext * s )
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc = wc -> avf ;
int ret ;
2015-04-30 11:34:44 -07:00
int buffer_size ;
uint8_t * buffer ;
AVIOContext * pb ;
char filename [ MAX_FILENAME_SIZE ];
2015-03-30 14:46:10 -07:00
if ( wc -> chunk_start_index == wc -> chunk_index )
return 0 ;
// Flush the cluster in WebM muxer.
oc -> oformat -> write_packet ( oc , NULL );
2015-04-30 11:34:44 -07:00
buffer_size = avio_close_dyn_buf ( oc -> pb , & buffer );
ret = get_chunk_filename ( s , 0 , filename );
2015-03-30 14:46:10 -07:00
if ( ret < 0 )
2015-04-30 11:34:44 -07:00
goto fail ;
2016-01-30 02:17:51 +01:00
ret = ffio_open_whitelist ( & pb , filename , AVIO_FLAG_WRITE ,
& s -> interrupt_callback , NULL , s -> protocol_whitelist );
2015-04-30 11:34:44 -07:00
if ( ret < 0 )
goto fail ;
avio_write ( pb , buffer , buffer_size );
ret = avio_close ( pb );
if ( ret < 0 )
goto fail ;
oc -> pb = NULL ;
fail :
av_free ( buffer );
return ( ret < 0 ) ? ret : 0 ;
2015-03-30 14:46:10 -07: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 ;
if ( st -> codec -> codec_type == AVMEDIA_TYPE_AUDIO ) {
wc -> duration_written += av_rescale_q ( pkt -> pts - wc -> prev_pts ,
st -> time_base ,
( AVRational ) { 1 , 1000 });
wc -> prev_pts = pkt -> pts ;
}
// For video, a new chunk is started only on key frames. For audio, a new
// chunk is started based on chunk_duration.
if (( st -> codec -> codec_type == AVMEDIA_TYPE_VIDEO &&
( pkt -> flags & AV_PKT_FLAG_KEY )) ||
( st -> codec -> codec_type == AVMEDIA_TYPE_AUDIO &&
( pkt -> pts == 0 || wc -> duration_written >= wc -> chunk_duration ))) {
wc -> duration_written = 0 ;
if (( ret = chunk_end ( s )) < 0 || ( ret = chunk_start ( s )) < 0 ) {
goto fail ;
}
}
ret = oc -> oformat -> write_packet ( oc , pkt );
if ( ret < 0 )
goto fail ;
fail :
if ( ret < 0 ) {
oc -> streams = NULL ;
oc -> nb_streams = 0 ;
avformat_free_context ( oc );
}
return ret ;
}
static int webm_chunk_write_trailer ( AVFormatContext * s )
{
WebMChunkContext * wc = s -> priv_data ;
AVFormatContext * oc = wc -> avf ;
oc -> oformat -> write_trailer ( oc );
chunk_end ( s );
oc -> streams = NULL ;
oc -> nb_streams = 0 ;
avformat_free_context ( oc );
return 0 ;
}
#define OFFSET(x) offsetof(WebMChunkContext, x)
static const AVOption options [] = {
{ "chunk_start_index" , "start index of the chunk" , OFFSET ( chunk_start_index ), AV_OPT_TYPE_INT , {. i64 = 0 }, 0 , INT_MAX , AV_OPT_FLAG_ENCODING_PARAM },
{ "header" , "filename of the header where the initialization data will be written" , OFFSET ( header_filename ), AV_OPT_TYPE_STRING , { 0 }, 0 , 0 , AV_OPT_FLAG_ENCODING_PARAM },
{ "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 },
{ NULL },
};
#if CONFIG_WEBM_CHUNK_MUXER
static const AVClass webm_chunk_class = {
. class_name = "WebM Chunk Muxer" ,
. item_name = av_default_item_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
};
AVOutputFormat ff_webm_chunk_muxer = {
. 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 10:02:56 -07:00
AVFMT_TS_NONSTRICT ,
2015-03-30 14:46:10 -07:00
. priv_data_size = sizeof ( WebMChunkContext ),
. write_header = webm_chunk_write_header ,
. write_packet = webm_chunk_write_packet ,
. write_trailer = webm_chunk_write_trailer ,
. priv_class = & webm_chunk_class ,
};
#endif