diff options
-rw-r--r-- | gtk/src/callbacks.c | 7 | ||||
-rw-r--r-- | gtk/src/hb-backend.c | 17 | ||||
-rw-r--r-- | gtk/src/hb-backend.h | 2 | ||||
-rw-r--r-- | gtk/src/preview.c | 3 | ||||
-rw-r--r-- | libhb/common.c | 20 | ||||
-rw-r--r-- | libhb/common.h | 11 | ||||
-rw-r--r-- | libhb/decomb.c | 1 | ||||
-rw-r--r-- | libhb/denoise.c | 3 | ||||
-rw-r--r-- | libhb/enc_qsv.c | 2 | ||||
-rw-r--r-- | libhb/encavcodec.c | 26 | ||||
-rw-r--r-- | libhb/enctheora.c | 17 | ||||
-rw-r--r-- | libhb/encx264.c | 30 | ||||
-rw-r--r-- | libhb/encx265.c | 3 | ||||
-rw-r--r-- | libhb/hb.c | 8 | ||||
-rw-r--r-- | libhb/hb.h | 12 | ||||
-rw-r--r-- | libhb/hb_json.c | 4 | ||||
-rw-r--r-- | libhb/internal.h | 7 | ||||
-rw-r--r-- | libhb/muxavformat.c | 11 | ||||
-rw-r--r-- | libhb/muxcommon.c | 275 | ||||
-rw-r--r-- | libhb/reader.c | 657 | ||||
-rw-r--r-- | libhb/scan.c | 2 | ||||
-rw-r--r-- | libhb/stream.c | 2 | ||||
-rw-r--r-- | libhb/sync.c | 79 | ||||
-rw-r--r-- | libhb/work.c | 1465 |
24 files changed, 1311 insertions, 1353 deletions
diff --git a/gtk/src/callbacks.c b/gtk/src/callbacks.c index 897a64985..003bb23a9 100644 --- a/gtk/src/callbacks.c +++ b/gtk/src/callbacks.c @@ -2946,7 +2946,6 @@ start_new_log(signal_user_data_t *ud, GhbValue *js) static void submit_job(signal_user_data_t *ud, GhbValue *settings) { - static gint unique_id = 1; gchar *type, *modified; const char *name; GhbValue *js; @@ -2960,10 +2959,10 @@ submit_job(signal_user_data_t *ud, GhbValue *settings) modified = preset_modified ? "Modified " : ""; ghb_log("%s%sPreset: %s", modified, type, name); - ghb_dict_set_int(settings, "job_unique_id", unique_id); ghb_dict_set_int(settings, "job_status", GHB_QUEUE_RUNNING); start_new_log(ud, settings); - ghb_add_job(ghb_queue_handle(), settings, unique_id); + int unique_id = ghb_add_job(ghb_queue_handle(), settings); + ghb_dict_set_int(settings, "job_unique_id", unique_id); ghb_start_queue(); // Start queue activity spinner @@ -2983,8 +2982,6 @@ submit_job(signal_user_data_t *ud, GhbValue *settings) } g_free(path); } - - unique_id++; } static void diff --git a/gtk/src/hb-backend.c b/gtk/src/hb-backend.c index 88e7a89a1..d6e16d537 100644 --- a/gtk/src/hb-backend.c +++ b/gtk/src/hb-backend.c @@ -3375,7 +3375,7 @@ update_status(hb_state_t *state, ghb_instance_status_t *status) status->hours = p.hours; status->minutes = p.minutes; status->seconds = p.seconds; - status->unique_id = p.sequence_id & 0xFFFFFF; + status->unique_id = p.sequence_id; } else { @@ -3395,7 +3395,7 @@ update_status(hb_state_t *state, ghb_instance_status_t *status) status->hours = p.hours; status->minutes = p.minutes; status->seconds = p.seconds; - status->unique_id = p.sequence_id & 0xFFFFFF; + status->unique_id = p.sequence_id; } else { @@ -4316,18 +4316,19 @@ ghb_validate_audio(GhbValue *settings, GtkWindow *parent) return TRUE; } -void -ghb_add_job(hb_handle_t *h, GhbValue *js, gint unique_id) +int +ghb_add_job(hb_handle_t *h, GhbValue *js) { GhbValue *job; char *json_job; + int sequence_id; job = ghb_dict_get(js, "Job"); - ghb_dict_set_int(job, "SequenceID", unique_id); json_job = hb_value_get_json(job); - - hb_add_json(h, json_job); + sequence_id = hb_add_json(h, json_job); free(json_job); + + return sequence_id; } void @@ -4342,7 +4343,7 @@ ghb_remove_job(gint unique_id) ii = hb_count(h_queue) - 1; while ((job = hb_job(h_queue, ii--)) != NULL) { - if ((job->sequence_id & 0xFFFFFF) == unique_id) + if (job->sequence_id == unique_id) hb_rem(h_queue, job); } } diff --git a/gtk/src/hb-backend.h b/gtk/src/hb-backend.h index 4f2ea310f..b743865cf 100644 --- a/gtk/src/hb-backend.h +++ b/gtk/src/hb-backend.h @@ -91,7 +91,7 @@ void ghb_combo_init(signal_user_data_t *ud); void ghb_backend_init(gint debug); void ghb_log_level_set(int level); void ghb_backend_close(void); -void ghb_add_job(hb_handle_t *h, GhbValue *js, gint unique_id); +int ghb_add_job(hb_handle_t *h, GhbValue *js); void ghb_remove_job(gint unique_id); void ghb_start_queue(void); void ghb_stop_queue(void); diff --git a/gtk/src/preview.c b/gtk/src/preview.c index e567b7af0..e3336b076 100644 --- a/gtk/src/preview.c +++ b/gtk/src/preview.c @@ -799,8 +799,7 @@ live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud) ghb_dict_set_int(range, "SeekPoints", ghb_dict_get_int(ud->prefs, "preview_count")); - ud->preview->live_id = 0; - ghb_add_job(ghb_live_handle(), js, ud->preview->live_id); + ud->preview->live_id = ghb_add_job(ghb_live_handle(), js); ghb_start_live_encode(); ghb_value_free(&js); } diff --git a/libhb/common.c b/libhb/common.c index 47f1b26f9..3e91635be 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -694,6 +694,26 @@ const hb_rate_t* hb_video_framerate_get_next(const hb_rate_t *last) return ((hb_rate_internal_t*)last)->next; } +int hb_video_framerate_get_close(hb_rational_t *framerate, double thresh) +{ + double fps_in; + const hb_rate_t * rate = NULL; + int result = -1; + double closest = thresh; + + fps_in = (double)framerate->num / framerate->den; + while ((rate = hb_video_framerate_get_next(rate)) != NULL) + { + double fps = (double)hb_video_rate_clock / rate->rate; + if (ABS(fps - fps_in) < closest) + { + result = rate->rate; + closest = ABS(fps - fps_in); + } + } + return result; +} + int hb_audio_samplerate_get_best(uint32_t codec, int samplerate, int *sr_shift) { int best_samplerate; diff --git a/libhb/common.h b/libhb/common.h index a78f86475..92d911320 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -58,6 +58,9 @@ #ifndef MAX #define MAX( a, b ) ( (a) > (b) ? (a) : (b) ) #endif +#ifndef ABS +#define ABS(a) ((a) > 0 ? (a) : (-(a))) +#endif #define HB_ALIGN(x, a) (((x)+(a)-1)&~((a)-1)) @@ -365,6 +368,8 @@ const char* hb_video_framerate_get_name(int framerate); const char* hb_video_framerate_sanitize_name(const char *name); void hb_video_framerate_get_limits(int *low, int *high, int *clock); const hb_rate_t* hb_video_framerate_get_next(const hb_rate_t *last); +int hb_video_framerate_get_close(hb_rational_t *framerate, + double thresh); int hb_audio_samplerate_get_best(uint32_t codec, int samplerate, int *sr_shift); int hb_audio_samplerate_get_from_name(const char *name); @@ -531,6 +536,10 @@ struct hb_job_s double vquality; int vbitrate; hb_rational_t vrate; + // Some parameters that depend on vrate (like keyint) can't change + // between encoding passes. So orig_vrate is used to store the + // 1st pass rate. + hb_rational_t orig_vrate; int cfr; PRIVATE int pass_id; int twopass; // Enable 2-pass encode. Boolean @@ -1139,12 +1148,12 @@ struct hb_work_object_s hb_thread_t * thread; int yield; volatile int * done; + volatile int * die; int status; int codec_param; hb_title_t * title; hb_work_object_t * next; - int thread_sleep_interval; hb_handle_t * h; #endif diff --git a/libhb/decomb.c b/libhb/decomb.c index 3eaee3251..1da26adbc 100644 --- a/libhb/decomb.c +++ b/libhb/decomb.c @@ -73,7 +73,6 @@ which will feed EEDI2 interpolations to yadif. #define PARITY_DEFAULT -1 -#define ABS(a) ((a) > 0 ? (a) : (-(a))) #define MIN3(a,b,c) MIN(MIN(a,b),c) #define MAX3(a,b,c) MAX(MAX(a,b),c) diff --git a/libhb/denoise.c b/libhb/denoise.c index 2b6c6c4cf..f66c987be 100644 --- a/libhb/denoise.c +++ b/libhb/denoise.c @@ -24,9 +24,6 @@ #define HQDN3D_SPATIAL_CHROMA_DEFAULT 3.0f #define HQDN3D_TEMPORAL_LUMA_DEFAULT 6.0f -#define ABS(A) ( (A) > 0 ? (A) : -(A) ) -#define MIN( a, b ) ( (a) > (b) ? (b) : (a) ) - struct hb_filter_private_s { short hqdn3d_coef[6][512*16]; diff --git a/libhb/enc_qsv.c b/libhb/enc_qsv.c index 4f99bbc95..360db65b5 100644 --- a/libhb/enc_qsv.c +++ b/libhb/enc_qsv.c @@ -1023,7 +1023,7 @@ int encqsvInit(hb_work_object_t *w, hb_job_t *job) // set the keyframe interval if (pv->param.gop.gop_pic_size < 0) { - int rate = (int)((double)job->vrate.num / (double)job->vrate.den + 0.5); + int rate = (double)job->orig_vrate.num / job->orig_vrate.den + 0.5; if (pv->param.videoParam->mfx.RateControlMethod == MFX_RATECONTROL_CQP) { // ensure B-pyramid is enabled for CQP on Haswell diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c index 134dbcc6b..9cc4193b5 100644 --- a/libhb/encavcodec.c +++ b/libhb/encavcodec.c @@ -102,17 +102,8 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) // Set things in context that we will allow the user to // override with advanced settings. - if( job->pass_id == HB_PASS_ENCODE_2ND ) - { - hb_interjob_t * interjob = hb_interjob_get( job->h ); - fps.den = interjob->vrate.den; - fps.num = interjob->vrate.num; - } - else - { - fps.den = job->vrate.den; - fps.num = job->vrate.num; - } + fps.den = job->vrate.den; + fps.num = job->vrate.num; // If the fps.num is the internal clock rate, there's a good chance // this is a standard rate that we have in our hb_video_rates table. @@ -163,7 +154,8 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) context->time_base.den = fps.num; context->time_base.num = fps.den; - context->gop_size = 10 * ((double)job->vrate.num / job->vrate.den + 0.5); + context->gop_size = ((double)job->orig_vrate.num / job->orig_vrate.den + + 0.5) * 10; /* place job->encoder_options in an hb_dict_t for convenience */ hb_dict_t * lavc_opts = NULL; @@ -234,6 +226,16 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) context->sample_aspect_ratio.num = job->par.num; context->sample_aspect_ratio.den = job->par.den; + if (job->vcodec == HB_VCODEC_FFMPEG_MPEG4) + { + // MPEG-4 Part 2 stores the PAR num/den as unsigned 8-bit fields, + // and libavcodec's encoder fails to initialize if we don't + // reduce it to fit 8-bits. + hb_limit_rational(&context->sample_aspect_ratio.num, + &context->sample_aspect_ratio.den, + context->sample_aspect_ratio.num, + context->sample_aspect_ratio.den, 255); + } hb_log( "encavcodec: encoding with stored aspect %d/%d", job->par.num, job->par.den ); diff --git a/libhb/enctheora.c b/libhb/enctheora.c index a49d7ff6b..376b442ba 100644 --- a/libhb/enctheora.c +++ b/libhb/enctheora.c @@ -71,18 +71,8 @@ int enctheoraInit( hb_work_object_t * w, hb_job_t * job ) ti.frame_width = (job->width + 0xf) & ~0xf; ti.frame_height = (job->height + 0xf) & ~0xf; ti.pic_x = ti.pic_y = 0; - - if( job->pass_id == HB_PASS_ENCODE_2ND ) - { - hb_interjob_t * interjob = hb_interjob_get( job->h ); - ti.fps_numerator = interjob->vrate.num; - ti.fps_denominator = interjob->vrate.den; - } - else - { - ti.fps_numerator = job->vrate.num; - ti.fps_denominator = job->vrate.den; - } + ti.fps_numerator = job->vrate.num; + ti.fps_denominator = job->vrate.den; ti.aspect_numerator = job->par.num; ti.aspect_denominator = job->par.den; ti.colorspace = TH_CS_UNSPECIFIED; @@ -98,7 +88,8 @@ int enctheoraInit( hb_work_object_t * w, hb_job_t * job ) ti.quality = job->vquality; } - keyframe_frequency = 10 * ((double)job->vrate.num / job->vrate.den + 0.5); + keyframe_frequency = ((double)job->orig_vrate.num / job->orig_vrate.den + + 0.5) * 10; hb_log("theora: keyint: %i", keyframe_frequency); diff --git a/libhb/encx264.c b/libhb/encx264.c index 552e713ad..f87573609 100644 --- a/libhb/encx264.c +++ b/libhb/encx264.c @@ -92,7 +92,7 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) job->encoder_preset, job->encoder_tune) < 0) { free( pv ); - pv = NULL; + w->private_data = NULL; return 1; } @@ -120,17 +120,8 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) /* Some HandBrake-specific defaults; users can override them * using the encoder_options string. */ - if( job->pass_id == HB_PASS_ENCODE_2ND && job->cfr != 1 ) - { - hb_interjob_t * interjob = hb_interjob_get( job->h ); - param.i_fps_num = interjob->vrate.num; - param.i_fps_den = interjob->vrate.den; - } - else - { - param.i_fps_num = job->vrate.num; - param.i_fps_den = job->vrate.den; - } + param.i_fps_num = job->vrate.num; + param.i_fps_den = job->vrate.den; if ( job->cfr == 1 ) { param.i_timebase_num = 0; @@ -146,9 +137,9 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) /* Set min:max keyframe intervals to 1:10 of fps; * adjust +0.5 for when fps has remainder to bump * { 23.976, 29.976, 59.94 } to { 24, 30, 60 }. */ - param.i_keyint_min = (double)job->vrate.num / job->vrate.den + 0.5; + param.i_keyint_min = (double)job->orig_vrate.num / job->orig_vrate.den + + 0.5; param.i_keyint_max = 10 * param.i_keyint_min; - param.i_log_level = X264_LOG_INFO; /* set up the VUI color model & gamma to match what the COLR atom @@ -301,7 +292,7 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) if (hb_apply_h264_profile(¶m, job->encoder_profile, 1)) { free(pv); - pv = NULL; + w->private_data = NULL; return 1; } } @@ -311,7 +302,7 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) job->encoder_profile, 1) < 0) { free(pv); - pv = NULL; + w->private_data = NULL; return 1; } } @@ -354,7 +345,7 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) { hb_error("encx264: x264_encoder_open failed."); free( pv ); - pv = NULL; + w->private_data = NULL; return 1; } @@ -380,6 +371,11 @@ void encx264Close( hb_work_object_t * w ) { hb_work_private_t * pv = w->private_data; + if (pv == NULL) + { + // Not initialized + return; + } if (pv->delayed_chapters != NULL) { struct chapter_s *item; diff --git a/libhb/encx265.c b/libhb/encx265.c index d9c15637d..eb449173b 100644 --- a/libhb/encx265.c +++ b/libhb/encx265.c @@ -139,7 +139,8 @@ int encx265Init(hb_work_object_t *w, hb_job_t *job) hb_reduce(&vrate.num, &vrate.den, job->vrate.num, job->vrate.den); param->fpsNum = vrate.num; param->fpsDenom = vrate.den; - param->keyframeMin = (double)vrate.num / vrate.den + 0.5; + param->keyframeMin = (double)job->orig_vrate.num / job->orig_vrate.den + + 0.5; param->keyframeMax = param->keyframeMin * 10; /* diff --git a/libhb/hb.c b/libhb/hb.c index 0364a5a15..dedeadaf5 100644 --- a/libhb/hb.c +++ b/libhb/hb.c @@ -46,6 +46,7 @@ struct hb_handle_s /* The thread which processes the jobs. Others threads are launched from this one (see work.c) */ + int sequence_id; hb_list_t * jobs; hb_job_t * current_job; volatile int work_die; @@ -1524,11 +1525,14 @@ hb_job_t* hb_job_copy(hb_job_t * job) return job_copy; } -void hb_add( hb_handle_t * h, hb_job_t * job ) +int hb_add( hb_handle_t * h, hb_job_t * job ) { hb_job_t *job_copy = hb_job_copy(job); job_copy->h = h; + job_copy->sequence_id = ++h->sequence_id; hb_list_add(h->jobs, job_copy); + + return job_copy->sequence_id; } void hb_job_setup_passes(hb_handle_t * h, hb_job_t * job, hb_list_t * list_pass) @@ -1968,7 +1972,7 @@ void hb_set_state( hb_handle_t * h, hb_state_t * s ) { // Set which job is being worked on if (h->current_job) - h->state.param.working.sequence_id = h->current_job->sequence_id & 0xFFFFFF; + h->state.param.working.sequence_id = h->current_job->sequence_id; else h->state.param.working.sequence_id = 0; } diff --git a/libhb/hb.h b/libhb/hb.h index c8101ed68..0436703a0 100644 --- a/libhb/hb.h +++ b/libhb/hb.h @@ -92,7 +92,7 @@ void hb_add_filter( hb_job_t * job, hb_filter_object_t * filter, /* Handling jobs */ int hb_count( hb_handle_t * ); hb_job_t * hb_job( hb_handle_t *, int ); -void hb_add( hb_handle_t *, hb_job_t * ); +int hb_add( hb_handle_t *, hb_job_t * ); void hb_rem( hb_handle_t *, hb_job_t * ); hb_title_t * hb_find_title_by_index( hb_handle_t *h, int title_index ); @@ -111,11 +111,11 @@ void hb_system_sleep_prevent(hb_handle_t*); /* Persistent data between jobs. */ typedef struct hb_interjob_s { - int last_job; /* job->sequence_id & 0xFFFFFF */ - int frame_count; /* number of frames counted by sync */ - int out_frame_count; /* number of frames counted by render */ - uint64_t total_time; /* real length in 90kHz ticks (i.e. seconds / 90000) */ - hb_rational_t vrate; /* actual measured output vrate from 1st pass */ + int sequence_id; /* job->sequence_id */ + int frame_count; /* number of frames counted by sync */ + int out_frame_count; /* number of frames counted by render */ + int64_t total_time; /* measured length in 90kHz ticks */ + hb_rational_t vrate; /* measured output vrate */ hb_subtitle_t *select_subtitle; /* foreign language scan subtitle */ } hb_interjob_t; diff --git a/libhb/hb_json.c b/libhb/hb_json.c index ec2a8cae1..f9a4a3aa1 100644 --- a/libhb/hb_json.c +++ b/libhb/hb_json.c @@ -1374,9 +1374,7 @@ int hb_add_json( hb_handle_t * h, const char * json_job ) hb_job_t job; job.json = json_job; - hb_add(h, &job); - - return 0; + return hb_add(h, &job); } diff --git a/libhb/internal.h b/libhb/internal.h index 6401f7b4b..79a01dd9b 100644 --- a/libhb/internal.h +++ b/libhb/internal.h @@ -274,10 +274,13 @@ hb_thread_t * hb_scan_init( hb_handle_t *, volatile int * die, hb_thread_t * hb_work_init( hb_list_t * jobs, volatile int * die, hb_error_code * error, hb_job_t ** job ); void ReadLoop( void * _w ); +void hb_work_loop( void * ); hb_work_object_t * hb_muxer_init( hb_job_t * ); hb_work_object_t * hb_get_work( hb_handle_t *, int ); -hb_work_object_t * hb_codec_decoder( hb_handle_t *, int ); -hb_work_object_t * hb_codec_encoder( hb_handle_t *, int ); +hb_work_object_t * hb_audio_decoder( hb_handle_t *, int ); +hb_work_object_t * hb_audio_encoder( hb_handle_t *, int ); +hb_work_object_t * hb_video_decoder( hb_handle_t *, int, int ); +hb_work_object_t * hb_video_encoder( hb_handle_t *, int ); /*********************************************************************** * sync.c diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c index bfa377089..0d7059775 100644 --- a/libhb/muxavformat.c +++ b/libhb/muxavformat.c @@ -355,16 +355,7 @@ static int avformatInit( hb_mux_object_t * m ) track->st->codec->height = job->height; track->st->disposition |= AV_DISPOSITION_DEFAULT; - hb_rational_t vrate; - if( job->pass_id == HB_PASS_ENCODE_2ND ) - { - hb_interjob_t * interjob = hb_interjob_get( job->h ); - vrate = interjob->vrate; - } - else - { - vrate = job->vrate; - } + hb_rational_t vrate = job->vrate; // If the vrate is the internal clock rate, there's a good chance // this is a standard rate that we have in our hb_video_rates table. diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c index 990614540..452e41fb0 100644 --- a/libhb/muxcommon.c +++ b/libhb/muxcommon.c @@ -43,7 +43,6 @@ typedef struct typedef struct { hb_lock_t * mutex; - int ref; int done; hb_mux_object_t * m; double pts; // end time of next muxing chunk @@ -60,9 +59,10 @@ typedef struct struct hb_work_private_s { - hb_job_t * job; - int track; - hb_mux_t * mux; + hb_job_t * job; + int track; + hb_mux_t * mux; + hb_list_t * list_work; }; @@ -444,6 +444,7 @@ static int muxWork( hb_work_object_t * w, hb_buffer_t ** buf_in, if ( i >= mux->ntracks ) { mux->done = 1; + *w->done = 1; hb_unlock( mux->mutex ); hb_bitvec_free(&more); return HB_WORK_DONE; @@ -477,147 +478,133 @@ static void muxFlush(hb_mux_t * mux) } } -static void muxClose( hb_work_object_t * w ) +static void muxClose( hb_work_object_t * muxer ) { - hb_work_private_t * pv = w->private_data; - hb_mux_t * mux = pv->mux; - hb_job_t * job = pv->job; - hb_track_t * track; - int i; + hb_work_private_t * pv = muxer->private_data; + if (pv == NULL) + { + // Not initialized + return; + } + + hb_mux_t * mux = pv->mux; + hb_job_t * job = pv->job; + hb_track_t * track; + hb_work_object_t * w; + int i; hb_lock( mux->mutex ); - if ( --mux->ref == 0 ) + muxFlush(mux); + + // Update state before closing muxer. Closing the muxer + // may initiate optimization which can take a while and + // we want the muxing state to be visible while this is + // happening. + if( job->pass_id == HB_PASS_ENCODE || + job->pass_id == HB_PASS_ENCODE_2ND ) { - muxFlush(mux); - - // Update state before closing muxer. Closing the muxer - // may initiate optimization which can take a while and - // we want the muxing state to be visible while this is - // happening. - if( job->pass_id == HB_PASS_ENCODE || - job->pass_id == HB_PASS_ENCODE_2ND ) - { - /* Update the UI */ - hb_state_t state; - state.state = HB_STATE_MUXING; - state.param.muxing.progress = 0; - hb_set_state( job->h, &state ); - } + /* Update the UI */ + hb_state_t state; + state.state = HB_STATE_MUXING; + state.param.muxing.progress = 0; + hb_set_state( job->h, &state ); + } - if( mux->m ) - { - mux->m->end( mux->m ); - free( mux->m ); - } + if( mux->m ) + { + mux->m->end( mux->m ); + free( mux->m ); + } + + // we're all done muxing -- print final stats and cleanup. + if( job->pass_id == HB_PASS_ENCODE || + job->pass_id == HB_PASS_ENCODE_2ND ) + { + hb_stat_t sb; + uint64_t bytes_total, frames_total; - // we're all done muxing -- print final stats and cleanup. - if( job->pass_id == HB_PASS_ENCODE || - job->pass_id == HB_PASS_ENCODE_2ND ) + if (!hb_stat(job->file, &sb)) { - hb_stat_t sb; - uint64_t bytes_total, frames_total; + hb_deep_log( 2, "mux: file size, %"PRId64" bytes", (uint64_t) sb.st_size ); - if (!hb_stat(job->file, &sb)) + bytes_total = 0; + frames_total = 0; + for( i = 0; i < mux->ntracks; ++i ) { - hb_deep_log( 2, "mux: file size, %"PRId64" bytes", (uint64_t) sb.st_size ); - - bytes_total = 0; - frames_total = 0; - for( i = 0; i < mux->ntracks; ++i ) - { - track = mux->track[i]; - hb_log( "mux: track %d, %"PRId64" frames, %"PRId64" bytes, %.2f kbps, fifo %d", - i, track->frames, track->bytes, - 90000.0 * track->bytes / mux->pts / 125, - track->mf.flen ); - if( !i && job->vquality < 0 ) - { - /* Video */ - hb_deep_log( 2, "mux: video bitrate error, %+"PRId64" bytes", - (int64_t)(track->bytes - mux->pts * job->vbitrate * 125 / 90000) ); - } - bytes_total += track->bytes; - frames_total += track->frames; - } - - if( bytes_total && frames_total ) + track = mux->track[i]; + hb_log( "mux: track %d, %"PRId64" frames, %"PRId64" bytes, %.2f kbps, fifo %d", + i, track->frames, track->bytes, + 90000.0 * track->bytes / mux->pts / 125, + track->mf.flen ); + if( !i && job->vquality < 0 ) { - hb_deep_log( 2, "mux: overhead, %.2f bytes per frame", - (float) ( sb.st_size - bytes_total ) / - frames_total ); + /* Video */ + hb_deep_log( 2, "mux: video bitrate error, %+"PRId64" bytes", + (int64_t)(track->bytes - mux->pts * job->vbitrate * 125 / 90000) ); } + bytes_total += track->bytes; + frames_total += track->frames; } - } - for( i = 0; i < mux->ntracks; ++i ) - { - hb_buffer_t * b; - track = mux->track[i]; - while ( (b = mf_pull( mux, i )) != NULL ) - { - hb_buffer_close( &b ); - } - if( track->mux_data ) + if( bytes_total && frames_total ) { - free( track->mux_data ); - free( track->mf.fifo ); + hb_deep_log( 2, "mux: overhead, %.2f bytes per frame", + (float) ( sb.st_size - bytes_total ) / + frames_total ); } - free( track ); } - free(mux->track); - hb_unlock( mux->mutex ); - hb_lock_close( &mux->mutex ); - hb_bitvec_free(&mux->eof); - hb_bitvec_free(&mux->rdy); - hb_bitvec_free(&mux->allEof); - hb_bitvec_free(&mux->allRdy); - free( mux ); - } - else - { - hb_unlock( mux->mutex ); } - free( pv ); - w->private_data = NULL; -} - -static void mux_loop( void * _w ) -{ - hb_work_object_t * w = _w; - hb_work_private_t * pv = w->private_data; - hb_job_t * job = pv->job; - hb_buffer_t * buf_in; - while ( !*job->die && w->status != HB_WORK_DONE ) + for (i = 0; i < mux->ntracks; ++i) { - buf_in = hb_fifo_get_wait( w->fifo_in ); - if ( pv->mux->done ) - break; - if ( buf_in == NULL ) - continue; - if ( *job->die ) + hb_buffer_t * b; + track = mux->track[i]; + while ( (b = mf_pull( mux, i )) != NULL ) { - if( buf_in ) - { - hb_buffer_close( &buf_in ); - } - break; + hb_buffer_close( &b ); } - - w->status = w->work( w, &buf_in, NULL ); - if( buf_in ) + if( track->mux_data ) + { + free( track->mux_data ); + free( track->mf.fifo ); + } + free( track ); + } + free(mux->track); + hb_unlock( mux->mutex ); + hb_lock_close( &mux->mutex ); + hb_bitvec_free(&mux->eof); + hb_bitvec_free(&mux->rdy); + hb_bitvec_free(&mux->allEof); + hb_bitvec_free(&mux->allRdy); + free( mux ); + + // Close mux work threads + while ((w = hb_list_item(pv->list_work, 0))) + { + hb_list_rem(pv->list_work, w); + if (w->thread != NULL) { - hb_buffer_close( &buf_in ); + hb_thread_close( &w->thread ); } + free(w->private_data); + free(w); } + hb_list_close(&pv->list_work); + free( pv ); + muxer->private_data = NULL; } -hb_work_object_t * hb_muxer_init( hb_job_t * job ) +static int muxInit( hb_work_object_t * muxer, hb_job_t * job ) { - int i; - hb_mux_t * mux = calloc( sizeof( hb_mux_t ), 1 ); - hb_work_object_t * w; - hb_work_object_t * muxer; + muxer->private_data = calloc( sizeof( hb_work_private_t ), 1 ); + hb_work_private_t * pv = muxer->private_data; + + hb_mux_t * mux = calloc( sizeof( hb_mux_t ), 1 ); + int i; + hb_work_object_t * w; + + pv->list_work = hb_list_init(); // The bit vectors must be allocated before hb_thread_init for the // audio and subtitle muxer jobs below. @@ -649,7 +636,7 @@ hb_work_object_t * hb_muxer_init( hb_job_t * job ) hb_error( "No muxer selected, exiting" ); *job->done_error = HB_ERROR_INIT; *job->die = 1; - return NULL; + return -1; } /* Create file, write headers */ if( mux->m ) @@ -659,60 +646,50 @@ hb_work_object_t * hb_muxer_init( hb_job_t * job ) } /* Initialize the work objects that will receive fifo data */ - - muxer = hb_get_work( job->h, WORK_MUX ); - muxer->private_data = calloc( sizeof( hb_work_private_t ), 1 ); - muxer->private_data->job = job; - muxer->private_data->mux = mux; - mux->ref++; - muxer->private_data->track = mux->ntracks; + pv->job = job; + pv->mux = mux; + pv->track = mux->ntracks; muxer->fifo_in = job->fifo_mpeg4; add_mux_track( mux, job->mux_data, 1 ); - muxer->done = &muxer->private_data->mux->done; - for( i = 0; i < hb_list_count( job->list_audio ); i++ ) + for (i = 0; i < hb_list_count(job->list_audio); i++) { hb_audio_t *audio = hb_list_item( job->list_audio, i ); - w = hb_get_work( job->h, WORK_MUX ); - w->private_data = calloc( sizeof( hb_work_private_t ), 1 ); + w = hb_get_work(job->h, WORK_MUX); + w->private_data = calloc(sizeof(hb_work_private_t), 1); w->private_data->job = job; w->private_data->mux = mux; - mux->ref++; w->private_data->track = mux->ntracks; w->fifo_in = audio->priv.fifo_out; - add_mux_track( mux, audio->priv.mux_data, 1 ); - w->done = &job->done; - hb_list_add( job->list_work, w ); - w->thread = hb_thread_init( w->name, mux_loop, w, HB_NORMAL_PRIORITY ); + add_mux_track(mux, audio->priv.mux_data, 1); + hb_list_add(pv->list_work, w); } - for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) + for (i = 0; i < hb_list_count(job->list_subtitle); i++) { hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, i ); if (subtitle->config.dest != PASSTHRUSUB) continue; - w = hb_get_work( job->h, WORK_MUX ); - w->private_data = calloc( sizeof( hb_work_private_t ), 1 ); + w = hb_get_work(job->h, WORK_MUX); + w->private_data = calloc(sizeof(hb_work_private_t), 1); w->private_data->job = job; w->private_data->mux = mux; - mux->ref++; w->private_data->track = mux->ntracks; w->fifo_in = subtitle->fifo_out; - add_mux_track( mux, subtitle->mux_data, 0 ); - w->done = &job->done; - hb_list_add( job->list_work, w ); - w->thread = hb_thread_init( w->name, mux_loop, w, HB_NORMAL_PRIORITY ); + add_mux_track(mux, subtitle->mux_data, 0); + hb_list_add(pv->list_work, w); } - return muxer; -} -// muxInit does nothing because the muxer has a special initializer -// that takes care of initializing all muxer work objects -static int muxInit( hb_work_object_t * w, hb_job_t * job ) -{ + /* Launch processing threads */ + for (i = 0; i < hb_list_count(pv->list_work); i++) + { + w = hb_list_item(pv->list_work, i); + w->done = muxer->done; + w->thread = hb_thread_init(w->name, hb_work_loop, w, HB_LOW_PRIORITY); + } return 0; } diff --git a/libhb/reader.c b/libhb/reader.c index dda2a2e71..0c6f6d853 100644 --- a/libhb/reader.c +++ b/libhb/reader.c @@ -8,18 +8,21 @@ */ #include "hb.h" -static int hb_reader_init( hb_work_object_t * w, hb_job_t * job ); -static void hb_reader_close( hb_work_object_t * w ); +static int reader_init( hb_work_object_t * w, hb_job_t * job ); +static void reader_close( hb_work_object_t * w ); +static int reader_work( hb_work_object_t * w, hb_buffer_t ** buf_in, + hb_buffer_t ** buf_out); hb_work_object_t hb_reader = { - WORK_READER, - "Reader", - hb_reader_init, - NULL, - hb_reader_close, - NULL, - NULL + .id = WORK_READER, + .name = "Reader", + .init = reader_init, + .work = reader_work, + .close = reader_close, + .info = NULL, + .bsinfo = NULL, + .flush = NULL }; typedef struct @@ -56,6 +59,7 @@ struct hb_work_private_s int start_found; // found pts_to_start point int64_t pts_to_start; + int chapter_end; uint64_t st_first; uint64_t duration; hb_fifo_t * fifos[100]; @@ -68,7 +72,7 @@ static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id ); static void UpdateState( hb_work_private_t * r, int64_t start); /*********************************************************************** - * hb_reader_init + * reader_init *********************************************************************** * **********************************************************************/ @@ -78,27 +82,114 @@ static int hb_reader_open( hb_work_private_t * r ) { if ( !( r->bd = hb_bd_init( r->h, r->title->path ) ) ) return 1; + if(!hb_bd_start(r->bd, r->title)) + { + hb_bd_close(&r->bd); + return 1; + } + if (r->job->start_at_preview) + { + // XXX code from DecodePreviews - should go into its own routine + hb_bd_seek(r->bd, (float)r->job->start_at_preview / + (r->job->seek_points ? (r->job->seek_points + 1.0) + : 11.0)); + } + else if (r->job->pts_to_start) + { + // Note, bd seeks always put us to an i-frame. no need + // to start decoding early using r->pts_to_start + hb_bd_seek_pts(r->bd, r->job->pts_to_start); + r->duration -= r->job->pts_to_start; + r->job->pts_to_start = 0; + r->start_found = 1; + } + else + { + hb_bd_seek_chapter(r->bd, r->job->chapter_start); + } + if (r->job->angle > 1) + { + hb_bd_set_angle(r->bd, r->job->angle - 1); + } } - else if ( r->title->type == HB_DVD_TYPE ) + else if (r->title->type == HB_DVD_TYPE) { if ( !( r->dvd = hb_dvd_init( r->h, r->title->path ) ) ) return 1; + if(!hb_dvd_start( r->dvd, r->title, r->job->chapter_start)) + { + hb_dvd_close(&r->dvd); + return 1; + } + if (r->job->angle) + { + hb_dvd_set_angle(r->dvd, r->job->angle); + } + + if (r->job->start_at_preview) + { + hb_dvd_seek(r->dvd, (float)r->job->start_at_preview / + (r->job->seek_points ? (r->job->seek_points + 1.0) + : 11.0)); + } } - else if ( r->title->type == HB_STREAM_TYPE || - r->title->type == HB_FF_STREAM_TYPE ) + else if (r->title->type == HB_STREAM_TYPE || + r->title->type == HB_FF_STREAM_TYPE) { if (!(r->stream = hb_stream_open(r->h, r->title->path, r->title, 0))) return 1; + if (r->job->start_at_preview) + { + hb_stream_seek(r->stream, (float)(r->job->start_at_preview - 1) / + (r->job->seek_points ? (r->job->seek_points + 1.0) + : 11.0)); + } + else if (r->job->pts_to_start) + { + if (hb_stream_seek_ts( r->stream, r->job->pts_to_start ) >= 0) + { + // Seek takes us to the nearest I-frame before the timestamp + // that we want. So we will retrieve the start time of the + // first packet we get, subtract that from pts_to_start, and + // inspect the reset of the frames in sync. + r->start_found = 2; + r->duration -= r->job->pts_to_start; + } + // hb_stream_seek_ts does nothing for TS streams and will return + // an error. + } + else + { + // + // Standard stream, seek to the starting chapter, if set, + // and track the end chapter so that we end at the right time. + hb_chapter_t *chap; + int start = r->job->chapter_start; + chap = hb_list_item(r->job->list_chapter, r->job->chapter_end - 1); + + r->chapter_end = chap->index; + if (start > 1) + { + chap = hb_list_item(r->job->list_chapter, start - 1); + start = chap->index; + } + + /* + * Seek to the start chapter. + */ + hb_stream_seek_chapter(r->stream, start); + } } else { // Unknown type, should never happen return 1; } + return 0; } -static int hb_reader_init( hb_work_object_t * w, hb_job_t * job ) +static int reader_init( hb_work_object_t * w, hb_job_t * job ) { hb_work_private_t * r; @@ -124,6 +215,7 @@ static int hb_reader_init( hb_work_object_t * w, hb_job_t * job ) r->demux.last_scr = AV_NOPTS_VALUE; + r->chapter_end = job->chapter_end; if ( !job->pts_to_start ) r->start_found = 1; else @@ -171,7 +263,7 @@ static int hb_reader_init( hb_work_object_t * w, hb_job_t * job ) } -static void hb_reader_close( hb_work_object_t * w ) +static void reader_close( hb_work_object_t * w ) { hb_work_private_t * r = w->private_data; @@ -359,401 +451,282 @@ static void new_scr_offset( hb_work_private_t *r, hb_buffer_t *buf ) r->scr_changes = r->demux.scr_changes; } -/*********************************************************************** - * ReaderFunc - *********************************************************************** - * - **********************************************************************/ -void ReadLoop( void * _w ) +static void reader_send_eof( hb_work_private_t * r ) +{ + int ii; + + // send eof buffers downstream to decoders to signal we're done. + push_buf(r, r->job->fifo_mpeg2, hb_buffer_eof_init()); + + hb_audio_t *audio; + for (ii = 0; (audio = hb_list_item(r->job->list_audio, ii)); ++ii) + { + if (audio->priv.fifo_in) + push_buf(r, audio->priv.fifo_in, hb_buffer_eof_init()); + } + + hb_subtitle_t *subtitle; + for (ii = 0; (subtitle = hb_list_item(r->job->list_subtitle, ii)); ++ii) + { + if (subtitle->fifo_in && subtitle->source != SRTSUB) + push_buf(r, subtitle->fifo_in, hb_buffer_eof_init()); + } + hb_log("reader: done. %d scr changes", r->demux.scr_changes); +} + +static int reader_work( hb_work_object_t * w, hb_buffer_t ** buf_in, + hb_buffer_t ** buf_out) { - hb_work_object_t * w = _w; hb_work_private_t * r = w->private_data; hb_fifo_t ** fifos; - hb_buffer_t * buf = NULL; + hb_buffer_t * buf; hb_buffer_list_t list; - int n; - int chapter = -1; - int chapter_end = r->job->chapter_end; - uint8_t done = 0; + int ii, chapter = -1; + + hb_buffer_list_clear(&list); if (r->bd) + chapter = hb_bd_chapter( r->bd ); + else if (r->dvd) + chapter = hb_dvd_chapter( r->dvd ); + else if (r->stream) + chapter = hb_stream_chapter( r->stream ); + + if( chapter < 0 ) { - if( !hb_bd_start( r->bd, r->title ) ) - { - hb_bd_close( &r->bd ); - return; - } - if ( r->job->start_at_preview ) - { - // XXX code from DecodePreviews - should go into its own routine - hb_bd_seek( r->bd, (float)r->job->start_at_preview / - ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) ); - } - else if ( r->job->pts_to_start ) - { - // Note, bd seeks always put us to an i-frame. no need - // to start decoding early using r->pts_to_start - hb_bd_seek_pts( r->bd, r->job->pts_to_start ); - r->duration -= r->job->pts_to_start; - r->job->pts_to_start = 0; - r->start_found = 1; - } - else - { - hb_bd_seek_chapter( r->bd, r->job->chapter_start ); - } - if (r->job->angle > 1) - { - hb_bd_set_angle( r->bd, r->job->angle - 1 ); - } + hb_log( "reader: end of the title reached" ); + reader_send_eof(r); + return HB_WORK_DONE; } - else if (r->dvd) + if( chapter > r->chapter_end ) { - /* - * XXX this code is a temporary hack that should go away if/when - * chapter merging goes away in libhb/dvd.c - * map the start and end chapter numbers to on-media chapter - * numbers since chapter merging could cause the handbrake numbers - * to diverge from the media numbers and, if our chapter_end is after - * a media chapter that got merged, we'll stop ripping too early. - */ - int start = r->job->chapter_start; - hb_chapter_t *chap = hb_list_item( r->job->list_chapter, chapter_end - 1 ); - - chapter_end = chap->index; - if (start > 1) - { - chap = hb_list_item( r->job->list_chapter, start - 1 ); - start = chap->index; - } - /* end chapter mapping XXX */ - - if( !hb_dvd_start( r->dvd, r->title, start ) ) - { - hb_dvd_close( &r->dvd ); - return; - } - if (r->job->angle) - { - hb_dvd_set_angle( r->dvd, r->job->angle ); - } + hb_log("reader: end of chapter %d (media %d) reached at media chapter %d", + r->job->chapter_end, r->chapter_end, chapter); + reader_send_eof(r); + return HB_WORK_DONE; + } - if ( r->job->start_at_preview ) + if (r->bd) + { + if( (buf = hb_bd_read( r->bd )) == NULL ) { - // XXX code from DecodePreviews - should go into its own routine - hb_dvd_seek( r->dvd, (float)r->job->start_at_preview / - ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) ); + reader_send_eof(r); + return HB_WORK_DONE; } } - else if ( r->stream && r->job->start_at_preview ) - { - - // XXX code from DecodePreviews - should go into its own routine - hb_stream_seek( r->stream, (float)( r->job->start_at_preview - 1 ) / - ( r->job->seek_points ? ( r->job->seek_points + 1.0 ) : 11.0 ) ); - - } - else if ( r->stream && r->job->pts_to_start ) + else if (r->dvd) { - if ( hb_stream_seek_ts( r->stream, r->job->pts_to_start ) >= 0 ) + if( (buf = hb_dvd_read( r->dvd )) == NULL ) { - // Seek takes us to the nearest I-frame before the timestamp - // that we want. So we will retrieve the start time of the - // first packet we get, subtract that from pts_to_start, and - // inspect the reset of the frames in sync. - r->start_found = 2; - r->duration -= r->job->pts_to_start; + reader_send_eof(r); + return HB_WORK_DONE; } - // hb_stream_seek_ts does nothing for TS streams and will return - // an error. - } - else if( r->stream ) + } + else if (r->stream) { - /* - * Standard stream, seek to the starting chapter, if set, and track the - * end chapter so that we end at the right time. - */ - int start = r->job->chapter_start; - hb_chapter_t *chap = hb_list_item( r->job->list_chapter, chapter_end - 1 ); - - chapter_end = chap->index; - if (start > 1) + if ( (buf = hb_stream_read( r->stream )) == NULL ) { - chap = hb_list_item( r->job->list_chapter, start - 1 ); - start = chap->index; + reader_send_eof(r); + return HB_WORK_DONE; } - - /* - * Seek to the start chapter. - */ - hb_stream_seek_chapter( r->stream, start ); } - hb_buffer_list_clear(&list); + (hb_demux[r->title->demuxer])(buf, &list, &r->demux); - while(!*r->die && !r->job->done && !done) + while ((buf = hb_buffer_list_rem_head(&list)) != NULL) { - if (r->bd) - chapter = hb_bd_chapter( r->bd ); - else if (r->dvd) - chapter = hb_dvd_chapter( r->dvd ); - else if (r->stream) - chapter = hb_stream_chapter( r->stream ); + fifos = GetFifoForId( r, buf->s.id ); - if( chapter < 0 ) + if (fifos && r->stream && r->start_found == 2 ) { - hb_log( "reader: end of the title reached" ); - break; - } - if( chapter > chapter_end ) - { - hb_log( "reader: end of chapter %d (media %d) reached at media chapter %d", - r->job->chapter_end, chapter_end, chapter ); - break; - } - - if (buf == NULL) - { - if (r->bd) + // We will inspect the timestamps of each frame in sync + // to skip from this seek point to the timestamp we + // want to start at. + if (buf->s.start != AV_NOPTS_VALUE && + buf->s.start < r->job->pts_to_start) { - if( (buf = hb_bd_read( r->bd )) == NULL ) - { - break; - } - } - else if (r->dvd) - { - if( (buf = hb_dvd_read( r->dvd )) == NULL ) - { - break; - } + r->job->pts_to_start -= buf->s.start; } - else if (r->stream) + else if ( buf->s.start >= r->job->pts_to_start ) { - if ( (buf = hb_stream_read( r->stream )) == NULL ) - { - break; - } + r->job->pts_to_start = 0; } + r->start_found = 1; } - (hb_demux[r->title->demuxer])(buf, &list, &r->demux); - - while ((buf = hb_buffer_list_rem_head(&list)) != NULL) + if ( fifos && ! r->saw_video && !r->job->indepth_scan ) { - fifos = GetFifoForId( r, buf->s.id ); - - if (fifos && r->stream && r->start_found == 2 ) + // The first data packet with a PTS from an audio or video stream + // that we're decoding defines 'time zero'. Discard packets until + // we get one. + if (buf->s.start != AV_NOPTS_VALUE && + buf->s.renderOffset != AV_NOPTS_VALUE && + (buf->s.id == r->title->video_id || + is_audio( r, buf->s.id))) { - // We will inspect the timestamps of each frame in sync - // to skip from this seek point to the timestamp we - // want to start at. - if (buf->s.start != AV_NOPTS_VALUE && - buf->s.start < r->job->pts_to_start) - { - r->job->pts_to_start -= buf->s.start; - } - else if ( buf->s.start >= r->job->pts_to_start ) - { - r->job->pts_to_start = 0; - } - r->start_found = 1; + // force a new scr offset computation + r->scr_changes = r->demux.scr_changes - 1; + // create a stream state if we don't have one so the + // offset will get computed correctly. + id_to_st( r, buf, 1 ); + r->saw_video = 1; + hb_log( "reader: first SCR %"PRId64" id 0x%x DTS %"PRId64, + r->demux.last_scr, buf->s.id, buf->s.renderOffset ); } - - if ( fifos && ! r->saw_video && !r->job->indepth_scan ) + else { - // The first data packet with a PTS from an audio or video stream - // that we're decoding defines 'time zero'. Discard packets until - // we get one. - if (buf->s.start != AV_NOPTS_VALUE && - buf->s.renderOffset != AV_NOPTS_VALUE && - (buf->s.id == r->title->video_id || - is_audio( r, buf->s.id))) - { - // force a new scr offset computation - r->scr_changes = r->demux.scr_changes - 1; - // create a stream state if we don't have one so the - // offset will get computed correctly. - id_to_st( r, buf, 1 ); - r->saw_video = 1; - hb_log( "reader: first SCR %"PRId64" id 0x%x DTS %"PRId64, - r->demux.last_scr, buf->s.id, buf->s.renderOffset ); - } - else - { - fifos = NULL; - } + fifos = NULL; } + } - if ( r->job->indepth_scan || fifos ) + if ( r->job->indepth_scan || fifos ) + { + if ( buf->s.renderOffset != AV_NOPTS_VALUE ) { - if ( buf->s.renderOffset != AV_NOPTS_VALUE ) + if ( r->scr_changes != r->demux.scr_changes ) { - if ( r->scr_changes != r->demux.scr_changes ) + // This is the first audio or video packet after an SCR + // change. Compute a new scr offset that would make this + // packet follow the last of this stream with the + // correct average spacing. + stream_timing_t *st = id_to_st( r, buf, 0 ); + + // if this is the video stream and we don't have + // audio yet or this is an audio stream + // generate a new scr + if ( st->is_audio || + ( st == r->stream_timing && !r->saw_audio ) ) { - // This is the first audio or video packet after an SCR - // change. Compute a new scr offset that would make this - // packet follow the last of this stream with the - // correct average spacing. - stream_timing_t *st = id_to_st( r, buf, 0 ); - - // if this is the video stream and we don't have - // audio yet or this is an audio stream - // generate a new scr - if ( st->is_audio || - ( st == r->stream_timing && !r->saw_audio ) ) + new_scr_offset( r, buf ); + r->sub_scr_set = 0; + } + else + { + // defer the scr change until we get some + // audio since audio has a timestamp per + // frame but video & subtitles don't. Clear + // the timestamps so the decoder will generate + // them from the frame durations. + if (is_subtitle(r, buf->s.id) && + buf->s.start != AV_NOPTS_VALUE) { - new_scr_offset( r, buf ); - r->sub_scr_set = 0; + if (!r->sub_scr_set) + { + // We can't generate timestamps in the + // subtitle decoder as we can for + // audio & video. So we need to make + // the closest guess that we can + // for the subtitles start time here. + int64_t last = r->stream_timing[0].last; + r->scr_offset = buf->s.start - last; + r->sub_scr_set = 1; + } } else { - // defer the scr change until we get some - // audio since audio has a timestamp per - // frame but video & subtitles don't. Clear - // the timestamps so the decoder will generate - // them from the frame durations. - if (is_subtitle(r, buf->s.id) && - buf->s.start != AV_NOPTS_VALUE) - { - if (!r->sub_scr_set) - { - // We can't generate timestamps in the - // subtitle decoder as we can for - // audio & video. So we need to make - // the closest guess that we can - // for the subtitles start time here. - int64_t last = r->stream_timing[0].last; - r->scr_offset = buf->s.start - last; - r->sub_scr_set = 1; - } - } - else - { - buf->s.start = AV_NOPTS_VALUE; - buf->s.renderOffset = AV_NOPTS_VALUE; - } + buf->s.start = AV_NOPTS_VALUE; + buf->s.renderOffset = AV_NOPTS_VALUE; } } } - if ( buf->s.start != AV_NOPTS_VALUE ) - { - int64_t start = buf->s.start - r->scr_offset; + } + if ( buf->s.start != AV_NOPTS_VALUE ) + { + int64_t start = buf->s.start - r->scr_offset; - if (!r->start_found || r->job->indepth_scan) - { - UpdateState( r, start ); - } + if (!r->start_found || r->job->indepth_scan) + { + UpdateState( r, start ); + } - if (r->job->indepth_scan && r->job->pts_to_stop && - start >= r->pts_to_start + r->job->pts_to_stop) - { - // sync normally would terminate p-to-p - // but sync doesn't run during indepth scan - hb_log( "reader: reached pts %"PRId64", exiting early", start ); - done = 1; - break; - } + if (r->job->indepth_scan && r->job->pts_to_stop && + start >= r->pts_to_start + r->job->pts_to_stop) + { + // sync normally would terminate p-to-p + // but sync doesn't run during indepth scan + hb_log("reader: reached pts %"PRId64", exiting early", start); + reader_send_eof(r); + hb_buffer_list_close(&list); + return HB_WORK_DONE; + } - if (!r->start_found && start >= r->pts_to_start) - { - // pts_to_start point found - r->start_found = 1; - if (r->stream) - { - // libav multi-threaded decoders can get into - // a bad state if the initial data is not - // decodable. So try to improve the chances of - // a good start by waiting for an initial iframe - hb_stream_set_need_keyframe(r->stream, 1); - hb_buffer_close( &buf ); - continue; - } - } - // This log is handy when you need to debug timing problems - //hb_log("id %x scr_offset %"PRId64 - // " start %"PRId64" --> %"PRId64"", - // buf->s.id, r->scr_offset, buf->s.start, - // buf->s.start - r->scr_offset); - buf->s.start -= r->scr_offset; - if ( buf->s.stop != AV_NOPTS_VALUE ) + if (!r->start_found && start >= r->pts_to_start) + { + // pts_to_start point found + r->start_found = 1; + if (r->stream) { - buf->s.stop -= r->scr_offset; + // libav multi-threaded decoders can get into + // a bad state if the initial data is not + // decodable. So try to improve the chances of + // a good start by waiting for an initial iframe + hb_stream_set_need_keyframe(r->stream, 1); + hb_buffer_close( &buf ); + continue; } } - if ( buf->s.renderOffset != AV_NOPTS_VALUE ) + // This log is handy when you need to debug timing problems + //hb_log("id %x scr_offset %"PRId64 + // " start %"PRId64" --> %"PRId64"", + // buf->s.id, r->scr_offset, buf->s.start, + // buf->s.start - r->scr_offset); + buf->s.start -= r->scr_offset; + if ( buf->s.stop != AV_NOPTS_VALUE ) { - // This packet is referenced to the same SCR as the last. - // Adjust timestamp to remove the System Clock Reference - // offset then update the average inter-packet time - // for this stream. - buf->s.renderOffset -= r->scr_offset; - update_ipt( r, buf ); + buf->s.stop -= r->scr_offset; } -#if 0 - // JAS: This was added to fix a rare "audio time went backward" - // sync error I found in one sample. But it has a bad side - // effect on DVDs, causing frequent "adding silence" sync - // errors. So I am disabling it. - else - { - update_ipt( r, buf ); - } -#endif } - if( fifos ) + if ( buf->s.renderOffset != AV_NOPTS_VALUE ) { - if ( !r->start_found ) - { - hb_buffer_close( &buf ); - continue; - } - - buf->sequence = r->sequence++; - /* if there are mutiple output fifos, send a copy of the - * buffer down all but the first (we have to not ship the - * original buffer or we'll race with the thread that's - * consuming the buffer & inject garbage into the data stream). */ - for( n = 1; fifos[n] != NULL; n++) - { - hb_buffer_t *buf_copy = hb_buffer_init( buf->size ); - buf_copy->s = buf->s; - memcpy( buf_copy->data, buf->data, buf->size ); - push_buf( r, fifos[n], buf_copy ); - } - push_buf( r, fifos[0], buf ); - buf = NULL; + // This packet is referenced to the same SCR as the last. + // Adjust timestamp to remove the System Clock Reference + // offset then update the average inter-packet time + // for this stream. + buf->s.renderOffset -= r->scr_offset; + update_ipt( r, buf ); } +#if 0 + // JAS: This was added to fix a rare "audio time went backward" + // sync error I found in one sample. But it has a bad side + // effect on DVDs, causing frequent "adding silence" sync + // errors. So I am disabling it. else { - hb_buffer_close( &buf ); + update_ipt( r, buf ); } +#endif } - } - - // send empty buffers downstream to video & audio decoders to signal we're done. - if( !*r->die && !r->job->done ) - { - push_buf(r, r->job->fifo_mpeg2, hb_buffer_eof_init()); - - hb_audio_t *audio; - for( n = 0; (audio = hb_list_item( r->job->list_audio, n)); ++n ) + if( fifos ) { - if ( audio->priv.fifo_in ) - push_buf(r, audio->priv.fifo_in, hb_buffer_eof_init()); - } + if ( !r->start_found ) + { + hb_buffer_close( &buf ); + continue; + } - hb_subtitle_t *subtitle; - for( n = 0; (subtitle = hb_list_item( r->job->list_subtitle, n)); ++n ) + buf->sequence = r->sequence++; + /* if there are mutiple output fifos, send a copy of the + * buffer down all but the first (we have to not ship the + * original buffer or we'll race with the thread that's + * consuming the buffer & inject garbage into the data stream). */ + for (ii = 1; fifos[ii] != NULL; ii++) + { + hb_buffer_t *buf_copy = hb_buffer_init(buf->size); + buf_copy->s = buf->s; + memcpy(buf_copy->data, buf->data, buf->size); + push_buf(r, fifos[ii], buf_copy); + } + push_buf(r, fifos[0], buf); + buf = NULL; + } + else { - if ( subtitle->fifo_in && subtitle->source == VOBSUB) - push_buf(r, subtitle->fifo_in, hb_buffer_eof_init()); + hb_buffer_close(&buf); } } hb_buffer_list_close(&list); - - hb_log( "reader: done. %d scr changes", r->demux.scr_changes ); + return HB_WORK_OK; } static void UpdateState( hb_work_private_t * r, int64_t start) @@ -859,7 +832,7 @@ static hb_fifo_t ** GetFifoForId( hb_work_private_t * r, int id ) { return r->fifos; } - + if( !job->indepth_scan ) { for( i = n = 0; i < hb_list_count( job->list_audio ); i++ ) diff --git a/libhb/scan.c b/libhb/scan.c index 8626ccca8..34805a30a 100644 --- a/libhb/scan.c +++ b/libhb/scan.c @@ -1119,7 +1119,7 @@ static void LookForAudio(hb_scan_t *scan, hb_title_t * title, hb_buffer_t * b) } hb_fifo_push( audio->priv.scan_cache, b ); - hb_work_object_t *w = hb_codec_decoder(scan->h, audio->config.in.codec); + hb_work_object_t *w = hb_audio_decoder(scan->h, audio->config.in.codec); if ( w == NULL || w->bsinfo == NULL ) { diff --git a/libhb/stream.c b/libhb/stream.c index 08c1d47ea..3ddba0256 100644 --- a/libhb/stream.c +++ b/libhb/stream.c @@ -3930,7 +3930,7 @@ static void hb_ps_stream_find_streams(hb_stream_t *stream) static int probe_dts_profile( hb_stream_t *stream, hb_pes_stream_t *pes ) { hb_work_info_t info; - hb_work_object_t *w = hb_codec_decoder( stream->h, pes->codec ); + hb_work_object_t *w = hb_audio_decoder( stream->h, pes->codec ); w->codec_param = pes->codec_param; int ret = w->bsinfo( w, pes->probe_buf, &info ); diff --git a/libhb/sync.c b/libhb/sync.c index d3cb4cbde..512c00a8e 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -17,8 +17,6 @@ #endif #define INT64_MIN (-9223372036854775807LL-1) -#define ABS(a) ((a) < 0 ? -(a) : (a)) - typedef struct { /* Audio/Video sync thread synchronization */ @@ -27,8 +25,6 @@ typedef struct int64_t volatile * last_pts; int pts_count; - int ref; /* Reference count to tell us when it's unused */ - /* PTS synchronization */ int64_t audio_pts_slip; int64_t video_pts_slip; @@ -37,6 +33,9 @@ typedef struct /* point-to-point support */ int start_found; int count_frames; + + /* sync audio work objects */ + hb_list_t * list_work; } hb_sync_common_t; typedef struct @@ -117,7 +116,7 @@ static hb_buffer_t * OutputAudioFrame( hb_audio_t *audio, hb_buffer_t *buf, *********************************************************************** * Initialize the work object **********************************************************************/ -hb_work_object_t * hb_sync_init( hb_job_t * job ) +int syncVideoInit( hb_work_object_t * w, hb_job_t * job) { hb_title_t * title = job->title; hb_chapter_t * chapter; @@ -125,13 +124,11 @@ hb_work_object_t * hb_sync_init( hb_job_t * job ) uint64_t duration; hb_work_private_t * pv; hb_sync_video_t * sync; - hb_work_object_t * w; - hb_work_object_t * ret = NULL; pv = calloc( 1, sizeof( hb_work_private_t ) ); sync = &pv->type.video; pv->common = calloc( 1, sizeof( hb_sync_common_t ) ); - pv->common->ref++; + pv->common->list_work = hb_list_init(); pv->common->mutex = hb_lock_init(); pv->common->next_frame = hb_cond_init(); pv->common->pts_count = 1; @@ -144,7 +141,6 @@ hb_work_object_t * hb_sync_init( hb_job_t * job ) pv->common->start_found = 1; } - ret = w = hb_get_work( job->h, WORK_SYNC_VIDEO ); w->private_data = pv; w->fifo_in = job->fifo_raw; // Register condition with fifo to wake us up immediately if @@ -214,7 +210,17 @@ hb_work_object_t * hb_sync_init( hb_job_t * job ) { InitSubtitle(job, sync, i); } - return ret; + + /* Launch audio processing threads */ + for (i = 0; i < hb_list_count(pv->common->list_work); i++) + { + hb_work_object_t * audio_work; + audio_work = hb_list_item(pv->common->list_work, i); + audio_work->done = w->done; + audio_work->thread = hb_thread_init(audio_work->name, hb_work_loop, + audio_work, HB_LOW_PRIORITY); + } + return 0; } static void InitSubtitle( hb_job_t * job, hb_sync_video_t * sync, int ii ) @@ -278,7 +284,6 @@ void syncVideoClose( hb_work_object_t * w ) /* Preserve frame count for better accuracy in pass 2 */ hb_interjob_t * interjob = hb_interjob_get( job->h ); interjob->frame_count = pv->common->count_frames; - interjob->last_job = job->sequence_id; } if (sync->drops || sync->dups ) @@ -294,20 +299,24 @@ void syncVideoClose( hb_work_object_t * w ) } free(sync->subtitle_sanitizer); - hb_lock( pv->common->mutex ); - if ( --pv->common->ref == 0 ) - { - hb_unlock( pv->common->mutex ); - hb_cond_close( &pv->common->next_frame ); - hb_lock_close( &pv->common->mutex ); - free((void*)pv->common->last_pts); - free(pv->common); - } - else + // Close audio work threads + hb_work_object_t * audio_work; + while ((audio_work = hb_list_item(pv->common->list_work, 0))) { - hb_unlock( pv->common->mutex ); + hb_list_rem(pv->common->list_work, audio_work); + if (audio_work->thread != NULL) + { + hb_thread_close(&audio_work->thread); + } + audio_work->close(audio_work); + free(audio_work); } + hb_list_close(&pv->common->list_work); + hb_cond_close( &pv->common->next_frame ); + hb_lock_close( &pv->common->mutex ); + free((void*)pv->common->last_pts); + free(pv->common); free( pv ); w->private_data = NULL; } @@ -900,13 +909,6 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in, return HB_WORK_OK; } -// sync*Init does nothing because sync has a special initializer -// that takes care of initializing video and all audio tracks -int syncVideoInit( hb_work_object_t * w, hb_job_t * job) -{ - return 0; -} - hb_work_object_t hb_sync_video = { WORK_SYNC_VIDEO, @@ -938,20 +940,6 @@ void syncAudioClose( hb_work_object_t * w ) src_delete( sync->state ); } - hb_lock( pv->common->mutex ); - if ( --pv->common->ref == 0 ) - { - hb_unlock( pv->common->mutex ); - hb_cond_close( &pv->common->next_frame ); - hb_lock_close( &pv->common->mutex ); - free((void*)pv->common->last_pts); - free(pv->common); - } - else - { - hb_unlock( pv->common->mutex ); - } - free( pv ); w->private_data = NULL; } @@ -1138,10 +1126,11 @@ static void InitAudio( hb_job_t * job, hb_sync_common_t * common, int i ) sync->index = i; pv->job = job; pv->common = common; - pv->common->ref++; pv->common->pts_count++; w = hb_get_work( job->h, WORK_SYNC_AUDIO ); + hb_list_add(common->list_work, w); + w->private_data = pv; w->audio = hb_list_item( job->list_audio, i ); w->fifo_in = w->audio->priv.fifo_raw; @@ -1356,8 +1345,6 @@ static void InitAudio( hb_job_t * job, hb_sync_common_t * common, int i ) } sync->gain_factor = pow(10, w->audio->config.out.gain / 20); - - hb_list_add( job->list_work, w ); } static hb_buffer_t * OutputAudioFrame( hb_audio_t *audio, hb_buffer_t *buf, diff --git a/libhb/work.c b/libhb/work.c index 52bb900d4..186178d7c 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -28,7 +28,6 @@ typedef struct static void work_func(); static void do_job( hb_job_t *); -static void work_loop( void * ); static void filter_loop( void * ); #define FIFO_UNBOUNDED 65536 @@ -72,7 +71,7 @@ static void InitWorkState(hb_handle_t *h, int pass_id, int pass, int pass_count) p.rate_avg = 0.0; p.hours = -1; p.minutes = -1; - p.seconds = -1; + p.seconds = -1; #undef p hb_set_state( h, &state ); @@ -117,6 +116,7 @@ static void work_func( void * _work ) break; } new_job->h = job->h; + new_job->sequence_id = job->sequence_id; hb_job_close(&job); job = new_job; } @@ -163,7 +163,7 @@ hb_work_object_t * hb_get_work( hb_handle_t *h, int id ) return NULL; } -hb_work_object_t* hb_codec_decoder(hb_handle_t *h, int codec) +hb_work_object_t* hb_audio_decoder(hb_handle_t *h, int codec) { hb_work_object_t * w = NULL; if (codec & HB_ACODEC_FF_MASK) @@ -183,7 +183,62 @@ hb_work_object_t* hb_codec_decoder(hb_handle_t *h, int codec) return w; } -hb_work_object_t* hb_codec_encoder(hb_handle_t *h, int codec) +hb_work_object_t* hb_video_decoder(hb_handle_t *h, int vcodec, int param) +{ + hb_work_object_t * w; + + w = hb_get_work(h, vcodec); + if (w == NULL) + { + hb_error("Invalid video decoder: codec %d, param %d", vcodec, param); + return NULL; + } + w->codec_param = param; + + return w; +} + +hb_work_object_t* hb_video_encoder(hb_handle_t *h, int vcodec) +{ + hb_work_object_t * w = NULL; + + switch (vcodec) + { + case HB_VCODEC_FFMPEG_MPEG4: + w = hb_get_work(h, WORK_ENCAVCODEC); + w->codec_param = AV_CODEC_ID_MPEG4; + break; + case HB_VCODEC_FFMPEG_MPEG2: + w = hb_get_work(h, WORK_ENCAVCODEC); + w->codec_param = AV_CODEC_ID_MPEG2VIDEO; + break; + case HB_VCODEC_FFMPEG_VP8: + w = hb_get_work(h, WORK_ENCAVCODEC); + w->codec_param = AV_CODEC_ID_VP8; + break; + case HB_VCODEC_X264: + w = hb_get_work(h, WORK_ENCX264); + break; + case HB_VCODEC_QSV_H264: + case HB_VCODEC_QSV_H265: + w = hb_get_work(h, WORK_ENCQSV); + break; + case HB_VCODEC_THEORA: + w = hb_get_work(h, WORK_ENCTHEORA); + break; +#ifdef USE_X265 + case HB_VCODEC_X265: + w = hb_get_work(h, WORK_ENCX265); + break; +#endif + default: + hb_error("Unknown video codec (0x%x)", vcodec ); + } + + return w; +} + +hb_work_object_t* hb_audio_encoder(hb_handle_t *h, int codec) { if (codec & HB_ACODEC_FF_MASK) { @@ -211,10 +266,10 @@ void hb_display_job_info(hb_job_t *job) hb_title_t *title = job->title; hb_audio_t *audio; hb_subtitle_t *subtitle; - + hb_log("job configuration:"); hb_log( " * source"); - + hb_log( " + %s", title->path ); if( job->pts_to_start || job->pts_to_stop ) @@ -267,7 +322,7 @@ void hb_display_job_info(hb_job_t *job) { hb_log( " + data rate: %d kbps", title->data_rate / 1000 ); } - + hb_log( " * destination"); hb_log( " + %s", job->file ); @@ -290,9 +345,9 @@ void hb_display_job_info(hb_job_t *job) { hb_log( " + chapter markers" ); } - + hb_log(" * video track"); - + #ifdef USE_QSV if (hb_qsv_decode_is_enabled(job)) { @@ -316,7 +371,7 @@ void hb_display_job_info(hb_job_t *job) { hb_log( " + bitrate %d kbps", title->video_bitrate / 1000 ); } - + // Filters can modify dimensions. So show them first. if( hb_list_count( job->list_filter ) ) { @@ -339,7 +394,7 @@ void hb_display_job_info(hb_job_t *job) } } } - + hb_log( " + Output geometry" ); hb_log( " + storage dimensions: %d x %d", job->width, job->height ); hb_log( " + pixel aspect ratio: %d : %d", job->par.num, job->par.den ); @@ -441,7 +496,7 @@ void hb_display_job_info(hb_job_t *job) } } - if( job->indepth_scan ) + if (job->indepth_scan) { hb_log( " * Foreign Audio Search: %s%s%s", job->select_subtitle_config.dest == RENDERSUB ? "Render/Burn-in" : "Passthrough", @@ -491,7 +546,7 @@ void hb_display_job_info(hb_job_t *job) audio = hb_list_item( job->list_audio, i ); hb_log( " * audio track %d", audio->config.out.track ); - + if( audio->config.out.name ) hb_log( " + name: %s", audio->config.out.name ); @@ -562,85 +617,144 @@ void hb_display_job_info(hb_job_t *job) } /* Corrects framerates when actual duration and frame count numbers are known. */ -void correct_framerate( hb_job_t * job ) +void correct_framerate( hb_interjob_t * interjob, hb_job_t * job ) { - hb_interjob_t * interjob = hb_interjob_get( job->h ); - - if( ( job->sequence_id & 0xFFFFFF ) != ( interjob->last_job & 0xFFFFFF) ) - return; // Interjob information is for a different encode. + if (interjob->total_time <= 0 || interjob->out_frame_count <= 0 || + job->cfr == 1) + { + // Invalid or uninitialized frame statistics + // Or CFR output + return; + } // compute actual output vrate from first pass - interjob->vrate.den = (int64_t)job->vrate.num * interjob->total_time / - ((int64_t)interjob->out_frame_count * 90000); - interjob->vrate.num = job->vrate.num; + int64_t num, den; + num = interjob->out_frame_count * 90000L; + den = interjob->total_time; + hb_limit_rational64(&num, &den, num, den, INT_MAX); + + job->vrate.num = num; + job->vrate.den = den; + + den = hb_video_framerate_get_close(&job->vrate, 2.); + if (den > 0) + { + int low, high, clock; + hb_video_framerate_get_limits(&low, &high, &clock); + job->vrate.num = clock; + job->vrate.den = den; + } + if (ABS(((double)job->orig_vrate.num / job->orig_vrate.den) - + ((double) job->vrate.num / job->vrate.den)) > 0.05) + { + hb_log("work: correcting framerate, %d/%d -> %d/%d", + job->orig_vrate.num, job->orig_vrate.den, + job->vrate.num, job->vrate.den); + } } -/** - * Job initialization rountine. - * Initializes fifos. - * Creates work objects for synchronizer, video decoder, video renderer, video decoder, audio decoder, audio encoder, reader, muxer. - * Launches thread for each work object with work_loop. - * Loops while monitoring status of work threads and fifos. - * Exits loop when conversion is done and fifos are empty. - * Closes threads and frees fifos. - * @param job Handle work hb_job_t. - */ -static void do_job(hb_job_t *job) +static void analyze_subtitle_scan( hb_job_t * job ) { + hb_subtitle_t *subtitle; + int subtitle_highest = 0; + int subtitle_lowest = 0; + int subtitle_lowest_id = 0; + int subtitle_forced_id = 0; + int subtitle_forced_hits = 0; + int subtitle_hit = 0; int i; - hb_title_t *title; - hb_interjob_t *interjob; - hb_work_object_t *w; - hb_work_object_t *sync; - hb_work_object_t *muxer; - hb_work_object_t *reader = hb_get_work(job->h, WORK_READER); - hb_audio_t *audio; - hb_subtitle_t *subtitle; - unsigned int subtitle_highest = 0; - unsigned int subtitle_lowest = 0; - unsigned int subtitle_lowest_id = 0; - unsigned int subtitle_forced_id = 0; - unsigned int subtitle_forced_hits = 0; - unsigned int subtitle_hit = 0; + // Before closing the title print out our subtitle stats if we need to + // find the highest and lowest. + for (i = 0; i < hb_list_count(job->list_subtitle); i++) + { + subtitle = hb_list_item(job->list_subtitle, i); - title = job->title; - interjob = hb_interjob_get( job->h ); + hb_log("Subtitle track %d (id 0x%x) '%s': %d hits (%d forced)", + subtitle->track, subtitle->id, subtitle->lang, + subtitle->hits, subtitle->forced_hits); - if( job->pass_id == HB_PASS_ENCODE_2ND ) - { - correct_framerate( job ); - } + if (subtitle->hits == 0) + continue; - job->list_work = hb_list_init(); + if (subtitle_highest < subtitle->hits) + { + subtitle_highest = subtitle->hits; + } - /* - * OpenCL - * - * Note: we delay hb_ocl_init until here, since they're no point it loading - * the library if we aren't going to use it. But we only call hb_ocl_close - * in hb_global_close, since un/reloading the library each run is wasteful. - */ - if (job->use_opencl) - { - if (hb_ocl_init() || hb_init_opencl_run_env(0, NULL, "-I.")) + if (subtitle_lowest == 0 || + subtitle_lowest > subtitle->hits) { - hb_log("work: failed to initialize OpenCL environment, using fallback"); - hb_release_opencl_run_env(); - job->use_opencl = 0; + subtitle_lowest = subtitle->hits; + subtitle_lowest_id = subtitle->id; } + + // pick the track with fewest forced hits + if (subtitle->forced_hits > 0 && + (subtitle_forced_hits == 0 || + subtitle_forced_hits > subtitle->forced_hits)) + { + subtitle_forced_id = subtitle->id; + subtitle_forced_hits = subtitle->forced_hits; + } + } + + if (subtitle_forced_id && job->select_subtitle_config.force) + { + // If there is a subtitle stream with forced subtitles and forced-only + // is set, then select it in preference to the lowest. + subtitle_hit = subtitle_forced_id; + hb_log("Found a subtitle candidate with id 0x%x (contains forced subs)", + subtitle_hit ); + } + else if (subtitle_lowest > 0 && subtitle_lowest < subtitle_highest * 0.1) + { + // OK we have more than one, and the lowest is lower, + // but how much lower to qualify for turning it on by + // default? + // + // Let's say 10% as a default. + subtitle_hit = subtitle_lowest_id; + hb_log( "Found a subtitle candidate with id 0x%x", subtitle_hit ); } else { - // we're not (re-)using OpenCL here, we can release the environment - hb_release_opencl_run_env(); + hb_log( "No candidate detected during subtitle scan" ); } - hb_log( "starting job" ); + for (i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + if (subtitle->id == subtitle_hit) + { + hb_interjob_t *interjob = hb_interjob_get(job->h); + + subtitle->config = job->select_subtitle_config; + // Remove from list since we are taking ownership + // of the subtitle. + hb_list_rem(job->list_subtitle, subtitle); + interjob->select_subtitle = subtitle; + break; + } + } +} + +static int sanitize_subtitles( hb_job_t * job ) +{ + int i; + uint8_t one_burned = 0; + hb_interjob_t * interjob = hb_interjob_get(job->h); + hb_subtitle_t * subtitle; + + if (job->indepth_scan) + { + // Subtitles are set by hb_add() during subtitle scan + return 0; + } /* Look for the scanned subtitle in the existing subtitle list * select_subtitle implies that we did a scan. */ - if( !job->indepth_scan && interjob->select_subtitle ) + if (interjob->select_subtitle != NULL) { /* Disable forced subtitles if we didn't find any in the scan, so that * we display normal subtitles instead. */ @@ -649,38 +763,31 @@ static void do_job(hb_job_t *job) { interjob->select_subtitle->config.force = 0; } - for( i = 0; i < hb_list_count( job->list_subtitle ); ) - { - subtitle = hb_list_item( job->list_subtitle, i ); - if( subtitle ) + for (i = 0; i < hb_list_count( job->list_subtitle );) + { + subtitle = hb_list_item(job->list_subtitle, i); + /* Remove the scanned subtitle from the list if + * it would result in: + * - an emty track (forced and no forced hits) + * - an identical, duplicate subtitle track: + * -> both (or neither) are forced + * -> subtitle is not forced but all its hits are forced */ + if( ( interjob->select_subtitle->id == subtitle->id ) && + ( ( subtitle->config.force && + interjob->select_subtitle->forced_hits == 0 ) || + ( subtitle->config.force == interjob->select_subtitle->config.force ) || + ( !subtitle->config.force && + interjob->select_subtitle->hits == interjob->select_subtitle->forced_hits ) ) ) { - /* Remove the scanned subtitle from the list if - * it would result in: - * - an emty track (forced and no forced hits) - * - an identical, duplicate subtitle track: - * -> both (or neither) are forced - * -> subtitle is not forced but all its hits are forced */ - if( ( interjob->select_subtitle->id == subtitle->id ) && - ( ( subtitle->config.force && - interjob->select_subtitle->forced_hits == 0 ) || - ( subtitle->config.force == interjob->select_subtitle->config.force ) || - ( !subtitle->config.force && - interjob->select_subtitle->hits == interjob->select_subtitle->forced_hits ) ) ) - { - hb_list_rem( job->list_subtitle, subtitle ); - free( subtitle ); - continue; - } - /* Adjust output track number, in case we removed one. - * Output tracks sadly still need to be in sequential order. - * Note: out.track starts at 1, i starts at 0, and track 1 is interjob->select_subtitle */ - subtitle->out_track = ++i + 1; - } - else - { - // avoid infinite loop is subtitle == NULL - i++; + hb_list_rem( job->list_subtitle, subtitle ); + free( subtitle ); + continue; } + /* Adjust output track number, in case we removed one. + * Output tracks sadly still need to be in sequential order. + * Note: out.track starts at 1, i starts at 0, and track 1 is + * interjob->select_subtitle */ + subtitle->out_track = ++i + 1; } /* Add the subtitle that we found on the subtitle scan pass. @@ -703,77 +810,307 @@ static void do_job(hb_job_t *job) } } - if ( !job->indepth_scan ) + for (i = 0; i < hb_list_count(job->list_subtitle);) { - // Sanitize subtitles - uint8_t one_burned = 0; - for( i = 0; i < hb_list_count( job->list_subtitle ); ) + subtitle = hb_list_item(job->list_subtitle, i); + if (subtitle->config.dest == RENDERSUB) { - subtitle = hb_list_item( job->list_subtitle, i ); - if ( subtitle->config.dest == RENDERSUB ) + if (one_burned) { - if ( one_burned ) + if (!hb_subtitle_can_pass(subtitle->source, job->mux)) { - if ( !hb_subtitle_can_pass(subtitle->source, job->mux) ) - { - hb_log( "More than one subtitle burn-in requested, dropping track %d.", i ); - hb_list_rem( job->list_subtitle, subtitle ); - free( subtitle ); - continue; - } - else - { - hb_log( "More than one subtitle burn-in requested. Changing track %d to soft subtitle.", i ); - subtitle->config.dest = PASSTHRUSUB; - } + hb_log( "More than one subtitle burn-in requested, dropping track %d.", i ); + hb_list_rem(job->list_subtitle, subtitle); + free(subtitle); + continue; } - else if ( !hb_subtitle_can_burn(subtitle->source) ) + else { - hb_log( "Subtitle burn-in requested and input track can not be rendered. Changing track %d to soft subtitle.", i ); + hb_log("More than one subtitle burn-in requested. Changing track %d to soft subtitle.", i); subtitle->config.dest = PASSTHRUSUB; } + } + else if (!hb_subtitle_can_burn(subtitle->source)) + { + hb_log("Subtitle burn-in requested and input track can not be rendered. Changing track %d to soft subtitle.", i); + subtitle->config.dest = PASSTHRUSUB; + } + else + { + one_burned = 1; + } + } + + if (subtitle->config.dest == PASSTHRUSUB && + !hb_subtitle_can_pass(subtitle->source, job->mux)) + { + if (!one_burned) + { + hb_log("Subtitle pass-thru requested and input track is not compatible with container. Changing track %d to burned-in subtitle.", i); + subtitle->config.dest = RENDERSUB; + subtitle->config.default_track = 0; + one_burned = 1; + } + else + { + hb_log("Subtitle pass-thru requested and input track is not compatible with container. One track already burned, dropping track %d.", i); + hb_list_rem(job->list_subtitle, subtitle); + free(subtitle); + continue; + } + } + /* Adjust output track number, in case we removed one. + * Output tracks sadly still need to be in sequential order. + * Note: out.track starts at 1, i starts at 0 */ + subtitle->out_track = ++i; + } + if (one_burned) + { + // Add subtitle rendering filter + // Note that if the filter is already in the filter chain, this + // has no effect. Note also that this means the front-end is + // not required to add the subtitle rendering filter since + // we will always try to do it here. + hb_filter_object_t *filter = hb_filter_init(HB_FILTER_RENDER_SUB); + hb_add_filter(job, filter, NULL); + } + + return 0; +} + +static int sanitize_audio(hb_job_t *job) +{ + int i; + hb_audio_t * audio; + + if (job->indepth_scan) + { + // Audio is not processed during subtitle scan + return 0; + } + + // apply Auto Passthru settings + hb_autopassthru_apply_settings(job); + + for (i = 0; i < hb_list_count(job->list_audio);) + { + audio = hb_list_item(job->list_audio, i); + if (audio->config.out.codec == HB_ACODEC_AUTO_PASS) + { + // Auto Passthru should have been handled above + // remove track to avoid a crash + hb_log("Auto Passthru error, dropping track %d", + audio->config.out.track); + hb_list_rem(job->list_audio, audio); + free(audio); + continue; + } + if ((audio->config.out.codec & HB_ACODEC_PASS_FLAG) && + !(audio->config.in.codec & + audio->config.out.codec & HB_ACODEC_PASS_MASK)) + { + hb_log("Passthru requested and input codec is not the same as output codec for track %d, dropping track", + audio->config.out.track); + hb_list_rem(job->list_audio, audio); + free(audio); + continue; + } + /* Adjust output track number, in case we removed one. + * Output tracks sadly still need to be in sequential order. + * Note: out.track starts at 1, i starts at 0 */ + audio->config.out.track = ++i; + } + + int best_mixdown = 0; + int best_bitrate = 0; + int best_samplerate = 0; + + for (i = 0; i < hb_list_count(job->list_audio); i++) + { + audio = hb_list_item(job->list_audio, i); + + /* Passthru audio */ + if (audio->config.out.codec & HB_ACODEC_PASS_FLAG) + { + // Muxer needs these to be set correctly in order to + // set audio track MP4 time base. + audio->config.out.samples_per_frame = + audio->config.in.samples_per_frame; + audio->config.out.samplerate = audio->config.in.samplerate; + continue; + } + + /* Vorbis language information */ + if (audio->config.out.codec == HB_ACODEC_VORBIS) + audio->priv.config.vorbis.language = audio->config.lang.simple; + + /* sense-check the requested samplerate */ + if (audio->config.out.samplerate <= 0) + { + // if not specified, set to same as input + audio->config.out.samplerate = audio->config.in.samplerate; + } + best_samplerate = + hb_audio_samplerate_get_best(audio->config.out.codec, + audio->config.out.samplerate, + NULL); + if (best_samplerate != audio->config.out.samplerate) + { + hb_log("work: sanitizing track %d unsupported samplerate %d Hz to %s kHz", + audio->config.out.track, audio->config.out.samplerate, + hb_audio_samplerate_get_name(best_samplerate)); + audio->config.out.samplerate = best_samplerate; + } + + /* sense-check the requested mixdown */ + if (audio->config.out.mixdown <= HB_AMIXDOWN_NONE) + { + /* Mixdown not specified, set the default mixdown */ + audio->config.out.mixdown = + hb_mixdown_get_default(audio->config.out.codec, + audio->config.in.channel_layout); + hb_log("work: mixdown not specified, track %d setting mixdown %s", + audio->config.out.track, + hb_mixdown_get_name(audio->config.out.mixdown)); + } + else + { + best_mixdown = + hb_mixdown_get_best(audio->config.out.codec, + audio->config.in.channel_layout, + audio->config.out.mixdown); + if (audio->config.out.mixdown != best_mixdown) + { + /* log the output mixdown */ + hb_log("work: sanitizing track %d mixdown %s to %s", + audio->config.out.track, + hb_mixdown_get_name(audio->config.out.mixdown), + hb_mixdown_get_name(best_mixdown)); + audio->config.out.mixdown = best_mixdown; + } + } + + /* sense-check the requested compression level */ + if (audio->config.out.compression_level < 0) + { + audio->config.out.compression_level = + hb_audio_compression_get_default(audio->config.out.codec); + if (audio->config.out.compression_level >= 0) + { + hb_log("work: compression level not specified, track %d setting compression level %.2f", + audio->config.out.track, + audio->config.out.compression_level); + } + } + else + { + float best_compression = + hb_audio_compression_get_best(audio->config.out.codec, + audio->config.out.compression_level); + if (best_compression != audio->config.out.compression_level) + { + if (best_compression == -1) + { + hb_log("work: track %d, compression level not supported by codec", + audio->config.out.track); + } else { - one_burned = 1; + hb_log("work: sanitizing track %d compression level %.2f to %.2f", + audio->config.out.track, + audio->config.out.compression_level, + best_compression); } + audio->config.out.compression_level = best_compression; } + } - if ( subtitle->config.dest == PASSTHRUSUB && - !hb_subtitle_can_pass(subtitle->source, job->mux) ) + /* sense-check the requested quality */ + if (audio->config.out.quality != HB_INVALID_AUDIO_QUALITY) + { + float best_quality = + hb_audio_quality_get_best(audio->config.out.codec, + audio->config.out.quality); + if (best_quality != audio->config.out.quality) { - if ( !one_burned ) + if (best_quality == HB_INVALID_AUDIO_QUALITY) { - hb_log( "Subtitle pass-thru requested and input track is not compatible with container. Changing track %d to burned-in subtitle.", i ); - subtitle->config.dest = RENDERSUB; - subtitle->config.default_track = 0; - one_burned = 1; + hb_log("work: track %d, quality mode not supported by codec", + audio->config.out.track); } else { - hb_log( "Subtitle pass-thru requested and input track is not compatible with container. One track already burned, dropping track %d.", i ); - hb_list_rem( job->list_subtitle, subtitle ); - free( subtitle ); - continue; + hb_log("work: sanitizing track %d quality %.2f to %.2f", + audio->config.out.track, + audio->config.out.quality, best_quality); } + audio->config.out.quality = best_quality; } - /* Adjust output track number, in case we removed one. - * Output tracks sadly still need to be in sequential order. - * Note: out.track starts at 1, i starts at 0 */ - subtitle->out_track = ++i; } - if (one_burned) + + /* sense-check the requested bitrate */ + if (audio->config.out.quality == HB_INVALID_AUDIO_QUALITY) { - // Add subtitle rendering filter - // Note that if the filter is already in the filter chain, this - // has no effect. Note also that this means the front-end is - // not required to add the subtitle rendering filter since - // we will always try to do it here. - hb_filter_object_t *filter = hb_filter_init(HB_FILTER_RENDER_SUB); - hb_add_filter(job, filter, NULL); + if (audio->config.out.bitrate <= 0) + { + /* Bitrate not specified, set the default bitrate */ + audio->config.out.bitrate = + hb_audio_bitrate_get_default(audio->config.out.codec, + audio->config.out.samplerate, + audio->config.out.mixdown); + if (audio->config.out.bitrate > 0) + { + hb_log("work: bitrate not specified, track %d setting bitrate %d Kbps", + audio->config.out.track, + audio->config.out.bitrate); + } + } + else + { + best_bitrate = + hb_audio_bitrate_get_best(audio->config.out.codec, + audio->config.out.bitrate, + audio->config.out.samplerate, + audio->config.out.mixdown); + if (best_bitrate > 0 && + best_bitrate != audio->config.out.bitrate) + { + /* log the output bitrate */ + hb_log("work: sanitizing track %d bitrate %d to %d Kbps", + audio->config.out.track, + audio->config.out.bitrate, best_bitrate); + } + audio->config.out.bitrate = best_bitrate; + } + } + + /* sense-check the requested dither */ + if (hb_audio_dither_is_supported(audio->config.out.codec)) + { + if (audio->config.out.dither_method == + hb_audio_dither_get_default()) + { + /* "auto", enable with default settings */ + audio->config.out.dither_method = + hb_audio_dither_get_default_method(); + } + } + else if (audio->config.out.dither_method != + hb_audio_dither_get_default()) + { + /* specific dither requested but dithering not supported */ + hb_log("work: track %d, dithering not supported by codec", + audio->config.out.track); } } + return 0; +} +static int sanitize_qsv( hb_job_t * job ) +{ #ifdef USE_QSV + int i; + /* * XXX: mfxCoreInterface's CopyFrame doesn't work in old drivers, and our * workaround is really slow. If we have validated CPU-based filters in @@ -848,7 +1185,7 @@ static void do_job(hb_job_t *job) { // cropping and scaling always done via VPP filter case HB_FILTER_CROP_SCALE: - if (filter->settings == NULL || *filter->settings == '\0') + if (filter->settings == NULL || *filter->settings == 0) { // VPP defaults were set above, so not a problem // however, this should never happen, print an error @@ -936,6 +1273,93 @@ static void do_job(hb_job_t *job) } #endif + return 0; +} + +/** + * Job initialization rountine. + * + * Initializes fifos. + * Creates work objects for synchronizer, video decoder, video renderer, + * video decoder, audio decoder, audio encoder, reader, muxer. + * Launches thread for each work object with work_loop. + * Waits for completion of last work object. + * Closes threads and frees fifos. + * @param job Handle work hb_job_t. + */ +static void do_job(hb_job_t *job) +{ + int i, result; + hb_title_t * title; + hb_interjob_t * interjob; + hb_work_object_t * w; + hb_audio_t * audio; + hb_subtitle_t * subtitle; + + title = job->title; + + interjob = hb_interjob_get(job->h); + job->orig_vrate = job->vrate; + if (job->sequence_id != interjob->sequence_id) + { + // New job sequence, clear interjob + hb_subtitle_close(&interjob->select_subtitle); + memset(interjob, 0, sizeof(*interjob)); + interjob->sequence_id = job->sequence_id; + } + else if (job->pass_id == HB_PASS_ENCODE_2ND) + { + correct_framerate(interjob, job); + } + + job->list_work = hb_list_init(); + w = hb_get_work(job->h, WORK_READER); + hb_list_add(job->list_work, w); + + /* + * OpenCL + * + * Note: we delay hb_ocl_init until here, since they're no point it loading + * the library if we aren't going to use it. But we only call hb_ocl_close + * in hb_global_close, since un/reloading the library each run is wasteful. + */ + if (job->use_opencl) + { + if (hb_ocl_init() || hb_init_opencl_run_env(0, NULL, "-I.")) + { + hb_log("work: failed to initialize OpenCL environment, using fallback"); + hb_release_opencl_run_env(); + job->use_opencl = 0; + } + } + else + { + // we're not (re-)using OpenCL here, we can release the environment + hb_release_opencl_run_env(); + } + + hb_log( "starting job" ); + + // This must be performed before initializing filters because + // it can add the subtitle render filter. + result = sanitize_subtitles(job); + if (result) + { + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; + goto cleanup; + } + + // sanitize_qsv looks for subtitle render filter, so must happen after + // sanitize_subtitle + result = sanitize_qsv(job); + if (result) + { + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; + goto cleanup; + } + #ifdef USE_HWD /* * Check support for and enable DXVA2-accelerated when applicable; we need: @@ -955,7 +1379,7 @@ static void do_job(hb_job_t *job) // Filters have an effect on settings. // So initialize the filters and update the job. - if( job->list_filter && hb_list_count( job->list_filter ) ) + if (job->list_filter && hb_list_count(job->list_filter)) { hb_filter_init_t init; @@ -966,13 +1390,14 @@ static void do_job(hb_job_t *job) init.geometry.par = job->par; memcpy(init.crop, title->crop, sizeof(int[4])); - init.vrate = title->vrate; + init.vrate = job->vrate; init.cfr = 0; init.grayscale = 0; for( i = 0; i < hb_list_count( job->list_filter ); ) { hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); - if( filter->init( filter, &init ) ) + filter->done = &job->done; + if (filter->init(filter, &init)) { hb_log( "Failure to initialise filter '%s', disabling", filter->name ); @@ -1018,16 +1443,6 @@ static void do_job(hb_job_t *job) } /* - * MPEG-4 Part 2 stores the PAR num/den as unsigned 8-bit fields, - * and libavcodec's encoder fails to initialize if we don't handle it. - */ - if (job->vcodec == HB_VCODEC_FFMPEG_MPEG4) - { - hb_limit_rational(&job->par.num, &job->par.den, - job->par.num, job->par.den, 255); - } - - /* * The frame rate may affect the bitstream's time base, lose superfluous * factors for consistency (some encoders reduce fractions, some don't). */ @@ -1053,285 +1468,133 @@ static void do_job(hb_job_t *job) job->fifo_render = NULL; // Attached to filter chain } - /* Audio fifos must be initialized before sync */ - if (!job->indepth_scan) + result = sanitize_audio(job); + if (result) { - // apply Auto Passthru settings - hb_autopassthru_apply_settings(job); - // sanitize audio settings - for (i = 0; i < hb_list_count(job->list_audio);) - { - audio = hb_list_item(job->list_audio, i); - if (audio->config.out.codec == HB_ACODEC_AUTO_PASS) - { - // Auto Passthru should have been handled above - // remove track to avoid a crash - hb_log("Auto Passthru error, dropping track %d", - audio->config.out.track); - hb_list_rem(job->list_audio, audio); - free(audio); - continue; - } - if ((audio->config.out.codec & HB_ACODEC_PASS_FLAG) && - !(audio->config.in.codec & - audio->config.out.codec & HB_ACODEC_PASS_MASK)) - { - hb_log("Passthru requested and input codec is not the same as output codec for track %d, dropping track", - audio->config.out.track); - hb_list_rem(job->list_audio, audio); - free(audio); - continue; - } - /* Adjust output track number, in case we removed one. - * Output tracks sadly still need to be in sequential order. - * Note: out.track starts at 1, i starts at 0 */ - audio->config.out.track = ++i; - } - - int best_mixdown = 0; - int best_bitrate = 0; - int best_samplerate = 0; + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; + goto cleanup; + } + if (!job->indepth_scan) + { + // Set up audio decoder work objects + // Audio fifos must be initialized before sync for (i = 0; i < hb_list_count(job->list_audio); i++) { audio = hb_list_item(job->list_audio, i); - /* set up the audio work structures */ + /* set up the audio work fifos */ audio->priv.fifo_raw = hb_fifo_init(FIFO_SMALL, FIFO_SMALL_WAKE); audio->priv.fifo_sync = hb_fifo_init(FIFO_SMALL, FIFO_SMALL_WAKE); audio->priv.fifo_out = hb_fifo_init(FIFO_LARGE, FIFO_LARGE_WAKE); audio->priv.fifo_in = hb_fifo_init(FIFO_LARGE, FIFO_LARGE_WAKE); - /* Passthru audio */ - if (audio->config.out.codec & HB_ACODEC_PASS_FLAG) - { - // Muxer needs these to be set correctly in order to - // set audio track MP4 time base. - audio->config.out.samples_per_frame = - audio->config.in.samples_per_frame; - audio->config.out.samplerate = audio->config.in.samplerate; - continue; - } - - /* Vorbis language information */ - if (audio->config.out.codec == HB_ACODEC_VORBIS) - audio->priv.config.vorbis.language = audio->config.lang.simple; - - /* sense-check the requested samplerate */ - if (audio->config.out.samplerate <= 0) - { - // if not specified, set to same as input - audio->config.out.samplerate = audio->config.in.samplerate; - } - best_samplerate = - hb_audio_samplerate_get_best(audio->config.out.codec, - audio->config.out.samplerate, - NULL); - if (best_samplerate != audio->config.out.samplerate) - { - hb_log("work: sanitizing track %d unsupported samplerate %d Hz to %s kHz", - audio->config.out.track, audio->config.out.samplerate, - hb_audio_samplerate_get_name(best_samplerate)); - audio->config.out.samplerate = best_samplerate; - } - - /* sense-check the requested mixdown */ - if (audio->config.out.mixdown <= HB_AMIXDOWN_NONE) - { - /* Mixdown not specified, set the default mixdown */ - audio->config.out.mixdown = - hb_mixdown_get_default(audio->config.out.codec, - audio->config.in.channel_layout); - hb_log("work: mixdown not specified, track %d setting mixdown %s", - audio->config.out.track, - hb_mixdown_get_name(audio->config.out.mixdown)); - } - else - { - best_mixdown = - hb_mixdown_get_best(audio->config.out.codec, - audio->config.in.channel_layout, - audio->config.out.mixdown); - if (audio->config.out.mixdown != best_mixdown) - { - /* log the output mixdown */ - hb_log("work: sanitizing track %d mixdown %s to %s", - audio->config.out.track, - hb_mixdown_get_name(audio->config.out.mixdown), - hb_mixdown_get_name(best_mixdown)); - audio->config.out.mixdown = best_mixdown; - } - } - - /* sense-check the requested compression level */ - if (audio->config.out.compression_level < 0) + // Add audio decoder work object + w = hb_audio_decoder(job->h, audio->config.in.codec); + if (w == NULL) { - audio->config.out.compression_level = - hb_audio_compression_get_default(audio->config.out.codec); - if (audio->config.out.compression_level >= 0) - { - hb_log("work: compression level not specified, track %d setting compression level %.2f", - audio->config.out.track, - audio->config.out.compression_level); - } - } - else - { - float best_compression = - hb_audio_compression_get_best(audio->config.out.codec, - audio->config.out.compression_level); - if (best_compression != audio->config.out.compression_level) - { - if (best_compression == -1) - { - hb_log("work: track %d, compression level not supported by codec", - audio->config.out.track); - } - else - { - hb_log("work: sanitizing track %d compression level %.2f to %.2f", - audio->config.out.track, - audio->config.out.compression_level, - best_compression); - } - audio->config.out.compression_level = best_compression; - } + hb_error("Invalid input codec: %d", audio->config.in.codec); + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; + goto cleanup; } + w->fifo_in = audio->priv.fifo_in; + w->fifo_out = audio->priv.fifo_raw; + w->config = &audio->priv.config; + w->audio = audio; + w->codec_param = audio->config.in.codec_param; - /* sense-check the requested quality */ - if (audio->config.out.quality != HB_INVALID_AUDIO_QUALITY) - { - float best_quality = - hb_audio_quality_get_best(audio->config.out.codec, - audio->config.out.quality); - if (best_quality != audio->config.out.quality) - { - if (best_quality == HB_INVALID_AUDIO_QUALITY) - { - hb_log("work: track %d, quality mode not supported by codec", - audio->config.out.track); - } - else - { - hb_log("work: sanitizing track %d quality %.2f to %.2f", - audio->config.out.track, - audio->config.out.quality, best_quality); - } - audio->config.out.quality = best_quality; - } - } - - /* sense-check the requested bitrate */ - if (audio->config.out.quality == HB_INVALID_AUDIO_QUALITY) - { - if (audio->config.out.bitrate <= 0) - { - /* Bitrate not specified, set the default bitrate */ - audio->config.out.bitrate = - hb_audio_bitrate_get_default(audio->config.out.codec, - audio->config.out.samplerate, - audio->config.out.mixdown); - if (audio->config.out.bitrate > 0) - { - hb_log("work: bitrate not specified, track %d setting bitrate %d Kbps", - audio->config.out.track, - audio->config.out.bitrate); - } - } - else - { - best_bitrate = - hb_audio_bitrate_get_best(audio->config.out.codec, - audio->config.out.bitrate, - audio->config.out.samplerate, - audio->config.out.mixdown); - if (best_bitrate > 0 && - best_bitrate != audio->config.out.bitrate) - { - /* log the output bitrate */ - hb_log("work: sanitizing track %d bitrate %d to %d Kbps", - audio->config.out.track, - audio->config.out.bitrate, best_bitrate); - } - audio->config.out.bitrate = best_bitrate; - } - } - - /* sense-check the requested dither */ - if (hb_audio_dither_is_supported(audio->config.out.codec)) - { - if (audio->config.out.dither_method == - hb_audio_dither_get_default()) - { - /* "auto", enable with default settings */ - audio->config.out.dither_method = - hb_audio_dither_get_default_method(); - } - } - else if (audio->config.out.dither_method != - hb_audio_dither_get_default()) - { - /* specific dither requested but dithering not supported */ - hb_log("work: track %d, dithering not supported by codec", - audio->config.out.track); - } + hb_list_add( job->list_work, w ); } } - /* Synchronization */ - sync = hb_sync_init( job ); + // Subtitle fifos must be initialized before sync + for (i = 0; i < hb_list_count( job->list_subtitle ); i++) + { + subtitle = hb_list_item( job->list_subtitle, i ); + // Must set capacity of the raw-FIFO to be set >= the maximum + // number of subtitle lines that could be decoded prior to a + // video frame in order to prevent the following deadlock + // condition: + // 1. Subtitle decoder blocks trying to generate more subtitle + // lines than will fit in the FIFO. + // 2. Blocks the processing of further subtitle packets read + // from the input stream. + // 3. And that blocks the processing of any further video + // packets read from the input stream. + // 4. And that blocks the sync work-object from running, which + // is needed to consume the subtitle lines in the raw-FIFO. + // Since that number is unbounded, the FIFO must be made + // (effectively) unbounded in capacity. + subtitle->fifo_raw = hb_fifo_init( FIFO_UNBOUNDED, FIFO_UNBOUNDED_WAKE ); + subtitle->fifo_in = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); + subtitle->fifo_sync = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); + subtitle->fifo_out = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); + + w = hb_get_work( job->h, subtitle->codec ); + w->fifo_in = subtitle->fifo_in; + w->fifo_out = subtitle->fifo_raw; + w->subtitle = subtitle; + hb_list_add( job->list_work, w ); + } - /* Video decoder */ - if (title->video_codec == WORK_NONE) + // Video decoder + w = hb_video_decoder(job->h, title->video_codec, title->video_codec_param); + if (w == NULL) { - hb_error("No video decoder set!"); + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; goto cleanup; } - hb_list_add(job->list_work, (w = hb_get_work(job->h, title->video_codec))); w->yield = 1; // decoders yield to keep sync fifos more even - w->codec_param = title->video_codec_param; w->fifo_in = job->fifo_mpeg2; w->fifo_out = job->fifo_raw; + hb_list_add(job->list_work, w); - for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) - { - subtitle = hb_list_item( job->list_subtitle, i ); + // Synchronization + w = hb_get_work(job->h, WORK_SYNC_VIDEO); + hb_list_add(job->list_work, w); - if( subtitle ) + if (!job->indepth_scan) + { + for( i = 0; i < hb_list_count( job->list_audio ); i++ ) { - subtitle->fifo_in = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); - // Must set capacity of the raw-FIFO to be set >= the maximum number of subtitle - // lines that could be decoded prior to a video frame in order to prevent the following - // deadlock condition: - // 1. Subtitle decoder blocks trying to generate more subtitle lines than will fit in the FIFO. - // 2. Blocks the processing of further subtitle packets read from the input stream. - // 3. And that blocks the processing of any further video packets read from the input stream. - // 4. And that blocks the sync work-object from running, which is needed to consume the subtitle lines in the raw-FIFO. - // Since that number is unbounded, the FIFO must be made (effectively) unbounded in capacity. - subtitle->fifo_raw = hb_fifo_init( FIFO_UNBOUNDED, FIFO_UNBOUNDED_WAKE ); - subtitle->fifo_sync = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); - subtitle->fifo_out = hb_fifo_init( FIFO_SMALL, FIFO_SMALL_WAKE ); - - w = hb_get_work( job->h, subtitle->codec ); - w->fifo_in = subtitle->fifo_in; - w->fifo_out = subtitle->fifo_raw; - w->subtitle = subtitle; - hb_list_add( job->list_work, w ); + audio = hb_list_item( job->list_audio, i ); + + /* + * Audio Encoder Thread + */ + if ( !(audio->config.out.codec & HB_ACODEC_PASS_FLAG ) ) + { + /* + * Add the encoder thread if not doing pass through + */ + w = hb_audio_encoder( job->h, audio->config.out.codec); + if (w == NULL) + { + hb_error("Invalid audio codec: %#x", audio->config.out.codec); + w = NULL; + *job->done_error = HB_ERROR_WRONG_INPUT; + *job->die = 1; + goto cleanup; + } + w->fifo_in = audio->priv.fifo_sync; + w->fifo_out = audio->priv.fifo_out; + w->config = &audio->priv.config; + w->audio = audio; + + hb_list_add( job->list_work, w ); + } } - } - /* Set up the video filter fifo pipeline */ - if( !job->indepth_scan ) - { - if( job->list_filter ) + /* Set up the video filter fifo pipeline */ + if ( job->list_filter ) { - int filter_count = hb_list_count( job->list_filter ); - int i; hb_fifo_t * fifo_in = job->fifo_sync; - - for( i = 0; i < filter_count; i++ ) + for (i = 0; i < hb_list_count(job->list_filter); i++) { - hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); + hb_filter_object_t * filter = hb_list_item(job->list_filter, i); filter->fifo_in = fifo_in; filter->fifo_out = hb_fifo_init( FIFO_MINI, FIFO_MINI_WAKE ); @@ -1345,38 +1608,16 @@ static void do_job(hb_job_t *job) job->fifo_render = NULL; } - /* Video encoder */ - switch( job->vcodec ) + // Video encoder + w = hb_video_encoder(job->h, job->vcodec); + if (w == NULL) { - case HB_VCODEC_FFMPEG_MPEG4: - w = hb_get_work( job->h, WORK_ENCAVCODEC ); - w->codec_param = AV_CODEC_ID_MPEG4; - break; - case HB_VCODEC_FFMPEG_MPEG2: - w = hb_get_work( job->h, WORK_ENCAVCODEC ); - w->codec_param = AV_CODEC_ID_MPEG2VIDEO; - break; - case HB_VCODEC_FFMPEG_VP8: - w = hb_get_work( job->h, WORK_ENCAVCODEC ); - w->codec_param = AV_CODEC_ID_VP8; - break; - case HB_VCODEC_X264: - w = hb_get_work( job->h, WORK_ENCX264 ); - break; - case HB_VCODEC_QSV_H264: - case HB_VCODEC_QSV_H265: - w = hb_get_work( job->h, WORK_ENCQSV ); - break; - case HB_VCODEC_THEORA: - w = hb_get_work( job->h, WORK_ENCTHEORA ); - break; -#ifdef USE_X265 - case HB_VCODEC_X265: - w = hb_get_work( job->h, WORK_ENCX265 ); - break; -#endif + *job->done_error = HB_ERROR_INIT; + *job->die = 1; + goto cleanup; } - // Handle case where there are no filters. + + // Handle case where there are no filters. // This really should never happen. if ( job->fifo_render ) w->fifo_in = job->fifo_render; @@ -1388,59 +1629,17 @@ static void do_job(hb_job_t *job) hb_list_add( job->list_work, w ); - for( i = 0; i < hb_list_count( job->list_audio ); i++ ) - { - audio = hb_list_item( job->list_audio, i ); - - /* - * Audio Decoder Thread - */ - if ( audio->priv.fifo_in ) - { - w = hb_codec_decoder(job->h, audio->config.in.codec); - if (w == NULL) - { - hb_error("Invalid input codec: %d", audio->config.in.codec); - *job->done_error = HB_ERROR_WRONG_INPUT; - *job->die = 1; - goto cleanup; - } - w->fifo_in = audio->priv.fifo_in; - w->fifo_out = audio->priv.fifo_raw; - w->config = &audio->priv.config; - w->audio = audio; - w->codec_param = audio->config.in.codec_param; - - hb_list_add( job->list_work, w ); - } - - /* - * Audio Encoder Thread - */ - if ( !(audio->config.out.codec & HB_ACODEC_PASS_FLAG ) ) - { - /* - * Add the encoder thread if not doing AC-3 pass through - */ - w = hb_codec_encoder( job->h, audio->config.out.codec); - if (w == NULL) - { - hb_error("Invalid audio codec: %#x", audio->config.out.codec); - w = NULL; - *job->done_error = HB_ERROR_WRONG_INPUT; - *job->die = 1; - goto cleanup; - } - w->fifo_in = audio->priv.fifo_sync; - w->fifo_out = audio->priv.fifo_out; - w->config = &audio->priv.config; - w->audio = audio; + } - hb_list_add( job->list_work, w ); - } - } + // Add Muxer work object + // Muxer work object should be the last object added to the list + // during regular encoding pass. For subtitle scan, sync is last. + if (!job->indepth_scan) + { + w = hb_get_work(job->h, WORK_MUX); + hb_list_add(job->list_work, w); } - + if( job->chapter_markers && job->chapter_start == job->chapter_end ) { job->chapter_markers = 0; @@ -1450,200 +1649,90 @@ static void do_job(hb_job_t *job) /* Display settings */ hb_display_job_info( job ); - /* Init read & write threads */ - if ( reader->init( reader, job ) ) - { - hb_error( "Failure to initialise thread '%s'", reader->name ); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - goto cleanup; - } - reader->done = &job->done; - reader->thread = hb_thread_init( reader->name, ReadLoop, reader, HB_NORMAL_PRIORITY ); - + // Initialize all work objects job->done = 0; - - if( job->list_filter && !job->indepth_scan ) - { - int filter_count = hb_list_count( job->list_filter ); - int i; - - for( i = 0; i < filter_count; i++ ) - { - hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); - - if( !filter ) continue; - - // Filters were initialized earlier, so we just need - // to start the filter's thread - filter->done = &job->done; - filter->thread = hb_thread_init( filter->name, filter_loop, filter, - HB_LOW_PRIORITY ); - } - } - - /* Launch processing threads */ - for( i = 0; i < hb_list_count( job->list_work ); i++ ) + for (i = 0; i < hb_list_count( job->list_work ); i++) { w = hb_list_item( job->list_work, i ); w->done = &job->done; - w->thread_sleep_interval = 10; - if( w->init( w, job ) ) + if (w->init( w, job )) { hb_error( "Failure to initialise thread '%s'", w->name ); *job->done_error = HB_ERROR_INIT; *job->die = 1; goto cleanup; } - w->thread = hb_thread_init( w->name, work_loop, w, - HB_LOW_PRIORITY ); } - if ( job->indepth_scan ) - { - muxer = NULL; - w = sync; - sync->done = &job->done; - } - else + /* Launch processing threads */ + for (i = 0; i < hb_list_count( job->list_work ); i++) { - sync->done = &job->done; - sync->thread_sleep_interval = 10; - if( sync->init( w, job ) ) - { - hb_error( "Failure to initialise thread '%s'", w->name ); - *job->done_error = HB_ERROR_INIT; - *job->die = 1; - goto cleanup; - } - sync->thread = hb_thread_init( sync->name, work_loop, sync, - HB_LOW_PRIORITY ); - - // The muxer requires track information that's set up by the encoder - // init routines so we have to init the muxer last. - muxer = hb_muxer_init( job ); - w = muxer; + w = hb_list_item(job->list_work, i); + w->thread = hb_thread_init(w->name, hb_work_loop, w, HB_LOW_PRIORITY); } - - hb_buffer_t * buf_in, * buf_out = NULL; - - while ( !*job->die && !*w->done && w->status != HB_WORK_DONE ) + if (job->list_filter && !job->indepth_scan) { - buf_in = hb_fifo_get_wait( w->fifo_in ); - if ( buf_in == NULL ) - continue; - if ( *job->die ) + for (i = 0; i < hb_list_count(job->list_filter); i++) { - if( buf_in ) - { - hb_buffer_close( &buf_in ); - } - break; - } - - buf_out = NULL; - w->status = w->work( w, &buf_in, &buf_out ); + hb_filter_object_t * filter = hb_list_item(job->list_filter, i); - if( buf_in ) - { - hb_buffer_close( &buf_in ); - } - if ( buf_out && w->fifo_out == NULL ) - { - hb_buffer_close( &buf_out ); - } - if( buf_out ) - { - while ( !*job->die ) - { - if ( hb_fifo_full_wait( w->fifo_out ) ) - { - hb_fifo_push( w->fifo_out, buf_out ); - buf_out = NULL; - break; - } - } + // Filters were initialized earlier, so we just need + // to start the filter's thread + filter->thread = hb_thread_init( filter->name, filter_loop, filter, + HB_LOW_PRIORITY ); } } - if ( buf_out ) - { - hb_buffer_close( &buf_out ); - } + // Wait for the thread of the last work object to complete + w = hb_list_item(job->list_work, hb_list_count(job->list_work) - 1); + w->die = job->die; + hb_thread_close(&w->thread); hb_handle_t * h = job->h; hb_state_t state; hb_get_state2( h, &state ); - hb_log("work: average encoding speed for job is %f fps", state.param.working.rate_avg); - - job->done = 1; - if( muxer != NULL ) - { - muxer->close( muxer ); - free( muxer ); - - if( sync->thread != NULL ) - { - hb_thread_close( &sync->thread ); - sync->close( sync ); - } - free( sync ); - } + hb_log("work: average encoding speed for job is %f fps", + state.param.working.rate_avg); cleanup: - /* Stop the write thread (thread_close will block until the muxer finishes) */ job->done = 1; // Close render filter pipeline - if( job->list_filter ) + if (job->list_filter) { - int filter_count = hb_list_count( job->list_filter ); - int i; - - for( i = 0; i < filter_count; i++ ) + for (i = 0; i < hb_list_count(job->list_filter); i++) { - hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); - - if( !filter ) continue; - + hb_filter_object_t * filter = hb_list_item(job->list_filter, i); if( filter->thread != NULL ) { - hb_thread_close( &filter->thread ); + hb_thread_close(&filter->thread); } - filter->close( filter ); + filter->close(filter); } } /* Close work objects */ - while( ( w = hb_list_item( job->list_work, 0 ) ) ) + while ((w = hb_list_item(job->list_work, 0))) { - hb_list_rem( job->list_work, w ); - if( w->thread != NULL ) + hb_list_rem(job->list_work, w); + if (w->thread != NULL) { - hb_thread_close( &w->thread ); - w->close( w ); + hb_thread_close(&w->thread); } - free( w ); + w->close(w); + free(w); } hb_list_close( &job->list_work ); - /* Stop the read thread */ - if( reader->thread != NULL ) - { - hb_thread_close( &reader->thread ); - reader->close( reader ); - } - free( reader ); - /* Close fifos */ hb_fifo_close( &job->fifo_mpeg2 ); hb_fifo_close( &job->fifo_raw ); hb_fifo_close( &job->fifo_sync ); hb_fifo_close( &job->fifo_mpeg4 ); - for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) + for (i = 0; i < hb_list_count( job->list_subtitle ); i++) { subtitle = hb_list_item( job->list_subtitle, i ); if( subtitle ) @@ -1654,7 +1743,7 @@ cleanup: hb_fifo_close( &subtitle->fifo_out ); } } - for( i = 0; i < hb_list_count( job->list_audio ); i++ ) + for (i = 0; i < hb_list_count( job->list_audio ); i++) { audio = hb_list_item( job->list_audio, i ); if( audio->priv.fifo_in != NULL ) @@ -1667,94 +1756,22 @@ cleanup: hb_fifo_close( &audio->priv.fifo_out ); } - if( job->list_filter ) + if (job->list_filter) { - for( i = 0; i < hb_list_count( job->list_filter ); i++ ) + for (i = 0; i < hb_list_count( job->list_filter ); i++) { hb_filter_object_t * filter = hb_list_item( job->list_filter, i ); hb_fifo_close( &filter->fifo_out ); } } - if( job->indepth_scan ) + if (job->indepth_scan) { - /* Before closing the title print out our subtitle stats if we need to - * find the highest and lowest. */ - for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) - { - subtitle = hb_list_item( job->list_subtitle, i ); - - hb_log( "Subtitle track %d (id 0x%x) '%s': %d hits (%d forced)", - subtitle->track, subtitle->id, subtitle->lang, - subtitle->hits, subtitle->forced_hits ); - - if( subtitle->hits == 0 ) - continue; - - if( subtitle_highest < subtitle->hits ) - { - subtitle_highest = subtitle->hits; - } - - if( subtitle_lowest == 0 || - subtitle_lowest > subtitle->hits ) - { - subtitle_lowest = subtitle->hits; - subtitle_lowest_id = subtitle->id; - } - - // pick the track with fewest forced hits - if( subtitle->forced_hits > 0 && - ( subtitle_forced_hits == 0 || - subtitle_forced_hits > subtitle->forced_hits ) ) - { - subtitle_forced_id = subtitle->id; - subtitle_forced_hits = subtitle->forced_hits; - } - } - - if( subtitle_forced_id && job->select_subtitle_config.force ) - { - /* If there is a subtitle stream with forced subtitles and forced-only - * is set, then select it in preference to the lowest. */ - subtitle_hit = subtitle_forced_id; - hb_log( "Found a subtitle candidate with id 0x%x (contains forced subs)", - subtitle_hit ); - } - else if( subtitle_lowest > 0 && - subtitle_lowest < ( subtitle_highest * 0.1 ) ) - { - /* OK we have more than one, and the lowest is lower, - * but how much lower to qualify for turning it on by - * default? - * - * Let's say 10% as a default. */ - subtitle_hit = subtitle_lowest_id; - hb_log( "Found a subtitle candidate with id 0x%x", subtitle_hit ); - } - else - { - hb_log( "No candidate detected during subtitle scan" ); - } - - for( i = 0; i < hb_list_count( job->list_subtitle ); i++ ) - { - subtitle = hb_list_item( job->list_subtitle, i ); - if( subtitle->id == subtitle_hit ) - { - subtitle->config = job->select_subtitle_config; - // Remove from list since we are taking ownership - // of the subtitle. - hb_list_rem( job->list_subtitle, subtitle ); - interjob->select_subtitle = subtitle; - break; - } - } + analyze_subtitle_scan(job); } hb_buffer_pool_free(); - - hb_job_close( &job ); + hb_job_close(&job); } static inline void copy_chapter( hb_buffer_t * dst, hb_buffer_t * src ) @@ -1778,23 +1795,28 @@ static inline void copy_chapter( hb_buffer_t * dst, hb_buffer_t * src ) * Exits loop when work indiactor is set. * @param _w Handle to work object. */ -static void work_loop( void * _w ) +void hb_work_loop( void * _w ) { hb_work_object_t * w = _w; hb_buffer_t * buf_in = NULL, * buf_out = NULL; - while( !*w->done && w->status != HB_WORK_DONE ) + while ((w->die == NULL || !*w->die) && !*w->done && + w->status != HB_WORK_DONE) { - buf_in = hb_fifo_get_wait( w->fifo_in ); - if ( buf_in == NULL ) - continue; - if ( *w->done ) + // fifo_in == NULL means this is a data source (e.g. reader) + if (w->fifo_in != NULL) { - if( buf_in ) + buf_in = hb_fifo_get_wait( w->fifo_in ); + if ( buf_in == NULL ) + continue; + if ( *w->done ) { - hb_buffer_close( &buf_in ); + if( buf_in ) + { + hb_buffer_close( &buf_in ); + } + break; } - break; } // Invalidate buf_out so that if there is no output // we don't try to pass along junk. @@ -1832,20 +1854,11 @@ static void work_loop( void * _w ) { hb_buffer_close( &buf_out ); } - - // Consume data in incoming fifo till job complete so that - // residual data does not stall the pipeline - while( !*w->done ) - { - buf_in = hb_fifo_get_wait( w->fifo_in ); - if ( buf_in != NULL ) - hb_buffer_close( &buf_in ); - } } /** * Performs the filter object's specific work function. - * Loops calling work function for associated filter object. + * Loops calling work function for associated filter object. * Sleeps when fifo is full. * Monitors work done indicator. * Exits loop when work indiactor is set. |