欢迎诸位来阅读在下的博文~
在这里,在下会不定期发表一些浅薄的知识和经验,望诸位能与在下多多交流,共同努力
FFmpeg的入门实践系列一(环境搭建)
FFmpeg的入门实践系列二(基础知识)
FFmpeg的入门实践系列三(基础知识)
FFmpeg的入门实践系列四(AVS)
FFmpeg的入门实践系列五(编程入门之属性查看)
FFmpeg的入门实践系列六(编程入门之常见处理流程)
上期讲了FFmpeg编程的主要流程,大体的框架已经说得七七八八了,现在剩下的是往深往细的地方去讲。今天,主讲滤镜。
FFmpeg是一套可以用来记录、转换数字音视频,并进行流媒体播放和推流的多媒体框架。其中的过滤器(Filters)是其功能强大的一个组成部分,它们用于在解码和编码阶段之间处理音视频数据。
(1)过滤器的作用是在音视频数据流经FFmpeg时对其进行处理。这些处理可以包括但不限于:
(2)过滤器类型
FFmpeg内置了多种类型的过滤器,大致可以分为以下几类:
crop
(裁剪)、scale
(缩放)、vflip
(垂直翻转)、hue
(色相调整)等。volume
(音量调整)、equalizer
(均衡器)、aresample
(重采样)等。drawtext
(绘制文本)、overlay
(图像叠加)等。format
,用于转换像素或采样格式。(3)使用方法
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
用于定义复杂的过滤器图,其中包含多个视频处理步骤。
四大结构体分别是:AVFilter、AVFilterGraph、AVFilterContext、AVFilterInOut。下面开始逐一说明。
AVFilter
是 FFmpeg 框架中的一个结构体,定义了滤镜的规格。调用avfilter_get_by_name函数会获得指定名称的输入滤镜或者输出滤镜。
AVFilter
结构体包含了滤镜的基本信息和操作函数,下面是该结构体的一些关键字段的说明:
AVFilter
结构体字段
const char *name
const char *description
const AVFilterPad *inputs
AVFilterPad
是另一个结构体,描述了每个输入端口的属性。const AVFilterPad *outputs
int priv_size
int (*init)(AVFilterContext *ctx)
void (*uninit)(AVFilterContext *ctx)
int (*query_formats)(AVFilterContext *)
int flags
const AVClass *priv_class
int (*process_command)(AVFilterContext *, const char *cmd, const char *arg, char *res, int res_len, int flags)
void (*activate)(AVFilterContext *ctx)
滤镜图执行具体的过滤操作,包括检查有效性、加工处理等。该结构的常用函数说明如下。
AVFilterContext指的是滤镜实例,通过滤镜实例与数据帧交互,先把原始数据送给输入滤镜的实例,再从输出滤镜的实例中获取加工后的数据帧。该结构的常用函数说明如下:
调用avfilter_inout_alloc函数会初始化滤镜的输入或者输出参数,调用avfilter_inout_free函数会释放滤镜的输入或者输出参数。
AVFilterInOut的主要字段说明如下:
步骤:
由于滤镜初始化操作涉及的内容也很多,本章先介绍滤镜的初始化操作:
实例代码如下:
#include
// 之所以增加__cplusplus的宏定义,是为了同时兼容gcc编译器和g++编译器
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#include
#include
#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;
}