#pragma once
#include <config.h>
#include <stdio.h>
#include <iostream>
#include <stdexcept>
#define __STDC_CONSTANT_MACROS
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4242 4244)
#endif
#if __GNUC__ > 3
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#pragma GCC diagnostic ignored "-Wvariadic-macros"
#endif
extern "C"
{
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#if __GNUC__ > 3
#pragma GCC diagnostic pop
#endif
#include <utils/common/MsgHandler.h>
#include <utils/common/ToString.h>
class GUIVideoEncoder {
public:
GUIVideoEncoder(const char* const out_file, const int width, const int height, double frameDelay) {
avformat_alloc_output_context2(&myFormatContext, NULL, NULL, out_file);
if (myFormatContext == nullptr) {
throw ProcessError(TL("Unknown format!"));
}
int framerate = 25;
if (frameDelay > 0.) {
framerate = (int)(1000. / frameDelay);
if (framerate <= 0) {
framerate = 1;
}
}
AVStream* const video_st = avformat_new_stream(myFormatContext, 0);
video_st->time_base.num = 1;
video_st->time_base.den = framerate;
const AVCodec* codec = avcodec_find_encoder(myFormatContext->oformat->video_codec);
if (codec == nullptr) {
WRITE_WARNING(TL("Unknown codec, falling back to HEVC!"));
codec = avcodec_find_encoder_by_name("libx265");
}
if (codec == nullptr) {
throw ProcessError(TL("Unknown codec!"));
}
myCodecCtx = avcodec_alloc_context3(codec);
if (myCodecCtx == nullptr) {
throw ProcessError(TL("Could not allocate video codec context!"));
}
myCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
myCodecCtx->width = (width / 2) * 2;
myCodecCtx->height = (height / 2) * 2;
myCodecCtx->time_base.num = 1;
myCodecCtx->time_base.den = framerate;
myCodecCtx->framerate.num = framerate;
myCodecCtx->framerate.den = 1;
myCodecCtx->bit_rate = 4000000;
myCodecCtx->gop_size = 10;
if (myCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_opt_set(myCodecCtx->priv_data, "preset", "slow", 0);
}
if (myCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
av_opt_set(myCodecCtx->priv_data, "preset", "ultrafast", 0);
av_opt_set(myCodecCtx->priv_data, "tune", "zero-latency", 0);
}
if (avcodec_open2(myCodecCtx, codec, nullptr) < 0) {
throw ProcessError(TL("Could not open codec!"));
}
avcodec_parameters_from_context(video_st->codecpar, myCodecCtx);
myFrame = av_frame_alloc();
if (myFrame == nullptr) {
throw ProcessError(TL("Could not allocate video frame!"));
}
myFrame->format = myCodecCtx->pix_fmt;
myFrame->width = myCodecCtx->width;
myFrame->height = myCodecCtx->height;
if (av_frame_get_buffer(myFrame, 32) < 0) {
throw ProcessError(TL("Could not allocate the video frame data!"));
}
mySwsContext = sws_getContext(myCodecCtx->width, myCodecCtx->height, AV_PIX_FMT_RGBA,
myCodecCtx->width, myCodecCtx->height, AV_PIX_FMT_YUV420P,
0, 0, 0, 0);
if (avio_open(&myFormatContext->pb, out_file, AVIO_FLAG_WRITE) < 0) {
throw ProcessError(TL("Failed to open output file!"));
}
if (avformat_write_header(myFormatContext, nullptr) < 0) {
throw ProcessError(TL("Failed to write file header!"));
}
myFrameIndex = 0;
myPkt = av_packet_alloc();
if (myPkt == nullptr) {
throw ProcessError(TL("Could not allocate video packet!"));
}
}
~GUIVideoEncoder() {
int ret = 1;
if (!(myCodecCtx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
ret = 0;
}
if (avcodec_send_frame(myCodecCtx, nullptr) < 0) {
WRITE_WARNING(TL("Error sending final frame!"));
ret = -1;
}
while (ret >= 0) {
ret = avcodec_receive_packet(myCodecCtx, myPkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
WRITE_WARNING(TL("Error during final encoding step!"));
break;
}
ret = av_write_frame(myFormatContext, myPkt);
av_packet_unref(myPkt);
}
av_write_trailer(myFormatContext);
avio_closep(&myFormatContext->pb);
avcodec_free_context(&myCodecCtx);
av_frame_free(&myFrame);
av_packet_free(&myPkt);
avformat_free_context(myFormatContext);
}
void writeFrame(uint8_t* buffer) {
if (av_frame_make_writable(myFrame) < 0) {
throw ProcessError();
}
uint8_t* inData[1] = { buffer };
int inLinesize[1] = { 4 * myCodecCtx->width };
sws_scale(mySwsContext, inData, inLinesize, 0, myCodecCtx->height,
myFrame->data, myFrame->linesize);
myFrame->pts = myFrameIndex;
int r = avcodec_send_frame(myCodecCtx, myFrame);
if (r < 0) {
char errbuf[64];
av_strerror(r, errbuf, 64);
throw ProcessError(TL("Error sending frame for encoding!"));
}
int ret = 0;
while (ret >= 0) {
ret = avcodec_receive_packet(myCodecCtx, myPkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
throw ProcessError(TL("Error during encoding!"));
}
av_packet_rescale_ts(myPkt, myCodecCtx->time_base, myFormatContext->streams[0]->time_base);
myPkt->stream_index = 0;
ret = av_write_frame(myFormatContext, myPkt);
av_packet_unref(myPkt);
}
myFrameIndex++;
}
private:
AVFormatContext* myFormatContext;
SwsContext* mySwsContext;
AVCodecContext* myCodecCtx;
AVFrame* myFrame;
AVPacket* myPkt;
int myFrameIndex;
};