最近学习FFmpeg编程开发,想写个视频添加水印图片的demo(未对音频或字幕进行处理),代码编写中遇见很多问题,在这里进行做一个笔记来,易于自己记忆和理解。期间在网上找demo,发现很多都是ffmpeg3版本的一些demo,ffmpeg4有很大的改变,有很多方法不适用,因此写篇文章给初学者一些细微的帮助,也易于自己巩固,避免犯类似的错误。

一、总结一下编码过程:

1.初始化化封装格式上下文  并打开文件

avformat_alloc_context / avformat_alloc_output_context2

2.创建输出码流

avformat_new_stream

3.创建编码器(根据输入编码类型)

avcodec_find_encoder

4.创建视频编码器上下文 并设置参数

avcodec_alloc_context3

5.打开编码器

avcodec_open2

6.把编码器信息写入流中

avcodec_parameters_from_context

7.创建并初始化一个AVIOContext,用以访问URL(out_filename)指定的资源

avio_open

8.写入头文件信息

avformat_write_header

9.初始化过滤器 并过滤

init_filter av_buffersrc_add_frame_flags av_frame_make_writable

10.将编码后的视频压缩数据写入文件中

①设置帧类型②发送过滤的frame,接收packet③更新编码帧中流序号,并进行时间基转换④压缩数据写入文件中

11.写入文件尾部信息

av_write_trailer

12.释放资源

二、代码片段

1.输入文件的解码以及编码

int FfmpegTest::open_input_file(const char *filename)
{int ret;// 1. 打开视频文件:读取文件头,将文件格式信息存储在ifmt_ctx中if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0){av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");return ret;}// 2. 搜索流信息:读取一段视频文件数据,尝试解码,将取到的流信息填入ifmt_ctx.streams//    ifmt_ctx.streams是一个指针数组,数组大小是ifmt_ctx.nb_streamsif ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0){av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");return ret;}// 每路音频流/视频流一个AVCodecContextret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);if (ret < 0){cout << "no find best stream";return -1;}videoindex = ret;codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Failed to allocate the decoder context for stream #%u\n");return AVERROR(ENOMEM);}// 3.3 AVCodecContext初始化:使用codec参数codecpar初始化AVCodecContext相应成员ret = avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[videoindex]->codecpar);if (ret < 0){return ret;}ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", ret);return ret;}av_dump_format(ifmt_ctx, 0, filename, 0);if (!init_filter(codec_ctx))return -1;if (open_output_file("uuu.mp4") < 0)return -1;AVPacket *packet;packet = (AVPacket*)av_malloc(sizeof(AVPacket));AVPacket *outpacket=nullptr;outpacket = (AVPacket*)av_malloc(sizeof(AVPacket));AVFrame *frame;frame = av_frame_alloc();AVFrame *outframe;outframe = av_frame_alloc();bool write_status = true;AVBufferRef *bufferRef;while (av_read_frame(ifmt_ctx, packet) >= 0){//时间基转换,解码av_packet_rescale_ts(packet, ifmt_ctx->streams[videoindex]->time_base, encode_ctx->time_base);if (packet->stream_index == videoindex){ret = avcodec_send_packet(codec_ctx, packet);if (ret < 0){cout << "avcodec_send_packet" << endl;break;}while (ret >= 0){ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EINVAL)){cout << "codec not opened, or it is an encoder" << endl;goto end;}else if (ret == AVERROR(EAGAIN)){cout << "user must try to send new input" << endl;continue;}else if (ret == AVERROR_EOF){cout << "the decoder has been fully flushed" << endl;break;}else if (ret >= 0){frame->pts = frame->best_effort_timestamp;//push the decoded frame into the filtergrapthif (av_buffersrc_add_frame_flags(bufsrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0){cout << "Error while feeding the filtergraph" << endl;break;}//pull filtered frames from the filtergraphwhile (1){ret = av_buffersink_get_frame(bufsink_ctx, outframe);if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))break;if (ret < 0)goto end;if (!outframe || !encode_ctx)break;if (av_frame_make_writable(outframe) < 0){cout << "av_frame_make_writable NO!" << endl;break;}// 设置帧类型outframe->pict_type = AV_PICTURE_TYPE_NONE;//10.将编码后的视频压缩数据写入文件中while (1){write_status = true;ret = avcodec_send_frame(encode_ctx, outframe);if (ret < 0){cout << "avcodec_send_frame failed!" << endl;goto end;}ret = avcodec_receive_packet(encode_ctx, outpacket);if (ret == AVERROR_EOF || ret == AVERROR(EINVAL)){break;}else if(ret == AVERROR(EAGAIN)){continue;}else if (ret >= 0){// 3.3 更新编码帧中流序号,并进行时间基转换//     AVPacket.pts和AVPacket.dts的单位是AVStream.time_base,不同的封装格式其AVStream.time_base不同//     所以输出文件中,每个packet需要根据输出封装格式重新计算pts和dtsoutpacket->stream_index = videoindex;AVRational dst = { 1,25 };av_packet_rescale_ts(outpacket, encode_ctx->time_base, ofm_ctx->streams[0]->time_base);//将编码后的packet写入输出媒体文件ret = av_interleaved_write_frame(ofm_ctx, outpacket);av_packet_unref(outpacket);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "write vframe error %d\n", ret);}elsecout << "av_interleaved_write_frame success" << endl;write_status = false;break;}}                  av_frame_unref(outframe);if (!write_status)break;}}}}av_packet_unref(packet);}//11.写入文件尾部信息ret = av_write_trailer(ofm_ctx);cout << "success" << endl;
end:avfilter_graph_free(&filter_graph);av_packet_unref(packet);av_packet_unref(outpacket);av_frame_unref(outframe);av_frame_unref(frame);avcodec_close(codec_ctx);avformat_close_input(&ifmt_ctx);avcodec_free_context(&encode_ctx);avformat_free_context(ofm_ctx);return -1;}

2.初始化过滤器

bool FfmpegTest::init_filter(AVCodecContext *codec_ctx)
{char args[512] = "";int ret = -1;AVFilter *bufsink = (AVFilter*)avfilter_get_by_name("buffersink");AVFilter *bufsrc = (AVFilter*)avfilter_get_by_name("buffer");AVFilterInOut *input = avfilter_inout_alloc();AVFilterInOut *output = avfilter_inout_alloc();AVPixelFormat fmt1[] = { (AVPixelFormat)codec_ctx->pix_fmt, AV_PIX_FMT_NONE};AVRational time_base = ifmt_ctx->streams[videoindex]->time_base;filter_graph = avfilter_graph_alloc();snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt, codec_ctx->time_base.num,codec_ctx->time_base.den, codec_ctx->sample_aspect_ratio.num, codec_ctx->sample_aspect_ratio.den);ret = avfilter_graph_create_filter(&bufsrc_ctx, bufsrc, "in", args, NULL, filter_graph);if (ret < 0){cout << "bufsrc_ctx avfilter_graph_create_filter failed" << endl;return false;}ret = avfilter_graph_create_filter(&bufsink_ctx, bufsink, "out", NULL, NULL, filter_graph);if (ret < 0){cout << "avfilter_graph_create_filter failed" << endl;return false;}ret = av_opt_set_int_list(bufsink_ctx, "pix_fmts", fmt1, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);if (ret < 0){cout << "Cannot set output pixel format" << endl;return false;}output->name = av_strdup("in");output->filter_ctx = bufsrc_ctx;output->pad_idx = 0;output->next = nullptr;input->name = av_strdup("out");input->filter_ctx = bufsink_ctx;input->pad_idx = 0;input->next = nullptr;const char *filter_desc = "movie=Mario.png[wm];[in][wm]overlay=0:0[out]";//添加水印到坐标5,5  (这儿一定注意是否超出范围坐标)//const char *filter_desc = "scale=78:24,transpose=cclock";//const char *filter_desc = "drawtext = fontfile = simhei.ttf: text = 'we are family' : x = 10 : y = 10 : fontsize = 24 : fontcolor = white : shadowy = 2";ret = avfilter_graph_parse(filter_graph, filter_desc, input, output, NULL);if (ret < 0){return false;}ret = avfilter_graph_config(filter_graph, NULL);if (ret < 0)return false;return true;}

3.输出文件的初始化

int FfmpegTest::open_output_file(const char *filename)
{ret = -1;//1.初始化化封装格式上下文  并打开文件ofm_ctx = avformat_alloc_context();ret = avformat_alloc_output_context2(&ofm_ctx, NULL, NULL, filename);if (ret < 0){cout << "avformat_alloc_output_context2 failed!" << endl;goto end;}//2.创建输出码流a_stream = avformat_new_stream(ofm_ctx, NULL);//3.创建编码器(根据输入编码类型)encode = avcodec_find_encoder(ifmt_ctx->streams[videoindex]->codecpar->codec_id);//4.视频编码器上下文 并设置参数encode_ctx = avcodec_alloc_context3(encode);encode_ctx->codec_id = codec_ctx->codec_id;encode_ctx->codec_type = codec_ctx->codec_type;//encode_ctx->frame_size = codec_ctx->frame_size;encode_ctx->height = codec_ctx->height;encode_ctx->width = codec_ctx->width;encode_ctx->sample_aspect_ratio = codec_ctx->sample_aspect_ratio; // 采样宽高比:像素宽/像素高encode_ctx->refs = codec_ctx->refs;encode_ctx->extradata = codec_ctx->extradata;encode_ctx->delay = codec_ctx->delay;encode_ctx->framerate = codec_ctx->framerate;encode_ctx->time_base.den = 25;encode_ctx->time_base = av_inv_q(codec_ctx->framerate);  // 时基:解码器帧率取倒数encode_ctx->time_base.num = 1;encode_ctx->pix_fmt = codec_ctx->pix_fmt;        // 编码器采用解码器的像素格式encode_ctx->bit_rate = codec_ctx->bit_rate;//设置自己的比特率encode_ctx->max_b_frames = 0;//5.打开编码器ret = avcodec_open2(encode_ctx, encode, NULL);if (ret < 0){cout << "avcodec open failed!" << endl;goto end;}//6.把编码器信息写入流中ret = avcodec_parameters_from_context(a_stream->codecpar, encode_ctx);if (ret < 0){cout << "avcodec_parameters_from_context failed!" << endl;goto end;}  av_dump_format(ofm_ctx, 0, filename, 1);if (!(ofm_ctx->oformat->flags & AVFMT_NOFILE)) // TODO: 研究AVFMT_NOFILE标志{// 7. 创建并初始化一个AVIOContext,用以访问URL(out_filename)指定的资源ret = avio_open(&ofm_ctx->pb, filename, AVIO_FLAG_READ_WRITE);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename);goto end;}}    //8.写入头文件信息(前提得初始化一个AVIOContext)ret = avformat_write_header(ofm_ctx, NULL);if (ret < 0){cout << "write header failed!" << endl;goto end;}return 0;
end:avcodec_free_context(&encode_ctx);avformat_free_context(ofm_ctx);return -1;
}

整体上的代码函数就这三个,没有进行细化函数,存在许多优化,本身就是一个demo,能实现功能即可。

三、遇到问题

因之前一开始对编码和解码都是属于混乱状态,一直没理解,出现很多类似的问题,找问题出处,很难下手都是一一尝试,最后才解决的。

1.运行到avformat_write_header()时,ffmpeg_transcode.exe 中)引发的异常: 0xC0000005: 读取位置 0x0000000000000088 时发生访问冲突

write之前并没有执行avio_open(),所以一直报异常

 if (!(ofm_ctx->oformat->flags & AVFMT_NOFILE)) // TODO: 研究AVFMT_NOFILE标志{// 7. 创建并初始化一个AVIOContext,用以访问URL(out_filename)指定的资源ret = avio_open(&ofm_ctx->pb, filename, AVIO_FLAG_READ_WRITE);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", filename);goto end;}}    

2.过滤器初始化过程中错误,导致后面进行avcodec_send_frame或者avcodec_receive_packet也报类似的异常

①原因是格式设置错误,不对应,fmt格式应该于输入格式的pix_fmt对应。

AVPixelFormat fmt1[] = { (AVPixelFormat)codec_ctx->pix_fmt, AV_PIX_FMT_NONE};

②添加水印图片时,坐标起点加上图标大小超出界限

const char *filter_desc = "movie=Mario.png[wm];[in][wm]overlay=0:0[out]";//添加水印到坐标0.0  

3.编码后的视频出现时长不对,可能导致时间缩小(快进几倍的样子),时间变长(扩大播放时间)

原因是没有进行时间基转换av_packet_rescale_ts,得设置对应的时间基,不然就会出现问题。

解码时需要进行时间基转换,编码也需要进行转换,才能对应 (花了长时间才发现的 )

可参考:https://www.cnblogs.com/leisure_chn/p/10584910.html

以上问题就是在编写过程中,花了时间去解决的问题,很多都能避免的。学习新东西,就应该脚踏实地的去实践,不要想着直接把别人的demo进行复制,实现即可。还是需要自己理清思路一步一步实现,解决其中问题,才能更好掌握知识。

学习方法:先掌握基础知识和接口函数调用,再去看逻辑思路,最后再看原理和算法(借用我领导给我讲的东西,总结了一下)

时刻提醒自己,一步一步脚印来,不要好高骛远。

参考:http://ffmpeg.org/doxygen/trunk/examples.html

FFmpeg 视频添加水印图片相关推荐

  1. ffmpeg 视频添加水印 logo

    1.使用ffmpeg 视频添加水印(logo).在视频行业已经工作了两年多了,最近抽时间把一些处理视频的方法和经验写下来,一来做个记录,二来分享一下一起学习进步. ffmpeg -i D:\input ...

  2. Adobe Premiere视频添加水印图片教程,小白一看就会!

    最近在录制<微信小程序开发3天快速入门>和<微信小程序直播开发快速入门>的视频课程,就突发奇想:能否给视频加上标识水印呢?说干就干,对于视频编辑小白的我,只能网上搜索了. 首先 ...

  3. FFMpeg视频转图片

    FFmpeg 视频转图片流程: 1.找到视频流信息 2.初始化解码器上下文,并将流中解码器参数拷贝给解码器上下文 3.打开解码器 4.转换像素格式 5.循环读取视频数据包并对数据进行解码avcodec ...

  4. python ffmpeg 视频转图片 视频转音频 播放音频 多张图片+音频转视频 多个视频合成一个视频 改变视频播放速度

    文章目录 视频转图片 视频转音频 播放音频 图片+音频 转 视频 多个视频合成一个视频 改变视频播放速度 视频转图片 #!/usr/bin/env python # -*- encoding: utf ...

  5. 【ffmpeg】ffmpeg视频添加水印并解决字体问题Could not load font FreeSerif.ttf:cannot opencv resource

    解决字体问题Could not load font "FreeSerif.ttf":cannot opencv resource [root@localhost test]# ff ...

  6. ffmpeg视频处理教程

    1.ffmpeg简介 2.ffmpeg常用功能 3.ffmpeg的GPU加速 1.ffmpeg简介 FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或 ...

  7. PHP实现给视频加图片水印,怎么在视频画面上加图片?如何给视频添加自己的图片作为水印?视频添加图片水印的方法...

    今天就是周一啦,昨天周末大家有没有跟好友去玩呢~反正小编是跟同学聚会去了,聚会主题:胖.哈哈哈,不过小编可不跟他们一样只会长胖,小编可是瘦瘦瘦的呢,偷偷的告诉你们,小编减肥瘦了三十斤哦,嘻嘻.好啦,废 ...

  8. ffmpeg 给视频或者图片添加水印和马赛克的方法

    可以使用 FFmpeg 给视频或图片添加水印和马赛克.以下是具体方法: 添加水印 如果需要给视频添加水印,可以使用 overlay 滤镜.该滤镜将两个输入叠加在一起,即将视频和水印画面结合在一起.以下 ...

  9. FFmpeg实现将图片转换为视频

    ##名称:ffmpeg实现将图片转换为视频 ##平台:ubuntu(已经安装好了ffmpeg工具) ##日期:2017年12月10日 简介:因为学习需要,需要将连续图片转换成视频,昨天和今天早上用op ...

最新文章

  1. hyperledge工具-configtxgen
  2. 基本概念—回归、分类、聚类
  3. html+监听+页面滚动到底部,解决HTML5中滚动到底部的事件问题
  4. 2021年度抖音小红书美妆行业营销报告
  5. sql复制表定义及复制数据行
  6. Excel合并单元格基础注意事项(VSTO 2005)
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的药方中医管理系统
  8. python 列表为空报错_对比几段代码,看看你是 Python 菜鸟还是老鸟
  9. Java 数据库编程 ResultSet 的 使用方法
  10. python入门指南-Python 入门指南
  11. 线性代数之——正交矩阵和 Gram-Schmidt 正交化
  12. excel文件设置的工作表保护如何撤销
  13. 服务器工作室用什么系统,【小兵工作室】BING GHOST Server2003r2ee 服务器专用版
  14. 赛迪顾问《中国云计算市场研究年度报告》发布:“技术+服务”两手抓 华云数据成为云计算领域的重要核心厂商
  15. PS 照片,都是精华
  16. html中的字体样式
  17. Android安卓成品项目 购物商城系统源码apk
  18. 经济危机为什么也是创业良机?
  19. Node.js中的egg入门、egg路由、egg控制器、egg服务、egg中间件
  20. 【分块】铃铛计数问题

热门文章

  1. 通讯录版本1.0到3.0简易版
  2. ikbc 104键win键失效
  3. 使用 CNN 进行面部表情检测
  4. python3制作中文词云_Python_制作中文词云
  5. 个人网站真能转成商业网站,你能么?
  6. 贺新春丨大年初六 六六大顺
  7. Word Rotator‘s Distance——WRD算法应用
  8. 如何设置,QQ邮箱新版界面
  9. vhdl加法器和减法器_半减法器和全减法器的设计
  10. win7-32位系统SqlServer2014版本下载与安装