Search code examples
cffmpeg

How AVCodecContext bitrate, framerate and timebase is used when encoding single frame


I am trying to learn FFmpeg from examples as there is a tight schedule. The task is to encode a raw YUV image into JPEG format of the given width and height. I have found examples from ffmpeg official website, which turns out to be quite straight-forward. However there are some fields in AVCodecContext that I thought only makes sense when encoding videos(e.g. bitrate, framerate, timebase, gopsize, max_b_frames etc).

I understand on a high level what those values are when it comes to videos, but do I need to care about those when I just want a single image? Currently for testing, I am just setting them as dummy values and it seems to work. But I want to make sure that I am not making terrible assumptions that will break in the long run.

EDIT:

Here is the code I got. Most of them are copy and paste from examples, with some changes to replace old APIs with newer ones.

#include "thumbnail.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>

void print_averror(int error_code) {
    char err_msg[100] = {0};
    av_strerror(error_code, err_msg, 100);
    printf("Reason: %s\n", err_msg);
}

ffmpeg_status_t save_yuv_as_jpeg(uint8_t* source_buffer, char* output_thumbnail_filename, int thumbnail_width, int thumbnail_height) {
    const AVCodec* mjpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (!mjpeg_codec) {
        printf("Codec for mjpeg cannot be found.\n");
        return FFMPEG_THUMBNAIL_CODEC_NOT_FOUND;
    }

    AVCodecContext* codec_ctx = avcodec_alloc_context3(mjpeg_codec);
    if (!codec_ctx) {
        printf("Codec context cannot be allocated for the given mjpeg codec.\n");
        return FFMPEG_THUMBNAIL_ALLOC_CONTEXT_FAILED;
    }

    AVPacket* pkt = av_packet_alloc();
    if (!pkt) {
        printf("Thumbnail packet cannot be allocated.\n");
        return FFMPEG_THUMBNAIL_ALLOC_PACKET_FAILED;
    }

    AVFrame* frame = av_frame_alloc();
    if (!frame) {
        printf("Thumbnail frame cannot be allocated.\n");
        return FFMPEG_THUMBNAIL_ALLOC_FRAME_FAILED;
    }

    // The part that I don't understand
    codec_ctx->bit_rate = 400000;
    codec_ctx->width = thumbnail_width;
    codec_ctx->height = thumbnail_height;
    codec_ctx->time_base = (AVRational){1, 25};
    codec_ctx->framerate = (AVRational){1, 25};

    codec_ctx->gop_size = 10;
    codec_ctx->max_b_frames = 1;
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    int ret = av_image_fill_arrays(frame->data, frame->linesize, source_buffer, AV_PIX_FMT_YUV420P, thumbnail_width, thumbnail_height, 32);
    if (ret < 0) {
        print_averror(ret);
        printf("Pixel format: yuv420p, width: %d, height: %d\n", thumbnail_width, thumbnail_height);
        return FFMPEG_THUMBNAIL_FILL_FRAME_DATA_FAILED;
    }

    ret = avcodec_send_frame(codec_ctx, frame);
    if (ret < 0) {
        print_averror(ret);
        printf("Failed to send frame to encoder.\n");
        return FFMPEG_THUMBNAIL_FILL_SEND_FRAME_FAILED;
    }

    ret = avcodec_receive_packet(codec_ctx, pkt);
    if (ret < 0) {
        print_averror(ret);
        printf("Failed to receive packet from encoder.\n");
        return FFMPEG_THUMBNAIL_FILL_SEND_FRAME_FAILED;
    }

    // store the thumbnail in output
    int fd = open(output_thumbnail_filename, O_CREAT | O_RDWR);
    write(fd, pkt->data, pkt->size);
    close(fd);

    // freeing allocated structs
    avcodec_free_context(&codec_ctx);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    return FFMPEG_SUCCESS;
}

Solution

  • As you already figured out, bitrate, framerate, timebase, gopsize and max_b_frames parameters have no effect when encoding a single JPEG image.

    As we can see, FFmpeg uses MJPEG codec for encoding JPEG images.
    The "Motion JPEG" encoder supports both image encoding and video encoding, so the encoder has video related parameters.

    • bitrate has no effect because MJPEG encoder uses qcompress parameter for setting the quality level.
      In codecs that support bitrate, higher video bitrate allows higher video quality.
      MJPEG encoder doesn't support bitrate (even when used for encoding video).
      qcompress is a value in range [0, 1], when 0 is the worst quality and 1 is the best quality.
    • framerate has no effect when encoding images (framerate parameter is relevant only for video).
      Sine MJPEG codec may be used both for images and video, the encoder supports framerate parameter. (The framerate may be relevant only when encoding Motion JPEG video, and using video container as AVI for example).
    • gopsize is video related parameter.
      In MJPEG format, all frames are key frames and gopsize=1, regardless the value of gopsize parameter.
      When encoding an image, GOP is meaningless (GOP applies "group of pictures" in a video).
    • max_b_frames must be 0 in latest versions of FFmpeg (there is an error otherwize).
      Since all frames are key frames in MJPEG, they can't be B-Frames (all key frames are I-Frames).

    Testing:

    For making a reproducible example, we may create raw frame/image in yuv420p pixel format using FFmpeg CLI:

    ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vf scale=out_color_matrix=bt601:out_range=pc -f rawvideo -pix_fmt yuv420p thumbnail.yuv

    Since I am using Windows, and using FFmpeg version 5.1.2, I had to make few modifications to your code sample.
    The following code sample, also include a main function that reads thumbnail.yuv, and execute save_yuv_as_jpeg:

    //#include "thumbnail.h" //Non-standard header
    #include "libavcodec/avcodec.h"
    #include "libavutil/imgutils.h"
    #include <stdint.h>
    //#include <fcntl.h> 
    //#include <unistd.h> //Avoid Linux headers (I am using Windows).
    #include <stdio.h>
    
    typedef int ffmpeg_status_t;
    
    void print_averror(int error_code) {
        char err_msg[100] = {0};
        av_strerror(error_code, err_msg, 100);
        printf("Reason: %s\n", err_msg);
    }
    
    ffmpeg_status_t save_yuv_as_jpeg(uint8_t* source_buffer, const char* output_thumbnail_filename, int thumbnail_width, int thumbnail_height) {
        const AVCodec* mjpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
        if (!mjpeg_codec) {
            printf("Codec for mjpeg cannot be found.\n");
            return -1;//FFMPEG_THUMBNAIL_CODEC_NOT_FOUND;
        }
    
        AVCodecContext* codec_ctx = avcodec_alloc_context3(mjpeg_codec);
        if (!codec_ctx) {
            printf("Codec context cannot be allocated for the given mjpeg codec.\n");
            return -2;//FFMPEG_THUMBNAIL_ALLOC_CONTEXT_FAILED;
        }
    
        AVPacket* pkt = av_packet_alloc();
        if (!pkt) {
            printf("Thumbnail packet cannot be allocated.\n");
            return -3;//FFMPEG_THUMBNAIL_ALLOC_PACKET_FAILED;
        }
    
        AVFrame* frame = av_frame_alloc();
        if (!frame) {
            printf("Thumbnail frame cannot be allocated.\n");
            return -4;//FFMPEG_THUMBNAIL_ALLOC_FRAME_FAILED;
        }
    
        // The part that I don't understand
        codec_ctx->bit_rate = 400000;
        codec_ctx->width = thumbnail_width;
        codec_ctx->height = thumbnail_height;
        codec_ctx->time_base = (AVRational){1, 25};
        codec_ctx->framerate = (AVRational){25, 1};
    
        codec_ctx->gop_size = 10;
        codec_ctx->max_b_frames = 0;//1;  //Error: B-frames not supported by codec
        codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P; //= AV_PIX_FMT_YUV420P; //Error: Non full-range YUV is non-standard, set strict_std_compliance to at most unofficial to use it.
    
        //Select qualtiy level (0.5 is the default, and 1.0 is the maximum quality).
        codec_ctx->qcompress = 0.9;
    
        //open the codec
        int ret = avcodec_open2(codec_ctx, mjpeg_codec, NULL);
        if (ret < 0) {
            print_averror(ret);
            exit(1);
        }
    
    
        ret = av_image_fill_arrays(frame->data, frame->linesize, source_buffer, AV_PIX_FMT_YUV420P, thumbnail_width, thumbnail_height, 32);
        if (ret < 0) {
            print_averror(ret);
            printf("Pixel format: yuv420p, width: %d, height: %d\n", thumbnail_width, thumbnail_height);
            return -5;//FFMPEG_THUMBNAIL_FILL_FRAME_DATA_FAILED;
        }
    
        //We have to fill at least the width, hight and format
        frame->width = thumbnail_width;
        frame->height = thumbnail_height;
        frame->format = AV_PIX_FMT_YUVJ420P;
    
    
        ret = avcodec_send_frame(codec_ctx, frame);
        if (ret < 0) {
            print_averror(ret);
            printf("Failed to send frame to encoder.\n");
            return -6;//FFMPEG_THUMBNAIL_FILL_SEND_FRAME_FAILED;
        }
    
        ret = avcodec_receive_packet(codec_ctx, pkt);
        if (ret < 0) {
            print_averror(ret);
            printf("Failed to send frame to encoder.\n");
            return -7;//FFMPEG_THUMBNAIL_FILL_SEND_FRAME_FAILED;
        }
    
        // store the thumbnail in output
        //int fd = open(output_thumbnail_filename, O_CREAT | O_RDWR);
        //write(fd, pkt->data, pkt->size);
        //close(fd);
        FILE *f = fopen(output_thumbnail_filename, "wb");
        fwrite(pkt->data, 1, pkt->size, f);
        fclose(f);
    
        // freeing allocated structs
        avcodec_free_context(&codec_ctx);
        av_frame_free(&frame);
        av_packet_free(&pkt);
        return 0;//FFMPEG_SUCCESS;
    }
    
    //Building input file in YUV format using FFmpeg CLI:
    //ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -vf scale=out_color_matrix=bt601:out_range=pc -f rawvideo -pix_fmt yuv420p thumbnail.yuv
    
    int main()
    {
        ffmpeg_status_t sts;
        const char* output_thumbnail_filename = "thumbnail.jpg";
    
        const char* input_thumbnail_filename = "thumbnail.yuv";
        int thumbnail_width = 192;
        int thumbnail_height = 108;
    
        uint8_t* source_buffer = malloc(thumbnail_width*thumbnail_height*3/2);  //Buffer for storing input in YUV420 pixels format
    
        //Read thumbnail.yuv to source_buffer
        FILE *f = fopen(input_thumbnail_filename, "rb");
        fread(source_buffer, 1, thumbnail_width*thumbnail_height*3/2, f);
        fclose(f);
    
        sts = save_yuv_as_jpeg(source_buffer, output_thumbnail_filename, thumbnail_width, thumbnail_height);
    
        free(source_buffer);
    
        return (int)sts;
    }
    

    Output image (thumbnail.jpg):
    enter image description here