在开发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));
再次运行程序后打开文件播放,可以看到视频正常播放,查看编码器信息,也可以看到帧率了
源码链接:【CSDN】https://download.csdn.net/download/donghui_luo/11260185
【github】https://github.com/luodh/ffmpeg_record_screen