基于ffmpeg的视频帧编码 我试图用C++在虚幻引擎4中编码一段视频。我可以访问单独的框架。下面是读取视口的显示像素并存储在缓冲区中的代码 //Safely get render target resource. FRenderTarget* RenderTarget = TextureRenderTarget->GameThread_GetRenderTargetResource(); FIntPoint Size = RenderTarget->GetSizeXY(); auto ImageBytes = Size.X* Size.Y * static_cast<int32>(sizeof(FColor)); TArray<uint8> RawData; RawData.AddUninitialized(ImageBytes); //Get image raw data. if (!RenderTarget->ReadPixelsPtr((FColor*)RawData.GetData())) { RawData.Empty(); UE_LOG(ExportRenderTargetBPFLibrary, Error, TEXT("ExportRenderTargetAsImage: Failed to get raw data.")); return false; } Buffer::getInstance().add(RawData);

基于ffmpeg的视频帧编码 我试图用C++在虚幻引擎4中编码一段视频。我可以访问单独的框架。下面是读取视口的显示像素并存储在缓冲区中的代码 //Safely get render target resource. FRenderTarget* RenderTarget = TextureRenderTarget->GameThread_GetRenderTargetResource(); FIntPoint Size = RenderTarget->GetSizeXY(); auto ImageBytes = Size.X* Size.Y * static_cast<int32>(sizeof(FColor)); TArray<uint8> RawData; RawData.AddUninitialized(ImageBytes); //Get image raw data. if (!RenderTarget->ReadPixelsPtr((FColor*)RawData.GetData())) { RawData.Empty(); UE_LOG(ExportRenderTargetBPFLibrary, Error, TEXT("ExportRenderTargetAsImage: Failed to get raw data.")); return false; } Buffer::getInstance().add(RawData);,c++,ffmpeg,video-encoding,unreal-engine4,C++,Ffmpeg,Video Encoding,Unreal Engine4,有人能解释一下翻转帧部分(为什么这样做?)以及如何使用avcodec\u encode\u video2功能而不是avcodec\u encode\u video 不仅avcodec\u encode\u video已经过时,avcodec\u encode\u video2也被贴上了弃用标签。您现在应该使用新的avcodec\u发送\u帧和avcodec\u接收\u数据包进行编码 “翻转”部分对编码没有任何好处,我强烈建议不要在代码中这样做。如果您发现输出大小不正确,只需将swscaleint

有人能解释一下翻转帧部分(为什么这样做?)以及如何使用
avcodec\u encode\u video2
功能而不是
avcodec\u encode\u video

不仅
avcodec\u encode\u video
已经过时,
avcodec\u encode\u video2
也被贴上了弃用标签。您现在应该使用新的
avcodec\u发送\u帧
avcodec\u接收\u数据包
进行编码

“翻转”部分对编码没有任何好处,我强烈建议不要在代码中这样做。如果您发现输出大小不正确,只需将
swscale
interpolation algorithm标志切换到
SWS\u-ACCURATE\r\d

除了旧的
avcodec\u encode\u video
API之外,还有几个潜在风险:

  • 要使用H264编码器,请使用
    AV\u CODEC\u ID\u H264
    ,而不是
    AV\u CODEC\u ID\u MPEG1VIDEO
    ,ffmpeg LIB也应使用
    libx264
    构建。
    • 或者,如果您有一个支持NVNC的nvidia卡,那么通过名称(“h264 NVNC”)查找avcodec编码器将更好
  • delete FileHandle
    执行两次
  • avpicture…
    函数长期以来一直被弃用。改用其他函数
如果性能非常关键,则将所有编码过程移动到独立线程,而不是游戏线程

在我的自定义
GameViewportClient
类中,我有一些编码UE4视口输出的代码,这些代码类似于ffmpeg官方
muxing
encode_video
示例

MyGameViewportClient.h:

UCLASS(Config=Game)
class FUSIONCUT_API UMyGameViewportClient : public UGameViewportClient
{
    GENERATED_BODY()

public:
    virtual void Draw(FViewport* Viewport, FCanvas* SceneCanvas) override;

    void FirstTimeInit();

    void InitCodec();

    void TidyUp();

    void SetAutoRecording(bool val);
    void RecordNextFrame();
    bool CanRecordNextFrame();
    void SetRecording(bool val);
    void SetLevelDelay(int32 delay);

    void SetOver(bool val);
    void SetAbandon(bool val);
    void SetFilePath(FString out_file);
    void SetThumbnail(FString thumbnail_file, int32 thumbnail_frame);
    void SaveThumbnailImage();

private:
    UPROPERTY(Config)
    FString DeviceNum;

    UPROPERTY(Config)
    FString H264Crf;

    UPROPERTY(Config)
    int DeviceIndex;

    UPROPERTY()
    UFunction* ProgressFunc;

    UPROPERTY()
    UFunction* FinishFunc;

    FIntPoint ViewportSize;
    int count;

    TArray<FColor> ColorBuffer;
    TArray<uint8> IMG_Buffer;

    struct OutputStream {
        AVStream* Stream;
        AVCodecContext* Ctx;

        int64_t NextPts;

        AVFrame* Frame;

        struct SwsContext* SwsCtx;
    };

    OutputStream VideoSt = { 0 };
    AVOutputFormat* Fmt;
    AVFormatContext* FmtCtx;
    AVCodec* VideoCodec;
    AVDictionary* Opt = nullptr;
    SwsContext* SwsCtx;
    AVPacket Pkt;

    int GotOutput;
    int InLineSize[1];

    bool Start;
    bool Over;
    bool FirstTime;
    bool Abandon;
    bool AutoRecording;
    bool RecordingNextFrame;
    double LastSendingTime;
    std::string FilePath;
    FString UEFilePath;
    int32 LevelDelay;

    void EncodeAndWrite();

    void CaptureFrame();
    void AddStream(enum AVCodecID CodecID);
    void OpenVideo();
    int WriteFrame(bool need_save_thumbnail = true);
    void CloseStream();
    void AllocPicture();

    int FFmpegEncode(AVFrame *frame);
};
UCLASS(配置=游戏)
类FUSIONCUT_API UMyGameViewportClient:公共UGameViewportClient
{
生成的_BODY()
公众:
虚拟虚空绘制(FViewport*视口、FCanvas*场景视图)覆盖;
void FirstTimeInit();
void InitCodec();
void TidyUp();
无效设置自动记录(布尔值);
void RecordNextFrame();
bool CanRecordNextFrame();
无效设置记录(布尔值);
无效SetLevelDelay(int32延迟);
无效设置(布尔值);
无效设置放弃(布尔值);
void SetFilePath(FString out_文件);
void set缩略图(FString缩略图文件,int32缩略图帧);
void SaveThumbnailImage();
私人:
upperty(配置)
FString设备;
upperty(配置)
FString H264Crf;
upperty(配置)
int设备索引;
连根拔起
UFunction*ProgressFunc;
连根拔起
UFunction*FinishFunc;
FIntPoint视口大小;
整数计数;
焦油色缓冲液;
TArray IMG_缓冲器;
结构输出流{
AVStream*流;
AVCodecContext*Ctx;
int64_t NextPts;
AVFrame*Frame;
结构SwsContext*SwsCtx;
};
OutputStream VideoSt={0};
AVOutputFormat*Fmt;
AVFormatContext*FmtCtx;
AVCodec*视频编解码器;
AVDictionary*Opt=nullptr;
SwsContext*SwsCtx;
AVPacket-Pkt;
整数输出;
int InLineSize[1];
bool启动;
结束;
布尔第一次;
放弃;
布尔自动记录;
bool记录下一帧;
双上次发送时间;
std::字符串文件路径;
FString-UEFilePath;
int32电平延迟;
void EncodeAndWrite();
void CaptureFrame();
void AddStream(enum AVCodecID CodecID);
void OpenVideo();
int WriteFrame(bool需要保存缩略图=true);
void CloseStream();
无效分配结构();
int-FFmpegEncode(AVFrame*frame);
};
MyGameViewportClient.cpp:

void UMyGameViewportClient::InitCodec()
{
    ViewportSize = Viewport->GetSizeXY();

    av_register_all();
    avformat_alloc_output_context2(&FmtCtx, nullptr, nullptr, FilePath.c_str());
    if (!FmtCtx)
    {
        UE_LOG(LogTemp, Error, TEXT("cannot alloc format context"));
        return;
    }
    Fmt = FmtCtx->oformat;

    //auto codec_id = AV_CODEC_ID_H264;
    const char codec_name[32] = "h264_nvenc";
    //auto codec = avcodec_find_encoder(codec_id);
    auto codec = avcodec_find_encoder_by_name(codec_name);

    av_format_set_video_codec(FmtCtx, codec);

    if (Fmt->video_codec != AV_CODEC_ID_NONE)
    {
        AddStream(Fmt->video_codec);
    }
    OpenVideo();
    VideoSt.NextPts = 0;
    av_dump_format(FmtCtx, 0, FilePath.c_str(), 1);

    if (!(Fmt->flags & AVFMT_NOFILE))
    {
        auto ret = avio_open(&FmtCtx->pb, FilePath.c_str(), AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            auto errstr = FString(av_err2str(ret));
            UE_LOG(LogTemp, Error, TEXT("Could not open %s: %s"), *UEFilePath, *errstr);
            return;
        }
    }

    auto ret = avformat_write_header(FmtCtx, &Opt);
    if (ret < 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Error occurred when writing header to: %s"), *UEFilePath);
        return;
    }

    InLineSize[0] = 4 * VideoSt.Ctx->width;
    SwsCtx = sws_getContext(VideoSt.Ctx->width, VideoSt.Ctx->height, AV_PIX_FMT_RGBA,
                            VideoSt.Ctx->width, VideoSt.Ctx->height, VideoSt.Ctx->pix_fmt,
                            0, nullptr, nullptr, nullptr);
}

void UMyGameViewportClient::OpenVideo()
{
    auto c = VideoSt.Ctx;
    AVDictionary* opt = nullptr;

    av_dict_copy(&opt, Opt, 0);

    auto ret = avcodec_open2(c, VideoCodec, &opt);
    av_dict_free(&opt);
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("Could not open video codec: %s"), *errstr);
    }

    AllocPicture();
    if (!VideoSt.Frame)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate video frame"));
        return;
    }
    if (avcodec_parameters_from_context(VideoSt.Stream->codecpar, c))
    {
        UE_LOG(LogTemp, Error, TEXT("Could not copy the stream parameters"));
    }
}

void UMyGameViewportClient::AllocPicture()
{
    VideoSt.Frame = av_frame_alloc();
    if (!VideoSt.Frame)
    {
        UE_LOG(LogTemp, Error, TEXT("av_frame_alloc failed."));
        return;
    }

    VideoSt.Frame->format = VideoSt.Ctx->pix_fmt;
    VideoSt.Frame->width = ViewportSize.X;
    VideoSt.Frame->height = ViewportSize.Y;

    if (av_frame_get_buffer(VideoSt.Frame, 32) < 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate frame data"));
    }
}

void UMyGameViewportClient::AddStream(enum AVCodecID CodecID)
{
    VideoCodec = avcodec_find_encoder(CodecID);
    if (!VideoCodec)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not find encoder for '%s'"), ANSI_TO_TCHAR(avcodec_get_name(CodecID)));
    }


    VideoSt.Stream = avformat_new_stream(FmtCtx, nullptr);
    if (!VideoSt.Stream)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate stream"));
    }

    VideoSt.Stream->id = FmtCtx->nb_streams - 1;
    VideoSt.Ctx = avcodec_alloc_context3(VideoCodec);
    if (!VideoSt.Ctx)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not alloc an encoding context"));
    }

    VideoSt.Ctx->codec_id = CodecID;
    VideoSt.Ctx->width = ViewportSize.X;
    VideoSt.Ctx->height = ViewportSize.Y;
    VideoSt.Stream->time_base = VideoSt.Ctx->time_base = { 1, FRAMERATE };
    VideoSt.Ctx->gop_size = 10;
    VideoSt.Ctx->max_b_frames = 1;
    VideoSt.Ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    av_opt_set(VideoSt.Ctx->priv_data, "cq", TCHAR_TO_ANSI(*H264Crf), 0);  // change `cq` to `crf` if using libx264
    av_opt_set(VideoSt.Ctx->priv_data, "gpu", TCHAR_TO_ANSI(*DeviceNum), 0); // comment this line if using libx264

    if (FmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
        VideoSt.Ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

void UMyGameViewportClient::EncodeAndWrite()
{
    Pkt = { nullptr };
    av_init_packet(&Pkt);

    fflush(stdout);

    IMG_Buffer.SetNum(ColorBuffer.Num() * 4);
    uint8* DestPtr = nullptr;
    for (auto i = 0; i < ColorBuffer.Num(); i++)
    {
        DestPtr = &IMG_Buffer[i * 4];
        auto SrcPtr = ColorBuffer[i];
        *DestPtr++ = SrcPtr.R;
        *DestPtr++ = SrcPtr.G;
        *DestPtr++ = SrcPtr.B;
        *DestPtr++ = SrcPtr.A;
    }

    uint8* inData[1] = { IMG_Buffer.GetData() };
    sws_scale(SwsCtx, inData, InLineSize, 0, VideoSt.Ctx->height, VideoSt.Frame->data, VideoSt.Frame->linesize);

    VideoSt.Frame->pts = VideoSt.NextPts++;
    if (FFmpegEncode(VideoSt.Frame) < 0)
        UE_LOG(LogTemp, Error, TEXT("Error encoding frame %d"), count);

    auto ret = WriteFrame();
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("Error while writing video frame: %s"), *errstr);
    }
    av_packet_unref(&Pkt);
}

int UMyGameViewportClient::WriteFrame()
{
    av_packet_rescale_ts(&Pkt, VideoSt.Ctx->time_base, VideoSt.Stream->time_base);
    Pkt.stream_index = VideoSt.Stream->index;
    return av_interleaved_write_frame(FmtCtx, &Pkt);
}

int UMyGameViewportClient::FFmpegEncode(AVFrame *frame) {
    GotOutput = 0;
    auto ret = avcodec_send_frame(VideoSt.Ctx, frame);
    if (ret < 0 && ret != AVERROR_EOF) {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Warning, TEXT("error during sending frame, error : %s"), *errstr);
        return -1;
    }

    ret = avcodec_receive_packet(VideoSt.Ctx, &Pkt);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        return 0;

    if (ret < 0)
    {
        auto errstr = FString(av_make_error_string(ret).c_str());
        UE_LOG(LogTemp, Error, TEXT("Error during receiving frame, error : %s"), *errstr);
        av_packet_unref(&Pkt);
        return -1;
    }

    GotOutput = 1;
    return 0;
}

void UMyGameViewportClient::CloseStream()
{
    avcodec_free_context(&VideoSt.Ctx);
    av_frame_free(&VideoSt.Frame);
    sws_freeContext(SwsCtx);

    if (!(Fmt->flags & AVFMT_NOFILE))
    {
        auto ret = avio_closep(&FmtCtx->pb);
        if (ret < 0)
        {
            auto errstr = FString(av_err2str(ret));
            UE_LOG(LogTemp, Error, TEXT("avio close failed: %s"), *errstr);
        }
    }

    avformat_free_context(FmtCtx);
}

void UMyGameViewportClient::TidyUp()
{
    /* get the delayed frames */
    for (GotOutput = 1; GotOutput; count++)
    {
        fflush(stdout);

        FFmpegEncode(nullptr);

        if (GotOutput)
        {
            auto ret = WriteFrame(false);
            if (ret < 0)
            {
                auto errstr = FString(av_err2str(ret));
                UE_LOG(LogTemp, Error, TEXT("Error while writing video frame: %s"), *errstr);
            }
            av_packet_unref(&Pkt);
        }
    }

    auto ret = av_write_trailer(FmtCtx);
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("writing trailer error: %s"), *errstr);
    }

    CloseStream();
}

void UMyGameViewportClient::Draw(FViewport* Viewport, FCanvas* SceneCanvas)
{
    Super::Draw(Viewport, SceneCanvas);
    if (Over)  // You may need to set this in other class
    {
        Over = false;
        TidyUp();
    }

    else {
        CaptureFrame();
    }
}

void UMyGameViewportClient::CaptureFrame()
{
    if (!Viewport) {
        UE_LOG(LogTemp, Error, TEXT("No viewport"));
        return;
    }

    if (ViewportSize.X == 0 || ViewportSize.Y == 0) {
        UE_LOG(LogTemp, Error, TEXT("Viewport size is 0"));
        return;
    }

    ColorBuffer.Empty();

    if (!Viewport->ReadPixels(ColorBuffer, FReadSurfaceDataFlags(),
                              FIntRect(0, 0, ViewportSize.X, ViewportSize.Y)))
    {
        UE_LOG(LogTemp, Error, TEXT("Cannot read from viewport"));
        return;
    }

    EncodeAndWrite();  // call InitCodec() before this
}
void UMyGameViewportClient::InitCodec()
{
ViewportSize=视口->GetSizeXY();
av_寄存器_all();
avformat_alloc_output_context2(&FmtCtx、nullptr、nullptr、FilePath.c_str());
如果(!FmtCtx)
{
UE_日志(LogTemp、错误、文本(“无法分配格式上下文”);
返回;
}
Fmt=FmtCtx->oformat;
//自动编解码器id=AV编解码器id\U H264;
const char codec_name[32]=“h264_nvenc”;
//自动编解码器=avcodec\u find\u编码器(编解码器\u id);
自动编解码器=avcodec\u按\u名称查找\u编码器(编解码器\u名称);
av_格式_集_视频_编解码器(FmtCtx,编解码器);
如果(Fmt->video\u codec!=AV\u codec\u ID\u NONE)
{
AddStream(Fmt->视频编解码器);
}
OpenVideo();
VideoSt.NextPts=0;
av_dump_格式(FmtCtx,0,FilePath.c_str(),1);
如果(!(Fmt->标志和AVFMT_文件))
{
auto-ret=avio_-open(&FmtCtx->pb,FilePath.c_-str(),avio_-FLAG_-WRITE);
如果(ret<0)
{
自动errstr=FString(av_err2str(ret));
UE_日志(LogTemp,错误,文本(“无法打开%s:%s”),*UEFilePath,*errstr);
返回;
}
}
自动返回=avformat\U write\U头(FmtCtx和Opt);
如果(ret<0)
{
UE_日志(LogTemp,Error,TEXT(“将头写入:%s时出错”),*UEFilePath);
返回;
}
InLineSize[0]=4*VideoSt.Ctx->宽度;
SwsCtx=sws_getContext(VideoSt.Ctx->width,VideoSt.Ctx->height,AV_PIX_FMT_RGBA,
VideoSt.Ctx->宽度,VideoSt.Ctx->高度,VideoSt.Ctx->pix\u fmt,
0,nullptr,nullptr,nullptr);
}
void UMyGameViewportClient::OpenVideo()
{
自动c=VideoSt.Ctx;
AVDictionary*opt=nullptr;
影音副本(&opt,opt,0);
auto-ret=avcodec_open2(c、VideoCodec和opt);
av_dict_free(&opt);
如果(ret<0)
{
自动errstr=FString(av_err2str(ret));
UE_日志(LogTemp,错误,文本(“无法打开视频编解码器:%s”),*errstr);
}
AllocPicture();
如果(!视频标准帧)
{
UE_日志(LogTemp、错误、文本(“无法分配视频帧”);
返回;
}
if(avcodec_参数_来自_上下文(VideoSt.Stream->codecpar,c))
{
UE_日志(LogTemp、错误、文本(“无法复制流参数”);
}
void UMyGameViewportClient::InitCodec()
{
    ViewportSize = Viewport->GetSizeXY();

    av_register_all();
    avformat_alloc_output_context2(&FmtCtx, nullptr, nullptr, FilePath.c_str());
    if (!FmtCtx)
    {
        UE_LOG(LogTemp, Error, TEXT("cannot alloc format context"));
        return;
    }
    Fmt = FmtCtx->oformat;

    //auto codec_id = AV_CODEC_ID_H264;
    const char codec_name[32] = "h264_nvenc";
    //auto codec = avcodec_find_encoder(codec_id);
    auto codec = avcodec_find_encoder_by_name(codec_name);

    av_format_set_video_codec(FmtCtx, codec);

    if (Fmt->video_codec != AV_CODEC_ID_NONE)
    {
        AddStream(Fmt->video_codec);
    }
    OpenVideo();
    VideoSt.NextPts = 0;
    av_dump_format(FmtCtx, 0, FilePath.c_str(), 1);

    if (!(Fmt->flags & AVFMT_NOFILE))
    {
        auto ret = avio_open(&FmtCtx->pb, FilePath.c_str(), AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            auto errstr = FString(av_err2str(ret));
            UE_LOG(LogTemp, Error, TEXT("Could not open %s: %s"), *UEFilePath, *errstr);
            return;
        }
    }

    auto ret = avformat_write_header(FmtCtx, &Opt);
    if (ret < 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Error occurred when writing header to: %s"), *UEFilePath);
        return;
    }

    InLineSize[0] = 4 * VideoSt.Ctx->width;
    SwsCtx = sws_getContext(VideoSt.Ctx->width, VideoSt.Ctx->height, AV_PIX_FMT_RGBA,
                            VideoSt.Ctx->width, VideoSt.Ctx->height, VideoSt.Ctx->pix_fmt,
                            0, nullptr, nullptr, nullptr);
}

void UMyGameViewportClient::OpenVideo()
{
    auto c = VideoSt.Ctx;
    AVDictionary* opt = nullptr;

    av_dict_copy(&opt, Opt, 0);

    auto ret = avcodec_open2(c, VideoCodec, &opt);
    av_dict_free(&opt);
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("Could not open video codec: %s"), *errstr);
    }

    AllocPicture();
    if (!VideoSt.Frame)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate video frame"));
        return;
    }
    if (avcodec_parameters_from_context(VideoSt.Stream->codecpar, c))
    {
        UE_LOG(LogTemp, Error, TEXT("Could not copy the stream parameters"));
    }
}

void UMyGameViewportClient::AllocPicture()
{
    VideoSt.Frame = av_frame_alloc();
    if (!VideoSt.Frame)
    {
        UE_LOG(LogTemp, Error, TEXT("av_frame_alloc failed."));
        return;
    }

    VideoSt.Frame->format = VideoSt.Ctx->pix_fmt;
    VideoSt.Frame->width = ViewportSize.X;
    VideoSt.Frame->height = ViewportSize.Y;

    if (av_frame_get_buffer(VideoSt.Frame, 32) < 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate frame data"));
    }
}

void UMyGameViewportClient::AddStream(enum AVCodecID CodecID)
{
    VideoCodec = avcodec_find_encoder(CodecID);
    if (!VideoCodec)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not find encoder for '%s'"), ANSI_TO_TCHAR(avcodec_get_name(CodecID)));
    }


    VideoSt.Stream = avformat_new_stream(FmtCtx, nullptr);
    if (!VideoSt.Stream)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not allocate stream"));
    }

    VideoSt.Stream->id = FmtCtx->nb_streams - 1;
    VideoSt.Ctx = avcodec_alloc_context3(VideoCodec);
    if (!VideoSt.Ctx)
    {
        UE_LOG(LogTemp, Error, TEXT("Could not alloc an encoding context"));
    }

    VideoSt.Ctx->codec_id = CodecID;
    VideoSt.Ctx->width = ViewportSize.X;
    VideoSt.Ctx->height = ViewportSize.Y;
    VideoSt.Stream->time_base = VideoSt.Ctx->time_base = { 1, FRAMERATE };
    VideoSt.Ctx->gop_size = 10;
    VideoSt.Ctx->max_b_frames = 1;
    VideoSt.Ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    av_opt_set(VideoSt.Ctx->priv_data, "cq", TCHAR_TO_ANSI(*H264Crf), 0);  // change `cq` to `crf` if using libx264
    av_opt_set(VideoSt.Ctx->priv_data, "gpu", TCHAR_TO_ANSI(*DeviceNum), 0); // comment this line if using libx264

    if (FmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
        VideoSt.Ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

void UMyGameViewportClient::EncodeAndWrite()
{
    Pkt = { nullptr };
    av_init_packet(&Pkt);

    fflush(stdout);

    IMG_Buffer.SetNum(ColorBuffer.Num() * 4);
    uint8* DestPtr = nullptr;
    for (auto i = 0; i < ColorBuffer.Num(); i++)
    {
        DestPtr = &IMG_Buffer[i * 4];
        auto SrcPtr = ColorBuffer[i];
        *DestPtr++ = SrcPtr.R;
        *DestPtr++ = SrcPtr.G;
        *DestPtr++ = SrcPtr.B;
        *DestPtr++ = SrcPtr.A;
    }

    uint8* inData[1] = { IMG_Buffer.GetData() };
    sws_scale(SwsCtx, inData, InLineSize, 0, VideoSt.Ctx->height, VideoSt.Frame->data, VideoSt.Frame->linesize);

    VideoSt.Frame->pts = VideoSt.NextPts++;
    if (FFmpegEncode(VideoSt.Frame) < 0)
        UE_LOG(LogTemp, Error, TEXT("Error encoding frame %d"), count);

    auto ret = WriteFrame();
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("Error while writing video frame: %s"), *errstr);
    }
    av_packet_unref(&Pkt);
}

int UMyGameViewportClient::WriteFrame()
{
    av_packet_rescale_ts(&Pkt, VideoSt.Ctx->time_base, VideoSt.Stream->time_base);
    Pkt.stream_index = VideoSt.Stream->index;
    return av_interleaved_write_frame(FmtCtx, &Pkt);
}

int UMyGameViewportClient::FFmpegEncode(AVFrame *frame) {
    GotOutput = 0;
    auto ret = avcodec_send_frame(VideoSt.Ctx, frame);
    if (ret < 0 && ret != AVERROR_EOF) {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Warning, TEXT("error during sending frame, error : %s"), *errstr);
        return -1;
    }

    ret = avcodec_receive_packet(VideoSt.Ctx, &Pkt);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        return 0;

    if (ret < 0)
    {
        auto errstr = FString(av_make_error_string(ret).c_str());
        UE_LOG(LogTemp, Error, TEXT("Error during receiving frame, error : %s"), *errstr);
        av_packet_unref(&Pkt);
        return -1;
    }

    GotOutput = 1;
    return 0;
}

void UMyGameViewportClient::CloseStream()
{
    avcodec_free_context(&VideoSt.Ctx);
    av_frame_free(&VideoSt.Frame);
    sws_freeContext(SwsCtx);

    if (!(Fmt->flags & AVFMT_NOFILE))
    {
        auto ret = avio_closep(&FmtCtx->pb);
        if (ret < 0)
        {
            auto errstr = FString(av_err2str(ret));
            UE_LOG(LogTemp, Error, TEXT("avio close failed: %s"), *errstr);
        }
    }

    avformat_free_context(FmtCtx);
}

void UMyGameViewportClient::TidyUp()
{
    /* get the delayed frames */
    for (GotOutput = 1; GotOutput; count++)
    {
        fflush(stdout);

        FFmpegEncode(nullptr);

        if (GotOutput)
        {
            auto ret = WriteFrame(false);
            if (ret < 0)
            {
                auto errstr = FString(av_err2str(ret));
                UE_LOG(LogTemp, Error, TEXT("Error while writing video frame: %s"), *errstr);
            }
            av_packet_unref(&Pkt);
        }
    }

    auto ret = av_write_trailer(FmtCtx);
    if (ret < 0)
    {
        auto errstr = FString(av_err2str(ret));
        UE_LOG(LogTemp, Error, TEXT("writing trailer error: %s"), *errstr);
    }

    CloseStream();
}

void UMyGameViewportClient::Draw(FViewport* Viewport, FCanvas* SceneCanvas)
{
    Super::Draw(Viewport, SceneCanvas);
    if (Over)  // You may need to set this in other class
    {
        Over = false;
        TidyUp();
    }

    else {
        CaptureFrame();
    }
}

void UMyGameViewportClient::CaptureFrame()
{
    if (!Viewport) {
        UE_LOG(LogTemp, Error, TEXT("No viewport"));
        return;
    }

    if (ViewportSize.X == 0 || ViewportSize.Y == 0) {
        UE_LOG(LogTemp, Error, TEXT("Viewport size is 0"));
        return;
    }

    ColorBuffer.Empty();

    if (!Viewport->ReadPixels(ColorBuffer, FReadSurfaceDataFlags(),
                              FIntRect(0, 0, ViewportSize.X, ViewportSize.Y)))
    {
        UE_LOG(LogTemp, Error, TEXT("Cannot read from viewport"));
        return;
    }

    EncodeAndWrite();  // call InitCodec() before this
}