FFMPEG录屏(屏幕录像)并存储MP4视频演示代码

FFMPEG录屏(屏幕录像)并存储MP4视频示例 在开发FFMPEG音视频相关的,刚接触FFMPEG,遇到了很折磨人的问题,比如在录屏幕的时候,保存的视频文件播放的时候速度过快,相信很多新手也跟我

FFMPEG录屏(屏幕录像)并存储MP4视频示例

 在开发FFMPEG音视频相关的,刚接触FFMPEG,遇到了很折磨人的问题,比如在录屏幕的时候,保存的视频文件播放的时候速度过快,相信很多新手也跟我一样都会有出现这种问题,下面我用GDI截屏+H264编码存储MP4做例子[大神请绕过]


       现在来看看下面H264编码并存储为MP4的流程:


       1、avformat_alloc_output_context2 为输出视频格式分配媒体文件句柄;


        2、avcodec_find_encoder  找到对应视频编码器


        3、avcodec_alloc_context3 创建编码器上下文


        4、avformat_new_stream 根据媒体文件和指定的编码器创建一个视频输出流


        5、avcodec_open2 打开编码器


        6、avformat_write_header 为媒体文件句柄写入头信息


        7、avcodec_send_frame 发送一帧内容信息


        8、avcodec_receive_packet 从编码器里接收一帧编码后的内容


        9、av_write_frame  写入媒体文件


        10、av_write_trailer 录屏完输入文件尾


         11、释放内存


         根据上面流程,先完成步骤1-6:


    AVFormatContext* avFormCtx_Out;

AVCodecContext*  avCodecCtx_Out;

AVCodec*  avCodec;

AVStream* avStream;

AVFrame* frame;

AVPacket* packet;

 

int frameRate = 15;

int ret = 0;

char* filename = "out.mp4";

    //获取输出媒体文件句柄

ret = avformat_alloc_output_context2(&avFormCtx_Out, NULL, NULL, filename);

if (ret < 0)

{

printf("Init avformat object is faild! \n");

return 0;

}

    //找到输出媒体文件的编码类型对应的编码器

avCodec = avcodec_find_encoder(avFormCtx_Out->oformat->video_codec);

if (!avCodec)

{

printf("Init avCodec object is faild! \n");

return 0;

}

//获取对应编码器的上下文

avCodecCtx_Out = avcodec_alloc_context3(avCodec);

if (!avCodecCtx_Out)

{

printf("Init avCodecCtx_Out object is faild! \n");

return 0;

}

    //给输出媒体文件添加一个视频输出流

avStream = avformat_new_stream(avFormCtx_Out, avCodec);

if (!avStream)

{

printf("Init avStream object is faild! \n");

return 0;

}

//设置编码器参数

avCodecCtx_Out->flags |= AV_CODEC_FLAG_QSCALE;

avCodecCtx_Out->bit_rate = 4000000;

avCodecCtx_Out->rc_min_rate = 4000000;

avCodecCtx_Out->rc_max_rate = 4000000;

avCodecCtx_Out->bit_rate_tolerance = 4000000;

avCodecCtx_Out->time_base.den = frameRate;

avCodecCtx_Out->time_base.num = 1;

avCodecCtx_Out->width = width;

avCodecCtx_Out->height = height;

avCodecCtx_Out->gop_size = 12;

avCodecCtx_Out->max_b_frames = 0;

avCodecCtx_Out->thread_count = 4;

avCodecCtx_Out->pix_fmt = AV_PIX_FMT_YUV420P;

avCodecCtx_Out->codec_id = AV_CODEC_ID_H264;

avCodecCtx_Out->codec_type = AVMEDIA_TYPE_VIDEO;

 

av_opt_set(avCodecCtx_Out->priv_data, "b-pyramid", "none", 0);

av_opt_set(avCodecCtx_Out->priv_data, "preset", "superfast", 0);

av_opt_set(avCodecCtx_Out->priv_data, "tune", "zerolatency", 0);

 

if (avFormCtx_Out->oformat->flags & AVFMT_GLOBALHEADER)

avCodecCtx_Out->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    

    //打开对应编码器

ret = avcodec_open2(avCodecCtx_Out, avCodec, NULL);

if (ret < 0)

{

printf("Open avcodec is faild! \n");

return 0;

}

    //根据编码器上下文信息填充参数到输出流相关结构里

avcodec_parameters_from_context(avStream->codecpar, avCodecCtx_Out);

if (!(avFormCtx_Out->oformat->flags & AVFMT_NOFILE))

{

        //打开媒体文件

ret = avio_open(&avFormCtx_Out->pb, filename, AVIO_FLAG_WRITE);

if (ret < 0)

{

printf("Open file is faild! \n");

return 0;

}

}

    //写入文件头

ret = avformat_write_header(avFormCtx_Out, NULL);

if (ret < 0)

{

printf("write header is faild! \n");

return 0;

}

    

frame = av_frame_alloc();

if (!frame)

{

printf("Init frame is faild! \n");

return 0;

}

frame->format = AV_PIX_FMT_YUV420P;

frame->width = width;

frame->height = height;

 

LONG64 frameSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1);

BYTE* outbuffer = new BYTE[frameSize];

 

ret = av_image_fill_arrays(frame->data,

frame->linesize,

outbuffer,

AV_PIX_FMT_YUV420P,

width,

height, 1);

 

if (ret < 0)

{

printf("av_image_fill_arrays is faild! \n");

return 0;

}

packet = av_packet_alloc();

av_init_packet(packet);

if (!packet)

{

printf("packet is faild! \n");

return 0;

}

           下面开始截屏并编码存储:


    for (;;)

{

        //此处GDI截取的格式为bgr24

BYTE* frameimage = ccs->CaptureImage();

        //bgr24转yuv420

RGB24_TO_YUV420(frameimage, width, height, outbuffer);

        //设置视频PTS,每帧+1

frame->pkt_dts = frame->pts = frameNumber;

frame->pkt_duration = 0;

frame->pkt_pos = -1;

        //给编码器上下文输送一帧内容

ret = avcodec_send_frame(avCodecCtx_Out, frame);

if (ret < 0)

continue;

        //从编码器接收一帧内容

ret = avcodec_receive_packet(avCodecCtx_Out, packet);

if (ret < 0)

continue;

        

        //写入媒体文件

if (packet->size > 0)

{

av_write_frame(avFormCtx_Out, packet);

frameNumber++;

printf("录入第%d帧....\n", frameNumber);

}

        

if (frameNumber > 50)

break;

}

       到这里,录屏存储MP4文件基本上结束,运行程序后,VLC播放存储的MP4文件,发现播放速度太快了,打开播放器查看媒体信息,竟然没有帧率信息,上面编码器里参数设置的帧率是15帧,那这个帧率哪去了 呢?




没有正确的帧率,那播放器播放肯定不正确了。


通过代码跟踪,发现在添加的输出流里有time_base这个时间基参数初始为0,




在写入媒体文件头后,可以看到它的变化:




  媒体文件视频流时间基已经变为90000/1了,而编码器里设置的是15/1,两者之间明显不一样,两者都代表1秒内的刻度,好比古代的八两和现在的半斤,其实都是一样半斤,不过定义的标准不一样,古代的一两换成现在到底有多少两呢??我们换算一下,1/8  * 5 等于现代的0.625两,那么同样道理,我们编码器设置的帧率也要根据视频里的标准来,于是需要换算:


1/15 * 90000=6000,也就是说每一帧递增6000才是我们输出流需要的正确的PTS,我们修改以下代码:


//为了更直观的看不同标准的时间基换算

frame->pkt_dts = frame->pts = frameNumber * avCodecCtx_Out->time_base.num * avStream->time_base.den / (avCodecCtx_Out->time_base.den *  avStream->time_base.num);

//用FFMPEG自带的函数更方便

//frame->pkt_dts = frame->pts = av_rescale_q_rnd(frameNumber, avCodecCtx_Out->time_base, avStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

再次运行程序后打开文件播放,可以看到视频正常播放,查看编码器信息,也可以看到帧率了




 


源码链接:

                  【github】https://github.com/luodh/ffmpeg_record_screen



相关推荐

[!--temp.pl--]