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

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

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

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

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

图1 ffmpeg 添加水印基本流程

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

一  打开输入源:

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

  1. int OpenInput(char *fileName,int inputIndex)
  2. {
  3. context[inputIndex] = avformat_alloc_context();
  4. context[inputIndex]->interrupt_callback.callback = interrupt_cb;
  5. AVDictionary *format_opts = nullptr;
  6.  
  7. int ret = avformat_open_input(&context[inputIndex], fileName, nullptr, &format_opts);
  8. if(ret < 0)
  9. {
  10. return ret;
  11. }
  12. ret = avformat_find_stream_info(context[inputIndex],nullptr);
  13. av_dump_format(context[inputIndex], 0, fileName, 0);
  14. if(ret >= 0)
  15. {
  16. std::cout <<"open input stream successfully" << endl;
  17. }
  18. return ret;
  19. }

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

  1. shared_ptr<AVPacket> ReadPacketFromSource(int inputIndex)
  2. {
  3. std::shared_ptr<AVPacket> packet(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); });
  4. av_init_packet(packet.get());
  5. int ret = av_read_frame(context[inputIndex], packet.get());
  6. if(ret >= 0)
  7. {
  8. return packet;
  9. }
  10. else
  11. {
  12. return nullptr;
  13. }
  14. }

  三. 创建输出上下文

  1. int OpenOutput(char *fileName,int inputIndex)
  2. {
  3. int ret = 0;
  4. ret = avformat_alloc_output_context2(&outputContext, nullptr, "mpegts", fileName);
  5. if(ret < 0)
  6. {
  7. goto Error;
  8. }
  9. ret = avio_open2(&outputContext->pb, fileName, AVIO_FLAG_READ_WRITE,nullptr, nullptr);
  10. if(ret < 0)
  11. {
  12. goto Error;
  13. }
  14.  
  15. for(int i = 0; i < context[inputIndex]->nb_streams; i++)
  16. {
  17. AVStream * stream = avformat_new_stream(outputContext, outPutEncContext->codec);
  18. stream->codec = outPutEncContext;
  19. if(ret < 0)
  20. {
  21. goto Error;
  22. }
  23. }
  24. av_dump_format(outputContext, 0, fileName, 1);
  25. ret = avformat_write_header(outputContext, nullptr);
  26. if(ret < 0)
  27. {
  28. goto Error;
  29. }
  30. if(ret >= 0)
  31. cout <<"open output stream successfully" << endl;
  32. return ret ;
  33. Error:
  34. if(outputContext)
  35. {
  36. avformat_close_input(&outputContext);
  37. }
  38. return ret ;
  39. }

  四. 初始化编解码code

  1. int InitEncoderCodec( int iWidth, int iHeight,int inputIndex)
  2. {
  3. AVCodec * pH264Codec = avcodec_find_encoder(AV_CODEC_ID_H264);
  4. if(NULL == pH264Codec)
  5. {
  6. printf("%s", "avcodec_find_encoder failed");
  7. return -1;
  8. }
  9. outPutEncContext = avcodec_alloc_context3(pH264Codec);
  10. outPutEncContext->gop_size = 30;
  11. outPutEncContext->has_b_frames = 0;
  12. outPutEncContext->max_b_frames = 0;
  13. outPutEncContext->codec_id = pH264Codec->id;
  14. outPutEncContext->time_base.num =context[inputIndex]->streams[0]->codec->time_base.num;
  15. outPutEncContext->time_base.den = context[inputIndex]->streams[0]->codec->time_base.den;
  16. outPutEncContext->pix_fmt = *pH264Codec->pix_fmts;
  17. outPutEncContext->width = iWidth;
  18. outPutEncContext->height = iHeight;
  19.  
  20. outPutEncContext->me_subpel_quality = 0;
  21. outPutEncContext->refs = 1;
  22. outPutEncContext->scenechange_threshold = 0;
  23. outPutEncContext->trellis = 0;
  24. AVDictionary *options = nullptr;
  25. outPutEncContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
  26.  
  27. int ret = avcodec_open2(outPutEncContext, pH264Codec, &options);
  28. if (ret < 0)
  29. {
  30. printf("%s", "open codec failed");
  31. return ret;
  32. }
  33. return 1;
  34. }
  35.  
  36. int InitDecodeCodec(AVCodecID codecId,int inputIndex)
  37. {
  38. auto codec = avcodec_find_decoder(codecId);
  39. if(!codec)
  40. {
  41. return -1;
  42. }
  43. decoderContext[inputIndex] = context[inputIndex]->streams[0]->codec;
  44. if (!decoderContext) {
  45. fprintf(stderr, "Could not allocate video codec context\n");
  46. exit(1);
  47. }
  48.  
  49. int ret = avcodec_open2(decoderContext[inputIndex], codec, NULL);
  50. return ret;
  51.  
  52. }

五. 初始化Filter

  1. int InitInputFilter(AVFilterInOut *input,const char *filterName, int inputIndex)
  2. {
  3. char args[512];
  4. memset(args,0, sizeof(args));
  5. AVFilterContext *padFilterContext = input->filter_ctx;
  6.  
  7. auto filter = avfilter_get_by_name("buffer");
  8. auto codecContext = context[inputIndex]->streams[0]->codec;
  9.  
  10. sprintf_s(args, sizeof(args),
  11. "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
  12. codecContext->width, codecContext->height, codecContext->pix_fmt,
  13. codecContext->time_base.num, codecContext->time_base.den /codecContext->ticks_per_frame,
  14. codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den);
  15.  
  16. int ret = avfilter_graph_create_filter(&inputFilterContext[inputIndex],filter,filterName, args,
  17. NULL, filter_graph);
  18. if(ret < 0) return ret;
  19. ret = avfilter_link(inputFilterContext[inputIndex],0,padFilterContext,input->pad_idx);
  20. return ret;
  21. }
  22.  
  23. int InitOutputFilter(AVFilterInOut *output,const char *filterName)
  24. {
  25. AVFilterContext *padFilterContext = output->filter_ctx;
  26. auto filter = avfilter_get_by_name("buffersink");
  27.  
  28. int ret = avfilter_graph_create_filter(&outputFilterContext,filter,filterName, NULL,
  29. NULL, filter_graph);
  30. if(ret < 0) return ret;
  31. ret = avfilter_link(padFilterContext,output->pad_idx,outputFilterContext,0);
  32.  
  33. return ret;
  34. }

 六. Demo

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. //string fileInput = "D:\\test11.ts";
  4. string fileInput[2];
  5. fileInput[0]= "F:\\test.ts";
  6. fileInput[1]= "F:\\test1.jpg";
  7. string fileOutput = "F:\\codeoutput.ts";
  8. std::thread decodeTask;
  9. Init();
  10. int ret = 0;
  11. for(int i = 0; i < 2;i++)
  12. {
  13. if(OpenInput((char *)fileInput[i].c_str(),i) < 0)
  14. {
  15. cout << "Open file Input 0 failed!" << endl;
  16. this_thread::sleep_for(chrono::seconds(10));
  17. return 0;
  18. }
  19.  
  20. ret = InitDecodeCodec(context[i]->streams[0]->codec->codec_id,i);
  21. if(ret <0)
  22. {
  23. cout << "InitDecodeCodec failed!" << endl;
  24. this_thread::sleep_for(chrono::seconds(10));
  25. return 0;
  26. }
  27. }
  28.  
  29. ret = InitEncoderCodec(decoderContext[0]->width,decoderContext[0]->height,0);
  30. if(ret < 0)
  31. {
  32. cout << "open eccoder failed ret is " << ret<<endl;
  33. cout << "InitEncoderCodec failed!" << endl;
  34. this_thread::sleep_for(chrono::seconds(10));
  35. return 0;
  36. }
  37.  
  38. //ret = InitFilter(outPutEncContext);
  39. if(OpenOutput((char *)fileOutput.c_str(),0) < 0)
  40. {
  41. cout << "Open file Output failed!" << endl;
  42. this_thread::sleep_for(chrono::seconds(10));
  43. return 0;
  44. }
  45.  
  46. AVFrame *pSrcFrame[2];
  47. pSrcFrame[0] = av_frame_alloc();
  48. pSrcFrame[1] = av_frame_alloc();
  49.  
  50. AVFrame *inputFrame[2];
  51. inputFrame[0] = av_frame_alloc();
  52. inputFrame[1] = av_frame_alloc();
  53. auto filterFrame = av_frame_alloc();
  54. int got_output = 0;
  55. int64_t timeRecord = 0;
  56. int64_t firstPacketTime = 0;
  57. int64_t outLastTime = av_gettime();
  58. int64_t inLastTime = av_gettime();
  59. int64_t videoCount = 0;
  60.  
  61. filter_graph = avfilter_graph_alloc();
  62. if(!filter_graph)
  63. {
  64. cout <<"graph alloc failed"<<endl;
  65. goto End;
  66. }
  67. avfilter_graph_parse2(filter_graph, filter_descr, &inputs, &outputs);
  68. InitInputFilter(inputs,"MainFrame",0);
  69. InitInputFilter(inputs->next,"OverlayFrame",1);
  70. InitOutputFilter(outputs,"output");
  71.  
  72. FreeInout();
  73.  
  74. ret = avfilter_graph_config(filter_graph, NULL);
  75. if(ret < 0)
  76. {
  77. goto End;
  78. }
  79. decodeTask.swap(thread([&]{
  80. bool ret = true;
  81. while(ret)
  82. {
  83. auto packet = ReadPacketFromSource(1);
  84. ret = DecodeVideo(packet.get(),pSrcFrame[1],1);
  85. if(ret) break;
  86. }
  87. }));
  88. decodeTask.join();
  89.  
  90. while(true)
  91. {
  92. outLastTime = av_gettime();
  93. auto packet = ReadPacketFromSource(0);
  94. if(packet)
  95. {
  96. if(DecodeVideo(packet.get(),pSrcFrame[0],0))
  97. {
  98. av_frame_ref( inputFrame[0],pSrcFrame[0]);
  99. if (av_buffersrc_add_frame_flags(inputFilterContext[0], inputFrame[0], AV_BUFFERSRC_FLAG_PUSH) >= 0)
  100. {
  101. pSrcFrame[1]->pts = pSrcFrame[0]->pts;
  102. //av_frame_ref( inputFrame[1],pSrcFrame[1]);
  103. if(av_buffersrc_add_frame_flags(inputFilterContext[1],pSrcFrame[1], AV_BUFFERSRC_FLAG_PUSH) >= 0)
  104. {
  105. ret = av_buffersink_get_frame_flags(outputFilterContext, filterFrame,AV_BUFFERSINK_FLAG_NO_REQUEST);
  106.  
  107. //this_thread::sleep_for(chrono::milliseconds(10));
  108. if ( ret >= 0)
  109. {
  110. std::shared_ptr<AVPacket> pTmpPkt(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p); });
  111. av_init_packet(pTmpPkt.get());
  112. pTmpPkt->data = NULL;
  113. pTmpPkt->size = 0;
  114. ret = avcodec_encode_video2(outPutEncContext, pTmpPkt.get(), filterFrame, &got_output);
  115. if (ret >= 0 && got_output)
  116. {
  117. int ret = av_write_frame(outputContext, pTmpPkt.get());
  118. }
  119. //this_thread::sleep_for(chrono::milliseconds(10));
  120. }
  121. av_frame_unref(filterFrame);
  122. }
  123. }
  124. }
  125. }
  126. else break;
  127. }
  128. CloseInput(0);
  129. CloseOutput();
  130. std::cout <<"Transcode file end!" << endl;
  131. End:
  132. this_thread::sleep_for(chrono::hours(10));
  133. return 0;
  134. }

如需交流,可以加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. acmer之ubuntu下安装Eclipse

    ubuntu是acmer常用的系统,配置起CB还是比较简单的三行命令就OK了 //Current stable version of Code::Blocks IDE (16.01) //To ins ...

  2. oracle列转行 WM_CONCAT LISTAGG

    开发给个SQL说给某个条件时报ORA-22922 代码段: SELECT 袋号, SUM(实际重量) AS 实际重量, SUM(材积重量) AS 材积重量, COUNT(运单号) AS 件数, TO_ ...

  3. win 7 取得最高权限

    以管理员身份运行cmd,然后输入: net user administrator /active:yes 然后注销,就会看到你原来的用户已经是最高权限的用户了.以后做的操作都是最高权限的操作.

  4. JSON树节点的增删查改

    最近了解到使用json字符串存到数据库的一种存储方式,取出来的json字符串可以进行相应的节点操作 故借此机会练习下递归,完成对json节点操作对应的工具类. 介绍一下我使用的依赖 复制代码 < ...

  5. [转] Makefile 基础 (6) —— Makefile 使用条件判断

    该篇文章为转载,是对原作者系列文章的总汇加上标注. 支持原创,请移步陈浩大神博客:(最原始版本) http://blog.csdn.net/haoel/article/details/2886 我转自 ...

  6. iOS自定义Navbar

    1.修改Navigationbar navigationBar其实有三个子视图,leftBarButtonItem,rightBarButtonItem,以及titleView. 1.1  方法一:a ...

  7. Mondriaan's Dream(poj 2411)

    题意:在n*m的方格里铺1*2的骨牌,有多少种方案 /* 第一次做插头DP,感觉和状压差不多. 这道题是利用上一行的状态来更新下一行的状态. 1代表上一行这个位置填了一个竖的(即本行可以填): 0代表 ...

  8. gridview中的相关事件操作

    原文发布时间为:2008-07-27 -- 来源于本人的百度文章 [由搬家工具导入] using System;using System.Data;using System.Configuration ...

  9. UVA - 10050 Hartals

    #include <cstdio> #include <cstring> ]; ]; int main() { int t; scanf("%d", &am ...

  10. 设计模式原来如此-单例模式(Singleton Pattern)

    简单介绍一下我对Singleton的理解,说的不好请大家多多指点. 单例模式的简单定义就是确保一个类只有一个实例,并提供一个全局访问点. 单例模式有哪些用处呢? 有一些对象其实我们只需要一个,比方说: ...