http://blog.csdn.net/luotuo44/article/details/26486877
上一篇介紹了YUV格式,並給出了一個YUYV422轉RGB24的例子。其實,FFmpeg有一個函數專門進行圖像格式轉換的。本文就介紹怎么用FFmpeg轉換,因為在轉換時還要用到AVFrame這個結構體,所以這里也會介紹AVFrame。在FFmpeg中,AVFrame是一個比較重要的結構體。
AVFrame,顧名思義,這個結構體應該是保存視頻幀的信息的。像一幀圖像也是可以保存在AVFrame結構中。事實上,我們可以直接從一個YUV文件中,把一張YUV圖像數據讀到AVFrame中。本文后面的例子也是這樣做的。
為了弄懂AVFrame是怎么存放一張YUV圖像的(當然AVFrame可以存放其他格式圖像的),現在先看一下AVFrame結構體的主要成員。
- typedef struct AVFrame
- {
- #define AV_NUM_DATA_POINTERS 8
- uint8_t * data [AV_NUM_DATA_POINTERS]; //指向圖像數據
- int linesize [AV_NUM_DATA_POINTERS]; //行的長度
- int width; //圖像的寬
- int height; //圖像的高
- int format; //圖像格式
- ……
- }AVFrame;
注意到data成員是一個指針數組。其指向的內容就是圖像的實際數據。
可以用av_frame_alloc(void)函數來分配一個AVFrame結構體。這個函數只是分配AVFrame結構體,但data指向的內存並沒有分配,需要我們指定,這個內存的大小就是一張特定格式圖像所需的大小。
如前一篇博文中說到的,對於YUYV422格式,所需的大小是width * height * 2。所以AVFrame結構體的整個初始化過程如下:
- AVFrame* frame = av_frame_alloc();
- //這里FFmpeg會幫我們計算這個格式的圖片,需要多少字節來存儲
- //相當於前一篇博文例子中的width * height * 2
- int bytes_num = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height); //AV_PIX_FMT_YUV420P是FFmpeg定義的標明YUV420P圖像格式的宏定義
- //申請空間來存放圖片數據。包含源數據和目標數據
- uint8_t* buff = (uint8_t*)av_malloc(bytes_num);
- //前面的av_frame_alloc函數,只是為這個AVFrame結構體分配了內存,
- //而該類型的指針指向的內存還沒分配。這里把av_malloc得到的內存和AVFrame關聯起來。
- //當然,其還會設置AVFrame的其他成員
- avpicture_fill((AVPicture*)frame, buff, AV_PIX_FMT_ YUV420P,width, height);
看到這里,可能有些讀者會疑問:data成員是一個指針數組(即數組里面的每一個元素都是一個指針),一個buff怎么夠用(多對一的關系)。其實,這就是FFmpeg設計的一個巧妙之處。還記得前一篇博文說到的 圖像物理存儲有 planar和packed兩種模式嗎?
這個data指針數組就是為了planar設計的。對於planar模式的YUV。data[0]指向Y分量的開始位置、data[1]指向U分量的開始位置、data[2]指向V分量的開始位置。
對於packed模式YUV,data[0]指向數據的開始位置,而data[1]和data[2]都為NULL。
同時該函數還好對AVFrame->linesize變量進行賦值。見后面的例子程序。
在上面的代碼中,運行avpicture_fill后,data[0]將指向buff的開始位置,即data[0]等於buff。data[1]指向buff數組的某一個位置(該位置為U分量的開始處),data[2]也指向buff數組某一個位置(該位置為V分量的開始處)。
有些網友說到,對於planar模式,需要分開讀取和寫的。其實,無論是planar還是packed模式,在用acpicture_fill函數處理后,都可以用下面的方法把一張圖像的數據讀取到AVFrame中,而不需要分別讀data[0]、data[1]、data[2]。
因為對於圖像文件來說,如果是plannar模式的圖像格式,其存儲必然是先存完一張圖像所有的所有Y、緊接着再存一張圖像的所有U、緊接着存一張圖像的所有V。這剛好和data數組的三個指針的對應的。
- fread(frame->data[0], 1, bytes_num, fin);
同樣對於寫圖像也是如此。無需分data[0]、data[1]、data[2]。
扯了這么多,還沒說FFmpeg是怎么轉換圖像格式的。現在來說一下。
FFmpeg定義了一個結構體SwsContext,它記錄進行圖像格式轉換時,源圖像和目標圖像的格式、大小分別是什么。然后用sws_scale函數直接轉換即可。
過程如下:
- SwsContext* sws_ctx = sws_getContext(src_width, src_height,
- AV_PIX_FMT_YUV420P,
- dst_width, dst_height,
- AV_PIX_FMT_YUYV422,
- SWS_BICUBIC,
- NULL,
- NULL,
- NULL);
- sws_scale(sws_ctx, src_frame->data, src_frame->linesize,
- 0, height, //源圖像的高
- dst_frame->data, dst_frame->linesize);
下面給出完整的轉換例子。該例子將YUV420P轉換成YUYV422,並寫入一個文件中。
- #ifdef __cplusplus
- #define __STDC_CONSTANT_MACROS
- #ifdef _STDINT_H
- #undef _STDINT_H
- #endif
- # include <stdint.h>
- #endif
- extern "C"
- {
- #include<libavcodec/avcodec.h>
- #include<libavformat/avformat.h>
- #include<libavutil/log.h>
- #include<libswscale/swscale.h>
- }
- #include<stdio.h>
- #include <windows.h> //for saveAsBitmap
- bool saveAsBitmap(AVFrame *pFrameRGB, int width, int height, int iFrame)
- {
- FILE *pFile = NULL;
- BITMAPFILEHEADER bmpheader;
- BITMAPINFO bmpinfo;
- char fileName[32];
- int bpp = 24;
- // open file
- sprintf(fileName, "frame%d.bmp", iFrame);
- pFile = fopen(fileName, "wb");
- if (!pFile)
- return false;
- bmpheader.bfType = ('M' <<8)|'B';
- bmpheader.bfReserved1 = 0;
- bmpheader.bfReserved2 = 0;
- bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
- bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;
- bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmpinfo.bmiHeader.biWidth = width;
- bmpinfo.bmiHeader.biHeight = -height; //reverse the image
- bmpinfo.bmiHeader.biPlanes = 1;
- bmpinfo.bmiHeader.biBitCount = bpp;
- bmpinfo.bmiHeader.biCompression = BI_RGB;
- bmpinfo.bmiHeader.biSizeImage = 0;
- bmpinfo.bmiHeader.biXPelsPerMeter = 100;
- bmpinfo.bmiHeader.biYPelsPerMeter = 100;
- bmpinfo.bmiHeader.biClrUsed = 0;
- bmpinfo.bmiHeader.biClrImportant = 0;
- fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, pFile);
- fwrite(&bmpinfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
- uint8_t *buffer = pFrameRGB->data[0];
- for (int h=0; h<height; h++)
- {
- for (int w=0; w<width; w++)
- {
- fwrite(buffer+2, 1, 1, pFile);
- fwrite(buffer+1, 1, 1, pFile);
- fwrite(buffer, 1, 1, pFile);
- buffer += 3;
- }
- }
- fclose(pFile);
- return true;
- }
- int main(int argc, char** argv)
- {
- const char* filename = argc > 1 ? argv[1] : "flower_cif.yuv";
- FILE* fin = fopen(filename, "rb");
- if( fin == NULL )
- {
- printf("can't open the file\n");
- return -1;
- }
- int width = 352;
- int height = 288;
- AVPixelFormat src_fmt = AV_PIX_FMT_YUV420P;
- AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;
- AVFrame* src_frame = av_frame_alloc();
- AVFrame* dst_frame = av_frame_alloc();
- if( src_frame == NULL || dst_frame == NULL )
- {
- printf("av_frame_alloc fail\n");
- return -1;
- }
- //這里FFmpeg會幫我們計算這個格式的圖片,需要多少字節來存儲
- //相當於前面例子中的width * height * 2
- int src_bytes_num = avpicture_get_size(src_fmt,
- width, height);
- int dst_bytes_num = avpicture_get_size(dst_fmt,
- width, height);
- //申請空間來存放圖片數據。包含源數據和目標數據
- uint8_t* src_buff = (uint8_t*)av_malloc(src_bytes_num);
- uint8_t* dst_buff = (uint8_t*)av_malloc(dst_bytes_num);
- //前面的av_frame_alloc函數,只是為這個AVFrame結構體分配了內存,
- //而該類型的指針指向的內存還沒分配。這里把av_malloc得到的內存和AVFrame關聯起來。
- //當然,其還會設置AVFrame的其他成員
- avpicture_fill((AVPicture*)src_frame, src_buff, src_fmt, width, height); //該函數會自動填充AVFrame的data和linesize字段
- avpicture_fill((AVPicture*)dst_frame, dst_buff, dst_fmt,
- width, height);
- //這里主要說明linesize這個成員的含義。不想看可以忽略
- //YUV格式中有一個很重要的等量關系,那就是有多少個像素就有多少個y。
- //linesize正如其名,一條線(即一行)的大小。對於yuv420p(planar)。data[0]存放的是y,對應地linesize[0]就
- //指明一行有多少個y。對於352*288的圖像,一行有352個像素。根據剛才的等量關系。那么linesize[0]就
- //應該為352.即一行有352個y。對於linesize[1],因為data[1]存放的是u。而一行352個像素在yuv420p格式中,
- //其只需352/2,即176個。所以linesize[1]的大小為176。同理linesize[2]也為176。
- //而對於yuyv422格式。data[0]這一行要負責存放y、u、v這三個分量。而y:u:v = 2:1:1的關系。根據前面所說的
- //等量關系,y等於352(相對於352*288大小的圖像來說),u和v都等於352/2 。所以u+v等於352。所以linesize[0]
- //等於352*2.
- printf("%d %d %d\n", src_frame->linesize[0], src_frame->linesize[1], src_frame->linesize[2]);
- printf("%d %d %d \n", dst_frame->linesize[0], dst_frame->linesize[1], dst_frame->linesize[2]);
- //對轉換進行配置。這里要設置轉換源的大小、格式和轉換目標的大小、格式
- //設置后,下面就可以直接使用sws_scale函數,進行轉換
- SwsContext* sws_ctx = sws_getContext(width, height,
- src_fmt,
- width, height,
- dst_fmt,
- SWS_BICUBIC,
- //SWS_BILINEAR,
- NULL,
- NULL,
- NULL);
- if( sws_ctx == NULL)
- {
- printf("sws_getContext fail ");
- return -1;
- }
- FILE* fout = fopen("yuyv422.yuv", "wb");
- int count = 0;
- while( 1 )
- {
- int ret = fread(src_frame->data[0], 1, src_bytes_num, fin);
- if( ret != src_bytes_num )
- {
- printf("don't read enough data %d\n", ret);
- break;
- }
- sws_scale(sws_ctx, src_frame->data, src_frame->linesize,
- 0, height,
- dst_frame->data, dst_frame->linesize);
- ret = fwrite(dst_frame->data[0], 1, dst_bytes_num, fout);
- if( ret != dst_bytes_num )
- printf("don't write enough data %d \n", ret);
- //如果要保存為BMP格式,要把目標圖像的格式設置為RGB24。
- //只需把前面的AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;
- //改成AVPixelFormat dst_fmt = AV_PIX_FMT_RGB24;即可
- saveAsBitmap(dst_frame, width, height, count++);
- }
- av_free(src_frame);
- av_free(dst_frame);
- av_free(src_buff);
- av_free(dst_buff);
- sws_freeContext(sws_ctx);
- fclose(fin);
- fclose(fout);
- return 0;
- }
例子中,還可以把圖像保存成bmp圖片。那個函數來自:http://blog.csdn.net/ajaxhe/article/details/7340508。
例子中用到的YUV420P格式的文件,可以到這里下載。