本文主要讲述如何利用Ffmpeg向视频文件 添加水印这一功能,文中最后会给出源代码下载地址以及视频

下载地址,视频除了讲述添加水印的基本原理以及代码实现,还提到了要注意的一些地方,因为直接运行

demo源码可能会有问题。利用Ffmpeg向视频文件添加水印的基本原理是将视频文件的视频包解码成一帧帧

“Frame”,通过ffmpeg  Filter(overlay)实现待添加水印与“Frame”的叠加,最后将叠加后的视频帧进行编码

并将编码后的数据写到输出文件里。基本的流程如下图所示:

图1 ffmpeg 添加水印基本流程

了解了添加水印的基本原理及流程以后,下面给出代码部分:

一  打开输入源:

与以往的打开输入源的方法不同,这里的多了一个入参,根据输入的参数不同既可打开视频文件,可以打开水印图片。

int OpenInput(char *fileName,int inputIndex)
{
context[inputIndex] = avformat_alloc_context();
context[inputIndex]->interrupt_callback.callback = interrupt_cb;
AVDictionary *format_opts = nullptr; int ret = avformat_open_input(&context[inputIndex], fileName, nullptr, &format_opts);
if(ret < 0)
{
return ret;
}
ret = avformat_find_stream_info(context[inputIndex],nullptr);
av_dump_format(context[inputIndex], 0, fileName, 0);
if(ret >= 0)
{
std::cout <<"open input stream successfully" << endl;
}
return ret;
}

 二. 读取视频包(图片帧)

shared_ptr<AVPacket> ReadPacketFromSource(int inputIndex)
{
std::shared_ptr<AVPacket> packet(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); });
av_init_packet(packet.get());
int ret = av_read_frame(context[inputIndex], packet.get());
if(ret >= 0)
{
return packet;
}
else
{
return nullptr;
}
}

  三. 创建输出上下文

int OpenOutput(char *fileName,int inputIndex)
{
int ret = 0;
ret = avformat_alloc_output_context2(&outputContext, nullptr, "mpegts", fileName);
if(ret < 0)
{
goto Error;
}
ret = avio_open2(&outputContext->pb, fileName, AVIO_FLAG_READ_WRITE,nullptr, nullptr);
if(ret < 0)
{
goto Error;
} for(int i = 0; i < context[inputIndex]->nb_streams; i++)
{
AVStream * stream = avformat_new_stream(outputContext, outPutEncContext->codec);
stream->codec = outPutEncContext;
if(ret < 0)
{
goto Error;
}
}
av_dump_format(outputContext, 0, fileName, 1);
ret = avformat_write_header(outputContext, nullptr);
if(ret < 0)
{
goto Error;
}
if(ret >= 0)
cout <<"open output stream successfully" << endl;
return ret ;
Error:
if(outputContext)
{
avformat_close_input(&outputContext);
}
return ret ;
}

  四. 初始化编解码code

int InitEncoderCodec( int iWidth, int iHeight,int inputIndex)
{
AVCodec * pH264Codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if(NULL == pH264Codec)
{
printf("%s", "avcodec_find_encoder failed");
return -1;
}
outPutEncContext = avcodec_alloc_context3(pH264Codec);
outPutEncContext->gop_size = 30;
outPutEncContext->has_b_frames = 0;
outPutEncContext->max_b_frames = 0;
outPutEncContext->codec_id = pH264Codec->id;
outPutEncContext->time_base.num =context[inputIndex]->streams[0]->codec->time_base.num;
outPutEncContext->time_base.den = context[inputIndex]->streams[0]->codec->time_base.den;
outPutEncContext->pix_fmt = *pH264Codec->pix_fmts;
outPutEncContext->width = iWidth;
outPutEncContext->height = iHeight; outPutEncContext->me_subpel_quality = 0;
outPutEncContext->refs = 1;
outPutEncContext->scenechange_threshold = 0;
outPutEncContext->trellis = 0;
AVDictionary *options = nullptr;
outPutEncContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; int ret = avcodec_open2(outPutEncContext, pH264Codec, &options);
if (ret < 0)
{
printf("%s", "open codec failed");
return ret;
}
return 1;
} int InitDecodeCodec(AVCodecID codecId,int inputIndex)
{
auto codec = avcodec_find_decoder(codecId);
if(!codec)
{
return -1;
}
decoderContext[inputIndex] = context[inputIndex]->streams[0]->codec;
if (!decoderContext) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
} int ret = avcodec_open2(decoderContext[inputIndex], codec, NULL);
return ret; }

五. 初始化Filter

int InitInputFilter(AVFilterInOut *input,const char *filterName,  int inputIndex)
{
char args[512];
memset(args,0, sizeof(args));
AVFilterContext *padFilterContext = input->filter_ctx; auto filter = avfilter_get_by_name("buffer");
auto codecContext = context[inputIndex]->streams[0]->codec; sprintf_s(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->time_base.num, codecContext->time_base.den /codecContext->ticks_per_frame,
codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den); int ret = avfilter_graph_create_filter(&inputFilterContext[inputIndex],filter,filterName, args,
NULL, filter_graph);
if(ret < 0) return ret;
ret = avfilter_link(inputFilterContext[inputIndex],0,padFilterContext,input->pad_idx);
return ret;
} int InitOutputFilter(AVFilterInOut *output,const char *filterName)
{
AVFilterContext *padFilterContext = output->filter_ctx;
auto filter = avfilter_get_by_name("buffersink"); int ret = avfilter_graph_create_filter(&outputFilterContext,filter,filterName, NULL,
NULL, filter_graph);
if(ret < 0) return ret;
ret = avfilter_link(padFilterContext,output->pad_idx,outputFilterContext,0); return ret;
}

 六. Demo

int _tmain(int argc, _TCHAR* argv[])
{
//string fileInput = "D:\\test11.ts";
string fileInput[2];
fileInput[0]= "F:\\test.ts";
fileInput[1]= "F:\\test1.jpg";
string fileOutput = "F:\\codeoutput.ts";
std::thread decodeTask;
Init();
int ret = 0;
for(int i = 0; i < 2;i++)
{
if(OpenInput((char *)fileInput[i].c_str(),i) < 0)
{
cout << "Open file Input 0 failed!" << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
} ret = InitDecodeCodec(context[i]->streams[0]->codec->codec_id,i);
if(ret <0)
{
cout << "InitDecodeCodec failed!" << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
}
} ret = InitEncoderCodec(decoderContext[0]->width,decoderContext[0]->height,0);
if(ret < 0)
{
cout << "open eccoder failed ret is " << ret<<endl;
cout << "InitEncoderCodec failed!" << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
} //ret = InitFilter(outPutEncContext);
if(OpenOutput((char *)fileOutput.c_str(),0) < 0)
{
cout << "Open file Output failed!" << endl;
this_thread::sleep_for(chrono::seconds(10));
return 0;
} AVFrame *pSrcFrame[2];
pSrcFrame[0] = av_frame_alloc();
pSrcFrame[1] = av_frame_alloc(); AVFrame *inputFrame[2];
inputFrame[0] = av_frame_alloc();
inputFrame[1] = av_frame_alloc();
auto filterFrame = av_frame_alloc();
int got_output = 0;
int64_t timeRecord = 0;
int64_t firstPacketTime = 0;
int64_t outLastTime = av_gettime();
int64_t inLastTime = av_gettime();
int64_t videoCount = 0; filter_graph = avfilter_graph_alloc();
if(!filter_graph)
{
cout <<"graph alloc failed"<<endl;
goto End;
}
avfilter_graph_parse2(filter_graph, filter_descr, &inputs, &outputs);
InitInputFilter(inputs,"MainFrame",0);
InitInputFilter(inputs->next,"OverlayFrame",1);
InitOutputFilter(outputs,"output"); FreeInout(); ret = avfilter_graph_config(filter_graph, NULL);
if(ret < 0)
{
goto End;
}
decodeTask.swap(thread([&]{
bool ret = true;
while(ret)
{
auto packet = ReadPacketFromSource(1);
ret = DecodeVideo(packet.get(),pSrcFrame[1],1);
if(ret) break;
}
}));
decodeTask.join(); while(true)
{
outLastTime = av_gettime();
auto packet = ReadPacketFromSource(0);
if(packet)
{
if(DecodeVideo(packet.get(),pSrcFrame[0],0))
{
av_frame_ref( inputFrame[0],pSrcFrame[0]);
if (av_buffersrc_add_frame_flags(inputFilterContext[0], inputFrame[0], AV_BUFFERSRC_FLAG_PUSH) >= 0)
{
pSrcFrame[1]->pts = pSrcFrame[0]->pts;
//av_frame_ref( inputFrame[1],pSrcFrame[1]);
if(av_buffersrc_add_frame_flags(inputFilterContext[1],pSrcFrame[1], AV_BUFFERSRC_FLAG_PUSH) >= 0)
{
ret = av_buffersink_get_frame_flags(outputFilterContext, filterFrame,AV_BUFFERSINK_FLAG_NO_REQUEST); //this_thread::sleep_for(chrono::milliseconds(10));
if ( ret >= 0)
{
std::shared_ptr<AVPacket> pTmpPkt(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); });
av_init_packet(pTmpPkt.get());
pTmpPkt->data = NULL;
pTmpPkt->size = 0;
ret = avcodec_encode_video2(outPutEncContext, pTmpPkt.get(), filterFrame, &got_output);
if (ret >= 0 && got_output)
{
int ret = av_write_frame(outputContext, pTmpPkt.get());
}
//this_thread::sleep_for(chrono::milliseconds(10));
}
av_frame_unref(filterFrame);
}
}
}
}
else break;
}
CloseInput(0);
CloseOutput();
std::cout <<"Transcode file end!" << endl;
End:
this_thread::sleep_for(chrono::hours(10));
return 0;
}

如需交流,可以加QQ群1038388075,766718184,或者QQ:350197870

视频下载地址:http://www.chungen90.com/?news_3/

Demo下载地址: http://www.chungen90.com/?news_2

ffmpeg 视频教程 添加水印附源码的更多相关文章

  1. 为SRS流媒体服务器添加HLS加密功能(附源码)

    为SRS流媒体服务器添加HLS加密功能(附源码) 之前测试使用过nginx的HLS加密功能,会使用到一个叫做nginx-rtmp-module的插件,但此插件很久不更新了,网上搜索到一个中国制造的叫做 ...

  2. 一步步实现windows版ijkplayer系列文章之七——终结篇(附源码)

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  3. 在网站开发中很有用的8个 jQuery 效果【附源码】

    jQuery 作为最优秀 JavaScript 库之一,改变了很多人编写 JavaScript 的方式.它简化了 HTML 文档遍历,事件处理,动画和 Ajax 交互,而且有成千上万的成熟 jQuer ...

  4. Web 开发中很实用的10个效果【附源码下载】

    在工作中,我们可能会用到各种交互效果.而这些效果在平常翻看文章的时候碰到很多,但是一时半会又想不起来在哪,所以养成知识整理的习惯是很有必要的.这篇文章给大家推荐10个在 Web 开发中很有用的效果,记 ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  6. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)

    前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...

  7. 轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)

    在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Head ...

  8. jquery自定义插件结合baiduTemplate.js实现异步刷新(附源码)

    上一篇记录了BaiduTemplate模板引擎使用示例附源码,在此基础上对使用方法进行了封装 自定义插件jajaxrefresh.js 代码如下: //闭包限定命名空间 (function ($) { ...

  9. 精选9个值得学习的 HTML5 效果【附源码】

    这里精选了一组很酷的 HTML5 效果.HTML5 是现 Web 开发领域的热点, 拥有很多让人期待已久的新特性,特别是在移动端,Web 开发人员可以借助 HTML5 强大功能轻松制作各种交互性强.效 ...

随机推荐

  1. [bzoj1095][ZJOI2007]Hide 捉迷藏 点分树,动态点分治

    [bzoj1095][ZJOI2007]Hide 捉迷藏 2015年4月20日7,8876 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiaji ...

  2. linux JDK安装(一)

    1.先卸载服务器自带的jdk软件包# java -version #查看服务器是否安装过jdkjava version "1.6.0_17"OpenJDK Runtime Envi ...

  3. Number Sequence(poj 1019)

    题意: 有一串数字串,其规律为 1 12 123 1234 12345 123456 1234567 12345678 123456789 12345678910 1234567891011 1234 ...

  4. ObjectDataSource配置数据源的时候,选择业务对象下拉菜单没有任何东西

    原文发布时间为:2008-08-03 -- 来源于本人的百度文章 [由搬家工具导入] 问题:在App_Code这个文件夹中添加了一个类,然后保存,但是在ObjectDataSource配置数据源的时候 ...

  5. Object转json-常见问题总结

    Object转json-常见问题总结 1.Object中有Timestamp.Date等日期类型数据 http://blog.csdn.net/without0815/article/details/ ...

  6. 开始学习es6(一) 搭建个es6的开发环境

    1.开始学习es6 如果想在浏览器跑es6  需要给es6个环境 因为一直用vue-cli全家桶 这样虽然方便 但如果用es6需要跑起个vue全家桶 于是想到可以用gulp搭建个开发环境 首先需要1. ...

  7. mybatis传入map参数,map中包含list(输入参数)

    1.xml中配置: <!-- 根据条件查询满足条件的ID集合开始 --> <select id="getQuestionsIdsForExamPaper" res ...

  8. 【Visual Studio】LNK2038: mismatch detected for 'RuntimeLibrary': value 'MTd_StaticDebug' doesn't match value 'MDd_

    出现这个问题的原因:工程在转换过程中,发现有一些工程使用MD编译选项,有一些工程使用MTD编译选项,导致静态和动态连接MSVC的连接库有冲突.将全部工程改变MT或MTD编译,即可以解决. 工程 —&g ...

  9. 切換 java compiler 版本

    有些程式在執行時會顯示需要較新的 java jre 版本, 若系統裝有兩個 java jre 可以使用下列指令切協版本, sudo update-alternatives --config java ...

  10. hdu4183往返经过至多每个点一次/最大流

    题意:从s到t,每个点有f值,只能从f值小的到大的,到T后回来,只能从f值大的到 小的,求可行否. 往返,其实就是俩条路过去(每个点最多一次),所以想到流量为2,跑最大流,看是否满2,又要每个点最多一 ...