This is an exact inverse of the telecine filter unlike previously existing pullup and fieldmatch ones.
Tested on few samples generated using the telecine filter. Documentation is yet to be added. Added an additional parameter "start_frame" to allow using the filter for a stream that was cut and improved pts handling. --- Changelog | 2 +- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_detelecine.c | 335 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 libavfilter/vf_detelecine.c diff --git a/Changelog b/Changelog index 4950330..8733203 100644 --- a/Changelog +++ b/Changelog @@ -7,7 +7,7 @@ version <next>: - DTS lossless extension (XLL) decoding (not lossless, disabled by default) - showwavespic filter - libdcadec wrapper - +- Detelecine filter version 2.6: - nvenc encoder diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 2cde029..73e7adf 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -112,6 +112,7 @@ OBJS-$(CONFIG_DECIMATE_FILTER) += vf_decimate.o OBJS-$(CONFIG_DEJUDDER_FILTER) += vf_dejudder.o OBJS-$(CONFIG_DELOGO_FILTER) += vf_delogo.o OBJS-$(CONFIG_DESHAKE_FILTER) += vf_deshake.o +OBJS-$(CONFIG_DETELECINE_FILTER) += vf_detelecine.o OBJS-$(CONFIG_DRAWBOX_FILTER) += vf_drawbox.o OBJS-$(CONFIG_DRAWGRID_FILTER) += vf_drawbox.o OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 0288082..6bc01c5 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -128,6 +128,7 @@ void avfilter_register_all(void) REGISTER_FILTER(DEJUDDER, dejudder, vf); REGISTER_FILTER(DELOGO, delogo, vf); REGISTER_FILTER(DESHAKE, deshake, vf); + REGISTER_FILTER(DETELECINE, detelecine, vf); REGISTER_FILTER(DRAWBOX, drawbox, vf); REGISTER_FILTER(DRAWGRID, drawgrid, vf); REGISTER_FILTER(DRAWTEXT, drawtext, vf); diff --git a/libavfilter/vf_detelecine.c b/libavfilter/vf_detelecine.c new file mode 100644 index 0000000..36d4e94 --- /dev/null +++ b/libavfilter/vf_detelecine.c @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2015 Himangi Saraogi <himangi...@gmail.com> + * + * 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 + */ + +/** + * @file detelecine filter. + */ + + +#include "libavutil/avstring.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct { + const AVClass *class; + int first_field; + char *pattern; + int start_frame; + unsigned int pattern_pos; + unsigned int nskip_fields; + + AVRational pts; + int occupied; + + int nb_planes; + int planeheight[4]; + int stride[4]; + + AVFrame *frame; + AVFrame *temp; +} DetelecineContext; + +#define OFFSET(x) offsetof(DetelecineContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption detelecine_options[] = { + {"first_field", "select first field", OFFSET(first_field), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "field"}, + {"top", "select top field first", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"}, + {"t", "select top field first", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"}, + {"bottom", "select bottom field first", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"}, + {"b", "select bottom field first", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"}, + {"pattern", "pattern that describe for how many fields a frame is to be displayed", OFFSET(pattern), AV_OPT_TYPE_STRING, {.str="23"}, 0, 0, FLAGS}, + {"start_frame", "position of first frame with respect to the pattern if stream is cut", OFFSET(start_frame), AV_OPT_TYPE_INT, {.i64=0}, 0, 13, FLAGS}, + {NULL} +}; + +AVFILTER_DEFINE_CLASS(detelecine); + +static av_cold int init(AVFilterContext *ctx) +{ + DetelecineContext *s = ctx->priv; + const char *p; + int max = 0; + + if (!strlen(s->pattern)) { + av_log(ctx, AV_LOG_ERROR, "No pattern provided.\n"); + return AVERROR_INVALIDDATA; + } + + for (p = s->pattern; *p; p++) { + if (!av_isdigit(*p)) { + av_log(ctx, AV_LOG_ERROR, "Provided pattern includes non-numeric characters.\n"); + return AVERROR_INVALIDDATA; + } + + max = FFMAX(*p - '0', max); + s->pts.num += *p - '0'; + s->pts.den += 2; + } + + s->nskip_fields = 0; + s->pattern_pos = 0; + + if (s->start_frame != 0) { + int nfields = 0; + for (p = s->pattern; *p; p++) { + nfields += *p - '0'; + s->pattern_pos++; + if (nfields >= 2*s->start_frame) { + s->nskip_fields = nfields - 2*s->start_frame; + break; + } + } + } + + av_log(ctx, AV_LOG_INFO, "Detelecine pattern %s removes up to %d frames per frame, pts advance factor: %d/%d\n", + s->pattern, (max + 1) / 2, s->pts.num, s->pts.den); + + return 0; +} + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *pix_fmts = NULL; + int fmt; + + for (fmt = 0; av_pix_fmt_desc_get(fmt); fmt++) { + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); + if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL || + desc->flags & AV_PIX_FMT_FLAG_PAL || + desc->flags & AV_PIX_FMT_FLAG_BITSTREAM)) + ff_add_format(&pix_fmts, fmt); + } + + ff_set_common_formats(ctx, pix_fmts); + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + DetelecineContext *s = inlink->dst->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + int ret; + + s->temp = ff_get_video_buffer(inlink, inlink->w, inlink->h); + if (!s->temp) + return AVERROR(ENOMEM); + + s->frame = ff_get_video_buffer(inlink, inlink->w, inlink->h); + if (!s->frame) + return AVERROR(ENOMEM); + + if ((ret = av_image_fill_linesizes(s->stride, inlink->format, inlink->w)) < 0) + return ret; + + s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); + s->planeheight[0] = s->planeheight[3] = inlink->h; + + s->nb_planes = av_pix_fmt_count_planes(inlink->format); + + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + DetelecineContext *s = ctx->priv; + const AVFilterLink *inlink = ctx->inputs[0]; + AVRational fps = inlink->frame_rate; + + if (!fps.num || !fps.den) { + av_log(ctx, AV_LOG_ERROR, "The input needs a constant frame rate; " + "current rate of %d/%d is invalid\n", fps.num, fps.den); + return AVERROR(EINVAL); + } + fps = av_mul_q(fps, av_inv_q(s->pts)); + av_log(ctx, AV_LOG_VERBOSE, "FPS: %d/%d -> %d/%d\n", + inlink->frame_rate.num, inlink->frame_rate.den, fps.num, fps.den); + + outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP; + outlink->frame_rate = fps; + outlink->time_base = av_mul_q(inlink->time_base, s->pts); + av_log(ctx, AV_LOG_VERBOSE, "TB: %d/%d -> %d/%d\n", + inlink->time_base.num, inlink->time_base.den, outlink->time_base.num, outlink->time_base.den); + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + DetelecineContext *s = ctx->priv; + int i, len = 0, ret = 0, out = 0; + + if (s->nskip_fields >= 2) { + s->nskip_fields -= 2; + return 0; + } else if (s->nskip_fields >= 1) { + if (s->occupied) { + s->occupied = 0; + s->nskip_fields--; + } + else { + for (i = 0; i < s->nb_planes; i++) { + av_image_copy_plane(s->temp->data[i], s->temp->linesize[i], + inpicref->data[i], inpicref->linesize[i], + s->stride[i], + s->planeheight[i]); + } + s->occupied = 1; + s->nskip_fields--; + return 0; + } + } + + if (s->nskip_fields == 0) { + while(!len && s->pattern[s->pattern_pos]) { + len = s->pattern[s->pattern_pos] - '0'; + s->pattern_pos++; + } + + if (!s->pattern[s->pattern_pos]) + s->pattern_pos = 0; + + if(!len) { // do not output any field as the entire pattern is zero + av_frame_free(&inpicref); + return 0; + } + + if (s->occupied) { + for (i = 0; i < s->nb_planes; i++) { + // fill in the EARLIER field from the new pic + av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * s->first_field, + s->frame->linesize[i] * 2, + inpicref->data[i] + inpicref->linesize[i] * s->first_field, + inpicref->linesize[i] * 2, + s->stride[i], + (s->planeheight[i] - s->first_field + 1) / 2); + // fill in the LATER field from the buffered pic + av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * !s->first_field, + s->frame->linesize[i] * 2, + s->temp->data[i] + s->temp->linesize[i] * !s->first_field, + s->temp->linesize[i] * 2, + s->stride[i], + (s->planeheight[i] - !s->first_field + 1) / 2); + } + len -= 2; + for (i = 0; i < s->nb_planes; i++) { + av_image_copy_plane(s->temp->data[i], s->temp->linesize[i], + inpicref->data[i], inpicref->linesize[i], + s->stride[i], + s->planeheight[i]); + } + s->occupied = 1; + out = 1; + } else { + if (len >= 2) { + // output THIS image as-is + for (i = 0; i < s->nb_planes; i++) + av_image_copy_plane(s->frame->data[i], s->frame->linesize[i], + inpicref->data[i], inpicref->linesize[i], + s->stride[i], + s->planeheight[i]); + len -= 2; + out = 1; + } else if (len == 1) { + // fill in the EARLIER field from the new pic + av_image_copy_plane(s->frame->data[i] + s->frame->linesize[i] * s->first_field, + s->frame->linesize[i] * 2, + inpicref->data[i] + inpicref->linesize[i] * s->first_field, + inpicref->linesize[i] * 2, + s->stride[i], + (s->planeheight[i] - s->first_field + 1) / 2); + // TODO: not sure about the other field + + len--; + out = 1; + } + } + + if (len == 1 && s->occupied) + { + len--; + s->occupied = 0; + } + } + s->nskip_fields = len; + + if (out) { + AVFrame *frame = av_frame_clone(s->frame); + + if (!frame) { + av_frame_free(&inpicref); + return AVERROR(ENOMEM); + } + + frame->pts = outlink->frame_count * av_q2d(av_inv_q(av_mul_q(outlink->frame_rate, outlink->time_base))); + ret = ff_filter_frame(outlink, frame); + } + + av_frame_free(&inpicref); + + return ret; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + DetelecineContext *s = ctx->priv; + + av_frame_free(&s->temp); + av_frame_free(&s->frame); +} + +static const AVFilterPad detelecine_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad detelecine_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + }, + { NULL } +}; + +AVFilter ff_vf_detelecine = { + .name = "detelecine", + .description = NULL_IF_CONFIG_SMALL("Apply an inverse telecine pattern."), + .priv_size = sizeof(DetelecineContext), + .priv_class = &detelecine_class, + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = detelecine_inputs, + .outputs = detelecine_outputs, +}; -- 1.9.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel