本文为作者原创,转载请注明出处: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介绍的更多相关文章

  1. Redis网络库源码分析(1)之介绍篇

    一.前言 Redis网络库是一个单线程EPOLL模型的网络库,和Memcached使用的libevent相比,它没有那么庞大,代码一共2000多行,因此比较容易分析.其实网上已经有非常多有关这个网络库 ...

  2. Heritrix源码分析(一) 包介绍(转)

    本博客属原创文章,欢迎转载!但转载请务必注明出处:http://guoyunsky.iteye.com/blog/613249 本博客已迁移到本人独立博客: http://www.yun5u.com/ ...

  3. spark 源码分析之三 -- LiveListenerBus介绍

    LiveListenerBus 官方说明如下: Asynchronously passes SparkListenerEvents to registered SparkListeners. 即它的功 ...

  4. Redis 专栏(使用介绍、源码分析、常见问题...)

    一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...

  5. Quartz源码分析

    先简单介绍一下quartz,Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中 - 从最小的独立应用程序到最大的电子商务系统.quartz可用于创建执行数十,数百甚至数十 ...

  6. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

  7. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

  8. Spring mvc源码分析系列--Servlet的前世今生

    Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...

  9. 详解SpringMVC请求的时候是如何找到正确的Controller[附带源码分析]

    目录 前言 源码分析 重要接口介绍 SpringMVC初始化的时候做了什么 HandlerExecutionChain的获取 实例 资源文件映射 总结 参考资料 前言 SpringMVC是目前主流的W ...

随机推荐

  1. MongoDb学习(五)--Gridfs--上传下载

    版本 <dependency> <groupId>org.springframework.data</groupId> <artifactId>spri ...

  2. Dreamoon Likes Coloring 【CF 1329 A】

    传送门 思路:"Dreamoon will choose a number pipi from range [1,n−li+1](inclusive) and will paint all ...

  3. Access-Control-Allow-Headers等基础常识

    跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种机制,该机制使用附加的 HTTP 头来告诉浏览器,准许运行在一个源上的Web应用访问位于另一不同源选定的资源. 当一个Web应用发起一个与 ...

  4. LVS之1---工作原理

    LVS之1---工作原理 LVS 介绍 LVS:Linux Virtual Server,Linux虚拟服务器,负载调度器,是一个由章文嵩(花名 正明)博士从1998年开始发起的自由软件项目. 软件作 ...

  5. Solon rpc 之 SocketD 协议

    1. 简介 SocketD 是一种二进制的点对点通信协议,是一种新的网络通信第七层协议.旨在用于分布式应用程序中.从这个意义上讲,SocketD可以是RSocket等其他类似协议的替代方案.它的消息协 ...

  6. user&group_management.md

    登录shell bin/bash sbin/nologin useradd 的参数     -c  comment 用户的注释性信息     -u UID 指定用户的UID     -g initia ...

  7. 用python+sklearn(机器学习)实现天气预报数据 模型和使用

    用python+sklearn机器学习实现天气预报 模型和使用 项目地址 系列教程 0.前言 1.建立模型 a.准备 引入所需要的头文件 选择模型 选择评估方法 获取数据集 b.建立模型 c.获取模型 ...

  8. php 二位数组 转一维数组

    $result = []; array_map(function ($value) use (&$result) { $result = array_merge($result, array_ ...

  9. CSS 奇技淫巧:动态高度过渡动画

    这个问题源自于掘金上的一个留言,一个朋友问到,为什么我下面这段代码的高度过渡动画失效了? 伪代码大概是这样: { height: unset; transition: all 0.3s linear; ...

  10. .netcore 急速接入第三方登录,不看后悔

    新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢. 如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下: https://oauthlogin.net/ 前言 此次带来得这个小 ...