+static int decavcodecvInfo( hb_work_object_t *w, hb_work_info_t *info )
+{
+ hb_work_private_t *pv = w->private_data;
+
+ memset( info, 0, sizeof(*info) );
+
+ if ( pv && pv->context )
+ {
+ AVCodecContext *context = pv->context;
+ info->bitrate = context->bit_rate;
+ info->width = context->width;
+ info->height = context->height;
+
+ /* ffmpeg gives the frame rate in frames per second while HB wants
+ * it in units of the 27MHz MPEG clock. */
+ info->rate = 27000000;
+ info->rate_base = (int64_t)context->time_base.num * 27000000LL /
+ context->time_base.den;
+ if ( context->ticks_per_frame > 1 )
+ {
+ // for ffmpeg 0.5 & later, the H.264 & MPEG-2 time base is
+ // field rate rather than frame rate so convert back to frames.
+ info->rate_base *= context->ticks_per_frame;
+ }
+
+ info->pixel_aspect_width = context->sample_aspect_ratio.num;
+ info->pixel_aspect_height = context->sample_aspect_ratio.den;
+
+ /* Sometimes there's no pixel aspect set in the source ffmpeg context
+ * which appears to come from the video stream. In that case,
+ * try the pixel aspect in AVStream (which appears to come from
+ * the container). Else assume a 1:1 PAR. */
+ if ( info->pixel_aspect_width == 0 ||
+ info->pixel_aspect_height == 0 )
+ {
+ // There will not be an ffmpeg stream if the file is TS
+ AVStream *st = hb_ffmpeg_avstream( w->codec_param );
+ info->pixel_aspect_width = st && st->sample_aspect_ratio.num ?
+ st->sample_aspect_ratio.num : 1;
+ info->pixel_aspect_height = st && st->sample_aspect_ratio.den ?
+ st->sample_aspect_ratio.den : 1;
+ }
+ /* ffmpeg returns the Pixel Aspect Ratio (PAR). Handbrake wants the
+ * Display Aspect Ratio so we convert by scaling by the Storage
+ * Aspect Ratio (w/h). We do the calc in floating point to get the
+ * rounding right. */
+ info->aspect = (double)info->pixel_aspect_width *
+ (double)context->width /
+ (double)info->pixel_aspect_height /
+ (double)context->height;
+
+ info->profile = context->profile;
+ info->level = context->level;
+ info->name = context->codec->name;
+ return 1;
+ }
+ return 0;
+}
+
+static int decavcodecvBSInfo( hb_work_object_t *w, const hb_buffer_t *buf,
+ hb_work_info_t *info )
+{
+ return 0;
+}
+
+hb_work_object_t hb_decavcodecv =
+{
+ WORK_DECAVCODECV,
+ "Video decoder (libavcodec)",
+ decavcodecvInit,
+ decavcodecvWork,
+ decavcodecClose,
+ decavcodecvInfo,
+ decavcodecvBSInfo
+};
+
+
+// This is a special decoder for ffmpeg streams. The ffmpeg stream reader
+// includes a parser and passes information from the parser to the decoder
+// via a codec context kept in the AVStream of the reader's AVFormatContext.
+// We *have* to use that codec context to decode the stream or we'll get
+// garbage. ffmpeg_title_scan put a cookie that can be used to get to that
+// codec context in our codec_param.
+
+// this routine gets the appropriate context pointer from the ffmpeg
+// stream reader. it can't be called until we get the first buffer because
+// we can't guarantee that reader will be called before the our init
+// routine and if our init is called first we'll get a pointer to the
+// old scan stream (which has already been closed).
+static void init_ffmpeg_context( hb_work_object_t *w )
+{
+ hb_work_private_t *pv = w->private_data;
+ pv->context = hb_ffmpeg_context( w->codec_param );
+
+ // during scan the decoder gets closed & reopened which will
+ // close the codec so reopen it if it's not there
+ if ( ! pv->context->codec )
+ {
+ AVCodec *codec = avcodec_find_decoder( pv->context->codec_id );
+ hb_avcodec_open( pv->context, codec );
+ }
+ // set up our best guess at the frame duration.
+ // the frame rate in the codec is usually bogus but it's sometimes
+ // ok in the stream.
+ AVStream *st = hb_ffmpeg_avstream( w->codec_param );
+
+ if ( st->nb_frames && st->duration )
+ {
+ // compute the average frame duration from the total number
+ // of frames & the total duration.
+ pv->duration = ( (double)st->duration * (double)st->time_base.num ) /
+ ( (double)st->nb_frames * (double)st->time_base.den );
+ }
+ else
+ {
+ // XXX We don't have a frame count or duration so try to use the
+ // far less reliable time base info in the stream.
+ // Because the time bases are so screwed up, we only take values
+ // in the range 8fps - 64fps.
+ AVRational tb;
+ if ( st->avg_frame_rate.den * 64 > st->avg_frame_rate.num &&
+ st->avg_frame_rate.num > st->avg_frame_rate.den * 8 )
+ {
+ tb.num = st->avg_frame_rate.den;
+ tb.den = st->avg_frame_rate.num;
+ }
+ else if ( st->time_base.num * 64 > st->time_base.den &&
+ st->time_base.den > st->time_base.num * 8 )
+ {
+ tb = st->time_base;
+ }
+ else if ( st->r_frame_rate.den * 64 > st->r_frame_rate.num &&
+ st->r_frame_rate.num > st->r_frame_rate.den * 8 )
+ {
+ tb.num = st->r_frame_rate.den;
+ tb.den = st->r_frame_rate.num;
+ }
+ else
+ {
+ tb.num = 1001; /*XXX*/
+ tb.den = 24000; /*XXX*/
+ }
+ pv->duration = (double)tb.num / (double)tb.den;
+ }
+ pv->duration *= 90000.;
+
+ // we have to wrap ffmpeg's get_buffer to be able to set the pts (?!)
+ pv->context->opaque = pv;
+ pv->context->get_buffer = get_frame_buf;
+ pv->context->reget_buffer = reget_frame_buf;
+
+ // avi, mkv and possibly mp4 containers can contain the M$ VFW packed
+ // b-frames abortion that messes up frame ordering and timestamps.
+ // XXX ffmpeg knows which streams are broken but doesn't expose the
+ // info externally. We should patch ffmpeg to add a flag to the
+ // codec context for this but until then we mark all ffmpeg streams
+ // as suspicious.
+ pv->brokenByMicrosoft = 1;
+}
+
+static void prepare_ffmpeg_buffer( hb_buffer_t * in )
+{
+ // ffmpeg requires an extra 8 bytes of zero at the end of the buffer and
+ // will seg fault in odd, data dependent ways if it's not there. (my guess
+ // is this is a case of a local performance optimization creating a global
+ // performance degradation since all the time wasted by extraneous data
+ // copies & memory zeroing has to be huge compared to the minor reduction
+ // in inner-loop instructions this affords - modern cpus bottleneck on
+ // memory bandwidth not instruction bandwidth).
+ if ( in->size + FF_INPUT_BUFFER_PADDING_SIZE > in->alloc )
+ {
+ // have to realloc to add the padding
+ hb_buffer_realloc( in, in->size + FF_INPUT_BUFFER_PADDING_SIZE );
+ }
+ memset( in->data + in->size, 0, FF_INPUT_BUFFER_PADDING_SIZE );
+}
+
+static int decavcodecviInit( hb_work_object_t * w, hb_job_t * job )
+{
+
+ hb_work_private_t *pv = calloc( 1, sizeof( hb_work_private_t ) );
+ w->private_data = pv;
+ pv->job = job;
+ pv->list = hb_list_init();
+ pv->pts_next = -1;
+ pv->pts = -1;
+
+ if ( w->audio != NULL &&
+ hb_need_downmix( w->audio->config.in.channel_layout,
+ w->audio->config.out.mixdown) )
+ {
+ pv->downmix = hb_downmix_init(w->audio->config.in.channel_layout,
+ w->audio->config.out.mixdown);
+ hb_downmix_set_chan_map( pv->downmix, &hb_smpte_chan_map, &hb_qt_chan_map );
+ }
+
+ return 0;
+}
+
+static int decavcodecviWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
+ hb_buffer_t ** buf_out )
+{
+ hb_work_private_t *pv = w->private_data;
+ hb_buffer_t *in = *buf_in;
+ *buf_in = NULL;
+
+ /* if we got an empty buffer signaling end-of-stream send it downstream */
+ if ( in->size == 0 )
+ {
+ /* flush any frames left in the decoder */
+ while ( pv->context && decodeFrame( pv, NULL, 0, in->sequence ) )
+ {
+ }
+ flushDelayQueue( pv );
+ hb_list_add( pv->list, in );
+ *buf_out = link_buf_list( pv );
+ return HB_WORK_DONE;
+ }
+
+ if ( ! pv->context )
+ {
+ init_ffmpeg_context( w );
+ }
+
+ int64_t pts = in->start;
+ if( pts >= 0 )
+ {
+ // use the first timestamp as our 'next expected' pts
+ if ( pv->pts_next < 0 )
+ {
+ pv->pts_next = pts;
+ }
+ pv->pts = pts;
+ }
+
+ if ( in->new_chap )
+ {
+ pv->new_chap = in->new_chap;
+ pv->chap_time = pts >= 0? pts : pv->pts_next;
+ }
+ prepare_ffmpeg_buffer( in );
+ decodeFrame( pv, in->data, in->size, in->sequence );
+ hb_buffer_close( &in );
+ *buf_out = link_buf_list( pv );
+ return HB_WORK_OK;
+}
+
+static int decavcodecviInfo( hb_work_object_t *w, hb_work_info_t *info )
+{
+ if ( decavcodecvInfo( w, info ) )
+ {
+ hb_work_private_t *pv = w->private_data;
+ if ( ! pv->context )
+ {
+ init_ffmpeg_context( w );
+ }
+ // we have the frame duration in units of the 90KHz pts clock but
+ // need it in units of the 27MHz MPEG clock. */
+ info->rate = 27000000;
+ info->rate_base = pv->duration * 300.;
+ return 1;
+ }
+ return 0;
+}
+
+static void decodeAudio( hb_audio_t * audio, hb_work_private_t *pv, uint8_t *data, int size )
+{
+ AVCodecContext *context = pv->context;
+ int pos = 0;
+ int loop_limit = 256;
+
+ while ( pos < size )
+ {
+ int16_t *buffer = pv->buffer;
+ if ( buffer == NULL )
+ {
+ pv->buffer = av_malloc( AVCODEC_MAX_AUDIO_FRAME_SIZE );
+ buffer = pv->buffer;
+ }
+
+ AVPacket avp;
+ av_init_packet( &avp );
+ avp.data = data + pos;
+ avp.size = size - pos;
+
+ int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ int nsamples;
+ int len = avcodec_decode_audio3( context, buffer, &out_size, &avp );
+ if ( len < 0 )
+ {
+ return;
+ }
+ if ( len == 0 )
+ {
+ if ( !(loop_limit--) )
+ return;
+ }
+ else
+ loop_limit = 256;
+
+ pos += len;
+ if( out_size > 0 )
+ {
+ // We require signed 16-bit ints for the output format. If
+ // we got something different convert it.
+ if ( context->sample_fmt != SAMPLE_FMT_S16 )
+ {
+ // Note: av_audio_convert seems to be a work-in-progress but
+ // looks like it will eventually handle general audio
+ // mixdowns which would allow us much more flexibility
+ // in handling multichannel audio in HB. If we were doing
+ // anything more complicated than a one-for-one format
+ // conversion we'd probably want to cache the converter
+ // context in the pv.
+ int isamp = av_get_bits_per_sample_format( context->sample_fmt ) / 8;
+ AVAudioConvert *ctx = av_audio_convert_alloc( SAMPLE_FMT_S16, 1,
+ context->sample_fmt, 1,
+ NULL, 0 );
+ // get output buffer size (in 2-byte samples) then malloc a buffer
+ nsamples = out_size / isamp;
+ buffer = av_malloc( nsamples * 2 );
+
+ // we're doing straight sample format conversion which behaves as if
+ // there were only one channel.
+ const void * const ibuf[6] = { pv->buffer };
+ void * const obuf[6] = { buffer };
+ const int istride[6] = { isamp };
+ const int ostride[6] = { 2 };
+
+ av_audio_convert( ctx, obuf, ostride, ibuf, istride, nsamples );
+ av_audio_convert_free( ctx );
+ }
+ else
+ {
+ nsamples = out_size / 2;
+ }
+
+ hb_buffer_t * buf;
+
+ if ( pv->downmix )
+ {
+ pv->downmix_buffer = realloc(pv->downmix_buffer, nsamples * sizeof(hb_sample_t));
+
+ int i;
+ for( i = 0; i < nsamples; ++i )
+ {
+ pv->downmix_buffer[i] = buffer[i];
+ }
+
+ int n_ch_samples = nsamples / context->channels;
+ int channels = HB_AMIXDOWN_GET_DISCRETE_CHANNEL_COUNT(audio->config.out.mixdown);
+
+ buf = hb_buffer_init( n_ch_samples * channels * sizeof(float) );
+ hb_sample_t *samples = (hb_sample_t *)buf->data;
+ hb_downmix(pv->downmix, samples, pv->downmix_buffer, n_ch_samples);
+ }
+ else
+ {
+ buf = hb_buffer_init( nsamples * sizeof(float) );
+ float *fl32 = (float *)buf->data;
+ int i;
+ for( i = 0; i < nsamples; ++i )
+ {
+ fl32[i] = buffer[i];
+ }
+ int n_ch_samples = nsamples / context->channels;
+ hb_layout_remap( &hb_smpte_chan_map, &hb_qt_chan_map,
+ audio->config.in.channel_layout,
+ fl32, n_ch_samples );
+ }
+
+ double pts = pv->pts_next;
+ buf->start = pts;
+ pts += nsamples * pv->duration;
+ buf->stop = pts;
+ pv->pts_next = pts;
+
+ hb_list_add( pv->list, buf );
+
+ // if we allocated a buffer for sample format conversion, free it
+ if ( buffer != pv->buffer )
+ {
+ av_free( buffer );
+ }
+ }
+ }
+}
+
+static int decavcodecaiWork( hb_work_object_t *w, hb_buffer_t **buf_in,
+ hb_buffer_t **buf_out )
+{
+ if ( (*buf_in)->size <= 0 )
+ {
+ /* EOF on input stream - send it downstream & say that we're done */
+ *buf_out = *buf_in;
+ *buf_in = NULL;
+ return HB_WORK_DONE;
+ }
+
+ hb_work_private_t *pv = w->private_data;
+
+ if ( (*buf_in)->start < -1 && pv->pts_next <= 0 )
+ {
+ // discard buffers that start before video time 0
+ *buf_out = NULL;
+ return HB_WORK_OK;
+ }
+
+ if ( ! pv->context )
+ {
+ init_ffmpeg_context( w );
+ // duration is a scaling factor to go from #bytes in the decoded
+ // frame to frame time (in 90KHz mpeg ticks). 'channels' converts
+ // total samples to per-channel samples. 'sample_rate' converts
+ // per-channel samples to seconds per sample and the 90000
+ // is mpeg ticks per second.
+ pv->duration = 90000. /
+ (double)( pv->context->sample_rate * pv->context->channels );
+ }
+ hb_buffer_t *in = *buf_in;
+
+ // if the packet has a timestamp use it if we don't have a timestamp yet
+ // or if there's been a timing discontinuity of more than 100ms.
+ if ( in->start >= 0 &&
+ ( pv->pts_next < 0 || ( in->start - pv->pts_next ) > 90*100 ) )
+ {
+ pv->pts_next = in->start;
+ }
+ prepare_ffmpeg_buffer( in );
+ decodeAudio( w->audio, pv, in->data, in->size );
+ *buf_out = link_buf_list( pv );
+
+ return HB_WORK_OK;
+}
+
+hb_work_object_t hb_decavcodecvi =
+{
+ WORK_DECAVCODECVI,
+ "Video decoder (ffmpeg streams)",
+ decavcodecviInit,
+ decavcodecviWork,
+ decavcodecClose,
+ decavcodecviInfo,
+ decavcodecvBSInfo
+};
+
+hb_work_object_t hb_decavcodecai =
+{
+ WORK_DECAVCODECAI,
+ "Audio decoder (ffmpeg streams)",
+ decavcodecviInit,
+ decavcodecaiWork,
+ decavcodecClose,
+ decavcodecInfo,
+ decavcodecBSInfo
+};