C++ ffmpeg Specified pixel format is invalid or not supported

Viewed 60

So I have a program that reads an opengl window and encodes the read data as a video. Now through a series of experimentation I have learned that the bit format of my glfw window is 8:8:8 as returned by glfwGetVideoMode(monitor). So I use this function to read the window:

glReadPixels(0, 0,gl_width, gl_height,GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*) Buffer);

and I simply encode it in the AV_PIX_FMT_YUV420P format.

Under normal circumstances this method works just fine. However, when I actually run the program, the output I get, as opposed to what I can see in the glfw window, is really low resolution and a bit pixelated.

Here is what my GLFW window looks like: enter image description here

Now this is what I want it to look like. It looks just fine on the opengl window, and I encode it directly without altering Buffer.

And here is what the encoded result, test.mp4 looks like when I run it using mplayer or similar software:

enter image description here

It's a lot more blurry and pixelated compare to the GLFW window. With some experimentation and following an answer to another question I asked, I us avcodec_find_best_pix_fmt_of_list((*codec)->pix_fmts, AV_PIX_FMT_RGBA, 1, &ret) and it returned 13. Which led me to believe using AV_PIX_FMT_YUVJ422P is the best option for this convertion to not have a blurry/pixelated result. However, no matter which function I pass, every single format gives off an error except AV_PIX_FMT_YUV420P. The error is:

[mpeg4 @ 0x558e74f47900] Specified pixel format yuvj422p is invalid or not supported

I have no idea why this is happening, as the format is bound to a define and it is changed throughout the entire program when I change the define.

Here is my encoder so far (I have trimmed some parts):

video_encoder.cpp:

int video_encoder::write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
                       AVStream *st, AVFrame *frame, AVPacket *pkt)
    {
    int ret;
    // Conditional jump or move depends on uninitialised value
    // Use of uninitialised value of size 8
    // send the frame to the encoder

    // Error is about c.
    ret = avcodec_send_frame(c, frame);

    if (ret < 0) {
        std::cout << "Error sending a frame to the encoder: " << ret << std::endl;
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(c, pkt);

        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {
            std::cout << "Error encoding a frame: " << ret << std::endl;
            exit(1);
        }

        /* rescale output packet timestamp values from codec to stream timebase */
        av_packet_rescale_ts(pkt, c->time_base, st->time_base);
        pkt->stream_index = st->index;

        /* Write the compressed frame to the media file. */
        //log_packet(fmt_ctx, pkt);

        //std::cout << "Packet: " << pkt << std::endl;

        ret = av_interleaved_write_frame(fmt_ctx, pkt);

        /* pkt is now blank (av_interleaved_write_frame() takes ownership of
         * its contents and resets pkt), so that no unreferencing is necessary.
         * This would be different if one used av_write_frame(). */
        if (ret < 0) {
            std::cout << "Error while writing output packet: " << ret << std::endl;
            exit(1);
        }
    }

    return ret == AVERROR_EOF ? 1 : 0;
}


/* Add an output stream. */
void video_encoder::add_stream(OutputStream *ost, AVFormatContext *oc,
                       const AVCodec **codec,
                       enum AVCodecID codec_id)
{
    AVCodecContext *c;
    int i;

    /* find the encoder */
    *codec = avcodec_find_encoder(codec_id);
    
    if (!(*codec)) {
        fprintf(stderr, "Could not find encoder for '%s'\n",
                avcodec_get_name(codec_id));
        exit(1);
    }

    ost->tmp_pkt = av_packet_alloc();

    if (!ost->tmp_pkt) {
        fprintf(stderr, "Could not allocate AVPacket\n");
        exit(1);
    }

    ost->st = avformat_new_stream(oc, NULL);
    if (!ost->st) {
        fprintf(stderr, "Could not allocate stream\n");
        exit(1);
    }
    ost->st->id = oc->nb_streams-1;
    c = avcodec_alloc_context3(*codec);
    if (!c) {
        fprintf(stderr, "Could not alloc an encoding context\n");
        exit(1);
    }
    ost->enc = c;


    switch ((*codec)->type) {
    case AVMEDIA_TYPE_AUDIO:
        ...
    case AVMEDIA_TYPE_VIDEO:
        c->codec_id = codec_id;

        c->bit_rate = 10000;
        /* Resolution must be a multiple of two. */
        c->width    = width;
        c->height   = height;
        /* timebase: This is the fundamental unit of time (in seconds) in terms
         * of which frame timestamps are represented. For fixed-fps content,
         * timebase should be 1/framerate and timestamp increments should be
         * identical to 1. */
        ost->st->time_base = (AVRational){ 1, STREAM_FRAME_RATE }; // *frame_rate
        c->time_base       = ost->st->time_base;

        c->gop_size      = 7; /* emit one intra frame every twelve frames at most */
        c->pix_fmt       = STREAM_PIX_FMT;
        //if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) 
        //    c->max_b_frames = 2;
        if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
            /* Needed to avoid using macroblocks in which some coeffs overflow.
             * This does not happen with normal video, it just happens here as
             * the motion of the chroma plane does not match the luma plane. */
            c->mb_decision = 2;
        }

        if ((*codec)->pix_fmts){
            //c->pix_fmt = (*codec)->pix_fmts[0];
            std::cout << "NEW FORMAT : " << c->pix_fmt << std::endl;
        }

        int ret;
        avcodec_find_best_pix_fmt_of_list((*codec)->pix_fmts, AV_PIX_FMT_RGBA, 1, &ret);
        std::cout << "Desired format is: " << ret << std::endl;
        break;
    }
     

    /* Some formats want stream headers to be separate. */
    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

/**************************************************************/
/* video output */

AVFrame* video_encoder::alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
{
    AVFrame *picture;
    int ret;

    picture = av_frame_alloc();
    if (!picture)
        return NULL;

    picture->format = pix_fmt;
    picture->width  = width;
    picture->height = height;

    /* allocate the buffers for the frame data */
    ret = av_frame_get_buffer(picture, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate frame data.\n");
        exit(1);
    }

    return picture;
}

void video_encoder::open_video(AVFormatContext *oc, const AVCodec *codec,
                       OutputStream *ost, AVDictionary *opt_arg)
{
    int ret;
    AVCodecContext *c = ost->enc;
    AVDictionary *opt = NULL;
    av_dict_copy(&opt, opt_arg, 0);
    /* open the codec */
    ret = avcodec_open2(c, codec, &opt);
    av_dict_free(&opt);
    if (ret < 0) {
        fprintf(stderr, "Could not open video codec: %s\n", ret);
        exit(1);
    }

    /* allocate and init a re-usable frame */
    ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
    if (!ost->frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    /* If the output format is not YUV420P, then a temporary YUV420P
     * picture is needed too. It is then converted to the required
     * output format. */
    ost->tmp_frame = NULL;


    /* copy the stream parameters to the muxer */
    ret = avcodec_parameters_from_context(ost->st->codecpar, c);
    if (ret < 0) {
        fprintf(stderr, "Could not copy the stream parameters\n");
        exit(1);
    }
}


void video_encoder::set_frame_yuv_from_rgb(AVFrame *frame, struct SwsContext *sws_context) {
    const int in_linesize[1] = { 4 * width };
    //uint8_t* dest[4] = { rgb_data, NULL, NULL, NULL };

    
    sws_context = sws_getContext(
            width, height, AV_PIX_FMT_RGBA,
            width, height, STREAM_PIX_FMT,
            SCALE_FLAGS, 0, 0, 0);

    sws_scale(sws_context, (const uint8_t * const *)&rgb_data, in_linesize, 0,
            height, frame->data, frame->linesize);
}

AVFrame* video_encoder::get_video_frame(OutputStream *ost)
{
    AVCodecContext *c = ost->enc;
    /* check if we want to generate more frames */
    if (av_compare_ts(ost->next_pts, c->time_base,
                      (float) STREAM_DURATION / 1000, (AVRational){ 1, 1 }) > 0)
        return NULL;

    /* when we pass a frame to the encoder, it may keep a reference to it
     * internally; make sure we do not overwrite it here */
    if (av_frame_make_writable(ost->frame) < 0)
        exit(1);

        
    set_frame_yuv_from_rgb(ost->frame, ost->sws_ctx);
    
    
    ost->frame->pts = ost->next_pts++;

    return ost->frame;
}

/*
 * encode one video frame and send it to the muxer
 * return 1 when encoding is finished, 0 otherwise
 */
int video_encoder::write_video_frame(AVFormatContext *oc, OutputStream *ost)
{
    return write_frame(oc, ost->enc, ost->st, get_video_frame(ost), ost->tmp_pkt);
}

void video_encoder::close_stream(AVFormatContext *oc, OutputStream *ost)
{
    avcodec_free_context(&ost->enc);
    av_frame_free(&ost->frame);
    av_frame_free(&ost->tmp_frame);
    av_packet_free(&ost->tmp_pkt);
    //sws_freeContext(ost->sws_ctx);
    //swr_free(&ost->swr_ctx);
}

/**************************************************************/
/* media file output */

void video_encoder::set_encode_framebuffer(uint8_t* data, bool audio_only)
{
    rgb_data = data;
}

video_encoder::~video_encoder()
{
    av_write_trailer(enc_inf.oc);

    /* Close each codec. */
    if (enc_inf.have_video)
        close_stream(enc_inf.oc, &enc_inf.video_st);

    if (!(enc_inf.fmt->flags & AVFMT_NOFILE))
        /* Close the output file. */
        avio_closep(&enc_inf.oc->pb);

    /* free the stream */
    avformat_free_context(enc_inf.oc);
    std::cout << "Done, closing." << std::endl;  
}

bool video_encoder::encode_one_frame()
{
    if (enc_inf.encode_video || enc_inf.encode_audio) {
        /* select the stream to encode */
       if (enc_inf.encode_video &&
            (!enc_inf.encode_audio || av_compare_ts(enc_inf.video_st.next_pts, enc_inf.video_st.enc->time_base,
                                            enc_inf.audio_st.next_pts, enc_inf.audio_st.enc->time_base) <= 0)) {
            enc_inf.encode_video = !write_video_frame(enc_inf.oc, &enc_inf.video_st);
            return true;
        } 
    }
    return false;
}


video_encoder::video_encoder(int w, int h, float fps, unsigned int duration) 
 :width(w), height(h), STREAM_FRAME_RATE(fps), STREAM_DURATION(duration)
{
    //std::filesystem::create_directory("media"); 

    //std::string as_str = "./output/" + std::string(getenv ("OUTPUT_UUID")) + ".mp4";
    std::string as_str = "./output/video.mp4";

    char* filename = const_cast<char*>(as_str.c_str());
    enc_inf.video_st, enc_inf.audio_st = (struct OutputStream) { 0 };
    enc_inf.video_st.next_pts = 1; 
    enc_inf.audio_st.next_pts = 1;
    enc_inf.encode_audio, enc_inf.encode_video = 0;
    int ret;
    int i;
    //rgb_data = (uint8_t*)malloc( 48 * sizeof(uint8_t) );

    /* allocate the output media context */
    avformat_alloc_output_context2(&enc_inf.oc, NULL, NULL, filename);

    if (!enc_inf.oc) {
        //VI_ERROR("Could not deduce output format from file extension: using MPEG.\n");
        avformat_alloc_output_context2(&enc_inf.oc, NULL, "mpeg", filename);
    }
    if (!enc_inf.oc)
        std::cout << "FAILED" << std::endl;
        //return 1;

    enc_inf.fmt = enc_inf.oc->oformat;

    /* Add the audio and video streams using the default format codecs
     * and initialize the codecs. */
    if (enc_inf.fmt->video_codec != AV_CODEC_ID_NONE) {
        add_stream(&enc_inf.video_st, enc_inf.oc, &video_codec, enc_inf.fmt->video_codec);
        enc_inf.have_video = 1;
        enc_inf.encode_video = 1;
    }

    /* Now that all the parameters are set, we can open the audio and
     * video codecs and allocate the necessary encode buffers. */
    if (enc_inf.have_video)
        open_video(enc_inf.oc, video_codec, &enc_inf.video_st, opt);

    /* open the output file, if needed */
    if (!(enc_inf.fmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&enc_inf.oc->pb, filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            //VI_ERROR("Could not open '%s': %s\n", filename, ret);
            //return 1;
        }
    }

    /* Write the stream header, if any. */
    ret = avformat_write_header(enc_inf.oc, &opt);
    if (ret < 0) {
        VI_ERROR("Error occurred when opening output file:");
        //return 1;
    }
    
    //return 0;
}

video_encoder.h:

#define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */

#define SCALE_FLAGS SWS_SPLINE

/* The output bit rate in bit/s */
#define OUTPUT_BIT_RATE 96000
/* The number of output channels */
#define OUTPUT_CHANNELS 2

typedef struct OutputStream {
    AVStream *st;
    AVCodecContext *enc;

    /* pts of the next frame that will be generated */
    int64_t next_pts;
    int samples_count;

    AVFrame *frame;
    AVFrame *tmp_frame;

    AVPacket *tmp_pkt;

    float t, tincr, tincr2;

    struct SwsContext *sws_ctx;
    struct SwrContext *swr_ctx;
} OutputStream;
        typedef struct {
            OutputStream video_st, audio_st;
            const AVOutputFormat *fmt;
            AVFormatContext *oc;
            int have_video, have_audio, encode_video, encode_audio;
            std::string name;
        } encode_info;

Again, changing STREAM_PIX_FMT anything other than AV_PIX_FMT_YUV420P causes the program to give the error.

What is the cause of this and how can I fix this? Also am I on the right track for fixing the pixelation problem? I'm using ubuntu.

0 Answers
Related