基于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í)渲染:
5.完整示例
Gitee源碼鏈接:https://gitee.com/it-a-shui/ffmpeg
CSDN源碼鏈接:https://download.csdn.net/download/weixin_44453694/85084851
-
攝像頭
+關(guān)注
關(guān)注
59文章
4793瀏覽量
95279 -
ffmpeg
+關(guān)注
關(guān)注
0文章
46瀏覽量
7372
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論