0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于FFMPEG采集摄像头图像编码MP4视频+时间水印

嵌入式威廉希尔官方网站 来源:嵌入式威廉希尔官方网站 作者:嵌入式威廉希尔官方网站 2022-09-29 15:46 次阅读

基于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过滤器上下文失败AVFilterContextn");
		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过滤器上下文失败AVFilterContextn");
		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='%snIT_阿水'",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;inb_filters;i++)//查找使用的过滤器
	 {
		 AVFilterContext *filter_ctxn=filter_graph->filters[i];
		 printf("[%s %d]:filter_ctxn_name=%sn",__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_0n",__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,%dn",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.示例效果

  摄像头采集图像实时渲染:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASVRf6Zi_5rC0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center

5.完整示例

Gitee源码链接:https://gitee.com/it-a-shui/ffmpeg
CSDN源码链接:https://download.csdn.net/download/weixin_44453694/85084851

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 摄像头
    +关注

    关注

    59

    文章

    4840

    浏览量

    95655
  • ffmpeg
    +关注

    关注

    0

    文章

    46

    浏览量

    7398
收藏 人收藏

    评论

    相关推荐

    FFMPEG采集摄像头图像SDL渲染+MP4格式视频编码

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频
    的头像 发表于 09-29 15:36 2049次阅读

    关于摄像头图像采集问题

    `我用labview实现电脑摄像头图像采集,运行过程中出现问题了,截图如下,还请高手指教!谢谢啦`
    发表于 05-25 16:59

    现有MP4主控芯片boxchip f16和一u***摄像头怎么在MP4显示器成像

    本帖最后由 qwuiop7890123 于 2013-9-13 22:15 编辑 MP4主控芯片boxchipf16和一u***摄像头怎么在MP4显示器成像?需要什么开发软件,怎么编程?
    发表于 09-13 12:38

    摄像头采集图像处理

    就可以做到实际中图像处理并且做到无线传输。这样的图像采集处理功能在监控系统和在线检测都有很大的前景。 本作品是基于安芯一号SLH89F5162单片机,驱动并控制带FIFO的OV7670CMOS
    发表于 11-05 22:35

    【转载分享】USB摄像头采集图像

    `如果你有USB摄像头,就是随便的那种。平时QQ视频的就可以了(笔记本上自带的摄像头,也可以),那你就可以用LabVIEW进行图像采集了。注
    发表于 03-02 11:36

    【NanoPi NEO试用体验】USB摄像头

    (如:192.168.1.16:8080),即可看到摄像头采集的画面。如下图:图7接下来,通过USB摄像头录制一段视频。执行命令:ffmpeg
    发表于 11-20 14:44

    【FPGA DEMO】Lab 4摄像头HDMI显示(高速--HDMI&摄像头)

    `项目名称:摄像头HDMI显示。具体要求:摄像头采集视频图像数据通过HDMI实时显示。 系统设计:Perf-V开发板可以连接高速口——HD
    发表于 07-30 15:21

    请问CH32V307可以通过摄像头采集数据并保存为视频文件吗?

    CH32V307可以通过摄像头采集数据并保存为视频文件吗DVP摄像头常见的输出格式有MJPEG、YUV422、RGB565等。常见的MJPEG格式数据可以按照规则直接填充文件数据,成为
    发表于 05-12 09:08

    【飞凌OKA40i-C开发板试用体验】玩转FFmpeg

    对于1080p的视频压缩达到45fps,所以对USB摄像头视频压缩应该没有什么压力。三、FFmpeg性能测试FFmpeg有个benchma
    发表于 09-15 19:53

    【触觉智能 Purple Pi开发板试用】视频采集编码与推流开发

    的支持,视频采集方法,视频编码与网络实时视频传输的实现过程。## 一、测试USB摄像头驱动因为之
    发表于 10-11 01:40

    如何在OKMX6UL-C上利用摄像头图像采集

    要求在OKMX6UL-C(emmc版本)上利用摄像头图像采集视频采集,需要在LCD屏幕上将图像
    发表于 12-02 06:49

    通过OKA40i-C开发板来介绍FFmpeg的命令行工作方式

    。SDP文件并不需要手工编写,在ffmpeg运行时它会显示命令行所对应的SDP定义,如下图所示。5、FFmpeg转发USB摄像头视频流前面演示了将
    发表于 12-29 16:12

    mp4文件伪装摄像头画面

    电子小白,在网上苦苦寻求方案,请各位路过大侠指点: 主管交代,要弄一个Android设备。 能够用 mp4 视频文件伪装成摄像头画面,然后循环播放。 不知道能不能实现呢,请大家提供下思路。谢谢
    发表于 05-10 18:37

    【KV260视觉入门套件试用体验】2.PS端视频采集FFMPEG编码开发测试

    通过连接USB摄像头,顺利完成测试PS侧的视频采以及ffmpeg源码在本开发板上的编译过程,初步测试了视频采集
    发表于 09-11 00:52

    基于DirectShow的多摄像头视频采集

    1.为什么使用DirectShow 笔者使用的是两个USB摄像头,单摄像头视频采集使用OpenCV的VideoCapture类没有问题,但是双摄像头
    发表于 02-08 03:24 3301次阅读