【FFMpeg視訊開發與應用基礎】八、 呼叫FFMpeg SDK實現視訊縮放
《FFMpeg視訊開發與應用基礎——使用FFMpeg工具與SDK》視訊教程已經在“CSDN學院”上線,視訊中包含了從0開始逐行程式碼實現FFMpeg視訊開發的過程,歡迎觀看!連結地址:FFMpeg視訊開發與應用基礎——使用FFMpeg工具與SDK
視訊縮放是視訊開發中一項最基本的功能。通過對視訊的畫素資料進行取樣或插值,可以將低解析度的視訊轉換到更高的解析度,或者將高解析度的視訊轉換為更低的解析度。通過FFMpeg提供了libswscale庫,可以輕鬆實現視訊的解析度轉換功能。除此之外,libswscale庫還可以實現顏色空間轉換等功能。
通常情況下視訊縮放的主要思想是對視訊進行解碼到畫素域後,針對畫素域的畫素值進行取樣或差值操作。這種方式需要在解碼端消耗一定時間,但是通用性最好,不需要對碼流格式作出任何特殊處理。在FFMpeg中libswscale庫也是針對AVFrame結構進行縮放處理。
1. 解析命令列引數
輸入輸出的資料使用以下結構進行封裝:
typedef struct _IOFiles { char *inputName; //輸入檔名 char *outputName; //輸出檔名 char *inputFrameSize; //輸入影象的尺寸 char *outputFrameSize; //輸出影象的尺寸 FILE *iFile; //輸入檔案指標 FILE *oFile; //輸出檔案指標 } IOFiles;
輸入引數解析過程為:
static bool hello(int argc, char **argv, IOFiles &files) { printf("FFMpeg Scaling Demo.\nCommand format: %s input_file input_frame_size output_file output_frame_size\n", argv[0]); if (argc != 5) { printf("Error: command line error, please re-check.\n"); return false; } files.inputName = argv[1]; files.inputFrameSize = argv[2]; files.outputName = argv[3]; files.outputFrameSize = argv[4]; fopen_s(&files.iFile, files.inputName, "rb+"); if (!files.iFile) { printf("Error: cannot open input file.\n"); return false; } fopen_s(&files.oFile, files.outputName, "wb+"); if (!files.oFile) { printf("Error: cannot open output file.\n"); return false; } return true; }
在引數讀入完成後,需要從表示視訊解析度的字串中解析出影象的寬和高兩個值。我們在命令列中傳入的視訊解析度字串的格式為“width x height”,例如”720x480”。解析過程需要呼叫av_parse_video_size函式。宣告如下:
int av_parse_video_size(int *width_ptr, int *height_ptr, const char *str);
例如,我們傳入下面的引數:
int frameWidth, frameHeight;
av_parse_video_size(&frameWidth, &frameHeight, "720x480");
函式將分別把720和480傳入frameWidth和frameHeight中。
在獲取命令列引數後,呼叫該函式解析影象解析度:
int srcWidth, srcHeight, dstWidth, dstHeight;
if (av_parse_video_size(&srcWidth, &srcHeight, files.inputFrameSize))
{
printf("Error: parsing input size failed.\n");
goto end;
}
if (av_parse_video_size(&dstWidth, &dstHeight, files.outputFrameSize))
{
printf("Error: parsing output size failed.\n");
goto end;
}
這樣,我們就獲得了源和目標影象的寬和高度。
2. 建立SwsContext結構
進行視訊的縮放操作離不開libswscale的一個關鍵的結構,即SwsContext,該結構提供了縮放操作的必要引數。建立該結構需呼叫函式sws_getContext。該函式的宣告如下:
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);
該函式的前兩行引數分別表示輸入和輸出影象的寬、高、畫素格式,引數flags表示取樣和差值使用的演算法,常用的有SWS_BILINEAR表示雙線性差值等。剩餘的不常用引數通常設為NULL。建立該結構的程式碼如:
//建立SwsContext結構
enum AVPixelFormat src_pix_fmt = AV_PIX_FMT_YUV420P;
enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUV420P;
struct SwsContext *sws_ctx = sws_getContext(srcWidth, srcHeight, src_pix_fmt, dstWidth, dstHeight, dst_pix_fmt, SWS_BILINEAR, NULL,NULL,NULL );
if (!sws_ctx)
{
printf("Error: parsing output size failed.\n");
goto end;
}
3. 分配畫素快取
視訊縮放實際上是在畫素域實現,但是實際上我們沒有必要真的建立一個個AVFrame物件,我們只需要其畫素快取空間即可,我們定義兩個指標陣列和兩個儲存stride的陣列,併為其分配記憶體區域:
//分配input和output
uint8_t *src_data[4], *dst_data[4];
int src_linesize[4], dst_linesize[4];
if ((ret = av_image_alloc(src_data, src_linesize, srcWidth, srcHeight, src_pix_fmt, 32)) < 0)
{
printf("Error: allocating src image failed.\n");
goto end;
}
if ((ret = av_image_alloc(dst_data, dst_linesize, dstWidth, dstHeight, dst_pix_fmt, 1)) < 0)
{
printf("Error: allocating dst image failed.\n");
goto end;
}
4. 迴圈處理輸入frame
迴圈處理的程式碼為:
//從輸出frame中寫出到輸出檔案
int dst_bufsize = ret;
for (int idx = 0; idx < MAX_FRAME_NUM; idx++)
{
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 0, files);
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 1, files);
read_yuv_from_ifile(src_data, src_linesize, srcWidth, srcHeight, 2, files);
sws_scale(sws_ctx, (const uint8_t * const*)src_data, src_linesize, 0, srcHeight, dst_data, dst_linesize);
fwrite(dst_data[0], 1, dst_bufsize, files.oFile);
}
其核心函式為sws_scale,其宣告為:
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[]);
該函式的各個引數比較容易理解,除了第一個是之前建立的SwsContext之外,其他基本上都是源和目標影象的快取區和大小等。在寫完一幀後,呼叫fwrite將輸出的目標影象寫入輸出yuv檔案中。
5. 收尾工作
收尾工作除了釋放快取區和關閉輸入輸出檔案之外,就是需要釋放SwsContext結構,需呼叫函式:sws_freeContext。實現過程為:
fclose(files.iFile);
fclose(files.oFile);
av_freep(&src_data[0]);
av_freep(&dst_data[0]);
sws_freeContext(sws_ctx);
視訊縮放前後的效果圖如下: