diff options
-rw-r--r-- | gtk/src/callbacks.c | 14 | ||||
-rw-r--r-- | gtk/src/ghb.m4 | 29 | ||||
-rw-r--r-- | libhb/common.h | 8 | ||||
-rw-r--r-- | libhb/hb_json.c | 8 | ||||
-rw-r--r-- | libhb/preset.c | 3 | ||||
-rw-r--r-- | libhb/preset_builtin.h | 28 | ||||
-rw-r--r-- | libhb/sync.c | 368 | ||||
-rw-r--r-- | preset/preset_builtin.json | 25 | ||||
-rw-r--r-- | preset/preset_builtin.list | 2 | ||||
-rw-r--r-- | preset/preset_template.json | 1 | ||||
-rw-r--r-- | test/test.c | 7 |
11 files changed, 423 insertions, 70 deletions
diff --git a/gtk/src/callbacks.c b/gtk/src/callbacks.c index c195a2584..5f7c04b8e 100644 --- a/gtk/src/callbacks.c +++ b/gtk/src/callbacks.c @@ -1014,7 +1014,8 @@ update_title_duration(signal_user_data_t *ud) void ghb_show_container_options(signal_user_data_t *ud) { - GtkWidget *w2, *w3; + GtkWidget *w1, *w2, *w3; + w1 = GHB_WIDGET(ud->builder, "AlignAVStart"); w2 = GHB_WIDGET(ud->builder, "Mp4HttpOptimize"); w3 = GHB_WIDGET(ud->builder, "Mp4iPodCompatible"); @@ -1026,6 +1027,7 @@ void ghb_show_container_options(signal_user_data_t *ud) gint enc = ghb_settings_video_encoder_codec(ud->settings, "VideoEncoder"); + gtk_widget_set_visible(w1, (mux->format & HB_MUX_MASK_MP4)); gtk_widget_set_visible(w2, (mux->format & HB_MUX_MASK_MP4)); gtk_widget_set_visible(w3, (mux->format & HB_MUX_MASK_MP4) && (enc == HB_VCODEC_X264_8BIT)); @@ -1648,9 +1650,15 @@ container_changed_cb(GtkWidget *widget, signal_user_data_t *ud) { g_debug("container_changed_cb ()"); ghb_widget_to_setting(ud->settings, widget); - const char * mux = ghb_dict_get_string(ud->settings, "FileFormat"); + const char * mux_id = ghb_dict_get_string(ud->settings, "FileFormat"); GhbValue *dest_dict = ghb_get_job_dest_settings(ud->settings); - ghb_dict_set_string(dest_dict, "Mux", mux); + ghb_dict_set_string(dest_dict, "Mux", mux_id); + + const hb_container_t *mux = ghb_lookup_container_by_name(mux_id); + if (!(mux->format & HB_MUX_MASK_MP4)) + { + ghb_ui_update(ud, "AlignAVStart", ghb_boolean_value(FALSE)); + } ghb_check_dependency(ud, widget, NULL); ghb_show_container_options(ud); diff --git a/gtk/src/ghb.m4 b/gtk/src/ghb.m4 index 736ce9e1b..73577af35 100644 --- a/gtk/src/ghb.m4 +++ b/gtk/src/ghb.m4 @@ -1498,19 +1498,21 @@ This is often the feature title of a DVD.</property> </packing> </child> <child> - <object class="GtkCheckButton" id="Mp4iPodCompatible"> - <property name="label" translatable="yes">iPod 5G Support</property> + <object class="GtkCheckButton" id="AlignAVStart"> + <property name="label" translatable="yes">Align A/V Start</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">False</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="tooltip_text" translatable="yes">Add iPod Atom needed by some older iPods.</property> + <property name="tooltip_text" translatable="yes">Aligns the initial timestamps of all audio and video streams by +inserting blank frames or dropping frames. May improve audio/video +sync for broken players that do not honor MP4 edit lists.</property> <property name="halign">start</property> <property name="draw_indicator">True</property> <signal name="toggled" handler="setting_widget_changed_cb" swapped="no"/> </object> <packing> - <property name="top_attach">1</property> + <property name="top_attach">0</property> <property name="left_attach">1</property> <property name="width">1</property> <property name="height">1</property> @@ -1536,6 +1538,25 @@ This allows a player to initiate playback before downloading the entire file.</p <property name="height">1</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="Mp4iPodCompatible"> + <property name="label" translatable="yes">iPod 5G Support</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="tooltip_text" translatable="yes">Add iPod Atom needed by some older iPods.</property> + <property name="halign">start</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="setting_widget_changed_cb" swapped="no"/> + </object> + <packing> + <property name="top_attach">1</property> + <property name="left_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> + </child> </object> <packing> <property name="expand">False</property> diff --git a/libhb/common.h b/libhb/common.h index 04a969221..d25bcbdb3 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -607,6 +607,14 @@ struct hb_job_s int mux; char * file; + int align_av_start; // align A/V stream start times. + // This is used to work around mp4 + // players that do not support edit + // lists. When this option is used + // the resulting stream is not a + // faithful reproduction of the source + // stream and may have blank frames + // added or initial frames dropped. int mp4_optimize; int ipod_atom; diff --git a/libhb/hb_json.c b/libhb/hb_json.c index 3d42078f7..5a3b4ca9d 100644 --- a/libhb/hb_json.c +++ b/libhb/hb_json.c @@ -390,8 +390,8 @@ hb_dict_t* hb_job_to_dict( const hb_job_t * job ) "{" // SequenceID "s:o," - // Destination {Mux, ChapterMarkers, ChapterList} - "s:{s:o, s:o, s:[]}," + // Destination {Mux, AlignAVStart, ChapterMarkers, ChapterList} + "s:{s:o, s:o, s:o, s:[]}," // Source {Path, Title, Angle} "s:{s:o, s:o, s:o,}," // PAR {Num, Den} @@ -410,6 +410,7 @@ hb_dict_t* hb_job_to_dict( const hb_job_t * job ) "SequenceID", hb_value_int(job->sequence_id), "Destination", "Mux", hb_value_int(job->mux), + "AlignAVStart", hb_value_bool(job->align_av_start), "ChapterMarkers", hb_value_bool(job->chapter_markers), "ChapterList", "Source", @@ -852,7 +853,7 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) "s:i," // Destination {File, Mux, ChapterMarkers, ChapterList, // Mp4Options {Mp4Optimize, IpodAtom}} - "s:{s?s, s:o, s:b, s?o s?{s?b, s?b}}," + "s:{s?s, s:o, s?b, s:b, s?o s?{s?b, s?b}}," // Source {Angle, Range {Type, Start, End, SeekPoints}} "s:{s?i, s?{s:s, s?I, s?I, s?I}}," // PAR {Num, Den} @@ -877,6 +878,7 @@ hb_job_t* hb_dict_to_job( hb_handle_t * h, hb_dict_t *dict ) "Destination", "File", unpack_s(&destfile), "Mux", unpack_o(&mux), + "AlignAVStart", unpack_b(&job->align_av_start), "ChapterMarkers", unpack_b(&job->chapter_markers), "ChapterList", unpack_o(&chapter_list), "Mp4Options", diff --git a/libhb/preset.c b/libhb/preset.c index d31805a63..af94d8539 100644 --- a/libhb/preset.c +++ b/libhb/preset.c @@ -1711,6 +1711,9 @@ int hb_preset_apply_mux(const hb_dict_t *preset, hb_dict_t *job_dict) hb_dict_t *dest_dict = hb_dict_get(job_dict, "Destination"); hb_dict_set(dest_dict, "Mux", hb_value_string(container->short_name)); + hb_dict_set(dest_dict, "AlignAVStart", + hb_value_xform(hb_dict_get(preset, "AlignAVStart"), + HB_VALUE_TYPE_BOOL)); if (mux & HB_MUX_MASK_MP4) { hb_dict_t *mp4_dict = hb_dict_init(); diff --git a/libhb/preset_builtin.h b/libhb/preset_builtin.h index 594c67b18..e0939502f 100644 --- a/libhb/preset_builtin.h +++ b/libhb/preset_builtin.h @@ -4,6 +4,7 @@ const char hb_builtin_presets_json[] = " {\n" " \"ChildrenArray\": [\n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -104,6 +105,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -204,6 +206,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -304,6 +307,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -404,6 +408,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -504,6 +509,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -604,6 +610,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -704,6 +711,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -804,6 +812,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -918,6 +927,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1032,6 +1042,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1146,6 +1157,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1260,6 +1272,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1374,6 +1387,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1488,6 +1502,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1602,6 +1617,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -1723,6 +1739,7 @@ const char hb_builtin_presets_json[] = " {\n" " \"ChildrenArray\": [\n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -1823,6 +1840,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -1923,6 +1941,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -3528,6 +3547,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\", \n" @@ -3645,6 +3665,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\", \n" @@ -3762,6 +3783,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -3876,6 +3898,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\"\n" @@ -3990,6 +4013,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -4090,6 +4114,7 @@ const char hb_builtin_presets_json[] = " \"x264UseAdvancedOptions\": false\n" " }, \n" " {\n" +" \"AlignAVStart\": true, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\"\n" " ], \n" @@ -7277,6 +7302,7 @@ const char hb_builtin_presets_json[] = " ], \n" " \"PresetTemplate\": {\n" " \"Preset\": {\n" +" \"AlignAVStart\": false, \n" " \"AudioCopyMask\": [\n" " \"copy:aac\", \n" " \"copy:ac3\", \n" @@ -7385,7 +7411,7 @@ const char hb_builtin_presets_json[] = " \"x264Option\": \"\", \n" " \"x264UseAdvancedOptions\": false\n" " }, \n" -" \"VersionMajor\": 26, \n" +" \"VersionMajor\": 27, \n" " \"VersionMicro\": 0, \n" " \"VersionMinor\": 0\n" " }\n" diff --git a/libhb/sync.c b/libhb/sync.c index fa56e8d71..6a068dd79 100644 --- a/libhb/sync.c +++ b/libhb/sync.c @@ -295,17 +295,295 @@ static void shiftTS( sync_common_t * common, int64_t delta ) } } +static hb_buffer_t * CreateSilenceBuf( sync_stream_t * stream, + int64_t dur, int64_t pts ) +{ + double frame_dur, next_pts, duration; + int size; + hb_buffer_list_t list; + hb_buffer_t * buf; + + if (stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG) + { + return NULL; + } + duration = dur; + frame_dur = (90000. * stream->audio.audio->config.in.samples_per_frame) / + stream->audio.audio->config.in.samplerate; + size = sizeof(float) * stream->audio.audio->config.in.samples_per_frame * + av_get_channel_layout_nb_channels( + stream->audio.audio->config.in.channel_layout ); + + hb_buffer_list_clear(&list); + next_pts = pts; + while (duration >= frame_dur) + { + buf = hb_buffer_init(size); + memset(buf->data, 0, buf->size); + buf->s.start = next_pts; + next_pts += frame_dur; + buf->s.stop = next_pts; + buf->s.duration = frame_dur; + duration -= frame_dur; + hb_buffer_list_append(&list, buf); + } + if (duration > 0) + { + size = sizeof(float) * + (duration * stream->audio.audio->config.in.samplerate / 90000) * + av_get_channel_layout_nb_channels( + stream->audio.audio->config.in.channel_layout ); + if (size > 0) + { + buf = hb_buffer_init(size); + memset(buf->data, 0, buf->size); + buf->s.start = next_pts; + next_pts += duration; + buf->s.stop = next_pts; + buf->s.duration = duration; + hb_buffer_list_append(&list, buf); + } + } + return hb_buffer_list_clear(&list); +} + +static hb_buffer_t * CreateBlackBuf( sync_stream_t * stream, + int64_t dur, int64_t pts ) +{ + // I would like to just allocate one black frame here and give + // it the full duration. But encoders that use B-Frames compute + // the dts delay based on the pts of the 2nd or 3rd frame which + // can be a very large value in this case. This large dts delay + // causes problems with computing the dts of the frames (which is + // extrapolated from the pts and the computed dts delay). And the + // dts problems lead to problems with frame duration. + double frame_dur, next_pts, duration; + hb_buffer_list_t list; + hb_buffer_t * buf = NULL; + + hb_buffer_list_clear(&list); + duration = dur; + next_pts = pts; + + frame_dur = 90000. * stream->common->job->title->vrate.den / + stream->common->job->title->vrate.num; + + // Only create black buffers of frame_dur or longer + while (duration >= frame_dur) + { + if (buf == NULL) + { + buf = hb_frame_buffer_init(AV_PIX_FMT_YUV420P, + stream->common->job->title->geometry.width, + stream->common->job->title->geometry.height); + memset(buf->plane[0].data, 0x00, buf->plane[0].size); + memset(buf->plane[1].data, 0x80, buf->plane[1].size); + memset(buf->plane[2].data, 0x80, buf->plane[2].size); + } + else + { + buf = hb_buffer_dup(buf); + } + buf->s.start = next_pts; + next_pts += frame_dur; + buf->s.stop = next_pts; + buf->s.duration = frame_dur; + duration -= frame_dur; + hb_buffer_list_append(&list, buf); + } + if (buf != NULL) + { + if (buf->s.stop < pts + dur) + { + // Extend the duration of the last black buffer to fill + // the remaining gap. + buf->s.duration += pts + dur - buf->s.stop; + buf->s.stop = pts + dur; + } + } + + return hb_buffer_list_clear(&list); +} + +static void setNextPts( sync_common_t * common ) +{ + int ii; + + for (ii = 0; ii < common->stream_count; ii++) + { + sync_stream_t * stream = &common->streams[ii]; + hb_buffer_t * buf = hb_list_item(stream->in_queue, 0); + if (buf != NULL) + { + stream->next_pts = buf->s.start; + } + else + { + stream->next_pts = (int64_t)AV_NOPTS_VALUE; + } + } +} + +static void alignStream( sync_common_t * common, sync_stream_t * stream, + int64_t pts ) +{ + if (hb_list_count(stream->in_queue) <= 0 || + stream->type == SYNC_TYPE_SUBTITLE) + { + return; + } + + hb_buffer_t * buf = hb_list_item(stream->in_queue, 0); + int64_t gap = buf->s.start - pts; + + if (gap == 0) + { + return; + } + if (gap < 0) + { + int ii; + + // Drop frames from other streams + for (ii = 0; ii < common->stream_count; ii++) + { + sync_stream_t * other_stream = &common->streams[ii]; + if (stream == other_stream) + { + continue; + } + while (hb_list_count(other_stream->in_queue) > 0) + { + buf = hb_list_item(other_stream->in_queue, 0); + if (buf->s.start < pts) + { + hb_list_rem(other_stream->in_queue, buf); + hb_buffer_close(&buf); + } + else + { + // Fill the partial frame gap left after dropping frames + alignStream(common, other_stream, pts); + break; + } + } + } + } + else + { + hb_buffer_t * blank_buf = NULL; + + // Insert a blank frame to fill the gap + if (stream->type == SYNC_TYPE_AUDIO) + { + // Can't add silence padding to passthru streams + if (!(stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG)) + { + blank_buf = CreateSilenceBuf(stream, gap, pts); + } + } + else if (stream->type == SYNC_TYPE_VIDEO) + { + blank_buf = CreateBlackBuf(stream, gap, pts); + } + + int64_t last_stop = pts; + hb_buffer_t * next; + int pos; + for (pos = 0; blank_buf != NULL; blank_buf = next, pos++) + { + last_stop = blank_buf->s.stop; + next = blank_buf->next; + blank_buf->next = NULL; + hb_list_insert(stream->in_queue, pos, blank_buf); + } + if (stream->type == SYNC_TYPE_VIDEO && last_stop < buf->s.start) + { + // Extend the duration of the first frame to fill the remaining gap. + buf->s.duration += buf->s.start - last_stop; + buf->s.start = last_stop; + } + } +} + +static void alignStreams( sync_common_t * common, int64_t pts ) +{ + int ii; + hb_buffer_t * buf; + + if (common->job->align_av_start) + { + int64_t first_pts = AV_NOPTS_VALUE; + int audio_passthru = 0; + + for (ii = 0; ii < common->stream_count; ii++) + { + sync_stream_t * stream = &common->streams[ii]; + + buf = hb_list_item(stream->in_queue, 0); + + // P-to-P encoding will pass the start point in pts. + // Drop any buffers that are before the start point. + while (buf != NULL && buf->s.start < pts) + { + hb_list_rem(stream->in_queue, buf); + hb_buffer_close(&buf); + buf = hb_list_item(stream->in_queue, 0); + } + if (buf == NULL) + { + continue; + } + if (stream->type == SYNC_TYPE_AUDIO && + stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG) + { + // Find the largest initial pts of all passthru audio streams. + // We can not add silence to passthru audio streams. + // To align with a passthru audio stream, we must drop + // buffers from all other streams that are before + // the first buffer in the passthru audio stream. + audio_passthru = 1; + if (first_pts < buf->s.start) + { + first_pts = buf->s.start; + } + } + else if (!audio_passthru) + { + // Find the smallest initial pts of all streams when + // there is *no* passthru audio. + // When there is no passthru audio stream, we insert + // silence or black buffers to fill any gaps between + // the start of any stream and the start of the stream + // with the smallest pts. + if (first_pts == AV_NOPTS_VALUE || first_pts > buf->s.start) + { + first_pts = buf->s.start; + } + } + } + if (first_pts != AV_NOPTS_VALUE) + { + for (ii = 0; ii < common->stream_count; ii++) + { + // Fill the gap (or drop frames for passthru audio) + alignStream(common, &common->streams[ii], first_pts); + } + } + } +} + static void computeInitialTS( sync_common_t * common, sync_stream_t * first_stream ) { int ii; - hb_buffer_t * prev; + hb_buffer_t * prev, * buf; // Process first_stream first since it has the initial PTS prev = NULL; for (ii = 0; ii < hb_list_count(first_stream->in_queue);) { - hb_buffer_t * buf = hb_list_item(first_stream->in_queue, ii); + buf = hb_list_item(first_stream->in_queue, ii); if (!UpdateSCR(first_stream, buf)) { @@ -326,6 +604,7 @@ static void computeInitialTS( sync_common_t * common, prev = buf; } } + for (ii = 0; ii < common->stream_count; ii++) { sync_stream_t * stream = &common->streams[ii]; @@ -340,7 +619,7 @@ static void computeInitialTS( sync_common_t * common, prev = NULL; for (jj = 0; jj < hb_list_count(stream->in_queue);) { - hb_buffer_t * buf = hb_list_item(stream->in_queue, jj); + buf = hb_list_item(stream->in_queue, jj); if (!UpdateSCR(stream, buf)) { // Subtitle put into delay queue, remove it from in_queue @@ -362,6 +641,7 @@ static void computeInitialTS( sync_common_t * common, } } } + alignStreams(common, AV_NOPTS_VALUE); } static void checkFirstPts( sync_common_t * common ) @@ -380,13 +660,23 @@ static void checkFirstPts( sync_common_t * common ) } // If buffers are queued, find the lowest initial PTS - if (hb_list_count(stream->in_queue) > 0) + while (hb_list_count(stream->in_queue) > 0) { hb_buffer_t * buf = hb_list_item(stream->in_queue, 0); - if (buf->s.start != AV_NOPTS_VALUE && buf->s.start < first_pts) + if (buf->s.start != AV_NOPTS_VALUE) { - first_pts = buf->s.start; - first_stream = stream; + // We require an initial pts for every stream + if (buf->s.start < first_pts) + { + first_pts = buf->s.start; + first_stream = stream; + } + break; + } + else + { + hb_list_rem(stream->in_queue, buf); + hb_buffer_close(&buf); } } } @@ -707,56 +997,6 @@ static void dejitterAudio( sync_stream_t * stream ) } } -static hb_buffer_t * CreateSilenceBuf( sync_stream_t * stream, int64_t dur ) -{ - double frame_dur, next_pts; - int size; - hb_buffer_list_t list; - hb_buffer_t * buf; - - if (stream->audio.audio->config.out.codec & HB_ACODEC_PASS_FLAG) - { - return NULL; - } - frame_dur = (90000. * stream->audio.audio->config.out.samples_per_frame) / - stream->audio.audio->config.in.samplerate; - size = sizeof(float) * stream->audio.audio->config.out.samples_per_frame * - hb_mixdown_get_discrete_channel_count( - stream->audio.audio->config.out.mixdown ); - - hb_buffer_list_clear(&list); - next_pts = stream->next_pts; - while (dur >= frame_dur) - { - buf = hb_buffer_init(size); - memset(buf->data, 0, buf->size); - buf->s.start = next_pts; - buf->s.duration = frame_dur; - next_pts += frame_dur; - buf->s.stop = next_pts; - dur -= frame_dur; - hb_buffer_list_append(&list, buf); - } - if (dur > 0) - { - size = sizeof(float) * - (dur * stream->audio.audio->config.in.samplerate / 90000) * - hb_mixdown_get_discrete_channel_count( - stream->audio.audio->config.out.mixdown ); - if (size > 0) - { - buf = hb_buffer_init(size); - memset(buf->data, 0, buf->size); - buf->s.start = next_pts; - buf->s.duration = frame_dur; - next_pts += frame_dur; - buf->s.stop = next_pts; - hb_buffer_list_append(&list, buf); - } - } - return hb_buffer_list_clear(&list); -} - // Fix audio gaps that could not be corrected with dejitter static void fixAudioGap( sync_stream_t * stream ) { @@ -780,7 +1020,7 @@ static void fixAudioGap( sync_stream_t * stream ) { stream->gap_pts = buf->s.start; } - buf = CreateSilenceBuf(stream, gap); + buf = CreateSilenceBuf(stream, gap, stream->next_pts); if (buf != NULL) { hb_buffer_t * next; @@ -1367,6 +1607,7 @@ static void OutputBuffer( sync_common_t * common ) } if (buf->s.start < common->start_pts) { + out_stream->next_pts = buf->s.start + buf->s.duration; hb_list_rem(out_stream->in_queue, buf); hb_buffer_close(&buf); } @@ -1376,7 +1617,18 @@ static void OutputBuffer( sync_common_t * common ) // reset frame count to track number of frames after // the start position till the end of encode. out_stream->frame_count = 0; + shiftTS(common, buf->s.start); + alignStreams(common, buf->s.start); + setNextPts(common); + + buf = hb_list_item(out_stream->in_queue, 0); + if (buf == NULL) + { + // In case aligning timestamps causes all buffers in + // out_stream to be deleted... + continue; + } } // If pts_to_stop or frame_to_stop were specified, stop output diff --git a/preset/preset_builtin.json b/preset/preset_builtin.json index c29da5493..4c76bfae7 100644 --- a/preset/preset_builtin.json +++ b/preset/preset_builtin.json @@ -2,6 +2,7 @@ { "ChildrenArray": [ { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -105,6 +106,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -208,6 +210,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -311,6 +314,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -414,6 +418,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -517,6 +522,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -620,6 +626,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -723,6 +730,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -826,6 +834,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -943,6 +952,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1060,6 +1070,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1177,6 +1188,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1294,6 +1306,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1411,6 +1424,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1528,6 +1542,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1645,6 +1660,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -1769,6 +1785,7 @@ { "ChildrenArray": [ { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -1872,6 +1889,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -1975,6 +1993,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -3625,6 +3644,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3", @@ -3745,6 +3765,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3", @@ -3865,6 +3886,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -3982,6 +4004,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac", "copy:ac3" @@ -4099,6 +4122,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], @@ -4202,6 +4226,7 @@ "x264UseAdvancedOptions": false }, { + "AlignAVStart": true, "AudioCopyMask": [ "copy:aac" ], diff --git a/preset/preset_builtin.list b/preset/preset_builtin.list index 607780e65..0e1371974 100644 --- a/preset/preset_builtin.list +++ b/preset/preset_builtin.list @@ -1,6 +1,6 @@ <resources> <section name="PresetTemplate"> - <integer name="VersionMajor" value="26" /> + <integer name="VersionMajor" value="27" /> <integer name="VersionMinor" value="0" /> <integer name="VersionMicro" value="0" /> <json name="Preset" file="preset_template.json" /> diff --git a/preset/preset_template.json b/preset/preset_template.json index 9a3f646cc..4e5389230 100644 --- a/preset/preset_template.json +++ b/preset/preset_template.json @@ -1,4 +1,5 @@ { + "AlignAVStart": false, "AudioCopyMask": [ "copy:aac", "copy:ac3", diff --git a/test/test.c b/test/test.c index 73411aa63..3bc669972 100644 --- a/test/test.c +++ b/test/test.c @@ -58,6 +58,7 @@ /* Options */ static int debug = HB_DEBUG_ALL; +static int align_av_start = -1; static int dvdnav = 1; static char * input = NULL; static char * output = NULL; @@ -2053,6 +2054,8 @@ static int ParseOptions( int argc, char ** argv ) { "angle", required_argument, NULL, ANGLE }, { "markers", optional_argument, NULL, 'm' }, { "no-markers", no_argument, &chapter_markers, 0 }, + { "align-av", no_argument, &align_av_start, 1 }, + { "no-align-av", no_argument, &align_av_start, 0 }, { "audio-lang-list", required_argument, NULL, AUDIO_LANG_LIST }, { "all-audio", no_argument, &audio_all, 1 }, { "first-audio", no_argument, &audio_all, 0 }, @@ -3274,6 +3277,10 @@ static hb_dict_t * PreparePreset(const char *preset_name) { hb_dict_set(preset, "ChapterMarkers", hb_value_bool(chapter_markers)); } + if (align_av_start != -1) + { + hb_dict_set(preset, "AlignAVStart", hb_value_bool(align_av_start)); + } hb_value_array_t *subtitle_lang_array; subtitle_lang_array = hb_dict_get(preset, "SubtitleLanguageList"); if (subtitle_lang_array == NULL) |