一. 播放器的流程
1. 输入 : 从文件或网络等读取原数据,对原数据进行解析,比如文件,首先要分析文件格式,从文件中取得音视频编码参数,视频时间长度等信息,然后要从其中取出音频编码数据和视频编码数据送到解码部分,这里暂称这种编码源数据块为 packet。
2. 解码 : 初始化时,利用输入端从源数据中取得的信息调用不同的解码库初始化;然后接收输入端传送来的音视频编码数据,分别进行音频解码和视频解码,视频解码出来的数据一般是 YUV 或 RGB 数据,这里暂称为 picture, 音频解码出来的数据是采样数据,是声卡可以播放的数据,这里暂称为 sample。 解码所得的数据接下来送到输出部分。
3. 输出 : 接收解码部分送来的 picture 和 sample 并显示。 视频显示一般使用某个图形库,如 SDL, Xlib, DirectDraw, OpengGL, FrameBuffer等, 音频输出是把 sample 写入系统的音频驱动,由音频驱动送入声卡播放, 可用的音频输出有 ALSA, OSS, SDL, DirectSound, WaveOut等。
二. FFMPEG解码流程:
1. 注册所有容器格式和CODEC:av_register_all()
2. 打开文件: avformat_open_input()
3. 从文件中提取流信息: avformat_find_stream_info()
4. 穷举所有的流av_find_best_stream(),查找其中种类 AVMEDIA_TYPE_VIDEO或 AVMEDIA_TYPE_AUDIO
5. 查找对应的解码器: avcodec_find_decoder()
6. 打开编解码器:avcodec_open2()
7. 为解码帧分配内存:avcodec_alloc_frame()
8. 不停地从码流中提取出帧数据:av_read_frame()
9. 判断帧的类型,对于视频帧调用:avcodec_decode_video2()对于音频帧调用avcodec_decode_video2
10. 解码完后,释放解码器: avcodec_close()
11. 关闭输入文件: avformat_close_input_file()
主要数据结构:
基本概念:
编解码器、数据帧、媒体流和容器是数字媒体处理系统的四个基本概念。
首先需要统一术语:
容器/文件(Conainer/File):即特定格式的多媒体文件。
媒体流(Stream):指时间轴上的一段连续数据,如一段声音数据,一段视频数据或一段字幕数据,可以是压缩的,也可以是非压缩的,压缩的数据需要关联特定的编解码器。
数据帧/数据包(Frame/Packet):通常,一个媒体流由大量的数据帧组成,对于压缩数据,帧对应着编解码器的最小处理单元。通常,分属于不同媒体流的数据帧交错复用于容器之中,参见交错。
编解码器:编解码器以帧为单位实现压缩数据和原始数据之间的相互转换。
在FFMPEG中,使用AVFormatContext、AVStream、AVCodecContext、AVCodec及AVPacket等结构来抽象这些基本要素。
AVCodecContext
AVCodecContext:
这是一个描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息,如下列出了部分比较重要的域:
typedef struct AVCodecContext {
/ **
*一些编解码器需要/可以像使用extradata Huffman表。
* MJPEG:Huffman表
* RV10其他标志
* MPEG4:全球头(也可以是在比特流或这里)
*分配的内存应该是FF_INPUT_BUFFER_PADDING_SIZE字节较大
*,比extradata_size避免比特流器,如果它与读prolems。
* extradata按字节的内容必须不依赖于架构或CPU的字节顺序。
* - 编码:设置/分配/释放由libavcodec的。
* - 解码:由用户设置/分配/释放。
* /
uint8_t *extradata;
int extradata_size;
/ **
*这是时间的基本单位,在条件(以秒为单位)
*帧时间戳派代表出席了会议。对于固定fps的内容,
*基应该1/framerate和时间戳的增量应该
*相同的1。
* - 编码:必须由用户设置。
* - 解码:libavcodec的设置。
* /
AVRational time_base;
/*视频* /
/ **
*
/ **
*图片宽度/高度。
* - 编码:必须由用户设置。
* - 解码:libavcodec的设置。
*请注意:兼容性,它是可能的,而不是设置此
* coded_width/高解码之前。
* /
int width, height;
......
/ *仅音频* /
int sample_rate; ///<
int sample_rate; ///< 每秒采样
int channels; ///< 音频通道数
/ **
*音频采样格式
* - 编码:由用户设置。
* - 解码:libavcodec的设置。
* /
enum SampleFormat sample_fmt; ///< 样本格式
/ *下面的数据不应该被初始化。* /
/ **
*
/ **
*每包样品,初始化时调用“init”。
* /
int frame_size;
int frame_number; ///<音频或视频帧数量
char codec_name[32];
enum AVMediaType codec_type; /* 看到AVMEDIA_TYPE_xxx */
enum CodecID codec_id; /* see CODEC_ID_xxx */
/ **
*
enum CodecID codec_id; /* see CODEC_ID_xxx */
/ **
*的fourcc(LSB在前,所以“的ABCD” - >(“D”<< 24)(“C”<< 16)(“B”<< 8)+“A”)。
*这是用来解决一些编码错误。
*分路器应设置什么是编解码器用于识别领域中。
*如果有分路器等多个领域,在一个容器,然后选择一个
*最大化使用的编解码器有关的信息。
*如果在容器中的编解码器标记字段然后32位大分路器应该
*重新映射到一个表或其他结构的32位编号。也可选择新
* extra_codec_tag+大小可以添加,但必须证明这是一个明显的优势
*第一。
* - 编码:由用户设置,如果没有则默认基础上codec_id将使用。
* - 解码:由用户设置,将被转换成在初始化libavcodec的大写。
* /
unsigned int codec_tag;
......
/ **
*在解码器的帧重排序缓冲区的大小。
*对于MPEG-2,这是IPB1或0低延时IP。
* - 编码:libavcodec的设置。
* - 解码:libavcodec的设置。
* /
int has_b_frames;
/ **
*每包的字节数,如果常量和已知或0
*
*用于一些WAV的音频编解码器。
* /
int block_align;
/ **
*从分路器位每个样品/像素(huffyuv需要)。
* - 编码:libavcodec的设置。
* - 解码:由用户设置。
* /
int bits_per_coded_sample;
.....
} AVCodecContext;
如果是单纯使用libavcodec,这部分信息需要调用者进行初始化;如果是使用整个FFMPEG库,这部分信息在调用avformat_open_input和avformat_find_stream_info的过程中根据文件的头信息及媒体流内的头部信息完成初始化。其中几个主要域的释义如下:
extradata/extradata_size:这个buffer中存放了解码器可能会用到的额外信息,在av_read_frame中填充。一般来说,首先,某种具体格式的demuxer在读取格式头信息的时候会填充extradata,其次,如果demuxer没有做这个事情,比如可能在头部压根儿就没有相关的编解码信息,则相应的parser会继续从已经解复用出来的媒体流中继续寻找。在没有找到任何额外信息的情况下,这个buffer指针为空。
time_base:
width/height:视频的宽和高。
sample_rate/channels:音频的采样率和信道数目。
sample_fmt:音频的原始采样格式。
codec_name/codec_type/codec_id/codec_tag:编解码器的信息。
AVStrea
该结构体描述一个媒体流,定义如