summaryrefslogtreecommitdiffstats
path: root/libhb/encx265.c
diff options
context:
space:
mode:
authorRodeo <[email protected]>2014-02-11 21:33:34 +0000
committerRodeo <[email protected]>2014-02-11 21:33:34 +0000
commit307c777504c7d3d2fc33db9bcaa3ef3d310e4d38 (patch)
tree63a669c4c7de42590ac6788ddcc3af0a2682d4e4 /libhb/encx265.c
parent25b0757e8fc367d3b56422e41a096e59846b25bf (diff)
encx265: miscellaneous fixes, cleanup and refactoring.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@6026 b64f7644-9d1e-0410-96f1-a4d463321fa5
Diffstat (limited to 'libhb/encx265.c')
-rw-r--r--libhb/encx265.c356
1 files changed, 229 insertions, 127 deletions
diff --git a/libhb/encx265.c b/libhb/encx265.c
index afb6ef9e2..6b58be849 100644
--- a/libhb/encx265.c
+++ b/libhb/encx265.c
@@ -7,17 +7,15 @@
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
#ifdef USE_X265
-#include <stdarg.h>
-#include <time.h>
#include "hb.h"
#include "hb_dict.h"
#include "x265.h"
-int encx265Init( hb_work_object_t *, hb_job_t * );
-int encx265Work( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** );
-void encx265Close( hb_work_object_t * );
-void writeNALs(hb_work_private_t *, const x265_nal *, int );
+int encx265Init (hb_work_object_t*, hb_job_t*);
+int encx265Work (hb_work_object_t*, hb_buffer_t**, hb_buffer_t**);
+void encx265Close(hb_work_object_t*);
+void writeNALs (hb_work_private_t*, const x265_nal*, int);
hb_work_object_t hb_encx265 =
{
@@ -25,45 +23,52 @@ hb_work_object_t hb_encx265 =
"H.265/HEVC encoder (libx265)",
encx265Init,
encx265Work,
- encx265Close
+ encx265Close,
};
-#define FRAME_INFO_MAX2 (8) // 2^8 = 256; 90000/256 = 352 frames/sec
-#define FRAME_INFO_MIN2 (17) // 2^17 = 128K; 90000/131072 = 1.4 frames/sec
+#define FRAME_INFO_MAX2 (8) // 2^8 = 256; 90000/256 = 352 frames/sec
+#define FRAME_INFO_MIN2 (17) // 2^17 = 128K; 90000/131072 = 1.4 frames/sec
#define FRAME_INFO_SIZE (1 << (FRAME_INFO_MIN2 - FRAME_INFO_MAX2 + 1))
#define FRAME_INFO_MASK (FRAME_INFO_SIZE - 1)
static const char * const hb_x265_encopt_synonyms[][2] =
{
- { "me", "motion", },
- { NULL, NULL, },
+ { "me", "motion", },
+ { NULL, NULL, },
};
struct hb_work_private_s
{
- hb_job_t * job;
- x265_encoder * x265;
- x265_param * param;
- x265_picture pic_in;
- x265_nal * p_nal;
- uint32_t nal_count;
- uint8_t * grey_data;
-
- uint32_t frames_in;
- uint32_t frames_out;
- int chap_mark; // saved chap mark when we're propagating it
- int64_t last_stop; // Debugging - stop time of previous input frame
- int64_t next_chap;
-
- struct {
- int64_t duration;
- } frame_info[FRAME_INFO_SIZE];
+ hb_job_t *job;
+ x265_encoder *x265;
+ x265_param *param;
+ x265_picture pic_in; // TODO: use x265_picture_alloc
+ x265_nal *p_nal;
+ uint32_t nal_count;
+
+ int64_t last_stop;
+ uint32_t frames_in;
+ uint32_t frames_out;
+
+ hb_list_t *delayed_chapters;
+ int64_t next_chapter_pts;
- int i_type;
- int numEncode;
- int64_t i_pts;
+ struct
+ {
+ int64_t duration;
+ }
+ frame_info[FRAME_INFO_SIZE];
FILE *fout;
+
+ char csvfn[1024];
+};
+
+// used in delayed_chapters list
+struct chapter_s
+{
+ int index;
+ int64_t start;
};
/***********************************************************************
@@ -71,71 +76,134 @@ struct hb_work_private_s
***********************************************************************
*
**********************************************************************/
-int encx265Init( hb_work_object_t * w, hb_job_t * job )
+int encx265Init(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->next_chapter_pts = AV_NOPTS_VALUE;
+ pv->delayed_chapters = hb_list_init();
+ pv->job = job;
+ w->private_data = pv;
- pv->job = job;
pv->fout = fopen(job->file, "wb");
fseek(pv->fout, 0, SEEK_SET);
-
+
x265_param *param = pv->param = x265_param_alloc();
-
- x265_param_default_preset(param, "medium", "psnr");
- hb_log("Output video resolution: %dx%d", job->width, job->height);
- param->sourceWidth = job->width;
- param->sourceHeight = job->height;
- param->frameRate = job->vrate/job->vrate_base;
- param->poolNumThreads = hb_get_cpu_count();
-
- param->logLevel = X265_LOG_INFO;
- param->frameNumThreads = hb_get_cpu_count();
- param->tuQTMaxInterDepth = 1;
- param->tuQTMaxIntraDepth = 1;
-
- hb_dict_t *x265_opts = NULL;
- if (job->advanced_opts != NULL && *job->advanced_opts != '\0')
- {
- x265_opts = hb_encopts_to_dict(job->advanced_opts, job->vcodec);
- }
+ if (x265_param_default_preset(param,
+ job->x264_preset, job->x264_tune) < 0)
+ {
+ free(pv);
+ pv = NULL;
+ return 1;
+ }
+
+ /* If the PSNR or SSIM tunes are in use, enable the relevant metric */
+ param->bEnablePsnr = param->bEnableSsim = 0;
+ if (job->x264_tune != NULL && *job->x264_tune)
+ {
+ char *tmp = strdup(job->x264_tune);
+ char *tok = strtok(tmp, ",./-+");
+ do
+ {
+ if (!strncasecmp(tok, "psnr", 4))
+ {
+ param->bEnablePsnr = 1;
+ break;
+ }
+ if (!strncasecmp(tok, "ssim", 4))
+ {
+ param->bEnableSsim = 1;
+ break;
+ }
+ }
+ while ((tok = strtok(NULL, ",./-+")) != NULL);
+ free(tmp);
+ }
+
+ /*
+ * Some HandBrake-specific defaults; users can override them
+ * using the advanced_opts string.
+ */
+ param->frameRate = (int)((double)job->vrate / (double)job->vrate_base + 0.5); // yes, this is an int
+ param->keyframeMax = param->frameRate * 10;
+ param->keyframeMin = param->frameRate;
+
/* iterate through x265_opts and parse the options */
- int ret;
hb_dict_entry_t *entry = NULL;
+ hb_dict_t *x265_opts = hb_encopts_to_dict(job->advanced_opts, job->vcodec);
while ((entry = hb_dict_next(x265_opts, entry)) != NULL)
- {
-
- ret = x265_param_parse( param, entry->key, entry->value );
- /* Let x265 sanity check the options for us */
- if( ret == X265_PARAM_BAD_NAME )
- hb_log( "x265 options: Unknown suboption %s", entry->key );
- if( ret == X265_PARAM_BAD_VALUE )
- hb_log( "x265 options: Bad argument %s=%s", entry->key, entry->value ? entry->value : "(null)" );
-
- }
+ {
+ // here's where the strings are passed to libx265 for parsing
+ int ret = x265_param_parse(param, entry->key, entry->value);
+ // let x265 sanity check the options for us
+ switch (ret)
+ {
+ case X265_PARAM_BAD_NAME:
+ hb_log("encx265: unknown option '%s'", entry->key);
+ break;
+ case X265_PARAM_BAD_VALUE:
+ hb_log("encx265: bad argument '%s=%s'", entry->key,
+ entry->value ? entry->value : "(null)");
+ break;
+ default:
+ break;
+ }
+ }
hb_dict_free(&x265_opts);
- param->subpelRefine = 1;
- param->maxNumMergeCand = 1;
- param->bEnablePsnr = 1;
-
+ /*
+ * Settings which can't be overriden in the advanced_opts string
+ * (muxer-specific settings, resolution, ratecontrol, etc.).
+ */
+ param->sourceWidth = job->width;
+ param->sourceHeight = job->height;
+
if (job->vquality > 0)
- param->rc.qp = (int)job->vquality;
+ {
+ param->rc.rateControlMode = X265_RC_CRF;
+ param->rc.rfConstant = job->vquality;
+ }
+ else
+ {
+ param->rc.rateControlMode = X265_RC_ABR;
+ param->rc.bitrate = job->vbitrate;
+ }
- param->rc.bitrate = job->vbitrate;
+ /* statsfile (but not 2-pass) */
+ memset(pv->csvfn, 0, sizeof(pv->csvfn));
+ if (param->logLevel >= X265_LOG_DEBUG)
+ {
+ if (param->csvfn == NULL)
+ {
+ hb_get_tempory_filename(job->h, pv->csvfn, "x265.csv");
+ param->csvfn = pv->csvfn;
+ }
+ else
+ {
+ strncpy(pv->csvfn, param->csvfn, sizeof(pv->csvfn));
+ }
+ }
+
+ /* Apply profile and level settings last. */
+ if (x265_param_apply_profile(param, job->h264_profile) < 0)
+ {
+ free(pv);
+ pv = NULL;
+ return 1;
+ }
- x265_setup_primitives(param, 0);
+ /* we should now know whether B-frames are enabled */
+ job->areBframes = (param->bframes > 0) + (param->bframes > 0 &&
+ param->bBPyramid > 0);
- pv->x265 = x265_encoder_open( param );
- if ( pv->x265 == NULL )
+ pv->x265 = x265_encoder_open(param);
+ if (pv->x265 == NULL)
{
hb_error("encx265: x265_encoder_open failed.");
- free( pv );
+ free(pv);
pv = NULL;
return 1;
}
- pv->numEncode = 0;
if (!x265_encoder_headers(pv->x265, &pv->p_nal, &pv->nal_count))
{
writeNALs(pv, pv->p_nal, pv->nal_count);
@@ -144,9 +212,20 @@ int encx265Init( hb_work_object_t * w, hb_job_t * job )
return 0;
}
-void encx265Close( hb_work_object_t * w )
+void encx265Close(hb_work_object_t *w)
{
- hb_work_private_t * pv = w->private_data;
+ hb_work_private_t *pv = w->private_data;
+
+ if (pv->delayed_chapters != NULL)
+ {
+ struct chapter_s *item;
+ while ((item = hb_list_item(pv->delayed_chapters, 0)) != NULL)
+ {
+ hb_list_rem(pv->delayed_chapters, item);
+ free(item);
+ }
+ hb_list_close(&pv->delayed_chapters);
+ }
x265_param_free(pv->param);
x265_encoder_close(pv->x265);
@@ -159,7 +238,7 @@ void encx265Close( hb_work_object_t * w )
* 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 )
+static void save_frame_info(hb_work_private_t *pv, hb_buffer_t *in)
{
int i = (in->s.start >> FRAME_INFO_MAX2) & FRAME_INFO_MASK;
pv->frame_info[i].duration = in->s.stop - in->s.start;
@@ -167,7 +246,7 @@ static void save_frame_info( hb_work_private_t * pv, hb_buffer_t * in )
void writeNALs(hb_work_private_t * pv, const x265_nal* nal, int nalcount)
{
- int i;
+ int i;
for (i = 0; i < nalcount; i++)
{
fwrite((const char*)nal->payload, 1, nal->sizeBytes, pv->fout);
@@ -175,13 +254,11 @@ void writeNALs(hb_work_private_t * pv, const x265_nal* nal, int nalcount)
}
}
-static hb_buffer_t *x265_encode( hb_work_object_t *w, hb_buffer_t *in )
+static hb_buffer_t *x265_encode(hb_work_object_t *w, hb_buffer_t *in)
{
hb_work_private_t *pv = w->private_data;
- hb_job_t *job = pv->job;
-
+ hb_job_t *job = pv->job;
x265_picture pic_out;
- int numEncode;
pv->pic_in.stride[0] = in->plane[0].stride;
pv->pic_in.stride[1] = in->plane[1].stride;
@@ -189,97 +266,122 @@ static hb_buffer_t *x265_encode( hb_work_object_t *w, hb_buffer_t *in )
pv->pic_in.planes[0] = in->plane[0].data;
pv->pic_in.planes[1] = in->plane[1].data;
pv->pic_in.planes[2] = in->plane[2].data;
- pv->pic_in.bitDepth = 8;
-
- if( in->s.new_chap && job->chapter_markers )
+ pv->pic_in.poc = pv->frames_in;
+ pv->pic_in.pts = in->s.start;
+ pv->pic_in.bitDepth = 8;
+
+ if (in->s.new_chap && job->chapter_markers)
{
- pv->i_type = X265_TYPE_IDR;
- if( pv->next_chap == 0 )
+ /*
+ * Chapters have to start with an IDR frame so request that this frame be
+ * coded as IDR. Since there may be up to 16 frames currently buffered in
+ * the encoder, remember the timestamp so when this frame finally pops out
+ * of the encoder we'll mark its buffer as the start of a chapter.
+ */
+ pv->pic_in.sliceType = X265_TYPE_IDR;
+ if (pv->next_chapter_pts == AV_NOPTS_VALUE)
{
- pv->next_chap = in->s.start;
- pv->chap_mark = in->s.new_chap;
+ pv->next_chapter_pts = in->s.start;
}
+ /*
+ * Chapter markers are sometimes so close we can get a new one before
+ * the previous marker has been through the encoding queue.
+ *
+ * Dropping markers can cause weird side-effects downstream, including
+ * but not limited to missing chapters in the output, so we need to save
+ * it somehow.
+ */
+ struct chapter_s *item = malloc(sizeof(struct chapter_s));
+ if (item != NULL)
+ {
+ item->start = in->s.start;
+ item->index = in->s.new_chap;
+ hb_list_add(pv->delayed_chapters, item);
+ }
+ /* don't let 'work_loop' put a chapter mark on the wrong buffer */
in->s.new_chap = 0;
}
else
{
- pv->i_type = X265_TYPE_AUTO;
+ pv->pic_in.sliceType = X265_TYPE_AUTO;
}
- if( pv->last_stop != in->s.start )
+ if (pv->last_stop != in->s.start)
{
hb_log("encx265 input continuity err: last stop %"PRId64" start %"PRId64,
- pv->last_stop, in->s.start);
+ pv->last_stop, in->s.start);
}
pv->last_stop = in->s.stop;
+ save_frame_info(pv, in);
- save_frame_info( pv, in );
-
- pv->pic_in.pts = in->s.start;
- numEncode = x265_encoder_encode( pv->x265, &pv->p_nal, &pv->nal_count, &pv->pic_in, &pic_out );
- pv->numEncode += numEncode;
- if ( pv->nal_count > 0 )
+ x265_encoder_encode(pv->x265, &pv->p_nal, &pv->nal_count, &pv->pic_in, &pic_out);
+ if (pv->nal_count > 0)
+ {
writeNALs(pv, pv->p_nal, pv->nal_count);
+ }
+
+ if (pv->next_chapter_pts != AV_NOPTS_VALUE &&
+ pv->next_chapter_pts <= pic_out.pts)
+ {
+ // we're no longer looking for this chapter
+ pv->next_chapter_pts = AV_NOPTS_VALUE;
+
+ // get the chapter index from the list
+ struct chapter_s *item = hb_list_item(pv->delayed_chapters, 0);
+ if (item != NULL)
+ {
+ // we're done with this chapter
+ hb_list_rem(pv->delayed_chapters, item);
+ free(item);
+
+ // we may still have another pending chapter
+ item = hb_list_item(pv->delayed_chapters, 0);
+ if (item != NULL)
+ {
+ // we're looking for this one now
+ // we still need it, don't remove it
+ pv->next_chapter_pts = item->start;
+ }
+ }
+ }
return NULL;
}
-int encx265Work( hb_work_object_t * w, hb_buffer_t ** buf_in,
- hb_buffer_t ** buf_out )
+int encx265Work(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;
- int numEncode;
-
+ hb_buffer_t *in = *buf_in;
+
*buf_out = NULL;
if (in->size <= 0)
{
x265_picture pic_out;
uint32_t i_nal;
x265_nal *nal;
- x265_stats stats;
hb_buffer_t *last_buf = NULL;
while (1)
{
- numEncode = x265_encoder_encode(pv->x265, &nal, &i_nal, NULL, &pic_out);
+ x265_encoder_encode(pv->x265, &nal, &i_nal, NULL, &pic_out);
if (i_nal <= 0)
break;
- pv->numEncode += numEncode;
writeNALs(pv, nal, i_nal);
-
}
// Flushed everything - add the eof to the end of the chain.
- if ( last_buf == NULL )
+ if (last_buf == NULL)
*buf_out = in;
else
last_buf->next = in;
*buf_in = NULL;
-
- x265_encoder_get_stats(pv->x265, &stats, sizeof(stats));
- x265_encoder_close(pv->x265);
-
- if (stats.encodedPictureCount)
- {
- hb_log("X265 encoded %d frames in %.2fs (%.2f fps), %.2f kb/s, ", stats.encodedPictureCount,
- stats.elapsedEncodeTime, stats.encodedPictureCount / stats.elapsedEncodeTime, stats.bitrate);
-
- hb_log("Global PSNR: %.3f\n", stats.globalPsnr);
- }
- else
- hb_log("encoded 0 frames\n");
-
- hb_log("Work done!");
- exit(0);
return HB_WORK_DONE;
}
- pv->pic_in.poc = pv->frames_in;
++pv->frames_in;
++pv->frames_out;
- *buf_out = x265_encode( w, in );
+ *buf_out = x265_encode(w, in);
return HB_WORK_OK;
}