Capture desktop with gdigrab and save the result to video file using ffmpeg and C++

0

I'm building a C++ application that is able to capture the screen and save the output to a file (.mkv, .mp4, doesn't matter).

I tried to switch codecs and played with the AVCodecContext settings. When subsitute the gdi capture with reading from a static .mp4 file everything works fine.

Here is my gdigrab initalization.

_inputFormat = av_find_input_format("gdigrab");

    if (!_inputFormat) {
        std::cout << "Unable to open input Format" << std::endl;
        exit(1);
    }

    if (avformat_open_input(&_screenFormatContext, "desktop", _inputFormat, &_recordOptions) < 0) {
        std::cout << "Unable to open input stream" << std::endl;
        exit(1);
    }

    if (avformat_find_stream_info(_screenFormatContext, &_recordOptions) < 0) {
        std::cout << "Couldn't find input stream" << std::endl;
        exit(1);
    }

    av_dump_format(_screenFormatContext, 0, "GDI Capture", 0);
    for (int i = 0; i < _screenFormatContext->nb_streams; i++) {
        if (_screenFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            _videoStreamIdx = i;
            break;
        }
    }

    if (_videoStreamIdx == -1) {
        std::cout << "Unable to find video Stream" << std::endl;
        exit(1);
    }

    _codecPar = _screenFormatContext->streams[_videoStreamIdx]->codecpar;

Here is my setup code for the encoder and decoder.

    /*
        Decoder
    */
    _dCodec = avcodec_find_decoder(_codecPar->codec_id);
    if (!_dCodec) {
        std::cout << "Unable to find decoder" << std::endl;
        exit(1);
    }

    _dCodecContext = avcodec_alloc_context3(_dCodec);
    if (avcodec_parameters_to_context(_dCodecContext, _codecPar) < 0) {
        std::cout << "Unable to copy context" << std::endl;
        exit(1);
    }
    if (avcodec_open2(_dCodecContext, _dCodec, NULL) < 0) {
        std::cout << "Unable to open dCodec" << std::endl;
        exit(1);
    }

    /*
        Encoder
    */
    _eCodec = avcodec_find_encoder(AV_CODEC_ID_H265);
    if (!_eCodec) {
        std::cout << "Unable to find encoder" << std::endl;
        exit(1);
    }

    _eCodecContext = avcodec_alloc_context3(_eCodec);

    //width and height have to be divisible by 2
    if (_codecPar->width % 2 != 0)
        _codecPar->width--;
    if (_codecPar->height % 2 != 0)
        _codecPar->height--;

    _eCodecContext->pix_fmt = AV_PIX_FMT_YUV422P;
    _eCodecContext->width = _codecPar->width;
    _eCodecContext->height = _codecPar->height;
    _eCodecContext->gop_size = 10;
    _eCodecContext->max_b_frames = 1;

    _eCodecContext->time_base.den = 30;
    _eCodecContext->time_base.num = 1;

    if (avcodec_open2(_eCodecContext, _eCodec, NULL) < 0) {
        std::cout << "Unable to open eCodec" << std::endl;
        exit(1);
    }

Here is my main loop. Grabbing the packets from gdi, decoding them and encoding them.

    while (av_read_frame(_screenFormatContext, _pkt) == 0) {
        if (_pkt->stream_index == _videoStreamIdx) {
            if (_decodePacket() < 0)
                exit(1);
            _encodeFrame();
        }
        av_packet_unref(_pkt);
}

And here the encoding/decoding functions

int     ScreenRecorder::_decodePacket(void)
{
    //send packet to decoder
    int ret = avcodec_send_packet(_dCodecContext, _pkt);
    if (ret < 0) {
        std::cout << "error while sending packet to decoder" << std::endl;
        return ret;
    }

    ret = avcodec_receive_frame(_dCodecContext, _frame);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        return 0;
    //Was there an error?
    else if (ret < 0) {
        std::cout << "Error with receiving frame" << std::endl;
        exit(1);
    }
    //No errors -> got frame
    std::cout << "Got frame " << _dCodecContext->frame_number << " with size " << _frame->pkt_size << std::endl;
    char frame_filename[1024];
    snprintf(frame_filename, sizeof(frame_filename), "%s-%d.pgm", "frame", _dCodecContext->frame_number);
    //_saveGrayFrame(_frame->data[0], _frame->linesize[0], _frame->width, _frame->height, frame_filename);
    return 0;
}

void    ScreenRecorder::_encodeFrame(void)
{
    AVPacket* pkt = av_packet_alloc();

    // send frame to encoder - 0 on success
    int ret = avcodec_send_frame(_eCodecContext, _frame);
    if (ret < 0) {
        std::cerr << "Unable to send frame! ERRORCODE: " << ret << std::endl;
        exit(1);
    }

    while (ret == 0) {
        ret = avcodec_receive_packet(_eCodecContext, pkt);
        //Do I need to send more packets?
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            std::cout << "Needing one more packet " << std::endl;
            av_packet_unref(pkt);
            return ;
        }
        //Was there an error?
        else if (ret < 0) {
            std::cout << "Error with receiving packet" << std::endl;
            exit(1);
        }
        //No errors -> got packet
        std::cout << "Got packet " << pkt->pts << " of size " << pkt->size << std::endl;
        if (av_write_frame(_outFormatContext, pkt) < 0) {
            std::cout << "Unable to write frame" << std::endl;
            exit(1);
        }
        av_packet_unref(pkt);
    }
}

In case you still need the header file

#ifndef SCREENRECORDER_HPP
# define SCREENRECORDER_HPP

extern "C" {
    #include "libavcodec/avcodec.h"
    #include "libavdevice/avdevice.h"
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
}

class ScreenRecorder
{
private:
    AVCodecContext      *_dCodecContext;
    AVCodecContext      *_eCodecContext;

    AVFormatContext     *_fileFormatContext;
    AVFormatContext     *_screenFormatContext;
    AVFormatContext     *_outFormatContext;

    AVInputFormat       *_inputFormat;

    AVCodecParameters   *_codecPar;

    AVStream            *_videoTrack;

    AVCodec             *_dCodec;
    AVCodec             *_eCodec;

    AVFrame             *_frame;

    AVPacket            *_pkt;

    AVDictionary        *_options;
    AVDictionary        *_recordOptions;


    int                 _videoStreamIdx;

    int                 _decodePacket(void);
    void                _encodeFrame(void);
    void                _openInputFile(void);
    void                _initalizeCodec(void);
    void                _initalizeOutputFile(void);
    void                _closeCodec(void);
    void                _initalizeGDI(void);
    void                _saveGrayFrame(unsigned char* buf, int wrap, int xsize, int ysize, char* filename);

public:
    ScreenRecorder();
    ~ScreenRecorder();

    void                start();
};

#endif

When I run my code I am getting Exception thrown at 0x00007FF819864A80 (msvcrt.dll) in Streaming.exe: 0xC0000005: Access violation reading location 0x0000000000000EF0. It comes from avcodec_send_frame function. Seems like I'm trying to access memory that wasn't allocated. I am new to the ffmpeg lib and hope that someone can get me on the right track. Thank you very much. If you have any questions please ask them.

c++
ffmpeg
asked on Stack Overflow May 13, 2019 by Skunz • edited May 13, 2019 by Skunz

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0