To delay filtering until a given wallclock timestamp. Signed-off-by: Marton Balint <cus@passwd.hu>tags/n4.1
@@ -23,6 +23,7 @@ version <next>: | |||||
- WinCam Motion Video decoder | - WinCam Motion Video decoder | ||||
- 1D LUT filter (lut1d) | - 1D LUT filter (lut1d) | ||||
- RemotelyAnywhere Screen Capture decoder | - RemotelyAnywhere Screen Capture decoder | ||||
- cue and acue filters | |||||
version 4.0: | version 4.0: | ||||
@@ -551,6 +551,11 @@ Set LFO range. | |||||
Set LFO rate. | Set LFO rate. | ||||
@end table | @end table | ||||
@section acue | |||||
Delay audio filtering until a given wallclock timestamp. See the @ref{cue} | |||||
filter. | |||||
@section adeclick | @section adeclick | ||||
Remove impulsive noise from input audio. | Remove impulsive noise from input audio. | ||||
@@ -6987,6 +6992,37 @@ indicates 'never reset', and returns the largest area encountered during | |||||
playback. | playback. | ||||
@end table | @end table | ||||
@anchor{cue} | |||||
@section cue | |||||
Delay video filtering until a given wallclock timestamp. The filter first | |||||
passes on @option{preroll} amount of frames, then it buffers at most | |||||
@option{buffer} amount of frames and waits for the cue. After reaching the cue | |||||
it forwards the buffered frames and also any subsequent frames coming in its | |||||
input. | |||||
The filter can be used synchronize the output of multiple ffmpeg processes for | |||||
realtime output devices like decklink. By putting the delay in the filtering | |||||
chain and pre-buffering frames the process can pass on data to output almost | |||||
immediately after the target wallclock timestamp is reached. | |||||
Perfect frame accuracy cannot be guaranteed, but the result is good enough for | |||||
some use cases. | |||||
@table @option | |||||
@item cue | |||||
The cue timestamp expressed in a UNIX timestamp in microseconds. Default is 0. | |||||
@item preroll | |||||
The duration of content to pass on as preroll expressed in seconds. Default is 0. | |||||
@item buffer | |||||
The maximum duration of content to buffer before waiting for the cue expressed | |||||
in seconds. Default is 0. | |||||
@end table | |||||
@anchor{curves} | @anchor{curves} | ||||
@section curves | @section curves | ||||
@@ -36,6 +36,7 @@ OBJS-$(CONFIG_ACONTRAST_FILTER) += af_acontrast.o | |||||
OBJS-$(CONFIG_ACOPY_FILTER) += af_acopy.o | OBJS-$(CONFIG_ACOPY_FILTER) += af_acopy.o | ||||
OBJS-$(CONFIG_ACROSSFADE_FILTER) += af_afade.o | OBJS-$(CONFIG_ACROSSFADE_FILTER) += af_afade.o | ||||
OBJS-$(CONFIG_ACRUSHER_FILTER) += af_acrusher.o | OBJS-$(CONFIG_ACRUSHER_FILTER) += af_acrusher.o | ||||
OBJS-$(CONFIG_ACUE_FILTER) += f_cue.o | |||||
OBJS-$(CONFIG_ADECLICK_FILTER) += af_adeclick.o | OBJS-$(CONFIG_ADECLICK_FILTER) += af_adeclick.o | ||||
OBJS-$(CONFIG_ADECLIP_FILTER) += af_adeclick.o | OBJS-$(CONFIG_ADECLIP_FILTER) += af_adeclick.o | ||||
OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o | OBJS-$(CONFIG_ADELAY_FILTER) += af_adelay.o | ||||
@@ -178,6 +179,7 @@ OBJS-$(CONFIG_COREIMAGE_FILTER) += vf_coreimage.o | |||||
OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o | OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o | ||||
OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o | OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o | ||||
OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o | OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o | ||||
OBJS-$(CONFIG_CUE_FILTER) += f_cue.o | |||||
OBJS-$(CONFIG_CURVES_FILTER) += vf_curves.o | OBJS-$(CONFIG_CURVES_FILTER) += vf_curves.o | ||||
OBJS-$(CONFIG_DATASCOPE_FILTER) += vf_datascope.o | OBJS-$(CONFIG_DATASCOPE_FILTER) += vf_datascope.o | ||||
OBJS-$(CONFIG_DCTDNOIZ_FILTER) += vf_dctdnoiz.o | OBJS-$(CONFIG_DCTDNOIZ_FILTER) += vf_dctdnoiz.o | ||||
@@ -27,6 +27,7 @@ extern AVFilter ff_af_abench; | |||||
extern AVFilter ff_af_acompressor; | extern AVFilter ff_af_acompressor; | ||||
extern AVFilter ff_af_acontrast; | extern AVFilter ff_af_acontrast; | ||||
extern AVFilter ff_af_acopy; | extern AVFilter ff_af_acopy; | ||||
extern AVFilter ff_af_acue; | |||||
extern AVFilter ff_af_acrossfade; | extern AVFilter ff_af_acrossfade; | ||||
extern AVFilter ff_af_acrusher; | extern AVFilter ff_af_acrusher; | ||||
extern AVFilter ff_af_adeclick; | extern AVFilter ff_af_adeclick; | ||||
@@ -167,6 +168,7 @@ extern AVFilter ff_vf_coreimage; | |||||
extern AVFilter ff_vf_cover_rect; | extern AVFilter ff_vf_cover_rect; | ||||
extern AVFilter ff_vf_crop; | extern AVFilter ff_vf_crop; | ||||
extern AVFilter ff_vf_cropdetect; | extern AVFilter ff_vf_cropdetect; | ||||
extern AVFilter ff_vf_cue; | |||||
extern AVFilter ff_vf_curves; | extern AVFilter ff_vf_curves; | ||||
extern AVFilter ff_vf_datascope; | extern AVFilter ff_vf_datascope; | ||||
extern AVFilter ff_vf_dctdnoiz; | extern AVFilter ff_vf_dctdnoiz; | ||||
@@ -0,0 +1,182 @@ | |||||
/* | |||||
* Copyright (c) 2018 Marton Balint | |||||
* | |||||
* This file is part of FFmpeg. | |||||
* | |||||
* FFmpeg is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public License | |||||
* as published by the Free Software Foundation; either | |||||
* version 2.1 of the License, or (at your option) any later version. | |||||
* | |||||
* FFmpeg is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with FFmpeg; if not, write to the Free Software Foundation, Inc., | |||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||||
*/ | |||||
#include "libavutil/opt.h" | |||||
#include "libavutil/time.h" | |||||
#include "avfilter.h" | |||||
#include "filters.h" | |||||
#include "framequeue.h" | |||||
#include "internal.h" | |||||
typedef struct CueContext { | |||||
const AVClass *class; | |||||
int64_t first_pts; | |||||
int64_t cue; | |||||
int64_t preroll; | |||||
int64_t buffer; | |||||
int status; | |||||
FFFrameQueue queue; | |||||
} CueContext; | |||||
static av_cold int init(AVFilterContext *ctx) | |||||
{ | |||||
CueContext *s = ctx->priv; | |||||
ff_framequeue_init(&s->queue, &ctx->graph->internal->frame_queues); | |||||
return 0; | |||||
} | |||||
static av_cold void uninit(AVFilterContext *ctx) | |||||
{ | |||||
CueContext *s = ctx->priv; | |||||
ff_framequeue_free(&s->queue); | |||||
} | |||||
static int activate(AVFilterContext *ctx) | |||||
{ | |||||
AVFilterLink *inlink = ctx->inputs[0]; | |||||
AVFilterLink *outlink = ctx->outputs[0]; | |||||
CueContext *s = ctx->priv; | |||||
int64_t pts; | |||||
AVFrame *frame = NULL; | |||||
FF_FILTER_FORWARD_STATUS_BACK(outlink, inlink); | |||||
if (s->status < 3 || s->status == 5) { | |||||
int ret = ff_inlink_consume_frame(inlink, &frame); | |||||
if (ret < 0) | |||||
return ret; | |||||
if (frame) | |||||
pts = av_rescale_q(frame->pts, inlink->time_base, AV_TIME_BASE_Q); | |||||
} | |||||
if (!s->status && frame) { | |||||
s->first_pts = pts; | |||||
s->status++; | |||||
} | |||||
if (s->status == 1 && frame) { | |||||
if (pts - s->first_pts < s->preroll) | |||||
return ff_filter_frame(outlink, frame); | |||||
s->first_pts = pts; | |||||
s->status++; | |||||
} | |||||
if (s->status == 2 && frame) { | |||||
int ret = ff_framequeue_add(&s->queue, frame); | |||||
if (ret < 0) { | |||||
av_frame_free(&frame); | |||||
return ret; | |||||
} | |||||
frame = NULL; | |||||
if (!(pts - s->first_pts < s->buffer && (av_gettime() - s->cue) < 0)) | |||||
s->status++; | |||||
} | |||||
if (s->status == 3) { | |||||
int64_t diff; | |||||
while ((diff = (av_gettime() - s->cue)) < 0) | |||||
av_usleep(av_clip(-diff / 2, 100, 1000000)); | |||||
s->status++; | |||||
} | |||||
if (s->status == 4) { | |||||
if (ff_framequeue_queued_frames(&s->queue)) | |||||
return ff_filter_frame(outlink, ff_framequeue_take(&s->queue)); | |||||
s->status++; | |||||
} | |||||
if (s->status == 5 && frame) | |||||
return ff_filter_frame(outlink, frame); | |||||
FF_FILTER_FORWARD_STATUS(inlink, outlink); | |||||
FF_FILTER_FORWARD_WANTED(outlink, inlink); | |||||
return FFERROR_NOT_READY; | |||||
} | |||||
#define OFFSET(x) offsetof(CueContext, x) | |||||
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | |||||
static const AVOption options[] = { | |||||
{ "cue", "cue unix timestamp in microseconds", OFFSET(cue), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, FLAGS }, | |||||
{ "preroll", "preroll duration in seconds", OFFSET(preroll), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT64_MAX, FLAGS }, | |||||
{ "buffer", "buffer duration in seconds", OFFSET(buffer), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT64_MAX, FLAGS }, | |||||
{ NULL } | |||||
}; | |||||
#if CONFIG_CUE_FILTER | |||||
#define cue_options options | |||||
AVFILTER_DEFINE_CLASS(cue); | |||||
static const AVFilterPad cue_inputs[] = { | |||||
{ | |||||
.name = "default", | |||||
.type = AVMEDIA_TYPE_VIDEO, | |||||
}, | |||||
{ NULL } | |||||
}; | |||||
static const AVFilterPad cue_outputs[] = { | |||||
{ | |||||
.name = "default", | |||||
.type = AVMEDIA_TYPE_VIDEO, | |||||
}, | |||||
{ NULL } | |||||
}; | |||||
AVFilter ff_vf_cue = { | |||||
.name = "cue", | |||||
.description = NULL_IF_CONFIG_SMALL("Delay filtering to match a cue."), | |||||
.priv_size = sizeof(CueContext), | |||||
.priv_class = &cue_class, | |||||
.init = init, | |||||
.uninit = uninit, | |||||
.inputs = cue_inputs, | |||||
.outputs = cue_outputs, | |||||
.activate = activate, | |||||
}; | |||||
#endif /* CONFIG_CUE_FILTER */ | |||||
#if CONFIG_ACUE_FILTER | |||||
#define acue_options options | |||||
AVFILTER_DEFINE_CLASS(acue); | |||||
static const AVFilterPad acue_inputs[] = { | |||||
{ | |||||
.name = "default", | |||||
.type = AVMEDIA_TYPE_AUDIO, | |||||
}, | |||||
{ NULL } | |||||
}; | |||||
static const AVFilterPad acue_outputs[] = { | |||||
{ | |||||
.name = "default", | |||||
.type = AVMEDIA_TYPE_AUDIO, | |||||
}, | |||||
{ NULL } | |||||
}; | |||||
AVFilter ff_af_acue = { | |||||
.name = "acue", | |||||
.description = NULL_IF_CONFIG_SMALL("Delay filtering to match a cue."), | |||||
.priv_size = sizeof(CueContext), | |||||
.priv_class = &acue_class, | |||||
.init = init, | |||||
.uninit = uninit, | |||||
.inputs = acue_inputs, | |||||
.outputs = acue_outputs, | |||||
.activate = activate, | |||||
}; | |||||
#endif /* CONFIG_ACUE_FILTER */ |
@@ -30,7 +30,7 @@ | |||||
#include "libavutil/version.h" | #include "libavutil/version.h" | ||||
#define LIBAVFILTER_VERSION_MAJOR 7 | #define LIBAVFILTER_VERSION_MAJOR 7 | ||||
#define LIBAVFILTER_VERSION_MINOR 27 | |||||
#define LIBAVFILTER_VERSION_MINOR 28 | |||||
#define LIBAVFILTER_VERSION_MICRO 100 | #define LIBAVFILTER_VERSION_MICRO 100 | ||||
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ | #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ | ||||