如何 view/save AVFrame 有格式 AV_PIX_FMT_YUVJ420P 文件

How to view/save AVFrame have format AV_PIX_FMT_YUVJ420P to file

我有一个 AVFrame,我想将它保存到文件中。如果我只存储 frame->data[0] 到文件,图像将是灰色图像,如何查看全彩色?我用的是C语言

你对我应该阅读哪些内容来理解和自己做这些事情有什么建议吗?

AV_PIX_FMT_YUVJ420P 是平面格式。 data[0] 只是一个 Y 帧(灰度),对于具有您需要考虑的颜色的完整图像: data[1]data[2] 分别用于框架的 U 和 V 部分。

似乎这种格式 (AV_PIX_FMT_YUVJ420P) 已被弃用,取而代之的是更常见的 AV_PIX_FMT_YUV420P 格式,如果您愿意,请使用它。

保存和查看图像的一种相对简单的方法是将 Y、U 和 V(平面)数据写入二进制文件,然后使用 FFmpeg CLI 将二进制文件转换为 RGB。


一些背景:

yuvj420p 在 FFmpeg (libav) 术语中应用 YUV420“全范围”格式。
我想 yuvj 中的 j 来自 JPEG - JPEG 图像使用“全范围”YUV420 格式。

大部分视频文件使用“有限范围”(或电视范围)YUV 格式。

  • “限定范围”中,Y范围为[16, 235],U范围为[16, 240],V范围为[0, 240]。
  • “全范围”时,Y范围为[0, 255],U范围为[0, 255],V范围为[0, 255]。

yuvj420p 已弃用,应该在 FFmpeg CLI 中使用 yuv420p 结合 dst_range 1(或 src_range 1)进行标记。我从来没有在 C 中寻找过定义“全范围”的方法。


yuvj420p 在 FFmpeg (libav) 中应用“平面”格式。
Y 通道、U 通道和 V 通道的独立平面。
Y 平面以全分辨率给出,U、V 在每个轴上 down-scaled 乘以 x2 的系数。

插图:

Y - data[0]:  YYYYYYYYYYYY
              YYYYYYYYYYYY
              YYYYYYYYYYYY
              YYYYYYYYYYYY
            
U - data[1]:  UUUUUU
              UUUUUU
              UUUUUU

V - data[2]:  VVVVVV
              VVVVVV
              VVVVVV

在 C 中,每个“平面”都存储在内存中的单独缓冲区中。
当将数据写入二进制文件时,我们可能只是将缓冲区一个接一个地写入文件。


为了演示,我重用了我的后续

我复制并粘贴了完整的答案,并将 YUV420 替换为 YUVJ420。

示例中,输入格式为NV12(我保留了)。 输入格式无关紧要(您可以忽略它)- 只有输出格式与您的问题相关。


我创建了一个“自包含”代码示例,演示了使用 sws_scale.

从 NV12 到 YUV420 (yuvj420p) 的转换
  • 首先使用 FFmpeg(命令行工具)构建合成输入框架。
    该命令以原始 NV12 格式创建 320x240 视频帧:
    ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -vcodec rawvideo -pix_fmt nv12 -frames 1 -f rawvideo nv12_image.bin

下一个代码示例应用以下阶段:

  • 为源帧(NV12 格式)分配内存。
  • 从二进制文件中读取 NV12 数据(用于测试)。
  • 为目标帧分配内存(YUV420 / yuvj420 格式)。
  • 应用颜色 space 转换(使用 sws_scale)。
  • 将转换后的YUV420(yuvj420)数据写入二进制文件(测试用)。

完整代码如下:

//Use extern "C", because the code is built as C++ (cpp file) and not C.
extern "C"
{
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/pixdesc.h>
#include <libavutil/imgutils.h>
}


int main()
{
    int width = 320;
    int height = 240;   //The code sample assumes height is even.
    int align = 0;
    AVPixelFormat srcPxlFormat = AV_PIX_FMT_NV12;
    AVPixelFormat dstPxlFormat = AV_PIX_FMT_YUVJ420P;
    int sts;

    //Source frame allocation
    ////////////////////////////////////////////////////////////////////////////
    AVFrame* pNV12Frame = av_frame_alloc();

    pNV12Frame->format = srcPxlFormat;
    pNV12Frame->width = width;
    pNV12Frame->height = height;

    sts = av_frame_get_buffer(pNV12Frame, align);

    if (sts < 0)
    {
        return -1;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Read NV12 data from binary file (for testing)
    ////////////////////////////////////////////////////////////////////////////
    //Use FFmpeg for building raw NV12 image (used as input).
    //ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=1 -vcodec rawvideo -pix_fmt nv12 -frames 1 -f rawvideo nv12_image.bin

    FILE* f = fopen("nv12_image.bin", "rb");

    if (f == NULL)
    {
        return -1;  //Error!
    }

    //Read Y channel from nv12_image.bin (Y channel size is width x height).
    //Reading row by row is required in rare cases when pNV12Frame->linesize[0] != width
    uint8_t* Y = pNV12Frame->data[0];   //Pointer to Y color channel of the NV12 frame.
    for (int row = 0; row < height; row++)
    {
        fread(Y + (uintptr_t)row * pNV12Frame->linesize[0], 1, width, f); //Read row (width pixels) to Y0.
    }

    //Read UV channel from nv12_image.bin (UV channel size is width x height/2).
    uint8_t* UV = pNV12Frame->data[1];   //Pointer to UV color channels of the NV12 frame (ordered as UVUVUVUV...).
    for (int row = 0; row < height / 2; row++)
    {
        fread(UV + (uintptr_t)row * pNV12Frame->linesize[1], 1, width, f); //Read row (width pixels) to UV0.
    }

    fclose(f);
    ////////////////////////////////////////////////////////////////////////////



    //Destination frame allocation
    ////////////////////////////////////////////////////////////////////////////
    AVFrame* pYUV420Frame = av_frame_alloc();

    pYUV420Frame->format = dstPxlFormat;
    pYUV420Frame->width = width;
    pYUV420Frame->height = height;

    sts = av_frame_get_buffer(pYUV420Frame, align);

    if (sts < 0)
    {
        return -1;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Color space conversion
    ////////////////////////////////////////////////////////////////////////////
    SwsContext* sws_context = sws_getContext(width,
        height,
        srcPxlFormat,
        width,
        height,
        dstPxlFormat,
        SWS_FAST_BILINEAR,
        NULL,
        NULL,
        NULL);

    if (sws_context == NULL)
    {
        return -1;  //Error!
    }

    sts = sws_scale(sws_context, //struct SwsContext* c,
        pNV12Frame->data,        //const uint8_t* const srcSlice[],
        pNV12Frame->linesize,    //const int srcStride[],
        0,                       //int srcSliceY, 
        pNV12Frame->height,      //int srcSliceH,
        pYUV420Frame->data,      //uint8_t* const dst[], 
        pYUV420Frame->linesize); //const int dstStride[]);    

    if (sts != pYUV420Frame->height)
    {
        return -1;  //Error!
    }
    ////////////////////////////////////////////////////////////////////////////


    //Write YUV420 (yuvj420p) data to binary file (for testing)
    ////////////////////////////////////////////////////////////////////////////
    //Use FFmpeg for converting the binary image to PNG after saving the data.
    //ffmpeg -y -f rawvideo -video_size 320x240 -pixel_format yuvj420p -i yuvj420_image.bin -pix_fmt rgb24 rgb_image.png

    f = fopen("yuvj420_image.bin", "wb");

    if (f == NULL)
    {
        return -1;  //Error!
    }

    //Write Y channel to yuvj420_image.bin (Y channel size is width x height).
    //Writing row by row is required in rare cases when pYUV420Frame->linesize[0] != width
    Y = pYUV420Frame->data[0];   //Pointer to Y color channel of the YUV420 frame.
    for (int row = 0; row < height; row++)
    {
        fwrite(Y + (uintptr_t)row * pYUV420Frame->linesize[0], 1, width, f); //Write row (width pixels) to file.
    }

    //Write U channel to yuvj420_image.bin (U channel size is width/2 x height/2).
    uint8_t* U = pYUV420Frame->data[1];   //Pointer to U color channels of the YUV420 frame.
    for (int row = 0; row < height / 2; row++)
    {
        fwrite(U + (uintptr_t)row * pYUV420Frame->linesize[1], 1, width / 2, f); //Write row (width/2 pixels) to file.
    }

    //Write V channel to yuv420_image.bin (V channel size is width/2 x height/2).
    uint8_t* V = pYUV420Frame->data[2];   //Pointer to V color channels of the YUV420 frame.
    for (int row = 0; row < height / 2; row++)
    {
        fwrite(V + (uintptr_t)row * pYUV420Frame->linesize[2], 1, width / 2, f); //Write row (width/2 pixels) to file.
    }

    fclose(f);
    ////////////////////////////////////////////////////////////////////////////


    //Cleanup
    ////////////////////////////////////////////////////////////////////////////
    sws_freeContext(sws_context);
    av_frame_free(&pYUV420Frame);
    av_frame_free(&pNV12Frame);
    ////////////////////////////////////////////////////////////////////////////

    return 0;
}

执行显示一条警告消息(我们可以忽略):

[swscaler @ 000002a19227e640] deprecated pixel format used, make sure you did set range correctly


查看彩色图像输出:

  • 执行代码后,执行FFmpeg(命令行工具)
    以下命令将原始二进制帧(YUV420 / yuvj420p 格式)转换为 PNG(RGB 格式)。
    ffmpeg -y -f rawvideo -video_size 320x240 -pixel_format yuvj420p -i yuvj420_image.bin -pix_fmt rgb24 rgb_image.png

示例输出(从 yuvj420p 转换为 PNG 图像文件格式后):