1

I have almost zero experience with libav/FFMPEG. I wrote this piece of code which capturing the screen and writes it to the file, and I'm facing some prolems with that. I was working with AV_CODEC_ID_MPEG4 codec at first, it worked just fine, but very quikly application started to spam messages like that

[dshow @ 02da1c80] real-time buffer [screen-capture-recorder] [video input] too full or near too full (64% of size: 128000000 [rtbufsize parameter])! frame dropped!

So I googled for some time, and found that probbably encoder is too slow, and I need to change it to faster one. So I changed it to AV_CODEC_ID_H264. Suddenly written file became unreadable, and application started to spam messages

[libx264 @ 0455ff40] non-strictly-monotonic PTS

I looked everywhere and all I found was a suggestion to put this two lines

if(outPacket.pts != AV_NOPTS_VALUE) outPacket.pts = av_rescale_q(outPacket.pts, videoStream->codec->time_base, videoStream->time_base);
if(outPacket.dts != AV_NOPTS_VALUE) outPacket.dts = av_rescale_q(outPacket.dts, videoStream->codec->time_base, videoStream->time_base);

So I added them, and the result was the same.

So, what should I do? How do I configure output correctly?

Here's my code:

#include "MainWindow.h"

#include <QGuiApplication>
#include <QLabel>
#include <QScreen>
#include <QTimer>
#include <QLayout>
#include <QImage>
#include <QtConcurrent/QtConcurrent>
#include <QThreadPool>
#include <QVideoFrame>

#include "ScreenCapture.h"

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    resize(800, 600);

    label = new QLabel();
    label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);

    auto layout = new QHBoxLayout();
    layout->addWidget(label);

    auto widget = new QWidget();
    widget->setLayout(layout);
    setCentralWidget(widget);

    connect(this, &MainWindow::imageReady, [=](QImage image) {label->setPixmap(QPixmap::fromImage(image).scaled(label->size(), Qt::KeepAspectRatio));});

    init();
    initOutFile();
    collectFrames();
}

MainWindow::~MainWindow()
{
    isRunning = false;

    QThreadPool::globalInstance()->waitForDone();

    avformat_close_input(&inputFormatContext);
    avformat_free_context(inputFormatContext);
}

void MainWindow::init()
{
    av_register_all();
    avcodec_register_all();
    avdevice_register_all();

    auto screen = QGuiApplication::screens()[0];
    QRect geometry = screen->geometry();

    inputFormatContext = avformat_alloc_context();

//    AVDictionary* options = NULL;
//    av_dict_set(&options, "framerate", "30", NULL);
//    av_dict_set(&options, "offset_x", QString::number(geometry.x()).toLatin1().data(), NULL);
//    av_dict_set(&options, "offset_y", QString::number(geometry.y()).toLatin1().data(), NULL);
//    av_dict_set(&options, "preset", "ultrafast", NULL);
//    av_dict_set(&options, "probesize", "10MB", NULL);
//    av_dict_set(&options, "pix_fmt", "yuv420p", NULL);
//    av_dict_set(&options, "video_size", QString(QString::number(geometry.width()) + "x" + QString::number(geometry.height())).toLatin1().data(), NULL);

//    AVInputFormat* inputFormat = av_find_input_format("gdigrab");
//    avformat_open_input(&inputFormatContext, "desktop", inputFormat, &options);

    QSettings settings("HKEY_CURRENT_USER\\Software\\screen-capture-recorder", QSettings::NativeFormat);
    settings.setValue("start_x", geometry.x());
    settings.setValue("start_y", geometry.y());
    settings.setValue("capture_width", geometry.width());
    settings.setValue("capture_height", geometry.height());

    AVDictionary* options = NULL;
    av_dict_set(&options, "preset", "ultrafast", NULL);
    av_dict_set(&options, "vcodec", "h264", NULL);
    av_dict_set(&options, "video_size", "1920x1080", NULL);
    av_dict_set(&options, "crf", "0", NULL);
    av_dict_set(&options, "tune", "zerolatency", NULL);
    av_dict_set(&options, "rtbufsize", "128M", NULL);

    AVInputFormat *format = av_find_input_format("dshow");
    avformat_open_input(&inputFormatContext, "video=screen-capture-recorder", format, &options);

    av_dict_free(&options);
    avformat_find_stream_info(inputFormatContext, NULL);

    videoStreamIndex = av_find_best_stream(inputFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

    AVStream* inStream = inputFormatContext->streams[videoStreamIndex];

    inputCodec = avcodec_find_decoder(inStream->codecpar->codec_id);
    if(!inputCodec) qDebug() << "Can't find input codec!";

    inputCodecContext = avcodec_alloc_context3(inputCodec);

    qDebug() << "IN_FORMAT" << av_get_pix_fmt_name(inStream->codec->pix_fmt);

    avcodec_parameters_to_context(inputCodecContext, inStream->codecpar);

    if(avcodec_open2(inputCodecContext, inputCodec, NULL)) qDebug() << "Can't open input codec!";
}

void MainWindow::initOutFile()
{
    const char* filename = "C:/Temp/output.mp4";

    if(avformat_alloc_output_context2(&outFormatContext, NULL, NULL, filename) < 0) qDebug() << "Can't create out context!";

    outCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if(!outCodec) qDebug() << "Can't find codec!";

    videoStream = avformat_new_stream(outFormatContext, outCodec);
    videoStream->time_base = {1, 30};

    const AVPixelFormat* pixelFormat = outCodec->pix_fmts;
    while (*pixelFormat != AV_PIX_FMT_NONE)
    {
        qDebug() << "OUT_FORMAT" << av_get_pix_fmt_name(*pixelFormat);
        ++pixelFormat;
    }

    outCodecContext = videoStream->codec;
    outCodecContext->bit_rate = 16000000;
    outCodecContext->rc_max_rate = 0;
    outCodecContext->rc_buffer_size = 0;
    outCodecContext->qmin = 10;
    outCodecContext->qmax = 51;
    outCodecContext->qcompress = 0.6f;
    outCodecContext->width = inputCodecContext->width;
    outCodecContext->height = inputCodecContext->height;
    outCodecContext->time_base = videoStream->time_base;
    outCodecContext->gop_size = 10;
    outCodecContext->max_b_frames = 1;
    outCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;

    if (outFormatContext->oformat->flags & AVFMT_GLOBALHEADER) outCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;

    if(avcodec_open2(outCodecContext, outCodec, NULL)) qDebug() << "Can't open out codec!";

    swsContext = sws_getContext(inputCodecContext->width,
                                inputCodecContext->height,
                                inputCodecContext->pix_fmt,
                                outCodecContext->width,
                                outCodecContext->height,
                                outCodecContext->pix_fmt,
                                SWS_BICUBIC, NULL, NULL, NULL);

    if(avio_open(&outFormatContext->pb, filename, AVIO_FLAG_WRITE) < 0) qDebug() << "Can't open file!";
    if(avformat_write_header(outFormatContext, NULL) < 0) qDebug() << "Can't write header!";
}

void MainWindow::collectFrames()
{
    QtConcurrent::run([this](){

        AVFrame* inFrame = av_frame_alloc();
        inFrame->format = inputCodecContext->pix_fmt;
        inFrame->width = inputCodecContext->width;
        inFrame->height = inputCodecContext->height;

        int size = av_image_alloc(inFrame->data, inFrame->linesize, inFrame->width, inFrame->height, inputCodecContext->pix_fmt, 1);

        AVFrame* outFrame = av_frame_alloc();
        outFrame->format = outCodecContext->pix_fmt;
        outFrame->width = outCodecContext->width;
        outFrame->height = outCodecContext->height;

        av_image_alloc(outFrame->data, outFrame->linesize, outFrame->width, outFrame->height, outCodecContext->pix_fmt, 1);

        AVPacket packet;
        av_init_packet(&packet);

        while(isRunning && (av_read_frame(inputFormatContext, &packet) >= 0))
        {
            if(packet.stream_index == videoStream->index)
            {
                //for gdigrab
//                uint8_t* result = new uint8_t[inFrame->width * inFrame->height * 4];
//                for (int i = 0; i < inFrame->height * inFrame->width * 4; i += 4)
//                {
//                    result[i + 0] = packet.data[i + 2]; //B
//                    result[i + 1] = packet.data[i + 3]; //G
//                    result[i + 2] = packet.data[i + 0]; //R
//                    result[i + 3] = packet.data[i + 1]; //A
//                }

//                memcpy(inFrame->data[0], result, size);
//                delete result;

                QImage image(packet.data, inFrame->width, inFrame->height, QImage::Format_ARGB32);
                QImage mirrored = image.mirrored(false, true);
                emit imageReady(mirrored);

                memcpy(inFrame->data[0], mirrored.bits(), size);

                sws_scale(swsContext, inFrame->data, inFrame->linesize, 0, inputCodecContext->height, outFrame->data, outFrame->linesize);

                av_packet_unref(&packet);

                AVPacket outPacket;
                av_init_packet(&outPacket);

                int encodeResult = AVERROR(EAGAIN);
                while(encodeResult == AVERROR(EAGAIN))
                {
                    if(avcodec_send_frame(outCodecContext, outFrame)) qDebug() << "Send frame error!";

                    encodeResult = avcodec_receive_packet(outCodecContext, &outPacket);
                }
                if(encodeResult != 0) qDebug() << "Encoding error!" << encodeResult;

                if(outPacket.pts != AV_NOPTS_VALUE) outPacket.pts = av_rescale_q(outPacket.pts, videoStream->codec->time_base, videoStream->time_base);
                if(outPacket.dts != AV_NOPTS_VALUE) outPacket.dts = av_rescale_q(outPacket.dts, videoStream->codec->time_base, videoStream->time_base);

                av_interleaved_write_frame(outFormatContext, &outPacket);

                av_packet_unref(&outPacket);
            }
        }

        av_freep(inFrame->data);
        av_freep(outFrame->data);

        av_write_trailer(outFormatContext);
        avio_close(outFormatContext->pb);
    });

}
user1496491
  • 453
  • 2
  • 11
  • 23
  • 3
    H.264 encoder is not faster than MPEG4, conversely encoding H.264 is usually heavier (uses more CPU). Your system probably is just too slow to manage that video stream. Try to scale down and encode at lower resolution. – roalz Mar 13 '18 at 13:18
  • 2
    Alternatively you could buy a relatively recent nVidia graphics card (GF600+) and use nvenc hardware acceleration to encode to H.264 without performance issues: https://en.wikipedia.org/wiki/Nvidia_NVENC – MadMarky Mar 13 '18 at 14:15
  • Possible duplicate of [ffmpeg::avcodec\_encode\_video setting PTS h264](https://stackoverflow.com/questions/6603979/ffmpegavcodec-encode-video-setting-pts-h264) – Mohammad Kanan Mar 13 '18 at 14:59

1 Answers1

0

You need to set the pts of outFrame prior to calling send_frame.

szatmary
  • 29,969
  • 8
  • 44
  • 57