#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #define V_WIDTH 640 #define V_HEIGHT 480 AVFormatContext* open_dev(){ char* devicename = "/dev/video0"; //设备文件描述符 char errors[1024]; int ret; AVFormatContext* fmt_ctx=NULL; //格式上下文获取-----av_read_frame获取packet AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 //获取输入(采集)格式 iformat = av_find_input_format("video4linux2"); //驱动,用来录制视频 //设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480 -framerate 15 -i /dev/video0 out.yuv av_dict_set(&options,"video_size","640*480",0); av_dict_set(&options,"framerate","30",0); av_dict_set(&options,"pixel_format","yuyv422",0); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); //----打开输入设备,初始化格式上下文和选项 if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s\n",ret,errors); return NULL; } av_log(NULL,AV_LOG_INFO,"Success to open video device\n"); return fmt_ctx; } //作用:编码,将yuv420转H264 AVCodecContext* open_encoder(int width,int height){ //------1.打开编码器 AVCodec* codec = avcodec_find_encoder_by_name("libx264"); if(!codec){ av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder\n"); return NULL; } //------2.创建上下文 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); if(!codec_ctx){ av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder context\n"); return NULL; } //------3.设置上下文参数 //SPS/PPS codec_ctx->profile = FF_PROFILE_H264_HIGH_444; //main分支最高级别编码 codec_ctx->level = 50; //表示level级别是5.0;支持最大分辨率2560×1920 //分辨率 codec_ctx->width = width; //设置分辨率--宽度 codec_ctx->height = height; //设置分辨率--高度 //GOP codec_ctx->gop_size = 250; //设置GOP个数,根据业务处理; codec_ctx->keyint_min = 25; //(option)如果GOP过大,我们就在中间多设置几个I帧,使得避免卡顿。这里表示在一组GOP中,最小插入I帧的间隔 //B帧(增加压缩比,降低码率) codec_ctx->has_b_frames = 1; //(option)标志是否允许存在B帧 codec_ctx->max_b_frames = 3; //(option)设置中间连续B帧的最大个数 //参考帧(越大,还原性越好,但是压缩慢) codec_ctx->refs = 3; //(option)设置参考帧最大数量,缓冲队列 //要进行编码的数据的原始数据格式(输入的原始数据) codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P; //注意:我们如果不进行转换yuyv422为yuv420的话,这里直接设置为AV_PIX_FMT_YUYV422P即可 //设置码率 codec_ctx->bit_rate = 600000; //设置平均码率600kpbs(根据业务) //设置帧率 codec_ctx->time_base = (AVRational){1,25}; //时间基,为帧率的倒数;帧与帧之间的间隔 codec_ctx->framerate = (AVRational){25,1}; //帧率,每秒25帧 //------4.打开编码器 if(avcodec_open2(codec_ctx,codec,NULL)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to open libx264 context\n"); avcodec_free_context(&codec_ctx); return NULL; } return codec_ctx; } static AVFrame* initFrame(int width,int height){ int ret; AVFrame* frame = av_frame_alloc(); //分配frame空间,但是数据真正被存放在buffer中 if(!frame){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n"); return NULL; } //主要是设置分辨率,用来分配空间 frame->width = width; frame->height = height; frame->format = AV_PIX_FMT_YUV420P; ret = av_frame_get_buffer(frame,32); //第二个参数是对齐,对于音频,我们直接设置0,视频中必须为32位对齐 if(ret<0){ //内存分配出错 av_log(NULL,AV_LOG_ERROR,"Failed to alloc frame buffer\n"); av_frame_free(&frame); return NULL; } return frame; } //开始进行编码操作 static void encode(AVCodecContext* enc_ctx,AVFrame* frame,AVPacket* newpkt,FILE* encfp){ int len=0; int ret = avcodec_send_frame(enc_ctx,frame); //将frame交给编码器进行编码;内部会将一帧数据挂到编码器的缓冲区 while(ret>=0){ //只有当frame被放入缓冲区之后(数据设置成功),并且下面ret表示获取缓冲区数据完成后才退出循环。因为可能一个frame对应1或者多个packet,或者多个frame对应1个packet ret = avcodec_receive_packet(enc_ctx,newpkt); //从编码器中获取编码后的packet数据,处理多种情况 if(ret<0){ if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF){ //编码数据不足或者到达文件尾部 return; }else{ //编码器出错 av_log(NULL,AV_LOG_ERROR,"avcodec_receive_packet error! [%d] %s\n",ret,av_err2str(ret)); return; } } len = fwrite(newpkt->data,1,newpkt->size,encfp); fflush(encfp); if(len!=newpkt->size){ av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d\n",len,newpkt->size); }else{ av_log(NULL,AV_LOG_INFO,"Success write newpkt to file\n"); } } } void rec_video(){ char errors[1024]; int ret,count=0,len,i,j,y_idx,u_idx,v_idx,base_h,base=0; AVFormatContext* fmt_ctx = NULL; AVCodecContext* enc_ctx = NULL; AVFrame* fmt = NULL; AVPacket packet; //打开文件,存放转换为yuv420的数据 FILE* fp = fopen("./video.yuv","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //打开文件,存放编码后数据(其实上面没必要存在) FILE* encfp = fopen("./video.h264","wb"); if(encfp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out H264 file\n"); goto fail; } //打开摄像头设备的上下文格式 fmt_ctx = open_dev(); if(!fmt_ctx) goto fail; //打开编码上下文 enc_ctx = open_encoder(V_WIDTH,V_HEIGHT); if(!enc_ctx) goto fail; //创建AVFrame AVFrame* frame = initFrame(V_WIDTH,V_HEIGHT); //创建AVPacket AVPacket* newpkt = av_packet_alloc(); if(!newpkt){ av_log(NULL,AV_LOG_ERROR,"Failed to alloc avpacket\n"); goto fail; } //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //------先将YUYV422数据转YUV420数据(重点) //序列为YU YV YU YV,一个yuv422帧的长度 width * height * 2 个字节 //丢弃偶数行 u v //先存放Y数据 memset(frame->data[0],0,V_WIDTH*V_HEIGHT*sizeof(char)); for(i=0,y_idx=0;i<2*V_HEIGHT*V_WIDTH;i+=2){ frame->data[0][y_idx++]=packet.data[i]; } //再获取U、V数据 memset(frame->data[1],0,V_WIDTH*V_HEIGHT*sizeof(char)/4); memset(frame->data[2],0,V_WIDTH*V_HEIGHT*sizeof(char)/4); for(i=0,u_idx=0,v_idx=0;i<V_HEIGHT;i+=2){ //丢弃偶数行,注意:i<V_HEIGHT*2,总数据量是Y+UV,可以达到V_HEIGHT*2行 base_h = i*2*V_WIDTH; //获取奇数行开头数据位置 for(j=0;j<V_WIDTH*2;j+=4){ //遍历这一行数据,每隔4个为1组 y u y v frame->data[1][u_idx++] = packet.data[base_h+j+1]; //获取U数据 frame->data[2][v_idx++] = packet.data[base_h+j+3]; //获取V数据 } } //写入yuv420数据 fwrite(frame->data[0],1,V_WIDTH*V_HEIGHT,fp); fwrite(frame->data[1],1,V_WIDTH*V_HEIGHT/4,fp); fwrite(frame->data[2],1,V_WIDTH*V_HEIGHT/4,fp); //开始编码 frame->pts = base++; //重点:对帧的pts进行顺序累加;不能设置随机值;H264要求编码的帧的pts是连续的值 encode(enc_ctx,frame,newpkt,encfp); //释放空间 av_packet_unref(&packet); } encode(enc_ctx,NULL,newpkt,encfp); //告诉编码器编码结束,将后面剩余的数据全部写入即可 fail: if(fp) fclose(fp); if(encfp) fclose(encfp); if(frame) av_frame_free(&frame); if(newpkt) av_packet_free(&newpkt); //关闭设备、释放上下文空间 if(enc_ctx) avcodec_free_context(&enc_ctx); avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); rec_video(); return 0; }