I created a screen recorder with FFMPEG lib by referring this question .But the problem is that the recorded video is too fast.If i record screen for 20 seconds , I would get a video of less than 10 seconds.Video have full content but it is playing so fast that it will complete in 6 to 10 seconds. I tried by changing pts values of packets and frames to Elapsed time in millisecond ie.
outPacket->pts=timer->ElapsedTimeM()*90 and outFrame->pts=timer->ElapsedTimeM()*90
Now I am getting approximate correct length video (19 sec). But the frame rate of the video is 16 to 15fps only.But my expected frame rate is 30fps.
I counted the frames by counting no of times the while (av_read_frame(ifmt_ctx, av_pkt) >= 0)
loop is running and I got 40 times (for a 20 sec video) .I think all frames are not passing to the encoder because for a 20 sec video in 30fps it should contain (30*20)600 frames at least.
So my question is how can i produce a full length video with 30fps frame rate which can play at normal speed?
My UPDATED code is below
#define __STDC_CONSTANT_MACROS
#include<iostream>
#include <chrono>
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
using namespace std;
class Timer
{
chrono::time_point<chrono::system_clock> start;
public:
Timer() {};
void StartTimer()
{
start = chrono::system_clock::now();
};
__int64 ElapsedTime()
{
return chrono::duration_cast<chrono::seconds>(chrono::system_clock::now() - start).count();
}
__int64 ElapsedTimeM()
{
return chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - start).count();
}
};
int main(int argc, char** argv)
{
const char* out_filename = "new_out.mp4";
avdevice_register_all();
AVOutputFormat* ofmt = NULL;
AVInputFormat* ifmt = NULL;
AVFormatContext* ifmt_ctx = avformat_alloc_context();
AVFormatContext* ofmt_ctx = avformat_alloc_context();
AVCodecParameters* av_codec_par_in = avcodec_parameters_alloc();
AVCodecParameters* av_codec_par_out = avcodec_parameters_alloc();
AVCodecContext* avcodec_contx = NULL;
AVCodec* av_codec;
AVStream* video_stream = NULL;
av_codec_par_out->height = 600;
av_codec_par_out->width = 800;
av_codec_par_out->bit_rate = 40000;
av_codec_par_out->codec_id = AV_CODEC_ID_H264; //AV_CODEC_ID_MPEG4; //Try H.264 instead of MPEG4
av_codec_par_out->codec_type = AVMEDIA_TYPE_VIDEO;
av_codec_par_out->format = 0;
av_codec_par_out->sample_aspect_ratio.den = 4;
av_codec_par_out->sample_aspect_ratio.num = 6;
AVDictionary* options = NULL;
av_dict_set(&options, "framerate", "30", 0);
/* av_dict_set(&options, "offset_x", "20", 0);
av_dict_set(&options, "offset_y", "40", 0);*/
av_dict_set(&options, "video_size", "800x600", 0);
av_dict_set(&options, "probesize", "42M", 0);
av_dict_set(&options, "rtbufsize", "100M", 0);
av_dict_set(&options, "preset", "ultrafast", 0);
//int ret, i;
ifmt = av_find_input_format("gdigrab");
if (avformat_open_input(&ifmt_ctx, "desktop", ifmt, &options) < 0)
{
cout << "Error in opening file";
exit(1);
}
int VideoStreamIndx = -1;
avformat_find_stream_info(ifmt_ctx, NULL);
/* find the first video stream index . Also there is an API available to do the below operations */
for (int i = 0; i < (int)ifmt_ctx->nb_streams; i++) // find video stream position/index.
{
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
VideoStreamIndx = i;
break;
}
}
if (VideoStreamIndx == -1)
{
cout << "\nunable to find the video stream index. (-1)";
exit(1);
}
av_codec_par_in = ifmt_ctx->streams[VideoStreamIndx]->codecpar;
av_codec = avcodec_find_decoder(av_codec_par_in->codec_id);
if (av_codec == NULL)
{
cout << "\nunable to find the decoder";
exit(1);
}
avcodec_contx = avcodec_alloc_context3(av_codec);
//Consider using preset and crf
av_opt_set(avcodec_contx->priv_data, "preset", "ultrafast", 0);
//av_opt_set(avcodec_contx->priv_data, "crf", "18", 0);
if (avcodec_parameters_to_context(avcodec_contx, av_codec_par_in) < 0)
{
cout << "\nerror in converting the codec contexts";
exit(1);
}
//av_dict_set
int value = avcodec_open2(avcodec_contx, av_codec, NULL);//Initialize the AVCodecContext to use the given AVCodec.
if (value < 0)
{
cout << "\nunable to open the av codec";
exit(1);
}
value = 0;
ofmt = av_guess_format(NULL, out_filename, NULL);
if (!ofmt)
{
cout << "\nerror in guessing the video format. try with correct format";
exit(1);
}
avformat_alloc_output_context2(&ofmt_ctx, ofmt, NULL, out_filename);
if (!ofmt_ctx)
{
cout << "\nerror in allocating av format output context";
exit(1);
}
AVCodec* av_codec_out = avcodec_find_encoder(av_codec_par_out->codec_id);
if (av_codec_out == NULL)
{
cout << "\nunable to find the encoder";
exit(1);
}
video_stream = avformat_new_stream(ofmt_ctx, av_codec_out);
if (!video_stream)
{
cout << "\nerror in creating a av format new stream";
exit(1);
}
AVCodecContext* av_cntx_out;
av_cntx_out = avcodec_alloc_context3(av_codec_out);
if (!av_cntx_out)
{
cout << "\nerror in allocating the codec contexts";
exit(1);
}
if (avcodec_parameters_copy(video_stream->codecpar, av_codec_par_out) < 0)
{
cout << "\nCodec parameter canot copied";
exit(1);
}
if (avcodec_parameters_to_context(av_cntx_out, av_codec_par_out) < 0)
{
cout << "\nerror in converting the codec contexts";
exit(1);
}
//av_cntx_out->pix_fmt = AV_PIX_FMT_YUV420P;
av_cntx_out->gop_size = 30;//3; //Use I-Frame frame every second.
av_cntx_out->max_b_frames = 2;
av_cntx_out->time_base.num = 1;
av_cntx_out->time_base.den = 30;
// av_cntx_out->ticks_per_frame = 10;
value = avcodec_open2(av_cntx_out, av_codec_out, NULL);//Initialize the AVCodecContext to use the given AVCodec.
if (value < 0)
{
cout << "\nunable to open the av codec";
exit(1);
}
if (avcodec_contx->codec_id == AV_CODEC_ID_H264)
{
av_opt_set(av_cntx_out->priv_data, "preset", "ultrafast", 0);
}
avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_READ_WRITE);
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
{
av_cntx_out->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
if (avformat_write_header(ofmt_ctx, NULL) < 0)
{
cout << "\nerror in writing the header context";
exit(1);
}
AVPacket* av_pkt = av_packet_alloc();
//av_init_packet(av_pkt); //error C4996: 'av_init_packet': was declared deprecated
memset(av_pkt, 0, sizeof(AVPacket)); //???
AVFrame* av_frame = av_frame_alloc();
if (!av_frame)
{
cout << "\nunable to release the avframe resources";
exit(1);
}
AVFrame* outFrame = av_frame_alloc();//Allocate an AVFrame and set its fields to default values.
if (!outFrame)
{
cout << "\nunable to release the avframe resources for outframe";
exit(1);
}
av_frame->width = avcodec_contx->width;
av_frame->height = avcodec_contx->height;
av_frame->format = av_codec_par_in->format;
outFrame->width = av_cntx_out->width;
outFrame->height = av_cntx_out->height;
outFrame->format = av_codec_par_out->format;
av_frame_get_buffer(av_frame, 0);
av_frame_get_buffer(outFrame, 0);
SwsContext* swsCtx = sws_alloc_context();
if (sws_init_context(swsCtx, NULL, NULL) < 0)
{
cout << "\nUnable to Initialize the swscaler context sws_context.";
exit(1);
}
swsCtx = sws_getContext(avcodec_contx->width, avcodec_contx->height, avcodec_contx->pix_fmt,
av_cntx_out->width, av_cntx_out->height, av_cntx_out->pix_fmt,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (swsCtx == NULL)
{
cout << "\n Cannot allocate SWC Context";
exit(1);
}
int ii = 0;
int enc_packet_counter = 0; //Count encoded frames.
int no_frames = 100;
/* cout << "\nenter No. of frames to capture : ";
cin >> no_frames;*/
//int flag;
int frameFinished;
//int got_picture;
int frame_index = 0;
AVPacket* outPacket = av_packet_alloc();
Timer* timer = new Timer();
timer->StartTimer();
int j = 0;
while (av_read_frame(ifmt_ctx, av_pkt) >= 0)
{
/* if (ii++ == no_frames)
break;*/
int iElapsedtime = timer->ElapsedTimeM();
int pts = iElapsedtime * 90;
if (iElapsedtime >20000)
break;
/*cout << ii++ <<"\n";*/
int ret = avcodec_send_packet(avcodec_contx, av_pkt);
if (ret < 0)
{
printf("Error while sending packet");
}
frameFinished = true;
int response = 0;
response = avcodec_receive_frame(avcodec_contx, av_frame);
if (response < 0) //&& (response != AVERROR(EAGAIN)) && (response != AVERROR_EOF))
{
printf("Error while receiving frame from decoder");
frameFinished = false;
}
if (frameFinished)// Frame successfully decoded :)
{
//av_init_packet(outPacket); //error C4996: 'av_init_packet': was declared deprecated
memset(outPacket, 0, sizeof(AVPacket)); //???
outPacket->data = NULL; // packet data will be allocated by the encoder
outPacket->size = 0;
outPacket->pts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
//if (outPacket->dts != AV_NOPTS_VALUE)
// outPacket->dts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
outPacket->dts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
outPacket->duration =av_rescale_q(1, av_cntx_out->time_base, video_stream->time_base); //???
outFrame->pts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
outFrame->pkt_duration = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
enc_packet_counter++;
//Apply color space conversion from BGRA to YUV420p using sws_scale
////////////////////////////////////////////////////////////////
int sts = sws_scale(swsCtx, //struct SwsContext *c,
av_frame->data, //const uint8_t *const srcSlice[],
av_frame->linesize, //const int srcStride[],
0, //int srcSliceY,
av_frame->height, //int srcSliceH,
outFrame->data, //uint8_t *const dst[],
outFrame->linesize); //const int dstStride[]);
if (sts < 0)
{
printf("Error while executing sws_scale");
}
////////////////////////////////////////////////////////////////
int ret = 0;
//int i = 0;
do
{
//cout << i++ << "\n";
if (ret == AVERROR(EAGAIN))
{
av_packet_unref(outPacket);
ret = avcodec_receive_packet(av_cntx_out, outPacket);
if (ret) break; // deal with error
outPacket->duration = av_rescale_q(1, av_cntx_out->time_base, video_stream->time_base); //???
av_write_frame(ofmt_ctx, outPacket);
}
else if (ret != 0)
{
char str2[] = "";
cout << "\nError :" << av_make_error_string(str2, sizeof(str2), ret);
return -1;
}
ret = avcodec_send_frame(av_cntx_out, outFrame);
} while (ret);
} // frameFinished
av_packet_unref(av_pkt);
av_packet_unref(outPacket);
}
// flush the rest of the packets ???
////////////////////////////////////////////////////////////
int ret = 0;
//int i = 0;
avcodec_send_frame(av_cntx_out, NULL);
do
{
av_packet_unref(outPacket);
//cout << i++ << "\n";
ret = avcodec_receive_packet(av_cntx_out, outPacket);
if (!ret)
{
outPacket->pts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
outPacket->dts = av_rescale_q(enc_packet_counter, av_cntx_out->time_base, video_stream->time_base); //???
outPacket->duration = av_rescale_q(1, av_cntx_out->time_base, video_stream->time_base); //???
av_write_frame(ofmt_ctx, outPacket);
enc_packet_counter++;
}
} while (!ret);
////////////////////////////////////////////////////////////
value = av_write_trailer(ofmt_ctx);
if (value < 0)
{
cout << "\nerror in writing av trailer";
exit(1);
}
//THIS WAS ADDED LATER
/*av_free(video_outbuf);*/
avformat_close_input(&ifmt_ctx);
if (!ifmt_ctx)
{
cout << "\nfile closed successfully";
}
else
{
cout << "\nunable to close the file";
exit(1);
}
avformat_free_context(ifmt_ctx);
if (!ifmt_ctx)
{
cout << "\navformat free successfully";
}
else
{
cout << "\nunable to free avformat context";
exit(1);
}
//Free codec context.
////////////////////////////////////////////////////////////
avcodec_free_context(&av_cntx_out);
if (!av_cntx_out)
{
cout << "\navcodec free successfully";
}
else
{
cout << "\nunable to free avcodec context";
exit(1);
}
////////////////////////////////////////////////////////////
return 0;
}