转自: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步重复
pac ket包含了要被解码成原始数据帧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 函 数会读取媒体文件的头并且把这些信息保存到 AVFormatCo ntext结构体中,最后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,用来存放媒体文件的中即将要被转换的原始数据,我们用 avpi cture_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));
下面将 pFr a meRGB与 buf fer关联起来,这里的 AVPic ture 结构是 AVFr ame 结构的子集, AVPic ture 结构与 AVFr ame 结构的开始部分一模一样:
- // 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中。然后用解码器 pCodecC tx从 packet . data中解码出原始数据帧并存放到 pF r ame中。参数 frameFin ished判断转换的结果。如果转换完成,调用 img _co n vert函数把原始数据帧 pFram e转换成我们要的RGB帧 pFrameRG B,并调用 Sav eF rame函数把RGB帧保存成一个个的PPM图片(这里我们只保存了Video Stream的前15张图片,可以根据个人需要修改)。while循环最后会调用 av_fre e_ packet函数清除 av_r ead_ frame函数中读入packet的数据,然后循环继续读packet,继续解码,继续转换,继续保存成PPM图片,直到读完整个媒体文件。注意这里自己准备一个 p SwsC tx结构,这个结构比较灵活,可以对即将要生成的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 ]就是每行数据开头的地址。 wi d th是每行像素个数, wi d th * 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图片: