有关ffmpeg中主要的api函数源码解析参考雷神系列文章,整理如下ffmpeg学习(2)获取和使用,源码分析。
函数sws_scale位于libswscale库,该库是一个主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作。libswscale的使用参考文章:
FFmpeg源代码简单分析:libswscale的sws_getContext()
最简单的基于FFmpeg的libswscale的示例(YUV转RGB)
本文仅演示的程序将像素格式为YUV420P,分辨率为480x272的视频转换为像素格式为RGB24。另外,还记录将非1字节对齐的AVFrame转换为1字节对齐,便于保存处理。
简单的初始化方法
Libswscale使用起来很方便,最主要的函数只有3个:
(1) sws_getContext()
:使用参数初始化SwsContext
结构体。
(2) sws_scale()
:转换一帧图像。
(3) sws_freeContext()
:释放SwsContext
结构体。
其中sws_getContext()
也可以用另一个接口函数sws_getCachedContext()
取代。
复杂但是更灵活的初始化方法
初始化SwsContext
除了调用sws_getContext()
之外还有另一种方法,更加灵活,可以配置更多的参数。该方法调用的函数如下所示。
(1) sws_alloc_context()
:为SwsContext
结构体分配内存。
(2) av_opt_set_XXX()
:通过av_opt_set_int()
,av_opt_set()
…等等一系列方法设置SwsContext
结构体的值。在这里需要注意,SwsContext
结构体的定义看不到,所以不能对其中的成员变量直接进行赋值,必须通过av_opt_set()
这类的API才能对其进行赋值。
(3) sws_init_context()
:初始化SwsContext
结构体。
这种复杂的方法可以配置一些sws_getContext()
配置不了的参数。比如说设置图像的YUV像素的取值范围是JPEG标准(Y、U、V取值范围都是0-255)还是MPEG标准(Y取值范围是16-235,U、V的取值范围是16-240)。
后面使用函数sws_getContext()
和sws_scale()
完成本文目标。
下文记录几个图像像素数据处理过程中的几个基本知识点:像素格式,图像拉伸,YUV像素取值范围,色域。这里仅说明像素格式,其他可以参考相关博客。
像素格式的知识此前已经记录过,不再重复。在这里记录一下FFmpeg支持的像素格式。有几点注意事项:
(1) 所有的像素格式的名称都是以“AV_PIX_FMT_”开头
(2) 像素格式名称后面有“P”的,代表是planar格式,否则就是packed格式。Planar格式不同的分量分别存储在不同的数组中,例如AV_PIX_FMT_YUV420P存储方式如下:
data[0]: Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
data[1]: U1, U2, U3, U4……
data[2]: V1, V2, V3, V4……
Packed格式的数据都存储在同一个数组中,例如AV_PIX_FMT_RGB24存储方式如下:
data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……
(3) 像素格式名称后面有“BE”的,代表是Big Endian格式;名称后面有“LE”的,代表是Little Endian格式。
FFmpeg支持的像素格式的定义位于libavutil\pixfmt.h,是一个名称为AVPixelFormat的枚举类型。
FFmpeg有一个专门用于描述像素格式的结构体AVPixFmtDescriptor
。该结构体的定义位于libavutil\pixdesc.h。通过av_pix_fmt_desc_get()可以获得指定像素格式的AVPixFmtDescriptor结构体。通过AVPixFmtDescriptor结构体可以获得不同像素格式的一些信息。例如下文中用到了av_get_bits_per_pixel()
,通过该函数可以获得指定像素格式每个像素占用的比特数(Bit Per Pixel)
示例程序包含一个输入和一个输出,实现了从输入图像格式(YUV420P)到输出图像格式(RGB24)之间的转换,并且调整了分辨率。
#include
#ifdef __cplusplus
extern "C" {
#endif
#include "libswscale/swscale.h"
#include "libavutil/pixdesc.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#ifdef __cplusplus
}
#endif
int main()
{
//输入数据和参数
FILE *src_file = fopen("Titanic_640x272_yuv420p.yuv", "rb");
const int src_w = 640, src_h = 272;
AVPixelFormat src_pixfmt = AV_PIX_FMT_YUV420P;
int src_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(src_pixfmt));
FILE *dst_file = fopen("Titanic_640x480_rgb24.rgb", "wb");
const int dst_w = 640, dst_h = 480;
AVPixelFormat dst_pixfmt = AV_PIX_FMT_RGB24;
int dst_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(dst_pixfmt));
//使用AVFrame分配缓存yuv和rgb数据,用于转换
AVFrame *frame_yuv = av_frame_alloc();
av_image_alloc(frame_yuv->data, frame_yuv->linesize, src_w, src_h, src_pixfmt, 1);
AVFrame *frame_rgb = av_frame_alloc();
av_image_alloc(frame_rgb->data, frame_rgb->linesize, dst_w, dst_h, dst_pixfmt, 1);
// 读取yuv图像数据到frame_yuv缓存
if(fread(frame_yuv->data[0], 1, src_w*src_h*src_bpp / 8, src_file) != src_w*src_h*src_bpp / 8) {
return 0;
}
// 转换
SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(src_w, src_h, src_pixfmt,
dst_w,dst_h, dst_pixfmt,
SWS_BILINEAR,
NULL, NULL,NULL);
sws_scale(img_convert_ctx,
frame_yuv->data, frame_yuv->linesize, 0, src_h,
frame_rgb->data, frame_rgb->linesize);
// 保存rgb数据, Packed方式,数据全部保存在 frame_rgb->data[0]中
fwrite(frame_rgb->data[0], 1, dst_w*dst_h * dst_bpp / 8, dst_file); // Packed
// 释放
sws_freeContext(img_convert_ctx);
av_frame_free(&frame_yuv);
av_frame_free(&frame_rgb);
fclose(src_file);
fclose(dst_file);
}
在前面博客 ffmpeg学习(5)视频解码 中,解码出来AVFrame缓存的yuv数据非1字节对齐,并且三个分量保存在不同的内存区域,保存方式相对繁琐。这里再次给出
这里使用libswscale库函数sws_scale将解码后的图像转换为1字节对齐的yuv420p数据。
在源代码中添加 以下变量声明与初始化部分
// yuv420p对齐处理 变量
AVFrame *frame_yuv = av_frame_alloc();
// 分配缓冲区,接收转换后yuv420p的1字节对齐数据,分辨率不改变
frame_yuv->width = video_decoder_ctx->width;
frame_yuv->height = video_decoder_ctx->height;
av_image_alloc(frame_yuv->data, frame_yuv->linesize,
frame_yuv->width, frame_yuv->height, AV_PIX_FMT_YUV420P, 1);
// SwsContext上下文,用于sws_scale调用
SwsContext *sws_ctx =
sws_getContext(video_decoder_ctx->width, video_decoder_ctx->height, video_decoder_ctx->pix_fmt, // 输入格式
frame_yuv->width, frame_yuv->height, AV_PIX_FMT_YUV420P, // 输出格式
SWS_BILINEAR, NULL, NULL, NULL); // 变换处理
只会在解码后使用如下代码,进行yuv数据处理并保存在文件中
// 解码的视频数据处理
sws_scale(sws_ctx,
frame->data, frame->linesize, 0, frame->height,
frame_yuv->data, frame_yuv->linesize);
fwrite(frame_yuv->data[0], 1, frame_yuv->width*frame_yuv->height * 3 / 2, fyuv);
printf("\rSucceed to decode frame %d\n", frameCnt++);
这种方式更为简单。