如何在ffmpeg中添加一個簡單的filter

如何在ffmpeg中添加一個簡單的filter


大體步驟如下:

1. 編寫filter文件, 比如vf_xxx.c, 放在libavfilter目錄下
2. 在libavfilter/allfilters.c  +++  extern AVFilter ff_vf_xxx
3. 修改libavfilter/Makefile    +++  OBJS-$(CONFIG_XXX_FILTER) += vf_xxx.o
4. 修改ffbuild/config.mak      +++  CONFIG_XXX_FILTER=yes

filter大體寫法

一個filter的定義了不少回調,不過一個簡潔的也許你只需要寫init(),uinit(),query_format()即可. filter會在config_graph()的時候初始化整個濾鏡鏈圖,這裏會調用filter的init()和query_format(),add_buffersrc()會激活濾鏡鏈圖,使frame在濾鏡鏈圖中各種filter回調中進行處理.

pad -link-> filter -link-> pad -link->....
pad -link-|       |-link-> pad -link->
   ....              ....
    int (*preinit)(AVFilterContext *ctx);

    int (*init)(AVFilterContext *ctx);

    int (*init_dict)(AVFilterContext *ctx, AVDictionary **options);
 
    void (*uninit)(AVFilterContext *ctx);

    int (*query_formats)(AVFilterContext *);

    int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags);

    int (*init_opaque)(AVFilterContext *ctx, void *opaque);

    int (*activate)(AVFilterContext *ctx);

編寫一個vf_yuvy.c file 我們進行一個frame yuv 分量的提取測試.

yuv模型

這個顏色模型使用 Y 來表示亮度,還有兩種顏色通道:Cb(藍色色度) 和 Cr(紅色色度)。YCbCr 可以由 RGB 轉換得來,也可以轉換回 RGB。使用這個模型我們可以創建擁有完整色彩的圖像

YCbCr 和 RGB 之間的轉換公式

Y = 0.299R + 0.587G + 0.114B
一旦我們有了亮度後,我們就可以拆分顏色(藍色色度和紅色色度):

Cb = 0.564(B - Y)
Cr = 0.713(R - Y)

# 並且我們也可以使用 YCbCr 轉換回來,甚至得到綠色。
R = Y + 1.402Cr
B = Y + 1.772Cb
G = Y - 0.344Cb - 0.714Cr

省略實現,大體寫法如下:

#include <string.h>

#include "libavutil/internal.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"

#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"

typedef struct YUVYContext {
    const AVClass *class;
    char *yuv;

    enum AVPixelFormat *formats;
} YUVYContext;

static av_cold int init(AVFilterContext *ctx)
{
    YUVYContext *s = ctx->priv;

    ...

    return 0;
}

static av_cold void uninit(AVFilterContext *ctx)
{
    ...
}

//定義此filter在其輸入輸出所支持的format, 一般在filter初始化後調用,此callback必須設置AVFilterLink.out_formats
static int query_formats(AVFilterContext *ctx)
{
    static const enum AVPixelFormat pix_fmts[] = {
        AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE
    };

    AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
    if (!fmts_list)
        return AVERROR(ENOMEM);
    return ff_set_common_formats(ctx, fmts_list);
}

static int cnt = 0;
static int filter_frame(AVFilterLink *link, AVFrame *in)
{
    YUVYContext *yuvy = link->dst->priv;
    int i;
    AVFilterLink *olink = link->dst->outputs[0];
    AVFrame *out = av_frame_alloc();
    out->width   = in->width;
    out->height  = in->height;
    out->format  = in->format;

    out = ff_get_video_buffer(olink, olink->w, olink->h);
    if (!out) {
        av_frame_free(&in);
        return AVERROR(ENOMEM);
    }
    av_frame_copy_props(out, in);
    av_frame_copy(out, in);

    //av_log(link->dst, AV_LOG_INFO, "filter_frame %d.\n", cnt++);
    if (yuvy->yuv[0] == 'y') {
        ...

    } else if (yuvy->yuv[0] == 'u' && yuvy->yuv[1] == 'v') {
        ...
    } else if (yuvy->yuv[0] == 'b') {
        ...
    } else if (yuvy->yuv[0] == 'u') {
        ...
    } else if (yuvy->yuv[0] == 'v') {
        ...
    }

    return ff_filter_frame(olink, out);
}

static int config_props(AVFilterLink *outlink)
{
    ...
}

#define OFFSET(x) offsetof(YUVYContext, x)
static const AVOption options[] = {
    { "yuv", "Y|G|R|B", OFFSET(yuv), AV_OPT_TYPE_STRING, .flags = AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM },
    { NULL }
};

#define CONFIG_YUVY_FILTER 1
#if CONFIG_YUVY_FILTER

#define yuvy_options options
AVFILTER_DEFINE_CLASS(yuvy);

static const AVFilterPad avfilter_vf_yuvy_inputs[] = {
    {
        .name             = "default",
        .type             = AVMEDIA_TYPE_VIDEO,
        .filter_frame     = filter_frame,
    },
    { NULL }
};

static const AVFilterPad avfilter_vf_yuvy_outputs[] = {
    {
        .name         = "default",
        .type         = AVMEDIA_TYPE_VIDEO,
        .config_props = config_props,
    },
    { NULL }
};

AVFilter ff_vf_yuvy = {
    .name          = "yuvy",
    .description   = NULL_IF_CONFIG_SMALL("Convert the input video of yuv420p pixel format to Y|R|G|B graph"),

    .init          = init,
    .uninit        = uninit,

    .query_formats = query_formats,

    .priv_size     = sizeof(YUVYContext),
    .priv_class    = &yuvy_class,

    .inputs        = avfilter_vf_yuvy_inputs,
    .outputs       = avfilter_vf_yuvy_outputs,
};
#endif /* CONFIG_YUVY_FILTER */

測試

ffmpeg -y -i demo.mp4 -vf "yuvy=yuv=y" -c:v libx264 -qp 0  y.mp4
ffmpeg -y -i demo.mp4 -vf "yuvy=yuv=u" -c:v libx264 -qp 0  u.mp4
ffmpeg -y -i demo.mp4 -vf "yuvy=yuv=v" -c:v libx264 -qp 0  v.mp4
ffmpeg -y -i demo.mp4 -vf "yuvy=yuv=uv" -c:v libx264 -qp 0  uv.mp4
ffmpeg -y -i demo.mp4 -vf "yuvy=yuv=b" -c:v libx264 -qp 0  b.mp4

在這裏插入圖片描述

ffmpeg -y -i demo.jpeg -vf "yuvy=yuv=y" y.jpeg; \
ffmpeg -y -i demo.jpeg -vf "yuvy=yuv=u" u.jpeg; \
ffmpeg -y -i demo.jpeg -vf "yuvy=yuv=v" v.jpeg; \
ffmpeg -y -i demo.jpeg -vf "yuvy=yuv=uv" uv.jpeg; \
ffmpeg -y -i demo.jpeg -vf "yuvy=yuv=b" b.jpeg

graph dump

可以看到在部分輸入像素格式不受支持的時候,ffmpeg會自動加一個auto_scale 轉換了format.

                                                                       +---------------+
graph 0 input from stream 0:0:default--[1920x1080 1:1 yuv420p]--default| Parsed_yuvy_0 |default--[1920x1080 1:1 yuv420p]--format:default
                                                                       |    (yuvy)     |
                                                                       +---------------+

+-------------------------------+
| graph 0 input from stream 0:0 |default--[1920x1080 1:1 yuv420p]--Parsed_yuvy_0:default
|           (buffer)            |
+-------------------------------+

                                                +--------------+
format:default--[1920x1080 1:1 yuv420p]--default|   out_0_0    |
                                                | (buffersink) |
                                                +--------------+

                                                       +----------+
Parsed_yuvy_0:default--[1920x1080 1:1 yuv420p]--default|  format  |default--[1920x1080 1:1 yuv420p]--out_0_0:default
                                                       | (format) |
                                                       +----------+
                                                      +---------------+
auto_scaler_0:default--[1280x720 1:1 yuv420p]--default| Parsed_yuvy_0 |default--[1280x720 1:1 yuv420p]--format:default
                                                      |    (yuvy)     |
                                                      +---------------+

+-------------------------------+
| graph 0 input from stream 0:0 |default--[1280x720 1:1 yuv444p]--auto_scaler_0:default
|           (buffer)            |
+-------------------------------+

                                               +--------------+
format:default--[1280x720 1:1 yuv420p]--default|   out_0_0    |
                                               | (buffersink) |
                                               +--------------+

                                                      +----------+
Parsed_yuvy_0:default--[1280x720 1:1 yuv420p]--default|  format  |default--[1280x720 1:1 yuv420p]--out_0_0:default
                                                      | (format) |
                                                      +----------+

                                                                      +---------------+
graph 0 input from stream 0:0:default--[1280x720 1:1 yuv444p]--default| auto_scaler_0 |default--[1280x720 1:1 yuv420p]--Parsed_yuvy_0:default
                                                                      |    (scale)    |
                                                                      +---------------+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章