多媒体的时代,得多了解点编解码的技术才行,而ffmpeg为我们提供了一系列多媒体编解码的接口,如何用好这些接口达到自己所需要的目的,这也是一门重要的学问。
要是了解得不够,总是会遇到一堆又一堆问题;网上关于ffmpeg的讲解,说少也不少,说多也不多,由于版本更新又更新,能找着的资料基本上都不大能对得上,需要进行一定量的修改才能正常工作;所以,我也借着这个机会,重新走一遍ffmpeg的入门,然后理清同步等问题。
本文主要讲的是ffmpeg解码最基本的步骤,以及其用到的接口,另附有完整的实例代码。
使用工具:FFMPEG(2.0.1)、VS2010
平台:WINDOWS
下面就开始吧……
步骤一:初始化ffmpeg(必须的)
av_register_all();
avcodec_register_all();
使用以上语句初始化ffmpeg的功能,简单,没什么好讲的。
步骤二:打开多媒体文件,检查头部信息
AVFormatContext *pFormatCtx = avformat_alloc_context();;
// Open video file
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL)!=0)
return -1; // Couldn't open file
avformat_open_input()这个函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中,注意要先调用avformat_alloc_context()为我们的结构体申请空间,否则读取文件的信息无法保存,avformat_open_input会失败。
步骤三:检查文件中的流的信息
// 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, filename, 0);
av_find_stream_info()这个函数为pFormatCtx->streams填充上正确的信息。av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容。
步骤四:找出文件中的视频流
int i;
AVCodecContext *pCodecCtx;
// Find the first video stream
int videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_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;
pFormatCtx->streams仅仅是一组大小为pFormatCtx->nb_streams的指针,所以让我们可以使用它找到一个视频流,并记录其在stream中的下标号(后面根据此标号可以与该视频匹配)。当然,视频流有可能不止一个,如果想要把所有视频流都找到,可以使用数组记录,就不需要break了。
AVCodecContext,流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西。这里面包含了流中所使用的关于编解码器的所有信息,现在我们有了一个指向他的指针。
步骤五:找到该视频流真正的编解码器并且打开它
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
avcodec_find_decoder()根据找到的视频流格式,找到想应的编解码器;avcodec_open2()可以开启我们的解码器使其处于随时可工作的状态,因为ffmpeg版本更新过许多次,所以有些Function名字都会带一些数字。
步骤六:申请数据空间(保存帧数据用)
AVFrame *pFrame, *pFrameRGB;
// Allocate video frame
pFrame=avcodec_alloc_frame();
// Allocate an AVFrame structure
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
return -1;
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));
// 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);
AVFrame就是用来保存帧数据的,之所以定义了两个,是因为解码后帧数据一般是YUV格式,我们转换成RGB的帧数据,还需要一个存储的地方。我们使用avpicture_get_size()来获得我们需要的大小,然后用av_malloc手工申请内存空间,avpicture_fill来把帧和我们新申请的内存来结合。
步骤七:读取数据并解码
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
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if(frameFinished) {
// Convert the image from its native format to RGB
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getCachedContext(img_convert_ctx,
pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
if (!pCodecCtx)
{
printf("Cannot initialize sws conversion context\r\n");
return NULL;
}
sws_scale(img_convert_ctx,
(const uint8_t* const*)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // Save the frame to disk
if(++i<=5)
SaveFrame(pFrameRGB, pCodecCtx->width,
pCodecCtx->height, i);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}
这个循环过程是比较简单的:av_read_frame()读取一个包并且把它保存到AVPacket结构体中。注意我们仅仅申请了一个包的结构体 ――ffmpeg为我们申请了内部的数据的内存并通过packet.data指针来指向它。这些数据可以在后面通过av_free_packet()来释放。
函数avcodec_decode_video2()把包转换为帧。然而当解码一个包的时候,我们可能没有得到我们需要的关于帧的信息。因此,当我们得到下一帧的时候,avcodec_decode_video2()为我们设置了帧结束标志frameFinished。
然后,我们使用sws_getCachedContext()函数设置格式转换器,sws_scale()函数来把帧从原始格式(pCodecCtx->pix_fmt)转换成为RGB格式。最后,我们把帧和高度宽度信息传递给我们的SaveFrame()函数,保存成PPM的图片。
步骤八:清理一切,程序结束
// 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;
一旦我们开始读取完视频流,退出前需要释放所有申请的空间。
以上,就是ffmpeg解码的基本流程。最近也比较忙,时间也比较晚了,累了犯困,就不在此赘述环境的配置了,附上工程下载地址有兴趣的自行研究吧!
同时,欢迎广大志同道合的朋友讨论交流,要知道,You are not alone !