如何 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 图像文件格式后):
我有一个 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
.
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 图像文件格式后):