一、引言
在《音视频入门基础:FLV专题(3)------FLV header简介》中对FLV格式的FLV header进行了简介,本文讲述FFmpeg源码中是怎样解码FLV header,拿到里面的信息。执行命令ffmpeg -i XXX.flv时,FFmpeg源码内部会调用flv_probe函数检测该文件是否为FLV格式的文件(具体可以参考:《音视频入门基础:FLV专题(5)------FFmpeg源码中,判断某文件是否为FLV文件的实现》)。然后如果检测出该文件为FLV格式的文件,会调用flv_read_header函数解码FLV header。
二、flv_read_header函数的定义
flv_read_header函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中:
cpp
static int flv_read_header(AVFormatContext *s)
{
int flags;
FLVContext *flv = s->priv_data;
int offset;
int pre_tag_size = 0;
/* Actual FLV data at 0xe40000 in KUX file */
if(!strcmp(s->iformat->name, "kux"))
avio_skip(s->pb, 0xe40000);
avio_skip(s->pb, 4);
flags = avio_r8(s->pb);
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);
s->ctx_flags |= AVFMTCTX_NOHEADER;
offset = avio_rb32(s->pb);
avio_seek(s->pb, offset, SEEK_SET);
/* Annex E. The FLV File Format
* E.3 TheFLVFileBody
* Field Type Comment
* PreviousTagSize0 UI32 Always 0
* */
pre_tag_size = avio_rb32(s->pb);
if (pre_tag_size) {
av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
}
s->start_time = 0;
flv->sum_flv_tag_size = 0;
flv->last_keyframe_stream_index = -1;
return 0;
}
Fmpeg对媒体文件/流进行解复用时,会调用avformat_open_input函数,通过avformat_open_input函数内部的av_probe_input_format3函数来检测该文件是否为FLV格式的文件。如果是,avformat_open_input底层会调用flv_read_header函数来解码FLV header。
所以flv_read_header函数的作用就是解码FLV header。
形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型变量。执行flv_read_header函数后,s->priv_data会得到从FLV header中解码出来的信息。然后在flv_read_header函数外部可以通过语句FLVContext *flv = s->priv_data得到这些信息。FLVContext 结构体声明在libavformat/flvdec.c中:
cpp
typedef struct FLVContext {
const AVClass *class; ///< Class for private options.
int trust_metadata; ///< configure streams according onMetaData
int trust_datasize; ///< trust data size of FLVTag
int dump_full_metadata; ///< Dump full metadata of the onMetadata
int wrong_dts; ///< wrong dts due to negative cts
uint8_t *new_extradata[FLV_STREAM_TYPE_NB];
int new_extradata_size[FLV_STREAM_TYPE_NB];
int last_sample_rate;
int last_channels;
struct {
int64_t dts;
int64_t pos;
} validate_index[2];
int validate_next;
int validate_count;
int searched_for_end;
uint8_t resync_buffer[2*RESYNC_BUFFER_SIZE];
int broken_sizes;
int64_t sum_flv_tag_size;
int last_keyframe_stream_index;
int keyframe_count;
int64_t video_bit_rate;
int64_t audio_bit_rate;
int64_t *keyframe_times;
int64_t *keyframe_filepositions;
int missing_streams;
AVRational framerate;
int64_t last_ts;
int64_t time_offset;
int64_t time_pos;
FLVMetaVideoColor *metaVideoColor;
int meta_color_info_flag;
} FLVContext;
返回值:flv_read_header函数固定返回0。
三、flv_read_header函数的内部实现分析
flv_read_header函数中,首先判断该文件是否为KUX文件。kux格式是优酷的一种视频加密格式,为保护版权和经营策略,只能使用优酷播放器播放:
cpp
/* Actual FLV data at 0xe40000 in KUX file */
if(!strcmp(s->iformat->name, "kux"))
avio_skip(s->pb, 0xe40000);
AVIOContext文件位置指针(s->pb->buf_ptr)一开始指向FLV header的开头,通过avio_skip函数跳过4个字节,让AVIOContext文件位置指针指向FLV header的第5个字节。关于avio_skip函数用法可以参考:《FFmpeg源码:avio_skip函数分析》:
cpp
avio_skip(s->pb, 4);
读取FLV header的第5个字节,赋值给局部变量flags。从《音视频入门基础:FLV专题(3)------FLV header简介》可以知道,
如果FLV header第5个字节的值为0x04(二进制的0b00000100)表示该FLV文件中仅含有音频不含视频;第5个字节的值为0x01(二进制的0b00000001)表示该FLV文件中仅含有视频不含音频;第5个字节的值为0x05(二进制的0b00000101)表示该FLV文件中既含视频也含音频:
cpp
flags = avio_r8(s->pb);
枚举FLV_HEADER_FLAG_HASVIDEO和FLV_HEADER_FLAG_HASAUDIO声明在libavformat/flv.h中:
cpp
enum {
FLV_HEADER_FLAG_HASVIDEO = 1,
FLV_HEADER_FLAG_HASAUDIO = 4,
};
所以flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO)
= flags & (1 | 4)
= flags & 5
所以当flv->missing_streams值为0x04表示该FLV文件中仅含有音频不含视频;flv->missing_streams的值为0x01表示该FLV文件中仅含有视频不含音频;flv->missing_streams的值为0x05表示该FLV文件中既含视频也含音频:
cpp
flv->missing_streams = flags & (FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO);
读取FLV header的第6到第9个字节,即以字节为单位的整个FLV header的长度,赋值给局部变量offset。关于avio_rb32函数用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》:
cpp
offset = avio_rb32(s->pb);
把AVIOContext的文件位置指针移动到离文件开头offset字节处,即让文件位置指针指向FLV header紧接着的那个字节,也就是PreviousTagSize0。关于avio_seek函数用法可以参考:《FFmpeg源码:avio_seek函数分析》:
cpp
avio_seek(s->pb, offset, SEEK_SET);
读取FLV文件的PreviousTagSize0,赋值给局部变量pre_tag_size。从《音视频入门基础:FLV专题(3)------FLV header简介》可以知道,PreviousTagSize0,占4个字节,值必须为0。所以如果读取到的PreviousTagSize0值不为0,表示格式错误,打印日志:"Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0":
cpp
pre_tag_size = avio_rb32(s->pb);
if (pre_tag_size) {
av_log(s, AV_LOG_WARNING, "Read FLV header error, input file is not a standard flv format, first PreviousTagSize0 always is 0\n");
}
初始化s->start_time,flv->sum_flv_tag_size,flv->last_keyframe_stream_index:
cpp
s->start_time = 0;
flv->sum_flv_tag_size = 0;
flv->last_keyframe_stream_index = -1;