编码单帧时如何使用AVCodecContext比特率、帧率和时基

vu8f3i0k  于 2023-04-05  发布在  其他
关注(0)|答案(1)|浏览(202)

我正在尝试从示例中学习FFmpeg,因为时间很紧。任务是将原始YUV图像编码为给定宽度和高度的JPEG格式。我在FFmpeg官方网站上找到了示例,结果证明非常简单。然而,AVCodecContext中有一些字段我认为只有在编码视频时才有意义(例如bitrate,framerate,timebase,gopsize,max_b_frames等)。
对于视频,我很清楚这些值是什么,但是当我只想要一张图像时,我需要关心这些值吗?目前为了测试,我只是将它们设置为虚拟值,它似乎有效。但我想确保我没有做出从长远来看会失败的可怕假设。
编辑:
这是我得到的代码。大多数都是从示例中复制和粘贴的,其中一些更改是用新的API替换旧的API。

#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;
}
e0bqpujr

e0bqpujr1#

正如你已经知道的,比特率,帧率,时基,gopsize和max_b_frames参数在编码单个JPEG图像时没有影响。
正如我们所看到的,FFmpeg使用MJPEG编解码器对JPEG图像进行编码。
“Motion JPEG”编码器支持图像编码和视频编码,因此编码器具有视频相关参数。

  • bitrate无效,因为MJPEG编码器使用qcompress参数设置质量级别。

在支持bitrate的编解码器中,更高的视频比特率允许更高的视频质量。
MJPEG编码器不支持bitrate(即使用于编码视频)。
qcompress是范围[0,1]中的值,其中0是最差质量,1是最佳质量。

  • framerate在编码图像时无效(framerate参数仅与视频相关)。

Sine MJPEG编解码器可用于图像和视频,编码器支持framerate参数。(framerate可能仅在编码Motion JPEG视频时相关,例如使用视频容器作为AVI)。

  • gopsize是视频相关参数。

在MJPEG格式中,无论gopsize参数的值如何,所有帧都是关键帧,并且gopsize=1。
当对图像进行编码时,GOP是无意义的(GOP在视频中应用“图片组”)。

  • 在FFmpeg的最新版本中,max_b_frames必须是0(否则会出现错误)。

由于MJPEG中的所有帧都是关键帧,因此它们不能是B帧(所有关键帧都是I帧)。
测试:
为了制作可再现的示例,我们可以使用FFmpeg CLI以yuv420p像素格式创建原始帧/图像:
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
由于我使用的是Windows,并且使用的是FFmpeg版本5.1.2,所以我不得不对您的代码示例进行一些修改。
下面的代码示例,还包括一个main函数,它读取thumbnail.yuv,并执行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;
}

输出图像(thumbnail.jpg):

相关问题