rtmp-common.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  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 "twitch.h"
  8. #include "younow.h"
  9. #include "nimotv.h"
  10. #include "showroom.h"
  11. struct rtmp_common {
  12. char *service;
  13. char *server;
  14. char *key;
  15. char *output;
  16. struct obs_service_resolution *supported_resolutions;
  17. size_t supported_resolutions_count;
  18. int max_fps;
  19. bool supports_additional_audio_track;
  20. };
  21. static const char *rtmp_common_getname(void *unused)
  22. {
  23. UNUSED_PARAMETER(unused);
  24. return obs_module_text("StreamingServices");
  25. }
  26. static json_t *open_services_file(void);
  27. static inline json_t *find_service(json_t *root, const char *name,
  28. const char **p_new_name);
  29. static inline bool get_bool_val(json_t *service, const char *key);
  30. static inline const char *get_string_val(json_t *service, const char *key);
  31. static inline int get_int_val(json_t *service, const char *key);
  32. extern void twitch_ingests_refresh(int seconds);
  33. static void ensure_valid_url(struct rtmp_common *service, json_t *json,
  34. obs_data_t *settings)
  35. {
  36. json_t *servers = json_object_get(json, "servers");
  37. const char *top_url = NULL;
  38. json_t *server;
  39. size_t index;
  40. if (!service->server || !servers || !json_is_array(servers))
  41. return;
  42. if (astrstri(service->service, "Facebook") == NULL)
  43. return;
  44. json_array_foreach (servers, index, server) {
  45. const char *url = get_string_val(server, "url");
  46. if (!url)
  47. continue;
  48. if (!top_url)
  49. top_url = url;
  50. if (astrcmpi(service->server, url) == 0)
  51. return;
  52. }
  53. /* server was not found in server list, use first server instead */
  54. if (top_url) {
  55. bfree(service->server);
  56. service->server = bstrdup(top_url);
  57. obs_data_set_string(settings, "server", top_url);
  58. }
  59. }
  60. static void update_recommendations(struct rtmp_common *service, json_t *rec)
  61. {
  62. const char *out = get_string_val(rec, "output");
  63. if (out)
  64. service->output = bstrdup(out);
  65. json_t *sr = json_object_get(rec, "supported resolutions");
  66. if (sr && json_is_array(sr)) {
  67. DARRAY(struct obs_service_resolution) res_list;
  68. json_t *res_obj;
  69. size_t index;
  70. da_init(res_list);
  71. json_array_foreach (sr, index, res_obj) {
  72. if (!json_is_string(res_obj))
  73. continue;
  74. const char *res_str = json_string_value(res_obj);
  75. struct obs_service_resolution res;
  76. if (sscanf(res_str, "%dx%d", &res.cx, &res.cy) != 2)
  77. continue;
  78. if (res.cx <= 0 || res.cy <= 0)
  79. continue;
  80. da_push_back(res_list, &res);
  81. }
  82. if (res_list.num) {
  83. service->supported_resolutions = res_list.array;
  84. service->supported_resolutions_count = res_list.num;
  85. }
  86. }
  87. service->max_fps = get_int_val(rec, "max fps");
  88. }
  89. static void rtmp_common_update(void *data, obs_data_t *settings)
  90. {
  91. struct rtmp_common *service = data;
  92. bfree(service->service);
  93. bfree(service->server);
  94. bfree(service->output);
  95. bfree(service->key);
  96. bfree(service->supported_resolutions);
  97. service->service = bstrdup(obs_data_get_string(settings, "service"));
  98. service->server = bstrdup(obs_data_get_string(settings, "server"));
  99. service->key = bstrdup(obs_data_get_string(settings, "key"));
  100. service->supports_additional_audio_track = false;
  101. service->output = NULL;
  102. service->supported_resolutions = NULL;
  103. service->supported_resolutions_count = 0;
  104. service->max_fps = 0;
  105. json_t *root = open_services_file();
  106. if (root) {
  107. const char *new_name;
  108. json_t *serv = find_service(root, service->service, &new_name);
  109. if (new_name) {
  110. bfree(service->service);
  111. service->service = bstrdup(new_name);
  112. }
  113. if (serv) {
  114. json_t *rec = json_object_get(serv, "recommended");
  115. if (json_is_object(rec)) {
  116. update_recommendations(service, rec);
  117. }
  118. service->supports_additional_audio_track = get_bool_val(
  119. serv, "supports_additional_audio_track");
  120. ensure_valid_url(service, serv, settings);
  121. }
  122. }
  123. json_decref(root);
  124. if (!service->output)
  125. service->output = bstrdup("rtmp_output");
  126. }
  127. static void rtmp_common_destroy(void *data)
  128. {
  129. struct rtmp_common *service = data;
  130. bfree(service->supported_resolutions);
  131. bfree(service->service);
  132. bfree(service->server);
  133. bfree(service->output);
  134. bfree(service->key);
  135. bfree(service);
  136. }
  137. static void *rtmp_common_create(obs_data_t *settings, obs_service_t *service)
  138. {
  139. struct rtmp_common *data = bzalloc(sizeof(struct rtmp_common));
  140. rtmp_common_update(data, settings);
  141. UNUSED_PARAMETER(service);
  142. return data;
  143. }
  144. static inline const char *get_string_val(json_t *service, const char *key)
  145. {
  146. json_t *str_val = json_object_get(service, key);
  147. if (!str_val || !json_is_string(str_val))
  148. return NULL;
  149. return json_string_value(str_val);
  150. }
  151. static inline int get_int_val(json_t *service, const char *key)
  152. {
  153. json_t *integer_val = json_object_get(service, key);
  154. if (!integer_val || !json_is_integer(integer_val))
  155. return 0;
  156. return (int)json_integer_value(integer_val);
  157. }
  158. static inline bool get_bool_val(json_t *service, const char *key)
  159. {
  160. json_t *bool_val = json_object_get(service, key);
  161. if (!bool_val || !json_is_boolean(bool_val))
  162. return false;
  163. return json_is_true(bool_val);
  164. }
  165. static void add_service(obs_property_t *list, json_t *service, bool show_all,
  166. const char *cur_service)
  167. {
  168. json_t *servers;
  169. const char *name;
  170. bool common;
  171. if (!json_is_object(service)) {
  172. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  173. "is not an object");
  174. return;
  175. }
  176. name = get_string_val(service, "name");
  177. if (!name) {
  178. blog(LOG_WARNING, "rtmp-common.c: [add_service] service "
  179. "has no name");
  180. return;
  181. }
  182. common = get_bool_val(service, "common");
  183. if (!show_all && !common && strcmp(cur_service, name) != 0) {
  184. return;
  185. }
  186. servers = json_object_get(service, "servers");
  187. if (!servers || !json_is_array(servers)) {
  188. blog(LOG_WARNING,
  189. "rtmp-common.c: [add_service] service "
  190. "'%s' has no servers",
  191. name);
  192. return;
  193. }
  194. obs_property_list_add_string(list, name, name);
  195. }
  196. static void add_services(obs_property_t *list, json_t *root, bool show_all,
  197. const char *cur_service)
  198. {
  199. json_t *service;
  200. size_t index;
  201. if (!json_is_array(root)) {
  202. blog(LOG_WARNING, "rtmp-common.c: [add_services] JSON file "
  203. "root is not an array");
  204. return;
  205. }
  206. json_array_foreach (root, index, service) {
  207. add_service(list, service, show_all, cur_service);
  208. }
  209. service = find_service(root, cur_service, NULL);
  210. if (!service && cur_service && *cur_service) {
  211. obs_property_list_insert_string(list, 0, cur_service,
  212. cur_service);
  213. obs_property_list_item_disable(list, 0, true);
  214. }
  215. }
  216. static json_t *open_json_file(const char *file)
  217. {
  218. char *file_data = os_quick_read_utf8_file(file);
  219. json_error_t error;
  220. json_t *root;
  221. json_t *list;
  222. int format_ver;
  223. if (!file_data)
  224. return NULL;
  225. root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
  226. bfree(file_data);
  227. if (!root) {
  228. blog(LOG_WARNING,
  229. "rtmp-common.c: [open_json_file] "
  230. "Error reading JSON file (%d): %s",
  231. error.line, error.text);
  232. return NULL;
  233. }
  234. format_ver = get_int_val(root, "format_version");
  235. if (format_ver != RTMP_SERVICES_FORMAT_VERSION) {
  236. blog(LOG_DEBUG,
  237. "rtmp-common.c: [open_json_file] "
  238. "Wrong format version (%d), expected %d",
  239. format_ver, RTMP_SERVICES_FORMAT_VERSION);
  240. json_decref(root);
  241. return NULL;
  242. }
  243. list = json_object_get(root, "services");
  244. if (list)
  245. json_incref(list);
  246. json_decref(root);
  247. if (!list) {
  248. blog(LOG_WARNING, "rtmp-common.c: [open_json_file] "
  249. "No services list");
  250. return NULL;
  251. }
  252. return list;
  253. }
  254. static json_t *open_services_file(void)
  255. {
  256. char *file;
  257. json_t *root = NULL;
  258. file = obs_module_config_path("services.json");
  259. if (file) {
  260. root = open_json_file(file);
  261. bfree(file);
  262. }
  263. if (!root) {
  264. file = obs_module_file("services.json");
  265. if (file) {
  266. root = open_json_file(file);
  267. bfree(file);
  268. }
  269. }
  270. return root;
  271. }
  272. static void build_service_list(obs_property_t *list, json_t *root,
  273. bool show_all, const char *cur_service)
  274. {
  275. obs_property_list_clear(list);
  276. add_services(list, root, show_all, cur_service);
  277. }
  278. static void properties_data_destroy(void *data)
  279. {
  280. json_t *root = data;
  281. if (root)
  282. json_decref(root);
  283. }
  284. static bool fill_twitch_servers_locked(obs_property_t *servers_prop)
  285. {
  286. size_t count = twitch_ingest_count();
  287. obs_property_list_add_string(servers_prop,
  288. obs_module_text("Server.Auto"), "auto");
  289. if (count <= 1)
  290. return false;
  291. for (size_t i = 0; i < count; i++) {
  292. struct twitch_ingest ing = twitch_ingest(i);
  293. obs_property_list_add_string(servers_prop, ing.name, ing.url);
  294. }
  295. return true;
  296. }
  297. static inline bool fill_twitch_servers(obs_property_t *servers_prop)
  298. {
  299. bool success;
  300. twitch_ingests_lock();
  301. success = fill_twitch_servers_locked(servers_prop);
  302. twitch_ingests_unlock();
  303. return success;
  304. }
  305. static void fill_servers(obs_property_t *servers_prop, json_t *service,
  306. const char *name)
  307. {
  308. json_t *servers, *server;
  309. size_t index;
  310. obs_property_list_clear(servers_prop);
  311. servers = json_object_get(service, "servers");
  312. if (!json_is_array(servers)) {
  313. blog(LOG_WARNING,
  314. "rtmp-common.c: [fill_servers] "
  315. "Servers for service '%s' not a valid object",
  316. name);
  317. return;
  318. }
  319. if (strcmp(name, "Twitch") == 0) {
  320. if (fill_twitch_servers(servers_prop))
  321. return;
  322. }
  323. if (strcmp(name, "Nimo TV") == 0) {
  324. obs_property_list_add_string(
  325. servers_prop, obs_module_text("Server.Auto"), "auto");
  326. }
  327. json_array_foreach (servers, index, server) {
  328. const char *server_name = get_string_val(server, "name");
  329. const char *url = get_string_val(server, "url");
  330. if (!server_name || !url)
  331. continue;
  332. obs_property_list_add_string(servers_prop, server_name, url);
  333. }
  334. }
  335. static void fill_more_info_link(json_t *service, obs_data_t *settings)
  336. {
  337. const char *more_info_link;
  338. more_info_link = get_string_val(service, "more_info_link");
  339. if (more_info_link)
  340. obs_data_set_string(settings, "more_info_link", more_info_link);
  341. }
  342. static inline json_t *find_service(json_t *root, const char *name,
  343. const char **p_new_name)
  344. {
  345. size_t index;
  346. json_t *service;
  347. if (p_new_name)
  348. *p_new_name = NULL;
  349. json_array_foreach (root, index, service) {
  350. const char *cur_name = get_string_val(service, "name");
  351. if (strcmp(name, cur_name) == 0)
  352. return service;
  353. /* check for alternate names */
  354. json_t *alt_names = json_object_get(service, "alt_names");
  355. size_t alt_name_idx;
  356. json_t *alt_name_obj;
  357. json_array_foreach (alt_names, alt_name_idx, alt_name_obj) {
  358. const char *alt_name = json_string_value(alt_name_obj);
  359. if (alt_name && strcmp(name, alt_name) == 0) {
  360. if (p_new_name)
  361. *p_new_name = cur_name;
  362. return service;
  363. }
  364. }
  365. }
  366. return NULL;
  367. }
  368. static bool service_selected(obs_properties_t *props, obs_property_t *p,
  369. obs_data_t *settings)
  370. {
  371. const char *name = obs_data_get_string(settings, "service");
  372. json_t *root = obs_properties_get_param(props);
  373. json_t *service;
  374. const char *new_name;
  375. if (!name || !*name)
  376. return false;
  377. service = find_service(root, name, &new_name);
  378. if (!service) {
  379. const char *server = obs_data_get_string(settings, "server");
  380. obs_property_list_insert_string(p, 0, name, name);
  381. obs_property_list_item_disable(p, 0, true);
  382. p = obs_properties_get(props, "server");
  383. obs_property_list_insert_string(p, 0, server, server);
  384. obs_property_list_item_disable(p, 0, true);
  385. return true;
  386. }
  387. if (new_name) {
  388. name = new_name;
  389. obs_data_set_string(settings, "service", name);
  390. }
  391. fill_servers(obs_properties_get(props, "server"), service, name);
  392. fill_more_info_link(service, settings);
  393. return true;
  394. }
  395. static bool show_all_services_toggled(obs_properties_t *ppts, obs_property_t *p,
  396. obs_data_t *settings)
  397. {
  398. const char *cur_service = obs_data_get_string(settings, "service");
  399. bool show_all = obs_data_get_bool(settings, "show_all");
  400. json_t *root = obs_properties_get_param(ppts);
  401. if (!root)
  402. return false;
  403. build_service_list(obs_properties_get(ppts, "service"), root, show_all,
  404. cur_service);
  405. UNUSED_PARAMETER(p);
  406. return true;
  407. }
  408. static obs_properties_t *rtmp_common_properties(void *unused)
  409. {
  410. UNUSED_PARAMETER(unused);
  411. obs_properties_t *ppts = obs_properties_create();
  412. obs_property_t *p;
  413. json_t *root;
  414. root = open_services_file();
  415. if (root)
  416. obs_properties_set_param(ppts, root, properties_data_destroy);
  417. p = obs_properties_add_list(ppts, "service", obs_module_text("Service"),
  418. OBS_COMBO_TYPE_LIST,
  419. OBS_COMBO_FORMAT_STRING);
  420. obs_property_set_modified_callback(p, service_selected);
  421. p = obs_properties_add_bool(ppts, "show_all",
  422. obs_module_text("ShowAll"));
  423. obs_property_set_modified_callback(p, show_all_services_toggled);
  424. obs_properties_add_list(ppts, "server", obs_module_text("Server"),
  425. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  426. obs_properties_add_text(ppts, "key", obs_module_text("StreamKey"),
  427. OBS_TEXT_PASSWORD);
  428. return ppts;
  429. }
  430. static void apply_video_encoder_settings(obs_data_t *settings,
  431. json_t *recommended)
  432. {
  433. json_t *item = json_object_get(recommended, "keyint");
  434. if (json_is_integer(item)) {
  435. int keyint = (int)json_integer_value(item);
  436. obs_data_set_int(settings, "keyint_sec", keyint);
  437. }
  438. obs_data_set_string(settings, "rate_control", "CBR");
  439. item = json_object_get(recommended, "profile");
  440. obs_data_item_t *enc_item = obs_data_item_byname(settings, "profile");
  441. if (json_is_string(item) &&
  442. obs_data_item_gettype(enc_item) == OBS_DATA_STRING) {
  443. const char *profile = json_string_value(item);
  444. obs_data_set_string(settings, "profile", profile);
  445. }
  446. obs_data_item_release(&enc_item);
  447. item = json_object_get(recommended, "max video bitrate");
  448. if (json_is_integer(item)) {
  449. int max_bitrate = (int)json_integer_value(item);
  450. if (obs_data_get_int(settings, "bitrate") > max_bitrate) {
  451. obs_data_set_int(settings, "bitrate", max_bitrate);
  452. obs_data_set_int(settings, "buffer_size", max_bitrate);
  453. }
  454. }
  455. item = json_object_get(recommended, "bframes");
  456. if (json_is_integer(item)) {
  457. int bframes = (int)json_integer_value(item);
  458. obs_data_set_int(settings, "bf", bframes);
  459. }
  460. item = json_object_get(recommended, "x264opts");
  461. if (json_is_string(item)) {
  462. const char *x264_settings = json_string_value(item);
  463. const char *cur_settings =
  464. obs_data_get_string(settings, "x264opts");
  465. struct dstr opts;
  466. dstr_init_copy(&opts, cur_settings);
  467. if (!dstr_is_empty(&opts))
  468. dstr_cat(&opts, " ");
  469. dstr_cat(&opts, x264_settings);
  470. obs_data_set_string(settings, "x264opts", opts.array);
  471. dstr_free(&opts);
  472. }
  473. }
  474. static void apply_audio_encoder_settings(obs_data_t *settings,
  475. json_t *recommended)
  476. {
  477. json_t *item = json_object_get(recommended, "max audio bitrate");
  478. if (json_is_integer(item)) {
  479. int max_bitrate = (int)json_integer_value(item);
  480. if (obs_data_get_int(settings, "bitrate") > max_bitrate)
  481. obs_data_set_int(settings, "bitrate", max_bitrate);
  482. }
  483. }
  484. static void initialize_output(struct rtmp_common *service, json_t *root,
  485. obs_data_t *video_settings,
  486. obs_data_t *audio_settings)
  487. {
  488. json_t *json_service = find_service(root, service->service, NULL);
  489. json_t *recommended;
  490. if (!json_service) {
  491. if (service->service && *service->service)
  492. blog(LOG_WARNING,
  493. "rtmp-common.c: [initialize_output] "
  494. "Could not find service '%s'",
  495. service->service);
  496. return;
  497. }
  498. recommended = json_object_get(json_service, "recommended");
  499. if (!recommended)
  500. return;
  501. if (video_settings)
  502. apply_video_encoder_settings(video_settings, recommended);
  503. if (audio_settings)
  504. apply_audio_encoder_settings(audio_settings, recommended);
  505. }
  506. static void rtmp_common_apply_settings(void *data, obs_data_t *video_settings,
  507. obs_data_t *audio_settings)
  508. {
  509. struct rtmp_common *service = data;
  510. json_t *root = open_services_file();
  511. if (root) {
  512. initialize_output(service, root, video_settings,
  513. audio_settings);
  514. json_decref(root);
  515. }
  516. }
  517. static const char *rtmp_common_get_output_type(void *data)
  518. {
  519. struct rtmp_common *service = data;
  520. return service->output;
  521. }
  522. static const char *rtmp_common_url(void *data)
  523. {
  524. struct rtmp_common *service = data;
  525. if (service->service && strcmp(service->service, "Twitch") == 0) {
  526. if (service->server && strcmp(service->server, "auto") == 0) {
  527. struct twitch_ingest ing;
  528. twitch_ingests_refresh(3);
  529. twitch_ingests_lock();
  530. ing = twitch_ingest(0);
  531. twitch_ingests_unlock();
  532. return ing.url;
  533. }
  534. }
  535. if (service->service && strcmp(service->service, "YouNow") == 0) {
  536. if (service->server && service->key) {
  537. return younow_get_ingest(service->server, service->key);
  538. }
  539. }
  540. if (service->service && strcmp(service->service, "Nimo TV") == 0) {
  541. if (service->server && strcmp(service->server, "auto") == 0) {
  542. return nimotv_get_ingest(service->key);
  543. }
  544. }
  545. if (service->service && strcmp(service->service, "SHOWROOM") == 0) {
  546. if (service->server && service->key) {
  547. struct showroom_ingest *ingest;
  548. ingest = showroom_get_ingest(service->server,
  549. service->key);
  550. return ingest->url;
  551. }
  552. }
  553. return service->server;
  554. }
  555. static const char *rtmp_common_key(void *data)
  556. {
  557. struct rtmp_common *service = data;
  558. if (service->service && strcmp(service->service, "SHOWROOM") == 0) {
  559. if (service->server && service->key) {
  560. struct showroom_ingest *ingest;
  561. ingest = showroom_get_ingest(service->server,
  562. service->key);
  563. return ingest->key;
  564. }
  565. }
  566. return service->key;
  567. }
  568. static bool supports_multitrack(void *data)
  569. {
  570. struct rtmp_common *service = data;
  571. return service->supports_additional_audio_track;
  572. }
  573. static void rtmp_common_get_supported_resolutions(
  574. void *data, struct obs_service_resolution **resolutions, size_t *count)
  575. {
  576. struct rtmp_common *service = data;
  577. *count = service->supported_resolutions_count;
  578. *resolutions = bmemdup(service->supported_resolutions,
  579. *count * sizeof(struct obs_service_resolution));
  580. }
  581. static void rtmp_common_get_max_fps(void *data, int *fps)
  582. {
  583. struct rtmp_common *service = data;
  584. *fps = service->max_fps;
  585. }
  586. static void rtmp_common_get_max_bitrate(void *data, int *video_bitrate,
  587. int *audio_bitrate)
  588. {
  589. struct rtmp_common *service = data;
  590. json_t *root = open_services_file();
  591. json_t *item;
  592. if (!root)
  593. return;
  594. json_t *json_service = find_service(root, service->service, NULL);
  595. if (!json_service) {
  596. goto fail;
  597. }
  598. json_t *recommended = json_object_get(json_service, "recommended");
  599. if (!recommended) {
  600. goto fail;
  601. }
  602. if (audio_bitrate) {
  603. item = json_object_get(recommended, "max audio bitrate");
  604. if (json_is_integer(item))
  605. *audio_bitrate = (int)json_integer_value(item);
  606. }
  607. if (video_bitrate) {
  608. item = json_object_get(recommended, "max video bitrate");
  609. if (json_is_integer(item))
  610. *video_bitrate = (int)json_integer_value(item);
  611. }
  612. fail:
  613. json_decref(root);
  614. }
  615. struct obs_service_info rtmp_common_service = {
  616. .id = "rtmp_common",
  617. .get_name = rtmp_common_getname,
  618. .create = rtmp_common_create,
  619. .destroy = rtmp_common_destroy,
  620. .update = rtmp_common_update,
  621. .get_properties = rtmp_common_properties,
  622. .get_url = rtmp_common_url,
  623. .get_key = rtmp_common_key,
  624. .apply_encoder_settings = rtmp_common_apply_settings,
  625. .get_output_type = rtmp_common_get_output_type,
  626. .get_supported_resolutions = rtmp_common_get_supported_resolutions,
  627. .get_max_fps = rtmp_common_get_max_fps,
  628. .get_max_bitrate = rtmp_common_get_max_bitrate,
  629. };