转自:http://my.oschina.net/u/555701/blog/56616

ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料。可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快,有些API已经完全换掉了,导致dranger教程中的代码已经无法编译,正好最近需要使用ffmpeg,于是就利用dranger的教程和代码,自己边学边记录,于是也就有了这个所谓的 New FFmpeg Tutorial(教程),希望对学习ffmpeg的人有所帮助。

 

Tutorial1: Decoding video frames

source code:videoframe.c

视频播放过程

首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器(Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。

容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包(packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

 

视频播放的流程

总结如下:

         1)、 根据视频文件格式如xxx.mpeg视频文件,解析(demux)出其中的视频流和音频流等;

         2)、把解析到的数据读入包(packet)中,分别进行存储;

         3)、分别对视频帧和音频帧调用相应的解码器(decoder)进行解码;

         4)、解码后得到的就是原始图像(YUV or RGB)和声音(PCM)数据;

         5)、然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡;

         6)、这样就得到了我们所看到的视频。

 

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

利用Ffmpeg 从视频文件(如xxx.mpeg文件)中解码出图片:

第一步:

声明变量

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

int main(int argc, const char *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 中已经有所有流了,因此现在我们遍历它找到第一条视频流:

1

videoStream = -1;

2

for( i = 0; i < pFormatCtx->nb_streams; i++ )

3

if( pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {

4

videoStream = i;

5

break;

6

}

7

8

if( videoStream == -1 )

9

return -1;

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

1

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

2

3

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

4

if( pCodec == NULL )

5

return -1;

6

7

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

8

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解析完成

学习FFmpeg API –解码视频相关推荐

  1. 学习FFmpeg API -解码视频

    版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] Tutorial 1 Decoding video frames 视频播放过程 声明变量 打开文件 分配图像缓存 获取图像 ff ...

  2. 学习FFmpeg API – 解码视频流程总结

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

  3. ffmpeg api解码音频,得到pcm数据

    ffmpeg api解码音频,得到pcm数据,程序如下: extern "C" { #include "libavutil/avutil.h" #include ...

  4. php将视频流逐帧转图片,ffmpeg sdk解码视频帧后保存成BMP或JPG的方法

    ffmpeg解码视频的例子可以看官方自带的encode_decode.c. 官方解码保存成ppm,这里接下来保存成BMP或JPG. 原理: 保存BMP是解码成功后,从YUV420转成RGB24,然后构 ...

  5. ffmpeg硬解码视频文件播放器

    https://blog.csdn.net/lg15273112290/article/details/105288741

  6. ffmpeg 解码视频小例子

    原:http://blog.csdn.net/flyfight88/article/details/8541068 ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益 ...

  7. FFmpeg -- 解码视频

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

  8. Android音视频开发基础(六):学习MediaCodec API,完成视频H.264的解码

    前言 在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了<Android 音视频从入门到提高 - 任务列表>.本文是Android音视 ...

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

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

最新文章

  1. aws java mysql_Lambda本地连接到Aurora MySql - 部署到AWS时超时
  2. UNIX再学习 -- 函数 system
  3. 使用 unsafe_使用Unsafe真的是关于速度或功能吗?
  4. Java虚拟机栈详解
  5. xhell 镜像_2020官网下载Xshell 6.0.189.0中文版
  6. mongoose数据查询or、and、where等用法
  7. CSDN学院全面改版啦!这次真的“搞大”了!
  8. LeetCode131:Palindrome Partitioning
  9. idea关于mybatis去除黄色背景色与绿色背景
  10. MacOS怎样启用悬停文本功能的具体操作方法!
  11. JavaScript闭包 懂不懂由你反正我是懂了
  12. vod系统必须要用服务器吗,架设美萍VOD点播系统服务器
  13. 那些被苏宁奖励的人、重用的人
  14. mac以及windows日语键盘快捷键
  15. JavaScript之E-mail 地址格式验证
  16. 双动道岔计算机控制系统,车站信号自动控制习题.doc
  17. 【多目标跟踪论文阅读笔记——2021年CVPR论文粗读记录】
  18. FCN全卷积网络和Deconv转置卷积原理描述
  19. 全球最大 IPO,我们能否赚笔养老钱?
  20. c语言程序设计火车站售票系统,C语言程序--火车站售票系统程序

热门文章

  1. HTML5实现立方体及透视效果
  2. PMD相位提取及相位展开简述
  3. 文件重命名后缀名没法改,教你轻松解决方法
  4. 视频合并怎么制作?建议收藏这三款软件
  5. 【优化】WIN10 打开文件卡半秒 解决方案
  6. Python 爬虫基础
  7. 月下独酌(作者:李白li bai)
  8. html5 打气球小游戏,在javascript+css3中如何实现打气球小游戏
  9. Python(自学之旅二)
  10. WildFly11 相关配置