ffmpeg编解码中,给本地视频加上时间水印,并保存到本地,使用到的技术是ffmpeg中的avfilter库;
具体效果如下
yuv:
mp4
本方法不适合摄像头解码,解码出来糊得不行,本地视频的话会好得多;
具体代码如下:
int video::waterMark(AVFrame *frame_in,AVFrame *frame_out,int w,int h,const char *str)
{
int ret;
/*根据名字获取ffmegding定义的filter*/
const AVFilter *buffersrc=avfilter_get_by_name("buffer");//原始数据
const AVFilter *buffersink=avfilter_get_by_name("buffersink");//处理后的数据
/*动态分配AVFilterInOut空间*/
outputs=avfilter_inout_alloc();
inputs=avfilter_inout_alloc();
/*创建AVFilterGraph,分配空间*/
filter_graph = avfilter_graph_alloc();
enum AVPixelFormat pix_fmts[]={AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE};//设置格式
/*过滤器参数:解码器的解码帧将被插入这里。*/
char args[256];
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
w,h,AV_PIX_FMT_YUV420P,1,25,1,1);//图像宽高,格式,帧率,画面横纵比
qDebug()<<args;
/*创建过滤器上下文,源数据AVFilterContext*/
AVFilterContext *buffersrc_ctx = nullptr;
ret=avfilter_graph_create_filter(&buffersrc_ctx,buffersrc,"in",args,NULL,filter_graph);
if(ret<0)
{
qDebug()<<"创建过滤器上下文失败AVFilterContext";
return -1;
}
/*创建过滤器上下文,处理后数据buffersink_params*/
AVBufferSinkParams *buffersink_params;
buffersink_params=av_buffersink_params_alloc();
buffersink_params->pixel_fmts=pix_fmts;//设置格式
AVFilterContext *buffersink_ctx;
ret=avfilter_graph_create_filter(&buffersink_ctx,buffersink,"out",NULL,buffersink_params,filter_graph);
av_free(buffersink_params);
if(ret<0)
{
qDebug()<<"创建sink过滤器上下文失败AVFilterContext";
return -2;
}
/*过滤器链输入/输出链接列表*/
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;
char filter_desrc[200]={0};//要添加的水印数据
snprintf(filter_desrc,sizeof(filter_desrc),"drawtext=fontfile=arial.ttf:fontcolor=green:fontsize=20:x=450:y=50:text='%s'",str);
qDebug()<<filter_desrc;
if(avfilter_graph_parse_ptr(filter_graph,filter_desrc,&inputs,&outputs, NULL)<0)//设置过滤器数据内容
{
qDebug()<<avfilter_graph_parse_ptr(filter_graph,filter_desrc,&inputs,&outputs, NULL);
qDebug()<<"添加字符串信息失败";
return -3;
}
qDebug()<<"添加字符串信息成功";
/*检测配置信息是否正常*/
if(avfilter_graph_config(filter_graph,NULL)<0)
{
qDebug()<<"配置信息有误";
return -4;
}
qDebug()<<"配置信息成功";
/*往源滤波器buffer中输入待处理数据*/
if(av_buffersrc_add_frame(buffersrc_ctx,frame_in)<0)
{
qDebug()<<"往源滤波器buffer中输入待处理数据有误";
return -5;
}
qDebug()<<"往源滤波器buffer中输入待处理数据成功";
/*从滤波器中输出处理数据*/
if(av_buffersink_get_frame(buffersink_ctx, frame_out)<0)
{
qDebug()<<"从滤波器中输出处理数据失败";
return -6;
}
qDebug()<<"从滤波器中输出处理数据成功";
avfilter_inout_free(&outputs);
avfilter_inout_free(&inputs);
avfilter_graph_free(&filter_graph);
return 0;
}
上述的函数我是放入解码线程中进行循环操作,解码部分可以参考我之前的【Qt+FFmpeg】解码播放本地视频(一)_logani的博客
部分有些出入,所以这边还是贴一下代码
void video::run()
{
in_width=avcodec_context->width;
in_height=avcodec_context->height;
qDebug()<<"6.循环解码";
//packet开空间
av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//输入->环境一帧数据->缓冲区->类似于一张图
pFramein = av_frame_alloc();
//输出->帧数据->数据格式->RGB
pFrameRGB = av_frame_alloc();
pictureYUV=av_frame_alloc();
pFrameOUT=av_frame_alloc();
pictureYUV->format = avcodec_context->pix_fmt; //获取像素格式
pFrameRGB->format = avcodec_context->pix_fmt; //获取像素格式
//只有指定了AVFrame的像素格式、画面大小才能真正分配内存
//缓冲区分配内存RGB
pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
//缓冲区分配内存YUV
buffer = (uint8_t *)av_malloc(avpicture_get_size(
AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
bufferOUT= (uint8_t *)av_malloc(avpicture_get_size(
AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
//初始化缓冲区 类似于memset
avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);
//初始化缓冲区 类似于memset
avpicture_fill((AVPicture *)pictureYUV, buffer,
AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
avpicture_fill((AVPicture *)pFrameOUT, bufferOUT,
AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
//解码的状态类型(0:表示解码完毕,非0:表示正在解码)
int y_size, u_size, v_size=0;
//用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
//准备一个视频像素数据格式上下文
//参数一:输入帧数据宽
//参数二:输入帧数据高
//参数三:输入帧数据格式
//参数四:输出帧数据宽
//参数五:输出帧数据高
//参数六:输出帧数据格式->AV_PIX_FMT_RGB32
//参数七:视频像素数据格式转换算法类型
//参数八:字节对齐类型(C/C++里面)->提高读取效率
SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_RGB32,
SWS_BICUBIC,NULL,NULL,NULL);
SwsContext* ySwsContent = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL,NULL,NULL);
//保存 yuv 像素数据的文件
FILE *fpyuv=fopen("../videofile/saveYUV.yuv","wb+");
int ret=0;
while (m_stop == false)
{
//>=0:说明有数据,继续读取 <0:说明读取完毕,结束
//从视频文件上下文中读取包--- 有数据就一直读取
if (av_read_frame(avformat_context,av_packet) >= 0)
{
//解码什么类型流(视频流、音频流、字幕流等等...)
if (av_packet->stream_index == av_stream_index)
{
//发送一个包数据进行解码
avcodec_send_packet(avcodec_context, av_packet);
//接收一个包数据,解压成一帧
ret = avcodec_receive_frame(avcodec_context,pFramein);
if (ret == 0)
{
sws_scale(ySwsContent,(const unsigned char* const*)pFramein->data,pFramein->linesize, 0,avcodec_context->height,
pictureYUV->data, pictureYUV->linesize);
sws_scale(ySwsContent,(const unsigned char* const*)pFramein->data,pFramein->linesize, 0,avcodec_context->height,
pFrameOUT->data, pictureYUV->linesize);
pictureYUV->width=in_width;
pictureYUV->height=in_height;
pictureYUV->format=AV_PIX_FMT_YUV420P;
//图片格式的转换 输入 输出
sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
pFrameRGB->data, pFrameRGB->linesize);
QImage image(pOutbuffer,avcodec_context->width,avcodec_context->height,QImage::Format_RGB32);
qDebug()<<"接收图片信号"<<image;
sec=time(NULL);
if(sec!=sec2)
{
sec2=sec;
struct tm* today = localtime(&sec2);
strftime(sys_time, sizeof(sys_time), "%Y/%m/%d %H\\:%M\\:%S", today);
}
waterMark(pictureYUV,pFrameOUT,in_width,in_height,sys_time);//添加水印
//yuv420规则一:Y结构表示一个像素点
//yuv420规则二:四个Y对应一个U和一个V(也就是四个像素点,对应一个U和一个V)
// y = 宽 * 高
// u = y / 4
// v = y / 4
y_size = avcodec_context->width * avcodec_context->height;
u_size = y_size / 4;
v_size = y_size / 4;
fwrite(pFrameOUT->data[0],1,y_size,fpyuv);//Y UV 个数是 Y 的 1/4
fwrite(pFrameOUT->data[1],1,u_size,fpyuv);//U
fwrite(pFrameOUT->data[2],1,v_size,fpyuv);//V
//计数第几张图片
current_frame_index++;
//发送图片信号
emit sigGetOneFrame(image);
emit SendOneData(current_frame_index);
video::YUVQueue.enqueue(pFrameOUT);//YUV像素数据存入队列中
video::ImageQueue.enqueue(image);//RGB存入队列
//pCoding->codingFrame(pFramein);//发送YUV
//延时操作 1秒显示25帧--1000/25=40
QThread::msleep(timeSpeed);
//获取的视频信息
qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
}
}
}
else
{
qDebug()<<"播放完毕";
emit sigPlayOver();//读取完视频发出信号
this->current_frame_index=0;//清空计数
this->stop();
//关闭文件
fclose(fpyuv);
}
av_free_packet(av_packet);
}
}
做完这些,可能会存在的一些问题:
1. avfilter_graph_create_filter返回值小于0,打印值为-22;
原因:没有注册avfilter组件
解决方法:注册一下环境
2. avfilter_graph_parse_ptr返回值小于0,添加字符信息失败;
红字字体错误:[Parsed_drawtext_0 @ 0x70fc22fe00] Cannot find a valid font for the family Sans
[AVFilterGraph @ 0x71031fc980] Error initializing filter 'drawtext'
原因:没有本地字体文件
解决方法:网上下载一个
3. 红色字体错误Changing video/audio frame properties on the fly is not supported by all filters
原因:可能是输入流没有初始化大小和格式?
添加了下面代码就没有报错
本文参考了
基于FFMPEG水印添加---avfilter库_IT_阿水的博客-ffmpeg 动态水印
感谢观看!!!!
以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!