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帧,那这个帧率哪去了 呢?

FFMPEG简单录屏并存储MP4视频_第1张图片

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

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

FFMPEG简单录屏并存储MP4视频_第2张图片

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

FFMPEG简单录屏并存储MP4视频_第3张图片

  媒体文件视频流时间基已经变为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));

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

FFMPEG简单录屏并存储MP4视频_第4张图片

 

源码链接:【CSDN】https://download.csdn.net/download/donghui_luo/11260185

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

你可能感兴趣的:(FFMPEG)