如何使用libavfilter库给输入文件input.yuv添加视频滤镜?
一.视频滤镜初始化
本次代码实现的是给输入视频文件添加水平翻转滤镜,在视频滤镜初始化部分我们可以分为以下几步进行:
1.创建滤镜图结构
视频滤镜功能最核心的结构为滤镜图结构,即AVFilterGraph结构,我们调用avfilter_graph_alloc()函数就可以创建一个滤镜图结构。
2.创建滤镜实例结构
仅创建一个空的滤镜图显然是无法完成任何工作的,因此必须根据需求向滤镜图中添加相应的滤镜实例。这里,我们添加buffer滤镜和buffersink滤镜作为视频滤镜的输入和输出。滤镜由AVFilter结构实现,调用avfilter_get_by_name()函数即可获得相应的滤镜。在获取了这两个滤镜后,接下来,需要创建对应的滤镜实例,滤镜实例由AVFilterContext结构实现,通过调用avfilter_graph_create_filter()函数就能将滤镜实例添加到创建好的滤镜图中。
3.创建和配置滤镜接口
对于创建好的滤镜,需要将相应的接口连接后方可正常工作,滤镜接口类型定义为AVFilterInOut结构,其本质是一个链表的节点,创建输入输出接口可以调用avfilter_inout_alloc()函数,创建好之后,将滤镜对象和接口绑定即可。
4.根据滤镜描述解析并配置滤镜图
在完成滤镜图,相关滤镜和接口结构的创建后,接下来需要根据字符串类型的滤镜描述信息对整体的滤镜图进行解析和配置,这一步需要先后调用avfilter_graph_parse_ptr()和avfilter_graph_config()函数。
完整的初始化代码如下:
//video_filter_core.cpp
#define STREAM_FRAME_RATE 25
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
AVFrame *input_frame= nullptr,*output_frame= nullptr; static int32_t init_frames(int32_t width,int32_t height,enum AVPixelFormat pix_fmt){
int result=0;
input_frame=av_frame_alloc();
output_frame=av_frame_alloc();
if(!input_frame||!output_frame){
cerr<<"Error:frame allocation failed."<<endl;
return -1;
}
input_frame->width=width;
input_frame->height=height;
input_frame->format=pix_fmt;
result= av_frame_get_buffer(input_frame,0);
if(result<0){
cerr<<"Error:av_frame_get_buffer failed."<<endl;
return -1;
}
result= av_frame_make_writable(input_frame);
if(result<0){
cerr<<"Error:av_frame_make_writable failed."<<endl;
return -1;
}
return 0;
} int32_t init_video_filter(int32_t width,int32_t height,const char *filter_descr){
int32_t result=0;
char args[512]={0};
const AVFilter *buffersrc= avfilter_get_by_name("buffer");
const AVFilter *buffersink= avfilter_get_by_name("buffersink");
AVFilterInOut *outputs=avfilter_inout_alloc();
AVFilterInOut *inputs=avfilter_inout_alloc();
enum AVPixelFormat pix_fmts[]={AV_PIX_FMT_YUV420P,AV_PIX_FMT_NONE};
do{
filter_graph=avfilter_graph_alloc();
if(!outputs||!inputs||!filter_graph){
cerr<<"Error:creating filter graph failed."<<endl;
result=AVERROR(ENOMEM);
break;
}
snprintf(args,sizeof(args),"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",width,height,AV_PIX_FMT_YUV420P,1,STREAM_FRAME_RATE,1,1);
result= avfilter_graph_create_filter(&buffersrc_ctx,buffersrc,"in",args, nullptr,filter_graph);
if(result<0){
cerr<<"Error:could not create source filter."<<endl;
break;
}
result= avfilter_graph_create_filter(&buffersink_ctx,buffersink,"out", nullptr,nullptr,filter_graph);
if(result<0){
cerr<<"Error:could not create sink filter."<<endl;
break;
}
result= av_opt_set_int_list(buffersink_ctx,"pix_fmts",pix_fmts,AV_PIX_FMT_NONE,AV_OPT_SEARCH_CHILDREN);
if(result<0){
cerr<<"Error:could not set output pixel format."<<endl;
break;
}
outputs->name=av_strdup("in");
outputs->filter_ctx=buffersrc_ctx;
outputs->pad_idx=0;
outputs->next= nullptr;
inputs->name=av_strdup("out");
inputs->filter_ctx=buffersink_ctx;
inputs->pad_idx=0;
inputs->next= nullptr;
//根据滤镜描述解析并配置滤镜图
if((result= avfilter_graph_parse_ptr(filter_graph,filter_descr,&inputs,&outputs, nullptr))<0){
cerr<<"Error:avfilter_graph_parse_ptr failed."<<endl;
break;
}
//在解析滤镜描述后,需要验证滤镜图整体配置的有效性
if((result=avfilter_graph_config(filter_graph, nullptr))<0){
cerr<<"Error:Graph config invalid."<<endl;
break;
}
result= init_frames(width,height,AV_PIX_FMT_YUV420P);
if(result<0){
cerr<<"Error:init frames failed."<<endl;
break;
}
}while(0);
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return result;
}
static void free_frames(){
av_frame_free(&input_frame);
av_frame_free(&output_frame);
}
void destroy_video_filter(){
free_frames();
avfilter_graph_free(&filter_graph);
}
二.循环编辑视频帧
在这一步主要用到av_buffersrc_add_frame_flags()和av_buffersink_get_frame()这两个函数,它们的功能分别是将输入图像添加到滤镜图和从sink滤镜中获取编辑后的图像。代码如下:
//video_filter_core.cpp
static int32_t filter_frame(){
int32_t result=0;
if((result= av_buffersrc_add_frame_flags(buffersrc_ctx,input_frame,AV_BUFFERSRC_FLAG_KEEP_REF))<0){
cerr<<"Error:add frame to buffer src failed."<<endl;
return result;
}
while(true){
result= av_buffersink_get_frame(buffersink_ctx,output_frame);
if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){
return 1;
}
else if(result<0){
cerr<<"Error:buffersink_get_frame failed."<<endl;
return result;
}
cout<<"Frame filtered,width:"<<output_frame->width<<",height:"<<output_frame->height<<endl;
write_frame_to_yuv(output_frame);
av_frame_unref(output_frame);
}
return result;
} int32_t filtering_video(int32_t frame_cnt){
int32_t result=0;
for(size_t i=0;i<frame_cnt;i++){
result= read_yuv_to_frame(input_frame);
if(result<0){
cerr<<"Error:read_yuv_to_frame failed."<<endl;
return result;
}
result=filter_frame();
if(result<0){
cerr<<"Error:filter_frame failed."<<endl;
return result;
}
}
return result;
}
下面是数据读入和数据写出代码:
//io_data.cpp
static FILE* input_file= nullptr;
static FILE* output_file= nullptr; int32_t open_input_output_files(const char* input_name,const char* output_name){
if(strlen(input_name)==0||strlen(output_name)==0){
cout<<"Error:empty input or output file name."<<endl;
return -1;
}
close_input_output_files();
input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
if(input_file==nullptr){
cerr<<"Error:failed to open input file."<<endl;
return -1;
}
output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
if(output_file== nullptr){
cout<<"Error:failed to open output file."<<endl;
return -1;
}
return 0;
}
void close_input_output_files(){
if(input_file!= nullptr){
fclose(input_file);
input_file= nullptr;
}
if(output_file!= nullptr){
fclose(output_file);
output_file= nullptr;
}
}
int32_t read_yuv_to_frame(AVFrame* frame){
int32_t frame_width=frame->width;
int32_t frame_height=frame->height;
int32_t luma_stride=frame->linesize[0];
int32_t chroma_stride=frame->linesize[1];
int32_t frame_size=frame_width*frame_height*3/2;
int32_t read_size=0;
if(frame_width==luma_stride){
//如果width等于stride,则说明frame中不存在padding字节,可整体读取
read_size+=fread(frame->data[0],1,frame_width*frame_height,input_file);
read_size+=fread(frame->data[1],1,frame_width*frame_height/4,input_file);
read_size+=fread(frame->data[2],1,frame_width*frame_height/4,input_file);
}
else{
//如果width不等于stride,则说明frame中存在padding字节
//对三个分量应该逐行读取
for(size_t i=0;i<frame_height;i++){
read_size+=fread(frame->data[0]+i*luma_stride,1,frame_width,input_file);
}
for(size_t uv=1;uv<=2;uv++){
for(size_t i=0;i<frame_height/2;i++){
read_size+=fread(frame->data[uv]+i*chroma_stride,1,frame_width/2,input_file);
}
}
}
if(read_size!=frame_size){
cerr<<"Error:Read data error,frame_size:"<<frame_size<<",read_size:"<<read_size<<endl;
return -1;
}
return 0;
}
int32_t write_frame_to_yuv(AVFrame* frame){
uint8_t** pBuf=frame->data;
int* pStride=frame->linesize;
for(size_t i=0;i<3;i++){
int32_t width=(i==0?frame->width:frame->width/2);
int32_t height=(i==0?frame->height:frame->height/2);
for(size_t j=0;j<height;j++){
fwrite(pBuf[i],1,width,output_file);
pBuf[i]+= pStride[i];
}
}
return 0;
}
main函数实现:
int main(){
const char *input_file_name="../input.yuv";
int32_t pic_width=1920;
int32_t pic_height=1080;
int32_t total_frame_cnt=250;
const char *filter_descr="hflip";
const char *output_file_name="../output.yuv";
int32_t result= open_input_output_files(input_file_name,output_file_name);
if(result<0){
return result;
}
result= init_video_filter(pic_width,pic_height,filter_descr);
if(result<0){
return result;
}
result= filtering_video(total_frame_cnt);
if(result<0){
return result;
}
close_input_output_files();
destroy_video_filter();
return 0;
}
最后,可以以下指令测试输出的output.yuv文件:
ffplay -f rawvideo -video_size 1920x1080 -i output.yuv
如何使用libavfilter库给输入文件input.yuv添加视频滤镜?的更多相关文章
- 标准库 - 输入输出处理(input and output facilities) lua
标准库 - 输入输出处理(input and output facilities)责任编辑:cynthia作者:来自ITPUB论坛 2008-02-18 文本Tag: Lua [IT168 技术文档] ...
- luvcview,使用mplayer查看摄像头和luvcview保存YUV图像视频的播放(转)
luvcview,使用mplayer查看摄像头和luvcview保存YUV图像视频的播放 在mplayer中查看摄像头,可使用如下命令:mplayer tv:// -tv driver=v4l2:in ...
- chart.js图表库案例赏析,饼图添加文字
chart.js图表库案例赏析,饼图添加文字 Chart.js 是一个令人印象深刻的 JavaScript 图表库,建立在 HTML5 Canvas 基础上.目前,它支持6种图表类型(折线图,条形图, ...
- 给input元素添加float. 去除IE6 下input的空隙
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- input输入框添加内部图标
有可能在制作网页的过程中遇到各种美化表单设计,这次我们来试着做一个demo 将input输入框添加内部图标 话不多说,看一下最终效果 我们的思路是,在一个div中,加入一个div和一个input标签, ...
- jquery语言中获取input标签后如何给input标签添加disabled的属性
jquery语言中获取input标签后如何给input标签添加disabled的属性 1.使用JQuery函数prop():$("input").prop("disabl ...
- 表单input项使用label,同时引用Bootstrap库,导致input点击效果区增大
产品姐姐想法多,点击input项才能聚焦进行操作,点击外部不能有反应 好了...直入正题 为了让标签更加语义化,在表单项中,我们往往会使用label进行包裹 <label for="l ...
- 为input输入框添加圆角并去除阴影
<input type="text" name="bianhao" value="" placeholder="请输入商品编 ...
- Input checkbox 添加样式背景
<style type="text/css"> .chk_1 { width: 20px; height: 20px; position: absolute; top: ...
- SharePoint 2007 文档库中的文档添加评论功能
背景:接到一个项目,要求文档管理,当然文档库就可以了,但是要求文档需要大家去读,读完以后还可以发表评论,这Moss貌似就有点困难了.和同事一起合计,想来想去也没有太好的办法,后来想到传统开发,两个表的 ...
随机推荐
- [Java]大数运算之加法
一 题目 Input: String a = "15324564...1455445"; //数字过长,不能转换为int/long型运算 String b = "4564 ...
- 《Flask Web 开发指南 pt.2》
哈喽大家好,我是咸鱼 在<Flask Web 开发指南 pt.1>中,咸鱼跟大家介绍了 Flask 的由来--诞生于一个愚人节玩笑,简单介绍了一些关于 Flask 的概念,并且编写了一个简 ...
- 7.OAuth2
1.近几天在学习OAuth2协议,实际开发中用的最多的就是授权码模式 2.OAuth2的授权码模式流程:首先是用户去访问资源服务器,服务器会给用户一个授权码:用户根据授权码去访问认证服务器,服务器 ...
- 数据泵:导入导出dblink
环境介绍:12c->19c [oracle@enmoedu1 dpdump]$ expdp system/oracle directory=DATA_PUMP_DIR dumpfile=STAT ...
- Kurator v0.3.0版本发布
摘要:2023年4月8日,Kurator正式发布v0.3.0版本. 本文分享自华为云社区<华为云 Kurator v0.3.0 版本发布!集群舰队助力分布式云统一管理>,作者:云容器大未来 ...
- 官宣 | Hugging Face 中文博客正式发布!
作者:Tiezhen.Adina.Luke Hugging Face 的中国社区成立已经有五个月之久,我们也非常高兴的看到 Hugging Face 相关的中文内容在各个平台广受好评,我们也注意到,H ...
- chatgpt接口开发笔记1:completions接口
chatgpt接口开发笔记1:completions接口 个人博客地址: https://note.raokun.top 拥抱ChatGPT,国内访问网站:https://www.playchat.t ...
- Appweb交叉编译
Appweb交叉编译 编译环境:ubuntu-12.04 x64 开发平台:Hi3535 arm版 编译版本:appweb-6.1.1.zip 下载地址=> Appweb web site: h ...
- 关于vue3 上传图片到七牛云
引子:前端程序猿,很少写博客,担心有一些技术很牛逼的大佬看不上,还喜欢怼人,玻璃心容易影响心情,这个是我自己在项目上遇到的,也百度参考了很多大佬的文章,感觉多少有点不全,然后就自己整理一下,当一个笔记 ...
- 2021-08-06:天际线问题。城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回由这些建筑物形成的 天际线 。每个建筑物的几何信息由数组 build
2021-08-06:天际线问题.城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓.给你所有建筑物的位置和高度,请返回由这些建筑物形成的 天际线 .每个建筑物的几何信息由数组 build ...