当我们想要播放一个http链接的视频地址时, 首先需要做的是用这个地址进行拉流,下面我们就来分析一下在ffmpeg中这个过程是如何实现的。
第一个函数avformat_open_input
这个函数的实现源码在libavformat/utils.c
文件中。
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options){
//1.创建并初始化avformatContext对象
s = avformat_alloc_context();
//2.打开流地址
init_input(s,filename,...);
...
}
avformat_alloc_context 函数分析
AVFormatContext *avformat_alloc_context(void){
//分配AVFormatContext对象
AVFormatContext *ic;
ic = av_malloc(sizeof(AVFormatContext));
if (!ic) return ic;
//创建完ic对象后,首先需要对ic对象中的一些成员进行默认初始化
avformat_get_context_defaults(ic);
//internal这个成员是ffmpeg内部用于处理拉流解复用缓冲的
//不能被外界调用者访问,因此这里需要内部将这个变量初始化完成
ic->internal = av_mallocz(sizeof(*ic->internal));
if (!ic->internal) {
avformat_free_context(ic);
return NULL;
}
//offset表示在时间基为单位下时间戳的映射,这里初始化成未定义类型
ic->internal->offset = AV_NOPTS_VALUE;
//raw_packet_buffer可用的剩余大小,以字节为单位
ic->internal->raw_packet_buffer_remaining_size =RAW_PACKET_BUFFER_SIZE;
//一个媒体文件可能有多个流例如音频流和视频流,这个变量表示最短流结束的时间戳
ic->internal->shortest_end = AV_NOPTS_VALUE;
return ic;
}
对于上面的函数,我们需要关心的是avformat_get_context_defaults
做了什么事,以及这个函数的整体大致做了什么事,当我们清楚主体的架构后,在对每一个模块的细枝末节进行仔细的分析就快很多了。
avformat_get_context_default函数分析
static void avformat_get_context_defaults(AVFormatContext *s)
{
//清零
memset(s, 0, sizeof(AVFormatContext));
//为这个对象赋值一个class对象
s->av_class = &av_format_context_class;
//赋值两个函数指针用于io操作
s->io_open = io_open_default;
s->io_close = io_close_default;
//设置部分默认参数
av_opt_set_defaults(s);
}
在AVFormatContext对象里有两个很重要的函数,io_open
,io_close
,这两个函数专门负责对不同媒体协议进行打开读写。接下来我们就来分析一下ffmpeg中io_open_default函数的内部是如何实现的?
static int io_open_default(AVFormatContext *s, AVIOContext **ioCtx,
const char *url, int flags, AVDictionary **options){
return ffio_open_whitelist(ioCtx, url, flags,
&s->interrupt_callback,
options,
s->protocol_whitelist,
s->protocol_blacklist);
}
这个函数只是做了一层代理转发给了ffio_open_whitelist
函数。真个函数需要几个很重要的参数,第一个AVIOContext
对象,第二个url
不用多说,interrupt_callback
中断回调。
int ffio_open_whitelist(AVIOContext **s,
const char *filename,
int flags,
const AVIOInterruptCB *int_cb,
AVDictionary **options,
const char *whitelist, const char *blacklist){
URLContext *h;
int err;
//1.第一步
err = ffurl_open_whitelist(&h, filename,
flags, int_cb,
options, whitelist,
blacklist, NULL);
if (err < 0)
return err;
//1.第二步
err = ffio_fdopen(s, h);
if (err < 0) {
ffurl_close(h);
return err;
}
return 0;
}
通过代码我们能够很清楚的看到ffio_open_whitelist
函数内部是分两步进行的,第一步调用ffurl_open_whitelist
函数如果没有错误,则调用第二步ffio_fdopen
函数。
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
const AVIOInterruptCB *int_cb)
{
const URLProtocol *p = NULL;
//根据输入的文件地址名称,查找相关的协议
p = url_find_protocol(filename);
if (p) //找到协议后继续调用如下函数
return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
*puc = NULL;//没有找协议,检测是否是https协议如果是,则友好提示一下
if (av_strstart(filename, "https:", NULL))
av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
"openssl, gnutls "
"or securetransport enabled.\n");
return AVERROR_PROTOCOL_NOT_FOUND;
}
该函数同业也是分两步进行,第一步查找与该输入地址相关练的url协议,如果找到了相关的协议则赋值给URLProtocol对象,然后调用url_alloc_for_protocol
进行下一步操作,如果没有找到相关的协议,则直接返回,友好的是在返回前先检测一下是否是https协议,如果是则提示一下用户,这是因为,如果ffmpeg没有关联openssl库,则FFmpeg是不支持https协议的,也就是上一步会返回null对象。
#define URL_SCHEME_CHARS \
"abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"0123456789+-."
static const struct URLProtocol *url_find_protocol(const char *filename)
{
//这是是一个协议数组对象,每一个协议都是一个指针对象
const URLProtocol **protocols;
char proto_str[128], proto_nested[128], *ptr;
//获取协议头的长度,例如http://livelive.hkstv...
//返回的长度就是4。
size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
int i;
//这里是一个不常见协议的检测,我们平时一般不会遇到一般我们都是`协议头:`这种形式
//TODO:文件协议是否走这里
if (filename[proto_len] != ':' &&
(strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
is_dos_path(filename))
strcpy(proto_str, "file");
else//一般情况下,代码都会走这里,主要功能是将文件协议头复制到proto_str中
av_strlcpy(proto_str, filename,
FFMIN(proto_len + 1, sizeof(proto_str)));
//将协议头复制一份,用于下文的嵌套协议检测
av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
//检测协议头中是否有‘+’号,如果有则将加号的位置用‘\0’替代
//这样就将后面的协议去掉了
//这里是为了防止出现类似于这样的协议http+tls:\\这种协议
//如果这种协议出现,我们是无法在后面匹配到
if ((ptr = strchr(proto_nested, '+')))
*ptr = '\0';
//获取ffmpeg支持的全部协议数组
protocols = ffurl_get_protocols(NULL, NULL);
if (!protocols)
return NULL;
//遍历数组
for (i = 0; protocols[i]; i++) {
const URLProtocol *up = protocols[i];
//对比协议字符串
//如果匹配到,则释放协议数组,返回当前协议的URLProtocol对象
if (!strcmp(proto_str, up->name)) {
av_freep(&protocols);
return up;
}
//proto_str没有匹配到,则可能是嵌套协议,对比除去`+`后的协议头
if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
!strcmp(proto_nested, up->name)) {
av_freep(&protocols);
return up;
}
}
av_freep(&protocols);
return NULL;
}
经过上面分析,该函数就是查找ffmpeg中url所匹配的协议,如果找到,则返回与该协议相关的URLProtocol
对象,如果没找到则返回null
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
const URLProtocol **ffurl_get_protocols(const char *whitelist,
const char *blacklist){
const URLProtocol **ret;
int i, ret_idx = 0;
//分配协议数组空间
ret = av_mallocz_array(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));
if (!ret)
return NULL;
for (i = 0; url_protocols[i]; i++) {
const URLProtocol *up = url_protocols[i];
if (whitelist && *whitelist && !av_match_name(up->name, whitelist))
continue;
if (blacklist && *blacklist && av_match_name(up->name, blacklist))
continue;
ret[ret_idx++] = up;
}
return ret;
}
//protocol_list.c 这个文件是由configure生成的
static const URLProtocol * const url_protocols[] = {
&ff_async_protocol,
&ff_cache_protocol,
&ff_data_protocol,
&ff_ffrtmphttp_protocol,
&ff_file_protocol,
&ff_ftp_protocol,
&ff_hls_protocol,
&ff_http_protocol,
&ff_httpproxy_protocol,
&ff_ijkhttphook_protocol,
&ff_ijklongurl_protocol,
&ff_ijkmediadatasource_protocol,
&ff_ijksegment_protocol,
&ff_ijktcphook_protocol,
&ff_ijkio_protocol,
&ff_pipe_protocol,
&ff_prompeg_protocol,
&ff_rtmp_protocol,
&ff_rtmpt_protocol,
&ff_tee_protocol,
&ff_tcp_protocol,
&ff_udp_protocol,
&ff_udplite_protocol,
NULL };
网友评论