diff options
author | Rodeo <[email protected]> | 2014-02-11 21:33:34 +0000 |
---|---|---|
committer | Rodeo <[email protected]> | 2014-02-11 21:33:34 +0000 |
commit | 307c777504c7d3d2fc33db9bcaa3ef3d310e4d38 (patch) | |
tree | 63a669c4c7de42590ac6788ddcc3af0a2682d4e4 /libhb/encx265.c | |
parent | 25b0757e8fc367d3b56422e41a096e59846b25bf (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.c | 356 |
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; } |