【FFmpeg】avformat_open_input函数

【FFmpeg】avformat_open_input函数

  • 1.avformat_open_input
    • [1.1 初始化输入格式(init_input)](#1.1 初始化输入格式(init_input))
      • [1.1.1 文件路径判断格式(av_probe_input_format2)](#1.1.1 文件路径判断格式(av_probe_input_format2))
        • [1.1.1.1 格式探测(read_probe)](#1.1.1.1 格式探测(read_probe))
        • [1.1.1.2 扩展匹配检查(av_match_ext)](#1.1.1.2 扩展匹配检查(av_match_ext))
      • [1.1.2 打开文件推测格式(av_probe_input_buffer2)](#1.1.2 打开文件推测格式(av_probe_input_buffer2))
    • [1.2 读取头(read_header)](#1.2 读取头(read_header))
    • [1.3 更新流的上下文(update_stream_avctx)](#1.3 更新流的上下文(update_stream_avctx))
  • 3.小结

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染

avformat_open_input的函数调用关系如下

1.avformat_open_input

函数的声明位于libavformat\avformat.h中,从声明看,这个函数的主要作用是打开输入的数据源,并且读取头部,此时,没有打开解码器。同时,数据源必须使用avformat_close_input进行关闭

c 复制代码
/**
 * 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,
                        const AVInputFormat *fmt, AVDictionary **options);

函数的定义位于libavformat\demux.c中,定义如下,主要的工作流程为:

(1)为avformat分配空间(avformat_alloc_context)

(2)初始化输入格式(init_input)

(3)黑白名单的检查

(4)其他信息的检查

(5)读取头信息(read_header)

(6)更新流的音频/视频上下文(update_stream_avctx)

在函数执行的过程中,最核心的函数为初始化输入格式(init_input)

c 复制代码
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        const AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    FFFormatContext *si;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;
    int ret = 0;
	// ----- 1.avformat分配空间 ------ // 
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    si = ffformatcontext(s);
    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);
    }
    if (fmt)
        s->iformat = fmt;

    if (options)
        av_dict_copy(&tmp, *options, 0);

    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;
    }
	// ----- 2.初始化输入格式 ----- //
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;
	// ----- 3.检查黑白名单 ----- //
    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;
    }
	// ----- 4.其他信息的检查 ----- //
	// 检查跳过的字节
    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 (ffifmt(s->iformat)->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(ffifmt(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 an AVIOContext */
	// AVFMT_NOFILE格式将没有AVIOContext
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

    if (ffifmt(s->iformat)->read_header)
    	// ----- 5.读取头信息 ----- // 
        if ((ret = ffifmt(s->iformat)->read_header(s)) < 0) { 
            if (ffifmt(s->iformat)->flags_internal & FF_INFMT_FLAG_INIT_CLEANUP)
                goto close;
            goto fail;
        }
	// id3v3主要用于提供MP3文件的附加信息,例如标题、专辑、发行年份等等
    if (!s->metadata) {
        s->metadata    = si->id3v2_meta;
        si->id3v2_meta = NULL;
    } else if (si->id3v2_meta) {
        av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&si->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;
	// avio_tell = ftell(读写文件偏移量)
    if (s->pb && !si->data_offset)
        si->data_offset = avio_tell(s->pb);

    si->raw_packet_buffer_size = 0;
	// ----- 6.更新流的音频/视频上下文 ----- //
	// 更新AVCodecParameters,并且给到编解码器上下文AVCodecContext
    update_stream_avctx(s);

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;

close:
    if (ffifmt(s->iformat)->read_close)
        ffifmt(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;
}

1.1 初始化输入格式(init_input)

函数的主要工作内容为打开视频并且探测视频格式,定义位于libavformat\demux.c中,主要的工作流程为:

(1)在自定义AVIOContext情况下,如果指定了format则直接返回,如果没有指定format则会使用av_probe_input_buffer2探测。这种情况出现的数量不多,但当内存中读取信息(需要初始化自定义AVIOContext),则会走入这一分支

(2)在给定format情况下,会直接返回score;如果没有给format,但可以根据文件路径来判断格式,调用av_probe_input_format2进行判断。这种情况是最一般的情况。

(3)如果也无法根据文件路径来判断格式,则需要打开文件进行文件格式的猜测,先调用io_open打开数据源,然后调用av_probe_input_buffer2进行文件格式的猜测

c 复制代码
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;
	// ----- 1.自定义AVIOContext ----- //
	// 如果自定义了AVIOContext,如果指定了format则直接返回;如果没有指定format则使用av_probe_input_buffer2进行探测
	// 这种情况出现的数量不多,但当从内存中读取信息(需要初始化自定义AVIOContext),则会走入这一分支
    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.给定format或没给定format但可以根据文件路径来判断格式 ----- //
	// 更加普遍的情况,因为通常会指定一个format来避免格式出错
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
	// 打开这个数据源
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    if (s->iformat)
        return 0;
    // ----- 3.如果无法根据文件路径来判断格式,则需要打开文件进行探测格式 ----- //
	return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                  s, 0, s->format_probesize);
}

1.1.1 文件路径判断格式(av_probe_input_format2)

函数通过调用av_probe_input_format3获取判断的fmt和对应的score,如果获取的score大于外部指定的score,则返回该fmt,否则返回NULL

c 复制代码
const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
                                            int is_opened, int *score_max)
{
    int score_ret;
    const 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的定义如下,位于libavformat\format.c中,主要工作流程为:

(1)【音频】检测id3v2的标签头部信息

(2)循环遍历所有可用的fmt,查找一个score最大的fmt

(a)使用av_demuxer_iterate循环获取每一个fmt

(b)如果当前fmt的probe函数可用,则获取一个score;如果定义了extensions,会使用av_match_ext进行扩展匹配的检查。进行nodat的检查,并调整score,其中AVPROBE_SCORE_EXTENSION=50

(c)如果不包含probe但包含extensions,使用av_match_ext进行扩展的匹配

(d)比较输入媒体的mime和比较的mime,如果mime的score比score要大,则将score配置为mime

(e)寻找最大的score及其对应的format

c 复制代码
const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
                                            int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    const 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.检测id3v2的标签头部信息 ----- //
    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;
    }
	// ----- 2.循环遍历所有可用的fmt,查找一个score最大的fmt ----- //
    while ((fmt1 = av_demuxer_iterate(&i))) {
        if (fmt1->flags & AVFMT_EXPERIMENTAL)
            continue;
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
		// read_probe: 判断给定文件是否有可能被解析为这种格式
		// 提供的缓冲区保证是AVPROBE_PADDING_SIZE字节大,所以您不必检查它,除非您需要更多
		// 例如flv格式,会调用flv_probe函数,进而调用probe函数进行格式探测
        if (ffifmt(fmt1)->read_probe) {
            score = ffifmt(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);
			// extensions: 如果定义了扩展,则不执行探测。通常不应该使用扩展格式猜测,因为它不够可靠
            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:
					// AVPROBE_SCORE_EXTENSION = 50
                    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) {	// 不包含probe但包含extensions,使用av_match_ext进行扩展的匹配
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
		// 比较输入媒体的mime和比较的mime,如果mime的score比score要大,则将score配置为mime
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
			// AVPROBE_SCORE_MIME = 75
            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;
            }
        }
		// 寻找最大的score及其对应的format
        if (score > score_max) {
            score_max = score;
            fmt       = 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;
}
1.1.1.1 格式探测(read_probe)

在进行格式探测的时候,会根据不同的fmt来执行,例如当前输入的流为flv格式,会使用flv的probe函数,对应的结构体为ff_flv_demuxer,定义在libavformat\flvdec.c中

c 复制代码
const FFInputFormat ff_flv_demuxer = {
    .p.name         = "flv",
    .p.long_name    = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .p.extensions   = "flv",
    .p.priv_class   = &flv_kux_class,
    .priv_data_size = sizeof(FLVContext),
    .read_probe     = flv_probe,
    .read_header    = flv_read_header,
    .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
    .read_close     = flv_read_close,
};

与旧版本的FFmpeg相比,这里有一点改动,使用的是FFInputFormat而不是AVInputFormat,FFInputFormat对AVInputFormat进行了封装,定义为libavformat\demux.h中

c 复制代码
typedef struct FFInputFormat {
    /**
     * The public AVInputFormat. See avformat.h for it.
     */
    AVInputFormat p;

    /**
     * Raw demuxers store their codec ID here.
     */
    enum AVCodecID raw_codec_id;

    /**
     * Size of private data so that it can be allocated in the wrapper.
     */
    int priv_data_size;

    /**
     * Internal flags. See FF_INFMT_FLAG_* above and FF_FMT_FLAG_* in internal.h.
     */
    int flags_internal;

    /**
     * Tell if a given file has a chance of being parsed as this format.
     * The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes
     * big so you do not have to check for that unless you need more.
     */
    int (*read_probe)(const AVProbeData *);

    /**
     * Read the format header and initialize the AVFormatContext
     * structure. Return 0 if OK. 'avformat_new_stream' should be
     * called to create new streams.
     */
    int (*read_header)(struct AVFormatContext *);

    /**
     * Read one packet and put it in 'pkt'. pts and flags are also
     * set. 'avformat_new_stream' can be called only if the flag
     * AVFMTCTX_NOHEADER is used and only in the calling thread (not in a
     * background thread).
     * @return 0 on success, < 0 on error.
     *         Upon returning an error, pkt must be unreferenced by the caller.
     */
    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);

    /**
     * Close the stream. The AVFormatContext and AVStreams are not
     * freed by this function
     */
    int (*read_close)(struct AVFormatContext *);

    /**
     * Seek to a given timestamp relative to the frames in
     * stream component stream_index.
     * @param stream_index Must not be -1.
     * @param flags Selects which direction should be preferred if no exact
     *              match is available.
     * @return >= 0 on success (but not necessarily the new offset)
     */
    int (*read_seek)(struct AVFormatContext *,
                     int stream_index, int64_t timestamp, int flags);

    /**
     * Get the next timestamp in stream[stream_index].time_base units.
     * @return the timestamp or AV_NOPTS_VALUE if an error occurred
     */
    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
                              int64_t *pos, int64_t pos_limit);

    /**
     * Start/resume playing - only meaningful if using a network-based format
     * (RTSP).
     */
    int (*read_play)(struct AVFormatContext *);

    /**
     * Pause playing - only meaningful if using a network-based format
     * (RTSP).
     */
    int (*read_pause)(struct AVFormatContext *);

    /**
     * Seek to timestamp ts.
     * Seeking will be done so that the point from which all active streams
     * can be presented successfully will be closest to ts and within min/max_ts.
     * Active streams are all streams that have AVStream.discard < AVDISCARD_ALL.
     */
    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);

    /**
     * Returns device list with it properties.
     * @see avdevice_list_devices() for more details.
     */
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
} FFInputFormat;

这里的.read_probe使用的是flv_probe,而flv_probe又调用probe

c 复制代码
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;
}

static int flv_probe(const AVProbeData *p)
{
    return probe(p, 0);
}

参考雷博的介绍可以知道flv头部的定义为

因此,上述函数执行的操作流程为:

(1)获得第6至第9字节的数据(对应Headersize字段)并且做大小端转换,然后存入offset变量。之所以要进行大小端转换是因为FLV是以"大端"方式存储数据,而操作系统是以"小端"方式存储数据,这一转换主要通过AV_RB32()函数实现。AV_RB32()是一个宏定义,其对应的函数是av_bswap32()

(2)解析前3个字节,分别为"F","L"和"V"

(3)解析第4个字节,版本号,必须小于5

(4)解析第5个字节,置为0

(5)offset + 100 < p->buf_size 并且 offset > 8

满足上述条件的话,则认为是flv格式

另外,还有一点注意,如果是别的格式,例如是raw H264 格式,不会有read_probe这个函数,如下

c 复制代码
const FFOutputFormat ff_h264_muxer = {
    .p.name            = "h264",
    .p.long_name       = NULL_IF_CONFIG_SMALL("raw H.264 video"),
    .p.extensions      = "h264,264",
    .p.audio_codec     = AV_CODEC_ID_NONE,
    .p.video_codec     = AV_CODEC_ID_H264,
    .p.subtitle_codec  = AV_CODEC_ID_NONE,
    .flags_internal    = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
                         FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
    .write_packet      = ff_raw_write_packet,
    .check_bitstream   = h264_check_bitstream,
    .p.flags           = AVFMT_NOTIMESTAMPS,
};
1.1.1.2 扩展匹配检查(av_match_ext)

函数的功能是检查扩展匹配,定义位于libavformat\avformat.c中,之中调用av_match_name进行名称匹配

c 复制代码
int av_match_ext(const char *filename, const char *extensions)
{
    const char *ext;

    if (!filename)
        return 0;

    ext = strrchr(filename, '.');
    if (ext)
        return av_match_name(ext + 1, extensions);
    return 0;
}

av_match_name的定义位于libavutil\avstring.c中,如下所示,是一个检查字符串匹配的函数

c 复制代码
int av_match_name(const char *name, const char *names)
{
    const char *p;
    size_t len, namelen;

    if (!name || !names)
        return 0;

    namelen = strlen(name);
    while (*names) {
        int negate = '-' == *names;
        p = strchr(names, ',');
        if (!p)
            p = names + strlen(names);
        names += negate;
        len = FFMAX(p - names, namelen);
        if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
            return !negate;
        names = p + (*p == ',');
    }
    return 0;
}

1.1.2 打开文件推测格式(av_probe_input_buffer2)

函数根据输入的数据文件来推测使用的格式

c 复制代码
/*
	探测字节流以确定输入格式。每当探测返回的分数过低时,就会增加探测缓冲区的大小,并进行另一次尝试。
	当探测大小达到最大值时,返回得分最高的输入格式
*/
int av_probe_input_buffer2(AVIOContext *pb, const 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;
    int eof = 0;
	// max_probe_size表示用于推测格式的最大probe大小,默认为PROBE_BUF_MAX (1<<20≈1MB)
    if (!max_probe_size)
        max_probe_size = PROBE_BUF_MAX;
    else if (max_probe_size < PROBE_BUF_MIN) { // PROBE_BUF_MIN = 2048
        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);

    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';
        }
    }
	// 在确定了max_probe_size之后,以PROBE_BUF_MIN为最小值开始进行探测
    for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt && !eof;
         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. */
        // 读取probe的数据
        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)
            goto fail;
        // avio_read中底层会调用read_packet,但是读取的比特数只有头部,不会读取内容
        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 */
            eof   = 1;
        }
        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. */
        // 猜测文件的格式
        // av_probe_input_format2会调用av_probe_input_format3进行格式探测
        *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;
}

1.2 读取头(read_header)

在进行了格式探测之后,会使用.read_header进行头信息的读取,例如使用flv格式,如下,会使用flv_read_header进行头信息的读取

c 复制代码
const FFInputFormat ff_flv_demuxer = {
    .p.name         = "flv",
    .p.long_name    = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .p.extensions   = "flv",
    .p.priv_class   = &flv_kux_class,
    .priv_data_size = sizeof(FLVContext),
    .read_probe     = flv_probe,
    .read_header    = flv_read_header,
    .read_packet    = flv_read_packet,
    .read_seek      = flv_read_seek,
    .read_close     = flv_read_close,
};

flv_read_header的定义如下。与老版本不同的是,这里只会进行header信息的读取,不会创建新的流stream

c 复制代码
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 */
    // KUX格式是优酷专属的视频格式,用于版权保护
    // 这里的意思应该是FLV数据在KUX文件中的位置是0xe40000
    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;
}

这里就有一个问题待思考,新版本中的数据流在哪里进行创建?从代码来看,如果是flv格式,read_header之中不会去创建AVStream,如果是别的格式例如MPEG-TS格式,有可能会在read_header中创建AVStream,如下所示。

c 复制代码
static int mpegts_read_header(AVFormatContext *s)
{
    // ...
    if (s->iformat == &ff_mpegts_demuxer.p) {
        /* normal demux */

        /* first do a scan to get all the services */
        seek_back(s, pb, pos);

        mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
        mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
        mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);
		// ...
    } else {
        AVStream *st;
        int pcr_pid, pid, nb_packets, nb_pcrs, ret, pcr_l;
        int64_t pcrs[2], pcr_h;
        uint8_t packet[TS_PACKET_SIZE];
        const uint8_t *data;

        /* only read packets */
		// 在这里创建新的流
        st = avformat_new_stream(s, NULL);
        //...
}

如果谈到对于流的理解,我想这里的流AVStream应该是一个通道,这个通道里面不断的进行数据传输,由于一个多媒体文件中可能包含不同种类的数据(如视频,音频,字幕等),所以一个多媒体文件中会包含一个或者多个流。在avformat_input_open当中,会首先打开一个数据源,接收这个数据源的信息,再将这些信息进行解析成多个流进行处理。从代码上看,如果是flv格式,只会解析头部信息,而流通道的建立会在获取packet时去创建,即在flv_read_packet当中实现

1.3 更新流的上下文(update_stream_avctx)

c 复制代码
static int update_stream_avctx(AVFormatContext *s)
{
    int ret;
    // 为每一个流进行更新
    for (unsigned i = 0; i < s->nb_streams; i++) {
        AVStream *const st  = s->streams[i];
        FFStream *const sti = ffstream(st);
		// 不需要更新context
        if (!sti->need_context_update)
            continue;

        /* close parser, because it depends on the codec */
        if (sti->parser && sti->avctx->codec_id != st->codecpar->codec_id) {
            av_parser_close(sti->parser);
            sti->parser = NULL;
        }

        /* update internal codec context, for the parser */
        // 将codecpar中的参数copy到avctx之中
        ret = avcodec_parameters_to_context(sti->avctx, st->codecpar);
        if (ret < 0)
            return ret;
		// 使用二分查找,找到AVCodecDescriptor
        sti->codec_desc = avcodec_descriptor_get(sti->avctx->codec_id);

        sti->need_context_update = 0;
    }
    return 0;
}

avcodec_parameters_to_context的定义如下,主要功能是将AVCodecParameters中的参数copy到AVCodecContext之中

c 复制代码
int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)
{
    int ret;

    codec->codec_type = par->codec_type;
    codec->codec_id   = par->codec_id;
    codec->codec_tag  = par->codec_tag;

    codec->bit_rate              = par->bit_rate;
    codec->bits_per_coded_sample = par->bits_per_coded_sample;
    codec->bits_per_raw_sample   = par->bits_per_raw_sample;
    codec->profile               = par->profile;
    codec->level                 = par->level;

    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        codec->pix_fmt                = par->format;
        codec->width                  = par->width;
        codec->height                 = par->height;
        codec->field_order            = par->field_order;
        codec->color_range            = par->color_range;
        codec->color_primaries        = par->color_primaries;
        codec->color_trc              = par->color_trc;
        codec->colorspace             = par->color_space;
        codec->chroma_sample_location = par->chroma_location;
        codec->sample_aspect_ratio    = par->sample_aspect_ratio;
        codec->has_b_frames           = par->video_delay;
        codec->framerate              = par->framerate;
        break;
    case AVMEDIA_TYPE_AUDIO:
        codec->sample_fmt       = par->format;
        ret = av_channel_layout_copy(&codec->ch_layout, &par->ch_layout);
        if (ret < 0)
            return ret;
        codec->sample_rate      = par->sample_rate;
        codec->block_align      = par->block_align;
        codec->frame_size       = par->frame_size;
        codec->delay            =
        codec->initial_padding  = par->initial_padding;
        codec->trailing_padding = par->trailing_padding;
        codec->seek_preroll     = par->seek_preroll;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        codec->width  = par->width;
        codec->height = par->height;
        break;
    }

    av_freep(&codec->extradata);
    if (par->extradata) {
        codec->extradata = av_mallocz(par->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
        if (!codec->extradata)
            return AVERROR(ENOMEM);
        memcpy(codec->extradata, par->extradata, par->extradata_size);
        codec->extradata_size = par->extradata_size;
    }

    av_packet_side_data_free(&codec->coded_side_data, &codec->nb_coded_side_data);
    ret = codec_parameters_copy_side_data(&codec->coded_side_data, &codec->nb_coded_side_data,
                                          par->coded_side_data, par->nb_coded_side_data);
    if (ret < 0)
        return ret;

    return 0;
}

avcodec_descriptor_get的定义如下,其中调用了bsearch进行二分查找,该函数定义在corecrt_search.h中,是Windows内嵌函数

c 复制代码
const AVCodecDescriptor *avcodec_descriptor_get(enum AVCodecID id)
{
    return bsearch(&id, codec_descriptors, FF_ARRAY_ELEMS(codec_descriptors),
                   sizeof(codec_descriptors[0]), descriptor_compare);
}

3.小结

avformat_input_open作为FFmpeg项目中使用频率非常高的函数,它的使用目的可以理解为,输入一个url和一个AVFormatContext,将这个URL数据源当中的信息解析并存入到AVFormatContext之中,其中最终要的是AVInputFormat信息。在探测或者猜测了这个信息之后,可以确定后续以何种方式来解析这个源数据当中的头信息,比如FLV格式还是H264格式,其数据处理的方式是不同的。

在用法上看,avformat_input_open可以这么来用,需要注意最后需要使用一个avformat_close_input来关闭这个数据源

c 复制代码
int main()
{
	AVFormatContext* av_in_fmt_ctx = NULL;
	const char* in_filename = "test.flv";
	if ((ret = avformat_open_input(&av_in_fmt_ctx, in_filename, 0, 0)) < 0) {
		fprintf(stderr, "Could not open input file.");
		goto end;
	}
	// processing ....
	avformat_close_input(&av_in_fmt_ctx);
}

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

相关推荐
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn5 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
青花瓷5 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
观音山保我别报错5 小时前
C语言扫雷小游戏
c语言·开发语言·算法
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉6 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
岁月小龙6 小时前
如何让ffmpeg运行时从当前目录加载库,而不是从/lib64
ffmpeg·origin·ffprobe·rpath