如何使用 ffmpeg 的库将 YUV420P 图像转换为 JPEG?

How to convert YUV420P image to JPEG using ffmpeg's libraries?

我正在尝试使用 ffmpeg 的 libavformatlibavcodec 将 YUV420P 图像 (AV_PIX_FMT_YUV420P) 转换为 JPEG。到目前为止,这是我的代码:

AVFormatContext* pFormatCtx;
AVOutputFormat* fmt;
AVStream* video_st;
AVCodecContext* pCodecCtx;
AVCodec* pCodec;

uint8_t* picture_buf;
AVFrame* picture;
AVPacket pkt;
int y_size;
int got_picture=0;
int size;

int ret=0;

FILE *in_file = NULL;                            //YUV source
int in_w = 720, in_h = 576;                      //YUV's width and height
const char* out_file = "encoded_pic.jpg";    //Output file

in_file = fopen(argv[1], "rb");
av_register_all();

pFormatCtx = avformat_alloc_context();

fmt = NULL;
fmt = av_guess_format("mjpeg", NULL, NULL);
pFormatCtx->oformat = fmt;

//Output URL
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0)
{
    printf("Couldn't open output file.");
    return -1;
}

video_st = avformat_new_stream(pFormatCtx, 0);

if (video_st==NULL)
    return -1;

pCodecCtx = video_st->codec;
pCodecCtx->codec_id = fmt->video_codec;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

pCodecCtx->width = in_w;
pCodecCtx->height = in_h;

pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;

//Output some information
av_dump_format(pFormatCtx, 0, out_file, 1);

pCodec = avcodec_find_encoder(pCodecCtx->codec_id);

if (!pCodec)
{
    printf("Codec not found.");
    return -1;
}

if (avcodec_open2(pCodecCtx, pCodec,NULL) < 0){
    printf("Could not open codec.\n");
    return -1;
}

picture = avcodec_alloc_frame();
size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

picture_buf = (uint8_t *)av_malloc(size);

if (!picture_buf)
    return -1;

avpicture_fill((AVPicture *)picture, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

//Write Header
avformat_write_header(pFormatCtx,NULL);

y_size = pCodecCtx->width * pCodecCtx->height;
av_new_packet(&pkt,y_size*3);

//Read YUV
if (fread(picture_buf, 1, y_size*3/2, in_file) <=0)
{
    printf("Could not read input file.");
    return -1;
}

picture->data[0] = picture_buf;  // Y
picture->data[1] = picture_buf+ y_size;  // U
picture->data[2] = picture_buf+ y_size*5/4; // V

//Encode
ret = avcodec_encode_video2(pCodecCtx, &pkt,picture, &got_picture);
if(ret < 0)
{
    printf("Encode Error.\n");
    return -1;
}

if (got_picture==1)
{
    pkt.stream_index = video_st->index;
    ret = av_write_frame(pFormatCtx, &pkt);
}

av_free_packet(&pkt);
//Write Trailer
av_write_trailer(pFormatCtx);

printf("Encode Successful.\n");

if (video_st)
{
    avcodec_close(video_st->codec);
    av_free(picture);
    av_free(picture_buf);
}

avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);

fclose(in_file);

我收到这个错误:

Output #0, mjpeg, to 'encoded_pic.jpg':
Stream #0.0: Video: mjpeg, yuv420p, 720x576, q=2-31, 200 kb/s, 90k tbn, 25 tbc
[mjpeg @ 0x78c3a0] Specified pix_fmt is not supported
Could not open codec.

是否在 ffmpeg 的文档中提到不允许进行此转换?如果有,我们如何规避它?当我们使用命令行版本的ffmpeg时,ffmpeg内部是如何转换的?

以下code是您的问题:

.pix_fmts       = (const enum AVPixelFormat[]){
    AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_NONE
},

如你所见,它想要AV_PIX_FMT_YUVJ420P,而不是YUV420P。这是一个旧的遗留物,在很大程度上被认为已弃用,但它尚未被删除或标记为删除,并且仍在某些地方使用。 YUVJ420P的字面意思是"YUV420P with JPEG color-range"(即像素使用0-255范围而不是16-235范围,其中0而不是16是黑色,255而不是235是白色)。您也可以使用 AV_PIX_FMT_YUV420P 然后设置例如avctx->color_range 到 AVCOL_RANGE_JPEG.

你可能不关心这个,你想知道,如何将AV_PIX_FMT_YUV420P转换为AV_PIX_FMT_YUVJ420P?那么,在这种情况下,您的输入是 JPEG,因此它可能使用 JPEG 范围数据(通过检查 avctx->color_range 或 avframe->color_range 来检查)。然后你可以简单地将 avframe->pix_fmt 从 AV_PIX_FMT_YUV420P 更改为 AV_PIX_FMT_YUVJ420P。如果出于某种原因 color_range 是 AVCOL_RANGE_MPEG,您可以使用 libswscale to convert between the AV_PIX_FMT_YUV420P with MPEG-range and AV_PIX_FMT_YUVJ420P, see e.g. this example.