作者:叶余
来源:https://www.cnblogs.com/leisure_chn/p/14349382.html
libswscale 是 FFmpeg 中完成图像尺寸缩放和像素格式转换的库。用户可以编写程序,调用 libswscale 提供的 API 来进行图像尺寸缩放和像素格式转换。也可以使用 scale 滤镜完成这些功能,scale 滤镜实现中调用了 libswscale 的 API。libswscale 的 API 非常简单,就一个 sws_scale() 接口,但内部的实现却非常复杂。
本文分析 libswscale 源码,因篇幅较长,遂拆分成下面一系列文章:
[1]. FFmpeg libswscale源码分析1-API介绍
[2]. FFmpeg libswscale源码分析2-转码命令行与滤镜图
[3]. FFmpeg libswscale源码分析3-scale滤镜源码分析
[4]. FFmpeg libswscale源码分析4-libswscale源码分析
源码分析基于 FFmpeg 4.1 版本。
1. API 介绍
1.1 相关基础概念
在解释具体的函数前,必须理解与像素格式相关的几个基础概念:参色彩空间与像素格式一文第 4.1 节
pixel_format:像素格式,图像像素在内存中的排列格式。一种像素格式包含有色彩空间、采样方式、存储模式、位深等信息,其中体现的最重要信息就是存储模式,具体某一类的存储模式参照本文第 2 节、第 3 节。
bit_depth: 位深,指每个分量(Y、U、V、R、G、B 等)单个采样点所占的位宽度。
例如对于 yuv420p(位深是8)格式而言,每一个 Y 样本、U 样本和 V 样本都是 8 位的宽度,只不过在水平方向和垂直方向,U 样本数目和 V 样本数目都只有 Y 样本数目的一半。而 bpp (Bits Per Pixel)则是将图像总比特数分摊到每个像素上,计算出平均每个像素占多少个 bit,例如 yuv420p 的 bpp 是 12,表示平均每个像素占 12 bit(Y占8位、U占2位、V占2位),实际每个 U 样本和 V 样本都是 8 位宽度而不是 2 位宽度。
plane: 存储图像中一个或多个分量的一片内存区域。一个 plane 包含一个或多个分量。planar 存储模式中,至少有一个分量占用单独的一个 plane,具体到 yuv420p 格式有 Y、U、V 三个 plane,nv12 格式有 Y、UV 两个 plane,gbrap 格式有 G、B、R、A 四个 plane。packed 存储模式中,因为所有分量的像素是交织存放的,所以 packed 存储模式只有一个 plane。
slice: slice 是 FFmpeg 中使用的一个内部结构,在 codec、filter 中常有涉及,通常指图像中一片连续的行,表示将一帧图像分成多个片段。注意 slice 是针对图像分片,而不是针对 plane 分片,一帧图像有多个 plane,一个 slice 里同样包含多个 plane。
stride/pitch: 一行图像中某个分量(如亮度分量或色度分量)所占的字节数, 也就是一个 plane 中一行数据的宽度。有对齐要求,计算公式如下:
stride 值 = 图像宽度 * 分量数 * 单样本位宽度 / 水平子采样因子 / 8
其中,图像宽度表示图像宽度是多少个像素,分量数指当前 plane 包含多少个分量(如 rgb24 格式一个 plane 有 R、G、B 三个分量),单位本位宽度指某分量的一个样本在考虑对齐后在内存中占用的实际位数(例如位深 8 占 8 位宽,位深 10 实际占 16 位宽,对齐值与平台相关),水平子采样因子指在水平方向上每多少个像素采样出一个色度样本(亮度样本不进行下采样,所以采样因子总是 1)。
需要注意的是,stride 考虑的是 plane 中的一行。对 yuv420p 格式而言,Y 分量是完全采样,因此一行 Y 样本数等于图像宽度,U 分量和 V 分量水平采样因子是 2(每两个像素采样出一个U样本和V样本),因此一行 U 样本数和一行 V 样本数都等于图像宽度的一半。U 分量和 V 分量垂直采样因子也是 2,因此 U 分量和 V 分量的行数少了,只有图像高度的一半,但垂直方向的采样率并不影响一个 plane 的 stride 值,因为 stride 的定义决定了其值只取决于水平方向的采样率。
若源图像像素格式是 yuv420p(有 Y、U、V 三个 plane),位深是 8(每一个Y样本、U样本、V样本所占位宽度是 8 位),分辨率是 1280x720,则在 Y plane 的一行数据中,有 1280 个 Y 样本,占用 1280 个字节,stride 值是 1280;在 U plane 的一行数据中,有 640 个 U 样本,占用 640 个字节,stride 值是 640;在 V plane 的一行数据中,有 640 个样本,占用 640 个字节,stride 值是 640。
若源图像像素格式是 yuv420p10(有 Y、U、V 三个 plane),位深是 10 (内存对齐后每个样本占 16 位),分辨率仍然是 1280x720,则 Y plane 的 stride 值为 1280 x 16 / 8 = 2560,U plane stride 值为 640 x 16 / 8 = 1280,V plane stride 值为 640 x 16 / 8 = 1280。
若源图像像素格式是 yuv420p16le(有 Y、U、V 三个 plane),位深是 16,分辨率仍然是 1280x720,则 Y plane 的 stride 值为 1280 x 16 / 8 = 2560,U plane stride 值为 640 x 16 / 8 = 1280,V plane stride 值为 640 x 10 / 8 = 1280。
若源图像像素格式是 p010le(有 Y、UV 两个 plane),位深是 10 (内存对齐后,每个样本占 16 位),分辨率仍然是 1280x720,则 Y plane 的 stride 值为 1280 x 16 / 8 = 2560,UV plane stride 值为 640 x 2 x 16 / 8 = 2560。
若源图像像素格式是 bgr24(有 BGR 一个 plane),位深是 8,分辨率仍然是 1280x720。因 bgr24 像素格式是 packed 存储模式,每个像素 R、G、B 三个采样点交织存放,内存区的排列形式为 BGRBGR...,因此可以认为它只有一个 plane,此 plane 中一行图像有 1280 个 R 样本,1280 个 G 样本,1280 个 B 样本,此 plane 的 stride 值为 1280 x 3 x 8 / 8 = 3840。
1.2 初始化函数 sws_getContext()
sws_getContext()函数将创建一个 SwsContext,后续使用 sws_scale() 执行缩放/格式转换操作时需要用到此 SwsContext。
/** * Allocate and return an SwsContext. You need it to perform * scaling/conversion operations using sws_scale(). * * @param srcW the width of the source image * @param srcH the height of the source image * @param srcFormat the source image format * @param dstW the width of the destination image * @param dstH the height of the destination image * @param dstFormat the destination image format * @param flags specify which algorithm and options to use for rescaling * @param param extra parameters to tune the used scaler * For SWS_BICUBIC param[0] and [1] tune the shape of the basis * function, param[0] tunes f(1) and param[1] f´(1) * For SWS_GAUSS param[0] tunes the exponent and thus cutoff * frequency * For SWS_LANCZOS param[0] tunes the width of the window function * @return a pointer to an allocated context, or NULL in case of error * @note this function is to be removed after a saner alternative is * written */ struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
函数参数及返回值说明如下:
@param srcW
srcW 是源图像的宽度。
@param srcH
srcH 是源图像的高度。
@param srcFormat
srcFormat 是源图像的像素格式。
@param dstW
dstW 是目标图像的宽度。
@param dstH
dstH 是目标图像的高度。
@param dstFormat
dstFormat 是目标图像的像素格式。
@param flags
flags 可以指定用于缩放/转换操作的算法以及选项。
@param param
param 为缩放操作提供额外的参数。
对于 BICUBIC 算法,param[0] 和 param[1] 调整基函数的形状,param[0] 调整 f(1),param[1] 调整 f´(1)。
对于 GAUSS 算法,param[0] 调整指数,从而调整了截止频率。
对于 LANCZOS 算法,param[0] 调整窗口函数的宽度。
@return
返回值是一个指向已分配 context 的指针,出错时为 NULL 。
1.3 转换函数 sws_scale()
图像分辨率转换、像素格式转换都通过这一个函数完成。
sws_scale() 函数接口定义如下:
/** * Scale the image slice in srcSlice and put the resulting scaled * slice in the image in dst. A slice is a sequence of consecutive * rows in an image. * * Slices have to be provided in sequential order, either in * top-bottom or bottom-top order. If slices are provided in * non-sequential order the behavior of the function is undefined. * * @param c the scaling context previously created with * sws_getContext() * @param srcSlice the array containing the pointers to the planes of * the source slice * @param srcStride the array containing the strides for each plane of * the source image * @param srcSliceY the position in the source image of the slice to * process, that is the number (counted starting from * zero) in the image of the first row of the slice * @param srcSliceH the height of the source slice, that is the number * of rows in the slice * @param dst the array containing the pointers to the planes of * the destination image * @param dstStride the array containing the strides for each plane of * the destination image * @return the height of the output slice */ int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[]);
sws_scale() 函数处理的对象是图像中的一个 slice。源图像中的一个 slice 经 sws_scale() 函数处理后,变成目标图像中的一个slice。一个 slice 指图像中一片连接的行。每次向 sws_scale() 函数提供的源 slice 必须是连续的,可以按由图像顶部到底部的顺序,也可以使用从图像底部到顶部的顺序。如果不按顺序提供 slice,sws_scale() 的执行结果是不确定的。
函数参数及返回值说明如下:
@param c
c 是由 sws_getContext() 函数创建的 SwsContext 上下文。
@param srcSlice
srcSlice 是一个指针数组(数组的每个元素是指针),每个指针指向源 slice 里的各个 plane。一帧图像通常有多个 plane,若将一帧图像划分成多个 slice,则每个 slice 里同样包含多个 plane。
通常调用 sws_scale() 时不会将一帧图像划分多个 slice,一帧图像就是一个 slice,所以通常为此函数提供的实参是 AVFrame.*data[]。
在使用 scale 滤镜时,可以将 nb_slices 选项参数设置为大于 1,以观察将一帧图像划分为多个 slice 情况。scale 滤镜中 nb_slices 选项的说明中有提到,此选项仅用于调试目的。
@param srcStride
srcStride 是一个数组,每个元素表示源图像中一个 plane 的 stride。通常为此函数提供的实参是 AVFrame.linesize[]。如前所述,若源图像是 yuv420p 8bit,分辨率是 1280x720,则 srcStride 数组有三个元素具有有效值,依次是 1280、640、640。
@param srcSliceY
srcSliceY 表示待处理的 slice 在源图像中的起始位置(相对于第 1 行的行数),第 1 行位置为 0,第 2 行位置为 1,依此类推。
@param srcSliceH
srcSliceH 表示待处理的 slice 的高度(行数)。
@param dst
dst 是一个指针数组,每个指针指向目标图像中的一个 plane。
@param dstStride
dstStride 是一个数组,每个元素表示目标图像中一个 plane 的 stride。
@return
函数返回值表示输出 slice 的高度(行数)。
5 参考资料
[1] 色彩空间与像素格式, https://www.cnblogs.com/leisure_chn/p/10290575.html
[2] FFmpeg源代码简单分析:libswscale的sws_getContext(), https://blog.csdn.net/leixiaohua1020/article/details/44305697
[3] FFmpeg源代码简单分析:libswscale的sws_scale(), https://blog.csdn.net/leixiaohua1020/article/details/44346687
6 修改记录
2021-01-30 V1.0 初稿
「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。