yading@11: /* yading@11: * Copyright (c) 2012 Rudolf Polzer yading@11: * Copyright (c) 2013 Paul B Mahol yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: /** yading@11: * @file telecine filter, heavily based from mpv-player:TOOLS/vf_dlopen/telecine.c by yading@11: * Rudolf Polzer. yading@11: */ yading@11: yading@11: #include "libavutil/avstring.h" yading@11: #include "libavutil/imgutils.h" yading@11: #include "libavutil/opt.h" yading@11: #include "libavutil/pixdesc.h" yading@11: #include "avfilter.h" yading@11: #include "formats.h" yading@11: #include "internal.h" yading@11: #include "video.h" yading@11: yading@11: typedef struct { yading@11: const AVClass *class; yading@11: int first_field; yading@11: char *pattern; yading@11: unsigned int pattern_pos; yading@11: yading@11: AVRational pts; yading@11: double ts_unit; yading@11: int out_cnt; yading@11: int occupied; yading@11: int64_t frame_count; yading@11: yading@11: int nb_planes; yading@11: int planeheight[4]; yading@11: int stride[4]; yading@11: yading@11: AVFrame *frame[5]; yading@11: AVFrame *temp; yading@11: } TelecineContext; yading@11: yading@11: #define OFFSET(x) offsetof(TelecineContext, x) yading@11: #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM yading@11: yading@11: static const AVOption telecine_options[] = { yading@11: {"first_field", "select first field", OFFSET(first_field), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS, "field"}, yading@11: {"top", "select top field first", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"}, yading@11: {"t", "select top field first", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "field"}, yading@11: {"bottom", "select bottom field first", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"}, yading@11: {"b", "select bottom field first", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "field"}, yading@11: {"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}, yading@11: {NULL} yading@11: }; yading@11: yading@11: AVFILTER_DEFINE_CLASS(telecine); yading@11: yading@11: static av_cold int init(AVFilterContext *ctx) yading@11: { yading@11: TelecineContext *tc = ctx->priv; yading@11: const char *p; yading@11: int max = 0; yading@11: yading@11: if (!strlen(tc->pattern)) { yading@11: av_log(ctx, AV_LOG_ERROR, "No pattern provided.\n"); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: for (p = tc->pattern; *p; p++) { yading@11: if (!av_isdigit(*p)) { yading@11: av_log(ctx, AV_LOG_ERROR, "Provided pattern includes non-numeric characters.\n"); yading@11: return AVERROR_INVALIDDATA; yading@11: } yading@11: yading@11: max = FFMAX(*p - '0', max); yading@11: tc->pts.num += 2; yading@11: tc->pts.den += *p - '0'; yading@11: } yading@11: yading@11: tc->out_cnt = (max + 1) / 2; yading@11: av_log(ctx, AV_LOG_INFO, "Telecine pattern %s yields up to %d frames per frame, pts advance factor: %d/%d\n", yading@11: tc->pattern, tc->out_cnt, tc->pts.num, tc->pts.den); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int query_formats(AVFilterContext *ctx) yading@11: { yading@11: AVFilterFormats *pix_fmts = NULL; yading@11: int fmt; yading@11: yading@11: for (fmt = 0; fmt < AV_PIX_FMT_NB; fmt++) { yading@11: const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); yading@11: if (!(desc->flags & PIX_FMT_HWACCEL)) yading@11: ff_add_format(&pix_fmts, fmt); yading@11: } yading@11: yading@11: ff_set_common_formats(ctx, pix_fmts); yading@11: return 0; yading@11: } yading@11: yading@11: static int config_input(AVFilterLink *inlink) yading@11: { yading@11: TelecineContext *tc = inlink->dst->priv; yading@11: const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); yading@11: int i, ret; yading@11: yading@11: tc->temp = ff_get_video_buffer(inlink, inlink->w, inlink->h); yading@11: if (!tc->temp) yading@11: return AVERROR(ENOMEM); yading@11: for (i = 0; i < tc->out_cnt; i++) { yading@11: tc->frame[i] = ff_get_video_buffer(inlink, inlink->w, inlink->h); yading@11: if (!tc->frame[i]) yading@11: return AVERROR(ENOMEM); yading@11: } yading@11: yading@11: if ((ret = av_image_fill_linesizes(tc->stride, inlink->format, inlink->w)) < 0) yading@11: return ret; yading@11: yading@11: tc->planeheight[1] = tc->planeheight[2] = inlink->h >> desc->log2_chroma_h; yading@11: tc->planeheight[0] = tc->planeheight[3] = inlink->h; yading@11: yading@11: tc->nb_planes = av_pix_fmt_count_planes(inlink->format); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int config_output(AVFilterLink *outlink) yading@11: { yading@11: AVFilterContext *ctx = outlink->src; yading@11: TelecineContext *tc = ctx->priv; yading@11: const AVFilterLink *inlink = ctx->inputs[0]; yading@11: AVRational fps = inlink->frame_rate; yading@11: yading@11: if (!fps.num || !fps.den) { yading@11: av_log(ctx, AV_LOG_ERROR, "The input needs a constant frame rate; " yading@11: "current rate of %d/%d is invalid\n", fps.num, fps.den); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: fps = av_mul_q(fps, av_inv_q(tc->pts)); yading@11: av_log(ctx, AV_LOG_VERBOSE, "FPS: %d/%d -> %d/%d\n", yading@11: inlink->frame_rate.num, inlink->frame_rate.den, fps.num, fps.den); yading@11: yading@11: outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP; yading@11: outlink->frame_rate = fps; yading@11: outlink->time_base = av_mul_q(inlink->time_base, tc->pts); yading@11: yading@11: tc->ts_unit = av_q2d(av_inv_q(av_mul_q(fps, outlink->time_base))); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref) yading@11: { yading@11: AVFilterContext *ctx = inlink->dst; yading@11: AVFilterLink *outlink = ctx->outputs[0]; yading@11: TelecineContext *tc = ctx->priv; yading@11: int i, len, ret = 0, nout = 0; yading@11: yading@11: len = tc->pattern[tc->pattern_pos] - '0'; yading@11: yading@11: tc->pattern_pos++; yading@11: if (!tc->pattern[tc->pattern_pos]) yading@11: tc->pattern_pos = 0; yading@11: yading@11: if (!len) { // do not output any field from this frame yading@11: av_frame_free(&inpicref); yading@11: return 0; yading@11: } yading@11: yading@11: if (tc->occupied) { yading@11: for (i = 0; i < tc->nb_planes; i++) { yading@11: // fill in the EARLIER field from the buffered pic yading@11: av_image_copy_plane(tc->frame[nout]->data[i] + tc->frame[nout]->linesize[i] * tc->first_field, yading@11: tc->frame[nout]->linesize[i] * 2, yading@11: tc->temp->data[i] + tc->temp->linesize[i] * tc->first_field, yading@11: tc->temp->linesize[i] * 2, yading@11: tc->stride[i], yading@11: (tc->planeheight[i] - tc->first_field + 1) / 2); yading@11: // fill in the LATER field from the new pic yading@11: av_image_copy_plane(tc->frame[nout]->data[i] + tc->frame[nout]->linesize[i] * !tc->first_field, yading@11: tc->frame[nout]->linesize[i] * 2, yading@11: inpicref->data[i] + inpicref->linesize[i] * !tc->first_field, yading@11: inpicref->linesize[i] * 2, yading@11: tc->stride[i], yading@11: (tc->planeheight[i] - !tc->first_field + 1) / 2); yading@11: } yading@11: nout++; yading@11: len--; yading@11: tc->occupied = 0; yading@11: } yading@11: yading@11: while (len >= 2) { yading@11: // output THIS image as-is yading@11: for (i = 0; i < tc->nb_planes; i++) yading@11: av_image_copy_plane(tc->frame[nout]->data[i], tc->frame[nout]->linesize[i], yading@11: inpicref->data[i], inpicref->linesize[i], yading@11: tc->stride[i], yading@11: tc->planeheight[i]); yading@11: nout++; yading@11: len -= 2; yading@11: } yading@11: yading@11: if (len >= 1) { yading@11: // copy THIS image to the buffer, we need it later yading@11: for (i = 0; i < tc->nb_planes; i++) yading@11: av_image_copy_plane(tc->temp->data[i], tc->temp->linesize[i], yading@11: inpicref->data[i], inpicref->linesize[i], yading@11: tc->stride[i], yading@11: tc->planeheight[i]); yading@11: tc->occupied = 1; yading@11: } yading@11: yading@11: for (i = 0; i < nout; i++) { yading@11: AVFrame *frame = av_frame_clone(tc->frame[i]); yading@11: yading@11: if (!frame) { yading@11: av_frame_free(&inpicref); yading@11: return AVERROR(ENOMEM); yading@11: } yading@11: yading@11: av_frame_copy_props(frame, inpicref); yading@11: frame->pts = tc->frame_count++ * tc->ts_unit; yading@11: ret = ff_filter_frame(outlink, frame); yading@11: } yading@11: av_frame_free(&inpicref); yading@11: yading@11: return ret; yading@11: } yading@11: yading@11: static av_cold void uninit(AVFilterContext *ctx) yading@11: { yading@11: TelecineContext *tc = ctx->priv; yading@11: int i; yading@11: yading@11: av_frame_free(&tc->temp); yading@11: for (i = 0; i < tc->out_cnt; i++) yading@11: av_frame_free(&tc->frame[i]); yading@11: } yading@11: yading@11: static const AVFilterPad telecine_inputs[] = { yading@11: { yading@11: .name = "default", yading@11: .type = AVMEDIA_TYPE_VIDEO, yading@11: .filter_frame = filter_frame, yading@11: .config_props = config_input, yading@11: }, yading@11: { NULL } yading@11: }; yading@11: yading@11: static const AVFilterPad telecine_outputs[] = { yading@11: { yading@11: .name = "default", yading@11: .type = AVMEDIA_TYPE_VIDEO, yading@11: .config_props = config_output, yading@11: }, yading@11: { NULL } yading@11: }; yading@11: yading@11: AVFilter avfilter_vf_telecine = { yading@11: .name = "telecine", yading@11: .description = NULL_IF_CONFIG_SMALL("Apply a telecine pattern."), yading@11: .priv_size = sizeof(TelecineContext), yading@11: .priv_class = &telecine_class, yading@11: .init = init, yading@11: .uninit = uninit, yading@11: .query_formats = query_formats, yading@11: .inputs = telecine_inputs, yading@11: .outputs = telecine_outputs, yading@11: };