赞
踩
操作系统: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
本示例采样三个线程实现:
子线程1实现ffmpeg编解码器注册,设置图像格式,摄像头图像数据采集。
子线程2实现MP4视频格式编码。
主线程完成子线程创建,SDL库初始化,窗口创建,图像数据渲染。
通过ffmpeg自带avfilter库实现时间水印添加。
//添加水印 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; }
读取一帧图像数据,进行图像解码,图像格式转换,添加时间水印。
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; }
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; }
摄像头采集图像实时渲染:
Gitee源码链接:https://gitee.com/it-a-shui/ffmpeg
CSDN源码链接:https://download.csdn.net/download/weixin_44453694/85084851
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。