圖像視頻編碼和FFmpeg(3)-----用FFmpeg進行圖像格式轉換和AVFrame簡介 avpicture_fill


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結構體的主要成員。

 

[cpp]  view plain  copy
 
  1. typedef struct AVFrame  
  2. {  
  3. #define AV_NUM_DATA_POINTERS 8  
  4.     uint8_t *   data [AV_NUM_DATA_POINTERS]; //指向圖像數據  
  5.   
  6.     int linesize [AV_NUM_DATA_POINTERS]; //行的長度  
  7.   
  8.     int width; //圖像的寬  
  9.     int height; //圖像的高  
  10.     int format;  //圖像格式  
  11.      ……  
  12. }AVFrame;  

 

        注意到data成員是一個指針數組。其指向的內容就是圖像的實際數據。

        可以用av_frame_alloc(void)函數來分配一個AVFrame結構體。這個函數只是分配AVFrame結構體,但data指向的內存並沒有分配,需要我們指定,這個內存的大小就是一張特定格式圖像所需的大小。

前一篇博文中說到的,對於YUYV422格式,所需的大小是width * height * 2。所以AVFrame結構體的整個初始化過程如下:

 

[cpp]  view plain  copy
 
  1. AVFrame* frame = av_frame_alloc();  
  2.   
  3. //這里FFmpeg會幫我們計算這個格式的圖片,需要多少字節來存儲  
  4. //相當於前一篇博文例子中的width * height * 2  
  5. int bytes_num = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height); //AV_PIX_FMT_YUV420P是FFmpeg定義的標明YUV420P圖像格式的宏定義  
  6.   
  7. //申請空間來存放圖片數據。包含源數據和目標數據  
  8. uint8_t* buff = (uint8_t*)av_malloc(bytes_num);  
  9.   
  10. //前面的av_frame_alloc函數,只是為這個AVFrame結構體分配了內存,  
  11. //而該類型的指針指向的內存還沒分配。這里把av_malloc得到的內存和AVFrame關聯起來。  
  12. //當然,其還會設置AVFrame的其他成員  
  13. 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數組的三個指針的對應的。

 


 
  1. fread(frame->data[0], 1, bytes_num, fin);  

        同樣對於寫圖像也是如此。無需分data[0]、data[1]、data[2]。

 

        扯了這么多,還沒說FFmpeg是怎么轉換圖像格式的。現在來說一下。

        FFmpeg定義了一個結構體SwsContext,它記錄進行圖像格式轉換時,源圖像和目標圖像的格式、大小分別是什么。然后用sws_scale函數直接轉換即可

過程如下:

 

 
  1. SwsContext* sws_ctx = sws_getContext(src_width, src_height,  
  2.                                      AV_PIX_FMT_YUV420P,  
  3.                                      dst_width, dst_height,  
  4.                                      AV_PIX_FMT_YUYV422,  
  5.                                      SWS_BICUBIC,  
  6.                                      NULL,  
  7.                                      NULL,  
  8.                                      NULL);  
  9.   
  10. sws_scale(sws_ctx, src_frame->data, src_frame->linesize,  
  11.           0, height, //源圖像的高  
  12.           dst_frame->data, dst_frame->linesize);  

 

 

        下面給出完整的轉換例子。該例子將YUV420P轉換成YUYV422,並寫入一個文件中。

 
  1. #ifdef __cplusplus  
  2.  #define __STDC_CONSTANT_MACROS  
  3.  #ifdef _STDINT_H  
  4.   #undef _STDINT_H  
  5.  #endif  
  6.  # include <stdint.h>  
  7. #endif  
  8.   
  9. extern "C"  
  10. {  
  11. #include<libavcodec/avcodec.h>  
  12. #include<libavformat/avformat.h>  
  13. #include<libavutil/log.h>  
  14. #include<libswscale/swscale.h>  
  15. }  
  16.   
  17. #include<stdio.h>  
  18.   
  19. #include <windows.h> //for saveAsBitmap  
  20.   
  21. bool saveAsBitmap(AVFrame *pFrameRGB, int width, int height, int iFrame)  
  22. {  
  23.       FILE *pFile = NULL;  
  24.       BITMAPFILEHEADER bmpheader;  
  25.       BITMAPINFO bmpinfo;  
  26.   
  27.       char fileName[32];  
  28.       int bpp = 24;  
  29.   
  30.       // open file  
  31.       sprintf(fileName, "frame%d.bmp", iFrame);  
  32.       pFile = fopen(fileName, "wb");  
  33.       if (!pFile)  
  34.             return false;  
  35.   
  36.       bmpheader.bfType = ('M' <<8)|'B';  
  37.       bmpheader.bfReserved1 = 0;  
  38.       bmpheader.bfReserved2 = 0;  
  39.       bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  
  40.       bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;  
  41.   
  42.       bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);  
  43.       bmpinfo.bmiHeader.biWidth = width;  
  44.       bmpinfo.bmiHeader.biHeight = -height; //reverse the image  
  45.       bmpinfo.bmiHeader.biPlanes = 1;  
  46.       bmpinfo.bmiHeader.biBitCount = bpp;  
  47.       bmpinfo.bmiHeader.biCompression = BI_RGB;  
  48.       bmpinfo.bmiHeader.biSizeImage = 0;  
  49.       bmpinfo.bmiHeader.biXPelsPerMeter = 100;  
  50.       bmpinfo.bmiHeader.biYPelsPerMeter = 100;  
  51.       bmpinfo.bmiHeader.biClrUsed = 0;  
  52.       bmpinfo.bmiHeader.biClrImportant = 0;  
  53.   
  54.       fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, pFile);  
  55.       fwrite(&bmpinfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, pFile);  
  56.       uint8_t *buffer = pFrameRGB->data[0];  
  57.       for (int h=0; h<height; h++)  
  58.       {  
  59.             for (int w=0; w<width; w++)  
  60.             {  
  61.                   fwrite(buffer+2, 1, 1, pFile);  
  62.                   fwrite(buffer+1, 1, 1, pFile);  
  63.                   fwrite(buffer, 1, 1, pFile);  
  64.   
  65.                   buffer += 3;  
  66.             }  
  67.       }  
  68.       fclose(pFile);  
  69.   
  70.       return true;  
  71. }  
  72.   
  73. int main(int argc, char** argv)  
  74. {  
  75.     const char* filename = argc > 1 ? argv[1] : "flower_cif.yuv";  
  76.   
  77.     FILE* fin = fopen(filename, "rb");  
  78.     if( fin == NULL )  
  79.     {  
  80.         printf("can't open the file\n");  
  81.         return -1;  
  82.     }  
  83.   
  84.     int width = 352;  
  85.     int height = 288;  
  86.   
  87.     AVPixelFormat src_fmt = AV_PIX_FMT_YUV420P;  
  88.     AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;  
  89.   
  90.   
  91.     AVFrame* src_frame = av_frame_alloc();  
  92.     AVFrame* dst_frame = av_frame_alloc();  
  93.     if( src_frame == NULL || dst_frame == NULL )  
  94.     {  
  95.         printf("av_frame_alloc fail\n");  
  96.         return -1;  
  97.     }  
  98.   
  99.     //這里FFmpeg會幫我們計算這個格式的圖片,需要多少字節來存儲  
  100.     //相當於前面例子中的width * height * 2  
  101.     int src_bytes_num = avpicture_get_size(src_fmt,  
  102.                                            width, height);  
  103.     int dst_bytes_num = avpicture_get_size(dst_fmt,  
  104.                                            width, height);  
  105.   
  106.     //申請空間來存放圖片數據。包含源數據和目標數據  
  107.     uint8_t* src_buff = (uint8_t*)av_malloc(src_bytes_num);  
  108.     uint8_t* dst_buff = (uint8_t*)av_malloc(dst_bytes_num);  
  109.   
  110.     //前面的av_frame_alloc函數,只是為這個AVFrame結構體分配了內存,  
  111.     //而該類型的指針指向的內存還沒分配。這里把av_malloc得到的內存和AVFrame關聯起來。  
  112.     //當然,其還會設置AVFrame的其他成員  
  113.     avpicture_fill((AVPicture*)src_frame, src_buff, src_fmt,  width, height);     //該函數會自動填充AVFrame的data和linesize字段
  114.   
  115.     avpicture_fill((AVPicture*)dst_frame, dst_buff, dst_fmt,  
  116.                    width, height);  
  117.   
  118.   
  119.     //這里主要說明linesize這個成員的含義。不想看可以忽略  
  120.     //YUV格式中有一個很重要的等量關系,那就是有多少個像素就有多少個y。  
  121.     //linesize正如其名,一條線(即一行)的大小。對於yuv420p(planar)。data[0]存放的是y,對應地linesize[0]就  
  122.     //指明一行有多少個y。對於352*288的圖像,一行有352個像素。根據剛才的等量關系。那么linesize[0]就  
  123.     //應該為352.即一行有352個y。對於linesize[1],因為data[1]存放的是u。而一行352個像素在yuv420p格式中,  
  124.     //其只需352/2,即176個。所以linesize[1]的大小為176。同理linesize[2]也為176  
  125.   
  126.     //而對於yuyv422格式。data[0]這一行要負責存放y、u、v這三個分量。而y:u:v = 2:1:1的關系。根據前面所說的  
  127.     //等量關系,y等於352(相對於352*288大小的圖像來說),u和v都等於352/2 。所以u+v等於352。所以linesize[0]  
  128.     //等於352*2.  
  129.     printf("%d %d %d\n", src_frame->linesize[0],  src_frame->linesize[1], src_frame->linesize[2]);  
  130.     printf("%d %d %d \n", dst_frame->linesize[0],  dst_frame->linesize[1], dst_frame->linesize[2]);  
  131.   
  132.   
  133.     //對轉換進行配置。這里要設置轉換源的大小、格式和轉換目標的大小、格式  
  134.     //設置后,下面就可以直接使用sws_scale函數,進行轉換  
  135.     SwsContext* sws_ctx = sws_getContext(width, height,  
  136.                                          src_fmt,  
  137.                                          width, height,  
  138.                                          dst_fmt,  
  139.                                          SWS_BICUBIC,  
  140.                                          //SWS_BILINEAR,  
  141.                                          NULL,  
  142.                                          NULL,  
  143.                                          NULL);  
  144.   
  145.     if( sws_ctx == NULL)  
  146.     {  
  147.         printf("sws_getContext fail ");  
  148.         return -1;  
  149.     }  
  150.   
  151.   
  152.     FILE* fout = fopen("yuyv422.yuv""wb");  
  153.     int count = 0;  
  154.   
  155.     while( 1 )  
  156.     {  
  157.         int ret = fread(src_frame->data[0], 1, src_bytes_num, fin);  
  158.         if( ret != src_bytes_num )  
  159.         {  
  160.             printf("don't read enough data %d\n", ret);  
  161.             break;  
  162.         }  
  163.   
  164.         sws_scale(sws_ctx, src_frame->data, src_frame->linesize,  
  165.                   0, height,  
  166.                   dst_frame->data, dst_frame->linesize);  
  167.   
  168.   
  169.         ret = fwrite(dst_frame->data[0], 1, dst_bytes_num, fout);  
  170.         if( ret != dst_bytes_num )  
  171.             printf("don't write enough data %d \n", ret);  
  172.   
  173.   
  174.         //如果要保存為BMP格式,要把目標圖像的格式設置為RGB24。  
  175.         //只需把前面的AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;  
  176.         //改成AVPixelFormat dst_fmt = AV_PIX_FMT_RGB24;即可  
  177.         saveAsBitmap(dst_frame, width, height, count++);  
  178.     }  
  179.   
  180.   
  181.     av_free(src_frame);  
  182.     av_free(dst_frame);  
  183.     av_free(src_buff);  
  184.     av_free(dst_buff);  
  185.   
  186.     sws_freeContext(sws_ctx);  
  187.   
  188.   
  189.     fclose(fin);  
  190.     fclose(fout);  
  191.   
  192.     return 0;  
  193. }  

 

例子中,還可以把圖像保存成bmp圖片。那個函數來自:http://blog.csdn.net/ajaxhe/article/details/7340508

例子中用到的YUV420P格式的文件,可以到這里下載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM