rtmp-common.c 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180
  1. #include <util/platform.h>
  2. #include <util/dstr.h>
  3. #include <obs-module.h>
  4. #include <jansson.h>
  5. #include <obs-config.h>
  6. #include "rtmp-format-ver.h"
  7. #include "service-specific/twitch.h"
  8. #include "service-specific/nimotv.h"
  9. #include "service-specific/showroom.h"
  10. #include "service-specific/dacast.h"
  11. #include "service-specific/amazon-ivs.h"
  12. struct rtmp_common {
  13. char *service;
  14. char *protocol;
  15. char *server;
  16. char *key;
  17. struct obs_service_resolution *supported_resolutions;
  18. size_t supported_resolutions_count;
  19. int max_fps;
  20. char **video_codecs;
  21. char **audio_codecs;
  22. bool supports_additional_audio_track;
  23. };
  24. static const char *rtmp_common_getname(void *unused)
  25. {
  26. UNUSED_PARAMETER(unused);
  27. return obs_module_text("StreamingServices");
  28. }
  29. static json_t *open_services_file(void);
  30. static inline json_t *find_service(json_t *root, const char *name, const char **p_new_name);
  31. static inline bool get_bool_val(json_t *service, const char *key);
  32. static inline const char *get_string_val(json_t *service, const char *key);
  33. static inline int get_int_val(json_t *service, const char *key);
  34. extern void twitch_ingests_refresh(int seconds);
  35. extern void amazon_ivs_ingests_refresh(int seconds);
  36. static void ensure_valid_url(struct rtmp_common *service, json_t *json, obs_data_t *settings)
  37. {
  38. json_t *servers = json_object_get(json, "servers");
  39. const char *top_url = NULL;
  40. json_t *server;
  41. size_t index;
  42. if (!service->server || !servers || !json_is_array(servers))
  43. return;
  44. if (astrstri(service->service, "Facebook") == NULL)
  45. return;
  46. json_array_foreach (servers, index, server) {
  47. const char *url = get_string_val(server, "url");
  48. if (!url)
  49. continue;
  50. if (!top_url)
  51. top_url = url;
  52. if (astrcmpi(service->server, url) == 0)
  53. return;
  54. }
  55. /* server was not found in server list, use first server instead */
  56. if (top_url) {
  57. bfree(service->server);
  58. service->server = bstrdup(top_url);
  59. obs_data_set_string(settings, "server", top_url);
  60. }
  61. }
  62. static void update_recommendations(struct rtmp_common *service, json_t *rec)
  63. {
  64. json_t *sr = json_object_get(rec, "supported resolutions");
  65. if (sr && json_is_array(sr)) {
  66. DARRAY(struct obs_service_resolution) res_list;
  67. json_t *res_obj;
  68. size_t index;
  69. da_init(res_list);
  70. json_array_foreach (sr, index, res_obj) {
  71. if (!json_is_string(res_obj))
  72. continue;
  73. const char *res_str = json_string_value(res_obj);
  74. struct obs_service_resolution res;
  75. if (sscanf(res_str, "%dx%d", &res.cx, &res.cy) != 2)
  76. continue;
  77. if (res.cx <= 0 || res.cy <= 0)
  78. continue;
  79. da_push_back(res_list, &res);
  80. }
  81. if (res_list.num) {
  82. service->supported_resolutions = res_list.array;
  83. service->supported_resolutions_count = res_list.num;
  84. }
  85. }
  86. service->max_fps = get_int_val(rec, "max fps");
  87. }
  88. #define RTMP_PREFIX "rtmp://"
  89. #define RTMPS_PREFIX "rtmps://"
  90. static const char *get_protocol(json_t *service, obs_data_t *settings)
  91. {
  92. const char *protocol = get_string_val(service, "protocol");
  93. if (protocol) {
  94. return protocol;
  95. }
  96. json_t *servers = json_object_get(service, "servers");
  97. if (!json_is_array(servers))
  98. return "RTMP";
  99. json_t *server = json_array_get(servers, 0);
  100. const char *url = get_string_val(server, "url");
  101. if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) {
  102. obs_data_set_string(settings, "protocol", "RTMPS");
  103. return "RTMPS";
  104. }
  105. return "RTMP";
  106. }
  107. static void copy_info_to_settings(json_t *service, obs_data_t *settings);
  108. static void rtmp_common_update(void *data, obs_data_t *settings)
  109. {
  110. struct rtmp_common *service = data;
  111. bfree(service->supported_resolutions);
  112. if (service->video_codecs)
  113. bfree(service->video_codecs);
  114. if (service->audio_codecs)
  115. bfree(service->audio_codecs);
  116. bfree(service->service);
  117. bfree(service->protocol);
  118. bfree(service->server);
  119. bfree(service->key);
  120. service->service = bstrdup(obs_data_get_string(settings, "service"));
  121. service->protocol = bstrdup(obs_data_get_string(settings, "protocol"));
  122. service->server = bstrdup(obs_data_get_string(settings, "server"));
  123. service->key = bstrdup(obs_data_get_string(settings, "key"));
  124. service->supports_additional_audio_track = false;
  125. service->video_codecs = NULL;
  126. service->audio_codecs = NULL;
  127. service->supported_resolutions = NULL;
  128. service->supported_resolutions_count = 0;
  129. service->max_fps = 0;
  130. json_t *root = open_services_file();
  131. if (root) {
  132. const char *new_name;
  133. json_t *serv = find_service(root, service->service, &new_name);
  134. if (new_name) {
  135. bfree(service->service);
  136. service->service = bstrdup(new_name);
  137. }
  138. if ((service->protocol == NULL || service->protocol[0] == '\0')) {
  139. bfree(service->protocol);
  140. service->protocol = bstrdup(get_protocol(serv, settings));
  141. }
  142. if (serv) {
  143. copy_info_to_settings(serv, settings);
  144. json_t *rec = json_object_get(serv, "recommended");
  145. if (json_is_object(rec)) {
  146. update_recommendations(service, rec);
  147. }
  148. service->supports_additional_audio_track =
  149. get_bool_val(serv, "supports_additional_audio_track");
  150. ensure_valid_url(service, serv, settings);
  151. }
  152. }
  153. json_decref(root);
  154. }
  155. static void rtmp_common_destroy(void *data)
  156. {
  157. struct rtmp_common *service = data;
  158. bfree(service->supported_resolutions);
  159. if (service->video_codecs)
  160. bfree(service->video_codecs);
  161. if (service->audio_codecs)
  162. bfree(service->audio_codecs);
  163. bfree(service->service);
  164. bfree(service->protocol);
  165. bfree(service->server);
  166. bfree(service->key);
  167. bfree(service);
  168. }
  169. static void *rtmp_common_create(obs_data_t *settings, obs_service_t *service)
  170. {
  171. struct rtmp_common *data = bzalloc(sizeof(struct rtmp_common));
  172. rtmp_common_update(data, settings);
  173. UNUSED_PARAMETER(service);
  174. return data;
  175. }
  176. static inline const char *get_string_val(json_t *service, const char *key)
  177. {
  178. json_t *str_val = json_object_get(service, key);
  179. if (!str_val || !json_is_string(str_val))
  180. return NULL;
  181. return json_string_value(str_val);
  182. }
  183. static inline int get_int_val(json_t *service, const char *key)
  184. {
  185. json_t *integer_val = json_object_get(service, key);
  186. if (!integer_val || !json_is_integer(integer_val))
  187. return 0;
  188. return (int)json_integer_value(integer_val);
  189. }
  190. static inline bool get_bool_val(json_t *service, const char *key)
  191. {
  192. json_t *bool_val = json_object_get(service, key);
  193. if (!bool_val || !json_is_boolean(bool_val))
  194. return false;
  195. return json_is_true(bool_val);
  196. }
  197. static bool is_protocol_available(json_t *service)
  198. {
  199. const char *protocol = get_string_val(service, "protocol");
  200. if (protocol)
  201. return obs_is_output_protocol_registered(protocol);
  202. /* Test RTMP and RTMPS if no protocol found */
  203. json_t *servers;
  204. size_t index;
  205. json_t *server;
  206. const char *url;
  207. bool ret = false;
  208. servers = json_object_get(service, "servers");
  209. json_array_foreach (servers, index, server) {
  210. url = get_string_val(server, "url");
  211. if (strncmp(url, RTMP_PREFIX, strlen(RTMP_PREFIX)) == 0)
  212. ret |= obs_is_output_protocol_registered("RTMP");
  213. else if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0)
  214. ret |= obs_is_output_protocol_registered("RTMPS");
  215. }
  216. return ret;
  217. }
  218. static void add_service(obs_property_t *list, json_t *service, bool show_all, const char *cur_service)
  219. {
  220. json_t *servers;
  221. const char *name;
  222. bool common;
  223. if (!json_is_object(service)) {
  224. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  225. "is not an object");
  226. return;
  227. }
  228. name = get_string_val(service, "name");
  229. if (!name) {
  230. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  231. "has no name");
  232. return;
  233. }
  234. common = get_bool_val(service, "common");
  235. if (!show_all && !common && strcmp(cur_service, name) != 0) {
  236. return;
  237. }
  238. servers = json_object_get(service, "servers");
  239. if (!servers || !json_is_array(servers)) {
  240. blog(LOG_WARNING,
  241. "rtmp-common.c: [add_service] service "
  242. "'%s' has no servers",
  243. name);
  244. return;
  245. }
  246. obs_property_list_add_string(list, name, name);
  247. }
  248. static void add_services(obs_property_t *list, json_t *root, bool show_all, const char *cur_service)
  249. {
  250. json_t *service;
  251. size_t index;
  252. if (!json_is_array(root)) {
  253. blog(LOG_WARNING, "rtmp-common.c: [add_services] JSON file "
  254. "root is not an array");
  255. return;
  256. }
  257. json_array_foreach (root, index, service) {
  258. /* Skip service with non-available protocol */
  259. if (!is_protocol_available(service))
  260. continue;
  261. add_service(list, service, show_all, cur_service);
  262. }
  263. service = find_service(root, cur_service, NULL);
  264. if (!service && cur_service && *cur_service) {
  265. obs_property_list_insert_string(list, 0, cur_service, cur_service);
  266. obs_property_list_item_disable(list, 0, true);
  267. }
  268. }
  269. static json_t *open_json_file(const char *file)
  270. {
  271. char *file_data = os_quick_read_utf8_file(file);
  272. json_error_t error;
  273. json_t *root;
  274. json_t *list;
  275. int format_ver;
  276. if (!file_data)
  277. return NULL;
  278. root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
  279. bfree(file_data);
  280. if (!root) {
  281. blog(LOG_WARNING,
  282. "rtmp-common.c: [open_json_file] "
  283. "Error reading JSON file (%d): %s",
  284. error.line, error.text);
  285. return NULL;
  286. }
  287. format_ver = get_int_val(root, "format_version");
  288. if (format_ver != RTMP_SERVICES_FORMAT_VERSION) {
  289. blog(LOG_DEBUG,
  290. "rtmp-common.c: [open_json_file] "
  291. "Wrong format version (%d), expected %d",
  292. format_ver, RTMP_SERVICES_FORMAT_VERSION);
  293. json_decref(root);
  294. return NULL;
  295. }
  296. list = json_object_get(root, "services");
  297. if (list)
  298. json_incref(list);
  299. json_decref(root);
  300. if (!list) {
  301. blog(LOG_WARNING, "rtmp-common.c: [open_json_file] "
  302. "No services list");
  303. return NULL;
  304. }
  305. return list;
  306. }
  307. static json_t *open_services_file(void)
  308. {
  309. char *file;
  310. json_t *root = NULL;
  311. file = obs_module_config_path("services.json");
  312. if (file) {
  313. root = open_json_file(file);
  314. bfree(file);
  315. }
  316. if (!root) {
  317. file = obs_module_file("services.json");
  318. if (file) {
  319. root = open_json_file(file);
  320. bfree(file);
  321. }
  322. }
  323. return root;
  324. }
  325. static void build_service_list(obs_property_t *list, json_t *root, bool show_all, const char *cur_service)
  326. {
  327. obs_property_list_clear(list);
  328. add_services(list, root, show_all, cur_service);
  329. }
  330. static void properties_data_destroy(void *data)
  331. {
  332. json_t *root = data;
  333. if (root)
  334. json_decref(root);
  335. }
  336. static bool fill_twitch_servers_locked(obs_property_t *servers_prop)
  337. {
  338. size_t count = twitch_ingest_count();
  339. obs_property_list_add_string(servers_prop, obs_module_text("Server.Auto"), "auto");
  340. if (count <= 1)
  341. return false;
  342. for (size_t i = 0; i < count; i++) {
  343. struct ingest twitch_ing = twitch_ingest(i);
  344. obs_property_list_add_string(servers_prop, twitch_ing.name, twitch_ing.url);
  345. }
  346. return true;
  347. }
  348. static inline bool fill_twitch_servers(obs_property_t *servers_prop)
  349. {
  350. bool success;
  351. twitch_ingests_lock();
  352. success = fill_twitch_servers_locked(servers_prop);
  353. twitch_ingests_unlock();
  354. return success;
  355. }
  356. static bool fill_amazon_ivs_servers_locked(obs_property_t *servers_prop)
  357. {
  358. struct dstr name_buffer = {0};
  359. size_t count = amazon_ivs_ingest_count();
  360. bool rtmps_available = obs_is_output_protocol_registered("RTMPS");
  361. if (rtmps_available) {
  362. obs_property_list_add_string(servers_prop, obs_module_text("Server.AutoRTMPS"), "auto-rtmps");
  363. }
  364. obs_property_list_add_string(servers_prop, obs_module_text("Server.AutoRTMP"), "auto-rtmp");
  365. if (count <= 1)
  366. return false;
  367. if (rtmps_available) {
  368. for (size_t i = 0; i < count; i++) {
  369. struct ingest amazon_ivs_ing = amazon_ivs_ingest(i);
  370. dstr_printf(&name_buffer, "%s (RTMPS)", amazon_ivs_ing.name);
  371. obs_property_list_add_string(servers_prop, name_buffer.array, amazon_ivs_ing.rtmps_url);
  372. }
  373. }
  374. for (size_t i = 0; i < count; i++) {
  375. struct ingest amazon_ivs_ing = amazon_ivs_ingest(i);
  376. dstr_printf(&name_buffer, "%s (RTMP)", amazon_ivs_ing.name);
  377. obs_property_list_add_string(servers_prop, name_buffer.array, amazon_ivs_ing.url);
  378. }
  379. dstr_free(&name_buffer);
  380. return true;
  381. }
  382. static inline bool fill_amazon_ivs_servers(obs_property_t *servers_prop)
  383. {
  384. bool success;
  385. amazon_ivs_ingests_lock();
  386. success = fill_amazon_ivs_servers_locked(servers_prop);
  387. amazon_ivs_ingests_unlock();
  388. return success;
  389. }
  390. static void fill_servers(obs_property_t *servers_prop, json_t *service, const char *name)
  391. {
  392. json_t *servers, *server;
  393. size_t index;
  394. obs_property_list_clear(servers_prop);
  395. servers = json_object_get(service, "servers");
  396. if (!json_is_array(servers)) {
  397. blog(LOG_WARNING,
  398. "rtmp-common.c: [fill_servers] "
  399. "Servers for service '%s' not a valid object",
  400. name);
  401. return;
  402. }
  403. /* Assumption: Twitch should be RTMP only, so no RTMPS check */
  404. if (strcmp(name, "Twitch") == 0) {
  405. if (fill_twitch_servers(servers_prop))
  406. return;
  407. }
  408. /* Assumption: Nimo TV should be RTMP only, so no RTMPS check in the ingest */
  409. if (strcmp(name, "Nimo TV") == 0) {
  410. obs_property_list_add_string(servers_prop, obs_module_text("Server.Auto"), "auto");
  411. }
  412. if (strcmp(name, "Amazon IVS") == 0) {
  413. if (fill_amazon_ivs_servers(servers_prop))
  414. return;
  415. }
  416. json_array_foreach (servers, index, server) {
  417. const char *server_name = get_string_val(server, "name");
  418. const char *url = get_string_val(server, "url");
  419. if (!server_name || !url)
  420. continue;
  421. /* Skip RTMPS server if protocol not registered */
  422. if ((!obs_is_output_protocol_registered("RTMPS")) && (strncmp(url, "rtmps://", 8) == 0))
  423. continue;
  424. obs_property_list_add_string(servers_prop, server_name, url);
  425. }
  426. }
  427. static void copy_string_from_json_if_available(json_t *service, obs_data_t *settings, const char *name)
  428. {
  429. const char *string = get_string_val(service, name);
  430. if (string)
  431. obs_data_set_string(settings, name, string);
  432. }
  433. static void fill_more_info_link(json_t *service, obs_data_t *settings)
  434. {
  435. copy_string_from_json_if_available(service, settings, "more_info_link");
  436. }
  437. static void fill_stream_key_link(json_t *service, obs_data_t *settings)
  438. {
  439. copy_string_from_json_if_available(service, settings, "stream_key_link");
  440. }
  441. static void update_protocol(json_t *service, obs_data_t *settings)
  442. {
  443. const char *protocol = get_string_val(service, "protocol");
  444. if (protocol) {
  445. obs_data_set_string(settings, "protocol", protocol);
  446. return;
  447. }
  448. json_t *servers = json_object_get(service, "servers");
  449. if (!json_is_array(servers))
  450. return;
  451. json_t *server = json_array_get(servers, 0);
  452. const char *url = get_string_val(server, "url");
  453. if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) {
  454. obs_data_set_string(settings, "protocol", "RTMPS");
  455. return;
  456. }
  457. obs_data_set_string(settings, "protocol", "RTMP");
  458. }
  459. static void copy_info_to_settings(json_t *service, obs_data_t *settings)
  460. {
  461. const char *name = obs_data_get_string(settings, "service");
  462. fill_more_info_link(service, settings);
  463. fill_stream_key_link(service, settings);
  464. copy_string_from_json_if_available(service, settings, "multitrack_video_configuration_url");
  465. copy_string_from_json_if_available(service, settings, "multitrack_video_name");
  466. if (!obs_data_has_user_value(settings, "multitrack_video_name")) {
  467. obs_data_set_string(settings, "multitrack_video_name", "Multitrack Video");
  468. }
  469. const char *learn_more_link_url = get_string_val(service, "multitrack_video_learn_more_link");
  470. struct dstr learn_more_link = {0};
  471. if (learn_more_link_url) {
  472. dstr_init_copy(&learn_more_link, obs_module_text("MultitrackVideo.LearnMoreLink"));
  473. dstr_replace(&learn_more_link, "%1", learn_more_link_url);
  474. }
  475. struct dstr str;
  476. dstr_init_copy(&str, obs_module_text("MultitrackVideo.Disclaimer"));
  477. dstr_replace(&str, "%1", obs_data_get_string(settings, "multitrack_video_name"));
  478. dstr_replace(&str, "%2", name);
  479. if (learn_more_link.array) {
  480. dstr_cat(&str, learn_more_link.array);
  481. }
  482. obs_data_set_string(settings, "multitrack_video_disclaimer", str.array);
  483. dstr_free(&learn_more_link);
  484. dstr_free(&str);
  485. update_protocol(service, settings);
  486. }
  487. static inline json_t *find_service(json_t *root, const char *name, const char **p_new_name)
  488. {
  489. size_t index;
  490. json_t *service;
  491. if (p_new_name)
  492. *p_new_name = NULL;
  493. json_array_foreach (root, index, service) {
  494. /* skip service with non-available protocol */
  495. if (!is_protocol_available(service))
  496. continue;
  497. const char *cur_name = get_string_val(service, "name");
  498. if (strcmp(name, cur_name) == 0)
  499. return service;
  500. /* check for alternate names */
  501. json_t *alt_names = json_object_get(service, "alt_names");
  502. size_t alt_name_idx;
  503. json_t *alt_name_obj;
  504. json_array_foreach (alt_names, alt_name_idx, alt_name_obj) {
  505. const char *alt_name = json_string_value(alt_name_obj);
  506. if (alt_name && strcmp(name, alt_name) == 0) {
  507. if (p_new_name)
  508. *p_new_name = cur_name;
  509. return service;
  510. }
  511. }
  512. }
  513. return NULL;
  514. }
  515. static bool service_selected(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  516. {
  517. const char *name = obs_data_get_string(settings, "service");
  518. json_t *root = obs_properties_get_param(props);
  519. json_t *service;
  520. const char *new_name;
  521. if (!name || !*name)
  522. return false;
  523. service = find_service(root, name, &new_name);
  524. if (!service) {
  525. const char *server = obs_data_get_string(settings, "server");
  526. obs_property_list_insert_string(p, 0, name, name);
  527. obs_property_list_item_disable(p, 0, true);
  528. p = obs_properties_get(props, "server");
  529. obs_property_list_insert_string(p, 0, server, server);
  530. obs_property_list_item_disable(p, 0, true);
  531. return true;
  532. }
  533. if (new_name) {
  534. name = new_name;
  535. obs_data_set_string(settings, "service", name);
  536. }
  537. fill_servers(obs_properties_get(props, "server"), service, name);
  538. copy_info_to_settings(service, settings);
  539. return true;
  540. }
  541. static bool show_all_services_toggled(obs_properties_t *ppts, obs_property_t *p, obs_data_t *settings)
  542. {
  543. const char *cur_service = obs_data_get_string(settings, "service");
  544. bool show_all = obs_data_get_bool(settings, "show_all");
  545. json_t *root = obs_properties_get_param(ppts);
  546. if (!root)
  547. return false;
  548. build_service_list(obs_properties_get(ppts, "service"), root, show_all, cur_service);
  549. UNUSED_PARAMETER(p);
  550. return true;
  551. }
  552. static obs_properties_t *rtmp_common_properties(void *unused)
  553. {
  554. UNUSED_PARAMETER(unused);
  555. obs_properties_t *ppts = obs_properties_create();
  556. obs_property_t *p;
  557. json_t *root;
  558. root = open_services_file();
  559. if (root)
  560. obs_properties_set_param(ppts, root, properties_data_destroy);
  561. p = obs_properties_add_list(ppts, "service", obs_module_text("Service"), OBS_COMBO_TYPE_LIST,
  562. OBS_COMBO_FORMAT_STRING);
  563. obs_property_set_modified_callback(p, service_selected);
  564. p = obs_properties_add_bool(ppts, "show_all", obs_module_text("ShowAll"));
  565. obs_property_set_modified_callback(p, show_all_services_toggled);
  566. obs_properties_add_list(ppts, "server", obs_module_text("Server"), OBS_COMBO_TYPE_LIST,
  567. OBS_COMBO_FORMAT_STRING);
  568. obs_properties_add_text(ppts, "key", obs_module_text("StreamKey"), OBS_TEXT_PASSWORD);
  569. return ppts;
  570. }
  571. static int get_bitrate_matrix_max(json_t *array);
  572. static void apply_video_encoder_settings(obs_data_t *settings, json_t *recommended)
  573. {
  574. json_t *item = json_object_get(recommended, "keyint");
  575. if (json_is_integer(item)) {
  576. int keyint = (int)json_integer_value(item);
  577. obs_data_set_int(settings, "keyint_sec", keyint);
  578. }
  579. obs_data_set_string(settings, "rate_control", "CBR");
  580. item = json_object_get(recommended, "profile");
  581. obs_data_item_t *enc_item = obs_data_item_byname(settings, "profile");
  582. if (json_is_string(item) && obs_data_item_gettype(enc_item) == OBS_DATA_STRING) {
  583. const char *profile = json_string_value(item);
  584. obs_data_set_string(settings, "profile", profile);
  585. }
  586. obs_data_item_release(&enc_item);
  587. int max_bitrate = 0;
  588. item = json_object_get(recommended, "bitrate matrix");
  589. if (json_is_array(item)) {
  590. max_bitrate = get_bitrate_matrix_max(item);
  591. }
  592. item = json_object_get(recommended, "max video bitrate");
  593. if (!max_bitrate && json_is_integer(item)) {
  594. max_bitrate = (int)json_integer_value(item);
  595. }
  596. if (max_bitrate && obs_data_get_int(settings, "bitrate") > max_bitrate) {
  597. obs_data_set_int(settings, "bitrate", max_bitrate);
  598. obs_data_set_int(settings, "buffer_size", max_bitrate);
  599. }
  600. item = json_object_get(recommended, "bframes");
  601. if (json_is_integer(item)) {
  602. int bframes = (int)json_integer_value(item);
  603. obs_data_set_int(settings, "bf", bframes);
  604. }
  605. item = json_object_get(recommended, "x264opts");
  606. if (json_is_string(item)) {
  607. const char *x264_settings = json_string_value(item);
  608. const char *cur_settings = obs_data_get_string(settings, "x264opts");
  609. struct dstr opts;
  610. dstr_init_copy(&opts, cur_settings);
  611. if (!dstr_is_empty(&opts))
  612. dstr_cat(&opts, " ");
  613. dstr_cat(&opts, x264_settings);
  614. obs_data_set_string(settings, "x264opts", opts.array);
  615. dstr_free(&opts);
  616. }
  617. }
  618. static void apply_audio_encoder_settings(obs_data_t *settings, json_t *recommended)
  619. {
  620. json_t *item = json_object_get(recommended, "max audio bitrate");
  621. if (json_is_integer(item)) {
  622. int max_bitrate = (int)json_integer_value(item);
  623. if (obs_data_get_int(settings, "bitrate") > max_bitrate)
  624. obs_data_set_int(settings, "bitrate", max_bitrate);
  625. }
  626. }
  627. static void initialize_output(struct rtmp_common *service, json_t *root, obs_data_t *video_settings,
  628. obs_data_t *audio_settings)
  629. {
  630. json_t *json_service = find_service(root, service->service, NULL);
  631. json_t *recommended;
  632. if (!json_service) {
  633. if (service->service && *service->service)
  634. blog(LOG_WARNING,
  635. "rtmp-common.c: [initialize_output] "
  636. "Could not find service '%s'",
  637. service->service);
  638. return;
  639. }
  640. recommended = json_object_get(json_service, "recommended");
  641. if (!recommended)
  642. return;
  643. if (video_settings)
  644. apply_video_encoder_settings(video_settings, recommended);
  645. if (audio_settings)
  646. apply_audio_encoder_settings(audio_settings, recommended);
  647. }
  648. static void rtmp_common_apply_settings(void *data, obs_data_t *video_settings, obs_data_t *audio_settings)
  649. {
  650. struct rtmp_common *service = data;
  651. json_t *root = open_services_file();
  652. if (root) {
  653. initialize_output(service, root, video_settings, audio_settings);
  654. json_decref(root);
  655. }
  656. }
  657. static const char *rtmp_common_url(void *data)
  658. {
  659. struct rtmp_common *service = data;
  660. if (service->service && strcmp(service->service, "Twitch") == 0) {
  661. if (service->server && strcmp(service->server, "auto") == 0) {
  662. struct ingest twitch_ing;
  663. twitch_ingests_refresh(3);
  664. twitch_ingests_lock();
  665. twitch_ing = twitch_ingest(0);
  666. twitch_ingests_unlock();
  667. return twitch_ing.url;
  668. }
  669. }
  670. if (service->service && strcmp(service->service, "Amazon IVS") == 0) {
  671. if (service->server && strncmp(service->server, "auto", 4) == 0) {
  672. struct ingest amazon_ivs_ing;
  673. bool rtmp = strcmp(service->server, "auto-rtmp") == 0;
  674. amazon_ivs_ingests_refresh(3);
  675. amazon_ivs_ingests_lock();
  676. amazon_ivs_ing = amazon_ivs_ingest(0);
  677. amazon_ivs_ingests_unlock();
  678. return rtmp ? amazon_ivs_ing.url : amazon_ivs_ing.rtmps_url;
  679. }
  680. }
  681. if (service->service && strcmp(service->service, "Nimo TV") == 0) {
  682. if (service->server && strcmp(service->server, "auto") == 0) {
  683. return nimotv_get_ingest(service->key);
  684. }
  685. }
  686. if (service->service && strcmp(service->service, "SHOWROOM") == 0) {
  687. if (service->server && service->key) {
  688. struct showroom_ingest *ingest;
  689. ingest = showroom_get_ingest(service->server, service->key);
  690. return ingest->url;
  691. }
  692. }
  693. if (service->service && strcmp(service->service, "Dacast") == 0) {
  694. if (service->server && service->key) {
  695. dacast_ingests_load_data(service->server, service->key);
  696. struct dacast_ingest *ingest;
  697. ingest = dacast_ingest(service->key);
  698. return ingest->url;
  699. }
  700. }
  701. return service->server;
  702. }
  703. static const char *rtmp_common_key(void *data)
  704. {
  705. struct rtmp_common *service = data;
  706. if (service->service && strcmp(service->service, "SHOWROOM") == 0) {
  707. if (service->server && service->key) {
  708. struct showroom_ingest *ingest;
  709. ingest = showroom_get_ingest(service->server, service->key);
  710. return ingest->key;
  711. }
  712. }
  713. if (service->service && strcmp(service->service, "Dacast") == 0) {
  714. if (service->key) {
  715. struct dacast_ingest *ingest;
  716. ingest = dacast_ingest(service->key);
  717. return ingest->streamkey;
  718. }
  719. }
  720. return service->key;
  721. }
  722. static void rtmp_common_get_supported_resolutions(void *data, struct obs_service_resolution **resolutions,
  723. size_t *count)
  724. {
  725. struct rtmp_common *service = data;
  726. if (service->supported_resolutions_count) {
  727. *count = service->supported_resolutions_count;
  728. *resolutions = bmemdup(service->supported_resolutions, *count * sizeof(struct obs_service_resolution));
  729. } else {
  730. *count = 0;
  731. *resolutions = NULL;
  732. }
  733. }
  734. static void rtmp_common_get_max_fps(void *data, int *fps)
  735. {
  736. struct rtmp_common *service = data;
  737. *fps = service->max_fps;
  738. }
  739. static int get_bitrate_matrix_max(json_t *array)
  740. {
  741. size_t index;
  742. json_t *item;
  743. struct obs_video_info ovi;
  744. if (!obs_get_video_info(&ovi))
  745. return 0;
  746. double cur_fps = (double)ovi.fps_num / (double)ovi.fps_den;
  747. json_array_foreach (array, index, item) {
  748. if (!json_is_object(item))
  749. continue;
  750. const char *res = get_string_val(item, "res");
  751. double fps = (double)get_int_val(item, "fps") + 0.0000001;
  752. int bitrate = get_int_val(item, "max bitrate");
  753. if (!res)
  754. continue;
  755. int cx, cy;
  756. int c = sscanf(res, "%dx%d", &cx, &cy);
  757. if (c != 2)
  758. continue;
  759. if ((int)ovi.output_width == cx && (int)ovi.output_height == cy && cur_fps <= fps)
  760. return bitrate;
  761. }
  762. return 0;
  763. }
  764. static void rtmp_common_get_max_bitrate(void *data, int *video_bitrate, int *audio_bitrate)
  765. {
  766. struct rtmp_common *service = data;
  767. json_t *root = open_services_file();
  768. json_t *item;
  769. if (!root)
  770. return;
  771. json_t *json_service = find_service(root, service->service, NULL);
  772. if (!json_service) {
  773. goto fail;
  774. }
  775. json_t *recommended = json_object_get(json_service, "recommended");
  776. if (!recommended) {
  777. goto fail;
  778. }
  779. if (audio_bitrate) {
  780. item = json_object_get(recommended, "max audio bitrate");
  781. if (json_is_integer(item))
  782. *audio_bitrate = (int)json_integer_value(item);
  783. }
  784. if (video_bitrate) {
  785. int bitrate = 0;
  786. item = json_object_get(recommended, "bitrate matrix");
  787. if (json_is_array(item)) {
  788. bitrate = get_bitrate_matrix_max(item);
  789. }
  790. if (!bitrate) {
  791. item = json_object_get(recommended, "max video bitrate");
  792. if (json_is_integer(item))
  793. bitrate = (int)json_integer_value(item);
  794. }
  795. *video_bitrate = bitrate;
  796. }
  797. fail:
  798. json_decref(root);
  799. }
  800. static const char **rtmp_common_get_supported_video_codecs(void *data)
  801. {
  802. struct rtmp_common *service = data;
  803. if (service->video_codecs)
  804. return (const char **)service->video_codecs;
  805. struct dstr codecs = {0};
  806. json_t *root = open_services_file();
  807. if (!root)
  808. return NULL;
  809. json_t *json_service = find_service(root, service->service, NULL);
  810. if (!json_service) {
  811. goto fail;
  812. }
  813. json_t *json_video_codecs = json_object_get(json_service, "supported video codecs");
  814. if (!json_is_array(json_video_codecs)) {
  815. goto fail;
  816. }
  817. size_t index;
  818. json_t *item;
  819. json_array_foreach (json_video_codecs, index, item) {
  820. char codec[16];
  821. snprintf(codec, sizeof(codec), "%s", json_string_value(item));
  822. if (codecs.len)
  823. dstr_cat(&codecs, ";");
  824. dstr_cat(&codecs, codec);
  825. }
  826. service->video_codecs = strlist_split(codecs.array, ';', false);
  827. dstr_free(&codecs);
  828. fail:
  829. json_decref(root);
  830. return (const char **)service->video_codecs;
  831. }
  832. static const char **rtmp_common_get_supported_audio_codecs(void *data)
  833. {
  834. struct rtmp_common *service = data;
  835. if (service->audio_codecs)
  836. return (const char **)service->audio_codecs;
  837. struct dstr codecs = {0};
  838. json_t *root = open_services_file();
  839. if (!root)
  840. return NULL;
  841. json_t *json_service = find_service(root, service->service, NULL);
  842. if (!json_service) {
  843. goto fail;
  844. }
  845. json_t *json_audio_codecs = json_object_get(json_service, "supported audio codecs");
  846. if (!json_is_array(json_audio_codecs)) {
  847. goto fail;
  848. }
  849. size_t index;
  850. json_t *item;
  851. json_array_foreach (json_audio_codecs, index, item) {
  852. char codec[16];
  853. snprintf(codec, sizeof(codec), "%s", json_string_value(item));
  854. if (codecs.len)
  855. dstr_cat(&codecs, ";");
  856. dstr_cat(&codecs, codec);
  857. }
  858. service->audio_codecs = strlist_split(codecs.array, ';', false);
  859. dstr_free(&codecs);
  860. fail:
  861. json_decref(root);
  862. return (const char **)service->audio_codecs;
  863. }
  864. static const char *rtmp_common_username(void *data)
  865. {
  866. struct rtmp_common *service = data;
  867. if (service->service && strcmp(service->service, "Dacast") == 0) {
  868. if (service->key) {
  869. struct dacast_ingest *ingest;
  870. ingest = dacast_ingest(service->key);
  871. return ingest->username;
  872. }
  873. }
  874. return NULL;
  875. }
  876. static const char *rtmp_common_password(void *data)
  877. {
  878. struct rtmp_common *service = data;
  879. if (service->service && strcmp(service->service, "Dacast") == 0) {
  880. if (service->key) {
  881. struct dacast_ingest *ingest;
  882. ingest = dacast_ingest(service->key);
  883. return ingest->password;
  884. }
  885. }
  886. return NULL;
  887. }
  888. static const char *rtmp_common_get_protocol(void *data)
  889. {
  890. struct rtmp_common *service = data;
  891. return service->protocol ? service->protocol : "RTMP";
  892. }
  893. static const char *rtmp_common_get_connect_info(void *data, uint32_t type)
  894. {
  895. switch ((enum obs_service_connect_info)type) {
  896. case OBS_SERVICE_CONNECT_INFO_SERVER_URL:
  897. return rtmp_common_url(data);
  898. case OBS_SERVICE_CONNECT_INFO_STREAM_ID:
  899. return rtmp_common_key(data);
  900. case OBS_SERVICE_CONNECT_INFO_USERNAME:
  901. return rtmp_common_username(data);
  902. case OBS_SERVICE_CONNECT_INFO_PASSWORD:
  903. return rtmp_common_password(data);
  904. case OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE: {
  905. const char *protocol = rtmp_common_get_protocol(data);
  906. if ((strcmp(protocol, "SRT") == 0))
  907. return rtmp_common_password(data);
  908. else if ((strcmp(protocol, "RIST") == 0))
  909. return rtmp_common_key(data);
  910. break;
  911. }
  912. case OBS_SERVICE_CONNECT_INFO_BEARER_TOKEN:
  913. return NULL;
  914. }
  915. return NULL;
  916. }
  917. static bool rtmp_common_can_try_to_connect(void *data)
  918. {
  919. struct rtmp_common *service = data;
  920. const char *key = rtmp_common_key(data);
  921. if (service->service && strcmp(service->service, "Dacast") == 0)
  922. return (key != NULL && key[0] != '\0');
  923. const char *url = rtmp_common_url(data);
  924. return (url != NULL && url[0] != '\0') && (key != NULL && key[0] != '\0');
  925. }
  926. struct obs_service_info rtmp_common_service = {
  927. .id = "rtmp_common",
  928. .get_name = rtmp_common_getname,
  929. .create = rtmp_common_create,
  930. .destroy = rtmp_common_destroy,
  931. .update = rtmp_common_update,
  932. .get_properties = rtmp_common_properties,
  933. .get_protocol = rtmp_common_get_protocol,
  934. .get_url = rtmp_common_url,
  935. .get_key = rtmp_common_key,
  936. .get_username = rtmp_common_username,
  937. .get_password = rtmp_common_password,
  938. .get_connect_info = rtmp_common_get_connect_info,
  939. .apply_encoder_settings = rtmp_common_apply_settings,
  940. .get_supported_resolutions = rtmp_common_get_supported_resolutions,
  941. .get_max_fps = rtmp_common_get_max_fps,
  942. .get_max_bitrate = rtmp_common_get_max_bitrate,
  943. .get_supported_video_codecs = rtmp_common_get_supported_video_codecs,
  944. .get_supported_audio_codecs = rtmp_common_get_supported_audio_codecs,
  945. .can_try_to_connect = rtmp_common_can_try_to_connect,
  946. };