本文主要讲述如何利用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. PTA 11-散列1 电话聊天狂人 (25分)

    题目地址 https://pta.patest.cn/pta/test/15/exam/4/question/722 5-14 电话聊天狂人   (25分) 给定大量手机用户通话记录,找出其中通话次数 ...

  2. 九度oj 1006

    题目1006:ZOJ问题 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:20252 解决:3544 题目描述:                        对给定的字符串(只包含'z', ...

  3. 【转】简要分析unity3d中剪不断理还乱的yield

    在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy ...

  4. hdu6034[模拟] 2017多校1

    /*hdu6034[模拟] 2017多校1*/ //暴力模拟26个26进制数即可, 要注意进位 #include<bits/stdc++.h> using namespace std; t ...

  5. webstorm自带debugger服务器

    打开webstorm->settings->Build,Execution,Deployment->Debugger->把端口Port改成8089或者其他80端口,按确定就可以 ...

  6. BZOJ 3786 星系探索 ——Splay

    子树可以移动,唔. 还是用Splay维护DFS序即可. 子树的话直接截取出来就好了. 然后求前驱后继可能麻烦一些. 添加两个虚拟节点会比较好写. #include <map> #inclu ...

  7. [USACO12DEC]第一!First! (Trie树,拓扑排序)

    题目链接 Solution 感觉比较巧的题啊... 考虑几点: 可以交换无数次字母表,即字母表可以为任意形态. 对于以其他字符串为前缀的字符串,我们可以直接舍去. 因为此时它所包含的前缀的字典序绝对比 ...

  8. PHP中的验证码类(验证码功能设计之一)

    <!--vcode.class.php内容--> <?php class Vcode { private $width; //宽 private $height; //高 priva ...

  9. django:访问本地静态文件的配置

    1.在setting.py中新增如下配置,static为静态文件的目录,BASE_DIR为项目根目录 STATIC_URL = '/static/' STATIC_ROOT = os.path.joi ...

  10. Python Challenge 第三关

    进入第三关,还是一张图加一句话:One small letter, surrounded by EXACTLY three big bodyguards on each of its sides. 图 ...