FFmpeg的入门实践系列七(滤镜)

在这里插入图片描述

欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力

前期博客

FFmpeg的入门实践系列一(环境搭建)
FFmpeg的入门实践系列二(基础知识)
FFmpeg的入门实践系列三(基础知识)
FFmpeg的入门实践系列四(AVS)
FFmpeg的入门实践系列五(编程入门之属性查看)
FFmpeg的入门实践系列六(编程入门之常见处理流程)

一、滤镜

上期讲了FFmpeg编程的主要流程,大体的框架已经说得七七八八了,现在剩下的是往深往细的地方去讲。今天,主讲滤镜。
FFmpeg是一套可以用来记录、转换数字音视频,并进行流媒体播放和推流的多媒体框架。其中的过滤器(Filters)是其功能强大的一个组成部分,它们用于在解码和编码阶段之间处理音视频数据。

过滤器filter的简介

(1)过滤器的作用是在音视频数据流经FFmpeg时对其进行处理。这些处理可以包括但不限于:

  • 视频分辨率转换
  • 音频混音
  • 视频特效(如模糊、锐化)
  • 图像格式转换
  • 时间基线校正

(2)过滤器类型
FFmpeg内置了多种类型的过滤器,大致可以分为以下几类:

  1. 视频过滤器:如crop(裁剪)、scale(缩放)、vflip(垂直翻转)、hue(色相调整)等。
  2. 音频过滤器:如volume(音量调整)、equalizer(均衡器)、aresample(重采样)等。
  3. 图形和字幕过滤器:如drawtext(绘制文本)、overlay(图像叠加)等。
  4. 格式转换过滤器:如format,用于转换像素或采样格式。

(3)使用方法
FFmpeg过滤器的使用通常分为以下三个步骤:

  1. FilterGraph绘制:创建一个过滤器图,定义数据流如何通过各个过滤器。这通常是通过命令行参数或者使用FFmpeg的库函数在程序中完成的。
  2. 数据输入:将原始音视频数据输入到过滤器图中。
  3. 输出数据:处理过的数据从过滤器图输出,然后可以进一步编码和保存。

(4)命令行使用
在命令行中,过滤器可以通过-filter参数使用,例如:

ffmpeg -i input.mp4 -filter:v "crop=640:480:0:0" -filter:a "volume=0.5" output.mp4

在这个例子中,-filter:v指定视频过滤器,crop用于裁剪视频,-filter:a指定音频过滤器,volume用于调整音量。
(5)过滤器链
过滤器可以组合成链,在命令行中用逗号分隔,或者使用:分隔不同的过滤器参数。例如:

ffmpeg -i input.mp4 -filter_complex "[0:v]crop=640:480:0:0, scale=320:240[outv]" -map "[outv]" output.mp4

在这个例子中,filter_complex用于定义复杂的过滤器图,其中包含多个视频处理步骤。

FFmpeg编程的四大结构体

四大结构体分别是:AVFilter、AVFilterGraph、AVFilterContext、AVFilterInOut。下面开始逐一说明。

1.滤镜AVFilter

AVFilter 是 FFmpeg 框架中的一个结构体,定义了滤镜的规格。调用avfilter_get_by_name函数会获得指定名称的输入滤镜或者输出滤镜。
AVFilter 结构体包含了滤镜的基本信息和操作函数,下面是该结构体的一些关键字段的说明:
AVFilter 结构体字段

  1. const char *name
    • 描述:滤镜的名字。这是滤镜的唯一标识符。
    • 作用:用户通过名字来引用该滤镜。
  2. const char *description
    • 描述:滤镜的描述信息。
    • 作用:简要描述滤镜的功能。
  3. const AVFilterPad *inputs
    • 描述:定义滤镜的输入端口。
    • 作用:指定该滤镜可以接受的输入格式、类型等。AVFilterPad 是另一个结构体,描述了每个输入端口的属性。
  4. const AVFilterPad *outputs
    • 描述:定义滤镜的输出端口。
    • 作用:指定该滤镜的输出格式、类型等。
  5. int priv_size
    • 描述:滤镜私有数据的大小(以字节为单位)。
    • 作用:FFmpeg 框架会为滤镜分配这个大小的内存用于存储滤镜的私有数据。
  6. int (*init)(AVFilterContext *ctx)
    • 描述:初始化滤镜的回调函数。
    • 作用:在滤镜实例化时调用,进行必要的初始化操作。
  7. void (*uninit)(AVFilterContext *ctx)
    • 描述:反初始化(或清理)滤镜的回调函数。
    • 作用:在滤镜被销毁之前调用,用于释放资源。
  8. int (*query_formats)(AVFilterContext *)
    • 描述:查询支持的输入/输出格式的回调函数。
    • 作用:用于设置滤镜支持的格式列表。
  9. int flags
    • 描述:滤镜的标志位。
    • 作用:可以包含多个标志,如指示滤镜是动态还是静态的、是否支持多线程等。
  10. const AVClass *priv_class
    • 描述:定义私有选项的类。
    • 作用:用于在滤镜中定义私有选项(参数),这些选项可以通过 API 或命令行来配置。
  11. int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags)
    • 描述:处理命令的回调函数。
    • 作用:允许动态地向滤镜发送命令或修改其行为。
  12. void (*activate)(AVFilterContext *ctx)
    • 描述:激活滤镜的回调函数。
    • 作用:在滤镜框架内,激活滤镜处理数据流。
2.滤镜图AVFilterGraph

滤镜图执行具体的过滤操作,包括检查有效性、加工处理等。该结构的常用函数说明如下。

  • avfilter_graph_alloc:分配一个滤镜图。
  • avfilter_graph_parse_ptr:把通过字符串描述的图形添加到滤镜图,这里的字符串描述了一系列的加工操作及其详细的处理规格。
  • avfilter_graph_config:检查过滤字符串的有效性,并配置滤镜图中的所有前后连接和图像格式。
  • avfilter_graph_free:释放滤镜图的资源。
3.滤镜实例AVFilterContext

AVFilterContext指的是滤镜实例,通过滤镜实例与数据帧交互,先把原始数据送给输入滤镜的实例,再从输出滤镜的实例中获取加工后的数据帧。该结构的常用函数说明如下:

  • avfilter_graph_create_filter:根据滤镜定义以及音视频规格创建滤镜实例,并将其增加到现有的滤镜图中。
  • av_buffersrc_add_frame_flags:把一个数据帧增加到输入滤镜的实例中。
  • av_buffersink_get_frame:从输出滤镜的实例获取加工后的数据帧。
  • avfilter_free:释放滤镜的实例。
    注意:要先调用avfilter_graph_free,再调用avfilter_free,否则会报错。
4.滤镜输入输出参数AVFilterInOut

调用avfilter_inout_alloc函数会初始化滤镜的输入或者输出参数,调用avfilter_inout_free函数会释放滤镜的输入或者输出参数。
AVFilterInOut的主要字段说明如下:

  • name:参数名称。对于单路音视频来说,填in表示输入,填out表示输出。
  • filter_ctx:滤镜实例。对于输入参数来说,填输入滤镜的实例;对于输出参数来说,填输出滤镜的实例。
  • pad_idx:填0即可。
  • next:下一路音视频的输入输出参数。如果不存在,则填NULL。

二、使用滤镜加工音视频

步骤:

  • 01 根据源文件的数据流、解码器实例、过滤字符串来初始化滤镜,得到输入滤镜的实例和输出滤镜的实例。
  • 02 调用av_buffersrc_add_frame_flags函数把一个数据帧添加到输入滤镜的实例。
  • 03 调用av_buffersink_get_frame函数从输出滤镜获取加工后的数据帧。
  • 04 把加工后的数据帧压缩编码后保存到目标文件中。
  • 05 重复前面的第2~4步,直到源文件的所有数据帧都处理完毕。

由于滤镜初始化操作涉及的内容也很多,本章先介绍滤镜的初始化操作:

  • 01 声明滤镜的各种实例资源,除输入滤镜的实例、输出滤镜的实例、滤镜图外,还要调用avfilter_get_by_name函数分别获取输入和输出滤镜,调用avfilter_inout_alloc函数各自分配输入输出滤镜参数,调用avfilter_graph_alloc函数分配一个滤镜图。
  • 02 拼接输入源的媒体参数信息字符串,以视频为例,参数字符串需要包括视频宽高、像素格式、时间基等。
  • 03 调用avfilter_graph_create_filter函数,根据输入滤镜和第二步的参数字符串,创建输入滤镜的实例,并将其添加到现有的滤镜图中。
  • 04 调用avfilter_graph_create_filter函数,根据输出滤镜创建输出滤镜的实例,并将其增加到现有的滤镜图中。
  • 05 调用av_opt_set_int_list函数设置额外的选项参数,比如加工视频要给输出滤镜的实例设置像素格式。
  • 06 设置输入输出参数,给AVFilterInOut结构的filter_ctx字段填写输入滤镜的实例或者输出滤镜的实例。
  • 07 调用avfilter_graph_parse_ptr函数,把采用过滤字符串描述的图形添加到滤镜图中,这个过滤字符串指定了滤镜的种类名称及其参数取值。
  • 08 调用avfilter_graph_config函数检查过滤字符串的有效性,并配置滤镜图中的所有前后连接和图像格式。
  • 09 调用avfilter_inout_free函数分别释放滤镜的输入参数和输出参数。

实例代码如下:

#include <stdio.h>

// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#ifdef __cplusplus
};
#endif

AVFilterContext *buffersrc_ctx = NULL; // 输入滤镜的实例
AVFilterContext *buffersink_ctx = NULL; // 输出滤镜的实例
AVFilterGraph *filter_graph = NULL; // 滤镜图

// 初始化滤镜(也称过滤器、滤波器)。第一个参数是视频流,第二个参数是解码器实例,第三个参数是过滤字符串
int init_filter(AVStream *video_stream, AVCodecContext *video_decode_ctx, const char *filters_desc) {
    int ret = 0;
    const AVFilter *buffersrc = avfilter_get_by_name("buffer"); // 获取输入滤镜
    const AVFilter *buffersink = avfilter_get_by_name("buffersink"); // 获取输出滤镜
    AVFilterInOut *inputs = avfilter_inout_alloc(); // 分配滤镜的输入输出参数
    AVFilterInOut *outputs = avfilter_inout_alloc(); // 分配滤镜的输入输出参数
    AVRational time_base = video_stream->time_base;
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
    filter_graph = avfilter_graph_alloc(); // 分配一个滤镜图
    if (!outputs || !inputs || !filter_graph) {
        ret = AVERROR(ENOMEM);
        return ret;
    }
    char args[512]; // 临时字符串,存放输入源的媒体参数信息,比如视频的宽高、像素格式等
    snprintf(args, sizeof(args),
        "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
        video_decode_ctx->width, video_decode_ctx->height, video_decode_ctx->pix_fmt,
        time_base.num, time_base.den,
        video_decode_ctx->sample_aspect_ratio.num, video_decode_ctx->sample_aspect_ratio.den);
    av_log(NULL, AV_LOG_INFO, "args : %s\n", args);
    // 创建输入滤镜的实例,并将其添加到现有的滤镜图
    ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
        args, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
        return ret;
    }
    // 创建输出滤镜的实例,并将其添加到现有的滤镜图
    ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
        NULL, NULL, filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
        return ret;
    }
    // 将二进制选项设置为整数列表,此处给输出滤镜的实例设置像素格式
    ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
        AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
        return ret;
    }
    // 设置滤镜的输入输出参数
    outputs->name = av_strdup("in");
    outputs->filter_ctx = buffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = NULL;
    // 设置滤镜的输入输出参数
    inputs->name = av_strdup("out");
    inputs->filter_ctx = buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = NULL;
    // 把采用过滤字符串描述的图形添加到滤镜图
    ret = avfilter_graph_parse_ptr(filter_graph, filters_desc, &inputs, &outputs, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot parse graph string\n");
        return ret;
    }
    // 检查过滤字符串的有效性,并配置滤镜图中的所有前后连接和图像格式
    ret = avfilter_graph_config(filter_graph, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot config filter graph\n");
        return ret;
    }
    avfilter_inout_free(&inputs); // 释放滤镜的输入参数
    avfilter_inout_free(&outputs); // 释放滤镜的输出参数
    av_log(NULL, AV_LOG_INFO, "Success initialize filter.\n");
    return ret;
}

int main(int argc, char **argv) {
    const char *filename = "../fuzhou.mp4";
    if (argc > 1) {
        filename = argv[1];
    }
    AVFormatContext *fmt_ctx = NULL;
    // 打开音视频文件
    int ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Can't open file %s.\n", filename);
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "Success open input_file %s.\n", filename);
    // 查找音视频文件中的流信息
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Can't find stream information.\n");
        return -1;
    }
    av_log(NULL, AV_LOG_INFO, "Success find stream information.\n");
    
    // 找到视频流的索引
    int video_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    av_log(NULL, AV_LOG_INFO, "video_index=%d\n", video_index);
    if (video_index >= 0) {
        AVStream *video_stream = fmt_ctx->streams[video_index];
        enum AVCodecID video_codec_id = video_stream->codecpar->codec_id;
        av_log(NULL, AV_LOG_INFO, "video_stream codec_id=%d\n", video_codec_id);
        // 查找视频解码器
        AVCodec *video_codec = (AVCodec*) avcodec_find_decoder(video_codec_id);
        if (!video_codec) {
            av_log(NULL, AV_LOG_INFO, "video_codec not found\n");
            return -1;
        }
        av_log(NULL, AV_LOG_INFO, "video_codec name=%s\n", video_codec->name);
        av_log(NULL, AV_LOG_INFO, "video_codec long_name=%s\n", video_codec->long_name);
        // 下面的type字段来自AVMediaType定义,为0表示AVMEDIA_TYPE_VIDEO,为1表示AVMEDIA_TYPE_AUDIO
        av_log(NULL, AV_LOG_INFO, "video_codec type=%d\n", video_codec->type);
        
        AVCodecContext *video_decode_ctx = NULL; // 视频解码器的实例
        video_decode_ctx = avcodec_alloc_context3(video_codec); // 分配解码器的实例
        if (!video_decode_ctx) {
            av_log(NULL, AV_LOG_INFO, "video_decode_ctx is null\n");
            return -1;
        }
        // 把视频流中的编解码参数复制给解码器的实例
        avcodec_parameters_to_context(video_decode_ctx, video_stream->codecpar);
        av_log(NULL, AV_LOG_INFO, "Success copy video parameters_to_context.\n");
        ret = avcodec_open2(video_decode_ctx, video_codec, NULL); // 打开解码器的实例
        av_log(NULL, AV_LOG_INFO, "Success open video codec.\n");
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Can't open video_decode_ctx.\n");
            return -1;
        }
        // 初始化滤镜
        init_filter(video_stream, video_decode_ctx, "fps=25");
        avcodec_close(video_decode_ctx); // 关闭解码器的实例
        avcodec_free_context(&video_decode_ctx); // 释放解码器的实例
        avfilter_free(buffersrc_ctx); // 释放输入滤镜的实例
        avfilter_free(buffersink_ctx); // 释放输出滤镜的实例
        avfilter_graph_free(&filter_graph); // 释放滤镜图资源
    } else {
        av_log(NULL, AV_LOG_ERROR, "Can't find video stream.\n");
        return -1;
    }
    
    avformat_close_input(&fmt_ctx); // 关闭音视频文件
    return 0;
}

至此,结束~
在这里插入图片描述
望诸位不忘三连支持一下~

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值