From patchwork Wed Dec 27 12:39:08 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Vasile Toncu X-Patchwork-Id: 6993 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.79.195 with SMTP id r64csp7986595jad; Wed, 27 Dec 2017 04:39:25 -0800 (PST) X-Google-Smtp-Source: ACJfBou0Kl+O+0+NvQNWYI5LsXso0242aQCcUIOd/K+m2mkocOlxSGCB2LXhPZ/8CUJaL+7VDrS/ X-Received: by 10.223.164.78 with SMTP id e14mr24608530wra.68.1514378365478; Wed, 27 Dec 2017 04:39:25 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1514378365; cv=none; d=google.com; s=arc-20160816; b=GsEj1f2kIydVHupEUAbdgFa94gByyLG2DWkGyzbir561TNNTCtPyxVYTVg6hZyqJHA fzyHbgBod9EhrEJgydNLS922FB0Aw/d6ywWSjwo7RRASrEK3JHPZx3Rw5fVusUp84zFZ WG1GFs9KAp8Ry6RRzJIKBNObwDyFld4pCqZRQpEFoWSzq8NdJ6Mllf4TfOfYh18nVl2D 5Enm4WNXpWhT3jnFq7jzHB/AjKqoGCGFleDRi8nYapVwA/k74DxQhgRmjCmd8EeM4P3G 1LXBooBYuBVBR37FglN4VA+dUH5Xd9YvE8ksYlxobQf0xfsGh4QehTIcp/V0eAAUZxrA KD6A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to:arc-authentication-results; bh=9WQyxApm8MIMCBDjg8YI8t+33HCQjuDQGs3SsKTlbFE=; b=DmGd1MRyuK0DuPteH8h7gqdbdsL3fgYm6Rog+JmLENfb6KEL4EzOVoc8uniCzVeLrR BkMgVgz6gtkFBWSxt+KoWkSOTBlq7jrd5lKfjbGvQ8Fd4TCf/1vG8DylafKo0481B3yz bsktt+s7M9VgT6rsv+B7V5vP0Tq8geUybeXHfobRGVDwdCawaFL/zCYNTj1E0f3+qwbc GJ61YwuUkhljNYSjxnyCnCUOVe5mga+Co1G7wJ8UCZeA0p1rQO62iYYlRROmQ+ApEQ+N zBu/ER/ujvpy4BqLJwwXikV0n7UuQoRmKgJsCppxKkxeHAY2XBET5d1laWnHfmp+sNRM akpA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@tremend-com.20150623.gappssmtp.com header.s=20150623 header.b=vLJ1WuYe; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id s26si4805452wrb.407.2017.12.27.04.39.24; Wed, 27 Dec 2017 04:39:25 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@tremend-com.20150623.gappssmtp.com header.s=20150623 header.b=vLJ1WuYe; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id D5DC5688387; Wed, 27 Dec 2017 14:39:09 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr0-f181.google.com (mail-wr0-f181.google.com [209.85.128.181]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id A68A068832C for ; Wed, 27 Dec 2017 14:39:03 +0200 (EET) Received: by mail-wr0-f181.google.com with SMTP id p69so27802968wrb.8 for ; Wed, 27 Dec 2017 04:39:17 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tremend-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=6+zjCFWWP8/qn/qYiUW/kRspVXSNQb2+NZgx+nk46iM=; b=vLJ1WuYeBbXHDeMcw9n571rK5P9RTJ0s31wlE8EiD2UGb8Lee3Eb08I//akqqBBK+S H/VIMX06pjeIV8ppAtdUMdSkG6TKrCHngckrzf60/aQ7zUAATzyniq8F2nYQLogr1yhS uuhJH7Dv/2OQYHLVmfLMaS0StD6eHJ0ZFO12AR3xodQgSM1nvu6a+ijsxejHwnK6r3Pv 7NiVLp3F0PzCJfmhaQiwQ6z9uoAlFoBJEozU1LGYY+kgj8eUAizMxQOsdnTuuI3+rBCT q5ctKEif3067uY6haLIHLeboFt05CpYqilYp0YOYfjadmzpNdYbV9f6N08XgOPZXP3Xa 6rVw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=6+zjCFWWP8/qn/qYiUW/kRspVXSNQb2+NZgx+nk46iM=; b=uSQeBLMhKVYxdtXxXYixAOVsGo1KAyw6zS5DKvBA4ivvjHbutunrCrUsDkTLFNCCJy pTi+89mtQ/UDMyxhqmT+f4cMySzbVf5fyHaK7RggYb25kathhVnCEsLRT6XZVGpGlswr Vb28oJlWbkFNdWQuZfMW2iE3CYAXiBIL0oAZ1bXW2GRaaXTJrPKpWgMsR4dr8jH0m1rU 3SYxdQzk3WoYIlCrX1ue7zlm6MmoejmOKT17Kht720mjQNfmGbCkz9Lc/DVZujXCXpmx ZMDkdZOoiTv7HIE3Fx9Rdyh++BrqEu4qC3f2ja/eTUubgaIGfQTmVqLXb+lhENwk4Ts+ ebJQ== X-Gm-Message-State: AKGB3mKE/Io8dSDy7Dq09sqLAgh9rvYfG3ss4OXBDTdgOyoisiyw59ZX Ygu6AFrypMvhl4mhwqofl919aOit X-Received: by 10.223.163.79 with SMTP id d15mr27056187wrb.32.1514378356384; Wed, 27 Dec 2017 04:39:16 -0800 (PST) Received: from vt-pc.tremend.ro ([86.125.13.144]) by smtp.gmail.com with ESMTPSA id m69sm33396986wma.3.2017.12.27.04.39.15 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 27 Dec 2017 04:39:15 -0800 (PST) From: Vasile Toncu To: ffmpeg-devel@ffmpeg.org Date: Wed, 27 Dec 2017 14:39:08 +0200 Message-Id: <1514378348-28123-1-git-send-email-vasile.toncu@tremend.com> X-Mailer: git-send-email 2.7.4 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] added reitnerlace filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Vasile Toncu Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" --- doc/filters.texi | 87 ++++++- libavfilter/Makefile | 2 + libavfilter/allfilters.c | 2 + libavfilter/reinterlace.h | 130 ++++++++++ libavfilter/vf_reinterlace.c | 597 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 812 insertions(+), 6 deletions(-) create mode 100644 libavfilter/reinterlace.h create mode 100644 libavfilter/vf_reinterlace.c diff --git a/doc/filters.texi b/doc/filters.texi index 68f54f1..370be9b 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -564,13 +564,13 @@ select RIAA. @item cd select Compact Disc (CD). @item 50fm -select 50µs (FM). +select 50µs (FM). @item 75fm -select 75µs (FM). +select 75µs (FM). @item 50kf -select 50µs (FM-KF). +select 50µs (FM-KF). @item 75kf -select 75µs (FM-KF). +select 75µs (FM-KF). @end table @end table @@ -7346,7 +7346,7 @@ If not set, the filter will use the QP from the video stream (if available). @item strength Set filter strength. It accepts an integer in range -15 to 32. Lower values mean more details but also more artifacts, while higher values make the image smoother -but also blurrier. Default value is @code{0} − PSNR optimal. +but also blurrier. Default value is @code{0} − PSNR optimal. @item use_bframe_qp Enable the use of the QP from the B-Frames if set to @code{1}. Using this @@ -13191,6 +13191,81 @@ pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1. @table @option @end table +@section reinterlace +Reinterlace filter does various interlace operations with the frames of a video. + +@table @option + +@item mode +The mode of the filter + +The permitted values for @var{mode} are: + +@table @samp +@item merge, 0 +Merges lines of two consecutive frames. Skips even frames. The output has half frame rate of the input. + +@item drop_even, 1 +Drops even frames. The output has half frame rate of the input. + +@item drop_odd, 2 +Drop odd frames. The output has half frame rate of the input. + +@item pad, 3 +Merges all the frames with a black frame. The output has the same frame rate as as the input. + + +@item interleave_top, 4 +Interleaves lines of two consecutive frames. Skips even frames. The output has half frame rate of the input. + +@item interleave_bottom, 5 +Interleaves lines of two consecutive frames. Skips even frames. The output has half frame rate of the input. + +@item interlacex2, 6 +For every frames in the input frame adds another one which is obtaining by the interlace of two consecutive frames. +The output has double frame rate of the input. + +@item mergex2, 7 +Merge every frame with the next frame. The output has the same frame rate as as the input. + +@item merge_tff, 8 +Merges the frames of the input considering also the parity and the top_filed_first information of the frames. + +The rules for the @var{merge_tff} are the folowng: + + 1. ensure the odd frame metadata indicates a top field, @* + 2. ensure the even frame metadata indicates a bottom field, @* + 3. move the odd frame into the upper field of the new image, @* + 4. move the even frame into the lower field of the new image, @* + 5. if frames are out of order (bottom field then top field), drop the first field @* + 6. if frames are duplicates (top field then top field), drop the first field @* + 7. if frames don't have interlace metadata, merge as if they were in the right order @* + + +@item merge_bff, 9 +Merges the frames of the input considering also the parity and the top_filed_first information of the frames. + +The rules for the @var{merge_bff} are similar with those for @var{merge_tff}, albeit inverted appropriately. + +@end table + +Default mode is @code{merge, 0}. + +@item flags +One can add various flags to the reitnerlace filter. + +The permitted values for @var{flags} are: + +@table @option +@item low_pass_filter, 1 +Before copying a line of a frame, it gots filtered using a simple low pass filter with the upper and lowwer frame lines. + +Vertical low-pass filtering can only be enabled for @option{mode} +@var{interleave_top} and @var{interleave_bottom}. + +@end table +@end table + @c man end VIDEO FILTERS @chapter Video Sources @@ -15897,4 +15972,4 @@ movie=dvd.vob:s=v:0+#0x81 [video] [audio] @end example @end itemize -@c man end MULTIMEDIA SOURCES +@c man end MULTIMEDIA SOURCES \ No newline at end of file diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 8916588..606dfe0 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -286,6 +286,8 @@ OBJS-$(CONFIG_TESTSRC2_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_NULLSINK_FILTER) += vsink_nullsink.o +OBJS-$(CONFIG_REINTERLACE_FILTER) += vf_reinterlace.o + # multimedia filters OBJS-$(CONFIG_ADRAWGRAPH_FILTER) += f_drawgraph.o OBJS-$(CONFIG_AHISTOGRAM_FILTER) += avf_ahistogram.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index fa7d304..fa3c3d1 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -306,6 +306,8 @@ void avfilter_register_all(void) REGISTER_FILTER(NULLSINK, nullsink, vsink); + REGISTER_FILTER(REINTERLACE, reinterlace, vf); + /* multimedia filters */ REGISTER_FILTER(ADRAWGRAPH, adrawgraph, avf); REGISTER_FILTER(AHISTOGRAM, ahistogram, avf); diff --git a/libavfilter/reinterlace.h b/libavfilter/reinterlace.h new file mode 100644 index 0000000..924f347 --- /dev/null +++ b/libavfilter/reinterlace.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017 Vasile Toncu + * + * 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 + * Reinterlace filter + * + * @author Vasile Toncu ( toncu.vasile gmail com ) + * + * @see https://en.wikipedia.org/wiki/Interlaced_video + */ + +#include + +#include "libavutil/avassert.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/imgutils.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +enum FilterMode { + MODE_MERGE, + MODE_DROP_EVEN, + MODE_DROP_ODD, + MODE_PAD, + MODE_INTERLEAVE_TOP, + MODE_INTERLEAVE_BOTTOM, + MODE_INTERLACE_X2, + MODE_MERGE_X2, + MODE_MERGE_TFF, + MODE_MERGE_BFF, + MODE_NB +}; + +enum FilterFlags { + FLAG_NOTHING = 0x00, + FLAG_VLPF = 0x01, + FLAG_NB +}; + + +typedef struct { + const AVClass *class; + int mode; + int flags; + + AVFrame *prev_frame, *current_frame; + int current_frame_index; + + uint8_t *black_vec[4]; + + int skip_next_frame; + + void *thread_data; + +} ReInterlaceContext; + +#define OFFSET(x) offsetof(ReInterlaceContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption reinterlace_options[] = { + { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_MERGE}, 0, MODE_NB - 1, FLAGS, "mode" }, + { "merge", "MODE_MERGE", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MERGE}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "drop_even", "MODE_DROP_EVEN", 0, AV_OPT_TYPE_CONST, {.i64=MODE_DROP_EVEN}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "drop_odd", "MODE_DROP_ODD", 0, AV_OPT_TYPE_CONST, {.i64=MODE_DROP_ODD}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "pad", "MODE_PAD", 0, AV_OPT_TYPE_CONST, {.i64=MODE_PAD}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "interleave_top", "MODE_INTERLEAVE_TOP", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLEAVE_TOP}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "interleave_bottom", "MODE_INTERLEAVE_BOTTOM", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLEAVE_BOTTOM}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "interlacex2", "MODE_INTERLACE_X2", 0, AV_OPT_TYPE_CONST, {.i64=MODE_INTERLACE_X2}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "mergex2", "MODE_MERGE_X2", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MERGE_X2}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "merge_tff", "MODE_MERGE_TFF", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MERGE_TFF}, INT_MIN, INT_MAX, FLAGS, "mode"}, + { "merge_bff", "MODE_MERGE_BFF", 0, AV_OPT_TYPE_CONST, {.i64=MODE_MERGE_BFF}, INT_MIN, INT_MAX, FLAGS, "mode"}, + + { "flags", "add flag for reinterlace", OFFSET(flags), AV_OPT_TYPE_INT, {.i64=FLAG_NOTHING}, 0, 0xFF, FLAGS, "flags" }, + { "low_pass_filter", "FLAG_VLPF", 0, AV_OPT_TYPE_CONST, {.i64 = FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags"}, + { "vlpf", "FLAG_VLPF", 0, AV_OPT_TYPE_CONST, {.i64 = FLAG_VLPF}, INT_MIN, INT_MAX, FLAGS, "flags"}, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(reinterlace); + +#define IS_ODD(value) (value & 1) + +typedef struct ReInterlaceThreadData { + AVFrame *out, *first, *second; + int plane; + ReInterlaceContext *reinterlace; + + int scale_w_plane12_factor; + int scale_h_plane12_factor; + +} ReInterlaceThreadData; + +#define PIXEL_FORMATS AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, \ + AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, \ + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV411P, \ + AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUVJ422P, \ + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ420P, \ + AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ440P, \ + AV_PIX_FMT_YUV444P9, AV_PIX_FMT_YUV422P9, \ + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV444P10, \ + AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV420P10, \ + AV_PIX_FMT_YUV440P10, AV_PIX_FMT_YUV444P12, \ + AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV420P12, \ + AV_PIX_FMT_YUV440P12, AV_PIX_FMT_YUV444P14, \ + AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14, \ + AV_PIX_FMT_YUV444P16, AV_PIX_FMT_YUV422P16, \ + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_GRAY8, \ + AV_PIX_FMT_NONE + diff --git a/libavfilter/vf_reinterlace.c b/libavfilter/vf_reinterlace.c new file mode 100644 index 0000000..ee28593 --- /dev/null +++ b/libavfilter/vf_reinterlace.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2017 Vasile Toncu + * + * 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 + * Reinterlace filter + * + * @author Vasile Toncu ( toncu.vasile gmail com ) + * + * @see https://en.wikipedia.org/wiki/Interlaced_video + */ + + +#include "reinterlace.h" + +static av_cold int init(AVFilterContext *ctx) +{ + ReInterlaceContext *reinterlace = ctx->priv; + + reinterlace->current_frame_index = 0; + reinterlace->prev_frame = NULL; + reinterlace->current_frame = NULL; + + for (int i = 0; i < 4; i++) + reinterlace->black_vec[i] = NULL; + + reinterlace->skip_next_frame = 0; + + reinterlace->thread_data = (ReInterlaceThreadData *) malloc(4 * sizeof(ReInterlaceThreadData)); + + return 0; +} + +static int query_formats(AVFilterContext *ctx) +{ + //const ReInterlaceContext *reinterlace = ctx->priv; + + // TODO add more necesary formats + static const enum AVPixelFormat all_pix_fmts[] = { + PIXEL_FORMATS + }; + + AVFilterFormats *fmts_list; + const enum AVPixelFormat *pix_fmts = NULL; + + pix_fmts = all_pix_fmts; + fmts_list = ff_make_format_list(pix_fmts); + + if (!fmts_list) + return AVERROR(ENOMEM); + + return ff_set_common_formats(ctx, fmts_list); +} + +static int config_out_props(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AVFilterLink *inlink = outlink->src->inputs[0]; + ReInterlaceContext *reinterlace = ctx->priv; + int r_mode = reinterlace->mode; + + switch (r_mode) { + case MODE_MERGE: + outlink->h = 2 * inlink->h; + outlink->time_base = av_mul_q(inlink->time_base , (AVRational){2,1}); + outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){1,2}); + outlink->sample_aspect_ratio = av_mul_q(inlink->sample_aspect_ratio, av_make_q(2, 1)); + break; + + case MODE_PAD: + outlink->h = 2 * inlink->h; + outlink->sample_aspect_ratio = av_mul_q(inlink->sample_aspect_ratio, av_make_q(2, 1)); + outlink->frame_rate = inlink->frame_rate; + outlink->time_base = inlink->time_base; + break; + + case MODE_DROP_EVEN: + case MODE_DROP_ODD: + case MODE_INTERLEAVE_TOP: + case MODE_INTERLEAVE_BOTTOM: + outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){1,2}); + outlink->time_base = av_mul_q(inlink->time_base , (AVRational){2,1}); + break; + + case MODE_INTERLACE_X2: + outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){2,1}); + outlink->time_base = av_mul_q(inlink->time_base , (AVRational){1,2}); + break; + + case MODE_MERGE_X2: + outlink->h = 2 * inlink->h; + outlink->sample_aspect_ratio = av_mul_q(inlink->sample_aspect_ratio, av_make_q(2, 1)); + outlink->frame_rate = inlink->frame_rate; + outlink->time_base = inlink->time_base; + break; + + case MODE_MERGE_BFF: + case MODE_MERGE_TFF: + outlink->h = 2 * inlink->h; + outlink->sample_aspect_ratio = av_mul_q(inlink->sample_aspect_ratio, av_make_q(2, 1)); + outlink->frame_rate = av_mul_q(inlink->frame_rate, (AVRational){1,2}); + break; + + default: + av_assert0(0); + + } + + if (reinterlace->flags & FLAG_VLPF) + if (r_mode != MODE_INTERLEAVE_TOP && r_mode != MODE_INTERLEAVE_BOTTOM) + reinterlace->flags &= ~FLAG_VLPF; + + return 0; +} + +static void copy_line_lowpass(uint8_t *to, ptrdiff_t width, uint8_t *from, + uint8_t *from_up, uint8_t *from_down) +{ + for (int i = 0; i < width; i++) + //for (int i = width; i--; ) + to[i] = (1 + from[i] + from[i] + from_up[i] + from_down[i]) >> 2; +} + +static int filter_frame_plane(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + // jobnr is usualy plane number + ReInterlaceThreadData *rtd = arg; + ReInterlaceContext *reinterlace = rtd->reinterlace; + AVFrame *first = rtd->first; + AVFrame *second = rtd->second; + AVFrame *out = rtd->out; + + int plane = rtd->plane; + int r_mode = reinterlace->mode; + + int x = (1 == plane || 2 == plane) ? rtd->scale_w_plane12_factor : 1; + int y = (1 == plane || 2 == plane) ? rtd->scale_h_plane12_factor : 1; + int ls_offset; + int offset1, offset2, offset3, offset4; + + switch (r_mode) { + case MODE_MERGE: + av_image_copy_plane(out->data[plane], 2 * out->linesize[plane], + first->data[plane], first->linesize[plane], first->width / x, first->height / y); + av_image_copy_plane(out->data[plane] + out->linesize[plane], 2 * out->linesize[plane], + second->data[plane], second->linesize[plane], second->width / x, second->height / y); + break; + + case MODE_PAD: + ls_offset = (reinterlace->current_frame_index & 1) ? 0 : out->linesize[plane]; + av_image_copy_plane(out->data[plane] + ls_offset, 2 * out->linesize[plane], + second->data[plane], second->linesize[plane], second->width / x, second->height / y); + av_image_copy_plane(out->data[plane] + out->linesize[plane] - ls_offset, 2 * out->linesize[plane], + reinterlace->black_vec[plane], second->linesize[plane], second->width / x, second->height / y); + break; + + case MODE_INTERLEAVE_BOTTOM: + case MODE_INTERLEAVE_TOP: + y = y * 2; + + if (reinterlace->flags & FLAG_VLPF) { + + int lines, cols; + AVFrame *from_frame; + uint8_t *from, *to; + int from_step, to_step; + + lines = (MODE_INTERLEAVE_TOP == r_mode) ? (2 * out->height / y + 1) / 2 : (2 * out->height / y + 0) / 2; + cols = out->width / x; + from_frame = first; + from = from_frame->data[plane]; + to = out->data[plane]; + + if (MODE_INTERLEAVE_BOTTOM == r_mode) { + from = from + from_frame->linesize[plane]; + to = to + out->linesize[plane]; + } + + from_step = 2 * from_frame->linesize[plane]; + to_step = 2 * out->linesize[plane]; + + // when i = lines + copy_line_lowpass(to, cols, from, from, from + from_frame->linesize[plane]); + to += to_step; + from += from_step; + + for (int i = lines - 2; i; i--) { + //from_up = (lines == i) ? from : from - from_frame->linesize[plane]; + //from_down = (1 == i) ? from : from + from_frame->linesize[plane]; + copy_line_lowpass(to, cols, from, from - from_frame->linesize[plane], from + from_frame->linesize[plane]); + to += to_step; + from += from_step; + } + + // when i == 1 + copy_line_lowpass(to, cols, from, from - from_frame->linesize[plane], from); + to += to_step; + from += from_step; + + lines = (MODE_INTERLEAVE_BOTTOM == r_mode) ? ((2 * out->height / y) + 1) / 2 : (2 * out->height / y + 0) / 2; + cols = out->width / x; + from_frame = second; + from = from_frame->data[plane]; + to = out->data[plane]; + + if (MODE_INTERLEAVE_TOP == r_mode) { + from = from + from_frame->linesize[plane]; + to = to + out->linesize[plane]; + } + + from_step = 2 * from_frame->linesize[plane]; + to_step = 2 * out->linesize[plane]; + + // when i = lines + copy_line_lowpass(to, cols, from, from, from + from_frame->linesize[plane]); + to += to_step; + from += from_step; + + for (int i = lines - 2; i; i--) { + //from_up = (lines == i) ? from : from - from_frame->linesize[plane]; + //from_down = (1 == i) ? from : from + from_frame->linesize[plane]; + copy_line_lowpass(to, cols, from, from - from_frame->linesize[plane], from + from_frame->linesize[plane]); + to += to_step; + from += from_step; + } + + // when i == 1 + copy_line_lowpass(to, cols, from, from - from_frame->linesize[plane], from); + to += to_step; + from += from_step; + + } else { + offset1 = (MODE_INTERLEAVE_TOP == r_mode) ? 0 : out->linesize[plane]; + offset2 = (MODE_INTERLEAVE_TOP == r_mode) ? 0 : first->linesize[plane]; + offset3 = (MODE_INTERLEAVE_TOP == r_mode) ? out->linesize[plane] : 0; + offset4 = (MODE_INTERLEAVE_TOP == r_mode) ? second->linesize[plane] : 0; + + av_image_copy_plane(out->data[plane] + offset1, 2 * out->linesize[plane], + first->data[plane] + offset2, 2 * first->linesize[plane], + first->width / x, first->height / y); + av_image_copy_plane(out->data[plane] + offset3, 2 * out->linesize[plane], + second->data[plane] + offset4, 2 * second->linesize[plane], + second->width / x, second->height / y); + } + break; + + case MODE_INTERLACE_X2: + y = y * 2; + + offset1 = 0; offset2 = 0; + offset3 = out->linesize[plane]; + offset4 = second->linesize[plane]; + + if (second->interlaced_frame && second->top_field_first) { + offset1 = out->linesize[plane]; + offset2 = first->linesize[plane]; + offset3 = 0; offset4 = 0; + } + + av_image_copy_plane(out->data[plane] + offset1, 2 * out->linesize[plane], + first->data[plane] + offset2, 2 * first->linesize[plane], + first->width / x, first->height / y); + av_image_copy_plane(out->data[plane] + offset3, 2 * out->linesize[plane], + second->data[plane] + offset4, 2 * second->linesize[plane], + second->width / x, second->height / y); + break; + + case MODE_MERGE_X2: + if ( IS_ODD(reinterlace->current_frame_index - 1) ) { + av_image_copy_plane(out->data[plane], 2 * out->linesize[plane], + second->data[plane], second->linesize[plane], second->width / x, second->height / y); + av_image_copy_plane(out->data[plane] + out->linesize[plane], 2 * out->linesize[plane], + first->data[plane], first->linesize[plane], first->width / x, first->height / y); + } else { + av_image_copy_plane(out->data[plane], 2 * out->linesize[plane], + first->data[plane], first->linesize[plane], first->width / x, first->height / y); + av_image_copy_plane(out->data[plane] + out->linesize[plane], 2 * out->linesize[plane], + second->data[plane], second->linesize[plane], second->width / x, second->height / y); + } + break; + + case MODE_MERGE_TFF: + case MODE_MERGE_BFF: + offset1 = (MODE_MERGE_TFF == r_mode) ? 0 : out->linesize[plane]; + offset2 = (MODE_MERGE_TFF == r_mode) ? out->linesize[plane] : 0; + + av_image_copy_plane(out->data[plane] + offset1, 2 * out->linesize[plane], + first->data[plane], first->linesize[plane], first->width / x, first->height / y); + av_image_copy_plane(out->data[plane] + offset2, 2 * out->linesize[plane], + second->data[plane], second->linesize[plane], second->width / x, second->height / y); + break; + + default: + break; + } + + return 0; +} + +static ReInterlaceThreadData *get_ReInterlaceThreadData(AVFrame *out, AVFrame *first, AVFrame *second, + int plane, ReInterlaceContext *reinterlace, + int scale_w_plane12_factor, + int scale_h_plane12_factor) +{ + ReInterlaceThreadData *rtd = &((ReInterlaceThreadData *)reinterlace->thread_data)[plane]; + + if (!rtd) + return rtd; + + rtd->out = out; + rtd->first = first; + rtd->second = second; + rtd->plane = plane; + rtd->reinterlace = reinterlace; + rtd->scale_h_plane12_factor = scale_h_plane12_factor; + rtd->scale_w_plane12_factor = scale_w_plane12_factor; + + return rtd; +} + +/** + * alocate memory for a black frame + */ +static int init_black_buffers(ReInterlaceContext *reinterlace, AVFrame *frame, int format) +{ + int black_vec_size = frame->width * frame->height * 3; + int val0 = 16; + int val128 = 128; + + if (AV_PIX_FMT_YUVJ420P == format || + AV_PIX_FMT_YUVJ422P == format || + AV_PIX_FMT_YUVJ440P == format || + AV_PIX_FMT_YUVJ444P == format) { + + val0 = 0; + + } + + for (int i = 0; i < 4; i++) { + reinterlace->black_vec[i] = (uint8_t *) malloc(black_vec_size); + + if ( !reinterlace->black_vec[i] ) + return AVERROR(ENOMEM); + + memset(reinterlace->black_vec[i], (0 == i || 3 == i ? val0 : val128), black_vec_size); + } + + return 0; +} + +static void copy_all_planes(AVFilterContext *ctx, + ReInterlaceContext *reinterlace, + const AVPixFmtDescriptor *desc, + AVFrame *out, AVFrame *first, AVFrame *second) +{ + int scale_w_plane12_factor = 1 << desc->log2_chroma_w; + int scale_h_plane12_factor = 1 << desc->log2_chroma_h; + + for (int plane = 0; plane < desc->nb_components; plane++) { + + ReInterlaceThreadData *rtd = get_ReInterlaceThreadData(out, first, second, + plane, reinterlace, scale_w_plane12_factor, scale_h_plane12_factor); + + ctx->internal->execute(ctx, filter_frame_plane, rtd, NULL, FFMIN(desc->nb_components, ctx->graph->nb_threads)); + //filter_frame_plane(ctx, rtd, plane, desc->nb_components); + } +} + + + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + ReInterlaceContext *reinterlace = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); + AVFrame *out, *first, *second; + int ret; + + int r_mode = reinterlace->mode; + + av_frame_free(&(reinterlace->prev_frame)); + reinterlace->prev_frame = reinterlace->current_frame; + reinterlace->current_frame = in; + reinterlace->current_frame_index++; + + // we process two frames at a time, thus only even frame indexes are considered + if ( IS_ODD(reinterlace->current_frame_index) ) { + if (MODE_PAD == r_mode || MODE_MERGE_X2 == r_mode + || MODE_INTERLACE_X2 == r_mode || MODE_MERGE_BFF == r_mode + || MODE_MERGE_TFF == r_mode) { + } else { + return 0; + } + } + + if (1 == reinterlace->current_frame_index) { + ret = init_black_buffers(reinterlace, in, outlink->format); + + if (ret < 0) + return ret; + } + + first = reinterlace->prev_frame; + second = reinterlace->current_frame; + + switch (r_mode) { + case MODE_DROP_EVEN: + case MODE_DROP_ODD: + out = (r_mode == MODE_DROP_ODD) ? reinterlace->current_frame : reinterlace->prev_frame; + out = av_frame_clone(out); + + if (!out) + return AVERROR(ENOMEM); + + out->pts = out->pts >> 1; + ret = ff_filter_frame(outlink, out); + break; + + case MODE_MERGE: + case MODE_MERGE_X2: + case MODE_MERGE_TFF: + case MODE_MERGE_BFF: + if (MODE_MERGE_X2 == r_mode && 1 == reinterlace->current_frame_index) + return 0; + + if (MODE_MERGE_BFF == r_mode || MODE_MERGE_TFF == r_mode) { + if (!first) + return 0; + + if (reinterlace->skip_next_frame) { + reinterlace->skip_next_frame = 0; + return 0; + } + + if (1 == first->interlaced_frame && 1 == second->interlaced_frame) + { + if (first->top_field_first == second->top_field_first) + return 0; + else if (MODE_MERGE_BFF == reinterlace->mode && first->top_field_first != 0) + return 0; + else if (MODE_MERGE_TFF == reinterlace->mode && first->top_field_first != 1) + return 0; + } + } + + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + + if (!out) + return AVERROR(ENOMEM); + + av_frame_copy_props(out, first); + out->sample_aspect_ratio = av_mul_q(first->sample_aspect_ratio, av_make_q(2, 1)); + out->interlaced_frame = 1; + out->top_field_first = MODE_MERGE_BFF == r_mode ? 0 : 1; + out->height = outlink->h; + + if (MODE_MERGE == r_mode) + out->pts = out->pts >> 1; + + copy_all_planes(ctx, reinterlace, desc, out, first, second); + + if (MODE_MERGE_BFF == r_mode || MODE_MERGE_TFF == r_mode) + reinterlace->skip_next_frame = 1; + + ret = ff_filter_frame(outlink, out); + break; + + case MODE_PAD: + out = ff_get_video_buffer(outlink, outlink->w, outlink->h * 2); + + if (!out) + return AVERROR(ENOMEM); + + av_frame_copy_props(out, second); + out->sample_aspect_ratio = av_mul_q(second->sample_aspect_ratio, av_make_q(2, 1)); + out->height = outlink->h; + + copy_all_planes(ctx, reinterlace, desc, out, first, second); + + ret = ff_filter_frame(outlink, out); + break; + + case MODE_INTERLEAVE_BOTTOM: + case MODE_INTERLEAVE_TOP: + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + av_frame_copy_props(out, first); + + copy_all_planes(ctx, reinterlace, desc, out, first, second); + + out->pts = out->pts >> 1; + out->interlaced_frame = 1; + out->top_field_first = (MODE_INTERLEAVE_TOP == r_mode) ? 1 : 0; + ret = ff_filter_frame(outlink, out); + break; + + case MODE_INTERLACE_X2: + if (1 == reinterlace->current_frame_index) + return 0; + + out = av_frame_clone(first); + + if (!out) + return AVERROR(ENOMEM); + + // output first frame + out->pts = (AV_NOPTS_VALUE != first->pts ) ? first->pts * 2 : AV_NOPTS_VALUE; + out->interlaced_frame = 1; + ret = ff_filter_frame(outlink, out); + + if (ret < 0) + return ret; + + // output the second frame interlaced with first frame + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + + if (!out) + return AVERROR(ENOMEM); + + av_frame_copy_props(out, second); + out->interlaced_frame = 1; + out->top_field_first = !out->top_field_first; + out->pts = first->pts + second->pts; + out->pts = (AV_NOPTS_VALUE == first->pts || AV_NOPTS_VALUE == second->pts) ? AV_NOPTS_VALUE : out->pts; + + copy_all_planes(ctx, reinterlace, desc, out, first, second); + + ret = ff_filter_frame(outlink, out); + break; + + default: + av_assert0(0); + } + + return ret; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + ReInterlaceContext *reinterlace = ctx->priv; + int i; + + for (i = 0; i < 4; i++) + if (reinterlace->black_vec[i]) + free (reinterlace->black_vec[i]); + + free(reinterlace->thread_data); + +} + +static const AVFilterPad reinterlace_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad reinterlace_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_out_props, + }, + { NULL } +}; + +AVFilter ff_vf_reinterlace = { + .name = "reinterlace", + .description = NULL_IF_CONFIG_SMALL("Various interlace frame manipulations"), + .priv_size = sizeof(ReInterlaceContext), + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = reinterlace_inputs, + .outputs = reinterlace_outputs, + .priv_class = &reinterlace_class, + .flags = AVFILTER_FLAG_SLICE_THREADS | AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +};