OBS的推流地址是在哪里设置的呢?我们来看看吧:
首先我们反着来推,比较方便查找:
在rtmp_stream.c中,有个connect_thread线程函数,该行数应该就是处理RTMP连接的,那么里面肯定就有关于推流地址的信息:
info("Connecting to RTMP URL %s...", stream->path.array);
RTMP_AddStream(&stream->rtmp, stream->key.array);
上面两个一个是推流的path,另外一个就是key了;
反过来,connect_thread是在rtmp_stream_start中被创建的,而rtmp_stream对象就是其参数void* data,那么,我们看看rtmp_stream_start是在哪里被调用的就可以找到前一步的代码:
.start = rtmp_stream_start
查找发现,这个函数是注册到了obs_output_info中了,那么我们就要找到这个结构在哪里被创建的,才好进行下一步的反推:
我们可以找到再SimpleOutput对象的构造函数中,会创建rtmp_output结构:
streamOutput = obs_output_create("rtmp_output", "simple_stream", nullptr, nullptr);
并且在obs_output_create中,会调用obs_context_data_insert(&output->context, &obs->data.outputs_mutex, &obs->data.first_output);从而将output_info结构存入first_output的链表中。
那到底start在哪里被调用呢?搜一下吧,搜索start,可以发现obs_output_actual_start中,有调用success = output->info.start(output->context.data);,这很可能就是start的调用地方;
搜下这个函数在哪里调用:obs_output_start这里有调用,而obs_output_start在SimpleOutput的startStreaming中会被调用:
而on_streamButton_clicked中会调用startStreaming,这下总算有点眉目了,反推到最上层的按钮点击事件了;
现在我们再从按钮往前推看看,这样可以理清下流程:
在on_streamButton_clicked中对推流的抽象,主要是抽象为outputHandler(而SimpleOutput就是outputHandler的一个实现);
然后调用outputHandler的StartStreaming,而StartStreaming主要操作的是outputHandler的streamOutput成员,它会调用obs_output_start(streamOutput);
我们看看streamOutput到底是什么?是rtmp_stream吗?
它是一个OBSOutput结构:using OBSOutput = OBSRef<obs_output_t*, obs_output_addref, obs_output_release>;
好吧,我们来分析下怎么办,看来主要还是obs_output_t结构要先了解下,可能obs_output_t中存在对rtmp_stream的封装吧;
回头我们又发现streamOutput是在SimpleOutput中被创建的:streamOutput = obs_output_create("rtmp_output", "simple_stream",nullptr, nullptr);
在obs_output_create中一上来,就先调用const struct obs_output_info *info = find_output(id);就先调用查找注册的obs_output_info,查找注册的obs_output_info;
在obs_output_create中有这么一句if (info) output->context.data = info->create(output->context.settings, output);我们总是发现上面的start调用参数创建的地方啦!并且创建该结构时,会根据output->context.setting和output来,我们分析下这两个里面是否包含推流地址的信息!
额,不好意思,发现找了半天,还是没找到rtmp_stream->path和rtmp_stream->key在哪里设置的;
刚脆搜下stream->key吧,发现了下面几句:
dstr_copy(&stream->path, obs_service_get_url(service));
dstr_copy(&stream->key, obs_service_get_key(service));
dstr_copy(&stream->username, obs_service_get_username(service));
dstr_copy(&stream->password, obs_service_get_password(service));
哈哈,总算找到这里了,不过前面的分析还是对理解obs_output_t和obs_output_info有些作用;
这几句是在init_connect被调用的,而init_connect是在rtmp-stream.c的connect_thread中被调用的;
好了,找来找去,最后还是回到了rtmp-stream.c中,那么我们看看obs_service_get_url这个是怎么获取到path和key的呢?
来看看service是怎么回事:
通过service = obs_output_get_service(stream->output);,init_connect中获取到service结构,我们主要还是要看下service是怎么被挂到obs_output_t上的;
看看obs_service的定义:
struct obs_service {
struct obs_context_data context;
struct obs_service_info info;
struct obs_weak_service *control;
/* indicates ownership of the info.id buffer */
bool owns_info_id;
bool active;
bool destroy;
struct obs_output *output;
};
它已经包含了obs_output的信息;
我们搜索下output->service,发现在obs_output_set_service会设置obs_output_t的service变量,而obs_output_set_service是在SimpleOutput的StartStreaming中被调用的,而StartStreaming的参数就是obs_service_t *service,这个参数就是class OBSBasic : public OBSMainWindow的成员变量;
好吧,在OBSBasic的LoadService中,会创建service = obs_service_create(type, "default_service", settings,hotkey_data);
还有SaveStream1Settings中也会创建service,而且会调用
main->SetService(newService);
main->SaveService();
替换service变量;
在obs_service_create中,会调用service->context.data = service->info.create(service->context.settings, service);对service进行初始化,
而该函数目前了解到,会调用到rtmp_common_service中的create,而在rtmp_common_create中,会调用
rtmp_common_update(data, settings);对设置进行初始化,
service->service = bstrdup(obs_data_get_string(settings, "service"));
service->server = bstrdup(obs_data_get_string(settings, "server"));
service->key = bstrdup(obs_data_get_string(settings, "key"));
server就是path变量,key就是key变量;
现在,再看下:
struct obs_service_info rtmp_common_service = {
.id = "rtmp_common",
.get_name = rtmp_common_getname,
.create = rtmp_common_create,
.destroy = rtmp_common_destroy,
.update = rtmp_common_update,
.get_properties = rtmp_common_properties,
.get_url = rtmp_common_url,
.get_key = rtmp_common_key,
.apply_encoder_settings = rtmp_common_apply_settings,
};
发现了吧?原来obs_service_get_url这些函数最终都是调用这里的rtmp_common_url函数,而这些函数最终返回的就是上面设置的变量;
好了,来总结下吧:
1、首先在系统加载时,OBSBasicSetting会调用LoadService加载配置文件中的json,然后根据json配置的rtmp_common,setting,hotkeys等内容,创建default_service,我们看看配置文件长啥样吧:
{
"settings": {
"key": "9v0XqR?record=true&filename=10454_9v0XqR",
"server": "rtmp://push.jingchangkan.tv/jingchangkan/"
},
"type": "rtmp_custom"
}
ok,下面看代码:
obs_data_set_default_string(data, "type", "rtmp_common");
type = obs_data_get_string(data, "type");
obs_data_t *settings = obs_data_get_obj(data, "settings");
obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys");
service = obs_service_create(type, "default_service", settings, hotkey_data);
这就是了,type是一个string,并且是rtmp_custom,settings是一个obj,然后传入obs_service_create创建服务;
当然还有在SaveStream1Settings也会根据界面的配置创建服务,并且保存到json配置文件中;
2、执行info->create,记录setting中的设置,后面通过obs_service_get_url等可以获取到;
3、这里load的service会记录到OBSBasic的成员变量service中,整个工程中只有一个service在这里记录,SaveStream1Settings也会记录在service中;
4、点击“开始串流“时,触发on_streamButton_clicked(outputHandler在CreateSimpleOutputHandler中被创建outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this));)这里就不赘述了;
5、创建SimpleOutput对象的构造函数中,创建streamOutput,而且该output的id是rtmp_output;
6、在on_streamButton_clicked中调用outputHandler->StartStreaming(service)
7、 obs_output_set_service(streamOutput, service);设置好流的服务信息描述(也就是上面创建的service,从service中可以获得推流地址);
8、调用obs_output_start-->obs_output_actual_start,而output->context.data就是rtmp_stream结构,rtmp_stream_create返回的。
9、调用success = output->info.start(output->context.data); out->context.data在创建的时候就被赋值为一个创建的rtmp_stream变量,而rtmp_stream变量中有回指到output的指针;
10、调用info.start就是创建connect_thread线程;
11、connect_thread线程先调用init_connect,通过output找到service,然后初始化rtmp_stream的推流地址信息;
12、connect_thread线程调用try_connect,就开始调用librtmp库进行推流连接初始化操作啦!
至此,我们了解了OBS整个设置推流地址的流程,中间过程非常绕,但是可以知道,OBS进行了一些分层和分模块:
rtmp_output_info(obs_output_info)<--rtmp_common.c(obs_output_t)
rtmp_common_service(obs_service_info)<--obs_service