多媒体的时代,得多了解点编解码的技术才行,而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 !

DownLoad_SourceCode_ClickHere (免分哦,亲!)

最新文章

  1. 认识Git
  2. TSQL HASHBYTES 用法
  3. C# .net ACCESS 网页增删改查 --留言板
  4. ROS主题发布订阅控制真实的机器人下位机
  5. html 微信开发——微信授权
  6. 新浪微博failed to receive access token
  7. 将证书写到jre中
  8. 软中断与硬中断 &amp; 中断抢占 中断嵌套
  9. 坏块管理(Bad Block Management,BBM)
  10. Android内核三大核心功能之一AMS内部原理
  11. day8(字符串操作)
  12. ORACLE字符集修改ORA-02374\ORA-12899\ORA-02372
  13. springmvc的日期类型转换
  14. Failed to set MokListRT: Invalid Parameter Something as gone seriously wrong: import_mok_state() failed: Invalid Parameter
  15. Oracle异常:Caused by: java.sql.SQLException: ORA-01536: 超出表空间 &#39;登录名&#39; 的空间限额 (JPA保存数据)
  16. 海思QT4.8.0开发总结
  17. Pycharm常用配置
  18. Nexus 搭建maven 私有仓库
  19. Eclipse web项目更改项目名称
  20. caffe 配置文件详解

热门文章

  1. java基础2
  2. nginx和apache的特点优点和使用场景
  3. HTTP get、post请求
  4. HDU 3001 状压DP
  5. Qt窗口句柄
  6. js页面加载进度条(这个就比较正式了,改改时间就完事儿)
  7. mysql函数date_format统计刷选按年月日统计的数据
  8. 调查程序phpquestionnaire 2.2中文安装注意
  9. Python一路走来 - 模块
  10. [C入门 - 游戏编程系列] 贪吃蛇篇(三) - 蛇定义