An ffmpeg and SDL Tutorial
在
ffmpeg-1.0.1
上的更新
Tutorial01
http://cutebunny.blog.51cto.com/blog/301216/1121847
本篇是整个系列的基础,详细介绍了
ffmpeg
的整个工作流程,以及重要的数据结构。
Sample code
实现了将视频中的前
5
帧图像提取出来另存为
PPM
文件。流程非常清晰,没有太多困难的地方,我们直接改代码。
更新
Line22, 23
#include
#include
改为
#include
#include
Ffmpeg-1.0.1
中,头文件已经分类到各个库单独的文件夹中,所以需指定其所在目录。
Line50
AVFormatContext *pFormatCtx;
改为
AVFormatContext *pFormatCtx = NULL;
后续与
avformat_open_input()
函数一起说明。
Line69
if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
return -1; // Couldn't open file
改为
if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
return -1; // Couldn't open file
ffmpeg-1.0.1
中,函数
av_open_input_file()
已经完全被
avformat_open_input()
取代。前两个参数没变,分别是
AVFormatContext
结构和通过命令行参数传入的媒体文件文件名。后两个参数暂未深究,可能传入
NULL ffmpeg
会自动处理。
这里重点说一下
AVFormatContext
结构,它是整个程序中贯穿始终的一个数据结构,可以理解为整个编解码环境的上下文,它包含了其他一些很重要的数据结构。注意
AVFormatContext
根据不同的应用场景其初始化过程会有差异的分步完成。当把它的地址作为参数传入
avformat_open_input()
时,如果之前
pFormatCtx
没有分配空间,必须将其赋为
NULL
,否则将造成段错误。这也是第
50
行所作修改的原因。
Line77
dump_format(pFormatCtx, 0, argv[1], 0);
改为
av_dump_format(pFormatCtx, 0, argv[1], 0);
ffmpeg-1.0.1
中,函数
dump_format()
变更为
av_dump_format()
,功能不变。
Line82
if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
改为
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
ffmpeg-1.0.1
中
codec_type
的类型变更。
Line127
avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
packet.data, packet.size);
改为
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished,
&packet);
ffmpeg-1.0.1
中,函数
avcodec_decode_video()
被
avcodec_decode_video2()
替换,原本
avcodec_decode_video()
中的第
4
个参数
packet.data
和第
5
个参数
packet.size
直接改成填入
packet
地址即可。
Line133
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,
(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,
pCodecCtx->height);
改为
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
ffmpeg-1.0.1
中,
img_convert()
被一种更准确更高效的方法——
sws_scale()
替代。其实
tutorial08
专门解释了这个改变,这里只是给出了一个简化写法。当然,别忘了
#include
。
好了,编译执行吧。以下是我的编译脚本,这么简单一玩意儿咱也犯不着去
make
了。你的目录结构可能和我不尽相同,请自己修改。源文件可直接从附件中得到。http://cutebunny.blog.51cto.com/blog/301216/1121847
#!/bin/sh
FFMPEG_ROOT="ffmpeg-1.0.1"
gcc $1 -g -o sample -I../$FFMPEG_ROOT \
-L../$FFMPEG_ROOT/libavformat -L../$FFMPEG_ROOT/libavcodec -L../$FFMPEG_ROOT/libavutil -L../$FFMPEG_ROOT/libswscale \
-lavformat -lavcodec -lswscale -lavutil -lz -lbz2 -lm –lpthread
解释
Q:
为什么同样是
AVFrame
的
pFrame
和
pFrameRGB
都经过了
avcodec_alloc_frame()
分配空间,
pFrame
直接拿来用,而
pFrameRGB
却要再用
avpicture_fill()
填入一个
buffer
?
A:
用
gdb
跟一下,观察
pFrame->data
和
pFrame->linesize
,发现在
avcodec_decode_video2()
过程中自动分配了空间,
之前
(gdb) print *pFrame
$5 = {data = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, linesize = {0, 0, 0, 0,
0, 0, 0, 0},
之后
(gdb) print *pFrame
$6 = {data = {0xa7c2bf0 '\020' ...,
0xa832830 '\200' ...,
0xa84f070 '\200' ..., 0x0, 0x0, 0x0, 0x0, 0x0},
linesize = {768, 384, 384, 0, 0, 0, 0, 0},
而
pFrameRGB
并没有
decode
的过程,它只是用来存储将原始的
pFrame
转换为
RGB
格式的帧,所以需要手动分配其存储空间。
疑问
Q:
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
if(frameFinished) {
……
}
上述代码中在
avcodec_decode_video2()
后会进行
framefinished
的判断。在
tutorial
的说明中,先提到
Technically a packet can contain partial frames or other bits of data, but ffmpeg's parser ensures that the packets we get contain either complete or
multiple frames.
可是随后又说,
However, we might not have all the information
we need for a frame after decoding a packet, so avcodec_decode_video() sets
frameFinished for us when we have the next frame.
是不是看上去有点矛盾?到底一个
Packet
能不能包含一个或多个完整的帧?我只是从一个
C Programmer
的角度去使用
ffmpeg
,并没有深入理解编解码的规范和
ffmpeg
的源码。所以这个疑问还望有知情人解答。