从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片

原文地址:http://blog.yundiantech.com/?log=blog&id=8

前面我们已经知道怎么使用FFMPEG了。

现在,开始着手使用FFMPEG吧:


首先来个简单的例子,使用FFMPEG打开视频文件,并解码保存成一张张的图片。

具体的步骤如下所示:


1.首先需要先初始化一下,使用如下函数:

1
av_register_all();  //初始化FFMPEG  调用了这个才能正常适用编码器和解码器



使用这个函数完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用,否则会在打开编解码器的时候失败。


2.接着需要分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行

1
AVFormatContext *pFormatCtx = avformat_alloc_context();


3.接着调用打开视频文件

这里文件名先不要使用中文,否则会打开失败,后期再讲解如何处理中文。

1
2
char  *file_path =  "E:in.mp4" ;
avformat_open_input(&pFormatCtx, file_path, NULL, NULL);


4.文件打开成功后就是查找文件中的视频流了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     ///循环查找视频中包含的流信息,直到找到视频类型的流    
     ///便将其记录下来 保存到videoStream变量中
     ///这里我们现在只处理视频流  音频流先不管他
     for  (i = 0; i < pFormatCtx->nb_streams; i++) {
         if  (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
             videoStream = i;
         }
     }
 
     ///如果videoStream为-1 说明没有找到视频流
     if  (videoStream == -1) {
         printf ("Didn't find a video stream.
");
         return  -1;
     }


5.现在根据视频流  打开一个解码器来解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
     ///查找解码器    
     pCodecCtx = pFormatCtx->streams[videoStream]->codec;
     pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
 
     if  (pCodec == NULL) {
         printf ("Codec not found.
");
         return  -1;
     }
 
     ///打开解码器
     if  (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
         printf ("Could not open codec.
");
         return  -1;
     }


可以看出  我们可以直接根据查找到的视频流信息获取到解码器。

而且我们并不知道他实际用的是什么编码器。

这就是为什么一开始我们使用FFMPEG来操作,因为很多东西我们可以不关系。



6.现在开始读取视频了:

1
2
3
4
5
6
7
8
     int  y_size = pCodecCtx->width * pCodecCtx->height;
     AVPacket *packet = (AVPacket *)  malloc ( sizeof (AVPacket));  //分配一个packet
     av_new_packet(packet, y_size);  //分配packet的数据
 
     if  (av_read_frame(pFormatCtx, packet) < 0)
     {
         break //这里认为视频读取完了
     }

可以看出 av_read_frame读取的是一帧视频,并存入一个AVPacket的结构中。



7.前面我们说过 视频里面的数据是经过编码压缩的,因此这里我们需要将其解码:

1
2
3
4
5
6
7
8
9
10
     if  (packet->stream_index == videoStream) 
     {        
         ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
 
         if  (ret < 0) {
             printf ("decode error.
");
             return  -1;
         }
     }


8.基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件,因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成:

1
2
3
4
5
6
     if  (got_picture) {        
         sws_scale(img_convert_ctx,
                 (uint8_t  const  const  *) pFrame->data,
                 pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                 pFrameRGB->linesize);
     }


至于YUV420和RGB图像格式的具体内容,这里不用去了解。这里只需要知道有这么个东西就行了,对我们使用FFMPEG转换没有影响。



9.得到RGB数据之后就是直接写入文件了:

1
     SaveFrame(pFrameRGB,     pCodecCtx->width,pCodecCtx->height,index++);  //保存图片     if (index > 50) return 0; //这里我们就保存50张图片



至此读取视频解码保存成图片就写好了:

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
extern  "C" {
     #include  "libavcodec/avcodec.h"
     #include  "libavformat/avformat.h"
     #include  "libavutil/pixfmt.h"
     #include  "libswscale/swscale.h"
}
 
#include 
 
///现在我们需要做的是让SaveFrame函数能把RGB信息定稿到一个PPM格式的文件中。
///我们将生成一个简单的PPM格式文件,请相信,它是可以工作的。
void  SaveFrame(AVFrame *pFrame,  int  width,  int  height, int  index)
{
 
   FILE  *pFile;
   char  szFilename[32];
   int   y;
 
   // Open file
   sprintf (szFilename,  "frame%d.ppm" , index);
   pFile= fopen (szFilename,  "wb" );
 
   if (pFile==NULL)
     return ;
 
   // Write header
   fprintf (pFile, "P6
%d %d
255
", width, height);
 
   // Write pixel data
   for (y=0; y
   {
     fwrite (pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
   }
 
   // Close file
   fclose (pFile);
 
}
 
 
int  main( int  argc,  char  *argv[])
{
     char  *file_path =  "E:in.mp4" ;
 
     AVFormatContext *pFormatCtx;
     AVCodecContext *pCodecCtx;
     AVCodec *pCodec;
     AVFrame *pFrame, *pFrameRGB;
     AVPacket *packet;
     uint8_t *out_buffer;
 
     static  struct  SwsContext *img_convert_ctx;
 
     int  videoStream, i, numBytes;
     int  ret, got_picture;
 
     av_register_all();  //初始化FFMPEG  调用了这个才能正常适用编码器和解码器
 
     //Allocate an AVFormatContext.
     pFormatCtx = avformat_alloc_context();
 
     if  (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {
         printf ("can't open the file. 
");
         return  -1;
     }
 
     if  (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
         printf ("Could't find stream infomation.
");
         return  -1;
     }
 
     videoStream = -1;
 
     ///循环查找视频中包含的流信息,直到找到视频类型的流
     ///便将其记录下来 保存到videoStream变量中
     ///这里我们现在只处理视频流  音频流先不管他
     for  (i = 0; i < pFormatCtx->nb_streams; i++) {
         if  (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
             videoStream = i;
         }
     }
 
     ///如果videoStream为-1 说明没有找到视频流
     if  (videoStream == -1) {
         printf ("Didn't find a video stream.
");
         return  -1;
     }
 
     ///查找解码器
     pCodecCtx = pFormatCtx->streams[videoStream]->codec;
     pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
 
     if  (pCodec == NULL) {
         printf ("Codec not found.
");
         return  -1;
     }
 
     ///打开解码器
     if  (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
         printf ("Could not open codec.
");
         return  -1;
     }
 
     pFrame = av_frame_alloc();
     pFrameRGB = av_frame_alloc();
 
     img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
             pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
             PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
 
     numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,pCodecCtx->height);
 
     out_buffer = (uint8_t *) av_malloc(numBytes *  sizeof (uint8_t));
     avpicture_fill((AVPicture *) pFrameRGB, out_buffer, PIX_FMT_RGB24,
             pCodecCtx->width, pCodecCtx->height);
 
     int  y_size = pCodecCtx->width * pCodecCtx->height;
 
     packet = (AVPacket *)  malloc ( sizeof (AVPacket));  //分配一个packet
     av_new_packet(packet, y_size);  //分配packet的数据
 
     av_dump_format(pFormatCtx, 0, file_path, 0);  //输出视频信息
 
     int  index = 0;
 
     while  (1)
     {
         if  (av_read_frame(pFormatCtx, packet) < 0)
         {
             break //这里认为视频读取完了
         }
 
         if  (packet->stream_index == videoStream) {
             ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
 
             if  (ret < 0) {
                 printf ("decode error.
");
                 return  -1;
             }
 
             if  (got_picture) {
                 sws_scale(img_convert_ctx,
                         (uint8_t  const  const  *) pFrame->data,
                         pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                         pFrameRGB->linesize);
 
                 SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height,index++);  //保存图片
                 if  (index > 50)  return  0;  //这里我们就保存50张图片
             }
         }
         av_free_packet(packet);
     }
     av_free(out_buffer);
     av_free(pFrameRGB);
     avcodec_close(pCodecCtx);
     avformat_close_input(&pFormatCtx);
 
     return  0;
}


完整的工程下载:

http://download.csdn.net/detail/qq214517703/9623516


音视频技术交流讨论欢迎加 QQ群 121376426  


原文地址:http://blog.yundiantech.com/?log=blog&id=8


你可能感兴趣的:(从零开始学习音视频编程技术)