0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于FFMPEG采集攝像頭圖像編碼MP4視頻+時(shí)間水印

嵌入式技術(shù) ? 來(lái)源:嵌入式技術(shù) ? 作者:嵌入式技術(shù) ? 2022-09-29 15:46 ? 次閱讀

基于FFMPEG采集攝像頭圖像編碼MP4視頻+時(shí)間水印

1.硬件平臺(tái)

操作系統(tǒng):Ubuntu18.04
ffmpeg版本:ffmpeg4.2.5
攝像頭:電腦自帶或USB免驅(qū)攝像頭
水印處理:avfilter
圖像渲染:SDL庫(kù)

?攝像頭圖像采集+MP4視頻編碼參考示例:https://blog.csdn.net/weixin_44453694/article/details/123885112
水印添加處理參數(shù)示例:https://blog.csdn.net/weixin_44453694/article/details/123909568

2.功能實(shí)現(xiàn)

本示例采樣三個(gè)線程實(shí)現(xiàn):
?子線程1實(shí)現(xiàn)ffmpeg編解碼器注冊(cè),設(shè)置圖像格式,攝像頭圖像數(shù)據(jù)采集。
?子線程2實(shí)現(xiàn)MP4視頻格式編碼。
?主線程完成子線程創(chuàng)建,SDL庫(kù)初始化,窗口創(chuàng)建,圖像數(shù)據(jù)渲染。
通過(guò)ffmpeg自帶avfilter庫(kù)實(shí)現(xiàn)時(shí)間水印添加。

3.核心代碼

3.1 水印處理函數(shù)

//添加水印
int waterMark(AVFrame *frame_in,AVFrame *frame_out,int w,int h,const char *str)
{
	int ret;
	/*根據(jù)名字獲取ffmegding定義的filter*/
	const AVFilter *buffersrc=avfilter_get_by_name("buffer");//原始數(shù)據(jù)
	const AVFilter *buffersink=avfilter_get_by_name("buffersink");//處理后的數(shù)據(jù)
	/*動(dòng)態(tài)分配AVFilterInOut空間*/
	AVFilterInOut *outputs=avfilter_inout_alloc();
	AVFilterInOut *inputs=avfilter_inout_alloc();	
	/*創(chuàng)建AVFilterGraph,分配空間*/
	AVFilterGraph *filter_graph;//對(duì)filters系統(tǒng)的整體管理結(jié)構(gòu)體
	filter_graph = avfilter_graph_alloc();
	enum AVPixelFormat pix_fmts[]={AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE};//設(shè)置格式
	/*過(guò)濾器參數(shù):解碼器的解碼幀將被插入這里。*/
	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);//圖像寬高,格式,幀率,畫(huà)面橫縱比
	/*創(chuàng)建過(guò)濾器上下文,源數(shù)據(jù)AVFilterContext*/
	AVFilterContext *buffersrc_ctx;
	ret=avfilter_graph_create_filter(&buffersrc_ctx,buffersrc,"in",args,NULL,filter_graph);
	if(ret<0)
	{
		printf("創(chuàng)建src過(guò)濾器上下文失敗AVFilterContextn");
		return -1;
	}		
	/*創(chuàng)建過(guò)濾器上下文,處理后數(shù)據(jù)buffersink_params*/
	AVBufferSinkParams *buffersink_params;
	buffersink_params=av_buffersink_params_alloc();
	buffersink_params->pixel_fmts=pix_fmts;//設(shè)置格式
	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("創(chuàng)建sink過(guò)濾器上下文失敗AVFilterContextn");
		return -2;
	}	
	/*過(guò)濾器鏈輸入/輸出鏈接列表*/
	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};//要添加的水印數(shù)據(jù)
	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)//設(shè)置過(guò)濾器數(shù)據(jù)內(nèi)容
	{
		printf("添加字符串信息失敗n");
		return -3;
	}
	/*檢測(cè)配置信息是否正常*/
	if(avfilter_graph_config(filter_graph,NULL)<0)
	{
		printf("配置信息有誤n");
		return -4;
	}	
	#if 0
	/*
		查找要在使用的過(guò)濾器,將要觸處理的數(shù)據(jù)添加到過(guò)濾器
		注意:時(shí)間若從外面?zhèn)魅?即144行數(shù)據(jù)已完整),則此處不需要查找,直接添加即可,否則需要添加下面代碼
	*/
	AVFilterContext* filter_ctx;//上下文
	int parsed_drawtext_0_index = -1;
	 for(int i=0;inb_filters;i++)//查找使用的過(guò)濾器
	 {
		 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__);//沒(méi)有找到過(guò)濾器
	 }
	 filter_ctx=filter_graph->filters[parsed_drawtext_0_index];//保存找到的過(guò)濾器
	 
		/*獲取系統(tǒng)時(shí)間,將時(shí)間加入到過(guò)濾器*/
		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小時(shí)制
		}
		av_opt_set(filter_ctx->priv, "text", sys_time, 0 );  //設(shè)置text到過(guò)濾器
	 #endif

	/*往源濾波器buffer中輸入待處理數(shù)據(jù)*/
	 if(av_buffersrc_add_frame(buffersrc_ctx,frame_in)<0)
	 {
		return -5;
	 }
	 /*從濾波器中輸出處理數(shù)據(jù)*/
	 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 讀取一幀數(shù)據(jù)

??讀取一幀圖像數(shù)據(jù),進(jìn)行圖像解碼,圖像格式轉(zhuǎn)換,添加時(shí)間水印。

static AVFrame *get_video_frame(OutputStream *ost,IntputDev* input, int *got_pic)
{
	
	int ret,got_picture;
	AVCodecContext *c=ost->enc;
	AVFrame *ret_frame=NULL;
	/*在各自的時(shí)基中比較兩個(gè)時(shí)間戳。*/
	if(av_compare_ts(ost->next_pts,c->time_base,STREAM_DURATION, (AVRational){1,1})>=0)
	{
		return  (void*)-1;
	}
	/*確保幀數(shù)據(jù)可寫(xiě),盡可能避免數(shù)據(jù)復(fù)制。*/
	if(av_frame_make_writable(ost->frame)<0)
	{
		exit(1);
	}
	/*此函數(shù)返回文件中存儲(chǔ)的內(nèi)容,并且不驗(yàn)證是否存在解碼器的有效幀。*/
	if(av_read_frame(input->v_ifmtCtx,input->in_packet)>=0)
	{
		if(input->in_packet->stream_index == input->videoindex)
		{
			/*解碼一幀視頻數(shù)據(jù)。輸入一個(gè)壓縮編碼的結(jié)構(gòu)體AVPacket,輸出一個(gè)解碼后的結(jié)構(gòu)體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);
				//添加水印,調(diào)用libavfilter庫(kù)實(shí)現(xiàn)
				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庫(kù)圖像渲染

int main()
{
	/*創(chuàng)建攝像頭采集線程*/
	pthread_t pthid[2];
    pthread_create(&pthid[0],NULL,Video_CollectImage, NULL);
	pthread_detach(pthid[0]);/*設(shè)置分離屬性*/
	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);
	/*創(chuàng)建mp4視頻編碼線程*/
	pthread_create(&pthid[1],NULL,Video_savemp4, NULL);
	pthread_detach(pthid[1]);/*設(shè)置分離屬性*/
 	/*創(chuàng)建窗口 */
	SDL_Window *window=SDL_CreateWindow("SDL_VIDEO", SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,800,480,SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_RESIZABLE);
    /*創(chuàng)建渲染器*/
	SDL_Renderer *render=SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED);
	/*清空渲染器*/
	SDL_RenderClear(render);
   /*創(chuàng)建紋理*/
	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))/*事件監(jiān)測(cè)*/
		{
			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);/*銷(xiāo)毀紋理*/
    SDL_DestroyRenderer(render);/*銷(xiāo)毀渲染器*/
    SDL_DestroyWindow(window);/*銷(xiāo)毀窗口 */
    SDL_Quit();/*關(guān)閉SDL*/
    pthread_mutex_destroy(&fastmutex);/*銷(xiāo)毀互斥鎖*/
    pthread_cond_destroy(&cond);/*銷(xiāo)毀條件變量*/
	free(rgb_buff);
	free(rgb_data);
	return 0;
}

4.示例效果

??攝像頭采集圖像實(shí)時(shí)渲染:

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

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 攝像頭
    +關(guān)注

    關(guān)注

    59

    文章

    4793

    瀏覽量

    95279
  • ffmpeg
    +關(guān)注

    關(guān)注

    0

    文章

    46

    瀏覽量

    7372
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    FFMPEG采集攝像頭圖像SDL渲染+MP4格式視頻編碼

    FFmpeg是一套可以用來(lái)記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開(kāi)源計(jì)算機(jī)程序。采用LGPL或GPL許可證。它提供了錄制、轉(zhuǎn)換以及流化音視頻的完整解決方案。它包含了非常先進(jìn)的音頻/視頻
    的頭像 發(fā)表于 09-29 15:36 ?1951次閱讀

    關(guān)于攝像頭圖像采集問(wèn)題

    `我用labview實(shí)現(xiàn)電腦攝像頭圖像采集,運(yùn)行過(guò)程中出現(xiàn)問(wèn)題了,截圖如下,還請(qǐng)高手指教!謝謝啦`
    發(fā)表于 05-25 16:59

    現(xiàn)有MP4主控芯片boxchip f16和一u***攝像頭怎么在MP4顯示器成像

    本帖最后由 qwuiop7890123 于 2013-9-13 22:15 編輯 MP4主控芯片boxchipf16和一u***攝像頭怎么在MP4顯示器成像?需要什么開(kāi)發(fā)軟件,怎么編程?
    發(fā)表于 09-13 12:38

    攝像頭采集圖像處理

    就可以做到實(shí)際中圖像處理并且做到無(wú)線傳輸。這樣的圖像采集處理功能在監(jiān)控系統(tǒng)和在線檢測(cè)都有很大的前景。 本作品是基于安芯一號(hào)SLH89F5162單片機(jī),驅(qū)動(dòng)并控制帶FIFO的OV7670CMOS
    發(fā)表于 11-05 22:35

    【轉(zhuǎn)載分享】USB攝像頭采集圖像

    `如果你有USB攝像頭,就是隨便的那種。平時(shí)QQ視頻的就可以了(筆記本上自帶的攝像頭,也可以),那你就可以用LabVIEW進(jìn)行圖像采集了。注
    發(fā)表于 03-02 11:36

    【NanoPi NEO試用體驗(yàn)】USB攝像頭

    (如:192.168.1.16:8080),即可看到攝像頭采集的畫(huà)面。如下圖:圖7接下來(lái),通過(guò)USB攝像頭錄制一段視頻。執(zhí)行命令:ffmpeg
    發(fā)表于 11-20 14:44

    【FPGA DEMO】Lab 4攝像頭HDMI顯示(高速--HDMI&攝像頭)

    `項(xiàng)目名稱:攝像頭HDMI顯示。具體要求:攝像頭采集視頻圖像數(shù)據(jù)通過(guò)HDMI實(shí)時(shí)顯示。 系統(tǒng)設(shè)計(jì):Perf-V開(kāi)發(fā)板可以連接高速口——HD
    發(fā)表于 07-30 15:21

    請(qǐng)問(wèn)CH32V307可以通過(guò)攝像頭采集數(shù)據(jù)并保存為視頻文件嗎?

    CH32V307可以通過(guò)攝像頭采集數(shù)據(jù)并保存為視頻文件嗎DVP攝像頭常見(jiàn)的輸出格式有MJPEG、YUV422、RGB565等。常見(jiàn)的MJPEG格式數(shù)據(jù)可以按照規(guī)則直接填充文件數(shù)據(jù),成為
    發(fā)表于 05-12 09:08

    【飛凌OKA40i-C開(kāi)發(fā)板試用體驗(yàn)】玩轉(zhuǎn)FFmpeg

    對(duì)于1080p的視頻壓縮達(dá)到45fps,所以對(duì)USB攝像頭視頻壓縮應(yīng)該沒(méi)有什么壓力。三、FFmpeg性能測(cè)試FFmpeg有個(gè)benchma
    發(fā)表于 09-15 19:53

    【觸覺(jué)智能 Purple Pi開(kāi)發(fā)板試用】視頻采集編碼與推流開(kāi)發(fā)

    的支持,視頻采集方法,視頻編碼與網(wǎng)絡(luò)實(shí)時(shí)視頻傳輸?shù)膶?shí)現(xiàn)過(guò)程。## 一、測(cè)試USB攝像頭驅(qū)動(dòng)因?yàn)橹?/div>
    發(fā)表于 10-11 01:40

    如何在OKMX6UL-C上利用攝像頭圖像采集

    要求在OKMX6UL-C(emmc版本)上利用攝像頭圖像采集、視頻采集,需要在LCD屏幕上將圖像
    發(fā)表于 12-02 06:49

    通過(guò)OKA40i-C開(kāi)發(fā)板來(lái)介紹FFmpeg的命令行工作方式

    。SDP文件并不需要手工編寫(xiě),在ffmpeg運(yùn)行時(shí)它會(huì)顯示命令行所對(duì)應(yīng)的SDP定義,如下圖所示。5、FFmpeg轉(zhuǎn)發(fā)USB攝像頭視頻流前面演示了將
    發(fā)表于 12-29 16:12

    mp4文件偽裝攝像頭畫(huà)面

    電子小白,在網(wǎng)上苦苦尋求方案,請(qǐng)各位路過(guò)大俠指點(diǎn): 主管交代,要弄一個(gè)Android設(shè)備。 能夠用 mp4 視頻文件偽裝成攝像頭畫(huà)面,然后循環(huán)播放。 不知道能不能實(shí)現(xiàn)呢,請(qǐng)大家提供下思路。謝謝
    發(fā)表于 05-10 18:37

    【KV260視覺(jué)入門(mén)套件試用體驗(yàn)】2.PS端視頻采集FFMPEG編碼開(kāi)發(fā)測(cè)試

    通過(guò)連接USB攝像頭,順利完成測(cè)試PS側(cè)的視頻采以及ffmpeg源碼在本開(kāi)發(fā)板上的編譯過(guò)程,初步測(cè)試了視頻采集
    發(fā)表于 09-11 00:52

    基于DirectShow的多攝像頭視頻采集

    1.為什么使用DirectShow 筆者使用的是兩個(gè)USB攝像頭,單攝像頭視頻采集使用OpenCV的VideoCapture類(lèi)沒(méi)有問(wèn)題,但是雙攝像頭
    發(fā)表于 02-08 03:24 ?3246次閱讀