diff options
author | John Stebbins <[email protected]> | 2017-06-02 08:51:21 -0700 |
---|---|---|
committer | Bradley Sepos <[email protected]> | 2017-06-13 17:22:11 -0400 |
commit | 16cc03076330b23a0e8744ea125db61a5a2bd2e8 (patch) | |
tree | 9f3f38746c96dc2d70395f7b02e56aa60ae5d9b0 | |
parent | e018cdea0566e22edf72de4b82a6634c791a90bc (diff) |
sync: work-around players with broken edit list support
This adds a preset key AlignAVStart that enables this work-around. When
enabled, blank frames are inserted or frames are dropped to force
alignment of the initial timestamp of every audio and video stream.
Aligning the start times minimizes the impact of broken edit list
support in players.
Closes #763.
Squashed:
sync: improve alignment when passthru audio is present
presets: enable AlignAVStart for General and Gmail presets
LinGui: Improve AlignAVStart tooltip
sync: avoid inserting a black frame < nominal frame duration
sync: fix start alignment when doing p-to-p encoding
sync: add comments
-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) |