图像视频编码和FFmpeg(3)-----⽤FFmpeg进⾏图像格式转换
和AVFrame简。。。
上⼀篇介绍了YUV格式,并给出了⼀个YUYV422转RGB24的例⼦。其实,FFmpeg有⼀个函数专门进⾏图像格式转换的。本⽂就介绍怎么⽤FFmpeg转换,因为在转换时还要⽤到AVFrame这个结构体,所以这⾥也会介绍AVFrame。在FFmpeg中,AVFrame是⼀个⽐较重要的结构体。
AVFrame,顾名思义,这个结构体应该是保存视频帧的信息的。像⼀帧图像也是可以保存在AVFrame结构中。事实上,我们可以直接从⼀个YUV⽂件中,把⼀张YUV图像数据读到AVFrame中。本⽂后⾯的例⼦也是这样做的。
为了弄懂AVFrame是怎么存放⼀张YUV图像的(当然AVFrame可以存放其他格式图像的),现在先看⼀下AVFrame结构体的主要成员。
[cpp]
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]
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;
fopen和open区别
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. }
例⼦中⽤到的YUV420P格式的⽂件,可以到下载。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。