ffmpeg源码分析之媒体打开过程

从打开文件开始.入口函数是avformat_open_input(),下面是对此函数的分析

//参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功,
//会返回一个AVFormatContext的实例.
//参数filename是媒体文件名或URL.
//参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以
//传入一个调用者定义的inputFormat,对应命令行中的 -f xxx段,如果指定了它,
//在打开文件中就不会探测文件的实际格式了,以它为准了.
//参数options是对某种格式的一些操作,是为了在命令行中可以对不同的格式传入
//特殊的操作参数而建的, 为了了解流程,完全可以无视它.
int avformat_open_input(AVFormatContext **ps,
		const char *filename,
		AVInputFormat *fmt,
		AVDictionary **options)
{
	AVFormatContext *s = *ps;
	int ret = 0;
	AVFormatParameters ap = { { 0 } };
	AVDictionary *tmp = NULL;

	//创建上下文结构
	if (!s && !(s = avformat_alloc_context()))
		return AVERROR(ENOMEM);
	//如果用户指定了输入格式,直接使用它
	if (fmt)
		s->iformat = fmt;

	//忽略
	if (options)
		av_dict_copy(&tmp, *options, 0);

	if ((ret = av_opt_set_dict(s, &tmp)) < 0)
		goto fail;

	//打开输入媒体(如果需要的话),初始化所有与媒体读写有关的结构们,比如
	//AVIOContext,AVInputFormat等等
	if ((ret = init_input(s, filename)) < 0)
		goto fail;
	//执行完此函数后,s->pb和s->iformat都已经指向了有效实例.pb是用于读写数据的,它
	//把媒体数据当做流来读写,不管是什么媒体格式,而iformat把pb读出来的流按某种媒体格
	//式进行分析,也就是说pb在底层,iformat在上层.

	//很多静态图像文件格式,都被当作一个格式处理,比如要打开.jpeg文件,需要的格式
	//名为image2.此处还不是很了解具体细节,作不得准哦.
	/* 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;
	//上下文中保存下文件名
	av_strlcpy(s->filename, filename, sizeof(s->filename));

	/* allocate private data */
	//为当前格式分配私有数据,主要用于某格式的读写操作时所用的私有结构.
	//此结构的大小在定义AVInputFormat时已指定了.
	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 */
	//从mp3文件中读ID3数据并保存之.
	if (s->pb)
		ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC);

	//读一下媒体的头部,在read_header()中主要是做某种格式的初始化工作,比如填充自己的
	//私有结构,根据流的数量分配流结构并初始化,把文件指针指向数据区开始处等.
	if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
		if ((ret = s->iformat->read_header(s, &ap)) < 0)
			goto fail;

	//保存数据区开始的位置
	if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)
		s->data_offset = avio_tell(s->pb);

	s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

	if (options) {
		av_dict_free(options);
		*options = tmp;
	}
	*ps = s;
	//执行成功
	return 0;

	//执行失败
	fail: av_dict_free(&tmp);
	if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
		avio_close(s->pb);
	avformat_free_context(s);
	*ps = NULL;
	return ret;
}
下面分析init_input():

//打开输入媒体并填充其AVInputFormat结构
static int init_input(AVFormatContext *s, const char *filename)
{
	int ret;
	AVProbeData pd = { filename, NULL, 0 };

	//当调用者已指定了pb(数据取得的方式)--一般不会这样.
	if (s->pb) {
		s->flags |= AVFMT_FLAG_CUSTOM_IO;
		if (!s->iformat)
			//如果已指定了pb但没指定iformat,以pb读取媒体数据进行探测,取得.取得iformat.
			return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);
		else if (s->iformat->flags & AVFMT_NOFILE)
			//如果已指定pb也指定了iformat,但是又指定了不需要文件(也包括URL指定的地址),这就矛盾了,
			//此时应是不需要pb的,因为不需操作文件,提示一下吧,也不算错.
			av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
					"will be ignored with AVFMT_NOFILE format.\n");
		return 0;
	}

	//一般会执行到这里
	if ((s->iformat && s->iformat->flags & AVFMT_NOFILE)
			|| (!s->iformat && (s->iformat = av_probe_input_format(&pd, 0))))
		//如果已指定了iformat并且不需要文件,也就不需要pb了,可以直接返回
		//如果没指定iformat,但是可以从文件名中猜出iformat,也成功.
		return 0;

	//如果从文件名中也猜不出媒体格式,则只能打开这个文件进行探测了,先打开文件
	if ((ret = avio_open(&s->pb, filename, AVIO_FLAG_READ)) < 0)
		return ret;
	if (s->iformat)
		return 0;
	//再探测之
	return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);
}
再看一下文件打开过程:

//打开一个地址指向的媒体
int avio_open(AVIOContext **s, const char *filename, int flags)
{
	//URLContext代表一个URL地址指向的媒体文件,本地路径也算一种.它封装了
	//操作一个媒体文件的相关数据,最重要的是prot变量,是URLProtocol型的.
	//prot代表一个特定的协义和协议操作函数们,URLContext包含不同的prot,
	//就可以通过URLContext使用不同的协议读写媒体数据,比如tcp,http,本地
	//文件用file协议.
    URLContext *h;
    int err;

    //创建并初始化URLContext,其prot通过文件名确定.然后打开这个媒体文件
    err = ffurl_open(&h, filename, flags);
    if (err < 0)
        return err;
    //其实文件已经在上边真正打开了.这里只是填充AVIOContext.使它记录下
    //URLContext,以及填充读写数据的函数指针.
    err = ffio_fdopen(s, h);
    if (err < 0) {
        ffurl_close(h);
        return err;
    }
    return 0;
}
下面是探测函数

int av_probe_input_buffer(AVIOContext *pb,
		AVInputFormat **fmt,
		const char *filename,
		void *logctx,
		unsigned int offset,
		unsigned int max_probe_size)
{
	AVProbeData pd = { filename ? filename : "", NULL, -offset };
	unsigned char *buf = NULL;
	int ret = 0, probe_size;

	//计算最多探测数据的字节数
	if (!max_probe_size) {
		max_probe_size = PROBE_BUF_MAX;
	} else if (max_probe_size > PROBE_BUF_MAX) {
		max_probe_size = PROBE_BUF_MAX;
	} else if (max_probe_size < PROBE_BUF_MIN) {
		return AVERROR(EINVAL);
	}

	if (offset >= max_probe_size) {
		return AVERROR(EINVAL);
	}

	//循环直到探测完指定的数据
	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))) {
		int score = probe_size < max_probe_size ? AVPROBE_SCORE_MAX / 4 : 0;
		int buf_offset = (probe_size == PROBE_BUF_MIN) ? 0 : probe_size >> 1;
		void *buftmp;

		if (probe_size < offset) {
			continue;
		}

		/* read probe data */
		//分配读取数据存放的缓冲
		buftmp = av_realloc(buf, probe_size + AVPROBE_PADDING_SIZE);
		if (!buftmp) {
			av_free(buf);
			return AVERROR(ENOMEM);
		}
		buf = buftmp;
		//利用pb读数据到缓冲的剩余空间中
		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) {
				av_free(buf);
				return ret;
			}
			score = 0;
			ret = 0; /* error was end of file, nothing read */
		}
		pd.buf_size += ret;
		pd.buf = &buf[offset];

		//缓冲中没有数据的部分要清0
		memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);

		/* guess file format */
		//从一个打开的文件只探测媒体格式
		*fmt = av_probe_input_format2(&pd, 1, &score);
		if (*fmt) {
			if (score <= AVPROBE_SCORE_MAX / 4) { //this can only be true in the last iteration
				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 (!*fmt) {
		av_free(buf);
		return AVERROR_INVALIDDATA;
	}

	/* rewind. reuse probe buffer to avoid seeking */
	//把探测时读入的数据保存到pb中,为的是真正读时直接利用之.
	if ((ret = ffio_rewind_with_probe_data(pb, buf, pd.buf_size)) < 0)
		av_free(buf);

	return ret;
}



  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
FFmpeg 是一套功用十分强大的开源媒体框架,被广泛应用于媒体处理和转码等方面。该框架由 C 语言编写实现,其核心功能主要包括媒体解码、编码、过滤、封装等。下面我们主要从源码层面展开分析FFmpeg 作为开源软件,源码具有极高的可读性和可扩展性,但是难度也很大,不适合初学者。如果我们要逐层分析 FFmpeg 常用流程,我们可以先从 FFmpeg 的主要模块开始,例如 AVFormatContext、AVCodecContext、AVFrame 等数据结构。 在一个典型的流程中,FFmpeg 首先通过 AVFormatContext 处理输入文件,然后通过 AVCodecContext 解码处理后输出到 AVFrame,最后通过 AVFormatContext 实现封装输出成文件。 此外,FFmpeg 还可以通过多种输入视频流格式(例如 RTSP,HTTP,FLV 等)对视频进行采集和处理,并支持多种输出格式(例如 MP4,FLV,AVI 等)。同时,FFmpeg 还能够实现多幅图像的合并、重采样和音视频混合等功能。要实现这些功能,我们需要从源代码层面着手。 FFmpeg 的源代码分别包括 libavformat、libavcodec、libavutil 等库,实现不同的功能。其中,libavformat 库主要提供了媒体文件的输入输出、封装和解封装等功能,libavcodec 库主要提供了音视频编码解码的功能,libavutil 库则提供了一些公共的工具函数和数据类型定义。通过逐层深入分析,我们可以深入地了解 FFmpeg 的实现原理,以及如何使用 FFmpeg 库来完成多种媒体处理和转码任务。 总的来说,FFmpeg 是一款优秀的媒体处理和转码库,它的源码具有极高的可扩展性和自定义性,同时难度也较大。如果要深入了解和使用 FFmpeg,需要有扎实的编程背景和相关经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值