欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力
文章目录
前期博客
FFmpeg的入门实践系列一(环境搭建)
FFmpeg的入门实践系列二(基础知识)
FFmpeg的入门实践系列三(基础知识)
FFmpeg的入门实践系列四(AVS)
FFmpeg的入门实践系列五(编程入门之属性查看)
FFmpeg的入门实践系列六(编程入门之常见处理流程)
一、滤镜
上期讲了FFmpeg编程的主要流程,大体的框架已经说得七七八八了,现在剩下的是往深往细的地方去讲。今天,主讲滤镜。
FFmpeg是一套可以用来记录、转换数字音视频,并进行流媒体播放和推流的多媒体框架。其中的过滤器(Filters)是其功能强大的一个组成部分,它们用于在解码和编码阶段之间处理音视频数据。
过滤器filter的简介
(1)过滤器的作用是在音视频数据流经FFmpeg时对其进行处理。这些处理可以包括但不限于:
- 视频分辨率转换
- 音频混音
- 视频特效(如模糊、锐化)
- 图像格式转换
- 时间基线校正
(2)过滤器类型
FFmpeg内置了多种类型的过滤器,大致可以分为以下几类:
- 视频过滤器:如
crop
(裁剪)、scale
(缩放)、vflip
(垂直翻转)、hue
(色相调整)等。 - 音频过滤器:如
volume
(音量调整)、equalizer
(均衡器)、aresample
(重采样)等。 - 图形和字幕过滤器:如
drawtext
(绘制文本)、overlay
(图像叠加)等。 - 格式转换过滤器:如
format
,用于转换像素或采样格式。
(3)使用方法
FFmpeg过滤器的使用通常分为以下三个步骤:
- FilterGraph绘制:创建一个过滤器图,定义数据流如何通过各个过滤器。这通常是通过命令行参数或者使用FFmpeg的库函数在程序中完成的。
- 数据输入:将原始音视频数据输入到过滤器图中。
- 输出数据:处理过的数据从过滤器图输出,然后可以进一步编码和保存。
(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
结构体字段
const char *name
- 描述:滤镜的名字。这是滤镜的唯一标识符。
- 作用:用户通过名字来引用该滤镜。
const char *description
- 描述:滤镜的描述信息。
- 作用:简要描述滤镜的功能。
const AVFilterPad *inputs
- 描述:定义滤镜的输入端口。
- 作用:指定该滤镜可以接受的输入格式、类型等。
AVFilterPad
是另一个结构体,描述了每个输入端口的属性。
const AVFilterPad *outputs
- 描述:定义滤镜的输出端口。
- 作用:指定该滤镜的输出格式、类型等。
int priv_size
- 描述:滤镜私有数据的大小(以字节为单位)。
- 作用:FFmpeg 框架会为滤镜分配这个大小的内存用于存储滤镜的私有数据。
int (*init)(AVFilterContext *ctx)
- 描述:初始化滤镜的回调函数。
- 作用:在滤镜实例化时调用,进行必要的初始化操作。
void (*uninit)(AVFilterContext *ctx)
- 描述:反初始化(或清理)滤镜的回调函数。
- 作用:在滤镜被销毁之前调用,用于释放资源。
int (*query_formats)(AVFilterContext *)
- 描述:查询支持的输入/输出格式的回调函数。
- 作用:用于设置滤镜支持的格式列表。
int flags
- 描述:滤镜的标志位。
- 作用:可以包含多个标志,如指示滤镜是动态还是静态的、是否支持多线程等。
const AVClass *priv_class
- 描述:定义私有选项的类。
- 作用:用于在滤镜中定义私有选项(参数),这些选项可以通过 API 或命令行来配置。
int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags)
- 描述:处理命令的回调函数。
- 作用:允许动态地向滤镜发送命令或修改其行为。
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;
}
至此,结束~
望诸位不忘三连支持一下~