|
|
|
@ -33,6 +33,8 @@ typedef struct ASyncContext { |
|
|
|
|
AVAudioResampleContext *avr; |
|
|
|
|
int64_t pts; ///< timestamp in samples of the first sample in fifo
|
|
|
|
|
int min_delta; ///< pad/trim min threshold in samples
|
|
|
|
|
int first_frame; ///< 1 until filter_frame() has processed at least 1 frame with a pts != AV_NOPTS_VALUE
|
|
|
|
|
int64_t first_pts; ///< user-specified first expected pts, in samples
|
|
|
|
|
|
|
|
|
|
/* options */ |
|
|
|
|
int resample; |
|
|
|
@ -50,7 +52,7 @@ static const AVOption options[] = { |
|
|
|
|
{ "min_delta", "Minimum difference between timestamps and audio data " |
|
|
|
|
"(in seconds) to trigger padding/trimmin the data.", OFFSET(min_delta_sec), AV_OPT_TYPE_FLOAT, { .dbl = 0.1 }, 0, INT_MAX, A }, |
|
|
|
|
{ "max_comp", "Maximum compensation in samples per second.", OFFSET(max_comp), AV_OPT_TYPE_INT, { .i64 = 500 }, 0, INT_MAX, A }, |
|
|
|
|
{ "first_pts", "Assume the first pts should be this value.", OFFSET(pts), AV_OPT_TYPE_INT64, { .i64 = AV_NOPTS_VALUE }, INT64_MIN, INT64_MAX, A }, |
|
|
|
|
{ "first_pts", "Assume the first pts should be this value.", OFFSET(first_pts), AV_OPT_TYPE_INT64, { .i64 = AV_NOPTS_VALUE }, INT64_MIN, INT64_MAX, A }, |
|
|
|
|
{ NULL }, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -75,6 +77,9 @@ static int init(AVFilterContext *ctx, const char *args) |
|
|
|
|
} |
|
|
|
|
av_opt_free(s); |
|
|
|
|
|
|
|
|
|
s->pts = AV_NOPTS_VALUE; |
|
|
|
|
s->first_frame = 1; |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -122,6 +127,20 @@ static int64_t get_delay(ASyncContext *s) |
|
|
|
|
return avresample_available(s->avr) + avresample_get_delay(s->avr); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void handle_trimming(AVFilterContext *ctx) |
|
|
|
|
{ |
|
|
|
|
ASyncContext *s = ctx->priv; |
|
|
|
|
|
|
|
|
|
if (s->pts < s->first_pts) { |
|
|
|
|
int delta = FFMIN(s->first_pts - s->pts, avresample_available(s->avr)); |
|
|
|
|
av_log(ctx, AV_LOG_VERBOSE, "Trimming %d samples from start\n", |
|
|
|
|
delta); |
|
|
|
|
avresample_read(s->avr, NULL, delta); |
|
|
|
|
s->pts += delta; |
|
|
|
|
} else if (s->first_frame) |
|
|
|
|
s->pts = s->first_pts; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int request_frame(AVFilterLink *link) |
|
|
|
|
{ |
|
|
|
|
AVFilterContext *ctx = link->src; |
|
|
|
@ -134,7 +153,11 @@ static int request_frame(AVFilterLink *link) |
|
|
|
|
ret = ff_request_frame(ctx->inputs[0]); |
|
|
|
|
|
|
|
|
|
/* flush the fifo */ |
|
|
|
|
if (ret == AVERROR_EOF && (nb_samples = get_delay(s))) { |
|
|
|
|
if (ret == AVERROR_EOF) { |
|
|
|
|
if (s->first_pts != AV_NOPTS_VALUE) |
|
|
|
|
handle_trimming(ctx); |
|
|
|
|
|
|
|
|
|
if (nb_samples = get_delay(s)) { |
|
|
|
|
AVFilterBufferRef *buf = ff_get_audio_buffer(link, AV_PERM_WRITE, |
|
|
|
|
nb_samples); |
|
|
|
|
if (!buf) |
|
|
|
@ -148,6 +171,7 @@ static int request_frame(AVFilterLink *link) |
|
|
|
|
|
|
|
|
|
buf->pts = s->pts; |
|
|
|
|
return ff_filter_frame(link, buf); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ret; |
|
|
|
@ -185,12 +209,18 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) |
|
|
|
|
return write_to_fifo(s, buf); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (s->first_pts != AV_NOPTS_VALUE) { |
|
|
|
|
handle_trimming(ctx); |
|
|
|
|
if (!avresample_available(s->avr)) |
|
|
|
|
return write_to_fifo(s, buf); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* when we have two timestamps, compute how many samples would we have
|
|
|
|
|
* to add/remove to get proper sync between data and timestamps */ |
|
|
|
|
delta = pts - s->pts - get_delay(s); |
|
|
|
|
out_size = avresample_available(s->avr); |
|
|
|
|
|
|
|
|
|
if (labs(delta) > s->min_delta) { |
|
|
|
|
if (labs(delta) > s->min_delta || (s->first_frame && delta)) { |
|
|
|
|
av_log(ctx, AV_LOG_VERBOSE, "Discontinuity - %"PRId64" samples.\n", delta); |
|
|
|
|
out_size = av_clipl_int32((int64_t)out_size + delta); |
|
|
|
|
} else { |
|
|
|
@ -210,18 +240,33 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) |
|
|
|
|
goto fail; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
avresample_read(s->avr, buf_out->extended_data, out_size); |
|
|
|
|
buf_out->pts = s->pts; |
|
|
|
|
if (s->first_frame && delta > 0) { |
|
|
|
|
int ch; |
|
|
|
|
|
|
|
|
|
av_samples_set_silence(buf_out->extended_data, 0, delta, |
|
|
|
|
nb_channels, buf->format); |
|
|
|
|
|
|
|
|
|
for (ch = 0; ch < nb_channels; ch++) |
|
|
|
|
buf_out->extended_data[ch] += delta; |
|
|
|
|
|
|
|
|
|
if (delta > 0) { |
|
|
|
|
av_samples_set_silence(buf_out->extended_data, out_size - delta, |
|
|
|
|
delta, nb_channels, buf->format); |
|
|
|
|
avresample_read(s->avr, buf_out->extended_data, out_size); |
|
|
|
|
|
|
|
|
|
for (ch = 0; ch < nb_channels; ch++) |
|
|
|
|
buf_out->extended_data[ch] -= delta; |
|
|
|
|
} else { |
|
|
|
|
avresample_read(s->avr, buf_out->extended_data, out_size); |
|
|
|
|
|
|
|
|
|
if (delta > 0) { |
|
|
|
|
av_samples_set_silence(buf_out->extended_data, out_size - delta, |
|
|
|
|
delta, nb_channels, buf->format); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
buf_out->pts = s->pts; |
|
|
|
|
ret = ff_filter_frame(outlink, buf_out); |
|
|
|
|
if (ret < 0) |
|
|
|
|
goto fail; |
|
|
|
|
s->got_output = 1; |
|
|
|
|
} else { |
|
|
|
|
} else if (avresample_available(s->avr)) { |
|
|
|
|
av_log(ctx, AV_LOG_WARNING, "Non-monotonous timestamps, dropping " |
|
|
|
|
"whole buffer.\n"); |
|
|
|
|
} |
|
|
|
@ -233,6 +278,7 @@ static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *buf) |
|
|
|
|
ret = avresample_convert(s->avr, NULL, 0, 0, buf->extended_data, |
|
|
|
|
buf->linesize[0], buf->audio->nb_samples); |
|
|
|
|
|
|
|
|
|
s->first_frame = 0; |
|
|
|
|
fail: |
|
|
|
|
avfilter_unref_buffer(buf); |
|
|
|
|
|
|
|
|
|