准备整体分析下ffmpeg整体流程,先从解码开始往下走下,希望能帮助到一些朋友,有不当之处希望各位不吝指教,一起努力~~
整个解码流程基本从avformat_open_input
开始,先总结下大概的流程功能,带着目的去读代码会更加清晰点,整体篇幅偏长,需要耐心点看看。
- 根据传入的文件路径
filename
,通过一些列的判断,来探测出文件格式,得到最终的文件格式上下对象AVInputFormat
。 - 读取文件头信息,在信息足够的情况下,提前对
AVstream
,创建音视频的编解码信息。 - 填充
AVFormatContext
其他参数。
一句话总结就是,尽可能的收集各方面的信息并填充AVFormatContext
结构体,基本上是做了除过解码之外的所有工作。
avformat_open_input
定义在libavformat/avformat.h
中
/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
ps
:函数调用成功之后处理过的AVFormatContext
结构体,参数可为空,为空时,内部将创建AVFormatContext
url
:资源地址fmt
:强制指定AVFormatContext
中AVInputFormat
的。这个参数一般情况下可以设置为NULL,这样FFmpeg
可以自动检测AVInputFormat
- options:附加的一些选项
看一下定义实现,路径在libavformat\utils.c
中
看一下定义实现,路径在libavformat\utils.c
中
int avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
AVFormatContext *s = *ps;
int i, ret = 0;
AVDictionary *tmp = NULL;
ID3v2ExtraMeta *id3v2_extra_meta = NULL;
//如果ps为空,并且avformat_alloc_context也是失败,直接返回错误
if (!s && !(s = avformat_alloc_context()))
return AVERROR(ENOMEM);
if (!s->av_class) {
av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
return AVERROR(EINVAL);
}
//如果外面传进来了fmt,直接赋值到AVFormatContext中,下面基本都是一些赋值操作
if (fmt)
s->iformat = fmt;
if (options)
av_dict_copy(&tmp, *options, 0);
//如果上层自定义AVIOContext,更新标记flag为AVFMT_FLAG_CUSTOM_IO
if (s->pb) // must be before any goto fail
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if ((ret = av_opt_set_dict(s, &tmp)) < 0)
goto fail;
if (!(s->url = av_strdup(filename ? filename : ""))) {
ret = AVERROR(ENOMEM);
goto fail;
}
#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
//这里是初始化init_input,这个函数后面详细分析,主要找到一个合适的format,这里的合适是用分数来计算的
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
//这里是得到的分数
s->probe_score = ret;
//赋值操作
if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
if (!s->protocol_whitelist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
if (!s->protocol_blacklist) {
ret = AVERROR(ENOMEM);
goto fail;
}
}
//检查是否在白名单之中
if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
av_log(s, AV_LOG_ERROR, "Format not on whitelist '%s'\n", s->format_whitelist);
ret = AVERROR(EINVAL);
goto fail;
}
avio_skip(s->pb, s->skip_initial_bytes);
/* Check filename in case an image number is expected. */
if (s->iformat->flags & AVFMT_NEEDNUMBER) {
if (!av_filename_number_test(filename)) {
ret = AVERROR(EINVAL);
goto fail;
}
}
s->duration = s->start_time = AV_NOPTS_VALUE;
/* Allocate private data. */
if (s->iformat->priv_data_size > 0) {
if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (s->iformat->priv_class) {
*(const AVClass **) s->priv_data = s->iformat->priv_class;
av_opt_set_defaults(s->priv_data);
if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
goto fail;
}
}
/* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
//ID3是一种metadata容器,多应用于MP3格式的音频文件中。它可以将相关的曲名、演唱者、专辑、音轨数等信息存储在MP3文件中,又称作“ID3Tags”
// 如果AVIOContext存在,读取ID3信息
if (s->pb)
ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
#if FF_API_DEMUXER_OPEN
// 读取文件头,可能获取到metadata信息,流信息
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#else
if (s->iformat->read_header)
#endif
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
if (!s->metadata) {
s->metadata = s->internal->id3v2_meta;
s->internal->id3v2_meta = NULL;
} else if (s->internal->id3v2_meta) {
av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
av_dict_free(&s->internal->id3v2_meta);
}
if (id3v2_extra_meta) {
if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
!strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)
goto close;
if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)
goto close;
if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)
goto close;
} else
av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
}
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
if ((ret = avformat_queue_attached_pictures(s)) < 0)
goto close;
#if FF_API_DEMUXER_OPEN
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#else
if (s->pb && !s->internal->data_offset)
#endif
s->internal->data_offset = avio_tell(s->pb);
s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
update_stream_avctx(s);
for (i = 0; i < s->nb_streams; i++)
s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;
if (options) {
av_dict_free(options);
*options = tmp;
}
*ps = s;
return 0;
close:
if (s->iformat->read_close)
s->iformat->read_close(s);
fail:
ff_id3v2_free_extra_meta(&id3v2_extra_meta);
av_dict_free(&tmp);
if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
avio_closep(&s->pb);
avformat_free_context(s);
*ps = NULL;
return ret;
}
函数比较长,但是大体都是一些赋值操作,比较重要为两个函数init_input
,read_header
。
init_input
绝大数的初始化工作都是在这个函数里做的。
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
//#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4),AVPROBE_SCORE_MAX =100
//这里定义了初始的分数score 25分
int score = AVPROBE_SCORE_RETRY;
//#1
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
//#2
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;
//#3
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
//#4
if (s->iformat)
return 0;
//#5
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
- #1,如果上层自己定义了
AVIOContext
,更新标记为AVFMT_FLAG_CUSTOM_IO
,并且如果没有传入AVInputFormat
,则使用av_probe_input_buffer2
进行探测,选择合适的AVInputFormat
,如果传入了AVInputFormat
,则直接返回就好 - #2,如果指定了输入文件格式,该文件格式的标志
AVFMT_NOFILE
被设置,表示不需要IO层接口了,则直接返回,若没有指定文件格式,那么通过av_probe_input_format2
方法来推测文件格式,这里提醒下a||b,当a为false的时候,才会执行b,a&&b,当a为true的时候,才会执行b - #3,能走到这一步,只有2剩下情况,1.用户指定了
AVInputFormat
,但是AVFMT_NOFILE
为false,也就是后续的操作是需要读取文件的 2.av_probe_input_format2
通过文件的扩展名也并不能得出AVInputFormat
,那么最终就需要尝试打开文件。 - #4 ,如果已经获取到
AVInputFormat
,直接返回 - #5,通过
av_probe_input_buffer2
,打开文件获取到最终的AVInputFormat
补充一下探测参数AVProbeData
的定义
/**
* This structure contains the data a format has to probe a file.
*/
typedef struct AVProbeData {
const char *filename; //url地址
unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */ //探测数据承载的buffer
int buf_size; /**< Size of buf except extra allocated bytes */
const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
av_probe_input_buffer2
定义在libavformat/avformat.h
/**
* Probe a bytestream to determine the input format. Each time a probe returns
* with a score that is too low, the probe buffer size is increased and another
* attempt is made. When the maximum probe size is reached, the input format
* with the highest score is returned.
*
* @param pb the bytestream to probe
* @param fmt the input format is put here
* @param url the url of the stream
* @param logctx the log context
* @param offset the offset within the bytestream to probe from
* @param max_probe_size the maximum probe buffer size (zero for default)
* @return the score in case of success, a negative value corresponding to an
* the maximal score is AVPROBE_SCORE_MAX
* AVERROR code otherwise
*/
int av_probe_input_buffer2(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
const char *url, void *logctx,
unsigned int offset, unsigned int max_probe_size);
pb
:用于读取数据的AVIOContext
fmt
:输出推测出来的AVInputFormat
url
:资源路径logctx
:日志offset
:推测buffer
的偏移量max_probe_size
:推测buffer的最大值 看下实现定义在libavformat/format.c
中
int av_probe_input_buffer2(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
const char *filename, void *logctx,
unsigned int offset, unsigned int max_probe_size)
{
AVProbeData pd = { filename ? filename : "" };
uint8_t *buf = NULL;
int ret = 0, probe_size, buf_offset = 0;
int score = 0;
int ret2;
//初始化buffer最大容量,默认PROBE_BUF_MAX 1<<20=1048576,约为1M左右
if (!max_probe_size)
max_probe_size = PROBE_BUF_MAX;
else if (max_probe_size < PROBE_BUF_MIN) {
av_log(logctx, AV_LOG_ERROR,
"Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);
return AVERROR(EINVAL);
}
//如果偏移量大于最大容量,报错
if (offset >= max_probe_size)
return AVERROR(EINVAL);
// 找寻音视频资源的mime_type
if (pb->av_class) {
uint8_t *mime_type_opt = NULL;
char *semi;
av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
pd.mime_type = (const char *)mime_type_opt;
semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
if (semi) {
*semi = '\0';
}
}
//通过循环不断的调用avio_read()读取数据并且使用av_probe_input_format2(),推测格式
for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
probe_size = FFMIN(probe_size << 1,
FFMAX(max_probe_size, probe_size + 1))) {
score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;
/* Read probe data. */
if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
goto fail;
if ((ret = avio_read(pb, buf + buf_offset,
probe_size - buf_offset)) < 0) {
/* Fail if error was not end of file, otherwise, lower score. */
if (ret != AVERROR_EOF)
goto fail;
score = 0;
ret = 0; /* error was end of file, nothing read */
}
buf_offset += ret;
if (buf_offset < offset)
continue;
pd.buf_size = buf_offset - offset;
pd.buf = &buf[offset];
memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);
/* Guess file format. */
*fmt = av_probe_input_format2(&pd, 1, &score);
if (*fmt) {
/* This can only be true in the last iteration. */
if (score <= AVPROBE_SCORE_RETRY) {
av_log(logctx, AV_LOG_WARNING,
"Format %s detected only with low score of %d, "
"misdetection possible!\n", (*fmt)->name, score);
} else
av_log(logctx, AV_LOG_DEBUG,
"Format %s probed with size=%d and score=%d\n",
(*fmt)->name, probe_size, score);
#if 0
FILE *f = fopen("probestat.tmp", "ab");
fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);
fclose(f);
#endif
}
}
if (!*fmt)
ret = AVERROR_INVALIDDATA;
fail:
/* Rewind. Reuse probe buffer to avoid seeking. */
ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
if (ret >= 0)
ret = ret2;
av_freep(&pd.mime_type);
return ret < 0 ? ret : score;
主要的逻辑在一个循环之中,不断的通过avio_read
()读取数据并且使用av_probe_input_format2
()推测格式,看下这个循环的构造,probe_size = PROBE_BUF_MIN
,循环默认初始值为#define PROBE_BUF_MIN 2048
,每次循环容量增加一倍probe_size << 1
,直到达到最大容量或者找到了合适的AVInputformat
退出循环,那么就很明显了,为了减少一次性读取的容量,这里采用不断的叠加去读取数据,因为推测大部分媒体的媒体数据根本用不了1M
的数据。
av_probe_input_format2
/**
* Guess the file format.
*
* @param pd data to be probed
* @param is_opened Whether the file is already opened; determines whether
* demuxers with or without AVFMT_NOFILE are probed.
* @param score_max A probe score larger that this is required to accept a
* detection, the variable is set to the actual detection
* score afterwards.
* If the score is <= AVPROBE_SCORE_MAX / 4 it is recommended
* to retry with a larger probe buffer.
*/
ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max);
- pd:推测媒体格式的结构图
- is_opened:这个参数非常重要,表示是否需要打开文件,过滤哪些文件
- score_max:分数的阈值,小于这个分数可以认为失败
ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{
int score_ret;
ff_const59 AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
if (score_ret > *score_max) {
*score_max = score_ret;
return fmt;
} else
return NULL;
}
很简单,调用了av_probe_input_format3
av_probe_input_format3
ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;
const AVInputFormat *fmt1 = NULL;
ff_const59 AVInputFormat *fmt = NULL;
int score, score_max = 0;
void *i = 0;
const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
enum nodat {
NO_ID3,
ID3_ALMOST_GREATER_PROBE,
ID3_GREATER_PROBE,
ID3_GREATER_MAX_PROBE,
} nodat = NO_ID3;
if (!lpd.buf)
lpd.buf = (unsigned char *) zerobuffer;
//#1
if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
int id3len = ff_id3v2_tag_len(lpd.buf);
if (lpd.buf_size > id3len + 16) {
if (lpd.buf_size < 2LL*id3len + 16)
nodat = ID3_ALMOST_GREATER_PROBE;
lpd.buf += id3len;
lpd.buf_size -= id3len;
} else if (id3len >= PROBE_BUF_MAX) {
nodat = ID3_GREATER_MAX_PROBE;
} else
nodat = ID3_GREATER_PROBE;
}
while ((fmt1 = av_demuxer_iterate(&i))) {
//#2
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue;
score = 0;
if (fmt1->read_probe) {
//#3
score = fmt1->read_probe(&lpd);
if (score)
av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
switch (nodat) {
case NO_ID3:
score = FFMAX(score, 1);
break;
case ID3_GREATER_PROBE:
case ID3_ALMOST_GREATER_PROBE:
score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
break;
case ID3_GREATER_MAX_PROBE:
score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
break;
}
}
} else if (fmt1->extensions) {
if (av_match_ext(lpd.filename, fmt1->extensions))
score = AVPROBE_SCORE_EXTENSION;
}
if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
if (AVPROBE_SCORE_MIME > score) {
av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
score = AVPROBE_SCORE_MIME;
}
}
if (score > score_max) {
score_max = score;
fmt = (AVInputFormat*)fmt1;
} else if (score == score_max)
fmt = NULL;
}
if (nodat == ID3_GREATER_PROBE)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;
}
大概判定的方式有3中,1.文件扩展名判断 2.mime/type
判断 3.读取头文件内容判断,优先级3>2>1,
- #1 关于
ID3
的相关判断,这里没有深入研究过,就不误人子弟了,可以参考zh.wikipedia.org/wiki/ID3 - #2 这里通过
is_opend
进行过滤文件,IO层已经打开,则不需要读取文件的会被过滤掉,IO层没有打开,那么需要读取文件的格式将会被过滤掉 - #3 到这一步,说明前面已经根据IO获取到了头数据在探测
buffer
之中,见上面的av_probe_input_buffer2
里面的那个循环,然后调用AVformat
的read_probe
进行探测,这里是个函数指针调用
AVInputFormat->read_probe()
这里会调用到对应AVInputFormat
的read_probe
方法,我们找一个例子,例如,FLV
封装格式的AVInputFormat
模块定义(位于libavformat\flvdec.c
)如下所示
AVInputFormat ff_live_flv_demuxer = {
.name = "live_flv",
.long_name = NULL_IF_CONFIG_SMALL("live RTMP FLV (Flash Video)"),
.priv_data_size = sizeof(FLVContext),
.read_probe = live_flv_probe,
.read_header = flv_read_header,
.read_packet = flv_read_packet,
.read_seek = flv_read_seek,
.read_close = flv_read_close,
.extensions = "flv",
.priv_class = &live_flv_class,
.flags = AVFMT_TS_DISCONT
};
static int live_flv_probe(const AVProbeData *p)
{
return probe(p, 1);
}
static int probe(const AVProbeData *p, int live)
{
const uint8_t *d = p->buf;
unsigned offset = AV_RB32(d + 5);
if (d[0] == 'F' &&
d[1] == 'L' &&
d[2] == 'V' &&
d[3] < 5 && d[5] == 0 &&
offset + 100 < p->buf_size &&
offset > 8) {
int is_live = !memcmp(d + offset + 40, "NGINX RTMP", 10);
if (live == is_live)
return AVPROBE_SCORE_MAX;
}
return 0;
}
从判断代码大概能分析出来,通过探测buffer的字节数据,来判断是否为flv
格式,比如前面3个字节的判断等,具体可以百度下大概flv
的封装格式对应的看下,这里就不多说了
整个init_input()
大概就分析完成了,回到最上面,还有1个函数read_header
AVInputFormat-> read_header()
初始化完成之后,得到了对应的AVInputformat
之后,会调用对应的函数指针read_header
方法完成多媒体头文件相关的初始化工作,还是上面flv
那个例子
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;
}
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;
int 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;
} FLVContext;
- 将
AVFormatContext
的priv_data
指向FLVContext
,通过字节的读取校验,不断的进行赋值操作,读一个字节函数avio_r8()
,读取四个字节数据avio_r32()
,以此类推,参考flv/heder
图