rtmp-common.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #include <util/platform.h>
  2. #include <util/dstr.h>
  3. #include <obs-module.h>
  4. #include <jansson.h>
  5. #include "rtmp-format-ver.h"
  6. struct rtmp_common {
  7. char *service;
  8. char *server;
  9. char *key;
  10. };
  11. static const char *rtmp_common_getname(void *unused)
  12. {
  13. UNUSED_PARAMETER(unused);
  14. return obs_module_text("StreamingServices");
  15. }
  16. static void rtmp_common_update(void *data, obs_data_t *settings)
  17. {
  18. struct rtmp_common *service = data;
  19. bfree(service->service);
  20. bfree(service->server);
  21. bfree(service->key);
  22. service->service = bstrdup(obs_data_get_string(settings, "service"));
  23. service->server = bstrdup(obs_data_get_string(settings, "server"));
  24. service->key = bstrdup(obs_data_get_string(settings, "key"));
  25. }
  26. static void rtmp_common_destroy(void *data)
  27. {
  28. struct rtmp_common *service = data;
  29. bfree(service->service);
  30. bfree(service->server);
  31. bfree(service->key);
  32. bfree(service);
  33. }
  34. static void *rtmp_common_create(obs_data_t *settings, obs_service_t *service)
  35. {
  36. struct rtmp_common *data = bzalloc(sizeof(struct rtmp_common));
  37. rtmp_common_update(data, settings);
  38. UNUSED_PARAMETER(service);
  39. return data;
  40. }
  41. static inline const char *get_string_val(json_t *service, const char *key)
  42. {
  43. json_t *str_val = json_object_get(service, key);
  44. if (!str_val || !json_is_string(str_val))
  45. return NULL;
  46. return json_string_value(str_val);
  47. }
  48. static inline int get_int_val(json_t *service, const char *key)
  49. {
  50. json_t *integer_val = json_object_get(service, key);
  51. if (!integer_val || !json_is_integer(integer_val))
  52. return 0;
  53. return (int)json_integer_value(integer_val);
  54. }
  55. static inline bool get_bool_val(json_t *service, const char *key)
  56. {
  57. json_t *bool_val = json_object_get(service, key);
  58. if (!bool_val || !json_is_boolean(bool_val))
  59. return false;
  60. return json_is_true(bool_val);
  61. }
  62. static void add_service(obs_property_t *list, json_t *service, bool show_all,
  63. const char *cur_service)
  64. {
  65. json_t *servers;
  66. const char *name;
  67. bool common;
  68. if (!json_is_object(service)) {
  69. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  70. "is not an object");
  71. return;
  72. }
  73. name = get_string_val(service, "name");
  74. if (!name) {
  75. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  76. "has no name");
  77. return;
  78. }
  79. common = get_bool_val(service, "common");
  80. if (!show_all && !common && strcmp(cur_service, name) != 0) {
  81. return;
  82. }
  83. servers = json_object_get(service, "servers");
  84. if (!servers || !json_is_array(servers)) {
  85. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  86. "'%s' has no servers", name);
  87. return;
  88. }
  89. obs_property_list_add_string(list, name, name);
  90. }
  91. static void add_services(obs_property_t *list, json_t *root, bool show_all,
  92. const char *cur_service)
  93. {
  94. json_t *service;
  95. size_t index;
  96. if (!json_is_array(root)) {
  97. blog(LOG_WARNING, "rtmp-common.c: [add_services] JSON file "
  98. "root is not an array");
  99. return;
  100. }
  101. json_array_foreach (root, index, service) {
  102. add_service(list, service, show_all, cur_service);
  103. }
  104. }
  105. static json_t *open_json_file(const char *file)
  106. {
  107. char *file_data = os_quick_read_utf8_file(file);
  108. json_error_t error;
  109. json_t *root;
  110. json_t *list;
  111. int format_ver;
  112. if (!file_data)
  113. return NULL;
  114. root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
  115. bfree(file_data);
  116. if (!root) {
  117. blog(LOG_WARNING, "rtmp-common.c: [open_json_file] "
  118. "Error reading JSON file (%d): %s",
  119. error.line, error.text);
  120. return NULL;
  121. }
  122. format_ver = get_int_val(root, "format_version");
  123. if (format_ver != RTMP_SERVICES_FORMAT_VERSION) {
  124. blog(LOG_WARNING, "rtmp-common.c: [open_json_file] "
  125. "Wrong format version (%d), expected %d",
  126. format_ver, RTMP_SERVICES_FORMAT_VERSION);
  127. json_decref(root);
  128. return NULL;
  129. }
  130. list = json_object_get(root, "services");
  131. if (list)
  132. json_incref(list);
  133. json_decref(root);
  134. if (!list) {
  135. blog(LOG_WARNING, "rtmp-common.c: [open_json_file] "
  136. "No services list");
  137. return NULL;
  138. }
  139. return list;
  140. }
  141. static json_t *open_services_file(void)
  142. {
  143. char *file;
  144. json_t *root = NULL;
  145. file = obs_module_config_path("services.json");
  146. if (file) {
  147. root = open_json_file(file);
  148. bfree(file);
  149. }
  150. if (!root) {
  151. file = obs_module_file("services.json");
  152. if (file) {
  153. root = open_json_file(file);
  154. bfree(file);
  155. }
  156. }
  157. return root;
  158. }
  159. static void build_service_list(obs_property_t *list, json_t *root,
  160. bool show_all, const char *cur_service)
  161. {
  162. obs_property_list_clear(list);
  163. add_services(list, root, show_all, cur_service);
  164. }
  165. static void properties_data_destroy(void *data)
  166. {
  167. json_t *root = data;
  168. if (root)
  169. json_decref(root);
  170. }
  171. static void fill_servers(obs_property_t *servers_prop, json_t *service,
  172. const char *name)
  173. {
  174. json_t *servers, *server;
  175. size_t index;
  176. obs_property_list_clear(servers_prop);
  177. servers = json_object_get(service, "servers");
  178. if (!json_is_array(servers)) {
  179. blog(LOG_WARNING, "rtmp-common.c: [fill_servers] "
  180. "Servers for service '%s' not a valid object",
  181. name);
  182. return;
  183. }
  184. json_array_foreach (servers, index, server) {
  185. const char *server_name = get_string_val(server, "name");
  186. const char *url = get_string_val(server, "url");
  187. if (!server_name || !url)
  188. continue;
  189. obs_property_list_add_string(servers_prop, server_name, url);
  190. }
  191. }
  192. static inline json_t *find_service(json_t *root, const char *name)
  193. {
  194. size_t index;
  195. json_t *service;
  196. json_array_foreach (root, index, service) {
  197. const char *cur_name = get_string_val(service, "name");
  198. if (strcmp(name, cur_name) == 0)
  199. return service;
  200. }
  201. return NULL;
  202. }
  203. static bool service_selected(obs_properties_t *props, obs_property_t *p,
  204. obs_data_t *settings)
  205. {
  206. const char *name = obs_data_get_string(settings, "service");
  207. json_t *root = obs_properties_get_param(props);
  208. json_t *service;
  209. if (!name || !*name)
  210. return false;
  211. service = find_service(root, name);
  212. if (!service)
  213. return false;
  214. fill_servers(obs_properties_get(props, "server"), service, name);
  215. UNUSED_PARAMETER(p);
  216. return true;
  217. }
  218. static bool show_all_services_toggled(obs_properties_t *ppts,
  219. obs_property_t *p, obs_data_t *settings)
  220. {
  221. const char *cur_service = obs_data_get_string(settings, "service");
  222. bool show_all = obs_data_get_bool(settings, "show_all");
  223. json_t *root = obs_properties_get_param(ppts);
  224. if (!root)
  225. return false;
  226. build_service_list(obs_properties_get(ppts, "service"), root, show_all,
  227. cur_service);
  228. UNUSED_PARAMETER(p);
  229. return true;
  230. }
  231. static obs_properties_t *rtmp_common_properties(void *unused)
  232. {
  233. UNUSED_PARAMETER(unused);
  234. obs_properties_t *ppts = obs_properties_create();
  235. obs_property_t *p;
  236. json_t *root;
  237. root = open_services_file();
  238. if (root)
  239. obs_properties_set_param(ppts, root, properties_data_destroy);
  240. p = obs_properties_add_list(ppts, "service",
  241. obs_module_text("Service"),
  242. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  243. obs_property_set_modified_callback(p, service_selected);
  244. p = obs_properties_add_bool(ppts, "show_all",
  245. obs_module_text("ShowAll"));
  246. obs_property_set_modified_callback(p, show_all_services_toggled);
  247. obs_properties_add_list(ppts, "server", obs_module_text("Server"),
  248. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  249. obs_properties_add_text(ppts, "key", obs_module_text("StreamKey"),
  250. OBS_TEXT_PASSWORD);
  251. return ppts;
  252. }
  253. static void apply_video_encoder_settings(obs_data_t *settings,
  254. json_t *recommended)
  255. {
  256. json_t *item = json_object_get(recommended, "keyint");
  257. if (item && json_is_integer(item)) {
  258. int keyint = (int)json_integer_value(item);
  259. obs_data_set_int(settings, "keyint_sec", keyint);
  260. }
  261. obs_data_set_string(settings, "rate_control", "CBR");
  262. item = json_object_get(recommended, "profile");
  263. if (item && json_is_string(item)) {
  264. const char *profile = json_string_value(item);
  265. obs_data_set_string(settings, "profile", profile);
  266. }
  267. item = json_object_get(recommended, "max video bitrate");
  268. if (item && json_is_integer(item)) {
  269. int max_bitrate = (int)json_integer_value(item);
  270. if (obs_data_get_int(settings, "bitrate") > max_bitrate) {
  271. obs_data_set_int(settings, "bitrate", max_bitrate);
  272. obs_data_set_int(settings, "buffer_size", max_bitrate);
  273. }
  274. }
  275. item = json_object_get(recommended, "x264opts");
  276. if (item && json_is_string(item)) {
  277. const char *x264_settings = json_string_value(item);
  278. const char *cur_settings =
  279. obs_data_get_string(settings, "x264opts");
  280. struct dstr opts;
  281. dstr_init_copy(&opts, cur_settings);
  282. if (!dstr_is_empty(&opts))
  283. dstr_cat(&opts, " ");
  284. dstr_cat(&opts, x264_settings);
  285. obs_data_set_string(settings, "x264opts", opts.array);
  286. dstr_free(&opts);
  287. }
  288. }
  289. static void apply_audio_encoder_settings(obs_data_t *settings,
  290. json_t *recommended)
  291. {
  292. json_t *item = json_object_get(recommended, "max audio bitrate");
  293. if (item && json_is_integer(item)) {
  294. int max_bitrate = (int)json_integer_value(item);
  295. if (obs_data_get_int(settings, "bitrate") > max_bitrate)
  296. obs_data_set_int(settings, "bitrate", max_bitrate);
  297. }
  298. }
  299. static void initialize_output(struct rtmp_common *service, json_t *root,
  300. obs_data_t *video_settings, obs_data_t *audio_settings)
  301. {
  302. json_t *json_service = find_service(root, service->service);
  303. json_t *recommended;
  304. if (!json_service) {
  305. blog(LOG_WARNING, "rtmp-common.c: [initialize_output] "
  306. "Could not find service '%s'",
  307. service->service);
  308. return;
  309. }
  310. recommended = json_object_get(json_service, "recommended");
  311. if (!recommended)
  312. return;
  313. if (video_settings)
  314. apply_video_encoder_settings(video_settings, recommended);
  315. if (audio_settings)
  316. apply_audio_encoder_settings(audio_settings, recommended);
  317. }
  318. static void rtmp_common_apply_settings(void *data,
  319. obs_data_t *video_settings, obs_data_t *audio_settings)
  320. {
  321. struct rtmp_common *service = data;
  322. json_t *root = open_services_file();
  323. if (root) {
  324. initialize_output(service, root, video_settings,
  325. audio_settings);
  326. json_decref(root);
  327. }
  328. }
  329. static const char *rtmp_common_url(void *data)
  330. {
  331. struct rtmp_common *service = data;
  332. return service->server;
  333. }
  334. static const char *rtmp_common_key(void *data)
  335. {
  336. struct rtmp_common *service = data;
  337. return service->key;
  338. }
  339. struct obs_service_info rtmp_common_service = {
  340. .id = "rtmp_common",
  341. .get_name = rtmp_common_getname,
  342. .create = rtmp_common_create,
  343. .destroy = rtmp_common_destroy,
  344. .update = rtmp_common_update,
  345. .get_properties = rtmp_common_properties,
  346. .get_url = rtmp_common_url,
  347. .get_key = rtmp_common_key,
  348. .apply_encoder_settings = rtmp_common_apply_settings,
  349. };