]> granicus.if.org Git - handbrake/commitdiff
sync: work-around players with broken edit list support
authorJohn Stebbins <jstebbins.hb@gmail.com>
Fri, 2 Jun 2017 15:51:21 +0000 (08:51 -0700)
committerBradley Sepos <bradley@bradleysepos.com>
Tue, 13 Jun 2017 21:22:11 +0000 (17:22 -0400)
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

gtk/src/callbacks.c
gtk/src/ghb.m4
libhb/common.h
libhb/hb_json.c
libhb/preset.c
libhb/preset_builtin.h
libhb/sync.c
preset/preset_builtin.json
preset/preset_builtin.list
preset/preset_template.json
test/test.c

index c195a258436104a60d37c079cbd9c1977ae257b9..5f7c04b8e0742bb02a07382a927fee0618d5d807 100644 (file)
@@ -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);
index 736ce9e1b39075712033b942926da0319a62c771..73577af350c0bcc9bdba310cef7c58a84b3543a9 100644 (file)
@@ -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>
index 04a969221e93f3aad3a7a104297fd2b982e63297..d25bcbdb30748f61cd00e425563cdfcd3a13ad92 100644 (file)
@@ -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;
 
index 3d42078f77b6666eb685fd64639758a740f3e734..5a3b4ca9d77fa849ef2bc86947a577ed04688d5b 100644 (file)
@@ -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",
index d31805a6316ffdfad20a698b1869736b1ed0dece..af94d853914be3ca78545e8058169a5d2f451f07 100644 (file)
@@ -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();
index 594c67b18824b53e4b7ab56f8367250a424371f1..e0939502f066ab69ebf2c57fd6ca9c35a55b8d8b 100644 (file)
@@ -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"
index fa56e8d7145476d7bd087e54bb1d9d22c66c9b81..6a068dd797a2a8487f2fddeca2db4d2f9075406b 100644 (file)
@@ -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
index c29da549343b03b38cbe9876beb26378cc54cacb..4c76bfae75efcfe74460f5e0dffd93084a50ff36 100644 (file)
@@ -2,6 +2,7 @@
         {
             "ChildrenArray": [
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
         {
             "ChildrenArray": [
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3",
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3",
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac",
                         "copy:ac3"
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
                     "x264UseAdvancedOptions": false
                 },
                 {
+                    "AlignAVStart": true,
                     "AudioCopyMask": [
                         "copy:aac"
                     ],
index 607780e654fcc3c3d78d9db864e58e9767b05a7d..0e137197476d0f340678768385ce091242611a56 100644 (file)
@@ -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" />
index 9a3f646cc68fdb96a82e8b19797a9e52cce579de..4e53892303de5bd81eb1f313597fc98fcb303701 100644 (file)
@@ -1,4 +1,5 @@
     {
+        "AlignAVStart": false,
         "AudioCopyMask": [
             "copy:aac",
             "copy:ac3",
index 73411aa63a5a66ba9a6ba2cb381be7c48369700e..3bc669972600cf4b84eb55f94ed3dd6f4eea8144 100644 (file)
@@ -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)