基于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