您的位置:首页 > 其它

ffmpeg框架阅读笔记二 : 寻找AVIOContext初始化过程,自定义初始化。

2017-06-19 15:35 871 查看
在avformat_open_input中,有一个 init_input函数,它的作用是打开输入媒体,初始化所有与媒体读写有关的结构们,例如/AVIOContext,AVInputFormat等等。分析init_input函数,找出AVIOContext的初始化过程。以下对于init_input函数的分析代码摘自 http://blog.csdn.NET/nkmnkm/article/details/7043241,表示感谢!

补充一下:

[cpp] view plain copy

//参数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;                       //输入格式可通过AVProbeData(包含文件名/buffer)传入av_probe_input_format函数来获得。

//忽略
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);  //ID3v2是某些MP3文件的标签帧,包含歌曲的某些信息。是ID3v1(尾部128byte)的扩展。

//读一下媒体的头部,在read_header()中主要是做某种格式的初始化工作,比如填充自己的
//私有结构,根据流的数量分配流结构并初始化,把文件指针指向数据区开始处等.
//当mp3文件时,调用ff_mp3_demuxer的read_header() 。在里面检测媒体信息,如果没有则说明没有id3v2头,跳转到尾部,去读id3v1信息。
if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s, &ap)) < 0)//内部使用一系列函数,分析mp3头部信息
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;


}

分析ff_mp3_demuxer的read_header()函数

ffmpeg先读取id3v2,读不到则跳转至距离文件尾部128字节处读取id3v1.

[cpp] view plain copy

static int mp3_read_header(AVFormatContext *s,

AVFormatParameters *ap)

{

AVStream *st;

int64_t off;

st = av_new_stream(s, 0);
if (!st)
return AVERROR(ENOMEM);

st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
st->codec->codec_id = CODEC_ID_MP3;
st->need_parsing = AVSTREAM_PARSE_FULL;
st->start_time = 0;

// lcm of all mp3 sample rates
av_set_pts_info(st, 64, 1, 14112000);

off = avio_tell(s->pb);

//没有必须的媒体信息(无id3v2头或者损坏),则读id3v1信息
if (!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX))
ff_id3v1_read(s);

if (mp3_parse_vbr_tags(s, st, off) < 0) //分析如果是vbr格式,计算时长。cbr和vbr的时长计算有相关文章。
avio_seek(s->pb, off, SEEK_SET);

/* the parameters will be extracted from the compressed bitstream */
return 0;


}

[cpp] view plain copy

//打开输入媒体并填充其AVInputFormat结构

static int init_input(AVFormatContext *s, const char *filename)

{

int ret;

AVProbeData pd = { filename, NULL, 0 };

//当调用者已指定了pb(数据取得的方式)--一般不会这样.除非自定义AVIOContext,不使用file或者pipe
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))))//根据AVProbeData的内容来获取格式。可以根据文件名或者
//buffer数据。内部首先匹配demuxer的AVInputformat全局变量,然后调用其read_probe成员。该函数在对应格式中初始化,读格式信息

//如果已指定了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);
}


通过以上分析,看出当用户不提供pb即AVIOContext,需要文件,并且根据文件无法猜出iformat的时候,就要打开文件进行探测。此处有一个疑问:当从文件名中猜出iformat时,此时pb是否也被初始化?因为有文件就肯定需要pb的。没有读源码,此时暂不需要。

依然回来寻找AVIOContext的初始化函数,找到avio_open函数,输入参数是s->pb,可能在里面对其初始化,分析之。

[cpp] view plain copy

//打开一个地址指向的媒体

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;


}

找到函数ffio_fdopen,第一个参数s。分析函数ffio_fdopen。

[cpp] view plain copy

int ffio_fdopen(AVIOContext **s, URLContext *h)

{

uint8_t *buffer;
int buffer_size, max_packet_size;

max_packet_size = h->max_packet_size;
if (max_packet_size) {
buffer_size = max_packet_size; /* no need to bufferize more than one packet */
} else {
buffer_size = IO_BUFFER_SIZE;
}
//创建buffer缓冲 用来初始化s
buffer = av_malloc(buffer_size);
if (!buffer)
return AVERROR(ENOMEM);
//为s分配内存空间
*s = av_mallocz(sizeof(AVIOContext));
if(!*s) {
av_free(buffer);
return AVERROR(ENOMEM);
}

//初始化AVIOContext
if (ffio_init_context(*s, buffer, buffer_size,
h->flags & AVIO_FLAG_WRITE, h,
(void*)ffurl_read, (void*)ffurl_write, (void*)ffurl_seek) < 0) {
av_free(buffer);
av_freep(s);
return AVERROR(EIO);
}


if FF_API_OLD_AVIO

(*s)->is_streamed = h->is_streamed;


endif

(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
(*s)->max_packet_size = max_packet_size;
if(h->prot) {
(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;
(*s)->read_seek  = (int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;
}
return 0;


}

找到初始化函数ffio_init_context,分析。

[cpp] view plain copy

nt ffio_init_context(AVIOContext *s,

unsigned char *buffer,

int buffer_size,

int write_flag,

void *opaque,

int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),

int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),

int64_t (*seek)(void *opaque, int64_t offset, int whence))

{

s->buffer = buffer;

s->buffer_size = buffer_size;

s->buf_ptr = buffer;

s->opaque = opaque;

url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);

s->write_packet = write_packet;

s->read_packet = read_packet;

s->seek = seek;

s->pos = 0;

s->must_flush = 0;

s->eof_reached = 0;

s->error = 0;

if FF_API_OLD_AVIO

s->is_streamed = 0;


endif

s->seekable = AVIO_SEEKABLE_NORMAL;
s->max_packet_size = 0;
s->update_checksum= NULL;
if(!read_packet && !write_flag){
s->pos = buffer_size;
s->buf_end = s->buffer + buffer_size;
}
s->read_pause = NULL;
s->read_seek  = NULL;
return 0;


}

对AVIOContex的初始化,ffmpeg提供了另外一个api,

[cpp] view plain copy

AVIOContext *avio_alloc_context(

unsigned char *buffer,

int buffer_size,

int write_flag,

void *opaque,

int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),

int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),

int64_t (*seek)(void *opaque, int64_t offset, int whence))

{

AVIOContext *s = av_mallocz(sizeof(AVIOContext));

if (!s)

return NULL;

ffio_init_context(s, buffer, buffer_size, write_flag, opaque,

read_packet, write_packet, seek);

return s;

}

根据以上分析,知道创建并初始化AVIOContext的步骤:av_mallocz(sizeof(AVIOContext)) ——–> ffio_init_context.

可以自定义回调函数,buffer缓冲区来进行初始化。后面再记录完整测试过程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ffmpeg