一直想写点分析FFmpeg的文章,一来让自己回头总结下知识,二是分享分享自己的经验。音视频在我才工作的时候是个很冷门的技术,那个时候流行java开发,智能手机的解码能力还挺堪忧更别说编码了。好在音视频有一个最棒的开源项目FFmpeg。
FFmpeg项目基本是目前所有音视频项目的基石,但是FFmpeg有一个问题就是太庞大。一开始接触音视频的同学可能比较蒙圈,一上来就看这么大的项目都不知道从何入手。不过没关系相信看完我的FFmpeg解析系列应该对这个库有一定的认识。
基础数据
音视频的处理基本围绕着两种数据
原始的数据
原始数据可以看作是图像或者音频原始采集数据,这些是未经过编码的。如我们平时开发时麦克风采集的PCM数据,亦或者是摄像头采集的YUV数据。
编码的数据
编码数据顾名思义就是编码过的数据,原始数据通过一定的运算就变成了编码的数据,我们常说的AAC/MP3或者H264都是编码数据
在FFmpeg里也是围绕着这两个基础数据来进行操作,FFmpeg使用AVPacket来承载编码的数据,用AVFrame来承载原始数据.所以这里就很有必要先说说这两个基础数据在FFmpeg里的实现。
AVPacket
AVPacket 定义在avcodec.h里面我们先看看他的定义
typedef struct AVPacket{
AVBufferRef *buf;
int64_t pts;
int64_t dts;
uint8_t *data;
int size;
int stream_index;
int flags;
AVPacketSideData *side_data;
int side_data_elems;
int64_t duration;
int64_t pos;
#if FF_API_CONVERGENCE_DURATION
attribute_deprecated
int64_t convergence_duration;
#endif
}AVPacket;
buf
buf可以看作是存储实际数据的对象,它保存了编码的数据。当不使用引用计数的时候为NULL
pts
pts 是一个画面或者音频的呈现时间,什么叫呈现时间呢?实际上就是看到和听到的时间。
这个概念很重要因为我们后面有一个DTS的概念一定不要混淆他们了。
dts
dts 是解码时间,这个时间的意思是向解码器传输数据的时间。在视频编码时 有一些编码有I P B帧的概念,I帧单独解码 P帧依靠I帧解码,B帧解码会依赖他的前后帧。就像下图
|I|B|P|I|B|P|I|B|P|I|B|P|
|1|2|3|4|5|6| | | | | | |
在向解码器给数据的时候第一帧第一个进解码器,如果第二帧B帧进解码器那么解码器就不能解码出画面,因为B帧需要后面的数据才能解码,所以应该时第三帧先进解码器。那么正确的数据是132465...这就是DTS的作用。那么也就是说如果没有B帧的话PTS应该等于DTS
data
当有buf时,data时buf->data的一个地址,如果没有buf data就保存了packet的实际数据
size
size表示data数据的大小
stream_index
stream_index 存储这个packet 在整个音视频文件里的index,FFmpeg在解析流时把不同的音视频轨道按index来区分,比如一个视频文件可能音频的index为0 视频的index为1,这样你就可以通过stream_index 来知道这是一个音频数据还是视频数据。
flags
flags 用于存储AV_PKT_FLAG flag有:
#define AV_PKT_FLAG_KEY 0x0001 ///< The packet contains a keyframe
#define AV_PKT_FLAG_CORRUPT 0x0002 ///< The packet content is corrupted
/**
* Flag is used to discard packets which are required to maintain valid
* decoder state but are not required for output and should be dropped
* after decoding.
**/
#define AV_PKT_FLAG_DISCARD 0x0004
/**
* The packet comes from a trusted source.
*
* Otherwise-unsafe constructs such as arbitrary pointers to data
* outside the packet may be followed.
*/
#define AV_PKT_FLAG_TRUSTED 0x0008
/**
* Flag is used to indicate packets that contain frames that can
* be discarded by the decoder. I.e. Non-reference frames.
*/
#define AV_PKT_FLAG_DISPOSABLE 0x0010
一般我们可以从flags里得到是否是关键帧信息如:
int has_key_frame = pkt->flags&AV_PKT_FLAG_KEY
side_data
side_data FFmpeg里给的信息是容器提供的额外数据,目前我还没用到过
side_data_elems
side_data_elems给出了side_data的大小
duration
duration 表示这个包的时长,比如一个30FPS的视频一帧的duration = 1000/30 ms ,这个数据一般在解码后播放时会使用。
pos
pos 表示这个packet在文件或者流的具体位置
AVPacket的用处
AVPacket 在FFmpeg里面用于存储编码后的数据,我们通常会在一个文件读取时最先接触到如
AVPacket pkt;
AVFormatContext *ctx ;
int ret = av_read_frame(ctx,&pkt);
这里我们使用AVPacket来保存流里面的数据,一般我们打开一个网络或者本地流时都会使用上述操作来读取一帧数据。读取完后就会向解码码器发送如
AVCodecContext *ctx;
ret = avcodec_send_packet(ctx,&pkt);
最后我们不需要这个数据包了
av_packet_unref(&pkt);
这就是AVPacket在解码时最常见的用法。
当然这只是一个简单的介绍,AVPacket几乎所有FFmpeg的编解码器MUX都会用到。说它是FFmpeg的基本单位都不为过。
AVFrame
与AVPacket相对应的是AVFrame,AVFrame用来保存原始数据,他也是FFmpeg里最常用的数据,我们将在下一篇文章里对AVFrame深入理解