rtmp-common.c 31 KB

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