基于FFMPEG采集摄像头图像编码MP4视频+时间水印
1.硬件平台
操作系统:Ubuntu18.04
ffmpeg版本:ffmpeg4.2.5
摄像头:电脑自带或USB免驱摄像头
水印处理:avfilter
图像渲染:SDL库
摄像头图像采集+MP4视频编码参考示例:https://blog.csdn.net/weixin_44453694/article/details/123885112
水印添加处理参数示例:https://blog.csdn.net/weixin_44453694/article/details/123909568
2.功能实现
本示例采样三个线程实现:
子线程1实现ffmpeg编解码器注册,设置图像格式,摄像头图像数据采集。
子线程2实现MP4视频格式编码。
主线程完成子线程创建,SDL库初始化,窗口创建,图像数据渲染。
通过ffmpeg自带avfilter库实现时间水印添加。
3.核心代码
3.1 水印处理函数
//添加水印
int 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空间*/AVFilterInOut *outputs=avfilter_inout_alloc();AVFilterInOut *inputs=avfilter_inout_alloc(); /*创建AVFilterGraph,分配空间*/AVFilterGraph *filter_graph;//对filters系统的整体管理结构体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);//图像宽高,格式,帧率,画面横纵比/*创建过滤器上下文,源数据AVFilterContext*/AVFilterContext *buffersrc_ctx;ret=avfilter_graph_create_filter(&buffersrc_ctx,buffersrc,"in",args,NULL,filter_graph);if(ret<0){printf("创建src过滤器上下文失败AVFilterContext\n");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){printf("创建sink过滤器上下文失败AVFilterContext\n");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=msyhbd.ttc:fontcolor=red:fontsize=25:x=50:y=20:text='%s\nIT_阿水'",str);if(avfilter_graph_parse_ptr(filter_graph,filter_desrc,&inputs,&outputs, NULL)<0)//设置过滤器数据内容{printf("添加字符串信息失败\n");return -3;}/*检测配置信息是否正常*/if(avfilter_graph_config(filter_graph,NULL)<0){printf("配置信息有误\n");return -4;} #if 0/*查找要在使用的过滤器,将要触处理的数据添加到过滤器注意:时间若从外面传入(即144行数据已完整),则此处不需要查找,直接添加即可,否则需要添加下面代码*/AVFilterContext* filter_ctx;//上下文int parsed_drawtext_0_index = -1;for(int i=0;i<filter_graph->nb_filters;i++)//查找使用的过滤器{AVFilterContext *filter_ctxn=filter_graph->filters[i];printf("[%s %d]:filter_ctxn_name=%s\n",__FUNCTION__,__LINE__,filter_ctxn->name);if(!strcmp(filter_ctxn->name,"Parsed_drawtext_0")){parsed_drawtext_0_index=i;}}if(parsed_drawtext_0_index==-1){printf("[%s %d]:no Parsed_drawtext_0\n",__FUNCTION__,__LINE__);//没有找到过滤器}filter_ctx=filter_graph->filters[parsed_drawtext_0_index];//保存找到的过滤器/*获取系统时间,将时间加入到过滤器*/char sys_time[64];time_t sec,sec2; 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); //24小时制}av_opt_set(filter_ctx->priv, "text", sys_time, 0 ); //设置text到过滤器#endif/*往源滤波器buffer中输入待处理数据*/if(av_buffersrc_add_frame(buffersrc_ctx,frame_in)<0){return -5;}/*从滤波器中输出处理数据*/if(av_buffersink_get_frame(buffersink_ctx, frame_out)<0){return -6;}avfilter_inout_free(&outputs);avfilter_inout_free(&inputs);avfilter_graph_free(&filter_graph);return 0;
}
3.2 读取一帧数据
读取一帧图像数据,进行图像解码,图像格式转换,添加时间水印。
static AVFrame *get_video_frame(OutputStream *ost,IntputDev* input, int *got_pic)
{int ret,got_picture;AVCodecContext *c=ost->enc;AVFrame *ret_frame=NULL;/*在各自的时基中比较两个时间戳。*/if(av_compare_ts(ost->next_pts,c->time_base,STREAM_DURATION, (AVRational){1,1})>=0){return (void*)-1;}/*确保帧数据可写,尽可能避免数据复制。*/if(av_frame_make_writable(ost->frame)<0){exit(1);}/*此函数返回文件中存储的内容,并且不验证是否存在解码器的有效帧。*/if(av_read_frame(input->v_ifmtCtx,input->in_packet)>=0){if(input->in_packet->stream_index == input->videoindex){/*解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame*/ret=avcodec_decode_video2(input->pcodecCtx, input->pFrame,&got_picture,input->in_packet);*got_pic=got_picture;if(ret<0){printf("Decode Error.\n");av_packet_unref(input->in_packet);return NULL;}if(got_picture){sws_scale(input->img_convert_ctx, (const unsigned char * const *)input->pFrame->data,input->pFrame->linesize,0,input->pcodecCtx->height,input->pFrameYUV->data,input->pFrameYUV->linesize);sws_scale(input->img_convert_ctx, (const unsigned char * const *)input->pFrame->data,input->pFrame->linesize,0,input->pcodecCtx->height,ost->frame->data,ost->frame->linesize);pthread_mutex_lock(&fastmutex);//互斥锁上锁memcpy(rgb_buff,input->pFrameYUV->data[0],size);pthread_cond_broadcast(&cond);//广播唤醒所有线程pthread_mutex_unlock(&fastmutex);//互斥锁解锁//ost->frame->pts=ost->next_pts++;//水印添加处理//frame->frame->format=AV_PIX_FMT_YUV420P;AVFrame *frame_out=av_frame_alloc();unsigned char *frame_buffer_out;frame_buffer_out=(unsigned char *)av_malloc(size);av_image_fill_arrays(frame_out->data,frame_out->linesize,frame_buffer_out,AV_PIX_FMT_YUV420P,width,height,32);//添加水印,调用libavfilter库实现time_t sec;sec=time(NULL);struct tm* today = localtime(&sec);char sys_time[64];strftime(sys_time, sizeof(sys_time), "%Y/%m/%d %H\\:%M\\:%S", today); waterMark(ost->frame,frame_out,width,height,sys_time);//yuv420p,y表示亮度,uv表示像素颜色ost->frame=frame_out;ost->frame->pts=ost->next_pts++;ret_frame=frame_out;}}av_packet_unref(input->in_packet);}return ret_frame;
}
3.3 SDL库图像渲染
int main()
{/*创建摄像头采集线程*/pthread_t pthid[2];pthread_create(&pthid[0],NULL,Video_CollectImage, NULL);pthread_detach(pthid[0]);/*设置分离属性*/sleep(1);while(1){if(width!=0 && height!=0 && size!=0)break;if(video_flag==0)return 0;}printf("image:%d * %d,%d\n",width,height,size);unsigned char *rgb_data=malloc(size);/*创建mp4视频编码线程*/pthread_create(&pthid[1],NULL,Video_savemp4, NULL);pthread_detach(pthid[1]);/*设置分离属性*//*创建窗口 */SDL_Window *window=SDL_CreateWindow("SDL_VIDEO", SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,800,480,SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_RESIZABLE);/*创建渲染器*/SDL_Renderer *render=SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED);/*清空渲染器*/SDL_RenderClear(render);/*创建纹理*/SDL_Texture*sdltext=SDL_CreateTexture(render,SDL_PIXELFORMAT_IYUV,SDL_TEXTUREACCESS_STREAMING,width,height);bool quit=true;SDL_Event event;SDL_Rect rect;int count=0;while(quit){while(SDL_PollEvent(&event))/*事件监测*/{if(event.type==SDL_QUIT)/*退出事件*/{quit=false;video_flag=0;pthread_cancel(pthid[1]);/*杀死指定线程*/pthread_cancel(pthid[0]);/*杀死指定线程*/continue;}else if(event.type == SDL_KEYDOWN){if(event.key.keysym.sym==SDLK_q)//按‘q’保存视频{count++;snprintf(file_name,sizeof(file_name),"%d.mp4",count);mp4_decode_stat=1;}}}if(!video_flag){quit=false;continue;}pthread_mutex_lock(&fastmutex);//互斥锁上锁pthread_cond_wait(&cond,&fastmutex);memcpy(rgb_data,rgb_buff,size);pthread_mutex_unlock(&fastmutex);//互斥锁解锁SDL_UpdateTexture(sdltext,NULL,rgb_data,width);//SDL_RenderCopy(render, sdltext, NULL,NULL); // 拷贝纹理到渲染器SDL_RenderCopyEx(render, sdltext,NULL,NULL,0,NULL,SDL_FLIP_NONE);SDL_RenderPresent(render); // 渲染}SDL_DestroyTexture(sdltext);/*销毁纹理*/SDL_DestroyRenderer(render);/*销毁渲染器*/SDL_DestroyWindow(window);/*销毁窗口 */SDL_Quit();/*关闭SDL*/pthread_mutex_destroy(&fastmutex);/*销毁互斥锁*/pthread_cond_destroy(&cond);/*销毁条件变量*/free(rgb_buff);free(rgb_data);return 0;
}
4.示例效果
摄像头采集图像实时渲染:
5.完整示例
Gitee源码链接:https://gitee.com/it-a-shui/ffmpeg
CSDN源码链接:https://download.csdn.net/download/weixin_44453694/85084851
基于FFMPEG采集摄像头图像编码MP4视频+时间水印相关推荐
- 在Ubuntu虚拟机使用ffmpeg采集摄像头的yuv视频数据
在Ubuntu虚拟机使用ffmpeg采集摄像头的yuv数据 使用命令从视频提取出yuv数据 提取yuv视频数据 单独提取视频的y分量或u.v分量 通过摄像头获取yuv数据 使用命令从视频提取出yuv数 ...
- FFMPEG采集摄像头图像SDL渲染+MP4格式视频编码
FFMPEG采集摄像头图像SDL渲染+MP4格式视频编码 FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音 ...
- html5视频抓取,js和HTML5基于过滤器从摄像头中捕获视频的方法
这篇文章主要介绍了js+HTML5基于过滤器从摄像头中捕获视频的方法,涉及javascript基于html5元素操作多媒体的使用技巧,需要的朋友可以参考下 本文实例讲述了js+HTML5基于过滤器从摄 ...
- ffmpeg 采集摄像头yuv数据
本文讲述在linux下,如何使用ffmpeg采集摄像头yuv数据,并保存为文件. 分为4个部分进行讲解 需要的软硬件环境 使用ffmpeg命令采集yuv数据. 使用ffmpeg代码采集yuv数据. 查 ...
- 基于opencv在摄像头ubuntu根据视频获取
基于opencv在摄像头ubuntu根据视频获取 1 工具 原料 平台 :UBUNTU12.04 安装库 Opencv-2.3 2 安装编译执行步骤 安装编译opencv-2.3 參考h ...
- C++编程FFMpeg实时美颜直播推流实战-基于ffmpeg,qt5,opencv视频课程-夏曹俊-专题视频课程...
C++编程FFMpeg实时美颜直播推流实战-基于ffmpeg,qt5,opencv视频课程-11788人已学习 课程介绍 C++编程FFMpeg实时美颜直播推流实战视频培训教程,本课程 ...
- 基于FFmpeg的封装格式MP4(TS)
一. 封装MP4原理: 每一帧音频或视频都有一个持续时间:duration: 采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数. .正常人听觉的频率范围大约在20Hz~20kHz之 ...
- linux下使用ffmpeg采集摄像头数据并编码成h264文件
本文讲述如何在linux下,使用ffmpeg采集视频数据,并编码成h264文件. 打算分成3部分讲解: 需要具备的软硬件环境 ffmpeg命令采集摄像头数据并编码成h264文件 ffmpeg代码采集摄 ...
- 怎么去除mp4视频的水印?教你去视频水印的方法
在观看视频的时候难免会有一些比较喜欢的视频想下载下来以便日后观看,但是很多视频都带有水印.对于很多强迫症来说视频带水印是难以忍受的,且如果想把一些有意思的视频分享到朋友圈或一些平台时视频中带有水印也是 ...
最新文章
- 刷新序号公共方法 公司内部用
- Win7 命令行下C语言学习环境搭建(三)
- 解决‘C:\Program‘ 不是内部或外部命令,也不是可运行的程序或批处理文件
- 信息学奥赛一本通(1208:2的幂次方表示)
- 用etcd实现服务注册和发现
- JAVA开发面试常问问题总结4
- ZooKeeper官方文档学习笔记03-程序员指南
- css模型安装,【CSS】CSS-框模型+定位+实训练习
- 2021苹果最新供应链名单公布
- java-时间间隔类period类和Duration类
- ADB识别失败,驱动显示感叹号解决方案——记录一次驱动重装导致的不识别手机问题
- 世界上第一台电子计算机的研制目的,最初研制电子计算机的目的
- BFS宽度优先搜索(新冠病毒的传播)
- Java程序员 面试如何介绍项目经验? Java程序员应该如何介绍自己的项目经验和自我介绍?面试如何突出自己
- 基于STM32设计的拼图小游戏
- springcloud2-注册中心eureka及nacos
- 用Python写的猜灯谜软件源代码
- 验证HTTP Referer字段
- 共治、共建、共享!龙蜥社区第 16 次运营委员会会议顺利召开!
- 中兴测试软件可以看sip信令吗,中兴光猫FTTH_SIP协议开通调试现场配置指导书