FFmpeg libswscale源碼分析1-API介紹
本文為作者原創(chuàng),轉(zhuǎn)載請注明出處:http://www.rzrgm.cn/leisure_chn/p/14349382.html
libswscale 是 FFmpeg 中完成圖像尺寸縮放和像素格式轉(zhuǎn)換的庫。用戶可以編寫程序,調(diào)用 libswscale 提供的 API 來進(jìn)行圖像尺寸縮放和像素格式轉(zhuǎn)換。也可以使用 scale 濾鏡完成這些功能,scale 濾鏡實(shí)現(xiàn)中調(diào)用了 libswscale 的 API。libswscale 的 API 非常簡單,就一個 sws_scale() 接口,但內(nèi)部的實(shí)現(xiàn)卻非常復(fù)雜。
本文分析 libswscale 源碼,因篇幅較長,遂拆分成下面一系列文章:
[1]. FFmpeg libswscale源碼分析1-API介紹
[2]. FFmpeg libswscale源碼分析2-轉(zhuǎn)碼命令行與濾鏡圖
[3]. FFmpeg libswscale源碼分析3-scale濾鏡源碼分析
[4]. FFmpeg libswscale源碼分析4-libswscale源碼分析
源碼分析基于 FFmpeg 4.1 版本。
1. API 介紹
1.1 相關(guān)基礎(chǔ)概念
在解釋具體的函數(shù)前,必須理解與像素格式相關(guān)的幾個基礎(chǔ)概念:參色彩空間與像素格式一文第 4.1 節(jié)
pixel_format:像素格式,圖像像素在內(nèi)存中的排列格式。一種像素格式包含有色彩空間、采樣方式、存儲模式、位深等信息,其中體現(xiàn)的最重要信息就是存儲模式,具體某一類的存儲模式參照本文第 2 節(jié)、第 3 節(jié)。
bit_depth: 位深,指每個分量(Y、U、V、R、G、B 等)單個采樣點(diǎn)所占的位寬度。
例如對于 yuv420p(位深是8)格式而言,每一個 Y 樣本、U 樣本和 V 樣本都是 8 位的寬度,只不過在水平方向和垂直方向,U 樣本數(shù)目和 V 樣本數(shù)目都只有 Y 樣本數(shù)目的一半。而 bpp (Bits Per Pixel)則是將圖像總比特數(shù)分?jǐn)偟矫總€像素上,計算出平均每個像素占多少個 bit,例如 yuv420p 的 bpp 是 12,表示平均每個像素占 12 bit(Y占8位、U占2位、V占2位),實(shí)際每個 U 樣本和 V 樣本都是 8 位寬度而不是 2 位寬度。
plane: 存儲圖像中一個或多個分量的一片內(nèi)存區(qū)域。一個 plane 包含一個或多個分量。planar 存儲模式中,至少有一個分量占用單獨(dú)的一個 plane,具體到 yuv420p 格式有 Y、U、V 三個 plane,nv12 格式有 Y、UV 兩個 plane,gbrap 格式有 G、B、R、A 四個 plane。packed 存儲模式中,因?yàn)樗蟹至康南袼厥墙豢棿娣诺模?packed 存儲模式只有一個 plane。
slice: slice 是 FFmpeg 中使用的一個內(nèi)部結(jié)構(gòu),在 codec、filter 中常有涉及,通常指圖像中一片連續(xù)的行,表示將一幀圖像分成多個片段。注意 slice 是針對圖像分片,而不是針對 plane 分片,一幀圖像有多個 plane,一個 slice 里同樣包含多個 plane。
stride/pitch: 一行圖像中某個分量(如亮度分量或色度分量)所占的字節(jié)數(shù), 也就是一個 plane 中一行數(shù)據(jù)的寬度。有對齊要求,計算公式如下:
stride 值 = 圖像寬度 * 分量數(shù) * 單樣本位寬度 / 水平子采樣因子 / 8
其中,圖像寬度表示圖像寬度是多少個像素,分量數(shù)指當(dāng)前 plane 包含多少個分量(如 rgb24 格式一個 plane 有 R、G、B 三個分量),單位本位寬度指某分量的一個樣本在考慮對齊后在內(nèi)存中占用的實(shí)際位數(shù)(例如位深 8 占 8 位寬,位深 10 實(shí)際占 16 位寬,對齊值與平臺相關(guān)),水平子采樣因子指在水平方向上每多少個像素采樣出一個色度樣本(亮度樣本不進(jìn)行下采樣,所以采樣因子總是 1)。
需要注意的是,stride 考慮的是 plane 中的一行。對 yuv420p 格式而言,Y 分量是完全采樣,因此一行 Y 樣本數(shù)等于圖像寬度,U 分量和 V 分量水平采樣因子是 2(每兩個像素采樣出一個U樣本和V樣本),因此一行 U 樣本數(shù)和一行 V 樣本數(shù)都等于圖像寬度的一半。U 分量和 V 分量垂直采樣因子也是 2,因此 U 分量和 V 分量的行數(shù)少了,只有圖像高度的一半,但垂直方向的采樣率并不影響一個 plane 的 stride 值,因?yàn)?stride 的定義決定了其值只取決于水平方向的采樣率。
若源圖像像素格式是 yuv420p(有 Y、U、V 三個 plane),位深是 8(每一個Y樣本、U樣本、V樣本所占位寬度是 8 位),分辨率是 1280x720,則在 Y plane 的一行數(shù)據(jù)中,有 1280 個 Y 樣本,占用 1280 個字節(jié),stride 值是 1280;在 U plane 的一行數(shù)據(jù)中,有 640 個 U 樣本,占用 640 個字節(jié),stride 值是 640;在 V plane 的一行數(shù)據(jù)中,有 640 個樣本,占用 640 個字節(jié),stride 值是 640。
若源圖像像素格式是 yuv420p10(有 Y、U、V 三個 plane),位深是 10 (內(nèi)存對齊后每個樣本占 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 (內(nèi)存對齊后,每個樣本占 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 三個采樣點(diǎn)交織存放,內(nèi)存區(qū)的排列形式為 BGRBGR...,因此可以認(rèn)為它只有一個 plane,此 plane 中一行圖像有 1280 個 R 樣本,1280 個 G 樣本,1280 個 B 樣本,此 plane 的 stride 值為 1280 x 3 x 8 / 8 = 3840。
1.2 初始化函數(shù) sws_getContext()
sws_getContext()函數(shù)將創(chuàng)建一個 SwsContext,后續(xù)使用 sws_scale() 執(zhí)行縮放/格式轉(zhuǎn)換操作時需要用到此 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);
函數(shù)參數(shù)及返回值說明如下:
@param srcW
srcW 是源圖像的寬度。
@param srcH
srcH 是源圖像的高度。
@param srcFormat
srcFormat 是源圖像的像素格式。
@param dstW
dstW 是目標(biāo)圖像的寬度。
@param dstH
dstH 是目標(biāo)圖像的高度。
@param dstFormat
dstFormat 是目標(biāo)圖像的像素格式。
@param flags
flags 可以指定用于縮放/轉(zhuǎn)換操作的算法以及選項(xiàng)。
@param param
param 為縮放操作提供額外的參數(shù)。
對于 BICUBIC 算法,param[0] 和 param[1] 調(diào)整基函數(shù)的形狀,param[0] 調(diào)整 f(1),param[1] 調(diào)整 f′(1)。
對于 GAUSS 算法,param[0] 調(diào)整指數(shù),從而調(diào)整了截止頻率。
對于 LANCZOS 算法,param[0] 調(diào)整窗口函數(shù)的寬度。
@return
返回值是一個指向已分配 context 的指針,出錯時為 NULL 。
1.3 轉(zhuǎn)換函數(shù) sws_scale()
圖像分辨率轉(zhuǎn)換、像素格式轉(zhuǎn)換都通過這一個函數(shù)完成。
sws_scale() 函數(shù)接口定義如下:
/**
* 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() 函數(shù)處理的對象是圖像中的一個 slice。源圖像中的一個 slice 經(jīng) sws_scale() 函數(shù)處理后,變成目標(biāo)圖像中的一個slice。一個 slice 指圖像中一片連接的行。每次向 sws_scale() 函數(shù)提供的源 slice 必須是連續(xù)的,可以按由圖像頂部到底部的順序,也可以使用從圖像底部到頂部的順序。如果不按順序提供 slice,sws_scale() 的執(zhí)行結(jié)果是不確定的。
函數(shù)參數(shù)及返回值說明如下:
@param c
c 是由 sws_getContext() 函數(shù)創(chuàng)建的 SwsContext 上下文。
@param srcSlice
srcSlice 是一個指針數(shù)組(數(shù)組的每個元素是指針),每個指針指向源 slice 里的各個 plane。一幀圖像通常有多個 plane,若將一幀圖像劃分成多個 slice,則每個 slice 里同樣包含多個 plane。
通常調(diào)用 sws_scale() 時不會將一幀圖像劃分多個 slice,一幀圖像就是一個 slice,所以通常為此函數(shù)提供的實(shí)參是 AVFrame.*data[]。
在使用 scale 濾鏡時,可以將 nb_slices 選項(xiàng)參數(shù)設(shè)置為大于 1,以觀察將一幀圖像劃分為多個 slice 情況。scale 濾鏡中 nb_slices 選項(xiàng)的說明中有提到,此選項(xiàng)僅用于調(diào)試目的。
@param srcStride
srcStride 是一個數(shù)組,每個元素表示源圖像中一個 plane 的 stride。通常為此函數(shù)提供的實(shí)參是 AVFrame.linesize[]。如前所述,若源圖像是 yuv420p 8bit,分辨率是 1280x720,則 srcStride 數(shù)組有三個元素具有有效值,依次是 1280、640、640。
@param srcSliceY
srcSliceY 表示待處理的 slice 在源圖像中的起始位置(相對于第 1 行的行數(shù)),第 1 行位置為 0,第 2 行位置為 1,依此類推。
@param srcSliceH
srcSliceH 表示待處理的 slice 的高度(行數(shù))。
@param dst
dst 是一個指針數(shù)組,每個指針指向目標(biāo)圖像中的一個 plane。
@param dstStride
dstStride 是一個數(shù)組,每個元素表示目標(biāo)圖像中一個 plane 的 stride。
@return
函數(shù)返回值表示輸出 slice 的高度(行數(shù))。
5 參考資料
[1] 色彩空間與像素格式, http://www.rzrgm.cn/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 初稿

浙公網(wǎng)安備 33010602011771號