本章節重點講解FFMPEG中對于視訊圖像格式轉換的處理。
一、視訊格式轉換的基本API
視訊幀圖像的格式轉換、縮放等處理,主要使用 libswscale庫中的API函數完成的
- sws_getContext() 根據要輸入輸出圖像的 寬高和 像素格式 建立轉換器
- sws_scale() 根據輸入圖像資料進行實際的轉換操作,結果輸出到輸出緩沖區上
- sws_freeContext()釋放轉換器
這幾個API函數的功能比較明确,關鍵是參數的設定,特别是sws_scale()幾個參數的設定,具體的參數值依賴于相應的視訊幀圖像格式。
二、常用的視訊幀圖像格式
- YUV420P格式
在YUV420P的存儲格式中,每4個像素點占用4個Y、1個U、1個V,Y分量、U分量、V分量的資料分别單獨存放,對應AVFrame結構體中字段
typedef struct AVFrame {
......
//
// 視訊幀圖像資料 或者 音頻幀PCM資料, 根據不同的格式有不同的存放方式
// 對于視訊幀:RGB/RGBA 格式時 data[0] 中一次存放每個像素的RGB/RGBA資料
// YUV420 格式時 data[0]存放Y資料; data[1]存放U資料; data[2]存放V資料
// 對于音頻幀: data[0]存放左聲道資料; data[1]存放右聲道資料
//
uint8_t *data[AV_NUM_DATA_POINTERS];
//
// 行位元組跨度, 相當于stride
// 對于視訊幀: 上下兩行同一列像素相差的位元組數,例如:對于RGBA通常是(width*4), 但是有時FFMPEG内部會有擴充, 可能會比這個值大
// 對于音頻幀: 單個通道中所有采樣占用的位元組數
//
int linesize[AV_NUM_DATA_POINTERS];
int format; // 對于視訊幀是圖像格式; 對于音頻幀是采樣格式
int64_t pts; // 目前資料幀的時間戳
int width, height; // 僅用于視訊幀, 寬度高度
int key_frame; // 僅用于視訊, 目前是否是I幀
......
}
以一張 720*1280的視訊幀圖像為例,AVFrame中的各個字段值:
uint8_t* yuvData[4] = {nullptr};
int linesize[4] = {0};
av_image_alloc(yuvData, linesize, 720, 1280, AV_PIX_FMT_YUV420P, 1); // 配置設定記憶體空間
// 将 Y & U & V 資料分别填充到 yuvData[0] yuvData[1] yuvData[2] 中
AVFrame* pFrame = av_frame_alloc();
pFrame->format = AV_PIX_FMT_YUV420P;
pFrame->width = 720;
pFrame->height = 1280;
pFrame->data[0] = yuvData[0];
pFrame->data[1] = yuvData[1];
pFrame->data[2] = yuvData[2];
pFrame->linesize[0] = linesize[0]; // Y資料行長位元組數, 720
pFrame->linesize[1] = linesize[1]; // U資料行長位元組數, 360
pFrame->linesize[2] = linesize[1]; // V資料行長位元組數, 360
......
相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】
【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選加群免費領取~
這裡的linesize[]的值需要特别注意一下,這個地方可能會讓很多開發者疑惑:
既然linesize[]表示一個通道中一行像素資料的位元組數,那麼直接根據通道類型計算就可以了(例如:對于Y通道來說,圖像寬度720,那麼一行像素的Y資料就固定是720個位元組),為什麼要單獨使用一個字段表示? 。
具體原因:這裡會涉及到一個記憶體管理的處理,如果AVFrame的緩沖區是開發者自己建立的,上面的lineSize[]的确可以自己計算。 但是在FFMPEG内部編解碼器處理時需要處理圖像邊界等問題,很多時候為了友善優化處理,通常内部建立的圖像緩沖區要比輸出的原始圖像要大一些,而輸出的圖像内容隻是出于緩沖區中的一部分,此時要輸出圖像要麼建立一個新的緩沖區做一次圖像拷貝,要麼直接将這個緩沖區輸出,避免一次拷貝,FFMPEG就是采用後面一種方式少了一次拷貝。
例如:.mp4檔案中視訊幀原始圖像大小是 720*1280,解碼器内部實際為了解碼優化,實際建立的圖像緩沖區可能是768*1328大小,在調用 avcodec_receive_frame()擷取到的解碼後的視訊幀資料 pFrame中 data[0] 位址是通過pCacheBuffer直接位址偏移得到,同樣 linesize[0] = 768; linsize[1] = 384; linesize[2] = 384; 這樣通過data[0] 找到下一行第一個像素的Y資料就是 (data[0]+linsize[0]) 而不是直接 (data[0]+720)了
2. RGBA格式
RGBA圖像格式在大部分作業系統的GUI Framework和控件中比較常用,在RGBA的存儲格式中,每個像素點分别使用R、G、B、A四個通道交錯存放。在AVFrame結構體中僅需要用到data[0]字段來指定資料存儲區域,linesize[0]通常為 (width*4)
RGBA圖像格式的記憶體布局
3. 其他視訊圖像格式
在FFMPEG中視訊幀格式主要分為 YUV 和 RGB/RGBA 兩大系列類型,在這兩大類型中又根據不同存儲和記憶體布局有更多的細分,詳細可以參考FFMPEG相關的說明文檔。
在這兩種類型中主要注意的是:
如果格式後面帶'P'的表示是 planar存儲模式,通常不同的通道資料分别存儲在data[0], data[1], data[2], ..... 中
其他的格式是packed存儲模式,即:各個通道資料是交錯存儲在一起,此時資料通路隻能通過data[0]來進行偏移計算, data[1], data[2],..... 等都是空的
三、轉換示例代碼
直接使用 libswscale庫進行轉換
//
// 視訊幀格式轉換
//
int32_t VideoConvert(
const AVFrame* pInFrame, // 輸入視訊幀
AVPixelFormat eOutFormat, // 輸出視訊格式
int32_t nOutWidth, // 輸出視訊寬度
int32_t nOutHeight, // 輸出視訊高度
AVFrame** ppOutFrame ) // 輸出視訊幀
{
struct SwsContext* pSwsCtx = nullptr;
AVFrame* pOutFrame = nullptr;
// 建立格式轉換器, 指定縮放算法,轉換過程中不增加任何濾鏡特效處理
pSwsCtx = sws_getContext(pInFrame->width, pInFrame->height, (AVPixelFormat)pInFrame->format,
nOutWidth, nOutHeight, eOutFormat,
SWS_BICUBIC, nullptr, nullptr, nullptr);
if (pSwsCtx == nullptr)
{
LOGE("<VideoConvert> [ERROR] fail to sws_getContext()\n");
return -1;
}
// 建立輸出視訊幀對象以及配置設定相應的緩沖區
uint8_t* data[4] = {nullptr};
int linesize[4] = {0};
int res = av_image_alloc(data, linesize, nOutWidth, nOutHeight, eOutFormat, 1);
if (res < 0)
{
LOGE("<VideoConvert> [ERROR] fail to av_image_alloc(), res=%d\n", res);
sws_freeContext(pSwsCtx);
return -2;
}
pOutFrame = av_frame_alloc();
pOutFrame->format = eOutFormat;
pOutFrame->width = nOutWidth;
pOutFrame->height = nOutHeight;
pOutFrame->data[0] = data[0];
pOutFrame->data[1] = data[1];
pOutFrame->data[2] = data[2];
pOutFrame->data[3] = data[3];
pOutFrame->linesize[0] = linesize[0];
pOutFrame->linesize[1] = linesize[1];
pOutFrame->linesize[2] = linesize[2];
pOutFrame->linesize[3] = linesize[3];
// 進行格式轉換處理
res = sws_scale(pSwsCtx,
static_cast<const uint8_t* const*>(pInFrame->data),
pInFrame->linesize,
0,
pOutFrame->height,
pOutFrame->data,
pOutFrame->linesize);
if (res < 0)
{
LOGE("<VideoConvert> [ERROR] fail to sws_scale(), res=%d\n", res);
sws_freeContext(pSwsCtx);
av_frame_free(&pOutFrame);
return -3;
}
(*ppOutFrame) = pOutFrame;
sws_freeContext(pSwsCtx); // 釋放轉換器
return 0;
}
四、其他格式轉換庫
這裡 libswscale 是FFMPEG本身自帶的圖像格式轉換和縮放庫,但實際項目中這個庫的性能不太高,如果要考慮高性能的圖像格式轉換,可以考慮使用 libyuv 這個開源庫。
文章系列目錄
華叔-視覺魔術師:FFMPEG開發快速入坑——緒論62 贊同 · 11 評論文章
原文 https://zhuanlan.zhihu.com/p/345687433