summaryrefslogtreecommitdiffstats
path: root/libhb/encavcodec.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhb/encavcodec.c')
-rw-r--r--libhb/encavcodec.c339
1 files changed, 297 insertions, 42 deletions
diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c
index 07fc748a8..fd95f64b1 100644
--- a/libhb/encavcodec.c
+++ b/libhb/encavcodec.c
@@ -7,11 +7,37 @@
#include "hb.h"
#include "hbffmpeg.h"
+/*
+ * The frame info struct remembers information about each frame across calls
+ * to avcodec_encode_video. Since frames are uniquely identified by their
+ * frame number, we use this as an index.
+ *
+ * The size of the array is chosen so that two frames can't use the same
+ * slot during the encoder's max frame delay (set by the standard as 16
+ * frames) and so that, up to some minimum frame rate, frames are guaranteed
+ * to map to * different slots.
+ */
+#define FRAME_INFO_SIZE 32
+#define FRAME_INFO_MASK (FRAME_INFO_SIZE - 1)
+
struct hb_work_private_s
{
hb_job_t * job;
AVCodecContext * context;
FILE * file;
+
+ int frameno_in;
+ int frameno_out;
+ hb_buffer_t * delay_head;
+ hb_buffer_t * delay_tail;
+
+ int64_t dts_delay;
+
+ struct {
+ int64_t start;
+ int64_t stop;
+ int64_t renderOffset;
+ } frame_info[FRAME_INFO_SIZE];
};
int encavcodecInit( hb_work_object_t *, hb_job_t * );
@@ -44,39 +70,11 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
hb_log( "hb_work_encavcodec_init: avcodec_find_encoder "
"failed" );
}
- context = avcodec_alloc_context();
- if( job->vquality < 0.0 )
- {
- /* Rate control */
- context->bit_rate = 1000 * job->vbitrate;
- context->bit_rate_tolerance = 10 * context->bit_rate;
- }
- else
- {
- /* Constant quantizer */
- // These settings produce better image quality than
- // what was previously used
- context->flags |= CODEC_FLAG_QSCALE;
- if (job->vquality < 1.0)
- {
- float vquality;
- vquality = 31 - job->vquality * 31;
- // A value of 0 has undefined behavior
- // and ffmpeg qp has integral increments
- if (vquality < 1.0)
- vquality = 1.0;
- context->global_quality = FF_QP2LAMBDA * vquality + 0.5;
- }
- else
- {
- context->global_quality = FF_QP2LAMBDA * job->vquality + 0.5;
- }
- context->mb_decision = 1;
- hb_log( "encavcodec: encoding at constant quantizer %d",
- context->global_quality );
- }
- context->width = job->width;
- context->height = job->height;
+ context = avcodec_alloc_context3( codec );
+
+ // Set things in context that we will allow the user to
+ // override with advanced settings.
+ context->thread_count = ( hb_get_cpu_count() * 3 / 2 );
if( job->pass == 2 )
{
@@ -89,6 +87,13 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
rate_num = job->vrate_base;
rate_den = job->vrate;
}
+
+ // If the rate_den is 27000000, there's a good chance this is
+ // a standard rate that we have in our hb_video_rates table.
+ // Because of rounding errors and approximations made while
+ // measuring framerate, the actual value may not be exact. So
+ // we look for rates that are "close" and make an adjustment
+ // to rate_num.
if (rate_den == 27000000)
{
int ii;
@@ -114,6 +119,92 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
}
context->time_base = (AVRational) { rate_num, rate_den };
context->gop_size = 10 * (int)( (double)job->vrate / (double)job->vrate_base + 0.5 );
+
+ /*
+ This section passes the string advanced_opts to avutil for parsing
+ into an AVCodecContext.
+
+ The string is set up like this:
+ option1=value1:option2=value2
+
+ So, you have to iterate through based on the colons, and then put
+ the left side of the equals sign in "name" and the right side into
+ "value." Then you hand those strings off to avutil for interpretation.
+ */
+ if( job->advanced_opts != NULL && *job->advanced_opts != '\0' )
+ {
+ char *opts, *opts_start;
+
+ opts = opts_start = strdup(job->advanced_opts);
+
+ if( opts_start )
+ {
+ while( *opts )
+ {
+ char *name = opts;
+ char *value;
+ int ret;
+
+ opts += strcspn( opts, ":" );
+ if( *opts )
+ {
+ *opts = 0;
+ opts++;
+ }
+
+ value = strchr( name, '=' );
+ if( value )
+ {
+ *value = 0;
+ value++;
+ }
+
+ /* Here's where the strings are passed to avutil for parsing. */
+ ret = av_set_string3( context, name, value, 1, NULL );
+
+ /* Let avutil sanity check the options for us*/
+ if( ret == AVERROR(ENOENT) )
+ hb_log( "avcodec options: Unknown option %s", name );
+ if( ret == AVERROR(EINVAL) )
+ hb_log( "avcodec options: Bad argument %s=%s", name, value ? value : "(null)" );
+ }
+ }
+ free(opts_start);
+ }
+
+ // Now set the things in context that we don't want to allow
+ // the user to override.
+ if( job->vquality < 0.0 )
+ {
+ /* Rate control */
+ context->bit_rate = 1000 * job->vbitrate;
+ context->bit_rate_tolerance = 10 * context->bit_rate;
+ }
+ else
+ {
+ /* Constant quantizer */
+ // These settings produce better image quality than
+ // what was previously used
+ context->flags |= CODEC_FLAG_QSCALE;
+ if (job->vquality < 1.0)
+ {
+ float vquality;
+ vquality = 31 - job->vquality * 31;
+ // A value of 0 has undefined behavior
+ // and ffmpeg qp has integral increments
+ if (vquality < 1.0)
+ vquality = 1.0;
+ context->global_quality = FF_QP2LAMBDA * vquality + 0.5;
+ }
+ else
+ {
+ context->global_quality = FF_QP2LAMBDA * job->vquality + 0.5;
+ }
+ hb_log( "encavcodec: encoding at constant quantizer %d",
+ context->global_quality );
+ }
+ context->width = job->width;
+ context->height = job->height;
context->pix_fmt = PIX_FMT_YUV420P;
if( job->anamorphic.mode )
@@ -170,17 +261,15 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
}
pv->context = context;
+ if ( context->has_b_frames )
+ {
+ job->areBframes = 1;
+ }
if( ( job->mux & HB_MUX_MP4 ) && job->pass != 1 )
{
-#if 0
- /* Hem hem */
- w->config->mpeg4.length = 15;
- memcpy( w->config->mpeg4.bytes, context->extradata + 15, 15 );
-#else
w->config->mpeg4.length = context->extradata_size;
memcpy( w->config->mpeg4.bytes, context->extradata,
context->extradata_size );
-#endif
}
return 0;
@@ -209,6 +298,109 @@ void encavcodecClose( hb_work_object_t * w )
w->private_data = NULL;
}
+/*
+ * see comments in definition of 'frame_info' in pv struct for description
+ * of what these routines are doing.
+ */
+static void save_frame_info( hb_work_private_t * pv, hb_buffer_t * in )
+{
+ int i = pv->frameno_in & FRAME_INFO_MASK;
+ pv->frame_info[i].start = in->start;
+ pv->frame_info[i].stop = in->stop;
+}
+
+static int64_t get_frame_start( hb_work_private_t * pv, int64_t frameno )
+{
+ int i = frameno & FRAME_INFO_MASK;
+ return pv->frame_info[i].start;
+}
+
+static int64_t get_frame_stop( hb_work_private_t * pv, int64_t frameno )
+{
+ int i = frameno & FRAME_INFO_MASK;
+ return pv->frame_info[i].stop;
+}
+
+static void compute_dts_offset( hb_work_private_t * pv, hb_buffer_t * buf )
+{
+ if ( pv->job->areBframes )
+ {
+ if ( ( pv->frameno_in - 1 ) == pv->job->areBframes )
+ {
+ pv->dts_delay = buf->start;
+ pv->job->config.h264.init_delay = pv->dts_delay;
+ }
+ }
+}
+
+// Generate DTS by rearranging PTS in this sequence:
+// pts0 - delay, pts1 - delay, pts2 - delay, pts1, pts2, pts3...
+//
+// Where pts0 - ptsN are in decoded monotonically increasing presentation
+// order and delay == pts1 (1 being the number of frames the decoder must
+// delay before it has suffecient information to decode). The number of
+// frames to delay is set by job->areBframes, so it is configurable.
+// This guarantees that DTS <= PTS for any frame.
+//
+// This is similar to how x264 generates DTS
+static hb_buffer_t * process_delay_list( hb_work_private_t * pv, hb_buffer_t * buf )
+{
+ if ( pv->job->areBframes )
+ {
+ // Has dts_delay been set yet?
+ if ( pv->frameno_in <= pv->job->areBframes )
+ {
+ // dts_delay not yet set. queue up buffers till it is set.
+ if ( pv->delay_tail == NULL )
+ {
+ pv->delay_head = pv->delay_tail = buf;
+ }
+ else
+ {
+ pv->delay_tail->next = buf;
+ pv->delay_tail = buf;
+ }
+ return NULL;
+ }
+
+ // We have dts_delay. Apply it to any queued buffers renderOffset
+ // and return all queued buffers.
+ if ( pv->delay_tail == NULL && buf != NULL )
+ {
+ pv->frameno_out++;
+ // Use the cached frame info to get the start time of Nth frame
+ // Note that start Nth frame != start time this buffer since the
+ // output buffers have rearranged start times.
+ int64_t start = get_frame_start( pv, pv->frameno_out );
+ buf->renderOffset = start - pv->dts_delay;
+ return buf;
+ }
+ else
+ {
+ pv->delay_tail->next = buf;
+ buf = pv->delay_head;
+ while ( buf )
+ {
+ pv->frameno_out++;
+ // Use the cached frame info to get the start time of Nth frame
+ // Note that start Nth frame != start time this buffer since the
+ // output buffers have rearranged start times.
+ int64_t start = get_frame_start( pv, pv->frameno_out );
+ buf->renderOffset = start - pv->dts_delay;
+ }
+ buf = pv->delay_head;
+ pv->delay_head = pv->delay_tail = NULL;
+ return buf;
+ }
+ }
+ else if ( buf )
+ {
+ buf->renderOffset = buf->start - pv->dts_delay;
+ return buf;
+ }
+ return NULL;
+}
+
/***********************************************************************
* Work
***********************************************************************
@@ -241,15 +433,78 @@ int encavcodecWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
// doesn't do the trick. It must be set in the AVFrame.
frame->quality = pv->context->global_quality;
+ // Bizarro ffmpeg appears to require the input AVFrame.pts to be
+ // set to a frame number. Setting it to an actual pts causes
+ // jerky video.
+ // frame->pts = in->start;
+ frame->pts = ++pv->frameno_in;
+
+ // Remember info about this frame that we need to pass across
+ // the avcodec_encode_video call (since it reorders frames).
+ save_frame_info( pv, in );
+ compute_dts_offset( pv, in );
+
if ( pv->context->codec )
{
/* Should be way too large */
buf = hb_video_buffer_init( job->width, job->height );
buf->size = avcodec_encode_video( pv->context, buf->data, buf->alloc,
frame );
- buf->start = in->start;
- buf->stop = in->stop;
- buf->frametype = pv->context->coded_frame->key_frame ? HB_FRAME_KEY : HB_FRAME_REF;
+ if ( buf->size <= 0 )
+ {
+ hb_buffer_close( &buf );
+ }
+ else
+ {
+ int64_t frameno = pv->context->coded_frame->pts;
+ buf->start = get_frame_start( pv, frameno );
+ buf->stop = get_frame_stop( pv, frameno );
+ switch ( pv->context->coded_frame->pict_type )
+ {
+ case FF_P_TYPE:
+ {
+ buf->frametype = HB_FRAME_P;
+ } break;
+
+ case FF_B_TYPE:
+ {
+ buf->frametype = HB_FRAME_B;
+ } break;
+
+ case FF_S_TYPE:
+ {
+ buf->frametype = HB_FRAME_P;
+ } break;
+
+ case FF_SP_TYPE:
+ {
+ buf->frametype = HB_FRAME_P;
+ } break;
+
+ case FF_BI_TYPE:
+ case FF_SI_TYPE:
+ case FF_I_TYPE:
+ {
+ if ( pv->context->coded_frame->key_frame )
+ {
+ buf->frametype = HB_FRAME_IDR;
+ }
+ else
+ {
+ buf->frametype = HB_FRAME_I;
+ }
+ } break;
+
+ default:
+ {
+ if ( pv->context->coded_frame->key_frame )
+ buf->frametype = HB_FRAME_KEY;
+ else
+ buf->frametype = HB_FRAME_REF;
+ } break;
+ }
+ buf = process_delay_list( pv, buf );
+ }
}
else
{