1. 程式人生 > >【FFMpeg視訊開發與應用基礎】八、 呼叫FFMpeg SDK實現視訊縮放

【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);

視訊縮放前後的效果圖如下:

FFMpegScaling