diff --git a/Makefile b/Makefile index 0f4d0cf..8a31a45 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,20 @@ ifneq ($(findstring HAVE_GPAC, $(CONFIG)),) SRCCLI += output/mp4.c endif +ifneq ($(findstring HAVE_AUDIO 1, $(CONFIG)),) +SRCCLI += audio/audio.c audio/encoders.c \ + filters/audio/audio_filters.c \ + filters/audio/internal.c \ + audio/encoders/enc_raw.c +ifneq ($(findstring HAVE_LAVF, $(CONFIG)),) +SRCCLI += input/audio/lavf.c +endif +endif + +ifneq ($(findstring HAVE_LAME 1, $(CONFIG)),) +SRCCLI += audio/encoders/enc_mp3lame.c +endif + # Visualization sources ifeq ($(VIS),yes) SRCS += common/visualize.c common/display-x11.c diff --git a/audio/audio.c b/audio/audio.c new file mode 100644 index 0000000..fc115cf --- /dev/null +++ b/audio/audio.c @@ -0,0 +1,24 @@ +#include "filters/audio/internal.h" + +#include + +hnd_t audio_open_from_file( audio_filter_t *preferred_filter, char *path, int trackno ) +{ + audio_filter_t *source = preferred_filter ? preferred_filter : af_get_filter( "lavf" ); + if( !source ) + { + x264_cli_log( "audio", X264_LOG_ERROR, "no decoder / demuxer avilable!\n" ); + return NULL; + } + hnd_t h = NULL; + size_t init_arg_size = strlen( path ) + 10; + char *init_arg = malloc( init_arg_size ); + assert( snprintf( init_arg, init_arg_size, "%s,%d", path, trackno ) < init_arg_size ); + if( source->init( &h, init_arg ) < 0 || !h ) + { + x264_cli_log( "audio", X264_LOG_ERROR, "error initializing source filter!\n" ); + return NULL; + } + free( init_arg ); + return h; +} diff --git a/audio/audio.h b/audio/audio.h new file mode 100644 index 0000000..de9fa58 --- /dev/null +++ b/audio/audio.h @@ -0,0 +1,18 @@ +#ifndef AUDIO_AUDIO_H_ +#define AUDIO_AUDIO_H_ + +#include +#include "x264cli.h" +#include "filters/audio/audio_filters.h" + +enum AudioTrack +{ + TRACK_ANY = -1, + TRACK_NONE = -2 +}; + +hnd_t audio_open_from_file( audio_filter_t *preferred_filter, char *path, int trackno ); + +#include "audio/encoders.h" + +#endif /* AUDIO_AUDIO_H_ */ diff --git a/audio/encoders.c b/audio/encoders.c new file mode 100644 index 0000000..b839d13 --- /dev/null +++ b/audio/encoders.c @@ -0,0 +1,114 @@ +#include "audio/encoders.h" + +#include +#include + +struct aenc_t +{ + const audio_encoder_t *enc; + hnd_t handle; +}; + +hnd_t audio_encoder_open( const audio_encoder_t *encoder, hnd_t filter_chain, const char *opts ) +{ + assert( encoder && filter_chain ); + struct aenc_t *enc = calloc( 1, sizeof( struct aenc_t ) ); + enc->enc = encoder; + enc->handle = encoder->init( filter_chain, opts ); + + return enc; +} + +audio_info_t *audio_encoder_info( hnd_t encoder ) +{ + assert( encoder ); + struct aenc_t *enc = encoder; + + return enc->enc->get_info( enc->handle ); +} + +audio_packet_t *audio_encode_frame( hnd_t encoder ) +{ + assert( encoder ); + struct aenc_t *enc = encoder; + + return enc->enc->get_next_packet( enc->handle ); +} + +void audio_encoder_skip_samples( hnd_t encoder, uint64_t samplecount ) +{ + assert( encoder ); + struct aenc_t *enc = encoder; + + return enc->enc->skip_samples( enc->handle, samplecount ); +} + +audio_packet_t *audio_encoder_finish( hnd_t encoder ) +{ + assert( encoder ); + struct aenc_t *enc = encoder; + + return enc->enc->finish( enc->handle ); +} + +void audio_free_frame( hnd_t encoder, audio_packet_t *frame ) +{ + assert( encoder ); + struct aenc_t *enc = encoder; + + return enc->enc->free_packet( enc->handle, frame ); +} + +void audio_encoder_close( hnd_t encoder ) +{ + if( !encoder ) + return; + struct aenc_t *enc = encoder; + + enc->enc->close( enc->handle ); + free( enc ); +} + +const audio_encoder_t *encoder_by_name( char *name ) +{ +#define IFRET( enc ) if( !strcmp( #enc, name ) ) return &audio_encoder_ ## enc; +#if HAVE_LAME + IFRET( mp3 ); +#endif + IFRET( raw ); +#undef IFRET + return NULL; +} + +const audio_encoder_t *select_audio_encoder( char *encoder, char* allowed_list[] ) +{ + if( !encoder ) + return NULL; + if( allowed_list ) + { + if( !strcmp( encoder, "auto" ) ) + { + audio_encoder_t *enc; + for( int i = 0; allowed_list[i] != NULL; i++ ) + { + enc = encoder_by_name( allowed_list[i] ); + if( enc ) + return enc; + } + return NULL; + } + else + { + int valid = 0; + for( int i = 0; allowed_list[i] != NULL; i++ ) + if( !strcmp( encoder, allowed_list[i] ) ) + { + valid = 1; + break; + } + if( !valid ) + return NULL; + } + } + return encoder_by_name( encoder ); +} diff --git a/audio/encoders.h b/audio/encoders.h new file mode 100644 index 0000000..daacac2 --- /dev/null +++ b/audio/encoders.h @@ -0,0 +1,40 @@ +#ifndef AUDIO_ENCODERS_H_ +#define AUDIO_ENCODERS_H_ + +#include "audio/audio.h" +#include "filters/audio/audio_filters.h" + +typedef struct audio_encoder_t +{ + hnd_t (*init)( hnd_t filter_chain, const char *opts ); + audio_info_t *(*get_info)( hnd_t handle ); + audio_packet_t *(*get_next_packet)( hnd_t handle ); + void (*skip_samples)( hnd_t handle, uint64_t samplecount ); + audio_packet_t *(*finish)( hnd_t handle ); + void (*free_packet)( hnd_t handle, audio_packet_t *samples ); + void (*close)( hnd_t handle ); +} audio_encoder_t; + +extern const audio_encoder_t audio_encoder_raw; +#if HAVE_LAME +extern const audio_encoder_t audio_encoder_mp3; +#endif + +/* the first available encoder on allowed_list is the prefered encoder if encoder is "auto" + * allowed_list = NULL means any valid encoder is allowed + * The 'none' case isn't handled by this function (will return NULL like with any other invalid encoder) + * If the user wants 'none' to be a default, it must be tested outside of this function + * If the user wants to allow any encoder, the default case must be tested outside of this function */ +const audio_encoder_t *select_audio_encoder( char *encoder, char* allowed_list[] ); +const audio_encoder_t *encoder_by_name( char *name ); +hnd_t audio_encoder_open( const audio_encoder_t *encoder, hnd_t filter_chain, const char *opts ); + +audio_info_t *audio_encoder_info( hnd_t encoder ); +void audio_encoder_skip_samples( hnd_t encoder, uint64_t samplecount ); +audio_packet_t *audio_encode_frame( hnd_t encoder ); +audio_packet_t *audio_encoder_finish( hnd_t encoder ); +void audio_free_frame( hnd_t encoder, audio_packet_t *frame ); + +void audio_encoder_close( hnd_t encoder ); + +#endif diff --git a/audio/encoders/enc_mp3lame.c b/audio/encoders/enc_mp3lame.c new file mode 100644 index 0000000..77399e6 --- /dev/null +++ b/audio/encoders/enc_mp3lame.c @@ -0,0 +1,171 @@ +#include "audio/encoders.h" +#include "filters/audio/internal.h" + +#include "lame/lame.h" +#include + +typedef struct enc_lame_t +{ + audio_info_t info; + audio_info_t af_info; + hnd_t filter_chain; + + int finishing; + lame_global_flags *lame; + int64_t last_sample; + uint8_t *buffer; + size_t bufsize; + audio_packet_t *in; +} enc_lame_t; + +static hnd_t init( hnd_t filter_chain, const char *opt_str ) +{ + assert( filter_chain ); + audio_hnd_t *chain = filter_chain; + if( chain->info.channels > 2 ) + { + x264_cli_log( "lame", X264_LOG_ERROR, "only mono or stereo audio is supported\n" ); + return 0; + } + enc_lame_t *h = calloc( 1, sizeof( enc_lame_t ) ); + h->filter_chain = chain; + h->info = h->af_info = chain->info; + + char **opts = x264_split_options( opt_str, (const char*[]){ "bitrate", "vbr", "quality", NULL } ); + assert( opts ); + + char *cbr = x264_get_option( "bitrate", opts ); + char *vbr = x264_get_option( "vbr", opts ); + assert( ( cbr && !vbr ) || ( !cbr && vbr ) ); + + float brval = x264_otof( vbr, 6.0 ); + brval = x264_otof( cbr, brval ); + int quality = x264_otoi( x264_get_option( "quality", opts ), 0 ); + + x264_free_string_array( opts ); + + h->info.codec_name = "mp3"; + h->info.extradata = NULL; + h->info.extradata_size = 0; + + h->lame = lame_init(); + lame_set_scale( h->lame, 32768 ); + lame_set_in_samplerate( h->lame, h->info.samplerate ); + lame_set_num_channels( h->lame, h->info.channels ); + lame_set_quality( h->lame, quality ); + lame_set_VBR( h->lame, vbr_default ); + + if( cbr ) + { + lame_set_VBR( h->lame, vbr_off ); + lame_set_brate( h->lame, (int) brval ); + } + else + lame_set_VBR_quality( h->lame, brval ); + + lame_init_params( h->lame ); + + h->info.framelen = lame_get_framesize( h->lame ); + h->info.framesize = h->info.framelen * 2; + h->info.chansize = 2; + h->info.samplesize = 2 * h->info.channels; + + h->bufsize = 125 * h->info.framelen / 100 + 7200; + + x264_cli_log( "audio", X264_LOG_INFO, "opened lame mp3 encoder (%s: %g%s)\n", + ( cbr ? "bitrate" : "VBR" ), brval, + ( cbr ? "kbps" : "" ) ); + + return h; +} + +static audio_info_t *get_info( hnd_t handle ) +{ + assert( handle ); + enc_lame_t *h = handle; + + return &h->info; +} + +static void free_packet( hnd_t handle, audio_packet_t *packet ) +{ + packet->owner = NULL; + af_free_packet( packet ); +} + +static audio_packet_t *get_next_packet( hnd_t handle ) +{ + enc_lame_t *h = handle; + if( h->finishing ) + return NULL; + + audio_packet_t *out = calloc( 1, sizeof( audio_packet_t ) ); + out->rawdata = malloc( h->bufsize ); + + while( !out->size ) + { + if( h->in && h->in->flags & AUDIO_FLAG_EOF ) + { + h->finishing = 1; + goto error; // Not an error here but it'd do the same handling + } + af_free_packet( h->in ); + + if( !( h->in = af_get_samples( h->filter_chain, h->last_sample, h->last_sample + h->info.framelen ) ) ) + goto error; + h->last_sample += h->in->samplecount; + + out->size = lame_encode_buffer_float( h->lame, h->in->data[0], h->in->data[1], + h->in->samplecount, out->rawdata, h->bufsize ); + } + + return out; + +error: + af_free_packet( h->in ); + af_free_packet( out ); + return NULL; +} + +static void skip_samples( hnd_t handle, uint64_t samplecount ) +{ + ((enc_lame_t*)handle)->last_sample += samplecount; +} + +static audio_packet_t *finish( hnd_t encoder ) +{ + enc_lame_t *h = encoder; + h->finishing = 1; + + audio_packet_t *out = calloc( 1, sizeof( audio_packet_t ) ); + out->rawdata = malloc( h->bufsize ); + out->size = lame_encode_flush( h->lame, out->rawdata, h->bufsize ); + if( !out->size ) + goto error; + return out; + +error: + af_free_packet( out ); + return NULL; +} + + +static void mp3_close( hnd_t handle ) +{ + enc_lame_t *h = handle; + + lame_close( h->lame ); + free( h ); +} + +const audio_encoder_t audio_encoder_mp3 = +{ + .init = init, + .get_info = get_info, + .get_next_packet = get_next_packet, + .skip_samples = skip_samples, + .finish = finish, + .free_packet = free_packet, + .close = mp3_close +}; + diff --git a/audio/encoders/enc_raw.c b/audio/encoders/enc_raw.c new file mode 100644 index 0000000..c1a890c --- /dev/null +++ b/audio/encoders/enc_raw.c @@ -0,0 +1,92 @@ +#include "audio/encoders.h" +#include "filters/audio/internal.h" + +#include + +typedef struct enc_raw_t +{ + audio_info_t info; + int finishing; + hnd_t filter_chain; + int64_t last_sample; +} enc_raw_t; + +static hnd_t init( hnd_t filter_chain, const char *opts ) +{ + assert( filter_chain ); + enc_raw_t *h = calloc( 1, sizeof( enc_raw_t ) ); + audio_hnd_t *chain = h->filter_chain = filter_chain; + h->info = chain->info; + + h->info.codec_name = "raw"; + h->info.extradata = NULL; + h->info.extradata_size = 0; + h->info.chansize = 2; + h->info.samplesize = 2 * h->info.channels; + + x264_cli_log( "audio", X264_LOG_INFO, "opened raw encoder (%dbits, %dch, %dhz)\n", + h->info.chansize * 8, h->info.channels, h->info.samplerate ); + return h; +} + +static audio_info_t *get_info( hnd_t handle ) +{ + assert( handle ); + enc_raw_t *h = handle; + + return &h->info; +} + +static audio_packet_t *get_next_packet( hnd_t handle ) +{ + enc_raw_t *h = handle; + if( h->finishing ) + return NULL; + + audio_packet_t *smp = af_get_samples( h->filter_chain, h->last_sample, h->last_sample + h->info.framelen ); + if( !smp ) + return NULL; + h->last_sample += h->info.framelen; + + audio_packet_t *out = calloc( 1, sizeof( audio_packet_t ) ); + memcpy( out, smp, sizeof( audio_packet_t ) ); + out->data = NULL; + out->size = 0; + out->rawdata = af_interleave2( SMPFMT_S16, smp->data, smp->channels, smp->samplecount ); + out->size = smp->samplecount * h->info.samplesize; + af_free_packet( smp ); + + return out; +} + +static void skip_samples( hnd_t handle, uint64_t samplecount ) +{ + ((enc_raw_t*)handle)->last_sample += samplecount; +} + +static audio_packet_t *finish( hnd_t handle ) +{ + ((enc_raw_t*)handle)->finishing = 1; + return NULL; +} + +static void free_packet( hnd_t handle, audio_packet_t *packet ) +{ + af_free_packet( packet ); +} + +static void raw_close( hnd_t handle ) +{ + free( handle ); +} + +const audio_encoder_t audio_encoder_raw = +{ + .init = init, + .get_info = get_info, + .get_next_packet = get_next_packet, + .skip_samples = skip_samples, + .finish = finish, + .free_packet = free_packet, + .close = raw_close +}; diff --git a/configure b/configure index b6382f0..aab64af 100755 --- a/configure +++ b/configure @@ -11,6 +11,7 @@ echo " --disable-avs disables avisynth support (windows only)" echo " --disable-lavf disables libavformat support" echo " --disable-ffms disables ffmpegsource support" echo " --disable-gpac disables gpac support" +echo " --disable-audio disables audio support (requires lavf support)" echo " --disable-pthread disables multithreaded encoding" echo " --disable-swscale disables swscale support" echo " --disable-asm disables platform-specific assembly optimizations" @@ -138,6 +139,7 @@ avs="auto" lavf="auto" ffms="auto" gpac="auto" +audio="auto" pthread="auto" swscale="auto" asm="auto" @@ -148,6 +150,8 @@ vis="no" shared="no" bit_depth="8" +audio_mp3="auto" + CFLAGS="$CFLAGS -Wall -I." LDFLAGS="$LDFLAGS" LDFLAGSCLI="$LDFLAGSCLI" @@ -192,6 +196,9 @@ for opt do --disable-gpac) gpac="no" ;; + --disable-audio) + audio="no" + ;; --extra-asflags=*) ASFLAGS="$ASFLAGS ${opt#--extra-asflags=}" ;; @@ -625,6 +632,23 @@ elif [ "$swscale" = "yes" ]; then CFLAGS="$CFLAGS $SWSCALE_CFLAGS" fi +if [ "$audio" = "auto" ]; then + audio="yes" + audio_encs="raw" + if [ "$audio_mp3" = "auto" ]; then + if cc_check lame/lame.h "-lmp3lame" "get_lame_version();"; then + LDFLAGSCLI="-lmp3lame $LDFLAGSCLI" + audio_mp3="yes" + audio_encs="$audio_encs, mp3" + else + audio_mp3="no" + fi + fi +fi + +define HAVE_AUDIO $(test "$audio" = "yes" && echo 1 || echo 0) +define HAVE_LAME $(test "$audio_mp3" = "yes" && echo 1 || echo 0) + GPAC_LIBS="-lgpac_static" if [ $SYS = MINGW ]; then GPAC_LIBS="$GPAC_LIBS -lwinmm" @@ -779,6 +803,7 @@ asm: $asm avs: $avs lavf: $lavf ffms: $ffms +audio: $audio$(test $audio = "yes" && echo " ($audio_encs)") gpac: $gpac pthread: $pthread filters: $filters diff --git a/filters/audio/audio_filters.c b/filters/audio/audio_filters.c new file mode 100644 index 0000000..0bff822 --- /dev/null +++ b/filters/audio/audio_filters.c @@ -0,0 +1,61 @@ +#include "filters/audio/internal.h" + +#include + +audio_info_t *af_get_info( hnd_t handle ) +{ + return &((audio_hnd_t*)handle)->info; +} + +audio_filter_t *af_get_filter( char *name ) +{ +#define CHECK( filter ) \ + extern audio_filter_t audio_filter_##filter; \ + if ( !strcmp( name, audio_filter_##filter.name ) ) \ + return &audio_filter_##filter +#if HAVE_LAVF + CHECK( lavf ); +#endif +#undef CHECKFLT +#undef CHECK + return NULL; +} + +audio_packet_t *af_get_samples( hnd_t handle, int64_t first_sample, int64_t last_sample ) +{ + audio_hnd_t *h = handle; + audio_packet_t *out = h->self->get_samples( h, first_sample, last_sample ); + if( out ) + { + out->owner = h; + return out; + } + return 0; +} + +void af_free_packet( audio_packet_t *pkt ) +{ + if( !pkt ) + return; + audio_hnd_t *owner = pkt->owner; + if( owner ) + owner->self->free_packet( owner, pkt ); + else + { + if( pkt->priv ) + free( pkt->priv ); + if( pkt->rawdata ) + free( pkt->rawdata ); + if( pkt->data && pkt->channels ) + af_free_buffer( pkt->data, pkt->channels ); + free( pkt ); + } +} + +void af_close( hnd_t chain ) +{ + audio_hnd_t *h = chain; + if( h->prev ) + af_close( h->prev ); + h->self->close( h ); +} diff --git a/filters/audio/audio_filters.h b/filters/audio/audio_filters.h new file mode 100644 index 0000000..cdd9bf2 --- /dev/null +++ b/filters/audio/audio_filters.h @@ -0,0 +1,85 @@ +#ifndef FILTERS_AUDIO_AUDIO_FILTERS_H_ +#define FILTERS_AUDIO_AUDIO_FILTERS_H_ + +#include +#include "x264cli.h" +#include "filters/filters.h" + +// Ripped from ffmpeg's avcodec.h +#ifndef CH_FRONT_LEFT +#define CH_FRONT_LEFT 0x00000001 +#define CH_FRONT_RIGHT 0x00000002 +#define CH_FRONT_CENTER 0x00000004 +#define CH_LOW_FREQUENCY 0x00000008 +#define CH_BACK_LEFT 0x00000010 +#define CH_BACK_RIGHT 0x00000020 +#define CH_FRONT_LEFT_OF_CENTER 0x00000040 +#define CH_FRONT_RIGHT_OF_CENTER 0x00000080 +#define CH_BACK_CENTER 0x00000100 +#define CH_SIDE_LEFT 0x00000200 +#define CH_SIDE_RIGHT 0x00000400 +#define CH_TOP_CENTER 0x00000800 +#define CH_TOP_FRONT_LEFT 0x00001000 +#define CH_TOP_FRONT_CENTER 0x00002000 +#define CH_TOP_FRONT_RIGHT 0x00004000 +#define CH_TOP_BACK_LEFT 0x00008000 +#define CH_TOP_BACK_CENTER 0x00010000 +#define CH_TOP_BACK_RIGHT 0x00020000 +#define CH_STEREO_LEFT 0x20000000 ///< Stereo downmix. +#define CH_STEREO_RIGHT 0x40000000 ///< See CH_STEREO_LEFT. +#endif + +enum AudioFlags +{ + AUDIO_FLAG_NONE = 0, + AUDIO_FLAG_EOF = 1 +}; + +typedef struct audio_packet_t { + int64_t dts; + float **data; + int size; + unsigned channels; + unsigned samplecount; + uint8_t *rawdata; + int rawsize; + int64_t pos; + enum AudioFlags flags; + hnd_t priv; + hnd_t owner; +} audio_packet_t; + +typedef struct audio_filter_t +{ + int (*init)( hnd_t *handle, const char *opts ); + struct audio_packet_t *(*get_samples)( hnd_t handle, int64_t first_sample, int64_t last_sample ); + void (*free_packet)( hnd_t self, struct audio_packet_t *frame ); + void (*close)( hnd_t handle ); + char *name, *longname, *description, *help; + void (*help_callback)( int longhelp ); +} audio_filter_t; + +typedef struct audio_info_t +{ + char *codec_name; + int samplerate; // Sample Rate in Hz + int channels; // How many channels + int64_t chanlayout; // Channel layout (CH_*) + int framelen; // Frame length in samples + size_t framesize; // Frame size in bytes + int chansize; // Bytes per channel per sample (from the encoded audio) + int samplesize; // Bytes per sample (from the encoded audio) + int64_t time_base_num, time_base_den; + uint8_t *extradata; + int extradata_size; +} audio_info_t; + +#include "audio/audio.h" + +audio_info_t *af_get_info( hnd_t handle ); +audio_filter_t *af_get_filter( char *name ); +audio_packet_t *af_get_samples( hnd_t handle, int64_t first_sample, int64_t last_sample ); +void af_free_packet( audio_packet_t *pkt ); +void af_close( hnd_t chain ); + +#endif /* AUDIO_H_ */ diff --git a/filters/audio/internal.c b/filters/audio/internal.c new file mode 100644 index 0000000..d41b124 --- /dev/null +++ b/filters/audio/internal.c @@ -0,0 +1,160 @@ +#include "filters/audio/internal.h" +#include +#include + +float **af_get_buffer( unsigned channels, unsigned samplecount ) +{ + float **samples = malloc( sizeof( float* ) * channels ); + for( int i = 0; i < channels; i++ ) { + samples[i] = malloc( sizeof( float ) * samplecount ); + } + return samples; +} + +int af_resize_buffer( float **buffer, unsigned channels, unsigned samplecount ) +{ + for( int c = 0; c < channels; c++ ) + { + if( !(buffer[c] = realloc( buffer[c], sizeof( float ) * samplecount )) ) + return -1; + } + return 0; +} + +float **af_dup_buffer( float **buffer, unsigned channels, unsigned samplecount ) +{ + float **buf = af_get_buffer( channels, samplecount ); + for( int c = 0; c < channels; c++ ) + memcpy( buf[c], buffer[c], samplecount ); + return buf; +} + +void af_free_buffer( float **buffer, unsigned channels ) +{ + if( !buffer ) + return; + for( int c = 0; c < channels; c++ ) + free( buffer[c] ); + free( buffer ); +} + +int af_cat_buffer( float **buf, unsigned bufsamples, float **in, unsigned insamples, unsigned channels ) +{ + if( af_resize_buffer( buf, channels, bufsamples + insamples ) < 0 ) + return -1; + for( int c = 0; c < channels; c++ ) + for( int s = 0; s < insamples; s++ ) + buf[c][bufsamples+s] = in[c][s]; + return 0; +} + +float **af_deinterleave ( float *samples, unsigned channels, unsigned samplecount ) +{ + float **deint = af_get_buffer( channels, samplecount ); + for( int s = 0; s < samplecount; s++ ) + for( int c = 0; c < channels; c++ ) + deint[c][s] = samples[s*channels + c]; + return deint; +} + +float *af_interleave ( float **in, unsigned channels, unsigned samplecount ) +{ + float *inter = malloc( sizeof( float ) * channels * samplecount ); + for( int c = 0; c < channels; c++ ) + for( int s = 0; s < samplecount; s++ ) + inter[s*channels + c] = in[c][s]; + return inter; +} + +float **af_deinterleave2( uint8_t *samples, enum SampleFmt fmt, unsigned channels, unsigned samplecount ) +{ + float *in = (float*) af_convert( SMPFMT_FLT, samples, fmt, channels, samplecount ); + float **out = af_deinterleave( in, channels, samplecount ); + free( in ); + return out; +} + +uint8_t *af_interleave2( enum SampleFmt outfmt, float **in, unsigned channels, unsigned samplecount ) +{ + float *tmp = af_interleave( in, channels, samplecount ); + uint8_t *out = af_convert( outfmt, (uint8_t*) tmp, SMPFMT_FLT, channels, samplecount ); + free( tmp ); + return out; +} + +static inline int samplesize( enum SampleFmt fmt ) +{ + switch( fmt ) + { + case SMPFMT_U8: + return 1; + case SMPFMT_S16: + return 2; + case SMPFMT_S32: + case SMPFMT_FLT: + return 4; + case SMPFMT_DBL: + return 8; + default: + return 0; + } +} + +#define CLIPFUN( num, type, min, max ) \ + static inline type clip##num( int64_t i ) { \ + return (type)( ( i > max ) ? max : ( ( i < min ) ? min : i ) ); \ + } +CLIPFUN( 8, uint8_t, 0, UINT8_MAX ) +CLIPFUN( 16, int16_t, INT16_MIN, INT16_MAX ) +CLIPFUN( 32, int32_t, INT32_MIN, INT32_MAX ) +#undef CLIPFUN + +uint8_t *af_convert( enum SampleFmt outfmt, uint8_t *in, enum SampleFmt fmt, unsigned channels, unsigned samplecount ) +{ + int totalsamples = channels * samplecount; + int sz = samplesize( outfmt ) * totalsamples; + uint8_t *out = malloc( sz ); + if( !out ) + return NULL; + + if( fmt == outfmt ) + { + memcpy( out, in, sz ); + return out; + } + +#define CONVERT( ifmt, ofmt, otype, expr ) \ + if( ifmt == fmt && ofmt == outfmt ) { \ + for( int i = 0; i < totalsamples; i++ ) \ + { \ + ((otype*)out)[i] = (otype)expr; \ + } \ + return out; \ + } +#define IN( itype ) (((itype*)in)[i]) + + CONVERT( SMPFMT_U8, SMPFMT_S16, int16_t, (IN( uint8_t ) - 0x80) << 8 ); + CONVERT( SMPFMT_U8, SMPFMT_S32, int32_t, (IN( uint8_t ) - 0x80) << 24 ); + CONVERT( SMPFMT_U8, SMPFMT_FLT, float, (IN( uint8_t ) - 0x80) * (1.0 / (1<<7)) ); + CONVERT( SMPFMT_U8, SMPFMT_DBL, double, (IN( uint8_t ) - 0x80) * (1.0 / (1<<7)) ); + CONVERT( SMPFMT_S16, SMPFMT_U8, uint8_t, (IN( int16_t ) >> 8) + 0x80 ); + CONVERT( SMPFMT_S16, SMPFMT_S32, int32_t, IN( int16_t ) << 16 ); + CONVERT( SMPFMT_S16, SMPFMT_FLT, float, IN( int16_t ) * (1.0 / (1<<15)) ); + CONVERT( SMPFMT_S16, SMPFMT_DBL, double, IN( int16_t ) * (1.0 / (1<<15)) ); + CONVERT( SMPFMT_S32, SMPFMT_U8, uint8_t, (IN( int32_t ) >> 24) + 0x80 ); + CONVERT( SMPFMT_S32, SMPFMT_S16, int16_t, IN( int32_t ) >> 16 ); + CONVERT( SMPFMT_S32, SMPFMT_FLT, float, IN( int32_t ) * (1.0 / (1<<31)) ); + CONVERT( SMPFMT_S32, SMPFMT_DBL, double, IN( int32_t ) * (1.0 / (1<<31)) ); + CONVERT( SMPFMT_FLT, SMPFMT_U8, uint8_t, clip8( lrintf( IN( float ) * (1<<7) ) + 0x80 ) ); + CONVERT( SMPFMT_FLT, SMPFMT_S16, int16_t, clip16( lrintf( IN( float ) * (1<<15) ) ) ); + CONVERT( SMPFMT_FLT, SMPFMT_S32, int32_t, clip32( llrintf( IN( float ) * (1U<<31) ) ) ); + CONVERT( SMPFMT_FLT, SMPFMT_DBL, double, IN( float ) ); + CONVERT( SMPFMT_FLT, SMPFMT_U8, uint8_t, clip8( lrintf( IN( double ) * (1<<7) ) + 0x80 ) ); + CONVERT( SMPFMT_FLT, SMPFMT_S16, int16_t, clip16( lrintf( IN( double ) * (1<<15) ) ) ); + CONVERT( SMPFMT_FLT, SMPFMT_S32, int32_t, clip32( llrintf( IN( double ) * (1U<<31) ) ) ); + CONVERT( SMPFMT_FLT, SMPFMT_DBL, double, IN( double ) ); +#undef IN +#undef CONVERT + free( out ); + return NULL; +} diff --git a/filters/audio/internal.h b/filters/audio/internal.h new file mode 100644 index 0000000..56a833a --- /dev/null +++ b/filters/audio/internal.h @@ -0,0 +1,58 @@ +#ifndef FILTERS_AUDIO_INTERNAL_H_ +#define FILTERS_AUDIO_INTERNAL_H_ + +#include "filters/audio/audio_filters.h" + +#define AUDIO_FILTER_COMMON \ + const audio_filter_t *self; \ + audio_info_t info; \ + struct audio_hnd_t *prev; + +#define INIT_FILTER_STRUCT(filterstruct, structname) \ + structname *h; \ + do \ + { \ + h = calloc( 1, sizeof( structname ) ); \ + if( !h ) \ + goto fail; \ + h->self = &filterstruct; \ + h->prev = *handle; \ + if( h->prev ) \ + h->info = h->prev->info; \ + *handle = h; \ + } while( 0 ) + +// Generic audio handle (used to access fields from AUDIO_FILTER_COMMON) +typedef struct audio_hnd_t +{ + AUDIO_FILTER_COMMON +} audio_hnd_t; + +#define AF_LOG( handle, level, ... ) do { x264_cli_log( ((audio_hnd_t*)handle)->self->name, (level), __VA_ARGS__ ); } while (0) + +#define AF_LOG_ERR( handle, ... ) AF_LOG( (handle), X264_LOG_ERROR , __VA_ARGS__ ) +#define AF_LOG_WARN( handle, ... ) AF_LOG( (handle), X264_LOG_WARNING, __VA_ARGS__ ) + +enum SampleFmt { + SMPFMT_NONE = -1, + SMPFMT_U8, + SMPFMT_S16, + SMPFMT_S32, + SMPFMT_FLT, + SMPFMT_DBL +}; + +float **af_get_buffer ( unsigned channels, unsigned samplecount ); +int af_resize_buffer( float **buffer, unsigned channels, unsigned samplecount ); +void af_free_buffer ( float **buffer, unsigned channels ); +float **af_dup_buffer ( float **buffer, unsigned channels, unsigned samplecount ); +int af_cat_buffer ( float **buf, unsigned bufsamples, float **in, unsigned insamples, unsigned channels ); + +float **af_deinterleave ( float *samples, unsigned channels, unsigned samplecount ); +float *af_interleave ( float **in, unsigned channels, unsigned samplecount ); + +float **af_deinterleave2( uint8_t *samples, enum SampleFmt fmt, unsigned channels, unsigned samplecount ); +uint8_t *af_interleave2 ( enum SampleFmt outfmt, float **in, unsigned channels, unsigned samplecount ); +uint8_t *af_convert ( enum SampleFmt outfmt, uint8_t *in, enum SampleFmt fmt, unsigned channels, unsigned samplecount ); + +#endif /* FILTERS_AUDIO_INTERNAL_H_ */ diff --git a/input/audio/lavf.c b/input/audio/lavf.c new file mode 100644 index 0000000..8ead92a --- /dev/null +++ b/input/audio/lavf.c @@ -0,0 +1,396 @@ +#include "filters/audio/internal.h" +#undef DECLARE_ALIGNED +#include "libavformat/avformat.h" +#include "libavcodec/avcodec.h" +#include +#include +#include + +typedef struct lavf_source_t +{ + AUDIO_FILTER_COMMON + AVFormatContext *lavf; + AVCodecContext *ctx; + AVCodec *codec; + + int samplefmt; + unsigned track; + uint8_t *buffer; + intptr_t bufsize; + intptr_t surplus; + intptr_t len; + uint64_t bytepos; + + AVPacket *pkt; +} lavf_source_t; + +#define DEFAULT_BUFSIZE AVCODEC_MAX_AUDIO_FRAME_SIZE * 2 + +static int buffer_next_frame( lavf_source_t *h ); + +const audio_filter_t audio_filter_lavf; + +static int init( hnd_t *handle, const char *opt_str ) +{ + assert( opt_str ); + assert( !(*handle) ); // This must be the first filter + char **opts = x264_split_options( opt_str, (const char*[]){ "filename", "track", NULL } ); + + if( !opts ) + return -1; + + char *filename = x264_get_option( "filename", opts ); + char *trackstr = x264_otos( x264_get_option( "track", opts ), "any" ); + + assert( filename ); + + int track; + if ( !strcmp( trackstr, "any" ) ) + track = TRACK_ANY; + else + track = x264_otoi( trackstr, TRACK_NONE ); + + if( track == TRACK_NONE ) + { + x264_cli_log( "lavf", X264_LOG_ERROR, "no valid track requested ('any', 0 or a positive integer)\n" ); + goto fail2; + } + + INIT_FILTER_STRUCT( audio_filter_lavf, lavf_source_t ); + + av_register_all(); + if( !strcmp( filename, "-" ) ) + filename = "pipe:"; + + if( av_open_input_file( &h->lavf, filename, NULL, 0, NULL ) ) + { + AF_LOG_ERR( h, "could not open audio file\n" ); + goto fail; + } + + if( av_find_stream_info( h->lavf ) < 0 ) + { + AF_LOG_ERR( h, "could not find stream info\n" ); + goto fail; + } + + unsigned tid = TRACK_NONE; + if( track >= 0 ) + { + if( track < h->lavf->nb_streams && + h->lavf->streams[track]->codec->codec_type == CODEC_TYPE_AUDIO ) + tid = track; + else + AF_LOG_ERR( h, "requested track %d is unavailable " + "or is not an audio track\n", track ); + } + else // TRACK_ANY (pick first) + { + for( track = 0; + track < h->lavf->nb_streams && + h->lavf->streams[track]->codec->codec_type != CODEC_TYPE_AUDIO; ) + ++track; + if( track < h->lavf->nb_streams ) + tid = track; + else + AF_LOG_ERR( h, "could not find any audio track\n" ); + } + + if( tid == TRACK_NONE ) + goto fail; + + h->track = tid; + + h->ctx = h->lavf->streams[tid]->codec; + h->codec = avcodec_find_decoder( h->ctx->codec_id ); + if( avcodec_open( h->ctx, h->codec ) ) + goto codecfail; + + h->samplefmt = h->ctx->sample_fmt; + h->info = (audio_info_t) + { + .samplerate = h->ctx->sample_rate, + .channels = h->ctx->channels, + .chanlayout = h->ctx->channel_layout, + .framelen = h->ctx->frame_size, + .framesize = h->ctx->frame_size * sizeof( float ), + .chansize = av_get_bits_per_sample_format( h->samplefmt ) / 8, + .samplesize = av_get_bits_per_sample_format( h->samplefmt ) * h->ctx->channels / 8, + .time_base_num = h->ctx->time_base.num, + .time_base_den = h->ctx->time_base.den, + .extradata = h->ctx->extradata, + .extradata_size = h->ctx->extradata_size + }; + + h->bufsize = DEFAULT_BUFSIZE; + h->surplus = h->info.framesize * 3 / 2; + assert( h->bufsize > h->surplus * 2 ); + h->buffer = av_malloc( h->bufsize ); + + if( !buffer_next_frame( h ) ) + goto codecfail; + + x264_free_string_array( opts ); + return 0; + +codecfail: + AF_LOG_ERR( h, "error opening the %s decoder for track %d\n", h->codec->name, h->track ); +fail: + if( h->lavf ) + av_close_input_file( h->lavf ); + if( h ) + free( h ); + *handle = NULL; +fail2: + x264_free_string_array( opts ); + return -1; +} + +static inline void free_avpacket( AVPacket *pkt ) +{ + av_free_packet( pkt ); + free( pkt ); +} + +static void free_packet( hnd_t handle, audio_packet_t *pkt ) +{ + pkt->owner = NULL; + af_free_packet( pkt ); +} + +static struct AVPacket *next_packet( lavf_source_t *h ) +{ + AVPacket *pkt = calloc( 1, sizeof( AVPacket ) ); + + int ret; + do + { + if( pkt->data ) + av_free_packet( pkt ); + if( (ret = av_read_frame( h->lavf, pkt )) ) + { + if( ret != AVERROR_EOF ) + AF_LOG_ERR( h, "read error: %s\n", strerror( -ret ) ); + else + AF_LOG( h, X264_LOG_INFO, "end of file reached\n" ); + free_avpacket( pkt ); + return NULL; + } + } + while( pkt->stream_index != h->track ); + + return pkt; +} + +static int low_decode_audio( lavf_source_t *h, uint8_t *buf, intptr_t buflen ) +{ + static AVPacket pkt_temp; + static uint8_t desync_warn = 0; + + int len = 0, datalen = 0; + + while( h->pkt && pkt_temp.size > 0 ) + { + datalen = buflen; + len = avcodec_decode_audio3( h->ctx, (int16_t*) buf, &datalen, &pkt_temp ); + + if( len < 0 ) { + // Broken frame, drop + if( !desync_warn++ ) // repeat the warning every 256 errors + AF_LOG_WARN( h, "Decoding errors may cause audio desync\n" ); + pkt_temp.size = 0; + break; + } + + pkt_temp.data += len; + pkt_temp.size -= len; + + if( datalen < 0 ) + continue; + + return datalen; + } + + free_avpacket( h->pkt ); + h->pkt = next_packet( h ); + + if( !h->pkt ) + return -1; + + pkt_temp.data = h->pkt->data; + pkt_temp.size = h->pkt->size; + + return 0; +} + +static struct AVPacket *decode_next_frame( lavf_source_t *h ) +{ + AVPacket *dst = calloc( 1, sizeof( AVPacket ) ); + assert( !av_new_packet( dst, AVCODEC_MAX_AUDIO_FRAME_SIZE ) ); + + int len = 0; + while( ( len = low_decode_audio( h, dst->data, dst->size ) ) == 0 ) + { + // Read more + } + if( len < 0 ) // EOF or demuxing error + { + free_avpacket( dst ); + return NULL; + } + + dst->size = len; + + return dst; +} + +static int buffer_next_frame( lavf_source_t *h ) +{ + AVPacket *dec = decode_next_frame( h ); + if( !dec ) + return 0; + + if( h->len + dec->size > h->bufsize ) + { + memmove( h->buffer, h->buffer + dec->size, h->bufsize - dec->size ); + h->len -= dec->size; + h->bytepos += dec->size; + } + memcpy( h->buffer + h->len, dec->data, dec->size ); + h->len += dec->size; + + free_avpacket( dec ); + + return 1; +} + +static inline int not_in_cache( lavf_source_t *h, int64_t sample ) +{ + int64_t samplebyte = sample * h->info.samplesize; + if( samplebyte < h->bytepos ) + return -1; // before + else if( samplebyte < h->bytepos + h->len ) + return 0; // in cache + return 1; // after +} + +static int64_t fill_buffer_until( lavf_source_t *h, int64_t lastsample ) +{ + static int errored = 0; + if( errored ) + return -1; + if( not_in_cache( h, lastsample ) < 0 ) + { + AF_LOG_ERR( h, "backwards seeking not supported yet " + "(requested sample %"PRIu64", first available is %"PRIu64")\n", + lastsample, h->bytepos / h->info.samplesize ); + return -1; + } + int ret; + while( ( ret = not_in_cache( h, lastsample ) ) > 0 ) + { + if( !buffer_next_frame( h ) ) + { + // libavcodec already warns for us + errored = 1; + break; + } + } + assert( ret >= 0 ); + return h->bytepos + h->len; +} + + +static struct audio_packet_t *get_samples( hnd_t handle, int64_t first_sample, int64_t last_sample ) +{ + lavf_source_t *h = handle; + assert( first_sample >= 0 && last_sample > first_sample ); + + if( fill_buffer_until( h, first_sample ) < 0 ) + return NULL; + + audio_packet_t *pkt = calloc( 1, sizeof( audio_packet_t ) ); + pkt->channels = h->info.channels; + pkt->samplecount = last_sample - first_sample; + pkt->size = pkt->samplecount * h->info.samplesize; + + if( pkt->size + h->surplus > h->bufsize ) + { + int64_t pivot = first_sample + ( h->bufsize - h->surplus * 2 ) / h->info.samplesize; + int64_t expected_size = ( pivot - first_sample ) * h->info.samplesize; + + audio_packet_t *prev = get_samples( h, first_sample, pivot ); + if( !prev ) + goto fail; + + if( prev->size < expected_size ) // EOF + { + af_free_packet( pkt ); + prev->flags |= AUDIO_FLAG_EOF; + return prev; + } + assert( prev->size == expected_size ); + + audio_packet_t *next = get_samples( h, pivot, last_sample ); + if( !next ) + { + af_free_packet( prev ); + goto fail; + } + + pkt->data = af_dup_buffer( prev->data, prev->channels, prev->samplecount ); + af_cat_buffer( pkt->data, pkt->samplecount, next->data, next->samplecount, pkt->channels ); + + pkt->samplecount = prev->samplecount + next->samplecount; + pkt->size = prev->size + next->size; + + af_free_packet( prev ); + af_free_packet( next ); + } + else + { + int64_t lastreq = last_sample * h->info.samplesize; + int64_t lastavail = fill_buffer_until( h, last_sample ); + if( lastavail < 0 ) + goto fail; + + intptr_t start = ( first_sample * h->info.samplesize ) - h->bytepos; + + if( lastavail < lastreq ) + { + pkt->size = lastavail - h->bytepos - start; + pkt->samplecount = pkt->size / h->info.samplesize; + pkt->flags = AUDIO_FLAG_EOF; + } + assert( start + pkt->size <= h->bufsize ); + pkt->data = af_deinterleave2( h->buffer + start, h->samplefmt, pkt->channels, pkt->samplecount ); + } + + return pkt; + +fail: + af_free_packet( pkt ); + return NULL; +} + +static void lavf_close( hnd_t handle ) +{ + assert( handle ); + lavf_source_t *h = handle; + av_free( h->buffer ); + free_avpacket( h->pkt ); + avcodec_close( h->ctx ); + av_close_input_file( h->lavf ); + free( h ); +} + +const audio_filter_t audio_filter_lavf = +{ + .name = "lavf", + .description = "Demuxes and decodes audio files using libavformat + libavcodec", + .help = "Arguments: filename[:track]", + .init = init, + .get_samples = get_samples, + .free_packet = free_packet, + .close = lavf_close +}; diff --git a/input/ffms.c b/input/ffms.c index 84118d5..861cddc 100644 --- a/input/ffms.c +++ b/input/ffms.c @@ -35,8 +35,13 @@ #define SetConsoleTitle(t) #endif +#if HAVE_AUDIO +#include "audio/audio.h" +#endif + typedef struct { + char *filename; FFMS_VideoSource *video_source; FFMS_Track *track; int reduce_pts; @@ -85,6 +90,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c int trackno = FFMS_GetFirstTrackOfType( idx, FFMS_TYPE_VIDEO, &e ); FAIL_IF_ERROR( trackno < 0, "could not find video track\n" ) + h->filename = strdup( psz_filename ); h->video_source = FFMS_CreateVideoSource( psz_filename, trackno, idx, 1, seekmode, &e ); FAIL_IF_ERROR( !h->video_source, "could not create video source\n" ) @@ -173,8 +179,19 @@ static int close_file( hnd_t handle ) { ffms_hnd_t *h = handle; FFMS_DestroyVideoSource( h->video_source ); + free( h->filename ); free( h ); return 0; } +#if HAVE_AUDIO +static hnd_t open_audio( hnd_t handle, int track ) +{ + ffms_hnd_t *h = handle; + return audio_open_from_file( NULL, h->filename, track ); +} + +const cli_input_t ffms_input = { open_file, picture_alloc, read_frame, NULL, picture_clean, close_file, open_audio }; +#else const cli_input_t ffms_input = { open_file, picture_alloc, read_frame, NULL, picture_clean, close_file }; +#endif diff --git a/input/input.h b/input/input.h index bb1bfb7..18f0a40 100644 --- a/input/input.h +++ b/input/input.h @@ -83,6 +83,9 @@ typedef struct int (*release_frame)( cli_pic_t *pic, hnd_t handle ); void (*picture_clean)( cli_pic_t *pic ); int (*close_file)( hnd_t handle ); +#if HAVE_AUDIO + hnd_t (*open_audio)( hnd_t handle, int track ); +#endif } cli_input_t; extern const cli_input_t raw_input; diff --git a/input/lavf.c b/input/lavf.c index e1dd352..38fd405 100644 --- a/input/lavf.c +++ b/input/lavf.c @@ -27,8 +27,13 @@ #include #include +#if HAVE_AUDIO +#include "audio/audio.h" +#endif + typedef struct { + char *filename; AVFormatContext *lavf; int stream_id; int next_frame; @@ -134,6 +139,7 @@ static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, c if( param ) free( param ); FAIL_IF_ERROR( av_find_stream_info( h->lavf ) < 0, "could not find input stream info\n" ) + h->filename = strdup( psz_filename ); int i = 0; while( i < h->lavf->nb_streams && h->lavf->streams[i]->codec->codec_type != CODEC_TYPE_VIDEO ) @@ -211,8 +217,24 @@ static int close_file( hnd_t handle ) lavf_hnd_t *h = handle; avcodec_close( h->lavf->streams[h->stream_id]->codec ); av_close_input_file( h->lavf ); + free( h->filename ); free( h ); return 0; } +#if HAVE_AUDIO +static hnd_t open_audio( hnd_t handle, int track ) +{ + lavf_hnd_t *h = handle; + if ( !x264_is_regular_file_path( h->filename ) ) + { + x264_cli_log( "lavf", X264_LOG_WARNING, "reading audio from non-regular files is not implemented yet.\n" ); + return 0; + } + return audio_open_from_file( NULL, h->filename, track ); +} + +const cli_input_t lavf_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file, open_audio }; +#else const cli_input_t lavf_input = { open_file, picture_alloc, read_frame, release_frame, picture_clean, close_file }; +#endif diff --git a/output/flv.c b/output/flv.c index 9831a5b..084ed69 100644 --- a/output/flv.c +++ b/output/flv.c @@ -27,6 +27,20 @@ do {\ return -1;\ } while( 0 ) +#if HAVE_AUDIO +typedef struct +{ + audio_info_t *info; + hnd_t encoder; + int header; + int codecid; + int stereo; + int64_t lastdts; + int64_t step_num; + int64_t step_den; +} flv_audio_hnd_t; +#endif + typedef struct { flv_buffer *c; @@ -52,22 +66,116 @@ typedef struct int b_vfr_input; unsigned start; + +#if HAVE_AUDIO + flv_audio_hnd_t *a_flv; +#endif } flv_hnd_t; -static int write_header( flv_buffer *c ) +#if HAVE_AUDIO +static int audio_init( hnd_t handle, hnd_t filters, char *audio_enc, char *audio_parameters ) { - x264_put_tag( c, "FLV" ); // Signature - x264_put_byte( c, 1 ); // Version - x264_put_byte( c, 1 ); // Video Only - x264_put_be32( c, 9 ); // DataOffset - x264_put_be32( c, 0 ); // PreviousTagSize0 + if( !strcmp( audio_enc, "none" ) || !filters ) + return 0; + + // TODO: support adpcm_swf, pcm and aac + const audio_encoder_t *encoder = select_audio_encoder( audio_enc, (char*[]){ "mp3", "raw", NULL } ); + FAIL_IF_ERR( !encoder, "flv", "unable to select audio encoder\n" ); + + hnd_t enc; + FAIL_IF_ERR( !(enc = audio_encoder_open( encoder, filters, audio_parameters )), "flv", "error opening audio encoder" ); + flv_hnd_t *p_flv = handle; + flv_audio_hnd_t *a_flv = p_flv->a_flv = calloc( 1, sizeof( flv_audio_hnd_t ) ); + a_flv->lastdts = INT64_MIN; + audio_info_t *info = a_flv->info = audio_encoder_info( enc ); + + int header = 0; + if ( !strcmp( info->codec_name, "raw" ) ) + a_flv->codecid = FLV_CODECID_RAW; + else if( !strcmp( info->codec_name, "mp3" ) ) + a_flv->codecid = FLV_CODECID_MP3; + + header |= a_flv->codecid; + a_flv->stereo = info->channels == 2; + + switch( info->samplerate ) + { + case 5512: + case 8000: + header |= FLV_SAMPLERATE_SPECIAL; + break; + case 11025: + header |= FLV_SAMPLERATE_11025HZ; + break; + case 22050: + header |= FLV_SAMPLERATE_22050HZ; + break; + case 44100: + header |= FLV_SAMPLERATE_44100HZ; + break; + default: + x264_cli_log( "flv", X264_LOG_ERROR, "unsupported %dhz sample rate\n", info->samplerate ); + goto error; + } + + switch( info->chansize ) + { + case 1: + header |= FLV_SAMPLESSIZE_8BIT; + break; + case 2: + header |= FLV_SAMPLESSIZE_16BIT; + break; + default: + x264_cli_log( "flv", X264_LOG_ERROR, "%d-bit audio not supported\n", (int) info->chansize * 8 ); + goto error; + } + + switch( info->channels ) + { + case 1: + header |= FLV_MONO; + break; + case 2: + header |= FLV_STEREO; + break; + default: + x264_cli_log( "flv", X264_LOG_ERROR, "%d-channel audio not supported\n", info->channels ); + goto error; + } + + a_flv->header = header; + a_flv->step_num = a_flv->info->framelen * 1000; + a_flv->step_den = a_flv->info->samplerate; + + a_flv->encoder = enc; + + return 1; + + error: + audio_encoder_close( enc ); + free( p_flv->a_flv ); + p_flv->a_flv = NULL; + + return -1; +} +#endif + +static int write_header( flv_buffer *c, int audio ) +{ + x264_put_tag( c, "FLV" ); // Signature + x264_put_byte( c, 1 ); // Version + x264_put_byte( c, 1 | (audio ? 4 : 0) ); // Video + Audio (if requested) + x264_put_be32( c, 9 ); // DataOffset + x264_put_be32( c, 0 ); // PreviousTagSize 0 return flv_flush_data( c ); } -static int open_file( char *psz_filename, hnd_t *p_handle ) +static int open_file( char *psz_filename, hnd_t *p_handle, hnd_t audio_filters, char *audio_enc, char *audio_params ) { flv_hnd_t *p_flv = malloc( sizeof(*p_flv) ); + *p_handle = NULL; if( !p_flv ) return -1; @@ -77,10 +185,19 @@ static int open_file( char *psz_filename, hnd_t *p_handle ) if( !p_flv->c ) return -1; - CHECK( write_header( p_flv->c ) ); + int ret = 0; +#if HAVE_AUDIO + ret = audio_init( p_flv, audio_filters, audio_enc, audio_params ); + FAIL_IF_ERR( ret < 0, "flv", "unable to init audio output" ); +#endif + CHECK( write_header( p_flv->c, ret ) ); *p_handle = p_flv; +#if HAVE_AUDIO + return ret; +#else return 0; +#endif } static int set_param( hnd_t handle, x264_param_t *p_param ) @@ -132,6 +249,21 @@ static int set_param( hnd_t handle, x264_param_t *p_param ) p_flv->i_bitrate_pos = c->d_cur + c->d_total + 1; x264_put_amf_double( c, 0 ); // written at end of encoding +#if HAVE_AUDIO + if( p_flv->a_flv ) + { + flv_audio_hnd_t *a_flv = p_flv->a_flv; + x264_put_amf_string( c, "audiocodecid" ); + x264_put_amf_double( c, a_flv->codecid >> FLV_AUDIO_CODECID_OFFSET ); + x264_put_amf_string( c, "audiosamplesize" ); + x264_put_amf_double( c, a_flv->info->chansize ); + x264_put_amf_string( c, "audiosamplerate" ); + x264_put_amf_double( c, a_flv->info->samplerate ); + x264_put_amf_string( c, "stereo" ); + x264_put_amf_bool ( c, a_flv->stereo ); + } +#endif + x264_put_amf_string( c, "" ); x264_put_byte( c, AMF_END_OF_OBJECT ); @@ -152,6 +284,9 @@ static int set_param( hnd_t handle, x264_param_t *p_param ) static int write_headers( hnd_t handle, x264_nal_t *p_nal ) { flv_hnd_t *p_flv = handle; +#if HAVE_AUDIO + flv_audio_hnd_t *a_flv = p_flv->a_flv; +#endif flv_buffer *c = p_flv->c; int sps_size = p_nal[0].i_payload; @@ -202,11 +337,82 @@ static int write_headers( hnd_t handle, x264_nal_t *p_nal ) unsigned length = c->d_cur - p_flv->start; rewrite_amf_be24( c, length, p_flv->start - 10 ); x264_put_be32( c, length + 11 ); // Last tag size + +#if HAVE_AUDIO + if( a_flv && a_flv->codecid == FLV_CODECID_AAC ) + { + FAIL_IF_ERR( !a_flv->info->extradata, "flv", "audio codec is AAC but extradata is NULL\n" ); + x264_put_byte( c, FLV_TAG_TYPE_AUDIO ); + x264_put_be24( c, 2 + a_flv->info->extradata_size ); + x264_put_be24( c, 0 ); + x264_put_byte( c, 0 ); + x264_put_be24( c, 0 ); + + x264_put_byte( c, a_flv->header ); + x264_put_byte( c, 0 ); + flv_append_data( c, a_flv->info->extradata, a_flv->info->extradata_size ); + x264_put_be32( c, 11 + 2 + a_flv->info->extradata_size ); + } +#endif + CHECK( flv_flush_data( c ) ); return sei_size + sps_size + pps_size; } +#if HAVE_AUDIO +static int write_audio( flv_hnd_t *p_flv, int64_t video_dts, int finish ) +{ + flv_audio_hnd_t *a_flv = p_flv->a_flv; + flv_buffer *c = p_flv->c; + + assert( a_flv ); + + int aac = a_flv->codecid == FLV_CODECID_AAC; + if( a_flv->lastdts == INT64_MIN ) + { + if( video_dts > 0 ) + audio_encoder_skip_samples( a_flv->encoder, video_dts * a_flv->info->samplerate / 1000 ); + a_flv->lastdts = video_dts; // first frame (nonzero if --seek is used) + } + audio_packet_t *frame; + int frames = 0; + while( a_flv->lastdts <= video_dts || video_dts < 0 ) + { + if( finish ) + frame = audio_encoder_finish( a_flv->encoder ); + else if( !(frame = audio_encode_frame( a_flv->encoder )) ) + { + finish = 1; + continue; + } + + if( !frame ) break; + + a_flv->lastdts += a_flv->step_num / a_flv->step_den; + + x264_put_byte( c, FLV_TAG_TYPE_AUDIO ); + x264_put_be24( c, 1 + aac + frame->size ); + x264_put_be24( c, (int32_t) a_flv->lastdts ); + x264_put_byte( c, (int32_t) a_flv->lastdts >> 24 ); + x264_put_be24( c, 0 ); + + x264_put_byte( c, a_flv->header ); + if( aac ) + x264_put_byte( c, 1 ); + flv_append_data( c, frame->rawdata, frame->size ); + + x264_put_be32( c, 11 + 1 + aac + frame->size ); + + audio_free_frame( a_flv->encoder, frame ); + + CHECK( flv_flush_data( c ) ); + ++frames; + } + return frames; +} +#endif + 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; @@ -236,6 +442,10 @@ static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_ p_flv->i_prev_dts = p_picture->i_dts; p_flv->i_prev_pts = p_picture->i_pts; +#if HAVE_AUDIO + FAIL_IF_ERR( p_flv->a_flv && write_audio( p_flv, dts, 0 ) < 0, "flv", "error writing audio\n" ); +#endif + // A new frame - write packet header x264_put_byte( c, FLV_TAG_TYPE_VIDEO ); x264_put_be24( c, 0 ); // calculated later @@ -278,6 +488,14 @@ static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest flv_hnd_t *p_flv = handle; flv_buffer *c = p_flv->c; +#if HAVE_AUDIO + if( p_flv->a_flv ) + { + FAIL_IF_ERR( p_flv->a_flv && write_audio( p_flv, -1, 1 ) < 0, "flv", "error flushing audio\n" ); + audio_encoder_close( p_flv->a_flv->encoder ); + } +#endif + CHECK( flv_flush_data( c ) ); double total_duration = (double)(2 * largest_pts - second_largest_pts) * p_flv->i_timebase_num / p_flv->i_timebase_den; @@ -299,6 +517,11 @@ static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest } fclose( c->fp ); + +#if HAVE_AUDIO + if( p_flv->a_flv ) + free( p_flv->a_flv ); +#endif free( p_flv ); free( c ); diff --git a/output/flv_bytestream.c b/output/flv_bytestream.c index e02476c..2176786 100644 --- a/output/flv_bytestream.c +++ b/output/flv_bytestream.c @@ -78,6 +78,12 @@ void x264_put_amf_double( flv_buffer *c, double d ) x264_put_be64( c, dbl2int( d ) ); } +void x264_put_amf_bool( flv_buffer *c, int i ) +{ + x264_put_byte( c, AMF_DATA_TYPE_BOOL ); + x264_put_byte( c, !!i ); +} + /* flv writing functions */ flv_buffer *flv_create_writer( const char *filename ) diff --git a/output/flv_bytestream.h b/output/flv_bytestream.h index 00f37fe..db06b17 100644 --- a/output/flv_bytestream.h +++ b/output/flv_bytestream.h @@ -74,8 +74,11 @@ enum enum { - FLV_CODECID_MP3 = 2 << FLV_AUDIO_CODECID_OFFSET, - FLV_CODECID_AAC = 10<< FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_RAW = 0 << FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_ADPCM = 1 << FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_MP3 = 2 << FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_PCM = 3 << FLV_AUDIO_CODECID_OFFSET, + FLV_CODECID_AAC = 10 << FLV_AUDIO_CODECID_OFFSET, }; enum @@ -131,5 +134,6 @@ void x264_put_be24( flv_buffer *c, uint32_t val ); void x264_put_tag( flv_buffer *c, const char *tag ); void x264_put_amf_string( flv_buffer *c, const char *str ); void x264_put_amf_double( flv_buffer *c, double d ); +void x264_put_amf_bool( flv_buffer *c, int b ); #endif diff --git a/output/matroska.c b/output/matroska.c index a1219d0..d077162 100644 --- a/output/matroska.c +++ b/output/matroska.c @@ -35,10 +35,13 @@ typedef struct } mkv_hnd_t; -static int open_file( char *psz_filename, hnd_t *p_handle ) +static int open_file( char *psz_filename, hnd_t *p_handle, hnd_t audio_filters, char *audio_enc, char *audio_params ) { mkv_hnd_t *p_mkv; + FAIL_IF_ERR( audio_enc && ( strcmp( audio_enc, "none" ) && strcmp( audio_enc, "auto" ) ), "matroska", + "audio is not yet supported on this muxer\n" ); + *p_handle = NULL; p_mkv = malloc( sizeof(*p_mkv) ); diff --git a/output/mp4.c b/output/mp4.c index f2ff5be..b97adcd 100644 --- a/output/mp4.c +++ b/output/mp4.c @@ -155,10 +155,14 @@ static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest return 0; } -static int open_file( char *psz_filename, hnd_t *p_handle ) +static int open_file( char *psz_filename, hnd_t *p_handle, hnd_t audio_filters, char *audio_enc, char *audio_params ) { mp4_hnd_t *p_mp4; + + FAIL_IF_ERR( audio_enc && ( strcmp( audio_enc, "none" ) && strcmp( audio_enc, "auto" ) ), "mp4", + "audio is not yet supported on this muxer\n" ); + *p_handle = NULL; FILE *fh = fopen( psz_filename, "w" ); if( !fh ) diff --git a/output/output.h b/output/output.h index 094fefc..64c53fd 100644 --- a/output/output.h +++ b/output/output.h @@ -25,10 +25,13 @@ #define X264_OUTPUT_H #include "x264cli.h" +#if HAVE_AUDIO +#include "audio/encoders.h" +#endif typedef struct { - int (*open_file)( char *psz_filename, hnd_t *p_handle ); + int (*open_file)( char *psz_filename, hnd_t *p_handle, hnd_t audio_filters, char *audio_encoder, char *audio_parameters ); int (*set_param)( hnd_t handle, x264_param_t *p_param ); int (*write_headers)( hnd_t handle, x264_nal_t *p_nal ); int (*write_frame)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture ); diff --git a/output/raw.c b/output/raw.c index fc418fb..d7f627a 100644 --- a/output/raw.c +++ b/output/raw.c @@ -23,8 +23,11 @@ #include "output.h" -static int open_file( char *psz_filename, hnd_t *p_handle ) +static int open_file( char *psz_filename, hnd_t *p_handle, hnd_t audio_filters, char *audio_enc, char *audio_params ) { + FAIL_IF_ERR( audio_enc && ( strcmp( audio_enc, "none" ) && strcmp( audio_enc, "auto" ) ), "raw", + "audio is not supported on this muxer\n" ); + if( !strcmp( psz_filename, "-" ) ) *p_handle = stdout; else if( !(*p_handle = fopen( psz_filename, "w+b" )) ) diff --git a/x264.c b/x264.c index 2f4263e..77c2730 100644 --- a/x264.c +++ b/x264.c @@ -32,6 +32,9 @@ #include "common/common.h" #include "x264cli.h" +#if HAVE_AUDIO +#include "audio/audio.h" +#endif #include "input/input.h" #include "output/output.h" #include "filters/filters.h" @@ -64,6 +67,10 @@ typedef struct { int i_seek; hnd_t hin; hnd_t hout; +#if HAVE_AUDIO + hnd_t haud; + hnd_t haenc; +#endif FILE *qpfile; FILE *tcfile_out; double timebase_convert_multiplier; @@ -652,6 +659,26 @@ static void Help( x264_param_t *defaults, int longhelp ) H2( " --pic-struct Force pic_struct in Picture Timing SEI\n" ); H0( "\n" ); + H0( "Audio:\n" ); +#if HAVE_AUDIO + H0( "Audio is automatically opened from the input file if supported by the demuxer.\n" ); + H0( " --audiofile Uses audio from the specified file\n" ); + H0( " --acodec Specifies the audio codec [auto]."); + H1( " Supported codecs:\n" ); +#define CODEC( test, name ) if( test ) \ + H1( " - " name "\n" ) + CODEC( 1 , "auto" ); + CODEC( 1 , "none" ); + CODEC( 1 , "raw" ); + CODEC( HAVE_LAME, "mp3" ); +#undef CODEC + H0( "\n" ); + H0( " --abitrate Enable bitrate mode and specifies bitrate\n" ); + H0( " --aquality Specifies audio quality [6]\n" ); +#else /* HAVE_AUDIO */ + H0( "Audio support was not compiled in.\n" ); +#endif + H0( "\n" ); H0( "Input/Output:\n" ); H0( "\n" ); H0( " -o, --output Specify output file\n" ); @@ -730,7 +757,11 @@ enum { OPT_LOG_LEVEL, OPT_VIDEO_FILTER, OPT_INPUT_RES, - OPT_INPUT_CSP + OPT_INPUT_CSP, + OPT_AUDIOFILE, + OPT_AUDIOCODEC, + OPT_AUDIOBITRATE, + OPT_AUDIOQUALITY } OptionsOPT; static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw"; @@ -882,6 +913,10 @@ static struct option long_options[] = { "video-filter", required_argument, NULL, OPT_VIDEO_FILTER }, { "input-res", required_argument, NULL, OPT_INPUT_RES }, { "input-csp", required_argument, NULL, OPT_INPUT_CSP }, + { "audiofile", required_argument, NULL, OPT_AUDIOFILE }, + { "acodec", required_argument, NULL, OPT_AUDIOCODEC }, + { "abitrate", required_argument, NULL, OPT_AUDIOBITRATE }, + { "aquality", required_argument, NULL, OPT_AUDIOQUALITY }, {0, 0, 0, 0} }; @@ -1094,6 +1129,14 @@ static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) char *preset = NULL; char *tune = NULL; +#if HAVE_AUDIO + char *audio_enc = "auto"; + char *audio_filename = NULL; + int audio_bitrate = -1; + float audio_quality = 6; + int audio_enable = 1; +#endif + x264_param_default( &defaults ); cli_log_level = defaults.i_log_level; @@ -1255,6 +1298,35 @@ static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) case OPT_INPUT_CSP: input_opt.colorspace = optarg; break; + case OPT_AUDIOCODEC: +#if HAVE_AUDIO + audio_enc = optarg; + if( !strcmp( audio_enc, "none" ) ) + audio_enable = 0; + else FAIL_IF_ERROR( !strcmp( audio_enc, "auto" ) || !encoder_by_name( audio_enc ), + "audio encoder %s not supported or not compiled in\n" ) + break; +#else + if( !strcmp( optarg, "none" ) || !strcmp( optarg, "auto" ) ) + break; +#endif + case OPT_AUDIOFILE: +#if HAVE_AUDIO + audio_filename = optarg; + break; +#endif + case OPT_AUDIOBITRATE: +#if HAVE_AUDIO + audio_bitrate = atoi( optarg ); + FAIL_IF_ERROR( audio_bitrate <= 0, "bitrate must be > 0.\n" ); + break; +#endif + case OPT_AUDIOQUALITY: + FAIL_IF_ERROR( !HAVE_AUDIO, "audio support was not compiled in.\n" ); +#if HAVE_AUDIO + audio_quality = (float) atof( optarg ); + break; +#endif default: generic_option: { @@ -1299,7 +1371,6 @@ generic_option: if( select_output( muxer, output_filename, param ) ) return -1; - FAIL_IF_ERROR( output.open_file( output_filename, &opt->hout ), "could not open output file `%s'\n", output_filename ) input_filename = argv[optind++]; video_info_t info = {0}; @@ -1321,12 +1392,49 @@ generic_option: FAIL_IF_ERROR( !opt->hin && input.open_file( input_filename, &opt->hin, &info, &input_opt ), "could not open input file `%s'\n", input_filename ) +#if HAVE_AUDIO + if( audio_enable ) + { + if ( audio_filename ) + opt->haud = audio_open_from_file( NULL, audio_filename, TRACK_ANY ); + else if ( input.open_audio ) + opt->haud = input.open_audio( opt->hin, TRACK_ANY ); + else + { + x264_cli_log( "x264", X264_LOG_WARNING, "the used input does not support audio and --audiofile was not given, disabling audio.\n" ); + audio_enable = 0; + } + + if ( audio_filename && !opt->haud ) + return -1; + } +#endif + x264_reduce_fraction( &info.sar_width, &info.sar_height ); x264_reduce_fraction( &info.fps_num, &info.fps_den ); x264_cli_log( demuxername, X264_LOG_INFO, "%dx%d%c %d:%d @ %d/%d fps (%cfr)\n", info.width, info.height, info.interlaced ? 'i' : 'p', info.sar_width, info.sar_height, info.fps_num, info.fps_den, info.vfr ? 'v' : 'c' ); +#if HAVE_AUDIO + char arg[30] = { 0 }; + if( audio_enable ) + { + if ( audio_bitrate > 0 ) + snprintf( arg, 30, "bitrate=%d", audio_bitrate ); + else + snprintf( arg, 30, "vbr=%f", audio_quality ); + } +#endif + + FAIL_IF_ERROR( +#if HAVE_AUDIO + output.open_file( output_filename, &opt->hout, opt->haud, audio_enc, arg ) +#else + output.open_file( output_filename, &opt->hout, NULL, NULL, NULL ) +#endif + < 0, "could not open output file `%s'\n", output_filename ) + if( tcfile_name ) { FAIL_IF_ERROR( b_user_fps, "--fps + --tcfile-in is incompatible.\n" ) @@ -1725,6 +1833,12 @@ static int Encode( x264_param_t *param, cli_opt_t *opt ) opt->tcfile_out = NULL; } +#if HAVE_AUDIO + if( opt->haenc ) + audio_encoder_close( opt->haenc ); + if( opt->haud ) + af_close( opt->haud ); +#endif filter.free( opt->hin ); output.close_file( opt->hout, largest_pts, second_largest_pts );