一、引言
在《音视频入门基础:FLV专题(7)——Tag header简介》中对Tag header进行了简介,本文讲述FFmpeg源码中是怎样解码FLV文件的Tag header,拿到里面的信息。
二、FFmpeg源码中,解码Tag header的实现
FFmpeg源码中使用flv_read_packet函数来读取每个Tag的信息。该函数的前半部分实现了解码Tag header的功能。该函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中:
static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{
FLVContext *flv = s->priv_data;
int ret, i, size, flags;
enum FlvTagType type;
int stream_type=-1;
int64_t next, pos, meta_pos;
int64_t dts, pts = AV_NOPTS_VALUE;
int av_uninit(channels);
int av_uninit(sample_rate);
AVStream *st = NULL;
int last = -1;
int orig_size;
int enhanced_flv = 0;
uint32_t video_codec_id = 0;
retry:
/* pkt size is repeated at end. skip it */
pos = avio_tell(s->pb);
type = (avio_r8(s->pb) & 0x1F);
orig_size =
size = avio_rb24(s->pb);
flv->sum_flv_tag_size += size + 11LL;
dts = avio_rb24(s->pb);
dts |= (unsigned)avio_r8(s->pb) << 24;
av_log(s, AV_LOG_TRACE, "type:%d, size:%d, last:%d, dts:%"PRId64" pos:%"PRId64"\n", type, size, last, dts, avio_tell(s->pb));
if (avio_feof(s->pb))
return AVERROR_EOF;
avio_skip(s->pb, 3); /* stream id, always 0 */
flags = 0;
//...
if (size == 0) {
ret = FFERROR_REDO;
goto leave;
}
next = size + avio_tell(s->pb);
if (type == FLV_TAG_TYPE_AUDIO) {
//...
} else if (type == FLV_TAG_TYPE_VIDEO) {
//...
}else if (type == FLV_TAG_TYPE_META) {
//...
}else{
//...
}
//...
return ret;
}
flv_read_packet函数中,首先获取Tag header的TagType属性,赋值给局部变量type。关于avio_r8函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》。可以看到Tag header中的Filter属性并没有被保存到FFmpeg的内存中,所以FFmpeg源码内部是不会判断FLV文件是否被加密的:
type = (avio_r8(s->pb) & 0x1F);
在FFmpeg源码中TagType属性对应的枚举成员有三个:FLV_TAG_TYPE_AUDIO、FLV_TAG_TYPE_VIDEO、FLV_TAG_TYPE_META。如果局部变量type的值为FLV_TAG_TYPE_AUDIO表示该Tag为音频Tag;如果值为FLV_TAG_TYPE_VIDEO表示是视频Tag;如果值为FLV_TAG_TYPE_META表示是脚本Tag:
enum FlvTagType {
FLV_TAG_TYPE_AUDIO = 0x08,
FLV_TAG_TYPE_VIDEO = 0x09,
FLV_TAG_TYPE_META = 0x12,
};
获取Tag header的DataSize属性,即该Tag以字节为单位的Tag data的长度,赋值给局部变量orig_size和size:
orig_size =
size = avio_rb24(s->pb);
从《音视频入门基础:FLV专题(7)——Tag header简介》可以知道,DataSize属性的值等于整个Tag的长度 - 11。所以让DataSize属性的值加上11就是整个Tag的长度。所以flv->sum_flv_tag_size为已被读取的各个Tag加起来的总长度,单位为字节:
flv->sum_flv_tag_size += size + 11LL;
获取Tag header的Timestamp和TimestampExtended属性,合成1个4字节的解码时间戳,赋值给局部变量dts:
dts = avio_rb24(s->pb);
dts |= (unsigned)avio_r8(s->pb) << 24;
如果已经读取到文件末尾,返回AVERROR_EOF。关于avio_feof函数用法可以参考:《FFmpeg源码:avio_feof函数分析》:
if (avio_feof(s->pb))
return AVERROR_EOF;
如果还没有读取到文件末尾,继续往下执行,跳过Tag header的StreamID属性。关于avio_skip函数用法可以参考:《FFmpeg源码:avio_skip函数分析》:
avio_skip(s->pb, 3); /* stream id, always 0 */
avio_tell(s->pb)是当前读取到的位置相对于文件首的偏移,关于avio_tell函数用法可以参考:《FFmpeg源码:avio_tell函数分析》。这时候已经读取完了该Tag的Tag header了,而从前面我们可以知道,局部变量size存贮该Tag的Tag data的长度。所以size + avio_tell(s->pb)为该Tag对应的PreviousTagSize相对于文件首的偏移(单位为字节):
next = size + avio_tell(s->pb);
根据该Tag为音频Tag、视频Tag还是脚本Tag,分别执行不同的解码操作:
if (type == FLV_TAG_TYPE_AUDIO) {
//...
} else if (type == FLV_TAG_TYPE_VIDEO) {
//...
}else if (type == FLV_TAG_TYPE_META) {
//...
}else{
//...
}
//...
三、总结
1.FFmpeg源码中通过flv_read_packet函数的前半部分来解码FLV文件每个Tag的Tag header,根据Tag header的TagType属性来判断该Tag的类型,然后分别执行不同的解码Tag的操作。
2.Tag header中的Filter属性并没有被保存到FFmpeg的内存中,FFmpeg源码内部是不会判断FLV文件是否被加密的。要想处理加密过的FLV文件,得改FFmpeg源码或者自己实现。