本文转载
视频播放过程
首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 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;
}
打开文件
接下来我们打开一个视频文件。
av_register_all 定义在 libavformat 里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。
1 |
if( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 ) |
使用新的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 ) |
| |
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(); |
5 |
pFrameRGB = avcodec_alloc_frame(); |
6 |
if( pFrameRGB == NULL ) |
调用 avcodec_alloc_frame 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据:
1 |
numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, |
这里调用 avpicture_get_size,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间:
1 |
buffer = av_malloc(numBytes); |
3 |
avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, |
4 |
pCodecCtx->width, pCodecCtx->height); |
接着上面的,首先是用 av_malloc 分配上面计算大小的内存空间,然后调用 avpicture_fill 将 pFrameRGB 跟 buffer 指向的内存关联起来。
获取图像
OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。
02 |
while( av_read_frame(pFormatCtx, &packet) >= 0 ) { |
03 |
if( packet.stream_index == videoStream ) { |
04 |
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); |
07 |
struct SwsContext *img_convert_ctx = NULL; |
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, |
14 |
if( !img_convert_ctx ) { |
15 |
fprintf(stderr, "Cannot initialize sws conversion context\n"); |
18 |
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, |
19 |
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, |
22 |
SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); |
25 |
av_free_packet(&packet); |
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 文件。最后释放图像以及关闭文件:
04 |
avcodec_close(pCodecCtx); |
05 |
avformat_close_input(&pFormatCtx); |
10 |
static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) |
16 |
sprintf(szFilename, "frame%d.ppm", iFrame); |
17 |
pFile = fopen(szFilename, "wb"); |
20 |
fprintf(pFile, "P6\n%d %d\n255\n", width, height); |
22 |
for( y = 0; y < height; y++ ) |
23 |
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile); |
- 【转】学习FFmpeg API – 解码视频
ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...
- 学习FFmpeg API
ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...
- 【学习ffmpeg】打开视频文件,帧分析,并bmp保存关键帧
http://www.tuicool.com/articles/jiUzua http://blog.csdn.net/code_future/article/details/8646717 主题 ...
- ffmpeg编解码视频导致噪声增大的一种解决方法
一.前言 ffmpeg在视音频编解码领域算是一个比较成熟的解决方案了.公司的一款视频编辑软件正是基于ffmpeg做了二次封装,并在此基础上进行音视频的编解码处理.然而,在观察编码后的视频质量时,发现图 ...
- 关于ffmpeg(libav)解码视频最后丢帧的问题
其实最初不是为了解决这个问题而来的,是Peter兄给我的提示解决另一个问题却让我误打误撞解决了另外一个问题之后也把这个隐藏了很久的bug找到(之前总是有一些特别短的视频产生不知所措还以为是视频素材本身 ...
- FFmpeg 学习(五):FFmpeg 编解码 API 分析
在上一篇文章 FFmpeg学习(四):FFmpeg API 介绍与通用 API 分析 中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编 ...
- 新手学习FFmpeg - 调用API完成视频的读取和输出
在写了几个avfilter之后,原本以为对ffmpeg应该算是入门了. 结果今天想对一个视频文件进行转码操作,才发现基本的视频读取,输出都搞不定. 痛定思痛,仔细研究了一下ffmpeg提供的examp ...
- FFmpeg再学习 -- FFmpeg解码知识
继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 前面用了五个篇幅来讲 FFmpeg,其主要目的是为实现将图片转视频的功能. 总的来说,对于 FFmepg 多少有一些了解了.但 ...
- 如何用FFmpeg API采集摄像头视频和麦克风音频,并实现录制文件的功能
之前一直用Directshow技术采集摄像头数据,但是觉得涉及的细节比较多,要开发者比较了解Directshow的框架知识,学习起来有一点点难度.最近发现很多人问怎么用FFmpeg采集摄像头图像,事实 ...
随机推荐
- Cocos2dx 3.0 过渡篇(三十)灰机还是3D好(Sprite3D)
如今都非常少发3.0过渡篇这一系列的博文了,原因是多方面的,一个是游戏开发进度,尽管上面并没给我什么压力,但我自己一直在赶.还有一方面是个人私生活这块.五月份确实是多事之秋,有时候真的没办法全心思去研 ...
- NET平台机器学习组件-Infer.NET
NET平台机器学习组件-Infer.NET(三) Learner API—数据映射与序列化 阅读目录 关于本文档的说明 1.基本介绍 2.标准数据格式的映射 3.本地数据格式映射 4.评估数据格式映射 ...
- Oracle12C 怎样导入scott用户
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaG9uZ2thbmd3bA==/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- ruby中的模块
什么是模块 模块(module)是Ruby特有的功能之一.类用来表现具有数据与行为(程序)的"东西", 而模块大致来说,则是只有程序部分的集合体.类与模块最大的不同在于: 1.模块 ...
- [置顶] 如何在Python IDLE中调试Python代码?
好久没有用Python了,居然忘记了怎么在Python IDLE中调试Python代码.百度了一下,然后还是写下来吧,以免以后又忘记了. 1. Set break point in the sourc ...
- zoj 2972 - Hurdles of 110m
题目:110米栏,运动员能够用三种状态跑,1状态耗体力且跑得快,2状态不消耗体力,3状态恢复体力且跑得慢. 体力上限是M,且初始满体力,如今想知到最小的时间跑全然程. 分析:dp,全然背包.题目是一个 ...
- UVA 639 (13.08.25)
Don't Get Rooked In chess, the rook is a piece that can move any number of squaresvertically or ho ...
- OpenCV-Python教程(9、使用霍夫变换检测直线)
相比C++而言,Python适合做原型.本系列的文章介绍如何在Python中用OpenCV图形库,以及与C++调用相应OpenCV函数的不同之处.这篇文章介绍在Python中使用OpenCV的霍夫变换 ...
- PropertiesDemo
import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configurat ...
- ASP.NET 联想控件(Autocomplete)测试可用 ascx
效果图 前台 <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Auto ...