Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * Copyright (c) 2003 Michael Niedermayer <michaelni@gmx.at>
  3.  * Copyright (c) 2013 Clément Bœsch <u pkh me>
  4.  *
  5.  * This file is part of FFmpeg.
  6.  *
  7.  * FFmpeg is free software; you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License as published by
  9.  * the Free Software Foundation; either version 2 of the License, or
  10.  * (at your option) any later version.
  11.  *
  12.  * FFmpeg is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.  * GNU General Public License for more details.
  16.  *
  17.  * You should have received a copy of the GNU General Public License along
  18.  * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
  19.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  20.  */
  21.  
  22. /**
  23.  * @file
  24.  * Simple post processing filter
  25.  *
  26.  * This implementation is based on an algorithm described in
  27.  * "Aria Nosratinia Embedded Post-Processing for
  28.  * Enhancement of Compressed Images (1999)"
  29.  *
  30.  * Originally written by Michael Niedermayer for the MPlayer project, and
  31.  * ported by Clément Bœsch for FFmpeg.
  32.  */
  33.  
  34. #include "libavutil/avassert.h"
  35. #include "libavutil/imgutils.h"
  36. #include "libavutil/opt.h"
  37. #include "libavutil/pixdesc.h"
  38. #include "internal.h"
  39. #include "vf_spp.h"
  40.  
  41. enum mode {
  42.     MODE_HARD,
  43.     MODE_SOFT,
  44.     NB_MODES
  45. };
  46.  
  47. static const AVClass *child_class_next(const AVClass *prev)
  48. {
  49.     return prev ? NULL : avcodec_dct_get_class();
  50. }
  51.  
  52. static void *child_next(void *obj, void *prev)
  53. {
  54.     SPPContext *s = obj;
  55.     return prev ? NULL : s->dct;
  56. }
  57.  
  58. #define OFFSET(x) offsetof(SPPContext, x)
  59. #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
  60. static const AVOption spp_options[] = {
  61.     { "quality", "set quality", OFFSET(log2_count), AV_OPT_TYPE_INT, {.i64 = 3}, 0, MAX_LEVEL, FLAGS },
  62.     { "qp", "force a constant quantizer parameter", OFFSET(qp), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 63, FLAGS },
  63.     { "mode", "set thresholding mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = MODE_HARD}, 0, NB_MODES - 1, FLAGS, "mode" },
  64.         { "hard", "hard thresholding", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_HARD}, INT_MIN, INT_MAX, FLAGS, "mode" },
  65.         { "soft", "soft thresholding", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_SOFT}, INT_MIN, INT_MAX, FLAGS, "mode" },
  66.     { "use_bframe_qp", "use B-frames' QP", OFFSET(use_bframe_qp), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS },
  67.     { NULL }
  68. };
  69.  
  70. static const AVClass spp_class = {
  71.     .class_name       = "spp",
  72.     .item_name        = av_default_item_name,
  73.     .option           = spp_options,
  74.     .version          = LIBAVUTIL_VERSION_INT,
  75.     .category         = AV_CLASS_CATEGORY_FILTER,
  76.     .child_class_next = child_class_next,
  77.     .child_next       = child_next,
  78. };
  79.  
  80. // XXX: share between filters?
  81. DECLARE_ALIGNED(8, static const uint8_t, ldither)[8][8] = {
  82.     {  0,  48,  12,  60,   3,  51,  15,  63 },
  83.     { 32,  16,  44,  28,  35,  19,  47,  31 },
  84.     {  8,  56,   4,  52,  11,  59,   7,  55 },
  85.     { 40,  24,  36,  20,  43,  27,  39,  23 },
  86.     {  2,  50,  14,  62,   1,  49,  13,  61 },
  87.     { 34,  18,  46,  30,  33,  17,  45,  29 },
  88.     { 10,  58,   6,  54,   9,  57,   5,  53 },
  89.     { 42,  26,  38,  22,  41,  25,  37,  21 },
  90. };
  91.  
  92. static const uint8_t offset[127][2] = {
  93.     {0,0},
  94.     {0,0}, {4,4},                                           // quality = 1
  95.     {0,0}, {2,2}, {6,4}, {4,6},                             // quality = 2
  96.     {0,0}, {5,1}, {2,2}, {7,3}, {4,4}, {1,5}, {6,6}, {3,7}, // quality = 3
  97.  
  98.     {0,0}, {4,0}, {1,1}, {5,1}, {3,2}, {7,2}, {2,3}, {6,3}, // quality = 4
  99.     {0,4}, {4,4}, {1,5}, {5,5}, {3,6}, {7,6}, {2,7}, {6,7},
  100.  
  101.     {0,0}, {0,2}, {0,4}, {0,6}, {1,1}, {1,3}, {1,5}, {1,7}, // quality = 5
  102.     {2,0}, {2,2}, {2,4}, {2,6}, {3,1}, {3,3}, {3,5}, {3,7},
  103.     {4,0}, {4,2}, {4,4}, {4,6}, {5,1}, {5,3}, {5,5}, {5,7},
  104.     {6,0}, {6,2}, {6,4}, {6,6}, {7,1}, {7,3}, {7,5}, {7,7},
  105.  
  106.     {0,0}, {4,4}, {0,4}, {4,0}, {2,2}, {6,6}, {2,6}, {6,2}, // quality = 6
  107.     {0,2}, {4,6}, {0,6}, {4,2}, {2,0}, {6,4}, {2,4}, {6,0},
  108.     {1,1}, {5,5}, {1,5}, {5,1}, {3,3}, {7,7}, {3,7}, {7,3},
  109.     {1,3}, {5,7}, {1,7}, {5,3}, {3,1}, {7,5}, {3,5}, {7,1},
  110.     {0,1}, {4,5}, {0,5}, {4,1}, {2,3}, {6,7}, {2,7}, {6,3},
  111.     {0,3}, {4,7}, {0,7}, {4,3}, {2,1}, {6,5}, {2,5}, {6,1},
  112.     {1,0}, {5,4}, {1,4}, {5,0}, {3,2}, {7,6}, {3,6}, {7,2},
  113.     {1,2}, {5,6}, {1,6}, {5,2}, {3,0}, {7,4}, {3,4}, {7,0},
  114. };
  115.  
  116. static void hardthresh_c(int16_t dst[64], const int16_t src[64],
  117.                          int qp, const uint8_t *permutation)
  118. {
  119.     int i;
  120.     int bias = 0; // FIXME
  121.  
  122.     unsigned threshold1 = qp * ((1<<4) - bias) - 1;
  123.     unsigned threshold2 = threshold1 << 1;
  124.  
  125.     memset(dst, 0, 64 * sizeof(dst[0]));
  126.     dst[0] = (src[0] + 4) >> 3;
  127.  
  128.     for (i = 1; i < 64; i++) {
  129.         int level = src[i];
  130.         if (((unsigned)(level + threshold1)) > threshold2) {
  131.             const int j = permutation[i];
  132.             dst[j] = (level + 4) >> 3;
  133.         }
  134.     }
  135. }
  136.  
  137. static void softthresh_c(int16_t dst[64], const int16_t src[64],
  138.                          int qp, const uint8_t *permutation)
  139. {
  140.     int i;
  141.     int bias = 0; //FIXME
  142.  
  143.     unsigned threshold1 = qp * ((1<<4) - bias) - 1;
  144.     unsigned threshold2 = threshold1 << 1;
  145.  
  146.     memset(dst, 0, 64 * sizeof(dst[0]));
  147.     dst[0] = (src[0] + 4) >> 3;
  148.  
  149.     for (i = 1; i < 64; i++) {
  150.         int level = src[i];
  151.         if (((unsigned)(level + threshold1)) > threshold2) {
  152.             const int j = permutation[i];
  153.             if (level > 0) dst[j] = (level - threshold1 + 4) >> 3;
  154.             else           dst[j] = (level + threshold1 + 4) >> 3;
  155.         }
  156.     }
  157. }
  158.  
  159. static void store_slice_c(uint8_t *dst, const int16_t *src,
  160.                           int dst_linesize, int src_linesize,
  161.                           int width, int height, int log2_scale,
  162.                           const uint8_t dither[8][8])
  163. {
  164.     int y, x;
  165.  
  166. #define STORE(pos) do {                                                     \
  167.     temp = ((src[x + y*src_linesize + pos] << log2_scale) + d[pos]) >> 6;   \
  168.     if (temp & 0x100)                                                       \
  169.         temp = ~(temp >> 31);                                               \
  170.     dst[x + y*dst_linesize + pos] = temp;                                   \
  171. } while (0)
  172.  
  173.     for (y = 0; y < height; y++) {
  174.         const uint8_t *d = dither[y];
  175.         for (x = 0; x < width; x += 8) {
  176.             int temp;
  177.             STORE(0);
  178.             STORE(1);
  179.             STORE(2);
  180.             STORE(3);
  181.             STORE(4);
  182.             STORE(5);
  183.             STORE(6);
  184.             STORE(7);
  185.         }
  186.     }
  187. }
  188.  
  189. static void store_slice16_c(uint16_t *dst, const int16_t *src,
  190.                             int dst_linesize, int src_linesize,
  191.                             int width, int height, int log2_scale,
  192.                             const uint8_t dither[8][8], int depth)
  193. {
  194.     int y, x;
  195.     unsigned int mask = -1<<depth;
  196.  
  197. #define STORE16(pos) do {                                                   \
  198.     temp = ((src[x + y*src_linesize + pos] << log2_scale) + (d[pos]>>1)) >> 5;   \
  199.     if (temp & mask )                                                       \
  200.         temp = ~(temp >> 31);                                               \
  201.     dst[x + y*dst_linesize + pos] = temp;                                   \
  202. } while (0)
  203.  
  204.     for (y = 0; y < height; y++) {
  205.         const uint8_t *d = dither[y];
  206.         for (x = 0; x < width; x += 8) {
  207.             int temp;
  208.             STORE16(0);
  209.             STORE16(1);
  210.             STORE16(2);
  211.             STORE16(3);
  212.             STORE16(4);
  213.             STORE16(5);
  214.             STORE16(6);
  215.             STORE16(7);
  216.         }
  217.     }
  218. }
  219.  
  220. static inline void add_block(uint16_t *dst, int linesize, const int16_t block[64])
  221. {
  222.     int y;
  223.  
  224.     for (y = 0; y < 8; y++) {
  225.         *(uint32_t *)&dst[0 + y*linesize] += *(uint32_t *)&block[0 + y*8];
  226.         *(uint32_t *)&dst[2 + y*linesize] += *(uint32_t *)&block[2 + y*8];
  227.         *(uint32_t *)&dst[4 + y*linesize] += *(uint32_t *)&block[4 + y*8];
  228.         *(uint32_t *)&dst[6 + y*linesize] += *(uint32_t *)&block[6 + y*8];
  229.     }
  230. }
  231.  
  232. static void filter(SPPContext *p, uint8_t *dst, uint8_t *src,
  233.                    int dst_linesize, int src_linesize, int width, int height,
  234.                    const uint8_t *qp_table, int qp_stride, int is_luma, int depth)
  235. {
  236.     int x, y, i;
  237.     const int count = 1 << p->log2_count;
  238.     const int linesize = is_luma ? p->temp_linesize : FFALIGN(width+16, 16);
  239.     DECLARE_ALIGNED(16, uint64_t, block_align)[32];
  240.     int16_t *block  = (int16_t *)block_align;
  241.     int16_t *block2 = (int16_t *)(block_align + 16);
  242.     uint16_t *psrc16 = (uint16_t*)p->src;
  243.     const int sample_bytes = (depth+7) / 8;
  244.  
  245.     for (y = 0; y < height; y++) {
  246.         int index = 8 + 8*linesize + y*linesize;
  247.         memcpy(p->src + index*sample_bytes, src + y*src_linesize, width*sample_bytes);
  248.         if (sample_bytes == 1) {
  249.             for (x = 0; x < 8; x++) {
  250.                 p->src[index         - x - 1] = p->src[index +         x    ];
  251.                 p->src[index + width + x    ] = p->src[index + width - x - 1];
  252.             }
  253.         } else {
  254.             for (x = 0; x < 8; x++) {
  255.                 psrc16[index         - x - 1] = psrc16[index +         x    ];
  256.                 psrc16[index + width + x    ] = psrc16[index + width - x - 1];
  257.             }
  258.         }
  259.     }
  260.     for (y = 0; y < 8; y++) {
  261.         memcpy(p->src + (       7-y)*linesize * sample_bytes, p->src + (       y+8)*linesize * sample_bytes, linesize * sample_bytes);
  262.         memcpy(p->src + (height+8+y)*linesize * sample_bytes, p->src + (height-y+7)*linesize * sample_bytes, linesize * sample_bytes);
  263.     }
  264.  
  265.     for (y = 0; y < height + 8; y += 8) {
  266.         memset(p->temp + (8 + y) * linesize, 0, 8 * linesize * sizeof(*p->temp));
  267.         for (x = 0; x < width + 8; x += 8) {
  268.             int qp;
  269.  
  270.             if (p->qp) {
  271.                 qp = p->qp;
  272.             } else{
  273.                 const int qps = 3 + is_luma;
  274.                 qp = qp_table[(FFMIN(x, width - 1) >> qps) + (FFMIN(y, height - 1) >> qps) * qp_stride];
  275.                 qp = FFMAX(1, ff_norm_qscale(qp, p->qscale_type));
  276.             }
  277.             for (i = 0; i < count; i++) {
  278.                 const int x1 = x + offset[i + count - 1][0];
  279.                 const int y1 = y + offset[i + count - 1][1];
  280.                 const int index = x1 + y1*linesize;
  281.                 p->dct->get_pixels(block, p->src + sample_bytes*index, sample_bytes*linesize);
  282.                 p->dct->fdct(block);
  283.                 p->requantize(block2, block, qp, p->dct->idct_permutation);
  284.                 p->dct->idct(block2);
  285.                 add_block(p->temp + index, linesize, block2);
  286.             }
  287.         }
  288.         if (y) {
  289.             if (sample_bytes == 1) {
  290.                 p->store_slice(dst + (y - 8) * dst_linesize, p->temp + 8 + y*linesize,
  291.                                dst_linesize, linesize, width,
  292.                                FFMIN(8, height + 8 - y), MAX_LEVEL - p->log2_count,
  293.                                ldither);
  294.             } else {
  295.                 store_slice16_c((uint16_t*)(dst + (y - 8) * dst_linesize), p->temp + 8 + y*linesize,
  296.                                 dst_linesize/2, linesize, width,
  297.                                 FFMIN(8, height + 8 - y), MAX_LEVEL - p->log2_count,
  298.                                 ldither, depth);
  299.             }
  300.         }
  301.     }
  302. }
  303.  
  304. static int query_formats(AVFilterContext *ctx)
  305. {
  306.     static const enum AVPixelFormat pix_fmts[] = {
  307.         AV_PIX_FMT_YUV444P,  AV_PIX_FMT_YUV422P,
  308.         AV_PIX_FMT_YUV420P,  AV_PIX_FMT_YUV411P,
  309.         AV_PIX_FMT_YUV410P,  AV_PIX_FMT_YUV440P,
  310.         AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ422P,
  311.         AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ440P,
  312.         AV_PIX_FMT_YUV444P10,  AV_PIX_FMT_YUV422P10,
  313.         AV_PIX_FMT_YUV420P10,
  314.         AV_PIX_FMT_YUV444P9,  AV_PIX_FMT_YUV422P9,
  315.         AV_PIX_FMT_YUV420P9,
  316.         AV_PIX_FMT_GRAY8,
  317.         AV_PIX_FMT_GBRP,
  318.         AV_PIX_FMT_GBRP9,
  319.         AV_PIX_FMT_GBRP10,
  320.         AV_PIX_FMT_NONE
  321.     };
  322.  
  323.     AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
  324.     if (!fmts_list)
  325.         return AVERROR(ENOMEM);
  326.     return ff_set_common_formats(ctx, fmts_list);
  327. }
  328.  
  329. static int config_input(AVFilterLink *inlink)
  330. {
  331.     SPPContext *s = inlink->dst->priv;
  332.     const int h = FFALIGN(inlink->h + 16, 16);
  333.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  334.     const int bps = desc->comp[0].depth_minus1 + 1;
  335.  
  336.     av_opt_set_int(s->dct, "bits_per_sample", bps, 0);
  337.     avcodec_dct_init(s->dct);
  338.  
  339.     if (ARCH_X86)
  340.         ff_spp_init_x86(s);
  341.  
  342.     s->hsub = desc->log2_chroma_w;
  343.     s->vsub = desc->log2_chroma_h;
  344.     s->temp_linesize = FFALIGN(inlink->w + 16, 16);
  345.     s->temp = av_malloc_array(s->temp_linesize, h * sizeof(*s->temp));
  346.     s->src  = av_malloc_array(s->temp_linesize, h * sizeof(*s->src) * 2);
  347.  
  348.     if (!s->temp || !s->src)
  349.         return AVERROR(ENOMEM);
  350.     return 0;
  351. }
  352.  
  353. static int filter_frame(AVFilterLink *inlink, AVFrame *in)
  354. {
  355.     AVFilterContext *ctx = inlink->dst;
  356.     SPPContext *s = ctx->priv;
  357.     AVFilterLink *outlink = ctx->outputs[0];
  358.     AVFrame *out = in;
  359.     int qp_stride = 0;
  360.     const int8_t *qp_table = NULL;
  361.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  362.     const int depth = desc->comp[0].depth_minus1 + 1;
  363.  
  364.     /* if we are not in a constant user quantizer mode and we don't want to use
  365.      * the quantizers from the B-frames (B-frames often have a higher QP), we
  366.      * need to save the qp table from the last non B-frame; this is what the
  367.      * following code block does */
  368.     if (!s->qp) {
  369.         qp_table = av_frame_get_qp_table(in, &qp_stride, &s->qscale_type);
  370.  
  371.         if (qp_table && !s->use_bframe_qp && in->pict_type != AV_PICTURE_TYPE_B) {
  372.             int w, h;
  373.  
  374.             /* if the qp stride is not set, it means the QP are only defined on
  375.              * a line basis */
  376.             if (!qp_stride) {
  377.                 w = FF_CEIL_RSHIFT(inlink->w, 4);
  378.                 h = 1;
  379.             } else {
  380.                 w = qp_stride;
  381.                 h = FF_CEIL_RSHIFT(inlink->h, 4);
  382.             }
  383.  
  384.             if (w * h > s->non_b_qp_alloc_size) {
  385.                 int ret = av_reallocp_array(&s->non_b_qp_table, w, h);
  386.                 if (ret < 0) {
  387.                     s->non_b_qp_alloc_size = 0;
  388.                     return ret;
  389.                 }
  390.                 s->non_b_qp_alloc_size = w * h;
  391.             }
  392.  
  393.             av_assert0(w * h <= s->non_b_qp_alloc_size);
  394.             memcpy(s->non_b_qp_table, qp_table, w * h);
  395.         }
  396.     }
  397.  
  398.     if (s->log2_count && !ctx->is_disabled) {
  399.         if (!s->use_bframe_qp && s->non_b_qp_table)
  400.             qp_table = s->non_b_qp_table;
  401.  
  402.         if (qp_table || s->qp) {
  403.             const int cw = FF_CEIL_RSHIFT(inlink->w, s->hsub);
  404.             const int ch = FF_CEIL_RSHIFT(inlink->h, s->vsub);
  405.  
  406.             /* get a new frame if in-place is not possible or if the dimensions
  407.              * are not multiple of 8 */
  408.             if (!av_frame_is_writable(in) || (inlink->w & 7) || (inlink->h & 7)) {
  409.                 const int aligned_w = FFALIGN(inlink->w, 8);
  410.                 const int aligned_h = FFALIGN(inlink->h, 8);
  411.  
  412.                 out = ff_get_video_buffer(outlink, aligned_w, aligned_h);
  413.                 if (!out) {
  414.                     av_frame_free(&in);
  415.                     return AVERROR(ENOMEM);
  416.                 }
  417.                 av_frame_copy_props(out, in);
  418.                 out->width  = in->width;
  419.                 out->height = in->height;
  420.             }
  421.  
  422.             filter(s, out->data[0], in->data[0], out->linesize[0], in->linesize[0], inlink->w, inlink->h, qp_table, qp_stride, 1, depth);
  423.  
  424.             if (out->data[2]) {
  425.                 filter(s, out->data[1], in->data[1], out->linesize[1], in->linesize[1], cw,        ch,        qp_table, qp_stride, 0, depth);
  426.                 filter(s, out->data[2], in->data[2], out->linesize[2], in->linesize[2], cw,        ch,        qp_table, qp_stride, 0, depth);
  427.             }
  428.             emms_c();
  429.         }
  430.     }
  431.  
  432.     if (in != out) {
  433.         if (in->data[3])
  434.             av_image_copy_plane(out->data[3], out->linesize[3],
  435.                                 in ->data[3], in ->linesize[3],
  436.                                 inlink->w, inlink->h);
  437.         av_frame_free(&in);
  438.     }
  439.     return ff_filter_frame(outlink, out);
  440. }
  441.  
  442. static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
  443.                            char *res, int res_len, int flags)
  444. {
  445.     SPPContext *s = ctx->priv;
  446.  
  447.     if (!strcmp(cmd, "level")) {
  448.         if (!strcmp(args, "max"))
  449.             s->log2_count = MAX_LEVEL;
  450.         else
  451.             s->log2_count = av_clip(strtol(args, NULL, 10), 0, MAX_LEVEL);
  452.         return 0;
  453.     }
  454.     return AVERROR(ENOSYS);
  455. }
  456.  
  457. static av_cold int init_dict(AVFilterContext *ctx, AVDictionary **opts)
  458. {
  459.     SPPContext *s = ctx->priv;
  460.     int ret;
  461.  
  462.     s->avctx = avcodec_alloc_context3(NULL);
  463.     s->dct = avcodec_dct_alloc();
  464.     if (!s->avctx || !s->dct)
  465.         return AVERROR(ENOMEM);
  466.  
  467.     if (opts) {
  468.         AVDictionaryEntry *e = NULL;
  469.  
  470.         while ((e = av_dict_get(*opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
  471.             if ((ret = av_opt_set(s->dct, e->key, e->value, 0)) < 0)
  472.                 return ret;
  473.         }
  474.         av_dict_free(opts);
  475.     }
  476.  
  477.     s->store_slice = store_slice_c;
  478.     switch (s->mode) {
  479.     case MODE_HARD: s->requantize = hardthresh_c; break;
  480.     case MODE_SOFT: s->requantize = softthresh_c; break;
  481.     }
  482.     return 0;
  483. }
  484.  
  485. static av_cold void uninit(AVFilterContext *ctx)
  486. {
  487.     SPPContext *s = ctx->priv;
  488.  
  489.     av_freep(&s->temp);
  490.     av_freep(&s->src);
  491.     if (s->avctx) {
  492.         avcodec_close(s->avctx);
  493.         av_freep(&s->avctx);
  494.     }
  495.     av_freep(&s->dct);
  496.     av_freep(&s->non_b_qp_table);
  497. }
  498.  
  499. static const AVFilterPad spp_inputs[] = {
  500.     {
  501.         .name         = "default",
  502.         .type         = AVMEDIA_TYPE_VIDEO,
  503.         .config_props = config_input,
  504.         .filter_frame = filter_frame,
  505.     },
  506.     { NULL }
  507. };
  508.  
  509. static const AVFilterPad spp_outputs[] = {
  510.     {
  511.         .name = "default",
  512.         .type = AVMEDIA_TYPE_VIDEO,
  513.     },
  514.     { NULL }
  515. };
  516.  
  517. AVFilter ff_vf_spp = {
  518.     .name            = "spp",
  519.     .description     = NULL_IF_CONFIG_SMALL("Apply a simple post processing filter."),
  520.     .priv_size       = sizeof(SPPContext),
  521.     .init_dict       = init_dict,
  522.     .uninit          = uninit,
  523.     .query_formats   = query_formats,
  524.     .inputs          = spp_inputs,
  525.     .outputs         = spp_outputs,
  526.     .process_command = process_command,
  527.     .priv_class      = &spp_class,
  528.     .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
  529. };
  530.