FFMPEG源码分析(一)

ffmpeg之前公司项目中就使用过,但是多停留于应用层面,实现某个功能时,需要哪些结构体以及调用哪些函数。最近想系统的学习一下ffmpeg,于是开始看雷霄骅https://blog.csdn.net/leixiaohua1020的博客(在此缅怀下大神)。看到ffmpeg中重要的结构体方面,于是想要到源码层看看各个结构体初始化等工作,以便对各个结构体有一个更深入的了解。废话不多说,结构体的介绍请大家去雷神的博客了解,下面进入第一个结构体AVIOContext:

AVIOContext

/* 雷霄骅* 中国传媒大学/数字电视技术* leixiaohua1020@126.com**//*** Bytestream IO Context.* New fields can be added to the end with minor version bumps.* Removal, reordering and changes to existing fields require a major* version bump.* sizeof(AVIOContext) must not be used outside libav*.** @note None of the function pointers in AVIOContext should be called*       directly, they should only be set by the client application*       when implementing custom I/O. Normally these are set to the*       function pointers specified in avio_alloc_context()*/
typedef struct {/*** A class for private options.** If this AVIOContext is created by avio_open2(), av_class is set and* passes the options down to protocols.** If this AVIOContext is manually allocated, then av_class may be set by* the caller.** warning -- this field can be NULL, be sure to not pass this AVIOContext* to any av_opt_* functions in that case.*/AVClass *av_class;unsigned char *buffer;  /**< Start of the buffer. */int buffer_size;        /**< Maximum buffer size */unsigned char *buf_ptr; /**< Current position in the buffer */unsigned char *buf_end; /**< End of the data, may be less thanbuffer+buffer_size if the read function returnedless data than requested, e.g. for streams whereno more data has been received yet. */void *opaque;           /**< A private pointer, passed to the read/write/seek/...functions. */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);int64_t pos;            /**< position in the file of the current buffer */int must_flush;         /**< true if the next seek should flush */int eof_reached;        /**< true if eof reached */int write_flag;         /**< true if open for writing */int max_packet_size;unsigned long checksum;unsigned char *checksum_ptr;unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);int error;              /**< contains the error code or 0 if no error happened *//*** Pause or resume playback for network streaming protocols - e.g. MMS.*/int (*read_pause)(void *opaque, int pause);/*** Seek to a given timestamp in stream with the specified stream_index.* Needed for some network streaming protocols which don't support seeking* to byte position.*/int64_t (*read_seek)(void *opaque, int stream_index,int64_t timestamp, int flags);/*** A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable.*/int seekable;/*** max filesize, used to limit allocations* This field is internal to libavformat and access from outside is not allowed.*/int64_t maxsize;
} AVIOContext;

该结构体可以通过avio_open2函数来分配初始化,也可以手动分配初始化。下面看看avio_open2源码中在做什么。
在此之前,为了看源码时理解更顺畅一些,首先明确几个结构体间的关系,
AVIOContext中包含有一个URLContext 结构体,而在URLContext 中包含有一个URLProtocol结构体。

源码分析

int avio_open2(AVIOContext **s, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options)
{URLContext *h;int err;err = ffurl_open(&h, filename, flags, int_cb, options);if (err < 0)return err;err = ffio_fdopen(s, h);if (err < 0) {ffurl_close(h);return err;}return 0;
}

可以看到,该函数比较简单,其中两个主要的函数ffurl_open和ffio_fdopen,而需要生成的结构体AVIOContext **s,主要就是初始化了一个URLContext结构体给它。
一 ffurl_open函数

int ffurl_open(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options)
{int ret = ffurl_alloc(puc, filename, flags, int_cb);                     //--------------------(1)if (ret < 0)return ret;if (options && (*puc)->prot->priv_data_class &&(ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)   goto fail;if ((ret = av_opt_set_dict(*puc, options)) < 0)goto fail;ret = ffurl_connect(*puc, options);                                        //------------------(2)if (!ret)return 0;
fail:ffurl_close(*puc);*puc = NULL;return ret;
}
  1. 先看第(1)个函数:
int ffurl_alloc(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb)
{URLProtocol *p = NULL;if (!first_protocol) {av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. ""Missing call to av_register_all()?\n");}p = url_find_protocol(filename);if (p)return url_alloc_for_protocol(puc, p, filename, flags, int_cb);*puc = NULL;if (av_strstart(filename, "https:", NULL))av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with ""openssl, gnutls,\n""or securetransport enabled.\n");return AVERROR_PROTOCOL_NOT_FOUND;
}
这里出现了URLProtocol 这个结构体,用于解析路径的协议,比如file、udp、rtmp等等。static struct URLProtocol *url_find_protocol(const char *filename)
{URLProtocol *up = NULL;char proto_str[128], proto_nested[128], *ptr;size_t proto_len = strspn(filename, URL_SCHEME_CHARS);if (filename[proto_len] != ':' &&(filename[proto_len] != ',' || !strchr(filename + proto_len + 1, ':')) ||is_dos_path(filename))strcpy(proto_str, "file");elseav_strlcpy(proto_str, filename,FFMIN(proto_len + 1, sizeof(proto_str)));if ((ptr = strchr(proto_str, ',')))*ptr = '\0';av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));if ((ptr = strchr(proto_nested, '+')))*ptr = '\0';/* 这里proto_str已经储存为protocol的名字,比如"file"、“rtmp”、“http”等等,然后会循环获取当前注册了的协议结构体URLProtocol,* 比较其中的名字up->name是否 一样,如果一样就匹配成功,返回该URLProtocol*/while (up = ffurl_protocol_next(up)) {if (!strcmp(proto_str, up->name))break;if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&!strcmp(proto_nested, up->name))break;}return up;
}

这个函数内部主要是对路径的字符串做一些处理,从而获取到该路径对应的URLProtocol。如果成功获取,那么会 return url_alloc_for_protocol(puc, p, filename, flags, int_cb)

static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,const char *filename, int flags,const AVIOInterruptCB *int_cb)
{URLContext *uc;int err;#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())return AVERROR(EIO);
#endifif ((flags & AVIO_FLAG_READ) && !up->url_read) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for reading\n", up->name);return AVERROR(EIO);}if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {av_log(NULL, AV_LOG_ERROR,"Impossible to open the '%s' protocol for writing\n", up->name);return AVERROR(EIO);}uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);if (!uc) {err = AVERROR(ENOMEM);goto fail;}uc->av_class = &ffurl_context_class;uc->filename = (char *)&uc[1];strcpy(uc->filename, filename);uc->prot            = up;uc->flags           = flags;uc->is_streamed     = 0; /* default = not streamed */uc->max_packet_size = 0; /* default: stream file */if (up->priv_data_size) {uc->priv_data = av_mallocz(up->priv_data_size);if (!uc->priv_data) {err = AVERROR(ENOMEM);goto fail;}if (up->priv_data_class) {int proto_len= strlen(up->name);char *start = strchr(uc->filename, ',');*(const AVClass **)uc->priv_data = up->priv_data_class;av_opt_set_defaults(uc->priv_data);if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){int ret= 0;char *p= start;char sep= *++p;char *key, *val;p++;while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){*val= *key= 0;ret= av_opt_set(uc->priv_data, p, key+1, 0);if (ret == AVERROR_OPTION_NOT_FOUND)av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);*val= *key= sep;p= val+1;}if(ret<0 || p!=key){av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);av_freep(&uc->priv_data);av_freep(&uc);err = AVERROR(EINVAL);goto fail;}memmove(start, key+1, strlen(key));}}}if (int_cb)uc->interrupt_callback = *int_cb;*puc = uc;return 0;
fail:*puc = NULL;if (uc)av_freep(&uc->priv_data);av_freep(&uc);
#if CONFIG_NETWORKif (up->flags & URL_PROTOCOL_FLAG_NETWORK)ff_network_close();
#endifreturn err;
}

这个函数中主要就是malloc分配内存,并初始化URLContext 中一些变量的值。
至此,ffurl_alloc函数已经初始化一个URLContext结构体,并分配好空间。

  1. 第(2)个函数
int ffurl_connect(URLContext *uc, AVDictionary **options)
{int err =uc->prot->url_open2 ? uc->prot->url_open2(uc,uc->filename,uc->flags,options) :uc->prot->url_open(uc, uc->filename, uc->flags);if (err)return err;uc->is_connected = 1;/* We must be careful here as ffurl_seek() could be slow,* for example for http */if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)uc->is_streamed = 1;return 0;
}

在该函数中会查看结构体URLContext 中是否已经赋值了url_open2,如果有则通过url_open2 打开文件,否则使用url_open函数来打开文件。这个函数根据不同的协议会传入不同的回调,比如file类型其实就是调用open函数等。

二 ffio_fdopen函数

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 = av_malloc(buffer_size);if (!buffer)return AVERROR(ENOMEM);//分配AVIOContext 结构体内存空间,赋值一些参数和回调函数*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,(int (*)(void *, uint8_t *, int)) ffurl_read,(int (*)(void *, uint8_t *, int)) ffurl_write,(int64_t (*)(void *, int64_t, int)) ffurl_seek);if (!*s) {av_free(buffer);return AVERROR(ENOMEM);}(*s)->direct = h->flags & AVIO_FLAG_DIRECT;(*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;}(*s)->av_class = &ff_avio_class;return 0;
}

该函数首先初始化AVIOContext中的Buffer。如果URLContext中设置了max_packet_size,则将Buffer的大小设置为max_packet_size。如果没有设置的话(似乎大部分URLContext都没有设置该值),则会分配IO_BUFFER_SIZE个字节给Buffer。IO_BUFFER_SIZE取值为32768。后面就是一些属性的赋值操作。
其中有几个函数注意一下,ffurl_read、ffurl_write和ffurl_seek。

int ffurl_read(URLContext *h, unsigned char *buf, int size)
{if (!(h->flags & AVIO_FLAG_READ))return AVERROR(EIO);return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read);
}

该函数首先判断一下URLContext 结构体是否支持读操作,如果支持就会调用retry_transfer_wrapper函数。

static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,int size, int size_min,int (*transfer_func)(URLContext *h,uint8_t *buf,int size))
{int ret, len;int fast_retries = 5;int64_t wait_since = 0;len = 0;while (len < size_min) {if (ff_check_interrupt(&h->interrupt_callback))return AVERROR_EXIT;ret = transfer_func(h, buf + len, size - len);if (ret == AVERROR(EINTR))continue;if (h->flags & AVIO_FLAG_NONBLOCK)return ret;if (ret == AVERROR(EAGAIN)) {ret = 0;if (fast_retries) {fast_retries--;} else {if (h->rw_timeout) {if (!wait_since)wait_since = av_gettime_relative();else if (av_gettime_relative() > wait_since + h->rw_timeout)return AVERROR(EIO);}av_usleep(1000);}} else if (ret < 1)return (ret < 0 && ret != AVERROR_EOF) ? ret : len;if (ret)fast_retries = FFMAX(fast_retries, 2);len += ret;}return len;
}

可以看到核心就是 ret = transfer_func(h, buf + len, size - len),而该函数是传入的回调函数,在read中就是h->prot->url_read,这个函数会根据不同的协议而不同,也就是在进行读文件的实际操作,比如file协议中会调用read函数去实际读取数据。读完以后会进行一些容错处理。

下面给大家看一下file协议的函数就明白上面的意思了。文件协议中代码如下(file.c):

URLProtocol ff_file_protocol = {.name                = "file",.url_open            = file_open,.url_read            = file_read,.url_write           = file_write,.url_seek            = file_seek,.url_close           = file_close,.url_get_file_handle = file_get_handle,.url_check           = file_check,
};

其中的所有file_xxx函数如下:

/**雷霄骅*leixiaohua1020@126.com*中国传媒大学/数字电视技术*/
/* standard file protocol */static int file_read(URLContext *h, unsigned char *buf, int size)
{int fd = (intptr_t) h->priv_data;int r = read(fd, buf, size);return (-1 == r)?AVERROR(errno):r;
}static int file_write(URLContext *h, const unsigned char *buf, int size)
{int fd = (intptr_t) h->priv_data;int r = write(fd, buf, size);return (-1 == r)?AVERROR(errno):r;
}static int file_get_handle(URLContext *h)
{return (intptr_t) h->priv_data;
}static int file_check(URLContext *h, int mask)
{struct stat st;int ret = stat(h->filename, &st);if (ret < 0)return AVERROR(errno);ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ  : 0;ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0;return ret;
}#if CONFIG_FILE_PROTOCOLstatic int file_open(URLContext *h, const char *filename, int flags)
{int access;int fd;av_strstart(filename, "file:", &filename);if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {access = O_CREAT | O_TRUNC | O_RDWR;} else if (flags & AVIO_FLAG_WRITE) {access = O_CREAT | O_TRUNC | O_WRONLY;} else {access = O_RDONLY;}
#ifdef O_BINARYaccess |= O_BINARY;
#endiffd = open(filename, access, 0666);if (fd == -1)return AVERROR(errno);h->priv_data = (void *) (intptr_t) fd;return 0;
}/* XXX: use llseek */
static int64_t file_seek(URLContext *h, int64_t pos, int whence)
{int fd = (intptr_t) h->priv_data;if (whence == AVSEEK_SIZE) {struct stat st;int ret = fstat(fd, &st);return ret < 0 ? AVERROR(errno) : st.st_size;}return lseek(fd, pos, whence);
}static int file_close(URLContext *h)
{int fd = (intptr_t) h->priv_data;return close(fd);
}

到此,结构体AVIOContext就被初始化完成。

参考资料
[1]: https://blog.csdn.net/leixiaohua1020/article/details/14215369

FFMPEG源码分析(一)相关推荐

  1. FFMPEG源码分析(二)

    ffmpeg源码分析之数据流 本文主要介绍ffmpeg的数据流,在ffmpeg中主要分有三个主要用途用于媒体流的解码播放,媒体流的转换(解码之后再编码)和媒体流录制. 媒体流的解码播放 在ffmpeg ...

  2. FFMPEG 源码分析

    FFMPEG基本概念: ffmpeg是一个开源的编解码框架,它提供了一个音视频录制,解码和编码库.FFMPEG是在linux下开发的,但也有windows下的编译版本. ffmpeg项目由以下几部分组 ...

  3. FFmpeg源码分析-直播延迟-内存泄漏

    FFmpeg源码分析-直播延迟-内存泄漏|FFmpeg源码分析方法|ffmpeg播放为什么容易产生延迟|解复用.解码内存泄漏分析 专注后台服务器开发,包括C/C++,Linux,Nginx,ZeroM ...

  4. ffmpeg源码分析-parse_optgroup

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  5. ffmpeg源码分析-ffmpeg_parse_options

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  6. ffmpeg源码分析-transcode_step

    本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8 ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑. a. ...

  7. ffmpeg源码分析与应用示例(一)——H.264解码与QP提取

    本文包含以下内容 1.H.264解码流程详述与对应ffmpeg源码解析 2.针对一个应用实例介绍通过修改ffmpeg源码解决问题的方案 具有较强的综合性. 先介绍一下在第二部分中将要解决的实际问题:自 ...

  8. ffmpeg源码分析_ffmpeg音视频同步的几种策略

    在前面的文章中,我们介绍了播放器的视频渲染及音频渲染的相关知识,这些都是单独进行的,一旦在现实开发中将视频及音频结合在一起播放就会出现音视频不同步的问题. 下面我们就来分析一下如何解决音视频同步的问题 ...

  9. 【FFMPEG源码分析】ffmpeg中context与AVClass,AVOption之间的关系

    通过前面三篇文章的分析大致了解了ffmpeg中demuxer/decoder模块的内部大致结构和数据处理流程.在阅读源码的过程中经常会看到XXXContext,AVClass xxx_class, A ...

最新文章

  1. 2017年软件工程第四次作业-4每周例行报告
  2. 汉仪尚巍手书_官宣,汉仪字库入选“十大著作权合作伙伴”啦!
  3. [Sharepoint2007对象模型]第二回:Web应用程序服务(SPWebService)
  4. Android 之自定义组件
  5. idea打不了断点红点没对号_你可能不知道的 IDEA 高级调试技巧
  6. JavaScript实现类与继承
  7. linux 常见问题集 q,Linux新手几个常见问题集
  8. IT项目管理之系统设计
  9. bzoj 4501 旅行
  10. DLL系列6.函数转发器
  11. C primer plus 第六版pdf下载
  12. 如何将FLV视频转成MP3格式?
  13. 层次分析法(附代码)
  14. 全球与中国粒子测量系统市场现状及未来发展趋势
  15. 矩阵的求逆:inv( )
  16. 关于Windows 7与Ubuntu启动的一些注记(win7屏蔽ubuntu的启动项)
  17. 中央电大 c语言程序设计a 试题,最新-中央电大2008年秋C语言程序设计A试题1.doc...
  18. Excel删除文本中任意位置所有空格的3种方法比较
  19. Pycharm远程服务器无法显示图片
  20. 5.询问姓名及电话号码

热门文章

  1. oracle update单引号,Oracle学习笔记:update的字段中包括单引号
  2. python方括号里面单引号_Python中单引号,双引号,三个单引号,外双单引号内双引号,外双引号内单引号的区别...
  3. 关于面试中谈谈你对面向对象思想的认知的小结
  4. 程序猿媛一:Android滑动翻页+区域点击事件
  5. 准备学python用什么电脑_学Python买什么笔记本电脑?
  6. 多所计算机强校入选,每个都是千万级别项目!国家重点研发计划“工业软件”重点专项...
  7. currentStyle与getComputedStyle
  8. HBase中什么是Region,什么是RegionServer
  9. 【计算机网络第六版(谢希仁)】网络要点总结
  10. 10秒钟,chatgpt帮你生成简单贪吃蛇游戏