FFmpeg libswscale原始碼分析1-API介紹

葉餘發表於2021-02-01

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

相關文章