注册ffmpeg滤镜过程

1.注册流程

  • 在libavfilter目录下,创建vf_lpscale.c文件,这个文件为具体的滤镜代码文件。
  • 在libavfilter目目录下,修改allfilters.c文件中,增加:externAVFilter ff_vf_arlpscale;
  • 在libavfilter目录下,修改Makefile,增加一行:OBJS-$(CONFIG_ARLPSCALE_FILTER) += vf_lpscale.o
  • 重新配置,执行configure,生成config.mak/configh.h
  • 编译ffmpeg,新注册滤镜已经注册到了ffmpeg中。

2.滤镜基础

ffmpeg默认有三个滤镜,buffersrc、format、buffersink,buffersrc为filter graph的第一个filter,buffersink为输出filter,我们注册的滤镜在buffersrc、format之间。

  • 每一个滤镜对应一个AVFilterContext结构体,用于描述滤镜的上下文,
  • filter之间的链接通过AVFilterLink描述
  • filter之间数据传递的数据格式为avframe,可以配置frame的格式。

上图描述了滤镜、滤镜的连接。

3.滤镜实现

目标:实现视频截取,截取视频左上半部分。

3.1 编写filter

编写filter的本质就是实现AVFilter结构体。

static const AVOption transform_options[] = {
    {"backUp", "a backup parameters, NOT use so far", OFFSET(backUp), AV_OPT_TYPE_STRING, {.str = "0"}, CHAR_MIN, CHAR_MAX, FLAGS},
    // {"w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = TFLAGS},
    // {"width", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = TFLAGS},
    {NULL}
 
 
}; // TODO: add something if needed
 
static const AVClass transform_class = {
    .class_name = "transform",
    .item_name = av_default_item_name,
    .option = transform_options,
    .version = LIBAVUTIL_VERSION_INT,
    .category = AV_CLASS_CATEGORY_FILTER,
};
 
static const AVFilterPad avfilter_vf_transform_inputs[] = {
    {
        .name = "transform_inputpad",
        .type = AVMEDIA_TYPE_VIDEO,
        .filter_frame = filter_frame,
    },
    {NULL}};
 
static const AVFilterPad avfilter_vf_transform_outputs[] = {
    {
        .name = "transform_outputpad",
        .type = AVMEDIA_TYPE_VIDEO,
        .config_props = config_output,
    },
    {NULL}};
 
 
AVFilter ff_vf_armcc = {
    .name = "armcc",                  // 对外宣称的filter的名字,命令行中-vf armcc
    .description = NULL_IF_CONFIG_SMALL("ar mcc filter test"),   // 滤镜描述
    .priv_size = sizeof(TransformContext),   // 这个是私有的filter上下文,可以把各种需要的本地全局变量放在这
    .priv_class = &transform_class, // 配置滤镜输入的参数,比如scale滤镜,需要获取用户输入的视频宽高,可以在这里配置
    .init = init,  // 滤镜初始化函数,变量的初始化可以放在这里
    .uninit = uninit,  // 最后执行的函数,资源的释放可以放在这里
    .query_formats = query_formats, // 这个函数比较重要,指定了此滤镜支持的输入frame的数据格式
    .inputs = avfilter_vf_transform_inputs, // 声明函数指针filter_frame,会指向本代码的filter_frame函数,在这个函数里要对输入的数据进行处理
    .outputs = avfilter_vf_transform_outputs, // 声明函数指针config_ouput,用于配置输出frame的大小
};

这些函数,在滤镜运行时的执行顺序为:

  1. init
  2. query_formats
  3. config_output
  4. filter_frame,这个函数是循环调用的,每次调用输入一帧数据
  5. uninit

3.2 设置滤镜支持的数据格式

static int query_formats(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "query_formats------------------------\n");
    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);
}

本滤镜只支持yuv420p格式数据。

3.3 初始化&资源释放

// 1.第一步执行
static av_cold int init(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "init===========================\n");
    TransformContext *privCtx = ctx->priv;
    // 初始化变量/指针等
    return 0;
}
 
 
static av_cold void uninit(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "uninit------------------------\n");
    TransformContext *privCtx = ctx->priv;
    // 释放资源
}

3.4 设置输出

static av_cold int config_output(AVFilterLink *outlink)
{
    AVFilterContext *ctx = outlink->src;
    TransformContext *privCtx = ctx->priv;
    av_log(NULL, AV_LOG_WARNING, "enter config_output");
 
    // 修改输出视频的宽高
    outlink->w = ctx->inputs[0]->w / 2;
    outlink->h = ctx->inputs[0]->h / 2;
    av_log(NULL, AV_LOG_WARNING, "configure output, w h = (%d %d), format %d \n", outlink->w, outlink->h, outlink->format);
 
 
    return 0;
}

3.5 视频剪切

static int filter_frame(AVFilterLink *link, AVFrame *in)
{
    av_log(NULL, AV_LOG_WARNING, "### chenxf filter_frame, link %x, frame %x \n", link, in);
    AVFilterContext *avctx = link->dst;
    AVFilterLink *outlink = avctx->outputs[0];
    AVFrame *out;
 
 
    //allocate a new buffer, data is null
    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
    if (!out)
    {
        av_frame_free(&in);
        return AVERROR(ENOMEM);
    }
 
    //the new output frame, property is the same as input frame, only width/height is different
    av_frame_copy_props(out, in);
    out->width = outlink->w;
    out->height = outlink->h;
    av_log(NULL, AV_LOG_WARNING, "out->height: %d,out->width: %d \n", out->height, out->width);
    av_log(NULL, AV_LOG_WARNING, " avctx->graph->nb_threads: %d\n", avctx->graph->nb_threads);
    ThreadData td;
    td.in = in;
    td.out = out;
    int res;
    if (res = avctx->internal->execute(avctx, do_conversion, &td, NULL, FFMIN(outlink->h, avctx->graph->nb_threads)))
    {
        return res;
    }
 
    av_frame_free(&in);
 
    // 用于两个filter之间包的传递,该函数将包放入输出filter_link
    return ff_filter_frame(outlink, out);
}

这个函数是整个滤镜的关键函数,视频的变换就在这个函数里完成,实现了截取视频左上半部分的功能,同时将frame传递给下一个滤镜。

4. 完整代码

#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
#include "libavutil/avassert.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"
#include "libswscale/swscale.h"
 
 
typedef struct TransformContext
{
    const AVClass *class;
    int backUp;
    //add some private data if you want
    struct SwsContext *sws_ctx;
    char *w_expr;
    char *h_expr;
} TransformContext;
 
typedef struct ThreadData
{
    AVFrame *in, *out;
} ThreadData;
 
static void image_copy_plane(uint8_t *dst, int dst_linesize,
                             const uint8_t *src, int src_linesize,
                             int bytewidth, int height)
{
    if (!dst || !src)
        return;
    av_assert0(abs(src_linesize) >= bytewidth);
    av_assert0(abs(dst_linesize) >= bytewidth);
    for (; height > 0; height--)
    {
        memcpy(dst, src, bytewidth);
        dst += dst_linesize;
        src += src_linesize;
    }
}
 
//for YUV data, frame->data[0] save Y, frame->data[1] save U, frame->data[2] save V
static int frame_copy_video(AVFrame *dst, const AVFrame *src)
{
    int i, planes;
 
    if (dst->width > src->width ||
        dst->height > src->height)
        return AVERROR(EINVAL);
 
    planes = av_pix_fmt_count_planes(dst->format);
    //make sure data is valid
    for (i = 0; i < planes; i++)
        if (!dst->data[i] || !src->data[i])
            return AVERROR(EINVAL);
    // yuv420p描述信息
    // name            : yuv420p
    // nb_components   : 3
    // log2_chroma_w   : 1
    // log2_chroma_h   : 1
    // flags           : 16
    // alias           : (null)
    // comp[0]         :
    // plane : 0, step : 1, offset : 0, shift : 0, depth : 8
    // comp[1]         :
    // plane : 1, step : 1, offset : 0, shift : 0, depth : 8
    // comp[2]         :
    // plane : 2, step : 1, offset : 0, shift : 0, depth : 8
 
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(dst->format);
    int planes_nb = 0;
    for (i = 0; i < desc->nb_components; i++)
        planes_nb = FFMAX(planes_nb, desc->comp[i].plane + 1);
 
    for (i = 0; i < planes_nb; i++)
    {
        int h = dst->height;
        int bwidth = av_image_get_linesize(dst->format, dst->width, i);
        if (bwidth < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "av_image_get_linesize failed\n");
            return;
        }
        if (i == 1 || i == 2)
        {
            h = AV_CEIL_RSHIFT(dst->height, desc->log2_chroma_h);
        }
        image_copy_plane(dst->data[i], dst->linesize[i],
                         src->data[i], src->linesize[i],
                         bwidth, h);
    }
    return 0;
}
 
/**************************************************************************
* you can modify this function, do what you want here. use src frame, and blend to dst frame.
* for this demo, we just copy some part of src frame to dst frame(out_w = in_w/2, out_h = in_h/2)
***************************************************************************/
static int do_conversion(AVFilterContext *ctx, void *arg, int jobnr,
                         int nb_jobs)
{
    TransformContext *privCtx = ctx->priv;
    ThreadData *td = arg;
    AVFrame *dst = td->out;
    AVFrame *src = td->in;
 
    frame_copy_video(dst, src);
    return 0;
}
 
static int filter_frame(AVFilterLink *link, AVFrame *in)
{
    av_log(NULL, AV_LOG_WARNING, "### chenxf filter_frame, link %x, frame %x \n", link, in);
    AVFilterContext *avctx = link->dst;
    AVFilterLink *outlink = avctx->outputs[0];
    AVFrame *out;
 
    //allocate a new buffer, data is null
    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
    if (!out)
    {
        av_frame_free(&in);
        return AVERROR(ENOMEM);
    }
 
    //the new output frame, property is the same as input frame, only width/height is different
    av_frame_copy_props(out, in);
    out->width = outlink->w;
    out->height = outlink->h;
    av_log(NULL, AV_LOG_WARNING, "out->height: %d,out->width: %d \n", out->height, out->width);
    av_log(NULL, AV_LOG_WARNING, " avctx->graph->nb_threads: %d\n", avctx->graph->nb_threads);
    ThreadData td;
    td.in = in;
    td.out = out;
    int res;
    if (res = avctx->internal->execute(avctx, do_conversion, &td, NULL, FFMIN(outlink->h, avctx->graph->nb_threads)))
    {
        return res;
    }
 
    av_frame_free(&in);
 
    // 用于两个filter之间包的传递,该函数将包放入输出filter_link
    return ff_filter_frame(outlink, out);
}
 
static av_cold int config_output(AVFilterLink *outlink)
{
    AVFilterContext *ctx = outlink->src;
    TransformContext *privCtx = ctx->priv;
    av_log(NULL, AV_LOG_WARNING, "enter config_output");
    //you can modify output width/height here
    outlink->w = ctx->inputs[0]->w / 2;
    outlink->h = ctx->inputs[0]->h / 2;
    av_log(NULL, AV_LOG_WARNING, "configure output, w h = (%d %d), format %d \n", outlink->w, outlink->h, outlink->format);
 
    return 0;
}
 
// 1.第一步执行
static av_cold int init(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "init===========================\n");
    TransformContext *privCtx = ctx->priv;
    //init something here if you want
    return 0;
}
 
static av_cold void uninit(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "uninit------------------------\n");
    TransformContext *privCtx = ctx->priv;
    //uninit something here if you want
}
 
//currently we just support the most common YUV420, can add more if needed
static int query_formats(AVFilterContext *ctx)
{
    av_log(NULL, AV_LOG_WARNING, "query_formats------------------------\n");
    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);
}
 
//*************
#define OFFSET(x) offsetof(TransformContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
 
static const AVOption transform_options[] = {
    {"backUp", "a backup parameters, NOT use so far", OFFSET(backUp), AV_OPT_TYPE_STRING, {.str = "0"}, CHAR_MIN, CHAR_MAX, FLAGS},
    // {"w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = TFLAGS},
    // {"width", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, .flags = TFLAGS},
    {NULL}
 
}; // TODO: add something if needed
 
static const AVClass transform_class = {
    .class_name = "transform",
    .item_name = av_default_item_name,
    .option = transform_options,
    .version = LIBAVUTIL_VERSION_INT,
    .category = AV_CLASS_CATEGORY_FILTER,
};
 
static const AVFilterPad avfilter_vf_transform_inputs[] = {
    {
        .name = "transform_inputpad",
        .type = AVMEDIA_TYPE_VIDEO,
        .filter_frame = filter_frame,
    },
    {NULL}};
 
static const AVFilterPad avfilter_vf_transform_outputs[] = {
    {
        .name = "transform_outputpad",
        .type = AVMEDIA_TYPE_VIDEO,
        .config_props = config_output,
    },
    {NULL}};
 
AVFilter ff_vf_lpscale = {
    .name = "lp_scale",
    .description = NULL_IF_CONFIG_SMALL("lp scale filter test"),
    .priv_size = sizeof(TransformContext),
    .priv_class = &transform_class,
    .init = init,
    .uninit = uninit,
    .query_formats = query_formats,
    .inputs = avfilter_vf_transform_inputs,
    .outputs = avfilter_vf_transform_outputs,
};

5. 编译

重新编译ffmpeg,通过命令行ffmpeg -filters | grep lpscale命令可以查询到新实现的滤镜。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页