用ffmpeg进行图像格式转换

来源:互联网 发布:pivot table选出数据 编辑:程序博客网 时间:2024/05/20 01:37

     上一篇介绍了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。

        在上面的代码中,运行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数组的三个指针的对应的。

[cpp] view plain copy
  1. fread(frame->data[0], 1, bytes_num, fin);  
        同样对于写图像也是如此。无需分data[0]、data[1]、data[2]。

        扯了这么多,还没说FFmpeg是怎么转换图像格式的。现在来说一下。

        FFmpeg定义了一个结构体SwsContext,它记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。然后用sws_scale函数直接转换即可。过程如下:

[cpp] view plain copy
  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,并写入一个文件中。

[cpp] view plain copy
  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. //在linux下可以自行加入以下定义:
  21.  
  22.   typedef struct tagBITMAPFILEHEADER
    {
        WORD bfType;//位图文件的类型,必须为BM(1-2字节)
        DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前)
        WORD bfReserved1;//位图文件保留字,必须为0(7-8字节)
        WORD bfReserved2;//位图文件保留字,必须为0(9-10字节)
        DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前)文件头的偏移量表示,以字节为单位
    }BITMAPFILEHEADER;

  23. typedef struct tagBITMAPINFOHEADER{
    DWORD biSize;//本结构所占用字节数(15-18字节)
    LONG biWidth;//位图的宽度,以像素为单位(19-22字节)
    LONG biHeight;//位图的高度,以像素为单位(23-26字节)
    WORD biPlanes;//目标设备的级别,必须为1(27-28字节)
    WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节)
    //4(16色),8(256色)16(高彩色)或24(真彩色)之一
    DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)
    //1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
    DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)
    LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
    LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)
    DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节)
    DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节)
    }BITMAPINFOHEADER;

//自定义函数:将RGB数据保存成bmp格式
  1. bool saveAsBitmap(AVFrame *pFrameRGB, int width, int height, int iFrame)  
  2. {  
  3.       FILE *pFile = NULL;  
  4.       BITMAPFILEHEADER bmpheader;  
  5.       BITMAPINFOHEADER bmpinfo;  
  6.   
  7.       char fileName[32];  
  8.       int bpp = 24;  
  9.   
  10.       // open file  
  11.       sprintf(fileName, "frame%d.bmp", iFrame);  
  12.       pFile = fopen(fileName, "wb"); //新建文件 
  13.       if (!pFile)  
  14.             return false;  
  15.   
  16.       bmpheader.bfType = ('M' <<8)|'B';  //类型规定必须为BM
  17.       bmpheader.bfReserved1 = 0;  
  18.       bmpheader.bfReserved2 = 0;  
  19.       bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  
  20.       bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;//一个像素3字节,文件头大小+像素数据大小即为文件大小  
  21.   
  22.       bmpinfo.biSize = sizeof(BITMAPINFOHEADER);  
  23.       bmpinfo.biWidth = width;  
  24.       bmpinfo.biHeight = -height; //reverse the image  
  25.       bmpinfo.biPlanes = 1;  //规定必须为1
  26.       bmpinfo.biBitCount = bpp; //一个像素3字节表示
  27.       bmpinfo.biCompression = 0; 
  28.       bmpinfo.biSizeImage = ((((width * bpp) + 31) & ~31) / 8) * height;
  29.       bmpinfo.biXPelsPerMeter = 100;  
  30.       bmpinfo.biYPelsPerMeter = 100;  
  31.       bmpinfo.biClrUsed = 0;  
  32.       bmpinfo.biClrImportant = 0;  
  33.   
  34.       fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, pFile);  
  35.       fwrite(&bmpinfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, pFile);  
  36.       uint8_t *buffer = pFrameRGB->data[0];  
  37.       for (int h=0; h<height; h++)  
  38.       {  
  39.             for (int w=0; w<width; w++)  
  40.             {  
  41.                   fwrite(buffer+2, 1, 1, pFile);  
  42.                   fwrite(buffer+1, 1, 1, pFile);  
  43.                   fwrite(buffer, 1, 1, pFile);  
  44.   
  45.                   buffer += 3;  
  46.             }  
  47.       }  
  48.       fclose(pFile);  
  49.   
  50.       return true;  
  51. }  
  52.   
  53. int main(int argc, char** argv)  
  54. {  
  55.     const char* filename = argc > 1 ? argv[1] : "flower.yuv";  
  56.   
  57.     FILE* fin = fopen(filename, "rb");  
  58.     if( fin == NULL )  
  59.     {  
  60.         printf("can't open the file\n");  
  61.         return -1;  
  62.     }  
  63.   
  64.     int width = 352;  
  65.     int height = 288;  
  66.   
  67.     AVPixelFormat src_fmt = AV_PIX_FMT_YUV420P;  
  68.     AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;  
  69.   
  70.   
  71.     AVFrame* src_frame = av_frame_alloc();  
  72.     AVFrame* dst_frame = av_frame_alloc();  
  73.     if( src_frame == NULL || dst_frame == NULL )  
  74.     {  
  75.         printf("av_frame_alloc fail\n");  
  76.         return -1;  
  77.     }  
  78.   
  79.     //这里FFmpeg会帮我们计算这个格式的图片,需要多少字节来存储  
  80.     //相当于前面例子中的width * height * 2  
  81.     int src_bytes_num = avpicture_get_size(src_fmt,width, height);  
  82.     int dst_bytes_num = avpicture_get_size(dst_fmt,width, height);  
  83.   
  84.     //申请空间来存放图片数据。包含源数据和目标数据  
  85.     uint8_t* src_buff = (uint8_t*)av_malloc(src_bytes_num);  
  86.     uint8_t* dst_buff = (uint8_t*)av_malloc(dst_bytes_num);  
  87.   
  88.     //前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,  
  89.     //而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来。  
  90.     //当然,其还会设置AVFrame的其他成员  
  91.     avpicture_fill((AVPicture*)src_frame, src_buff, src_fmt,   width, height);  
  92.   
  93.     avpicture_fill((AVPicture*)dst_frame, dst_buff, dst_fmt,   width, height);  
  94.   
  95.   
  96.     //这里主要说明linesize这个成员的含义。不想看可以忽略  
  97.     //YUV格式中有一个很重要的等量关系,那就是有多少个像素就有多少个y。  
  98.     //linesize正如其名,一条线(即一行)的大小。对于yuv420p。data[0]存放的是y,对应地linesize[0]就  
  99.     //指明一行有多少个y。对于352*288的图像,一行有352个像素。根据刚才的等量关系。那么linesize[0]就  
  100.     //应该为352.即一行有352个y。对于linesize[1],因为data[1]存放的是u。而一行352个像素在yuv420p格式中,  
  101.     //其只需352/2,即176个。所以linesize[1]的大小为176。同理linesize[2]也为176。  
  102.   
  103.     //而对于yuyv422格式。data[0]这一行要负责存放y、u、v这三个分量。而y:u:v = 2:1:1的关系。根据前面所说的  
  104.     //等量关系,y等于352(相对于352*288大小的图像来说),u和v都等于352/2 。所以u+v等于352。所以linesize[0]  
  105.     //等于352*2.  
  106.     printf("%d %d %d\n", src_frame->linesize[0],  
  107.             src_frame->linesize[1], src_frame->linesize[2]);  
  108.     printf("%d %d %d \n", dst_frame->linesize[0],  
  109.             dst_frame->linesize[1], dst_frame->linesize[2]);  
  110.   
  111.   
  112.     //对转换进行配置。这里要设置转换源的大小、格式和转换目标的大小、格式  
  113.     //设置后,下面就可以直接使用sws_scale函数,进行转换  
  114.     SwsContext* sws_ctx = sws_getContext(width, height,   
  115. src_fmt,  
  116. width, 
  117. height,   
  118. dst_fmt,  
  119. SWS_BICUBIC,  
  120.                                          //SWS_BILINEAR,  
  121.                                          NULL,  
  122.                                          NULL,  
  123.                                          NULL);  
  124.   
  125.     if( sws_ctx == NULL)  
  126.     {  
  127.         printf("sws_getContext fail ");  
  128.         return -1;  
  129.     }  
  130.   
  131.   
  132.     FILE* fout = fopen("yuyv422.yuv""wb");  
  133.     int count = 0;  
  134.   
  135.     while( 1 )  
  136.     {  
  137.         int ret = fread(src_frame->data[0], 1, src_bytes_num, fin);  
  138.         if( ret != src_bytes_num )  
  139.         {  
  140.             printf("don't read enough data %d\n", ret);  
  141.             break;  
  142.         }  
  143.   
  144.         sws_scale(sws_ctx, src_frame->data, src_frame->linesize,  
  145.                   0, height,  
  146.                   dst_frame->data, dst_frame->linesize);  
  147.   
  148.   
  149.         ret = fwrite(dst_frame->data[0], 1, dst_bytes_num, fout);  
  150.         if( ret != dst_bytes_num )  
  151.             printf("don't write enough data %d \n", ret);  
  152.   
  153.   
  154.         //如果要保存为BMP格式,要把目标图像的格式设置为RGB24。  
  155.         //只需把前面的AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;  
  156.         //改成AVPixelFormat dst_fmt = AV_PIX_FMT_RGB24;即可  
  157.         saveAsBitmap(dst_frame, width, height, count++);  
  158.     }  
  159.   
  160.   
  161.     av_free(src_frame);  
  162.     av_free(dst_frame);  
  163.     av_free(src_buff);  
  164.     av_free(dst_buff);  
  165.   
  166.     sws_freeContext(sws_ctx);  
  167.   
  168.   
  169.     fclose(fin);  
  170.     fclose(fout);  
  171.   
  172.     return 0;  
  173. }  

原创粉丝点击