summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gtk/src/callbacks.c14
-rw-r--r--gtk/src/ghb.m429
-rw-r--r--libhb/common.h8
-rw-r--r--libhb/hb_json.c8
-rw-r--r--libhb/preset.c3
-rw-r--r--libhb/preset_builtin.h28
-rw-r--r--libhb/sync.c368
-rw-r--r--preset/preset_builtin.json25
-rw-r--r--preset/preset_builtin.list2
-rw-r--r--preset/preset_template.json1
-rw-r--r--test/test.c7
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)