Search code examples
c++ffmpegh.264

How to encode 1 image and receive it using ffmpeg h264_nvenc


I am trying to create a real-time stream of my screen using ffmpeg and encoder h264_nvenc but there's 5-6 frames delay between my mouse move and mouse move on the translation(first 5-6 frames are black)

I need to somehow encode 1 image of my screen using h264_nvenc and receive it as valid frame(packet) or maybe decrease delay to 1-2 frames

Maybe there are some other methods to get the image right away

UPD: I realised that my latency is because of encoder and decoder frames delay so is there any way to completely remove this frame delays?(Added decoder init function and decode function)

I've done some tests and managed to receive image right away with decoder and encoder flush method but cant do this the second time

Encode function:

static void encode(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* pkt, AVPacket** new_packet)
{
    int ret;

    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }

    int size = 0;

    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            //printf("count: %i size: %i\n", count, size);
            return;
        }
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }
        size += pkt->size;

        *new_packet = av_packet_clone(pkt);
        
        av_packet_unref(pkt);
    }
}

FFMpeg encoder initialization:

void InitializeFFmpegEncoder()
{
    const AVCodec* codec;
    int i, ret, x, y;
    FILE* f;

    // Encoder

    codec = avcodec_find_encoder_by_name("h264_nvenc"); // hevc_nvenc h264_nvenc
    if (!codec) {
        fprintf(stderr, "Codec '%s' not found\n", "hevc");
        exit(1);
    }

    /*codec = avcodec_find_encoder(AV_CODEC_ID_H265);
    if (!codec) {
        fprintf(stderr, "Codec '%s' not found\n", "hevc");
        exit(1);
    }*/
    cout << "Encoder codec name: " << codec->name << endl;

    cout << "1" << endl;

    enc_c = avcodec_alloc_context3(codec);
    if (!enc_c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
    cout << "2" << endl;
    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);

    cout << "3" << endl;

    /* put sample parameters */
    enc_c->bit_rate = 192000000;
    /* resolution must be a multiple of two */
    enc_c->width = 1920;
    enc_c->height = 1080;
    /* frames per second */
    enc_c->time_base = AVRational(1, 1);
    //enc_c->framerate = AVRational(60, 1);

    /* emit one intra frame every ten frames
     * check frame pict_type before passing frame
     * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
     * then gop_size is ignored and the output of encoder
     * will always be I frame irrespective to gop_size
     */
    enc_c->gop_size = 1;
    enc_c->max_b_frames = 0;
    enc_c->pix_fmt = AV_PIX_FMT_BGR0;
    enc_c->keyint_min = 0;

    if (codec->id == AV_CODEC_ID_H264)
    {
        //av_opt_set(enc_c->priv_data, "preset", "p1", 0);
        //av_opt_set(enc_c->priv_data, "tune", "ull", 0);
        //av_opt_set(enc_c->priv_data, "zerolatency", "1", 0);
        //av_opt_set(enc_c->priv_data, "preset", "p1", 0);
        //av_opt_set(enc_c->priv_data, "tune", "ull", 0);
        //av_opt_set(enc_c->priv_data, "strict_gop", "1", 0);
        //av_opt_set(enc_c->priv_data, "preset", "lossless", 0);
        //av_opt_set(enc_c->priv_data, "zerolatency", "1", 0);
    }

    cout << "4" << endl;

    /* open it */
    ret = avcodec_open2(enc_c, codec, NULL);
    if (ret < 0) {
        printf("Could not open codec: %s\n", av_make_error_string((char[64])(0), 64, ret));
        exit(1);
    }

    cout << "5" << endl;

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = AV_PIX_FMT_BGR0; // AV_PIX_FMT_YUV444P AV_PIX_FMT_ARGB
    frame->width = enc_c->width;
    frame->height = enc_c->height;

    cout << "6" << endl;

    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate the video frame data\n");
        exit(1);
    }

    cout << "7" << endl;
}

Decoder init function:

int InitializeFFmpegDecoder()
{
    int ret;
    const AVCodec* decoder = NULL;
    enum AVHWDeviceType type;
    int i;

    decoder = avcodec_find_decoder_by_name("h264_cuvid"); // h264_cuvid hevc_cuvid
    if (!decoder) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }

    cout << "Decoder name: " << decoder->name << endl;

    if (!(decoder_ctx = avcodec_alloc_context3(decoder)))
        return AVERROR(ENOMEM);
    decoder_ctx->get_format = ffmpeg_GetFormat;
    decoder_ctx->gop_size = 0;
    decoder_ctx->max_b_frames = 0;
    decoder_ctx->keyint_min = 0;
    decoder_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;

    //decoder_ctx->get_format = get_format;

    if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {
        fprintf(stderr, "Failed to open codec for stream \n");
        return -1;
    }

    if (ctx == NULL)
    {
        ctx = sws_getContext(1920, 1080,
            AV_PIX_FMT_NV12, 1920, 1080, // AV_PIX_FMT_YUV420P AV_PIX_FMT_NV12
            AV_PIX_FMT_BGR0, 0, 0, 0, 0);
    }
}

Decode function:

static void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt, char* bgra_image)
{
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }

        char* outData[1] = { bgra_image }; // RGB24 have one plane
        int outLinesize[1] = { 4 * 1920 }; // RGB stride
        sws_scale(ctx, frame->data, frame->linesize, 0, 1080, (uint8_t* const*)outData, outLinesize);

        //*new_frame = av_frame_clone(frame);

        //av_frame_ref()

        //break;
    }
}

Solution

  • For h264_nvenc set option delay to 0
    But for decoder h264_cuvid I don't know how to make 0 delay stream so I switched to DXVA2 and set max_b_frames to 0