FFmpeg libswscale源码分析1-API介绍
本文为作者原创,转载请注明出处: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 初稿
FFmpeg libswscale源码分析1-API介绍的更多相关文章
- Redis网络库源码分析(1)之介绍篇
一.前言 Redis网络库是一个单线程EPOLL模型的网络库,和Memcached使用的libevent相比,它没有那么庞大,代码一共2000多行,因此比较容易分析.其实网上已经有非常多有关这个网络库 ...
- Heritrix源码分析(一) 包介绍(转)
本博客属原创文章,欢迎转载!但转载请务必注明出处:http://guoyunsky.iteye.com/blog/613249 本博客已迁移到本人独立博客: http://www.yun5u.com/ ...
- spark 源码分析之三 -- LiveListenerBus介绍
LiveListenerBus 官方说明如下: Asynchronously passes SparkListenerEvents to registered SparkListeners. 即它的功 ...
- Redis 专栏(使用介绍、源码分析、常见问题...)
一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...
- Quartz源码分析
先简单介绍一下quartz,Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统.quartz可用于创建执行数十,数百甚至数十 ...
- quartz2.x源码分析——启动过程
title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...
- spark源码分析以及优化
第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...
- Spring mvc源码分析系列--Servlet的前世今生
Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...
- 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]
目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionChain的获取 实例 资源文件映射 总结 参考资料 前言 SpringMVC是目前主流的W ...
随机推荐
- 【代码周边】npm是What?
社区 程序员自古以来就有社区文化: 社区的意思是:拥有共同职业或兴趣的人们,自发组织在一起,通过分享信息和资源进行合作.虚拟社区的参与者经常会在线讨论相关话题,或访问某些网站.前端程序员也有社区,世界 ...
- linkedhashmap中关于LRU算法的实现
//LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面 public LinkedHashMap(int ...
- Spring Security OAuth2.0认证授权一:框架搭建和认证测试
一.OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容. 1.s ...
- 5款极简极美WordPress主题,亲测可用附送源码
2020年深冬,新闻上报道是.从1950年以来最寒冷的冬天. 一个周六的下午,我找遍了全网的简约博客主题,搭建了三年来的第7个独立博客, 多么难得的周末啊,我却在家花了一整天的时间.整理出直接套用5️ ...
- 如何将项目推到github上面
1.先查看是否安装git. 2.如果没有安装git ,下载之后别忘了配置环境变量.(右击此电脑 --属性--高级系统设置--环境变量--系统变量中的path) 3.推代码 查看状态(可查可不查) gi ...
- 冷饭新炒:理解Redisson中分布式锁的实现
前提 在很早很早之前,写过一篇文章介绍过Redis中的red lock的实现,但是在生产环境中,笔者所负责的项目使用的分布式锁组件一直是Redisson.Redisson是具备多种内存数据网格特性的基 ...
- C语言指针的大小
C语言指针的大小 今天看到一道题目是这样的,写出以下变量在32位设备上的大小(占多少个字节) 然后其中就有一些指针类型的数据,那么我们知道在C语言中指针的大小都是一样的,不管是有数据类型的还是void ...
- 比较Power BI和Tableau,好比用奔驰对比奥迪
经常会有人问Power BI和Tableau的区别,好吧,为了非IT专业的能看懂,咱们就用车,奔驰和奥迪来对比一下.因为他们确实有太多相似之处. 所以Power BI VS Tableau,就相当于国 ...
- LeetCode108.有序数组转二叉搜索树
题目 1 class Solution { 2 public: 3 TreeNode* sortedArrayToBST(vector<int>& nums) { 4 if(num ...
- Eclipse中给jar包导入JavaDoc的方法
原文转载自:http://blog.csdn.net/mr_von/article/details/7740138 在使用Java语言开发的过程中,开发人员经常需要用到一些开源的工具包.在使用别人的j ...