【问题标题】:How does FFMPEG 4.4 convert NV12 format to YUV420 on Windows?FFMPEG 4.4 如何在 Windows 上将 NV12 格式转换为 YUV420?
【发布时间】:2022-01-05 06:52:22
【问题描述】:

我想使用FFmpeg将输入的NV12格式转换为YUV420P。
我尝试使用 sws_scale 转换,但颜色都是绿色。
我已使用 sws 成功将 Y​​UYV422 转换为 420P,但无法将 NV12 转换为 YUV420。
我应该改变什么?

这是我的代码:

QStringList inList = int_put_res.split("x");

m_in_width = inList[0].toInt();

m_in_hieght = inList[1].toInt();

QStringList outList = out_put_res.split("x");

m_out_width = outList[0].toInt();

m_out_hieght = outList[1].toInt();

int VSize = m_in_width * m_in_hieght;

AVPacket *packet;

AVCodecContext *enc_ctx = NULL;

AVFrame *image_inFrame= nullptr;

packet = av_packet_alloc();

int ret = 0;

int base = 0; 

image_inFrame = av_frame_alloc();

open_encoder(m_out_width,m_out_hieght, &enc_ctx);

switch (AVPixelFormat(m_fmt_ctx->streams[m_videoindex]->codecpar->format))
{
case AV_PIX_FMT_YUV420P:
     m_yuv420p_convert_ctx = initSWS(image_inFrame,NULL,m_in_width,m_in_hieght,AV_PIX_FMT_YUV420P,AV_PIX_FMT_YUV420P);
    break;
case AV_PIX_FMT_YUYV422:
      m_yuv420p_convert_ctx = initSWS(image_inFrame,NULL,m_in_width,m_in_hieght,AV_PIX_FMT_YUYV422,AV_PIX_FMT_YUV420P);
    break;
case AV_PIX_FMT_NV12:
      m_yuv420p_convert_ctx = initSWS(image_inFrame,NULL,m_in_width,m_in_hieght,AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P);
    break;

}


m_frame = create_frame(m_out_width, m_out_hieght);

AVPacket *newpkt = av_packet_alloc();
if (!newpkt)
{
    spdlog::error("failed to alloc avpacket!");
    goto __ERROR;
}
if (!m_fmt_ctx)
{

    goto __ERROR;
}
while (m_isStop != true)
{
    if(av_read_frame(m_fmt_ctx, packet) != 0) {
        goto __ERROR;
    }

    getSWS(m_yuv420p_convert_ctx,packet,image_inFrame,m_frame);
    av_packet_unref(packet);


    m_frame->pts = base++;
    
}

SwsContext *FFSDK::initSWS(AVFrame *scrFrame, AVFrame *dstFrame, int 
image_width, int image_height,enum AVPixelFormat scrFmt, enum 
AVPixelFormat dstFmt)

if(scrFrame != NULL){
    av_image_alloc(scrFrame->data, scrFrame->linesize,image_width, image_height, scrFmt, 1);
}

if(dstFrame != NULL){
    av_image_alloc(dstFrame->data, dstFrame->linesize, m_out_width, m_out_hieght, dstFmt, 1);
}

return  sws_getContext(image_width,image_height,
                       scrFmt,
                       m_out_width, m_out_hieght, dstFmt,
                       SWS_BICUBIC,
                       nullptr,
                       nullptr,
                       nullptr);

}

void FFSDK::getSWS(SwsContext *context,AVPacket *packet,AVFrame *scrFrame, AVFrame *dstFrame)
{

    scrFrame->data[0] = packet->data;
    sws_scale(context,
              scrFrame->data,
              scrFrame->linesize, 0, m_in_hieght, dstFrame->data,
              dstFrame->linesize);
}

【问题讨论】:

    标签: c++ ffmpeg yuv swscale nv12-nv21


    【解决方案1】:

    从您发布的代码片段中我看不出问题。

    绿色通常是零数据的结果(Y、U 和 V 的所有元素都是零)。
    零内容的原因可能是没有任何内容写入视频帧的目标(或源)缓冲区。


    我创建了一个“自包含”代码示例,演示了使用 sws_scale 从 NV12 到 YUV420 的转换。

    • 首先使用 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 格式)。
    • 应用色彩空间转换(使用sws_scale)。
    • 将转换后的 YUV420 数据写入二进制文件(用于测试)。

    完整代码如下:

    //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_YUV420P;
        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 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 yuv420p -i yuv420_image.bin -pix_fmt rgb24 rgb_image.png
    
        f = fopen("yuv420_image.bin", "wb");
    
        if (f == NULL)
        {
            return -1;  //Error!
        }
    
        //Write Y channel to yuv420_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 yuv420_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;
    }
    

    用于验证输出:

    • 执行完代码后,执行FFmpeg(命令行工具)。
      以下命令将原始二进制帧(YUV420 格式)转换为 PNG(RGB 格式)。

        ffmpeg -y -f rawvideo -video_size 320x240 -pixel_format yuv420p -i yuv420_image.bin -pix_fmt rgb24 rgb_image.png
      

    输出样例(YUV420转PNG图片文件格式后):


    希望能帮助你找到问题...

    【讨论】:

    • 感谢提供项目,我找到了问题,现在我的问题是,获取nv12时的原始视频格式,我需要如何将nv12 AVPacket*的内容分配到一个NV12 AVFrame中正确的方式
    • 我在下面的post 中使用了类似的东西(但输入是 BGRA 而不是 NV12)。您将数据包发送到视频解码器avcodec_send_packet,并接收一帧avcodec_receive_frame。这从来都不是简单的...tutorial 解释得更好。
    • 您可以发布另一个问题。如果你这样做了,请让代码示例“自包含”——我们可以编译和执行。尝试在您的帖子中添加指向输入视频示例的链接,或构建输入视频示例的 FFmpeg 命令。如果我有时间,我会尝试发布答案。
    • 我用 av_image_fill_arrays 解决了我的问题。感谢您的示例
    猜你喜欢
    • 1970-01-01
    • 2021-11-25
    • 1970-01-01
    • 2013-08-01
    • 1970-01-01
    • 2020-12-18
    • 2022-12-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多