@@ -518,6 +518,22 @@ see @ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1)
Like the @code{-ss} option but relative to the "end of file". That is negative
values are earlier in the file, 0 is at EOF.
+@item -isync @var{input_index} (@emph{input})
+Assign an input as a sync source.
+
+This will take the difference between the start times of the target and referenced inputs and
+offset the timestamps of the target file by that difference. The source timestamps of the two
+inputs should derive from the same clock source for expected results. If @code{copyts} is set
+then @code{start_at_zero} must also be set. If at least one of the inputs has no starting
+timestamp then the wallclock time at time of reception of the inputs is used as a best-effort
+sync basis.
+
+Acceptable values are those that refer to a valid ffmpeg input index. If the sync reference is
+the target index itself or @var{-1}, then no adjustment is made to target timestamps. A sync
+reference may not itself be synced to any other input.
+
+Default value is var{-1}.
+
@item -itsoffset @var{offset} (@emph{input})
Set the input time offset.
@@ -118,6 +118,7 @@ typedef struct OptionsContext {
float readrate;
int accurate_seek;
int thread_queue_size;
+ int input_sync_ref;
SpecifierOpt *ts_scale;
int nb_ts_scale;
@@ -410,6 +411,7 @@ typedef struct InputFile {
at the moment when looping happens */
AVRational time_base; /* time base of the duration */
int64_t input_ts_offset;
+ int input_sync_ref;
int64_t ts_offset;
int64_t last_ts;
@@ -235,6 +235,7 @@ static void init_options(OptionsContext *o)
o->chapters_input_file = INT_MAX;
o->accurate_seek = 1;
o->thread_queue_size = -1;
+ o->input_sync_ref = -1;
}
static int show_hwaccels(void *optctx, const char *opt, const char *arg)
@@ -287,6 +288,58 @@ static int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, in
return 0;
}
+static int apply_sync_offsets(void)
+{
+ for (int i = 0; i < nb_input_files; i++) {
+ InputFile *ref, *self = input_files[i];
+ int64_t adjustment;
+ int64_t self_start_time, ref_start_time, self_seek_start, ref_seek_start;
+ int sync_fpw = 0;
+
+ if (self->input_sync_ref == -1 || self->input_sync_ref == i) continue;
+ if (self->input_sync_ref >= nb_input_files || self->input_sync_ref < -1) {
+ av_log(NULL, AV_LOG_FATAL, "-isync for input %d references non-existent input %d.\n", i, self->input_sync_ref);
+ exit_program(1);
+ }
+
+ if (copy_ts && !start_at_zero) {
+ av_log(NULL, AV_LOG_FATAL, "Use of -isync requires that start_at_zero be set if copyts is set.\n");
+ exit_program(1);
+ }
+
+ ref = input_files[self->input_sync_ref];
+ if (ref->input_sync_ref != -1 && ref->input_sync_ref != self->input_sync_ref) {
+ av_log(NULL, AV_LOG_ERROR, "-isync for input %d references a resynced input %d. Sync not set.\n", i, self->input_sync_ref);
+ continue;
+ }
+
+ if (self->ctx->start_time_realtime != AV_NOPTS_VALUE && ref->ctx->start_time_realtime != AV_NOPTS_VALUE) {
+ self_start_time = self->ctx->start_time_realtime;
+ ref_start_time = ref->ctx->start_time_realtime;
+ } else if (self->ctx->start_time != AV_NOPTS_VALUE && ref->ctx->start_time != AV_NOPTS_VALUE) {
+ self_start_time = self->ctx->start_time;
+ ref_start_time = ref->ctx->start_time;
+ } else {
+ self_start_time = self->ctx->first_pkt_wallclock;
+ ref_start_time = ref->ctx->first_pkt_wallclock;
+ sync_fpw = 1;
+ }
+
+ self_seek_start = self->start_time == AV_NOPTS_VALUE ? 0 : self->start_time;
+ ref_seek_start = ref->start_time == AV_NOPTS_VALUE ? 0 : ref->start_time;
+
+ adjustment = (self_start_time - ref_start_time) + !copy_ts*(self_seek_start - ref_seek_start) + ref->input_ts_offset;
+
+ self->ts_offset += adjustment;
+
+ av_log(NULL, AV_LOG_INFO, "Adjusted ts offset for Input #%d by %"PRId64"d us to sync with Input #%d", i, adjustment, self->input_sync_ref);
+ if (sync_fpw) av_log(NULL, AV_LOG_INFO, " using reception wallclock time. Sync may not be obtained");
+ av_log(NULL, AV_LOG_INFO, ".\n");
+ }
+
+ return 0;
+}
+
static int opt_filter_threads(void *optctx, const char *opt, const char *arg)
{
av_free(filter_nbthreads);
@@ -1305,6 +1358,7 @@ static int open_input_file(OptionsContext *o, const char *filename)
f->ist_index = nb_input_streams - ic->nb_streams;
f->start_time = o->start_time;
f->recording_time = o->recording_time;
+ f->input_sync_ref = o->input_sync_ref;
f->input_ts_offset = o->input_ts_offset;
f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp);
f->nb_streams = ic->nb_streams;
@@ -3489,6 +3543,8 @@ int ffmpeg_parse_options(int argc, char **argv)
goto fail;
}
+ apply_sync_offsets();
+
/* create the complex filtergraphs */
ret = init_complex_filters();
if (ret < 0) {
@@ -3603,6 +3659,9 @@ const OptionDef options[] = {
{ "accurate_seek", OPT_BOOL | OPT_OFFSET | OPT_EXPERT |
OPT_INPUT, { .off = OFFSET(accurate_seek) },
"enable/disable accurate seeking with -ss" },
+ { "isync", HAS_ARG | OPT_INT | OPT_OFFSET |
+ OPT_EXPERT | OPT_INPUT, { .off = OFFSET(input_sync_ref) },
+ "Indicate the input index for sync reference", "sync ref" },
{ "itsoffset", HAS_ARG | OPT_TIME | OPT_OFFSET |
OPT_EXPERT | OPT_INPUT, { .off = OFFSET(input_ts_offset) },
"set the input ts offset", "time_off" },