Commit 25a02945 authored by Kieran Kunhya's avatar Kieran Kunhya Committed by Fiona Glaser

FLV muxing support

parent b9ce3a10
......@@ -13,7 +13,8 @@ SRCS = common/mc.c common/predict.c common/pixel.c common/macroblock.c \
encoder/cavlc.c encoder/encoder.c encoder/lookahead.c
SRCCLI = x264.c input/yuv.c input/y4m.c output/raw.c \
output/matroska.c output/matroska_ebml.c
output/matroska.c output/matroska_ebml.c \
output/flv.c output/flv_bytestream.c
MUXERS := $(shell grep -E "(IN|OUT)PUT" config.h)
......
......@@ -151,6 +151,7 @@ static inline int x264_pthread_create( x264_pthread_t *t, void *a, void *(*f)(vo
#ifdef WORDS_BIGENDIAN
#define endian_fix(x) (x)
#define endian_fix64(x) (x)
#define endian_fix32(x) (x)
#define endian_fix16(x) (x)
#else
......@@ -160,31 +161,34 @@ static ALWAYS_INLINE uint32_t endian_fix32( uint32_t x )
asm("bswap %0":"+r"(x));
return x;
}
static ALWAYS_INLINE intptr_t endian_fix( intptr_t x )
{
asm("bswap %0":"+r"(x));
return x;
}
#elif defined(__GNUC__) && defined(HAVE_ARMV6)
static ALWAYS_INLINE intptr_t endian_fix( intptr_t x )
static ALWAYS_INLINE uint32_t endian_fix32( uint32_t x )
{
asm("rev %0, %0":"+r"(x));
return x;
}
#define endian_fix32 endian_fix
#else
static ALWAYS_INLINE uint32_t endian_fix32( uint32_t x )
{
return (x<<24) + ((x<<8)&0xff0000) + ((x>>8)&0xff00) + (x>>24);
}
static ALWAYS_INLINE intptr_t endian_fix( intptr_t x )
#endif
#if defined(__GNUC__) && defined(ARCH_X86_64)
static ALWAYS_INLINE uint64_t endian_fix64( uint64_t x )
{
if( WORD_SIZE == 8 )
return endian_fix32(x>>32) + ((uint64_t)endian_fix32(x)<<32);
else
return endian_fix32(x);
asm("bswap %0":"+r"(x));
return x;
}
#else
static ALWAYS_INLINE uint64_t endian_fix64( uint64_t x )
{
return endian_fix32(x>>32) + ((uint64_t)endian_fix32(x)<<32);
}
#endif
static ALWAYS_INLINE intptr_t endian_fix( intptr_t x )
{
return WORD_SIZE == 8 ? endian_fix64(x) : endian_fix32(x);
}
static ALWAYS_INLINE uint16_t endian_fix16( uint16_t x )
{
return (x<<8)|(x>>8);
......
......@@ -348,9 +348,13 @@ fi
CFLAGS="$CFLAGS -DARCH_$ARCH -DSYS_$SYS"
echo "unsigned int endian = 'B' << 24 | 'I' << 16 | 'G' << 8 | 'E';" > conftest.c
echo "int i = 0x42494745; double f = 0x1.0656e6469616ep+102;" > conftest.c
$CC $CFLAGS conftest.c -c -o conftest.o 2>$DEVNULL || die "endian test failed"
grep -q BIGE conftest.o && CFLAGS="$CFLAGS -DWORDS_BIGENDIAN"
if grep -q BIGE conftest.o && grep -q FPendian conftest.o ; then
CFLAGS="$CFLAGS -DWORDS_BIGENDIAN"
elif !(grep -q EGIB conftest.o && grep -q naidnePF conftest.o) ; then
die "endian test failed"
fi
# autodetect options that weren't forced nor disabled
......
/*****************************************************************************
* flv.c:
*****************************************************************************
* Copyright (C) 2009 Kieran Kunhya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*****************************************************************************/
#include "muxers.h"
#include "flv_bytestream.h"
#define CHECK(x)\
do {\
if( (x) < 0 )\
return -1;\
} while( 0 )
typedef struct
{
flv_buffer *c;
uint8_t b_sps;
uint8_t b_pps;
uint8_t *sei;
int sei_len;
int64_t i_fps_num;
int64_t i_fps_den;
int64_t i_init_delay;
int64_t i_framenum;
int64_t i_mspf;
uint64_t i_duration_pos;
uint64_t i_filesize_pos;
uint64_t i_bitrate_pos;
uint8_t b_write_length;
unsigned start;
} flv_hnd_t;
static int write_header( flv_buffer *c )
{
put_tag( c, "FLV" ); // Signature
put_byte( c, 1 ); // Version
put_byte( c, 1 ); // Video Only
put_be32( c, 9 ); // DataOffset
put_be32( c, 0 ); // PreviousTagSize0
return flv_flush_data( c );
}
static int open_file( char *psz_filename, hnd_t *p_handle )
{
flv_hnd_t *p_flv = malloc( sizeof(*p_flv) );
*p_handle = NULL;
if( !p_flv )
return -1;
memset( p_flv, 0, sizeof(*p_flv) );
p_flv->c = flv_create_writer( psz_filename );
if( !p_flv->c )
return -1;
CHECK( write_header( p_flv->c ) );
*p_handle = p_flv;
return 0;
}
static int set_param( hnd_t handle, x264_param_t *p_param )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
put_byte( c, FLV_TAG_TYPE_META ); // Tag Type "script data"
int start = c->d_cur;
put_be24( c, 0 ); // data length
put_be24( c, 0 ); // timestamp
put_be32( c, 0 ); // reserved
put_byte( c, AMF_DATA_TYPE_STRING );
put_amf_string( c, "onMetaData" );
put_byte( c, AMF_DATA_TYPE_MIXEDARRAY );
put_be32( c, 7 );
put_amf_string( c, "width" );
put_amf_double( c, p_param->i_width );
put_amf_string( c, "height" );
put_amf_double( c, p_param->i_height );
put_amf_string( c, "framerate" );
put_amf_double( c, p_param->i_fps_num / p_param->i_fps_den );
put_amf_string( c, "videocodecid" );
put_amf_double( c, FLV_CODECID_H264 );
put_amf_string( c, "duration" );
p_flv->i_duration_pos = c->d_cur + c->d_total + 1; // + 1 because of the following AMF_DATA_TYPE_NUMBER byte
put_amf_double( c, 0 ); // written at end of encoding
put_amf_string( c, "filesize" );
p_flv->i_filesize_pos = c->d_cur + c->d_total + 1;
put_amf_double( c, 0 ); // written at end of encoding
put_amf_string( c, "videodatarate" );
p_flv->i_bitrate_pos = c->d_cur + c->d_total + 1;
put_amf_double( c, 0 ); // written at end of encoding
put_amf_string( c, "" );
put_byte( c, AMF_END_OF_OBJECT );
unsigned length = c->d_cur - start;
rewrite_amf_be24( c, length - 10, start );
put_be32( c, length + 1 ); // tag length
p_flv->i_fps_num = p_param->i_fps_num;
p_flv->i_fps_den = p_param->i_fps_den;
p_flv->i_init_delay = p_param->i_bframe ? (p_param->i_bframe_pyramid ? 2 : 1) : 0;
p_flv->i_mspf = 1000 * p_flv->i_fps_den / p_flv->i_fps_num;
fprintf( stderr, "flv [info]: initial delay %i frames\n",
(int)p_flv->i_init_delay );
return 0;
}
static int write_nalu( 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;
uint64_t dts = (uint64_t)p_flv->i_framenum * p_flv->i_mspf;
uint64_t pts = (uint64_t)p_picture->i_pts * p_flv->i_mspf / p_flv->i_fps_den;
uint64_t timestamp = dts + p_flv->i_init_delay * p_flv->i_mspf;
uint64_t offset = p_flv->i_init_delay * p_flv->i_mspf + pts - dts;
uint8_t type = p_nalu[4] & 0x1f;
switch( type )
{
// sps
case 0x07:
if( !p_flv->b_sps )
{
uint8_t *sps = p_nalu + 4;
put_byte( c, FLV_TAG_TYPE_VIDEO );
put_be24( c, 0 ); // rewrite later, pps size unknown
put_be24( c, 0 ); // timestamp
put_byte( c, 0 ); // timestamp extended
put_be24( c, 0 ); // StreamID - Always 0
p_flv->start = c->d_cur; // needed for overwriting length
put_byte( c, 7 | FLV_FRAME_KEY ); // Frametype and CodecID
put_byte( c, 0 ); // AVC sequence header
put_be24( c, 0 ); // composition time
put_byte( c, 1 ); // version
put_byte( c, sps[1] ); // profile
put_byte( c, sps[2] ); // profile
put_byte( c, sps[3] ); // level
put_byte( c, 0xff ); // 6 bits reserved (111111) + 2 bits nal size length - 1 (11)
put_byte( c, 0xe1 ); // 3 bits reserved (111) + 5 bits number of sps (00001)
put_be16( c, i_size - 4 );
flv_append_data( c, sps, i_size - 4 );
p_flv->b_sps = 1;
}
break;
// pps
case 0x08:
if( !p_flv->b_pps )
{
put_byte( c, 1 ); // number of pps
put_be16( c, i_size - 4 );
flv_append_data( c, p_nalu + 4, i_size - 4 );
// rewrite data length info
unsigned length = c->d_cur - p_flv->start;
rewrite_amf_be24( c, length, p_flv->start - 10 );
put_be32( c, length + 11 ); // Last tag size
p_flv->b_pps = 1;
}
break;
// slice
case 0x1:
case 0x5:
if( !p_flv->b_write_length )
{
// A new frame - write packet header
put_byte( c, FLV_TAG_TYPE_VIDEO );
put_be24( c, 0 ); // calculated later
put_be24( c, timestamp );
put_byte( c, timestamp >> 24 );
put_be24( c, 0 );
p_flv->start = c->d_cur;
put_byte( c, p_picture->i_type == X264_TYPE_IDR ? FLV_FRAME_KEY : FLV_FRAME_INTER );
put_byte( c, 1 ); // AVC NALU
put_be24( c, offset );
p_flv->b_write_length = 1;
}
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 );
break;
// sei
case 0x6:
/* It is within the spec to write this as-is but for
* mplayer/ffmpeg playback this is deferred until before the first frame */
p_flv->sei = malloc( i_size );
if( !p_flv->sei )
return -1;
p_flv->sei_len = i_size;
memcpy( p_flv->sei, p_nalu, i_size );
break;
}
return i_size;
}
static int set_eop( hnd_t handle, x264_picture_t *p_picture )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
if( p_flv->b_write_length )
{
unsigned length = c->d_cur - p_flv->start;
rewrite_amf_be24( c, length, p_flv->start - 10 );
put_be32( c, 11 + length ); // Last tag size
CHECK( flv_flush_data( c ) );
p_flv->b_write_length = 0;
}
p_flv->i_framenum++;
return 0;
}
static void rewrite_amf_double( FILE *fp, uint64_t position, double value )
{
uint64_t x = endian_fix64( dbl2int( value ) );
fseek( fp, position, SEEK_SET );
fwrite( &x, 8, 1, fp );
}
static int close_file( hnd_t handle )
{
flv_hnd_t *p_flv = handle;
flv_buffer *c = p_flv->c;
CHECK( flv_flush_data( c ) );
if( x264_is_regular_file( c->fp ) )
{
double duration = p_flv->i_fps_den * p_flv->i_framenum / p_flv->i_fps_num;
uint64_t filesize = ftell( c->fp );
rewrite_amf_double( c->fp, p_flv->i_duration_pos, duration );
rewrite_amf_double( c->fp, p_flv->i_filesize_pos, filesize );
rewrite_amf_double( c->fp, p_flv->i_bitrate_pos, filesize * 8 / ( duration * 1000 ) );
}
fclose( c->fp );
free( p_flv );
free( c );
return 0;
}
cli_output_t flv_output = { open_file, set_param, write_nalu, set_eop, close_file };
/*****************************************************************************
* flv_bytestream.c:
*****************************************************************************
* Copyright (C) 2009 Kieran Kunhya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "common/common.h"
#include "flv_bytestream.h"
uint64_t dbl2int( double value )
{
return (union {double f; uint64_t i;}){value}.i;
}
/* Put functions */
void put_byte( flv_buffer *c, uint8_t b )
{
flv_append_data( c, &b, 1 );
}
void put_be32( flv_buffer *c, uint32_t val )
{
put_byte( c, val >> 24 );
put_byte( c, val >> 16 );
put_byte( c, val >> 8 );
put_byte( c, val );
}
void put_be64( flv_buffer *c, uint64_t val )
{
put_be32( c, val >> 32 );
put_be32( c, val );
}
void put_be16( flv_buffer *c, uint16_t val )
{
put_byte( c, val >> 8 );
put_byte( c, val );
}
void put_be24( flv_buffer *c, uint32_t val )
{
put_be16( c, val >> 8 );
put_byte( c, val );
}
void put_tag( flv_buffer *c, const char *tag )
{
while( *tag )
put_byte( c, *tag++ );
}
void put_amf_string( flv_buffer *c, const char *str )
{
uint16_t len = strlen( str );
put_be16( c, len );
flv_append_data( c, (uint8_t*)str, len );
}
void put_amf_double( flv_buffer *c, double d )
{
put_byte( c, AMF_DATA_TYPE_NUMBER );
put_be64( c, dbl2int( d ) );
}
/* flv writing functions */
flv_buffer *flv_create_writer( const char *filename )
{
flv_buffer *c = malloc( sizeof(*c) );
if( !c )
return NULL;
memset( c, 0, sizeof(*c) );
if( !strcmp( filename, "-" ) )
c->fp = stdout;
else
c->fp = fopen( filename, "wb" );
if( !c->fp )
{
free( c );
return NULL;
}
return c;
}
int flv_append_data( flv_buffer *c, uint8_t *data, unsigned size )
{
unsigned ns = c->d_cur + size;
if( ns > c->d_max )
{
void *dp;
unsigned dn = 16;
while( ns > dn )
dn <<= 1;
dp = realloc( c->data, dn );
if( !dp )
return -1;
c->data = dp;
c->d_max = dn;
}
memcpy( c->data + c->d_cur, data, size );
c->d_cur = ns;
return 0;
}
void rewrite_amf_be24( flv_buffer *c, unsigned length, unsigned start )
{
*(c->data + start + 0) = length >> 16;
*(c->data + start + 1) = length >> 8;
*(c->data + start + 2) = length >> 0;
}
int flv_flush_data( flv_buffer *c )
{
if( !c->d_cur )
return 0;
if( fwrite( c->data, c->d_cur, 1, c->fp ) != 1 )
return -1;
c->d_total += c->d_cur;
c->d_cur = 0;
return 0;
}
/*****************************************************************************
* flv_bytestream.h:
*****************************************************************************
* Copyright (C) 2009 Kieran Kunhya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*****************************************************************************/
#ifndef X264_FLV_BYTESTREAM_H
#define X264_FLV_BYTESTREAM_H
/* offsets for packed values */
#define FLV_AUDIO_SAMPLESSIZE_OFFSET 1
#define FLV_AUDIO_SAMPLERATE_OFFSET 2
#define FLV_AUDIO_CODECID_OFFSET 4
#define FLV_VIDEO_FRAMETYPE_OFFSET 4
/* bitmasks to isolate specific values */
#define FLV_AUDIO_CHANNEL_MASK 0x01
#define FLV_AUDIO_SAMPLESIZE_MASK 0x02
#define FLV_AUDIO_SAMPLERATE_MASK 0x0c
#define FLV_AUDIO_CODECID_MASK 0xf0
#define FLV_VIDEO_CODECID_MASK 0x0f
#define FLV_VIDEO_FRAMETYPE_MASK 0xf0
#define AMF_END_OF_OBJECT 0x09
enum
{
FLV_HEADER_FLAG_HASVIDEO = 1,
FLV_HEADER_FLAG_HASAUDIO = 4,
};
enum
{
FLV_TAG_TYPE_AUDIO = 0x08,
FLV_TAG_TYPE_VIDEO = 0x09,
FLV_TAG_TYPE_META = 0x12,
};
enum
{
FLV_MONO = 0,
FLV_STEREO = 1,
};
enum
{
FLV_SAMPLESSIZE_8BIT = 0,
FLV_SAMPLESSIZE_16BIT = 1 << FLV_AUDIO_SAMPLESSIZE_OFFSET,
};
enum
{
FLV_SAMPLERATE_SPECIAL = 0, /**< signifies 5512Hz and 8000Hz in the case of NELLYMOSER */
FLV_SAMPLERATE_11025HZ = 1 << FLV_AUDIO_SAMPLERATE_OFFSET,
FLV_SAMPLERATE_22050HZ = 2 << FLV_AUDIO_SAMPLERATE_OFFSET,
FLV_SAMPLERATE_44100HZ = 3 << FLV_AUDIO_SAMPLERATE_OFFSET,
};
enum
{
FLV_CODECID_MP3 = 2 << FLV_AUDIO_CODECID_OFFSET,
FLV_CODECID_AAC = 10<< FLV_AUDIO_CODECID_OFFSET,
};
enum
{
FLV_CODECID_H264 = 7,
};
enum
{
FLV_FRAME_KEY = 1 << FLV_VIDEO_FRAMETYPE_OFFSET | 7,
FLV_FRAME_INTER = 2 << FLV_VIDEO_FRAMETYPE_OFFSET | 7,
};
typedef enum
{
AMF_DATA_TYPE_NUMBER = 0x00,
AMF_DATA_TYPE_BOOL = 0x01,
AMF_DATA_TYPE_STRING = 0x02,
AMF_DATA_TYPE_OBJECT = 0x03,
AMF_DATA_TYPE_NULL = 0x05,
AMF_DATA_TYPE_UNDEFINED = 0x06,
AMF_DATA_TYPE_REFERENCE = 0x07,
AMF_DATA_TYPE_MIXEDARRAY = 0x08,
AMF_DATA_TYPE_OBJECT_END = 0x09,
AMF_DATA_TYPE_ARRAY = 0x0a,
AMF_DATA_TYPE_DATE = 0x0b,
AMF_DATA_TYPE_LONG_STRING = 0x0c,
AMF_DATA_TYPE_UNSUPPORTED = 0x0d,
} AMFDataType;
typedef struct flv_buffer
{
uint8_t *data;
unsigned d_cur;
unsigned d_max;
FILE *fp;
uint64_t d_total;
} flv_buffer;
flv_buffer *flv_create_writer( const char *filename );
int flv_append_data( flv_buffer *c, uint8_t *data, unsigned size );
int flv_write_byte( flv_buffer *c, uint8_t *byte );
int flv_flush_data( flv_buffer *c );
void rewrite_amf_be24( flv_buffer *c, unsigned length, unsigned start );
uint64_t dbl2int( double value );
uint64_t get_amf_double( double value );
void put_byte( flv_buffer *c, uint8_t b );
void put_be32( flv_buffer *c, uint32_t val );
void put_be64( flv_buffer *c, uint64_t val );
void put_be16( flv_buffer *c, uint16_t val );
void put_be24( flv_buffer *c, uint32_t val );
void put_tag( flv_buffer *c, const char *tag );
void put_amf_string( flv_buffer *c, const char *str );
void put_amf_double( flv_buffer *c, double d );
#endif
......@@ -151,7 +151,7 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
return 0;
}
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size )
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
mkv_hnd_t *p_mkv = handle;
uint8_t type = p_nalu[4] & 0x1f;
......
......@@ -197,7 +197,7 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
return 0;
}
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size )
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
mp4_hnd_t *p_mp4 = handle;
GF_AVCConfigSlot *p_slot;
......
......@@ -28,7 +28,7 @@ typedef struct
{
int (*open_file)( char *psz_filename, hnd_t *p_handle );
int (*set_param)( hnd_t handle, x264_param_t *p_param );
int (*write_nalu)( hnd_t handle, uint8_t *p_nal, int i_size );
int (*write_nalu)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture );
int (*set_eop)( hnd_t handle, x264_picture_t *p_picture );
int (*close_file)( hnd_t handle );
} cli_output_t;
......@@ -36,5 +36,6 @@ typedef struct
extern cli_output_t raw_output;
extern cli_output_t mkv_output;
extern cli_output_t mp4_output;
extern cli_output_t flv_output;
#endif
......@@ -38,7 +38,7 @@ static int set_param( hnd_t handle, x264_param_t *p_param )
return 0;
}
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size )
static int write_nalu( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture )
{
if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) > 0 )
return i_size;
......
......@@ -66,7 +66,7 @@ static cli_output_t output;
/* i/o modules that work with pipes (and fifos) */
static const char * const stdin_format_names[] = { "yuv", "y4m", 0 };
static const char * const stdout_format_names[] = { "raw", "mkv", 0 };
static const char * const stdout_format_names[] = { "raw", "mkv", "flv", 0 };
static void Help( x264_param_t *defaults, int longhelp );
static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt );
......@@ -134,6 +134,7 @@ static void Help( x264_param_t *defaults, int longhelp )
"Outfile type is selected by filename:\n"
" .264 -> Raw bytestream\n"
" .mkv -> Matroska\n"
" .flv -> Flash Video\n"
" .mp4 -> MP4 if compiled with GPAC support (%s)\n"
"\n"
"Options:\n"
......@@ -364,7 +365,7 @@ static void Help( x264_param_t *defaults, int longhelp )