以前FFmpeg频频出场,都是它的应用,但FFmpeg自己的结构或流程却尚未介绍过。就“能用便可”的角度,能把FFmpeg这个黑盒子用好,就已是很好的成绩了。数组
但追求理解甚至想修改FFmpeg的你,应该会关心FFmpeg自己的结构与处理流程。ide
因而,小程准备用若干篇文章来介绍FFmpeg的结构与流程。在介绍过程当中,小程尽可能引用具体的数值,让你对结构有个直观的感知。为了拿到具体的数据,须要调试FFmpeg的代码,这部分的内容(包括gdb的使用)小程已经在前面的章节介绍过了。函数
本文介绍FFmpeg的帧的结构。编码
这里的帧并非咱们说的图像帧,它只是一个数据载体或一个结构体而已(能够是图像或音频数据)。指针
FFmpeg的“帧”涉及到两个结构,即AVPacket,以及AVFrame。调试
AVPacket,是压缩数据的结构体,也就是解码前或编码后的数据的载体。code
为了查看AVPacket结构体的变量的值,小程写了一段调用FFmpeg的代码:orm
#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" void show_frame(const char* filepath) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); AVFormatContext* formatContext = avformat_alloc_context(); int status = 0; int success = 0; int videostreamidx = -1; AVCodecContext* codecContext = NULL; status = avformat_open_input(&formatContext, filepath, NULL, NULL); if (status == 0) { status = avformat_find_stream_info(formatContext, NULL); if (status >= 0) { for (int i = 0; i < formatContext->nb_streams; i ++) { if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videostreamidx = i; break; } } if (videostreamidx > -1) { AVStream* avstream = formatContext->streams[videostreamidx]; codecContext = avstream->codec; AVCodec* codec = avcodec_find_decoder(codecContext->codec_id); if (codec) { status = avcodec_open2(codecContext, codec, NULL); if (status == 0) { success = 1; } } } } else { av_log(NULL, AV_LOG_DEBUG, "avformat_find_stream_info error\n"); } if (success) { av_dump_format(formatContext, 0, filepath, 0); int gotframe = 0; AVFrame* frame = av_frame_alloc(); int decodelen = 0; int limitcount = 10; int pcindex = 0; while (pcindex < limitcount) { AVPacket packet; av_init_packet( &packet ); status = av_read_frame(formatContext, &packet); if (status < 0) { if (status == AVERROR_EOF) { av_log(NULL, AV_LOG_DEBUG, "read end for file\n"); } else { av_log(NULL, AV_LOG_DEBUG, "av_read_frame error\n"); } av_packet_unref(&packet); break; } else { if (packet.stream_index == videostreamidx) { decodelen = avcodec_decode_video2(codecContext, frame, &gotframe, &packet); if (decodelen > 0 && gotframe) { av_log(NULL, AV_LOG_DEBUG, "got one avframe, pcindex=%d\n", pcindex); } } } av_packet_unref(&packet); pcindex ++; } av_frame_free(&frame); } avformat_close_input(&formatContext); } avformat_free_context(formatContext); } int main(int argc, char *argv[]) { show_frame("moments.mp4"); return 0; }
从上面的代码能够看到,调用av_read_frame能够取得一个AVPacket,因此在调用这个函数的地方下个断点,看一下AVPacet长什么样子。视频
先说一下怎么编译上面这段代码,我使用的是mac电脑。blog
把上面的代码保存成show_frame.c文件,而后写一个makefile编译脚本(保存成makefile文件,与show_frame.c在同一目录),内容以下:
exe=showframe srcs=show_frame.c $(exe):$(srcs) gcc -o $(exe) $(srcs) -Iffmpeg/include/ -Lffmpeg -lffmpeg -liconv -lz -g clean: rm -f $(exe) *.o
整个项目的代码目录结构是这样的:
注意,上图的show_avcodec.c要换成show_frame.c,小程沿用了另外一个例子的截图,而且没有更改:)。
另外,FFmpeg是事先就编译了的,对于FFmpeg的编译,前文已说。
准备好环境后,就能够编译这段代码了:
make
编译代码后,使用gdb启动调试:
gdb showframe b 38 r
单步调试时,能够看到,在没有调用av_read_frame前,AVPacket中的变量值:
调用av_read_frame后,AVPacket中的变量:
再一次av_read_frame后:
av_read_frame后,AVPacket也可能没有数据:
AVPacket是压缩数据,一个AVPacket,最多包含一帧视频数据,但能够包括多帧音频数据。AVPacket中的变量含义:
pts/dts,显示/解码时间戵,以packet所在的流的time_base为单位。 stream_index,所在流的索引。 data,avpacket拥有的数据。 size,avpacket的数据长度。 duration,avpacket的时长,一样以time_base为单位。
AVPacket结构,在libavcodec/avcodec.h中定义,你能够详细看下这个头文件的说明。
AVFrame,是原始数据的结构体,也就是解码后或编码前的数据的载体。
能够简单理解为,AVFrame就是原始的音频或视频数据。有了它,能够作一些处理,好比音效或图效处理、特征提取、特征图绘制,等等。
为了看AVFrame的数据,使用调试AVPacket的代码便可,部分代码如截图:
而后在avcodec_decode_video2的调用处下个断点,使用gdb进行单步调试。
能够看到,在解码前,avframe是这样的:
解码后,而且保证有解码到一帧数据时,avframe是这样的:
如下对AVFrame的一些变量做一些解释:
data,指针数组(最多8个指针),每一个指针指向不一样维度的byte数据。 对于视频来讲,若是是planar的,则data[0]可能指向Y维度的数据,data[1]可能指向U维度的数据。 对于音频来讲,若是声道是平面组织的(planar),则data[0]指向一个声道,data[1]指向另外一个声道...;若是声音是打包形式的(packed,即左右不分开),则只有data[0]。 linesize,长度的数组。 对于视频,若是是planar数据,则linesize[i]是某个维度的一行的长度;若是是packet数据,则只有linesize[0],并且表示全部数据的长度。 对于音频,只有linesize[0]可用;若是是planar数据,则linesize[0]对应data[i]的长度(每一个data[i]是同样的长度);若是是packed数据,则linesize[0]表示data[0]的长度。对于音频,没有“一行”的概念,linesize[0]表示的是整个长度。 对于视频,注意linesize[i]表示一行的长度时,可能比实际的数据的长度(宽)要大。 extern_data,对于视频,等同于data。对于音频,常常用于packed数据。 width/height,视频宽高。 nb_samples,一个声道的样本数。 format,视频的颜色空间,或音频的样本格式。 key_frame,是否为关键帧。 pict_type,视频帧的类型(ipb帧等)。 sample_aspect_ratio,宽高比例。 pts,表现时间戵。 pkt_pts,对应的AVPacket的pts。 quality,质量系数。 sample_rate,音频采样率。 channels,声道数。
AVFrame结构,在libavutil/frame.h中定义,你能够详细阅读里面的说明。
至此,FFmpeg的帧结构就介绍完毕了。
总结一下,本文介绍了FFmpeg的帧的结构(其实是数据载体),包括AVFrame与AVPacket,而且经过调试查看告终构中变量的值的变化。