注:本文参考,但是这篇比较老旧了,文中用的最新版的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();
-
...
-
}
这里调用的av_register_all会注册所有FFmpeg库支持的文件格式和codec,并且
av_register_all只调用一次。当一个文件被打开的时候,FFmpeg会自动找到对应的codec。
接下来打开媒体文件:
-
AVFormatContext *pFormatCtx;
-
-
// Open video file
-
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
-
return -1; // Couldn't open file
这里调用
avformat_open_input函数打开媒体文件的,媒体文件名由main函数参数argv传递进来。
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
然后调用一个用来debug的函数
dump_format,会在屏幕上打印一些该媒体文件的信息:
-
// Dump information about file onto standard error
-
av_dump_format(pFormatCtx, 0, argv[1], 0);
到此,该媒体的Video流会在一个数组中,
pFormatCtx->streams是这个数组的指针,数组大小是
pFormatCtx->nb_streams,下面来从数组中找到Video流数据:
-
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;
这里我们得到了这个流使用的codec的所有信息。用
pCodecCtx 指向这个信息位置。然后下面就利用
pCodecCtx 来找到这个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();
还需要一个地方来存放从这个pFrame帧转换成的RGB帧:
-
// Allocate an AVFrame structure
-
pFrameRGB = avcodec_alloc_frame();
-
if(pFrameRGB == NULL)
-
return -1;
然后把每个RGB帧画面输出到PPM文件中,PPM文件格式用24-bit RGB保存。现在要做的就是把帧frame从当前格式转换成RGB。当然FFmpeg会帮我们完成这个任务,大多数情况下,我们想把帧frame转换成指定格式。
别急,还需要一个内存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));
这里用的
av_malloc函数其实只是封装了malloc,并做了一些内存对齐操作,对于内存的操作,防止内存溢出以及释放,都跟malloc一样,需要我们自己来做。
下面将
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);
-
}
SaveFrame函数如下,先新建一个ppm文件,先把文件头写进去,然后把数据写进去,
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);
-
}
到此,该做些收尾工作了,释放内存,如果是在main函数中,最后记得返回:
-
// 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;
这里的
av_free函数对应上面的
av_malloc函数。
OK,到此完成了!编译,运行,会发现当前目录下有15长PPM图片:
阅读(8517) | 评论(0) | 转发(0) |