#include "output.h"
#include "flv_bytestream.h"
#define CHECK(x)\
do {\
if( (x) < 0 )\
return -1;\
} while( 0 )
typedef struct
{
flv_buffer *c;
uint8_t *sei;
int sei_len;
int64_t i_fps_num;
int64_t i_fps_den;
int64_t i_framenum;
uint64_t i_framerate_pos;
uint64_t i_duration_pos;
uint64_t i_filesize_pos;
uint64_t i_bitrate_pos;
uint8_t b_write_length;
int64_t i_prev_dts;
int64_t i_prev_cts;
int64_t i_delay_time;
int64_t i_init_delta;
int i_delay_frames;
double d_timebase;
int b_vfr_input;
int b_dts_compress;
unsigned start;
} flv_hnd_t;
static int write_header( flv_buffer *c )
{
flv_put_tag( c, "FLV" );
flv_put_byte( c, 1 );
flv_put_byte( c, 1 );
flv_put_be32( c, 9 );
flv_put_be32( c, 0 );
return flv_flush_data( c );
}
static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt )
{
flv_hnd_t *p_flv = calloc( 1, sizeof(flv_hnd_t) );
if( p_flv )
{
flv_buffer *c = flv_create_writer( psz_filename );
if( c )
{
if( !write_header( c ) )
{
p_flv->c = c;
p_flv->b_dts_compress = opt->use_dts_compress;
*p_handle = p_flv;
return 0;
}
fclose( c->fp );
free( c->data );
free( c );
}
free( p_flv );
}
*p_handle = NULL;
return -1;
}
static int set_param( hnd_t handle, x264_param_t *p_param )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
flv_put_byte( c, FLV_TAG_TYPE_META );
int start = c->d_cur;
flv_put_be24( c, 0 );
flv_put_be24( c, 0 );
flv_put_be32( c, 0 );
flv_put_byte( c, AMF_DATA_TYPE_STRING );
flv_put_amf_string( c, "onMetaData" );
flv_put_byte( c, AMF_DATA_TYPE_MIXEDARRAY );
flv_put_be32( c, 7 );
flv_put_amf_string( c, "width" );
flv_put_amf_double( c, p_param->i_width );
flv_put_amf_string( c, "height" );
flv_put_amf_double( c, p_param->i_height );
flv_put_amf_string( c, "framerate" );
if( !p_param->b_vfr_input )
flv_put_amf_double( c, (double)p_param->i_fps_num / p_param->i_fps_den );
else
{
p_flv->i_framerate_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 );
}
flv_put_amf_string( c, "videocodecid" );
flv_put_amf_double( c, FLV_CODECID_H264 );
flv_put_amf_string( c, "duration" );
p_flv->i_duration_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 );
flv_put_amf_string( c, "filesize" );
p_flv->i_filesize_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 );
flv_put_amf_string( c, "videodatarate" );
p_flv->i_bitrate_pos = c->d_cur + c->d_total + 1;
flv_put_amf_double( c, 0 );
flv_put_amf_string( c, "" );
flv_put_byte( c, AMF_END_OF_OBJECT );
unsigned length = c->d_cur - start;
flv_rewrite_amf_be24( c, length - 10, start );
flv_put_be32( c, length + 1 );
p_flv->i_fps_num = p_param->i_fps_num;
p_flv->i_fps_den = p_param->i_fps_den;
p_flv->d_timebase = (double)p_param->i_timebase_num / p_param->i_timebase_den;
p_flv->b_vfr_input = p_param->b_vfr_input;
p_flv->i_delay_frames = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
return 0;
}
static int write_headers( hnd_t handle, x264_nal_t *p_nal )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
int sps_size = p_nal[0].i_payload;
int pps_size = p_nal[1].i_payload;
int sei_size = p_nal[2].i_payload;
p_flv->sei = malloc( sei_size );
if( !p_flv->sei )
return -1;
p_flv->sei_len = sei_size;
memcpy( p_flv->sei, p_nal[2].p_payload, sei_size );
uint8_t *sps = p_nal[0].p_payload + 4;
flv_put_byte( c, FLV_TAG_TYPE_VIDEO );
flv_put_be24( c, 0 );
flv_put_be24( c, 0 );
flv_put_byte( c, 0 );
flv_put_be24( c, 0 );
p_flv->start = c->d_cur;
flv_put_byte( c, 7 | FLV_FRAME_KEY );
flv_put_byte( c, 0 );
flv_put_be24( c, 0 );
flv_put_byte( c, 1 );
flv_put_byte( c, sps[1] );
flv_put_byte( c, sps[2] );
flv_put_byte( c, sps[3] );
flv_put_byte( c, 0xff );
flv_put_byte( c, 0xe1 );
flv_put_be16( c, sps_size - 4 );
flv_append_data( c, sps, sps_size - 4 );
flv_put_byte( c, 1 );
flv_put_be16( c, pps_size - 4 );
flv_append_data( c, p_nal[1].p_payload + 4, pps_size - 4 );
unsigned length = c->d_cur - p_flv->start;
flv_rewrite_amf_be24( c, length, p_flv->start - 10 );
flv_put_be32( c, length + 11 );
CHECK( flv_flush_data( c ) );
return sei_size + sps_size + pps_size;
}
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
#define convert_timebase_ms( timestamp, timebase ) (int64_t)((timestamp) * (timebase) * 1000 + 0.5)
if( !p_flv->i_framenum )
{
p_flv->i_delay_time = p_picture->i_dts * -1;
if( !p_flv->b_dts_compress && p_flv->i_delay_time )
x264_cli_log( "flv", X264_LOG_INFO, "initial delay %"PRId64" ms\n",
convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase ) );
}
int64_t dts;
int64_t cts;
int64_t offset;
if( p_flv->b_dts_compress )
{
if( p_flv->i_framenum == 1 )
p_flv->i_init_delta = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase );
dts = p_flv->i_framenum > p_flv->i_delay_frames
? convert_timebase_ms( p_picture->i_dts, p_flv->d_timebase )
: p_flv->i_framenum * p_flv->i_init_delta / (p_flv->i_delay_frames + 1);
cts = convert_timebase_ms( p_picture->i_pts, p_flv->d_timebase );
}
else
{
dts = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase );
cts = convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase );
}
offset = cts - dts;
if( p_flv->i_framenum )
{
if( p_flv->i_prev_dts == dts )
x264_cli_log( "flv", X264_LOG_WARNING, "duplicate DTS %"PRId64" generated by rounding\n"
" decoding framerate cannot exceed 1000fps\n", dts );
if( p_flv->i_prev_cts == cts )
x264_cli_log( "flv", X264_LOG_WARNING, "duplicate CTS %"PRId64" generated by rounding\n"
" composition framerate cannot exceed 1000fps\n", cts );
}
p_flv->i_prev_dts = dts;
p_flv->i_prev_cts = cts;
flv_put_byte( c, FLV_TAG_TYPE_VIDEO );
flv_put_be24( c, 0 );
flv_put_be24( c, dts );
flv_put_byte( c, dts >> 24 );
flv_put_be24( c, 0 );
p_flv->start = c->d_cur;
flv_put_byte( c, p_picture->b_keyframe ? FLV_FRAME_KEY : FLV_FRAME_INTER );
flv_put_byte( c, 1 );
flv_put_be24( c, offset );
if( p_flv->sei )
{
flv_append_data( c, p_flv->sei, p_flv->sei_len );
free( p_flv->sei );
p_flv->sei = NULL;
}
flv_append_data( c, p_nalu, i_size );
unsigned length = c->d_cur - p_flv->start;
flv_rewrite_amf_be24( c, length, p_flv->start - 10 );
flv_put_be32( c, 11 + length );
CHECK( flv_flush_data( c ) );
p_flv->i_framenum++;
return i_size;
}
static int rewrite_amf_double( FILE *fp, uint64_t position, double value )
{
uint64_t x = endian_fix64( flv_dbl2int( value ) );
return !fseek( fp, position, SEEK_SET ) && fwrite( &x, 8, 1, fp ) == 1 ? 0 : -1;
}
#undef CHECK
#define CHECK(x)\
do {\
if( (x) < 0 )\
goto error;\
} while( 0 )
static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts )
{
int ret = -1;
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
CHECK( flv_flush_data( c ) );
double total_duration = (2 * largest_pts - second_largest_pts) * p_flv->d_timebase;
if( x264_is_regular_file( c->fp ) && total_duration > 0 )
{
double framerate;
uint64_t filesize = ftell( c->fp );
if( p_flv->i_framerate_pos )
{
framerate = (double)p_flv->i_framenum / total_duration;
CHECK( rewrite_amf_double( c->fp, p_flv->i_framerate_pos, framerate ) );
}
CHECK( rewrite_amf_double( c->fp, p_flv->i_duration_pos, total_duration ) );
CHECK( rewrite_amf_double( c->fp, p_flv->i_filesize_pos, filesize ) );
CHECK( rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8 / ( total_duration * 1000 ) ) );
}
ret = 0;
error:
fclose( c->fp );
free( c->data );
free( c );
free( p_flv );
return ret;
}
const cli_output_t flv_output = { open_file, set_param, write_headers, write_frame, close_file };