/* * encode_x264.c - encodes video using the x264 library * Written by Christian Bodenstedt, with NMS adaptation and other changes * by Andrew Church * * This file is part of transcode, a video stream processing tool. * transcode is free software, distributable under the terms of the GNU * General Public License (version 2 or later). See the file COPYING * for details. */ /* * Many parts of this file are taken from FFMPEGs "libavcodec/x264.c", * which is licensed under LGPL. Other sources of information were * "export_ffmpeg.c", X264s "x264.c" and MPlayers "libmpcodecs/ve_x264.c" * (all licensed GPL afaik). */ #include "transcode.h" #include "aclib/ac.h" #include "libtc/libtc.h" #include "libtc/cfgfile.h" #include "libtc/optstr.h" #include "libtc/tcmodule-plugin.h" #include "libtc/ratiocodes.h" #include #if X264_BUILD < 65 # error x264 version 65 or later is required #endif #define MOD_NAME "encode_x264.so" #define MOD_VERSION "v0.3.2s (2010-01-02)" #define MOD_CAP "x264 encoder" #define MOD_FEATURES \ TC_MODULE_FEATURE_ENCODE|TC_MODULE_FEATURE_VIDEO #define MOD_FLAGS \ TC_MODULE_FLAG_RECONFIGURABLE /* Module configuration file */ #define X264_CONFIG_FILE "x264.cfg" /* Private data for this module */ typedef struct { int framenum; int interval; int width; int height; int flush_flag; x264_param_t x264params; x264_t *enc; int twopass_bug_workaround; // Work around x264 logfile generation bug? char twopass_log_path[4096]; // Logfile path (for 2-pass bug workaround) } X264PrivateData; /* Static structure to provide pointers for configuration entries */ static struct confdata_struct { x264_param_t x264params; /* Dummy fields for obsolete options */ int dummy_direct_8x8; int dummy_bidir_me; int dummy_brdo; /* Local parameters */ int twopass_bug_workaround; } confdata; /*************************************************************************/ /* This array describes all option-names, pointers to where their * values are stored and the allowed ranges. It's needed to parse the * x264.cfg file using libtc. */ /* Use e.g. OPTION("overscan", vui.i_overscan) for x264params.vui.i_overscan */ #define OPTION(field,name,type,flag,low,high) \ {name, &confdata.x264params.field, (type), (flag), (low), (high)}, /* Option to turn a flag on or off; the off version will have "no" prepended */ #define OPT_FLAG(field,name) \ OPTION(field, name, TCCONF_TYPE_FLAG, 0, 0, 1) \ OPTION(field, "no" name, TCCONF_TYPE_FLAG, 0, 1, 0) /* Integer option with range */ #define OPT_RANGE(field,name,low,high) \ OPTION(field, name, TCCONF_TYPE_INT, TCCONF_FLAG_RANGE, (low), (high)) /* Floating-point option */ #define OPT_FLOAT(field,name) \ OPTION(field, name, TCCONF_TYPE_FLOAT, 0, 0, 0) /* Floating-point option with range */ #define OPT_RANGF(field,name,low,high) \ OPTION(field, name, TCCONF_TYPE_FLOAT, TCCONF_FLAG_RANGE, (low), (high)) /* String option */ #define OPT_STR(field,name) \ OPTION(field, name, TCCONF_TYPE_STRING, 0, 0, 0) /* Dummy entry that doesn't generate an option (placeholder) */ #define OPT_NONE(field) /*nothing*/ static TCConfigEntry conf[] ={ /* CPU flags */ /* CPU acceleration flags (we leave the x264 default alone) */ OPT_NONE (cpu) /* Divide each frame into multiple slices, encode in parallel */ OPT_RANGE(i_threads, "threads", 1, 4) /* Video Properties */ OPT_NONE (i_width) OPT_NONE (i_height) OPT_NONE (i_csp) /* CSP of encoded bitstream, only i420 supported */ /* H.264 level (1.0 ... 5.1) */ OPT_RANGE(i_level_idc, "level_idc", 10, 51) OPT_NONE (i_frame_total) /* number of frames to encode if known, else 0 */ /* they will be reduced to be 0 < x <= 65535 and prime */ OPT_NONE (vui.i_sar_height) OPT_NONE (vui.i_sar_width) /* 0=undef, 1=show, 2=crop */ OPT_RANGE(vui.i_overscan, "overscan", 0, 2) /* 0=component 1=PAL 2=NTSC 3=SECAM 4=Mac 5=undef */ OPT_RANGE(vui.i_vidformat, "vidformat", 0, 5) OPT_FLAG (vui.b_fullrange, "fullrange") /* 1=bt709 2=undef 4=bt470m 5=bt470bg 6=smpte170m 7=smpte240m 8=film */ OPT_RANGE(vui.i_colorprim, "colorprim", 0, 8) /* 1..7 as above, 8=linear, 9=log100, 10=log316 */ OPT_RANGE(vui.i_transfer, "transfer", 0, 10) /* 0=GBR 1=bt709 2=undef 4=fcc 5=bt470bg 6=smpte170m 7=smpte240m 8=YCgCo */ OPT_RANGE(vui.i_colmatrix, "colmatrix", 0, 8) /* ??? */ OPT_RANGE(vui.i_chroma_loc, "chroma_loc", 0, 5) OPT_NONE (i_fps_num) OPT_NONE (i_fps_den) /* Bitstream parameters */ /* Maximum number of reference frames */ OPT_RANGE(i_frame_reference, "frameref", 1, 16) /* Force an IDR keyframe at this interval */ OPT_RANGE(i_keyint_max, "keyint", 1,999999) OPT_RANGE(i_keyint_max, "keyint_max", 1,999999) /* Scenecuts closer together than this are coded as I, not IDR. */ OPT_RANGE(i_keyint_min, "keyint_min", 1,999999) /* How aggressively to insert extra I frames */ OPT_RANGE(i_scenecut_threshold, "scenecut", -1, 100) /* How many B-frames between 2 reference pictures */ OPT_RANGE(i_bframe, "bframes", 0, 16) /* Use adaptive B-frame encoding */ OPT_FLAG (i_bframe_adaptive, "b_adapt") /* How often B-frames are used */ OPT_RANGE(i_bframe_bias, "b_bias", -90, 100) /* Keep some B-frames as references */ #if X264_BUILD >= 78 OPT_RANGE(i_bframe_pyramid, "b_pyramid", 0, 2) #else OPT_FLAG (b_bframe_pyramid, "b_pyramid") #endif /* Use deblocking filter */ OPT_FLAG (b_deblocking_filter, "deblock") /* [-6, 6] -6 light filter, 6 strong */ OPT_RANGE(i_deblocking_filter_alphac0,"deblockalpha", -6, 6) /* [-6, 6] idem */ OPT_RANGE(i_deblocking_filter_beta, "deblockbeta", -6, 6) /* Use context-adaptive binary arithmetic coding */ OPT_FLAG (b_cabac, "cabac") /* Initial data for CABAC? */ OPT_RANGE(i_cabac_init_idc, "cabac_init_idc", 0, 2) #if X264_BUILD >= 89 /* Add NAL HRD parameters to the bitstream */ OPT_FLAG (i_nal_hrd, "nal_hrd") #endif /* Enable interlaced encoding (--encode_fields) */ OPT_NONE (b_interlaced) #if X264_BUILD >= 89 /* First field (1=top, 0=bottom) (--encode_fields) */ OPT_NONE (b_tff) #endif /* Quantization matrix selection: 0=flat 1=JVT 2=custom */ OPT_RANGE(i_cqm_preset, "cqm", 0, 2) /* Custom quant matrix filename */ OPT_STR (psz_cqm_file, "cqm_file") /* Quant matrix arrays set up by library */ /* Logging */ OPT_NONE (pf_log) OPT_NONE (p_log_private) OPT_NONE (i_log_level) OPT_NONE (b_visualize) /* Encoder analyser parameters */ /* Partition selection (we always enable everything) */ OPT_NONE (analyse.intra) OPT_NONE (analyse.inter) /* Allow integer 8x8 DCT transforms */ OPT_FLAG (analyse.b_transform_8x8, "8x8dct") /* Implicit weighting for B-frames */ OPT_FLAG (analyse.b_weighted_bipred, "weight_b") /* Spatial vs temporal MV prediction, 0=none 1=spatial 2=temporal 3=auto */ OPT_RANGE(analyse.i_direct_mv_pred, "direct_pred", 0, 3) /* QP difference between chroma and luma */ OPT_RANGE(analyse.i_chroma_qp_offset, "chroma_qp_offset",-12, 12) /* Motion estimation algorithm to use (X264_ME_*) 0=dia 1=hex 2=umh 3=esa*/ OPT_RANGE(analyse.i_me_method, "me", 0, 3) /* Integer pixel motion estimation search range (from predicted MV) */ OPT_RANGE(analyse.i_me_range, "me_range", 4, 64) /* Maximum length of a MV (in pixels), 32-2048 or -1=auto */ OPT_RANGE(analyse.i_mv_range, "mv_range", -1, 2048) /* Subpixel motion estimation quality: 1=fast, 9=best */ OPT_RANGE(analyse.i_subpel_refine, "subq", 1, 9) /* Chroma ME for subpel and mode decision in P-frames */ OPT_FLAG (analyse.b_chroma_me, "chroma_me") /* Allow each MB partition in P-frames to have its own reference number */ OPT_FLAG (analyse.b_mixed_references, "mixed_refs") /* Trellis RD quantization */ OPT_RANGE(analyse.i_trellis, "trellis", 0, 2) /* Early SKIP detection on P-frames */ OPT_FLAG (analyse.b_fast_pskip, "fast_pskip") /* Transform coefficient thresholding on P-frames */ OPT_FLAG (analyse.b_dct_decimate, "dct_decimate") /* Noise reduction */ OPT_RANGE(analyse.i_noise_reduction, "nr", 0, 65536) /* Compute PSNR stats, at the cost of a few % of CPU time */ OPT_FLAG (analyse.b_psnr, "psnr") /* Compute SSIM stats, at the cost of a few % of CPU time */ OPT_FLAG (analyse.b_ssim, "ssim") /* Rate control parameters */ /* QP value for constant-quality encoding (to be a transcode option, * eventually--FIXME) */ OPT_NONE (rc.i_qp_constant) /* Minimum allowed QP value */ OPT_RANGE(rc.i_qp_min, "qp_min", 0, 51) /* Maximum allowed QP value */ OPT_RANGE(rc.i_qp_max, "qp_max", 0, 51) /* Maximum QP difference between frames */ OPT_RANGE(rc.i_qp_step, "qp_step", 0, 50) /* Bitrate (transcode -w) */ OPT_NONE (rc.i_bitrate) /* Nominal QP for 1-pass VBR */ OPT_RANGF(rc.f_rf_constant, "crf", 0, 51) /* Allowed variance from average bitrate */ OPT_FLOAT(rc.f_rate_tolerance, "ratetol") /* Maximum local bitrate (kbit/s) */ OPT_RANGE(rc.i_vbv_max_bitrate, "vbv_maxrate", 0,240000) /* Size of VBV buffer for CBR encoding */ OPT_RANGE(rc.i_vbv_buffer_size, "vbv_bufsize", 0,240000) /* Initial occupancy of VBV buffer */ OPT_RANGF(rc.f_vbv_buffer_init, "vbv_init", 0.0, 1.0) /* QP ratio between I and P frames */ OPT_FLOAT(rc.f_ip_factor, "ip_ratio") /* QP ratio between P and B frames */ OPT_FLOAT(rc.f_pb_factor, "pb_ratio") /* Complexity blurring before QP compression */ OPT_RANGF(rc.f_complexity_blur, "cplx_blur", 0.0, 999.0) /* QP curve compression: 0.0 = constant bitrate, 1.0 = constant quality */ OPT_RANGF(rc.f_qcompress, "qcomp", 0.0, 1.0) /* QP blurring after compression */ OPT_RANGF(rc.f_qblur, "qblur", 0.0, 99.0) /* Rate control override zones (not supported by transcode) */ OPT_NONE (rc.zones) OPT_NONE (rc.i_zones) /* Alternate method of specifying zones */ OPT_STR (rc.psz_zones, "zones") /* Other parameters */ OPT_FLAG (b_aud, "aud") OPT_NONE (b_repeat_headers) OPT_NONE (i_sps_id) /* Module configuration options (which do not affect encoding) */ {"2pass_bug_workaround", &confdata.twopass_bug_workaround, TCCONF_TYPE_FLAG, 0, 0, 1}, {"no2pass_bug_workaround", &confdata.twopass_bug_workaround, TCCONF_TYPE_FLAG, 0, 1, 0}, /* Obsolete options (scheduled for future removal) */ {"direct_8x8", &confdata.dummy_direct_8x8, TCCONF_TYPE_FLAG, 0, 0, 1}, {"nodirect_8x8", &confdata.dummy_direct_8x8, TCCONF_TYPE_FLAG, 0, 1, 0}, {"bidir_me", &confdata.dummy_bidir_me, TCCONF_TYPE_FLAG, 0, 0, 1}, {"nobidir_me", &confdata.dummy_bidir_me, TCCONF_TYPE_FLAG, 0, 1, 0}, {"brdo", &confdata.dummy_brdo, TCCONF_TYPE_FLAG, 0, 0, 1}, {"nobrdo", &confdata.dummy_brdo, TCCONF_TYPE_FLAG, 0, 1, 0}, {NULL} }; /*************************************************************************/ /*************************************************************************/ /** * x264_log: Logging routine for x264 library. * * Parameters: * userdata: Unused. * level: x264 log level (X264_LOG_*). * format: Log message format string. * args: Log message format arguments. * Return value: * None. */ static void x264_log(void *userdata, int level, const char *format, va_list args) { TCLogLevel tclevel; char buf[TC_BUF_MAX]; if (!format) return; switch (level) { case X264_LOG_ERROR: tclevel = TC_LOG_ERR; break; case X264_LOG_WARNING: tclevel = TC_LOG_WARN; break; case X264_LOG_INFO: if (!(verbose & TC_INFO)) return; tclevel = TC_LOG_INFO; break; case X264_LOG_DEBUG: if (!(verbose & TC_DEBUG)) return; tclevel = TC_LOG_MSG; break; default: return; } tc_vsnprintf(buf, sizeof(buf), format, args); buf[strcspn(buf,"\r\n")] = 0; /* delete trailing newline */ tc_log(tclevel, MOD_NAME, "%s", buf); } /*************************************************************************/ /** * x264params_set_multipass: Does all settings related to multipass. * * Parameters: * pass: 0 = single pass * 1 = 1st pass * 2 = 2nd pass (final pass of multipass encoding) * 3 = Nth pass (intermediate passes of multipass encoding) * statsfilename: where to read and write multipass stat data. * Return value: * Always 0. * Preconditions: * params != NULL * pass == 0 || statsfilename != NULL */ static int x264params_set_multipass(x264_param_t *params, int pass, const char *statsfilename) { /* Drop the const and hope that x264 treats it as const anyway */ params->rc.psz_stat_in = (char *)statsfilename; params->rc.psz_stat_out = (char *)statsfilename; switch (pass) { default: params->rc.b_stat_write = 0; params->rc.b_stat_read = 0; break; case 1: params->rc.b_stat_write = 1; params->rc.b_stat_read = 0; break; case 2: params->rc.b_stat_write = 0; params->rc.b_stat_read = 1; break; case 3: params->rc.b_stat_write = 1; params->rc.b_stat_read = 1; break; } return TC_OK; } /*************************************************************************/ /** * x264params_check: Checks or corrects some strange combinations of * settings done in x264params. * * Parameters: * params: x264_param_t structure to check * Return value: * 0 on success, nonzero otherwise. */ static int x264params_check(x264_param_t *params) { /* don't know if these checks are really needed, but they won't hurt */ if (params->rc.i_qp_min > params->rc.i_qp_constant) { params->rc.i_qp_min = params->rc.i_qp_constant; } if (params->rc.i_qp_max < params->rc.i_qp_constant) { params->rc.i_qp_max = params->rc.i_qp_constant; } if (params->rc.i_rc_method == X264_RC_ABR) { if ((params->rc.i_vbv_max_bitrate > 0) != (params->rc.i_vbv_buffer_size > 0) ) { tc_log_error(MOD_NAME, "VBV requires both vbv_maxrate and vbv_bufsize."); return TC_ERROR; } } return TC_OK; } /*************************************************************************/ /** * x264params_set_by_vob: Handle transcode CLI and tc-autodetection * dependent entries in x264_param_t. * * This method copies various values from transcodes vob_t structure to * x264 $params. That means all settings that can be done through * transcodes CLI or autodetection are applied to x264s $params here * (and I hope nowhere else). * * Parameters: * params: x264_param_t structure to apply changes to * vob: transcodes vob_t structure to copy values from * Return value: * 0 on success, nonzero otherwise. * Preconditions: * params != NULL * vob != NULL */ static int x264params_set_by_vob(x264_param_t *params, const vob_t *vob) { /* Set video/bitstream parameters */ params->i_width = vob->ex_v_width; params->i_height = vob->ex_v_height; params->b_interlaced = (vob->encode_fields==TC_ENCODE_FIELDS_TOP_FIRST || vob->encode_fields==TC_ENCODE_FIELDS_BOTTOM_FIRST); #if X264_BUILD >= 89 params->b_tff = (vob->encode_fields==TC_ENCODE_FIELDS_TOP_FIRST); #endif if (params->rc.f_rf_constant != 0) { params->rc.i_rc_method = X264_RC_CRF; } else { params->rc.i_rc_method = X264_RC_ABR; } params->rc.i_bitrate = vob->divxbitrate; /* what a name */ if (TC_NULL_MATCH == tc_frc_code_to_ratio(vob->ex_frc, ¶ms->i_fps_num, ¶ms->i_fps_den) ) { if (vob->ex_fps > 29.9 && vob->ex_fps < 30) { params->i_fps_num = 30000; params->i_fps_den = 1001; } else if (vob->ex_fps > 23.9 && vob->ex_fps < 24) { params->i_fps_num = 24000; params->i_fps_den = 1001; } else if (vob->ex_fps > 59.9 && vob->ex_fps < 60) { params->i_fps_num = 60000; params->i_fps_den = 1001; } else { params->i_fps_num = vob->ex_fps * 1000; params->i_fps_den = 1000; } } if (0 != tc_find_best_aspect_ratio(vob, ¶ms->vui.i_sar_width, ¶ms->vui.i_sar_height, MOD_NAME) ) { tc_log_error(MOD_NAME, "unable to find sane value for SAR"); return TC_ERROR; } /* Set logging function and acceleration flags */ params->pf_log = x264_log; params->p_log_private = NULL; params->cpu &= ~(X264_CPU_MMX | X264_CPU_MMXEXT | X264_CPU_SSE | X264_CPU_SSE2 | X264_CPU_SSE3 | X264_CPU_SSSE3 | X264_CPU_SSE4 | X264_CPU_SSE42 | X264_CPU_LZCNT); if (tc_accel & AC_MMX) params->cpu |= X264_CPU_MMX; if (tc_accel & AC_MMXEXT) params->cpu |= X264_CPU_MMXEXT; if (tc_accel & AC_SSE) params->cpu |= X264_CPU_SSE; if (tc_accel & AC_SSE2) params->cpu |= X264_CPU_SSE2; if (tc_accel & AC_SSE3) params->cpu |= X264_CPU_SSE3; if (tc_accel & AC_SSSE3) params->cpu |= X264_CPU_SSSE3; if (tc_accel & AC_SSE41) params->cpu |= X264_CPU_SSE4; if (tc_accel & AC_SSE42) params->cpu |= X264_CPU_SSE42; if (tc_accel & AC_SSE4A) params->cpu |= X264_CPU_LZCNT; return TC_OK; } /*************************************************************************/ /** * do_2pass_bug_workaround: Work around a bug present in at least x264 * versions 65 through 67 which causes invalid frame numbers to be written * to the 2-pass logfile. * * Parameters: * path: Logfile pathname. * Return value: * 0 on success, nonzero otherwise. * Preconditions: * path != NULL */ static int do_2pass_bug_workaround(const char *path) { FILE *fp; char *buffer; long filesize, nread, offset; long nframes; fp = fopen(path, "r+"); if (!fp) { tc_log_warn(MOD_NAME, "Failed to open 2-pass logfile '%s': %s", path, strerror(errno)); goto error_return; } /* x264 treats the logfile as a single, semicolon-separated buffer * rather than a series of lines, so do the same here. */ /* Read in the logfile data */ if (fseek(fp, 0, SEEK_END) != 0) { tc_log_warn(MOD_NAME, "Seek to end of 2-pass logfile failed: %s", strerror(errno)); goto error_close_file; } filesize = ftell(fp); if (filesize < 0) { tc_log_warn(MOD_NAME, "Get size of 2-pass logfile failed: %s", strerror(errno)); goto error_close_file; } buffer = malloc(filesize); if (!buffer) { tc_log_warn(MOD_NAME, "No memory for 2-pass logfile buffer" " (%ld bytes)", filesize); goto error_close_file; } if (fseek(fp, 0, SEEK_SET) != 0) { tc_log_warn(MOD_NAME, "Seek to beginning of 2-pass logfile failed: %s", strerror(errno)); goto error_free_buffer; } nread = fread(buffer, 1, filesize, fp); if (nread != filesize) { tc_log_warn(MOD_NAME, "Short read on 2-pass logfile (expected %ld" " bytes, got %ld)", filesize, nread); goto error_free_buffer; } /* Count the number of frames */ nframes = 0; offset = 0; if (strncmp(buffer, "#options:", 9) == 0) { // just like x264 offset = strcspn(buffer, "\n") + 1; } for (; offset < filesize; offset++) { if (buffer[offset] == ';') { nframes++; } } /* Go through the frame list and check for out-of-range frame numbers */ offset = 0; if (strncmp(buffer, "#options:", 9) == 0) { offset = strcspn(buffer, "\n") + 1; } while (offset < filesize) { long framenum; char *s; if (strncmp(&buffer[offset], "in:", 3) != 0) { tc_log_warn(MOD_NAME, "Can't parse 2-pass logfile at offset %ld," " giving up.", offset); offset = filesize; // Don't truncate the file break; } framenum = strtol(&buffer[offset+3], &s, 10); if ((s && *s != ' ') || framenum < 0) { tc_log_warn(MOD_NAME, "Can't parse 2-pass logfile at offset %ld," " giving up.", offset+3); offset = filesize; // Don't truncate the file break; } if (framenum >= nframes) { tc_log_warn(MOD_NAME, "Truncating corrupt x264 logfile:"); tc_log_warn(MOD_NAME, " in(%ld) >= nframes(%ld) at offset %ld", framenum, nframes, offset); tc_log_warn(MOD_NAME, "Please report this bug to the x264" " developers."); break; // Truncate the file here } offset += strcspn(&buffer[offset], ";"); offset += strspn(&buffer[offset], ";\n"); } /* Truncate the file if the bug was detected */ if (offset < filesize) { if (ftruncate(fileno(fp), offset) != 0) { tc_log_warn(MOD_NAME, "Failed to truncate 2-pass logfile: %s", strerror(errno)); goto error_free_buffer; } } /* Successful return */ free(buffer); fclose(fp); return 0; /* Error handling */ error_free_buffer: free(buffer); error_close_file: fclose(fp); error_return: return -1; } /*************************************************************************/ /*************************************************************************/ /* Module interface routines and data. */ /*************************************************************************/ /** * x264_init: Initialize this instance of the module. See tcmodule-data.h * for function details. */ static int x264_init(TCModuleInstance *self, uint32_t features) { X264PrivateData *pd = NULL; TC_MODULE_SELF_CHECK(self, "init"); TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features); pd = tc_malloc(sizeof(X264PrivateData)); if (!pd) { tc_log_error(MOD_NAME, "init: out of memory!"); return TC_ERROR; } pd->framenum = 0; pd->enc = NULL; if (verbose) { tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP); } self->userdata = pd; return TC_OK; } /*************************************************************************/ /** * x264_fini: Clean up after this instance of the module. See * tcmodule-data.h for function details. */ static int x264_fini(TCModuleInstance *self) { X264PrivateData *pd = NULL; TC_MODULE_SELF_CHECK(self, "fini"); pd = self->userdata; if (pd->enc) { x264_encoder_close(pd->enc); pd->enc = NULL; } tc_free(self->userdata); self->userdata = NULL; return TC_OK; } /*************************************************************************/ /** * x264_configure: Configure this instance of the module. See * tcmodule-data.h for function details. */ static int x264_configure(TCModuleInstance *self, const char *options, vob_t *vob) { X264PrivateData *pd = NULL; char *s; TC_MODULE_SELF_CHECK(self, "configure"); pd = self->userdata; pd->flush_flag = vob->encoder_flush; /* Initialize parameter block */ memset(&confdata, 0, sizeof(confdata)); x264_param_default(&confdata.x264params); /* Parameters not (yet) settable via options: */ confdata.x264params.analyse.intra = ~0; confdata.x264params.analyse.inter = ~0; /* Watch for obsolete options being set */ confdata.dummy_direct_8x8 = -1; confdata.dummy_bidir_me = -1; confdata.dummy_brdo = -1; /* Read settings from configuration file */ module_read_config(X264_CONFIG_FILE, NULL, conf, MOD_NAME); /* Parse options given in -y option string (format: * "name1=value1:name2=value2:...") */ for (s = (vob->ex_v_string ? strtok(vob->ex_v_string,":") : NULL); s != NULL; s = strtok(NULL,":") ) { if (!module_read_config_line(s, conf, MOD_NAME)) { tc_log_error(MOD_NAME, "Error parsing module options"); return TC_ERROR; } } /* Complain about obsolete options being set */ if (confdata.dummy_direct_8x8 != -1) { tc_log_warn(MOD_NAME, "Option direct_8x8 is obsolete, and is now" " always active."); } if (confdata.dummy_bidir_me != -1) { tc_log_warn(MOD_NAME, "Option bidir_me is obsolete in x264 version 65.\n" " bidir_me will be automatically applied when" " subq >= 5."); } if (confdata.dummy_brdo != -1) { tc_log_warn(MOD_NAME, "Option bidir_me is obsolete in x264 version 65.\n" " brdo will be automatically applied when subq >= 7."); } /* Save multipass logfile name if 2-pass bug workaround was requested */ if (confdata.twopass_bug_workaround && (vob->divxmultipass == 1 || vob->divxmultipass == 3) ) { const size_t strsize = strlen(vob->divxlogfile) + 1; if (strsize > sizeof(pd->twopass_log_path)) { tc_log_error(MOD_NAME, "2-pass logfile path too long.\n" " Use a shorter pathname or disable the" " 2pass_bug_workaround option."); return TC_ERROR; } ac_memcpy(pd->twopass_log_path, vob->divxlogfile, strsize); pd->twopass_bug_workaround = 1; } else { pd->twopass_bug_workaround = 0; } /* Apply extra settings to $x264params */ if (0 != x264params_set_multipass(&confdata.x264params, vob->divxmultipass, vob->divxlogfile) ) { tc_log_error(MOD_NAME, "Failed to apply multipass settings."); return TC_ERROR; } /* Copy parameter block to module private data */ ac_memcpy(&pd->x264params, &confdata.x264params, sizeof(pd->x264params)); /* Apply transcode CLI and autodetected values from $vob to * $x264params. This is done as the last step to make transcode CLI * override any settings done before. */ if (0 != x264params_set_by_vob(&pd->x264params, vob)) { tc_log_error(MOD_NAME, "Failed to evaluate vob_t values."); return TC_ERROR; } /* Test if the set parameters fit together. */ if (0 != x264params_check(&pd->x264params)) { return TC_ERROR; } /* Now we've set all parameters gathered from transcode and the config * file to $x264params. Let's give some status report and finally open * the encoder. */ if (verbose >= TC_DEBUG) { module_print_config(conf, MOD_NAME); } pd->enc = x264_encoder_open(&pd->x264params); if (!pd->enc) { tc_log_error(MOD_NAME, "x264_encoder_open() returned NULL - sorry."); return TC_ERROR; } return TC_OK; } /*************************************************************************/ /** * x264_stop: Reset this instance of the module. See tcmodule-data.h for * function details. */ static int x264_stop(TCModuleInstance *self) { X264PrivateData *pd = NULL; TC_MODULE_SELF_CHECK(self, "stop"); pd = self->userdata; if (pd->enc) { x264_encoder_close(pd->enc); pd->enc = NULL; } if (pd->twopass_bug_workaround) { do_2pass_bug_workaround(pd->twopass_log_path); } return TC_OK; } /*************************************************************************/ /** * x264_inspect: Return the value of an option in this instance of the * module. See tcmodule-data.h for function details. */ static int x264_inspect(TCModuleInstance *self, const char *param, const char **value) { X264PrivateData *pd = NULL; static char buf[TC_BUF_MAX]; TC_MODULE_SELF_CHECK(self, "inspect"); TC_MODULE_SELF_CHECK(param, "inspect"); TC_MODULE_SELF_CHECK(value, "inspect"); pd = self->userdata; if (optstr_lookup(param, "help")) { tc_snprintf(buf, sizeof(buf), "Overview:\n" " Encodes video in h.264 format using the x264 library.\n" "Options available:\n" " All options in x264.cfg can be specified on the command line\n" " using the format: -y x264=name1=value1:name2=value2:...\n"); *value = buf; } /* FIXME: go through the option list to find a match to param */ return TC_OK; } /*************************************************************************/ /** * x264_encode_video: Decode a frame of data. See tcmodule-data.h for * function details. */ static int x264_encode_video(TCModuleInstance *self, vframe_list_t *inframe, vframe_list_t *outframe) { X264PrivateData *pd; x264_nal_t *nal; x264_picture_t pic, pic_out; int nnal, i, ret; TC_MODULE_SELF_CHECK(self, "encode_video"); pd = self->userdata; pd->framenum++; memset(&pic, 0, sizeof(pic)); memset(&pic_out, 0, sizeof(pic_out)); if (inframe == NULL) { outframe->video_len = 0; return TC_OK; } pic.img.i_csp = X264_CSP_I420; pic.img.i_plane = 3; pic.img.plane[0] = inframe->video_buf; pic.img.i_stride[0] = inframe->v_width; pic.img.plane[1] = pic.img.plane[0] + inframe->v_width*inframe->v_height; pic.img.i_stride[1] = inframe->v_width / 2; pic.img.plane[2] = pic.img.plane[1] + (inframe->v_width/2)*(inframe->v_height/2); pic.img.i_stride[2] = inframe->v_width / 2; pic.i_type = X264_TYPE_AUTO; pic.i_qpplus1 = 0; /* FIXME: Is this pts-handling ok? I don't have a clue how * PTS/DTS handling works. Does it matter, when no muxing is * done? */ pic.i_pts = (int64_t) pd->framenum * pd->x264params.i_fps_den; ret = x264_encoder_encode(pd->enc, &nal, &nnal, &pic, &pic_out); #if X264_BUILD >= 76 if (ret < 0) { #else if (ret != 0) { #endif return TC_ERROR; } outframe->video_len = 0; for (i = 0; i < nnal; i++) { int size = outframe->video_size - outframe->video_len; if (size <= 0) { tc_log_error(MOD_NAME, "output buffer overflow"); return TC_ERROR; } #if X264_BUILD >= 76 ac_memcpy(outframe->video_buf + outframe->video_len, nal[i].p_payload, nal[i].i_payload); outframe->video_len += nal[i].i_payload; #else ret = x264_nal_encode(outframe->video_buf + outframe->video_len, &size, 1, &nal[i]); if (ret < 0 || size > outframe->video_size - outframe->video_len) { tc_log_error(MOD_NAME, "output buffer overflow"); break; } outframe->video_len += size; #endif } /* FIXME: ok, that sucks. How to reformat it ina better way? -- fromani */ if ((pic_out.i_type == X264_TYPE_IDR) || (pic_out.i_type == X264_TYPE_I && pd->x264params.i_frame_reference == 1 && !pd->x264params.i_bframe)) { outframe->attributes |= TC_FRAME_IS_KEYFRAME; } return TC_OK; } /*************************************************************************/ static const TCCodecID x264_codecs_in[] = { TC_CODEC_YUV420P, TC_CODEC_ERROR }; static const TCCodecID x264_codecs_out[] = { TC_CODEC_H264, TC_CODEC_ERROR }; TC_MODULE_CODEC_FORMATS(x264); TC_MODULE_INFO(x264); static const TCModuleClass x264_class = { TC_MODULE_CLASS_HEAD(x264), .init = x264_init, .fini = x264_fini, .configure = x264_configure, .stop = x264_stop, .inspect = x264_inspect, .encode_video = x264_encode_video, }; TC_MODULE_ENTRY_POINT(x264) /*************************************************************************/ /* * Local variables: * c-file-style: "stroustrup" * c-file-offsets: ((case-label . *) (statement-case-intro . *)) * indent-tabs-mode: nil * End: * * vim: expandtab shiftwidth=4: */