ffmpeg将多媒体文件的Video Stream每帧画面保存为PPM格式图片
转自:http://blog.chinaunix.net/uid-20846214-id-4193590.html
注:本文参考http://dranger.com/ffmpeg/tutorial01.html,但是这篇比较老旧了,文中用的最新版的FFmpeg,很多API都跟老版的不同,请大家注意。
在最简单的情况下,其实处理Video和Audio的步骤是非常简单的:
1:open video_stream从video.avi中
2:从video_stream中读取packet到frame里面
3:如果frame不完整就goto到第2步继续
4:对frame做些处理
5:跳到第2步重复
packet包含了要被解码成原始数据帧frame的数据块。每个packet都包含了完整的frames。
这章,我们会打开一个媒体文件,从文件里面读取Video Stream,然后把帧frame写入一个PPM文件,PPM(Portable Pixelmap)文件是一种linux图片格式,它很简单,只包含格式,图像宽高,bit数等信息以及图像数据。
一:打开文件
要用FFmpeg库中的支持,必须包含它的头文件:
- #include <avcodec.h>
- #include <avformat.h>
- ...
- ...
- int main(int argc, char **argv)
- {
- ...
- av_register_all();
- ...
- }
接下来打开媒体文件:
- AVFormatContext *pFormatCtx;
-
// Open video file
-
if(avformat_open_input(&pFormatCtx, argv[1],
NULL,
NULL)!=0)
- return -1; // Couldn't open file
avformat_open_input函数会读取媒体文件的头并且把这些信息保存到AVFormatContext结构体中,最后2个参数是用来指定文件格式,buffer大小和格式参数,设置成NULL的话,libavformat库会自动去探测它们。
接下来我们需要Check Out这个文件的stream信息:
- // Retrieve stream information
-
if (av_find_stream_info(pFormatCtx) < 0)
- return -1; // Couldn't find stream information
- // Dump information about file onto standard
error
- av_dump_format(pFormatCtx, 0, argv[1], 0);
- int i;
- AVCodecContext *pCodecCtx;
-
// Find the first video stream
- videoStream = -1;
-
for (i=0; i<pFormatCtx->nb_streams;
i++) {
- if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
{
- videoStream = i;
- break;
- }
- }
-
if(videoStream == -1)
- return -1;
// Didn't find a video stream
-
//
Get a pointer to the codec context
for the video stream
- pCodecCtx = pFormatCtx->streams[videoStream]->codec;
- AVCodec *pCodec;
-
// Find the decoder
for the video stream
- pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
-
if (pCodec == NULL)
{
- fprintf(stderr,
"Unsupported codec!\n");
- return -1;
// Codec
not found
-
}
-
// Open codec
-
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
- return -1; // Could not open codec
二:存储数据
现在我们需要一个地方来存放从媒体文件中解码出的原始数据帧frame:
- AVFrame *pFrame;
-
// Allocate video frame
- pFrame = avcodec_alloc_frame();
- // Allocate an AVFrame structure
- pFrameRGB = avcodec_alloc_frame();
-
if(pFrameRGB == NULL)
- return -1;
别急,还需要一个内存buffer,用来存放媒体文件的中即将要被转换的原始数据,我们用avpicture_get_size函数来获取需要的size:
- uint8_t *buffer;
-
int numBytes;
-
// Determine required buffer size
and allocate buffer
- numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
- pCodecCtx->height);
- buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
下面将pFrameRGB与buffer关联起来,这里的AVPicture 结构是AVFrame 结构的子集,AVPicture 结构与AVFrame 结构的开始部分一模一样:
- // Assign appropriate parts of buffer
to image planes in pFrameRGB
-
// Note that pFrameRGB
is an AVFrame, but AVFrame
is a superset
-
// of AVPicture
- avpicture_fill((AVPicture
*)pFrameRGB, buffer, PIX_FMT_RGB24,
- pCodecCtx->width, pCodecCtx->height);
三:读数据
下面通过函数av_read_frame读取Video Stream到packet中。然后用解码器pCodecCtx从packet.data中解码出原始数据帧并存放到pFrame中。参数frameFinished判断转换的结果。如果转换完成,调用img_convert函数把原始数据帧pFrame转换成我们要的RGB帧pFrameRGB,并调用SaveFrame函数把RGB帧保存成一个个的PPM图片(这里我们只保存了Video Stream的前15张图片,可以根据个人需要修改)。while循环最后会调用av_free_packet函数清除av_read_frame函数中读入packet的数据,然后循环继续读packet,继续解码,继续转换,继续保存成PPM图片,直到读完整个媒体文件。注意这里自己准备一个pSwsCtx结构,这个结构比较灵活,可以对即将要生成的PPM图片进行操作配置,如反转图片。
- int frameFinished;
- AVPacket packet;
- i=0;
-
while(av_read_frame(pFormatCtx,
&packet)>=0)
{
- //
Is this a packet from the video stream?
- if(packet.stream_index==videoStream)
{
- // Decode video frame pCodecCtx, pFrame, &frameFinished, &packet
- avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
-
- // Did we
get a video frame?
- if(frameFinished)
{
- // Convert the image from its native format
to RGB
- sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); -
- // Save the frame
to disk
- if(++i<=15)
- SaveFrame(pFrameRGB, pCodecCtx->width,
- pCodecCtx->height, i);
- }
- }
-
- // Free the packet that was allocated by av_read_frame
- av_free_packet(&packet);
- }
ppm的第一部分由三行ASCII码组成:
第一行是P2 or P3 or P6,我们写的P6
第二行是图像的大小,先是列像素数,后是行像素数,中间有一个空格,我们写的%d %d
第三行是一个介于1和65535之间的整数,而且必须是文本的,用来表示每一个像素的一个分量用几个比特表示,我们写的255,即8bit表示一个像素分量,那一个像素就是24-bit了。
三行之后是图像的纯数据流,从左到右,从上到下。我们这里写数据部分,pFrame->data[0]是数据头,y是目前写入的行数,pFrame->linesize[0]是每行的字节数,pFrame->data[0]+y*pFrame->linesize[0]就是每行数据开头的地址。width是每行像素个数,width*3就是每行要写的数据个数,以像素分量为单位。
- void SaveFrame(AVFrame
*pFrame,
int width,
int height,
int iFrame)
{
- FILE *pFile;
- char szFilename[32];
- int y;
-
- // Open file
- sprintf(szFilename,
"frame%d.ppm", iFrame);
- pFile=fopen(szFilename,
"wb");
- if(pFile==NULL)
- return;
-
- // Write header
- fprintf(pFile,
"P6\n%d %d\n255\n", width, height);
-
- // Write pixel data
- for(y=0; y<height; y++)
- fwrite(pFrame->data[0]+y*pFrame->linesize[0],
1, width*3, pFile);
-
- // Close file
- fclose(pFile);
- }
- // Free the RGB image
- av_free(buffer);
- av_free(pFrameRGB);
-
// Free the YUV frame
- av_free(pFrame);
-
// Close the codec
- avcodec_close(pCodecCtx);
-
// Close the video file
- av_close_input_file(pFormatCtx);
- return 0;
OK,到此完成了!编译,运行,会发现当前目录下有15长PPM图片: