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

来源:互联网 发布:dcs编程软件 编辑:程序博客网 时间:2024/06/05 11:54

在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缓冲区来进行初始化。后面再记录完整测试过程

原创粉丝点击