ffmpeg解码流程解析(1)

1,725 阅读8分钟

准备整体分析下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:强制指定AVFormatContextAVInputFormat的。这个参数一般情况下可以设置为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里面的那个循环,然后调用AVformatread_probe进行探测,这里是个函数指针调用

AVInputFormat->read_probe()

这里会调用到对应AVInputFormatread_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;
  • AVFormatContextpriv_data指向FLVContext,通过字节的读取校验,不断的进行赋值操作,读一个字节函数 avio_r8(),读取四个字节数据avio_r32(),以此类推,参考flv/heder

20190323190458764.jpg

感谢观看~~