An ffmpeg and SDL Tutorial在ffmpeg-1.0.1上的更新,tutorial01_第1张图片

 
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 的源码。所以这个疑问还望有知情人解答。