/* hb.c Copyright (c) 2003-2014 HandBrake Team This file is part of the HandBrake source code Homepage: . It may be used under the terms of the GNU General Public License v2. For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html */ #include "hb.h" #include "opencl.h" #include "hbffmpeg.h" #include #include #include #ifdef USE_QSV #include "qsv_common.h" #endif #if defined( SYS_MINGW ) #include #if defined( PTW32_STATIC_LIB ) #include #endif #endif struct hb_handle_s { int id; /* The "Check for update" thread */ int build; char version[32]; hb_thread_t * update_thread; /* This thread's only purpose is to check other threads' states */ volatile int die; hb_thread_t * main_thread; int pid; /* DVD/file scan thread */ hb_title_set_t title_set; hb_thread_t * scan_thread; /* The thread which processes the jobs. Others threads are launched from this one (see work.c) */ hb_list_t * jobs; hb_job_t * current_job; int job_count; int job_count_permanent; volatile int work_die; hb_error_code work_error; hb_thread_t * work_thread; hb_lock_t * state_lock; hb_state_t state; int paused; hb_lock_t * pause_lock; /* For MacGui active queue increments each time the scan thread completes*/ int scanCount; volatile int scan_die; /* Stash of persistent data between jobs, for stuff like correcting frame count and framerate estimates on multi-pass encodes where frames get dropped. */ hb_interjob_t * interjob; // power management opaque pointer void *system_sleep_opaque; } ; hb_work_object_t * hb_objects = NULL; int hb_instance_counter = 0; static void thread_func( void * ); static int ff_lockmgr_cb(void **mutex, enum AVLockOp op) { switch ( op ) { case AV_LOCK_CREATE: { *mutex = hb_lock_init(); } break; case AV_LOCK_DESTROY: { hb_lock_close( (hb_lock_t**)mutex ); } break; case AV_LOCK_OBTAIN: { hb_lock( (hb_lock_t*)*mutex ); } break; case AV_LOCK_RELEASE: { hb_unlock( (hb_lock_t*)*mutex ); } break; default: break; } return 0; } void hb_avcodec_init() { av_lockmgr_register(ff_lockmgr_cb); av_register_all(); #ifdef _WIN64 // avresample's assembly optimizations can cause crashes under Win x86_64 // (see http://bugzilla.libav.org/show_bug.cgi?id=496) // disable AVX and FMA4 as a workaround hb_deep_log(2, "hb_avcodec_init: Windows x86_64, disabling AVX and FMA4"); int cpu_flags = av_get_cpu_flags() & ~AV_CPU_FLAG_AVX & ~AV_CPU_FLAG_FMA4; av_set_cpu_flags_mask(cpu_flags); #endif } int hb_avcodec_open(AVCodecContext *avctx, AVCodec *codec, AVDictionary **av_opts, int thread_count) { int ret; if ((thread_count == HB_FFMPEG_THREADS_AUTO || thread_count > 0) && (codec->type == AVMEDIA_TYPE_VIDEO)) { avctx->thread_count = (thread_count == HB_FFMPEG_THREADS_AUTO) ? hb_get_cpu_count() / 2 + 1 : thread_count; avctx->thread_type = FF_THREAD_FRAME|FF_THREAD_SLICE; avctx->thread_safe_callbacks = 1; } else { avctx->thread_count = 1; } if (codec->capabilities & CODEC_CAP_EXPERIMENTAL) { // "experimental" encoders will not open without this avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; } ret = avcodec_open2(avctx, codec, av_opts); return ret; } int hb_avcodec_close(AVCodecContext *avctx) { int ret; ret = avcodec_close(avctx); return ret; } int hb_avpicture_fill(AVPicture *pic, hb_buffer_t *buf) { int ret, ii; for (ii = 0; ii < 4; ii++) pic->linesize[ii] = buf->plane[ii].stride; ret = av_image_fill_pointers(pic->data, buf->f.fmt, buf->plane[0].height_stride, buf->data, pic->linesize); if (ret != buf->size) { hb_error("Internal error hb_avpicture_fill expected %d, got %d", buf->size, ret); } return ret; } static int handle_jpeg(enum AVPixelFormat *format) { switch (*format) { case AV_PIX_FMT_YUVJ420P: *format = AV_PIX_FMT_YUV420P; return 1; case AV_PIX_FMT_YUVJ422P: *format = AV_PIX_FMT_YUV422P; return 1; case AV_PIX_FMT_YUVJ444P: *format = AV_PIX_FMT_YUV444P; return 1; case AV_PIX_FMT_YUVJ440P: *format = AV_PIX_FMT_YUV440P; return 1; default: return 0; } } struct SwsContext* hb_sws_get_context(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags) { struct SwsContext * ctx; ctx = sws_alloc_context(); if ( ctx ) { int srcRange, dstRange; srcRange = handle_jpeg(&srcFormat); dstRange = handle_jpeg(&dstFormat); /* enable this when implemented in Libav flags |= SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP; */ av_opt_set_int(ctx, "srcw", srcW, 0); av_opt_set_int(ctx, "srch", srcH, 0); av_opt_set_int(ctx, "src_range", srcRange, 0); av_opt_set_int(ctx, "src_format", srcFormat, 0); av_opt_set_int(ctx, "dstw", dstW, 0); av_opt_set_int(ctx, "dsth", dstH, 0); av_opt_set_int(ctx, "dst_range", dstRange, 0); av_opt_set_int(ctx, "dst_format", dstFormat, 0); av_opt_set_int(ctx, "sws_flags", flags, 0); sws_setColorspaceDetails( ctx, sws_getCoefficients( SWS_CS_DEFAULT ), // src colorspace srcRange, // src range 0 = MPG, 1 = JPG sws_getCoefficients( SWS_CS_DEFAULT ), // dst colorspace dstRange, // dst range 0 = MPG, 1 = JPG 0, // brightness 1 << 16, // contrast 1 << 16 ); // saturation if (sws_init_context(ctx, NULL, NULL) < 0) { fprintf(stderr, "Cannot initialize resampling context\n"); sws_freeContext(ctx); ctx = NULL; } } return ctx; } uint64_t hb_ff_mixdown_xlat(int hb_mixdown, int *downmix_mode) { uint64_t ff_layout = 0; int mode = AV_MATRIX_ENCODING_NONE; switch (hb_mixdown) { // Passthru case HB_AMIXDOWN_NONE: break; case HB_AMIXDOWN_MONO: case HB_AMIXDOWN_LEFT: case HB_AMIXDOWN_RIGHT: ff_layout = AV_CH_LAYOUT_MONO; break; case HB_AMIXDOWN_DOLBY: ff_layout = AV_CH_LAYOUT_STEREO; mode = AV_MATRIX_ENCODING_DOLBY; break; case HB_AMIXDOWN_DOLBYPLII: ff_layout = AV_CH_LAYOUT_STEREO; mode = AV_MATRIX_ENCODING_DPLII; break; case HB_AMIXDOWN_STEREO: ff_layout = AV_CH_LAYOUT_STEREO; break; case HB_AMIXDOWN_5POINT1: ff_layout = AV_CH_LAYOUT_5POINT1; break; case HB_AMIXDOWN_6POINT1: ff_layout = AV_CH_LAYOUT_6POINT1; break; case HB_AMIXDOWN_7POINT1: ff_layout = AV_CH_LAYOUT_7POINT1; break; case HB_AMIXDOWN_5_2_LFE: ff_layout = (AV_CH_LAYOUT_5POINT1_BACK| AV_CH_FRONT_LEFT_OF_CENTER| AV_CH_FRONT_RIGHT_OF_CENTER); break; default: ff_layout = AV_CH_LAYOUT_STEREO; hb_log("hb_ff_mixdown_xlat: unsupported mixdown %d", hb_mixdown); break; } if (downmix_mode != NULL) *downmix_mode = mode; return ff_layout; } /* * Set sample format to the request format if supported by the codec. * The planar/packed variant of the requested format is the next best thing. */ void hb_ff_set_sample_fmt(AVCodecContext *context, AVCodec *codec, enum AVSampleFormat request_sample_fmt) { if (context != NULL && codec != NULL && codec->type == AVMEDIA_TYPE_AUDIO && codec->sample_fmts != NULL) { const enum AVSampleFormat *fmt; enum AVSampleFormat next_best_fmt; next_best_fmt = (av_sample_fmt_is_planar(request_sample_fmt) ? av_get_packed_sample_fmt(request_sample_fmt) : av_get_planar_sample_fmt(request_sample_fmt)); context->request_sample_fmt = AV_SAMPLE_FMT_NONE; for (fmt = codec->sample_fmts; *fmt != AV_SAMPLE_FMT_NONE; fmt++) { if (*fmt == request_sample_fmt) { context->request_sample_fmt = request_sample_fmt; break; } else if (*fmt == next_best_fmt) { context->request_sample_fmt = next_best_fmt; } } /* * When encoding and AVCodec.sample_fmts exists, avcodec_open2() * will error out if AVCodecContext.sample_fmt isn't set. */ if (context->request_sample_fmt == AV_SAMPLE_FMT_NONE) { context->request_sample_fmt = codec->sample_fmts[0]; } context->sample_fmt = context->request_sample_fmt; } } /** * Registers work objects, by adding the work object to a liked list. * @param w Handle to hb_work_object_t to register. */ void hb_register( hb_work_object_t * w ) { w->next = hb_objects; hb_objects = w; } void (*hb_log_callback)(const char* message); static void redirect_thread_func(void *); #if defined( SYS_MINGW ) #define pipe(phandles) _pipe (phandles, 4096, _O_BINARY) #endif /** * Registers the given function as a logger. All logs will be passed to it. * @param log_cb The function to register as a logger. */ void hb_register_logger( void (*log_cb)(const char* message) ) { hb_log_callback = log_cb; hb_thread_init("ioredirect", redirect_thread_func, NULL, HB_NORMAL_PRIORITY); } /** * libhb initialization routine. * @param verbose HB_DEBUG_NONE or HB_DEBUG_ALL. * @param update_check signals libhb to check for updated version from HandBrake website. * @return Handle to hb_handle_t for use on all subsequent calls to libhb. */ hb_handle_t * hb_init( int verbose, int update_check ) { hb_handle_t * h = calloc( sizeof( hb_handle_t ), 1 ); uint64_t date; /* See hb_deep_log() and hb_log() in common.c */ global_verbosity_level = verbose; if( verbose ) putenv( "HB_DEBUG=1" ); h->id = hb_instance_counter++; /* Check for an update on the website if asked to */ h->build = -1; /* Initialize opaque for PowerManagement purposes */ h->system_sleep_opaque = hb_system_sleep_opaque_init(); if( update_check ) { hb_log( "hb_init: checking for updates" ); date = hb_get_date(); h->update_thread = hb_update_init( &h->build, h->version ); for( ;; ) { if( hb_thread_has_exited( h->update_thread ) ) { /* Immediate success or failure */ hb_thread_close( &h->update_thread ); break; } if( hb_get_date() > date + 1000 ) { /* Still nothing after one second. Connection problem, let the thread die */ hb_log( "hb_init: connection problem, not waiting for " "update_thread" ); break; } hb_snooze( 500 ); } } /* * Initialise buffer pool */ hb_buffer_pool_init(); h->title_set.list_title = hb_list_init(); h->jobs = hb_list_init(); h->state_lock = hb_lock_init(); h->state.state = HB_STATE_IDLE; h->pause_lock = hb_lock_init(); h->interjob = calloc( sizeof( hb_interjob_t ), 1 ); /* Start library thread */ hb_log( "hb_init: starting libhb thread" ); h->die = 0; h->main_thread = hb_thread_init( "libhb", thread_func, h, HB_NORMAL_PRIORITY ); return h; } /** * libhb initialization routine. * This version is to use when calling the dylib, the macro hb_init isn't available from a dylib call! * @param verbose HB_DEBUG_NONE or HB_DEBUG_ALL. * @param update_check signals libhb to check for updated version from HandBrake website. * @return Handle to hb_handle_t for use on all subsequent calls to libhb. */ hb_handle_t * hb_init_dl( int verbose, int update_check ) { hb_handle_t * h = calloc( sizeof( hb_handle_t ), 1 ); uint64_t date; /* See hb_log() in common.c */ if( verbose > HB_DEBUG_NONE ) { putenv( "HB_DEBUG=1" ); } h->id = hb_instance_counter++; /* Check for an update on the website if asked to */ h->build = -1; /* Initialize opaque for PowerManagement purposes */ h->system_sleep_opaque = hb_system_sleep_opaque_init(); if( update_check ) { hb_log( "hb_init: checking for updates" ); date = hb_get_date(); h->update_thread = hb_update_init( &h->build, h->version ); for( ;; ) { if( hb_thread_has_exited( h->update_thread ) ) { /* Immediate success or failure */ hb_thread_close( &h->update_thread ); break; } if( hb_get_date() > date + 1000 ) { /* Still nothing after one second. Connection problem, let the thread die */ hb_log( "hb_init: connection problem, not waiting for " "update_thread" ); break; } hb_snooze( 500 ); } } h->title_set.list_title = hb_list_init(); h->jobs = hb_list_init(); h->current_job = NULL; h->state_lock = hb_lock_init(); h->state.state = HB_STATE_IDLE; h->pause_lock = hb_lock_init(); /* Start library thread */ hb_log( "hb_init: starting libhb thread" ); h->die = 0; h->main_thread = hb_thread_init( "libhb", thread_func, h, HB_NORMAL_PRIORITY ); return h; } /** * Returns current version of libhb. * @param h Handle to hb_handle_t. * @return character array of version number. */ char * hb_get_version( hb_handle_t * h ) { return HB_PROJECT_VERSION; } /** * Returns current build of libhb. * @param h Handle to hb_handle_t. * @return character array of build number. */ int hb_get_build( hb_handle_t * h ) { return HB_PROJECT_BUILD; } /** * Checks for needed update. * @param h Handle to hb_handle_t. * @param version Pointer to handle where version will be copied. * @return update indicator. */ int hb_check_update( hb_handle_t * h, char ** version ) { *version = ( h->build < 0 ) ? NULL : h->version; return h->build; } /** * Deletes current previews associated with titles * @param h Handle to hb_handle_t */ void hb_remove_previews( hb_handle_t * h ) { char filename[1024]; char dirname[1024]; hb_title_t * title; int i, count, len; DIR * dir; struct dirent * entry; memset( dirname, 0, 1024 ); hb_get_temporary_directory( dirname ); dir = opendir( dirname ); if (dir == NULL) return; count = hb_list_count( h->title_set.list_title ); while( ( entry = readdir( dir ) ) ) { if( entry->d_name[0] == '.' ) { continue; } for( i = 0; i < count; i++ ) { title = hb_list_item( h->title_set.list_title, i ); len = snprintf( filename, 1024, "%d_%d", h->id, title->index ); if (strncmp(entry->d_name, filename, len) == 0) { snprintf( filename, 1024, "%s/%s", dirname, entry->d_name ); unlink( filename ); break; } } } closedir( dir ); } /** * Initializes a scan of the by calling hb_scan_init * @param h Handle to hb_handle_t * @param path location of VIDEO_TS folder. * @param title_index Desired title to scan. 0 for all titles. * @param preview_count Number of preview images to generate. * @param store_previews Whether or not to write previews to disk. */ void hb_scan( hb_handle_t * h, const char * path, int title_index, int preview_count, int store_previews, uint64_t min_duration ) { hb_title_t * title; h->scan_die = 0; /* Clean up from previous scan */ hb_remove_previews( h ); while( ( title = hb_list_item( h->title_set.list_title, 0 ) ) ) { hb_list_rem( h->title_set.list_title, title ); hb_title_close( &title ); } /* Print CPU info here so that it's in all scan and encode logs */ const char *cpu_name = hb_get_cpu_name(); const char *cpu_type = hb_get_cpu_platform_name(); hb_log("CPU: %s", cpu_name != NULL ? cpu_name : ""); if (cpu_type != NULL) { hb_log(" - %s", cpu_type); } hb_log(" - logical processor count: %d", hb_get_cpu_count()); /* Print OpenCL info here so that it's in all scan and encode logs */ hb_opencl_info_print(); #ifdef USE_QSV /* Print QSV info here so that it's in all scan and encode logs */ hb_qsv_info_print(); #endif hb_log( "hb_scan: path=%s, title_index=%d", path, title_index ); h->scan_thread = hb_scan_init( h, &h->scan_die, path, title_index, &h->title_set, preview_count, store_previews, min_duration ); } /** * Returns the list of titles found. * @param h Handle to hb_handle_t * @return Handle to hb_list_t of the title list. */ hb_list_t * hb_get_titles( hb_handle_t * h ) { return h->title_set.list_title; } hb_title_set_t * hb_get_title_set( hb_handle_t * h ) { return &h->title_set; } int hb_save_preview( hb_handle_t * h, int title, int preview, hb_buffer_t *buf ) { FILE * file; char filename[1024]; hb_get_tempory_filename( h, filename, "%d_%d_%d", hb_get_instance_id(h), title, preview ); file = hb_fopen(filename, "wb"); if( !file ) { hb_error( "hb_save_preview: fopen failed (%s)", filename ); return -1; } int pp, hh; for( pp = 0; pp < 3; pp++ ) { uint8_t *data = buf->plane[pp].data; int stride = buf->plane[pp].stride; int w = buf->plane[pp].width; int h = buf->plane[pp].height; for( hh = 0; hh < h; hh++ ) { fwrite( data, w, 1, file ); data += stride; } } fclose( file ); return 0; } hb_buffer_t * hb_read_preview(hb_handle_t * h, hb_title_t *title, int preview) { FILE * file; char filename[1024]; hb_get_tempory_filename(h, filename, "%d_%d_%d", hb_get_instance_id(h), title->index, preview); file = hb_fopen(filename, "rb"); if (!file) { hb_error( "hb_read_preview: fopen failed (%s)", filename ); return NULL; } hb_buffer_t * buf; buf = hb_frame_buffer_init(AV_PIX_FMT_YUV420P, title->geometry.width, title->geometry.height); int pp, hh; for (pp = 0; pp < 3; pp++) { uint8_t *data = buf->plane[pp].data; int stride = buf->plane[pp].stride; int w = buf->plane[pp].width; int h = buf->plane[pp].height; for (hh = 0; hh < h; hh++) { fread(data, w, 1, file); data += stride; } } fclose(file); return buf; } hb_image_t* hb_get_preview2(hb_handle_t * h, int title_idx, int picture, hb_geometry_settings_t *geo, int deinterlace) { char filename[1024]; hb_buffer_t * in_buf, * deint_buf = NULL, * preview_buf; uint32_t swsflags; AVPicture pic_in, pic_preview, pic_deint, pic_crop; struct SwsContext * context; int width = geo->geometry.width * geo->geometry.par.num / geo->geometry.par.den; int height = geo->geometry.height; swsflags = SWS_LANCZOS | SWS_ACCURATE_RND; preview_buf = hb_frame_buffer_init(AV_PIX_FMT_RGB32, width, height); hb_avpicture_fill( &pic_preview, preview_buf ); // Allocate the AVPicture frames and fill in memset( filename, 0, 1024 ); hb_title_t * title; title = hb_find_title_by_index(h, title_idx); if (title == NULL) { hb_error( "hb_get_preview2: invalid title (%d)", title_idx ); return NULL; } in_buf = hb_read_preview( h, title, picture ); if ( in_buf == NULL ) { return NULL; } hb_avpicture_fill( &pic_in, in_buf ); if (deinterlace) { // Deinterlace and crop deint_buf = hb_frame_buffer_init( AV_PIX_FMT_YUV420P, title->geometry.width, title->geometry.height ); hb_deinterlace(deint_buf, in_buf); hb_avpicture_fill( &pic_deint, deint_buf ); av_picture_crop(&pic_crop, &pic_deint, AV_PIX_FMT_YUV420P, geo->crop[0], geo->crop[2] ); } else { // Crop av_picture_crop(&pic_crop, &pic_in, AV_PIX_FMT_YUV420P, geo->crop[0], geo->crop[2] ); } // Get scaling context context = hb_sws_get_context( title->geometry.width - (geo->crop[2] + geo->crop[3]), title->geometry.height - (geo->crop[0] + geo->crop[1]), AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGB32, swsflags); // Scale sws_scale(context, (const uint8_t* const *)pic_crop.data, pic_crop.linesize, 0, title->geometry.height - (geo->crop[0] + geo->crop[1]), pic_preview.data, pic_preview.linesize); // Free context sws_freeContext( context ); hb_image_t *image = hb_buffer_to_image(preview_buf); // Clean up hb_buffer_close( &in_buf ); hb_buffer_close( &deint_buf ); hb_buffer_close( &preview_buf ); return image; } /** * Analyzes a frame to detect interlacing artifacts * and returns true if interlacing (combing) is found. * * Code taken from Thomas Oestreich's 32detect filter * in the Transcode project, with minor formatting changes. * * @param buf An hb_buffer structure holding valid frame data * @param width The frame's width in pixels * @param height The frame's height in pixels * @param color_equal Sensitivity for detecting similar colors * @param color_diff Sensitivity for detecting different colors * @param threshold Sensitivity for flagging planes as combed * @param prog_equal Sensitivity for detecting similar colors on progressive frames * @param prog_diff Sensitivity for detecting different colors on progressive frames * @param prog_threshold Sensitivity for flagging progressive frames as combed */ int hb_detect_comb( hb_buffer_t * buf, int color_equal, int color_diff, int threshold, int prog_equal, int prog_diff, int prog_threshold ) { int j, k, n, off, cc_1, cc_2, cc[3]; // int flag[3] ; // debugging flag uint16_t s1, s2, s3, s4; cc_1 = 0; cc_2 = 0; if ( buf->s.flags & 16 ) { /* Frame is progressive, be more discerning. */ color_diff = prog_diff; color_equal = prog_equal; threshold = prog_threshold; } /* One pas for Y, one pass for Cb, one pass for Cr */ for( k = 0; k < 3; k++ ) { uint8_t * data = buf->plane[k].data; int width = buf->plane[k].width; int stride = buf->plane[k].stride; int height = buf->plane[k].height; for( j = 0; j < width; ++j ) { off = 0; for( n = 0; n < ( height - 4 ); n = n + 2 ) { /* Look at groups of 4 sequential horizontal lines */ s1 = ( ( data )[ off + j ] & 0xff ); s2 = ( ( data )[ off + j + stride ] & 0xff ); s3 = ( ( data )[ off + j + 2 * stride ] & 0xff ); s4 = ( ( data )[ off + j + 3 * stride ] & 0xff ); /* Note if the 1st and 2nd lines are more different in color than the 1st and 3rd lines are similar in color.*/ if ( ( abs( s1 - s3 ) < color_equal ) && ( abs( s1 - s2 ) > color_diff ) ) ++cc_1; /* Note if the 2nd and 3rd lines are more different in color than the 2nd and 4th lines are similar in color.*/ if ( ( abs( s2 - s4 ) < color_equal ) && ( abs( s2 - s3 ) > color_diff) ) ++cc_2; /* Now move down 2 horizontal lines before starting over.*/ off += 2 * stride; } } // compare results /* The final cc score for a plane is the percentage of combed pixels it contains. Because sensitivity goes down to hundreths of a percent, multiply by 1000 so it will be easy to compare against the threhold value which is an integer. */ cc[k] = (int)( ( cc_1 + cc_2 ) * 1000.0 / ( width * height ) ); } /* HandBrake is all yuv420, so weight the average percentage of all 3 planes accordingly.*/ int average_cc = ( 2 * cc[0] + ( cc[1] / 2 ) + ( cc[2] / 2 ) ) / 3; /* Now see if that average percentage of combed pixels surpasses the threshold percentage given by the user.*/ if( average_cc > threshold ) { #if 0 hb_log("Average %i combed (Threshold %i) %i/%i/%i | PTS: %"PRId64" (%fs) %s", average_cc, threshold, cc[0], cc[1], cc[2], buf->start, (float)buf->start / 90000, (buf->flags & 16) ? "Film" : "Video" ); #endif return 1; } #if 0 hb_log("SKIPPED Average %i combed (Threshold %i) %i/%i/%i | PTS: %"PRId64" (%fs) %s", average_cc, threshold, cc[0], cc[1], cc[2], buf->start, (float)buf->start / 90000, (buf->flags & 16) ? "Film" : "Video" ); #endif /* Reaching this point means no combing detected. */ return 0; } /** * Calculates destination width and height for anamorphic content * * Returns calculated geometry * @param source_geometry - Pointer to source geometry info * @param geometry - Pointer to requested destination parameters */ void hb_set_anamorphic_size2(hb_geometry_t *src_geo, hb_geometry_settings_t *geo, hb_geometry_t *result) { hb_rational_t in_par, out_par; int keep_display_aspect = !!(geo->keep & HB_KEEP_DISPLAY_ASPECT); int keep_height = !!(geo->keep & HB_KEEP_HEIGHT); /* Set up some variables to make the math easier to follow. */ int cropped_width = src_geo->width - geo->crop[2] - geo->crop[3]; int cropped_height = src_geo->height - geo->crop[0] - geo->crop[1]; double storage_aspect = (double)cropped_width / cropped_height; int mod = geo->modulus ? EVEN(geo->modulus) : 2; // Use 64 bits to avoid overflow till the final hb_reduce() call hb_reduce(&in_par.num, &in_par.den, geo->geometry.par.num, geo->geometry.par.den); int64_t dst_par_num = in_par.num; int64_t dst_par_den = in_par.den; hb_rational_t src_par = src_geo->par; /* If a source was really NTSC or PAL and the user specified ITU PAR values, replace the standard PAR values with the ITU broadcast ones. */ if (src_geo->width == 720 && geo->itu_par) { // convert aspect to a scaled integer so we can test for 16:9 & 4:3 // aspect ratios ignoring insignificant differences in the LSBs of // the floating point representation. int iaspect = src_geo->width * src_par.num * 9. / (src_geo->height * src_par.den); /* Handle ITU PARs */ if (src_geo->height == 480) { /* It's NTSC */ if (iaspect == 16) { /* It's widescreen */ dst_par_num = 40; dst_par_den = 33; } else if (iaspect == 12) { /* It's 4:3 */ dst_par_num = 10; dst_par_den = 11; } } else if (src_geo->height == 576) { /* It's PAL */ if (iaspect == 16) { /* It's widescreen */ dst_par_num = 16; dst_par_den = 11; } else if (iaspect == 12) { /* It's 4:3 */ dst_par_num = 12; dst_par_den = 11; } } } /* 3 different ways of deciding output dimensions: - 1: Strict anamorphic, preserve source dimensions - 2: Loose anamorphic, round to mod16 and preserve storage aspect ratio - 3: Power user anamorphic, specify everything */ int width, height; int maxWidth, maxHeight; maxWidth = MULTIPLE_MOD_DOWN(geo->maxWidth, mod); maxHeight = MULTIPLE_MOD_DOWN(geo->maxHeight, mod); if (maxWidth && maxWidth < 32) maxWidth = 32; if (maxHeight && maxHeight < 32) maxHeight = 32; switch (geo->mode) { case HB_ANAMORPHIC_NONE: { double par, cropped_sar, dar; par = (double)src_geo->par.num / src_geo->par.den; cropped_sar = (double)cropped_width / cropped_height; dar = par * cropped_sar; /* "None" anamorphic. a.k.a. non-anamorphic * - Uses mod-compliant dimensions, set by user * - Allows users to set the either width *or* height */ if (keep_display_aspect) { if (!keep_height) { width = MULTIPLE_MOD_UP(geo->geometry.width, mod); height = MULTIPLE_MOD(width / dar, mod); } else { height = MULTIPLE_MOD_UP(geo->geometry.height, mod); width = MULTIPLE_MOD(height * dar, mod); } } else { width = MULTIPLE_MOD_UP(geo->geometry.width, mod); height = MULTIPLE_MOD_UP(geo->geometry.height, mod); } if (maxWidth && (width > maxWidth)) { width = maxWidth; height = MULTIPLE_MOD(width / dar, mod); } if (maxHeight && (height > maxHeight)) { height = maxHeight; width = MULTIPLE_MOD(height * dar, mod); } dst_par_num = dst_par_den = 1; } break; default: case HB_ANAMORPHIC_STRICT: { /* "Strict" anamorphic. * - Uses mod2-compliant dimensions, * - Forces title - crop dimensions */ width = MULTIPLE_MOD_UP(cropped_width, 2); height = MULTIPLE_MOD_UP(cropped_height, 2); /* Adjust the output PAR for new width/height * Film AR is the source display width / cropped source height. * Output display width is the output height * film AR. * Output PAR is the output display width / output storage width. * * i.e. * source_display_width = cropped_width * source PAR * AR = source_display_width / cropped_height; * output_display_width = height * AR; * par = output_display_width / width; * * When these terms are reduced, you get the following... */ dst_par_num = (int64_t)height * cropped_width * src_par.num; dst_par_den = (int64_t)width * cropped_height * src_par.den; } break; case HB_ANAMORPHIC_LOOSE: { /* "Loose" anamorphic. * - Uses mod-compliant dimensions, set by user * - Allows users to set the either width *or* height */ if (!keep_height) { width = MULTIPLE_MOD_UP(geo->geometry.width, mod); height = MULTIPLE_MOD_UP(width / storage_aspect + 0.5, mod); } else { height = MULTIPLE_MOD_UP(geo->geometry.height, mod); width = MULTIPLE_MOD_UP(height * storage_aspect + 0.5, mod); } if (maxWidth && (maxWidth < width)) { width = maxWidth; height = MULTIPLE_MOD(width / storage_aspect + 0.5, mod); } if (maxHeight && (maxHeight < height)) { height = maxHeight; width = MULTIPLE_MOD(height * storage_aspect + 0.5, mod); } /* Adjust the output PAR for new width/height See comment in HB_ANAMORPHIC_STRICT */ dst_par_num = (int64_t)height * cropped_width * src_par.num; dst_par_den = (int64_t)width * cropped_height * src_par.den; } break; case HB_ANAMORPHIC_CUSTOM: { /* Anamorphic 3: Power User Jamboree - Set everything based on specified values */ /* Use specified storage dimensions */ storage_aspect = (double)geo->geometry.width / geo->geometry.height; /* Time to get picture dimensions that divide cleanly.*/ width = MULTIPLE_MOD_UP(geo->geometry.width, mod); height = MULTIPLE_MOD_UP(geo->geometry.height, mod); /* Bind to max dimensions */ if (maxWidth && width > maxWidth) { width = maxWidth; // If we are keeping the display aspect, then we are going // to be modifying the PAR anyway. So it's preferred // to let the width/height stray some from the original // requested storage aspect. // // But otherwise, PAR and DAR will change the least // if we stay as close as possible to the requested // storage aspect. if (!keep_display_aspect) { height = width / storage_aspect + 0.5; height = MULTIPLE_MOD(height, mod); } } if (maxHeight && height > maxHeight) { height = maxHeight; // Ditto, see comment above if (!keep_display_aspect) { width = height * storage_aspect + 0.5; width = MULTIPLE_MOD(width, mod); } } if (keep_display_aspect) { /* We can ignore the possibility of a PAR change * Adjust the output PAR for new width/height * See comment in HB_ANAMORPHIC_STRICT */ dst_par_num = (int64_t)height * cropped_width * src_par.num; dst_par_den = (int64_t)width * cropped_height * src_par.den; } else { /* If the dimensions were changed by the modulus * or by maxWidth/maxHeight, we also change the * output PAR so that the DAR is unchanged. * * PAR is the requested output display width / storage width * requested output display width is the original * requested width * original requested PAR */ dst_par_num = geo->geometry.width * dst_par_num; dst_par_den = width * dst_par_den; } } break; } /* Pass the results back to the caller */ result->width = width; result->height = height; /* While x264 is smart enough to reduce fractions on its own, libavcodec * needs some help with the math, so lose superfluous factors. */ hb_limit_rational64(&dst_par_num, &dst_par_den, dst_par_num, dst_par_den, 65535); /* If the user is directling updating PAR, don't override his values */ hb_reduce(&out_par.num, &out_par.den, dst_par_num, dst_par_den); if (geo->mode == HB_ANAMORPHIC_CUSTOM && !keep_display_aspect && out_par.num == in_par.num && out_par.den == in_par.den) { result->par.num = geo->geometry.par.num; result->par.den = geo->geometry.par.den; } else { hb_reduce(&result->par.num, &result->par.den, dst_par_num, dst_par_den); } } /** * Add a filter to a jobs filter list * * @param job Handle to hb_job_t * @param settings to give the filter */ void hb_add_filter( hb_job_t * job, hb_filter_object_t * filter, const char * settings_in ) { char * settings = NULL; if ( settings_in != NULL ) { settings = strdup( settings_in ); } filter->settings = settings; if( filter->enforce_order ) { // Find the position in the filter chain this filter belongs in int i; for( i = 0; i < hb_list_count( job->list_filter ); i++ ) { hb_filter_object_t * f = hb_list_item( job->list_filter, i ); if( f->id > filter->id ) { hb_list_insert( job->list_filter, i, filter ); return; } else if( f->id == filter->id ) { // Don't allow the same filter to be added twice hb_filter_close( &filter ); return; } } } // No position found or order not enforced for this filter hb_list_add( job->list_filter, filter ); } /** * Returns the number of jobs in the queue. * @param h Handle to hb_handle_t. * @return Number of jobs. */ int hb_count( hb_handle_t * h ) { return hb_list_count( h->jobs ); } /** * Returns handle to job at index i within the job list. * @param h Handle to hb_handle_t. * @param i Index of job. * @returns Handle to hb_job_t of desired job. */ hb_job_t * hb_job( hb_handle_t * h, int i ) { return hb_list_item( h->jobs, i ); } hb_job_t * hb_current_job( hb_handle_t * h ) { return( h->current_job ); } /** * Adds a job to the job list. * @param h Handle to hb_handle_t. * @param job Handle to hb_job_t. */ void hb_add( hb_handle_t * h, hb_job_t * job ) { hb_job_t * job_copy; hb_audio_t * audio; hb_subtitle_t * subtitle; int i; char audio_lang[4]; /* Copy the job */ job_copy = calloc( sizeof( hb_job_t ), 1 ); memcpy( job_copy, job, sizeof( hb_job_t ) ); /* If we're doing Foreign Audio Search, copy all subtitles matching the * first audio track language we find in the audio list. * * Otherwise, copy all subtitles found in the input job (which can be * manually selected by the user, or added after the Foreign Audio * Search pass). */ memset( audio_lang, 0, sizeof( audio_lang ) ); if( job->indepth_scan ) { /* Find the first audio language that is being encoded, then add all the * matching subtitles for that language. */ for( i = 0; i < hb_list_count( job->list_audio ); i++ ) { if( ( audio = hb_list_item( job->list_audio, i ) ) ) { strncpy( audio_lang, audio->config.lang.iso639_2, sizeof( audio_lang ) ); break; } } /* * If doing a subtitle scan then add all the matching subtitles for this * language. */ job_copy->list_subtitle = hb_list_init(); for( i = 0; i < hb_list_count( job->title->list_subtitle ); i++ ) { subtitle = hb_list_item( job->title->list_subtitle, i ); if( strcmp( subtitle->iso639_2, audio_lang ) == 0 && hb_subtitle_can_force( subtitle->source ) ) { /* Matched subtitle language with audio language, so add this to * our list to scan. * * We will update the subtitle list on the next pass later, after * the subtitle scan pass has completed. */ hb_list_add( job_copy->list_subtitle, hb_subtitle_copy( subtitle ) ); } } } else { /* Copy all subtitles from the input job to title_copy/job_copy. */ job_copy->list_subtitle = hb_subtitle_list_copy( job->list_subtitle ); } job_copy->list_chapter = hb_chapter_list_copy( job->list_chapter ); job_copy->list_audio = hb_audio_list_copy( job->list_audio ); job_copy->list_attachment = hb_attachment_list_copy( job->list_attachment ); job_copy->metadata = hb_metadata_copy( job->metadata ); if (job->encoder_preset != NULL) job_copy->encoder_preset = strdup(job->encoder_preset); if (job->encoder_tune != NULL) job_copy->encoder_tune = strdup(job->encoder_tune); if (job->encoder_options != NULL) job_copy->encoder_options = strdup(job->encoder_options); if (job->encoder_profile != NULL) job_copy->encoder_profile = strdup(job->encoder_profile); if (job->encoder_level != NULL) job_copy->encoder_level = strdup(job->encoder_level); if (job->file != NULL) job_copy->file = strdup(job->file); job_copy->h = h; job_copy->pause = h->pause_lock; /* Copy the job filter list */ job_copy->list_filter = hb_filter_list_copy( job->list_filter ); /* Add the job to the list */ hb_list_add( h->jobs, job_copy ); h->job_count = hb_count(h); h->job_count_permanent++; } /** * Removes a job from the job list. * @param h Handle to hb_handle_t. * @param job Handle to hb_job_t. */ void hb_rem( hb_handle_t * h, hb_job_t * job ) { hb_list_rem( h->jobs, job ); h->job_count = hb_count(h); if (h->job_count_permanent) h->job_count_permanent--; /* XXX free everything XXX */ } /** * Starts the conversion process. * Sets state to HB_STATE_WORKING. * calls hb_work_init, to launch work thread. Stores handle to work thread. * @param h Handle to hb_handle_t. */ void hb_start( hb_handle_t * h ) { /* XXX Hack */ h->job_count = hb_list_count( h->jobs ); h->job_count_permanent = h->job_count; hb_lock( h->state_lock ); h->state.state = HB_STATE_WORKING; #define p h->state.param.working p.progress = 0.0; p.job_cur = 1; p.job_count = h->job_count; p.rate_cur = 0.0; p.rate_avg = 0.0; p.hours = -1; p.minutes = -1; p.seconds = -1; p.sequence_id = 0; #undef p hb_unlock( h->state_lock ); h->paused = 0; h->work_die = 0; h->work_error = HB_ERROR_NONE; h->work_thread = hb_work_init( h->jobs, &h->work_die, &h->work_error, &h->current_job ); } /** * Pauses the conversion process. * @param h Handle to hb_handle_t. */ void hb_pause( hb_handle_t * h ) { if( !h->paused ) { hb_lock( h->pause_lock ); h->paused = 1; hb_current_job( h )->st_pause_date = hb_get_date(); hb_lock( h->state_lock ); h->state.state = HB_STATE_PAUSED; hb_unlock( h->state_lock ); } } /** * Resumes the conversion process. * @param h Handle to hb_handle_t. */ void hb_resume( hb_handle_t * h ) { if( h->paused ) { #define job hb_current_job( h ) if( job->st_pause_date != -1 ) { job->st_paused += hb_get_date() - job->st_pause_date; } #undef job hb_unlock( h->pause_lock ); h->paused = 0; } } /** * Stops the conversion process. * @param h Handle to hb_handle_t. */ void hb_stop( hb_handle_t * h ) { h->work_die = 1; h->job_count = hb_count(h); h->job_count_permanent = 0; hb_resume( h ); } /** * Stops the conversion process. * @param h Handle to hb_handle_t. */ void hb_scan_stop( hb_handle_t * h ) { h->scan_die = 1; h->job_count = hb_count(h); h->job_count_permanent = 0; hb_resume( h ); } /** * Returns the state of the conversion process. * @param h Handle to hb_handle_t. * @param s Handle to hb_state_t which to copy the state data. */ void hb_get_state( hb_handle_t * h, hb_state_t * s ) { hb_lock( h->state_lock ); memcpy( s, &h->state, sizeof( hb_state_t ) ); if ( h->state.state == HB_STATE_SCANDONE || h->state.state == HB_STATE_WORKDONE ) h->state.state = HB_STATE_IDLE; hb_unlock( h->state_lock ); } void hb_get_state2( hb_handle_t * h, hb_state_t * s ) { hb_lock( h->state_lock ); memcpy( s, &h->state, sizeof( hb_state_t ) ); hb_unlock( h->state_lock ); } /** * Called in MacGui in UpdateUI to check * for a new scan being completed to set a new source */ int hb_get_scancount( hb_handle_t * h) { return h->scanCount; } /** * Closes access to libhb by freeing the hb_handle_t handle ontained in hb_init. * @param _h Pointer to handle to hb_handle_t. */ void hb_close( hb_handle_t ** _h ) { hb_handle_t * h = *_h; hb_title_t * title; h->die = 1; hb_thread_close( &h->main_thread ); while( ( title = hb_list_item( h->title_set.list_title, 0 ) ) ) { hb_list_rem( h->title_set.list_title, title ); hb_title_close( &title ); } hb_list_close( &h->title_set.list_title ); hb_list_close( &h->jobs ); hb_lock_close( &h->state_lock ); hb_lock_close( &h->pause_lock ); hb_system_sleep_opaque_close(&h->system_sleep_opaque); free( h->interjob ); free( h ); *_h = NULL; } int hb_global_init() { int result = 0; result = hb_platform_init(); if (result < 0) { hb_error("Platform specific initialization failed!"); return -1; } #ifdef USE_QSV result = hb_qsv_info_init(); if (result < 0) { hb_error("hb_qsv_info_init failed!"); return -1; } #endif /* libavcodec */ hb_avcodec_init(); /* HB work objects */ hb_register(&hb_muxer); hb_register(&hb_reader); hb_register(&hb_sync_video); hb_register(&hb_sync_audio); hb_register(&hb_decavcodecv); hb_register(&hb_decavcodeca); hb_register(&hb_declpcm); hb_register(&hb_deccc608); hb_register(&hb_decpgssub); hb_register(&hb_decsrtsub); hb_register(&hb_decssasub); hb_register(&hb_dectx3gsub); hb_register(&hb_decutf8sub); hb_register(&hb_decvobsub); hb_register(&hb_encvobsub); hb_register(&hb_encavcodec); hb_register(&hb_encavcodeca); #ifdef __APPLE__ hb_register(&hb_encca_aac); hb_register(&hb_encca_haac); #endif hb_register(&hb_enclame); hb_register(&hb_enctheora); hb_register(&hb_encvorbis); hb_register(&hb_encx264); #ifdef USE_X265 hb_register(&hb_encx265); #endif #ifdef USE_QSV hb_register(&hb_encqsv); #endif hb_common_global_init(); return result; } /** * Cleans up libhb at a process level. Call before the app closes. Removes preview directory. */ void hb_global_close() { char dirname[1024]; DIR * dir; struct dirent * entry; /* Find and remove temp folder */ memset( dirname, 0, 1024 ); hb_get_temporary_directory( dirname ); dir = opendir( dirname ); if (dir) { while( ( entry = readdir( dir ) ) ) { char filename[1024]; if( entry->d_name[0] == '.' ) { continue; } memset( filename, 0, 1024 ); snprintf( filename, 1023, "%s/%s", dirname, entry->d_name ); unlink( filename ); } closedir( dir ); rmdir( dirname ); } } /** * Monitors the state of the update, scan, and work threads. * Sets scan done state when scan thread exits. * Sets work done state when work thread exits. * @param _h Handle to hb_handle_t */ static void thread_func( void * _h ) { hb_handle_t * h = (hb_handle_t *) _h; char dirname[1024]; h->pid = getpid(); /* Create folder for temporary files */ memset( dirname, 0, 1024 ); hb_get_temporary_directory( dirname ); hb_mkdir( dirname ); while( !h->die ) { /* In case the check_update thread hangs, it'll die sooner or later. Then, we join it here */ if( h->update_thread && hb_thread_has_exited( h->update_thread ) ) { hb_thread_close( &h->update_thread ); } /* Check if the scan thread is done */ if( h->scan_thread && hb_thread_has_exited( h->scan_thread ) ) { hb_thread_close( &h->scan_thread ); if ( h->scan_die ) { hb_title_t * title; hb_remove_previews( h ); while( ( title = hb_list_item( h->title_set.list_title, 0 ) ) ) { hb_list_rem( h->title_set.list_title, title ); hb_title_close( &title ); } hb_log( "hb_scan: canceled" ); } else { hb_log( "libhb: scan thread found %d valid title(s)", hb_list_count( h->title_set.list_title ) ); } hb_lock( h->state_lock ); h->state.state = HB_STATE_SCANDONE; //originally state.state hb_unlock( h->state_lock ); /*we increment this sessions scan count by one for the MacGui to trigger a new source being set */ h->scanCount++; } /* Check if the work thread is done */ if( h->work_thread && hb_thread_has_exited( h->work_thread ) ) { hb_thread_close( &h->work_thread ); hb_log( "libhb: work result = %d", h->work_error ); hb_lock( h->state_lock ); h->state.state = HB_STATE_WORKDONE; h->state.param.workdone.error = h->work_error; h->job_count = hb_count(h); if (h->job_count < 1) h->job_count_permanent = 0; hb_unlock( h->state_lock ); } hb_snooze( 50 ); } if( h->scan_thread ) { hb_scan_stop( h ); hb_thread_close( &h->scan_thread ); } if( h->work_thread ) { hb_stop( h ); hb_thread_close( &h->work_thread ); } hb_remove_previews( h ); } /** * Redirects stderr to the registered callback * function. * @param _data Unused. */ static void redirect_thread_func(void * _data) { int pfd[2]; pipe(pfd); #if defined( SYS_MINGW ) // dup2 doesn't work on windows for some stupid reason stderr->_file = pfd[1]; #else dup2(pfd[1], /*stderr*/ 2); #endif FILE * log_f = fdopen(pfd[0], "rb"); char line_buffer[500]; while(fgets(line_buffer, 500, log_f) != NULL) { hb_log_callback(line_buffer); } } /** * Returns the PID. * @param h Handle to hb_handle_t */ int hb_get_pid( hb_handle_t * h ) { return h->pid; } /** * Returns the id for the given instance. * @param h Handle to hb_handle_t * @returns The ID for the given instance */ int hb_get_instance_id( hb_handle_t * h ) { return h->id; } /** * Sets the current state. * @param h Handle to hb_handle_t * @param s Handle to new hb_state_t */ void hb_set_state( hb_handle_t * h, hb_state_t * s ) { hb_lock( h->pause_lock ); hb_lock( h->state_lock ); memcpy( &h->state, s, sizeof( hb_state_t ) ); if( h->state.state == HB_STATE_WORKING || h->state.state == HB_STATE_SEARCHING ) { /* XXX Hack */ if (h->job_count < 1) h->job_count_permanent = 1; h->state.param.working.job_cur = h->job_count_permanent - hb_list_count( h->jobs ); h->state.param.working.job_count = h->job_count_permanent; // Set which job is being worked on if (h->current_job) h->state.param.working.sequence_id = h->current_job->sequence_id; else h->state.param.working.sequence_id = 0; } hb_unlock( h->state_lock ); hb_unlock( h->pause_lock ); } void hb_system_sleep_allow(hb_handle_t *h) { hb_system_sleep_private_enable(h->system_sleep_opaque); } void hb_system_sleep_prevent(hb_handle_t *h) { hb_system_sleep_private_disable(h->system_sleep_opaque); } /* Passes a pointer to persistent data */ hb_interjob_t * hb_interjob_get( hb_handle_t * h ) { return h->interjob; }