本文转载

视频播放过程

首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

FFmpeg的API就是根据这个过程设计的,因此使用FFmpeg来处理视频文件的方法非常直观简单。下面就一步一步介绍从视频文件中解码出图片的过程。

声明变量

首先定义整个过程中需要使用到的变量:

int main(int argc,constchar*argv[]){AVFormatContext*pFormatCtx = NULL;int             i, videoStream;AVCodecContext*pCodecCtx;AVCodec*pCodec;AVFrame*pFrame;AVFrame*pFrameRGB;AVPacket        packet;int             frameFinished;int             numBytes;uint8_t*buffer;
}
  • AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等
  • AVCodecCotext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。
  • pCodec:真正的编解码器,其中有编解码需要调用的函数
  • AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像
  • AVPacket:解析文件时会将音/视频帧读入到packet中

打开文件

接下来我们打开一个视频文件。

1 av_register_all();

av_register_all 定义在 libavformat 里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。

1 if( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 )
2     return -1;

使用新的API avformat_open_input来打开一个文件,第一个参数是一个AVFormatContext指针变量的地址,它会根据打开的文件信息填充AVFormatContext,需要注意的是,此处的pFormatContext必须为NULL或由avformat_alloc_context分配得到,这也是上一节中将其初始化为NULL的原因,否则此函数调用会出问题。第二个参数是打开的文件名,通过argv[1]指定,也就是命令行的第一个参数。后两个参数分别用于指定特定的输入格式(AVInputFormat)以及指定文件打开额外参数的AVDictionary结构,这里均留作NULL。

  if( avformat_find_stream_info(pFormatCtx, NULL ) < 0 )
      return -1;
   
    av_dump_format(pFormatCtx, -1, argv[1], 0);

avformat_open_input函数只是读文件头,并不会填充流信息,因此我们需要接下来调用avformat_find_stream_info获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。
最后调用一个帮助函数av_dump_format,输出文件的信息,也就是我们在使用ffmpeg时能看到的文件详细信息。第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。

现在 pFormatCtx->streams 中已经有所有流了,因此现在我们遍历它找到第一条视频流:

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;

codec_type 的宏定义已经由以前的 CODEC_TYPE_VIDEO 改为 AVMEDIA_TYPE_VIDEO 了。接下来我们通过这条 video stream 的编解码信息打开相应的解码器:

pCodecCtx = pFormatCtx->streams[videoStream]->codec;

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if( pCodec == NULL )

return-1;

if( avcodec_open2(pCodecCtx, pCodec, NULL)<0)

return-1;

分配图像缓存

接下来我们准备给即将解码的图片分配内存空间。

1 pFrame = avcodec_alloc_frame();
2   if( pFrame == NULL )
3     return -1;
4   
5   pFrameRGB = avcodec_alloc_frame();
6   if( pFrameRGB == NULL )
7     return -1;

调用 avcodec_alloc_frame 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据:

1 numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
2               pCodecCtx->height);

这里调用 avpicture_get_size,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间:

1 buffer = av_malloc(numBytes);
2   
3   avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
4           pCodecCtx->width, pCodecCtx->height);

接着上面的,首先是用 av_malloc 分配上面计算大小的内存空间,然后调用 avpicture_fill 将 pFrameRGB 跟 buffer 指向的内存关联起来。

获取图像

OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。

01 i = 0;
02 while( av_read_frame(pFormatCtx, &packet) >= 0 ) {
03   if( packet.stream_index == videoStream ) {
04     avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
05  
06     if( frameFinished ) {
07   struct SwsContext *img_convert_ctx = NULL;
08   img_convert_ctx =
09     sws_getCachedContext(img_convert_ctx, pCodecCtx->width,
10                  pCodecCtx->height, pCodecCtx->pix_fmt,
11                  pCodecCtx->width, pCodecCtx->height,
12                  PIX_FMT_RGB24, SWS_BICUBIC,
13                  NULL, NULL, NULL);
14   if( !img_convert_ctx ) {
15     fprintf(stderr, "Cannot initialize sws conversion context\n");
16     exit(1);
17   }
18   sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,
19         pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
20         pFrameRGB->linesize);
21   if( i++ < 50 )
22     SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
23     }
24   }
25   av_free_packet(&packet);
26 }

av_read_frame 从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用 avcodec_decode_video2 对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置 frameFinished,如果已经得到下一帧图像则设置 frameFinished 非零,否则为零。所以这里我们判断 frameFinished 是否为零来确定 pFrame 中是否已经得到解码的图像。注意在每次处理完后需要调用 av_free_packet 释放读取的packet。

解码得到图像后,很有可能不是我们想要的 RGB24 格式,因此需要使用 swscale 来做转换,调用 sws_getCachedContext 得到转换上下文,使用 sws_scale 将图形从解码后的格式转换为 RGB24,最后将前50帧写人 ppm 文件。最后释放图像以及关闭文件:

01 av_free(buffer);
02   av_free(pFrameRGB);
03   av_free(pFrame);
04   avcodec_close(pCodecCtx);
05   avformat_close_input(&pFormatCtx);
06   
07   return 0;
08 }
09   
10 static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)
11 {
12   FILE *pFile;
13   char szFilename[32];
14   int y;
15   
16   sprintf(szFilename, "frame%d.ppm", iFrame);
17   pFile = fopen(szFilename, "wb");
18   if( !pFile )
19     return;
20   fprintf(pFile, "P6\n%d %d\n255\n", width, height);
21   
22   for( y = 0; y < height; y++ )
23     fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
24   
25   fclose(pFile);
26 }

学习FFmpeg API – 解码视频的更多相关文章

  1. 【转】学习FFmpeg API – 解码视频

    ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...

  2. 学习FFmpeg API

    ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...

  3. 【学习ffmpeg】打开视频文件,帧分析,并bmp保存关键帧

    http://www.tuicool.com/articles/jiUzua   http://blog.csdn.net/code_future/article/details/8646717 主题 ...

  4. ffmpeg编解码视频导致噪声增大的一种解决方法

    一.前言 ffmpeg在视音频编解码领域算是一个比较成熟的解决方案了.公司的一款视频编辑软件正是基于ffmpeg做了二次封装,并在此基础上进行音视频的编解码处理.然而,在观察编码后的视频质量时,发现图 ...

  5. 关于ffmpeg(libav)解码视频最后丢帧的问题

    其实最初不是为了解决这个问题而来的,是Peter兄给我的提示解决另一个问题却让我误打误撞解决了另外一个问题之后也把这个隐藏了很久的bug找到(之前总是有一些特别短的视频产生不知所措还以为是视频素材本身 ...

  6. FFmpeg 学习(五):FFmpeg 编解码 API 分析

    在上一篇文章 FFmpeg学习(四):FFmpeg API 介绍与通用 API 分析 中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编 ...

  7. 新手学习FFmpeg - 调用API完成视频的读取和输出

    在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...

  8. FFmpeg再学习 -- FFmpeg解码知识

    继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 前面用了五个篇幅来讲 FFmpeg,其主要目的是为实现将图片转视频的功能. 总的来说,对于 FFmepg 多少有一些了解了.但 ...

  9. 如何用FFmpeg API采集摄像头视频和麦克风音频,并实现录制文件的功能

    之前一直用Directshow技术采集摄像头数据,但是觉得涉及的细节比较多,要开发者比较了解Directshow的框架知识,学习起来有一点点难度.最近发现很多人问怎么用FFmpeg采集摄像头图像,事实 ...

随机推荐

  1. [c++语法]类

    什么是类 类 是 面向对象的基础.c里面是没有对象的,只有数据,即静态的死物. 从面向过程升级到面向对象后,有了对象的概念,对象是数据与方法的合体,是动态的活物. 类代表着一类事物的特征.而对象,是类 ...

  2. 验证码 Captcha 之大插件

    验证码 Captcha 之大插件小用 不知何年何月才能完成OADemo啊,总之还是一步一步来吧,这段时间开始着手了,先做登陆.  前段时间研究了一下在CentOS7下安装Mysql和Memcached ...

  3. 8张图理解Java(转)

    一图胜千言,下面图解均来自Program Creek 网站的Java教程,目前它们拥有最多的票选.如果图解没有阐明问题,那么你可以借助它的标题来一窥究竟. 1.字符串不变性 下面这张图展示了这段代码做 ...

  4. 14.3.5.3 How to Minimize and Handle Deadlocks 如何减少和处理死锁

    14.3.5.3 How to Minimize and Handle Deadlocks 如何减少和处理死锁 这个章节建立关于死锁的概念信息,它解释如何组织数据库操作来减少死锁和随后的错误处理: D ...

  5. Tiny并行计算框架之复杂演示样例

    问题来源  很感谢@doctorwho的问题: 假如职业介绍所来了一批生产汽车的工作,如果生产一辆汽车任务是这种:搭好底盘.拧4个轮胎.安装发动机.安装4个座椅.再装4个车门.最后安装顶棚. 之间有的 ...

  6. 欧舒丹 L'Occitane 活力清泉保湿面霜 - 男士护肤 - 香港草莓网StrawberryNET.com

    欧舒丹 L'Occitane 活力清泉保湿面霜 - 男士护肤 - 香港草莓网StrawberryNET.com 欧舒丹 活力清泉保湿面霜 50ml/1.7oz

  7. 在word 中复选框划勾或叉的方法

    输入大写字母R.大写字母Q ,然后将字体改为Wingdings 2, 就分离得到带框的勾和叉.

  8. Java中字符串中子串的查找共有四种方法(indexof())

    Java中字符串中子串的查找共有四种方法(indexof()) Java中字符串中子串的查找共有四种方法,如下:1.int indexOf(String str) :返回第一次出现的指定子字符串在此字 ...

  9. 泥鳅般的const(一个小Demo彻底搞清楚)

    #include<stdio.h> int main(){     int a = 3;     int b = 5;          /* C标准库函数中最常见格式, 目的是保护參数, ...

  10. OpenStack镜像管理3

    第三部分 OpenStack镜像管理 一.简介 很多源都有为OpenStack已经编译好的各种镜像了,您可以直接下载并通过使用这些镜像来熟悉OpenStack. 不过如果是为生产环境进行部署的话,您一 ...