本文為作者原創,轉載請注明出處: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 初稿