From 201bcda0514aa9726ed90eff6ad4180b0bb16fc3 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 21 Aug 2021 08:51:21 +0200 Subject: [PATCH 01/16] libobs,docs: Add protocol in Outputs API --- docs/sphinx/reference-outputs.rst | 26 +++++++++++++++++++++++++- libobs/obs-internal.h | 2 ++ libobs/obs-module.c | 21 +++++++++++++++++++++ libobs/obs-output.c | 10 ++++++++++ libobs/obs-output.h | 3 +++ libobs/obs.c | 14 ++++++++++++++ libobs/obs.h | 4 ++++ 7 files changed, 79 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/reference-outputs.rst b/docs/sphinx/reference-outputs.rst index 0cbe9f98865d2c..eb8b94c0b1e652 100644 --- a/docs/sphinx/reference-outputs.rst +++ b/docs/sphinx/reference-outputs.rst @@ -234,7 +234,15 @@ Output Definition Structure (obs_output_info) This variable specifies which codecs are supported by an encoded output, separated by semicolon. - (Optional, though recommended) + Required if **OBS_OUTPUT_SERVICE** flag is set, otherwise + recommended. + +.. member:: const char *obs_output_info.protocols + + This variable specifies which protocols are supported by an output, + separated by semicolon. + + Required only if **OBS_OUTPUT_SERVICE** flag is set. .. _output_signal_handler_reference: @@ -685,6 +693,22 @@ General Output Functions --------------------- +.. function:: const char *obs_output_get_protocols(const obs_output_t *output) + + :return: Supported protocols, separated by semicolon. Always NULL if the + output is not **OBS_OUTPUT_SERVICE**. + +--------------------- + +.. function:: bool obs_is_output_protocol_registered(const char *protocol) + + Check if one of the registered output use the given protocol. + + :return: A boolean showing if an output with the given + protocol is registered + +--------------------- + Functions used by outputs ------------------------- diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index f6a3835501998c..a67fb518cb8036 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -411,6 +411,8 @@ struct obs_core_data { obs_data_t *private_data; volatile bool valid; + + DARRAY(char *) protocols; }; /* user hotkeys */ diff --git a/libobs/obs-module.c b/libobs/obs-module.c index ff118814f75862..73c8228a57c1f2 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -836,6 +836,9 @@ void obs_register_output_s(const struct obs_output_info *info, size_t size) CHECK_REQUIRED_VAL_(info, start, obs_register_output); CHECK_REQUIRED_VAL_(info, stop, obs_register_output); + if (info->flags & OBS_OUTPUT_SERVICE) + CHECK_REQUIRED_VAL_(info, protocols, obs_register_output); + if (info->flags & OBS_OUTPUT_ENCODED) { CHECK_REQUIRED_VAL_(info, encoded_packet, obs_register_output); } else { @@ -856,6 +859,24 @@ void obs_register_output_s(const struct obs_output_info *info, size_t size) #undef CHECK_REQUIRED_VAL_ REGISTER_OBS_DEF(size, obs_output_info, obs->output_types, info); + + if (info->flags & OBS_OUTPUT_SERVICE) { + char **protocols = strlist_split(info->protocols, ';', false); + for (char **protocol = protocols; *protocol; ++protocol) { + bool skip = false; + for (size_t i = 0; i < obs->data.protocols.num; i++) { + if (strcmp(*protocol, + obs->data.protocols.array[i]) == 0) + skip = true; + } + + if (skip) + continue; + char *new_prtcl = bstrdup(*protocol); + da_push_back(obs->data.protocols, &new_prtcl); + } + strlist_free(protocols); + } return; error: diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 335b7655541dd1..148b31b4a13ece 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -2713,3 +2713,13 @@ const char *obs_output_get_supported_audio_codecs(const obs_output_t *output) ? output->info.encoded_audio_codecs : NULL; } + +const char *obs_output_get_protocols(const obs_output_t *output) +{ + if (!obs_output_valid(output, "obs_output_get_protocols")) + return NULL; + + return (output->info.flags & OBS_OUTPUT_SERVICE) + ? output->info.protocols + : NULL; +} diff --git a/libobs/obs-output.h b/libobs/obs-output.h index e57659fd9f5104..0469d39a174733 100644 --- a/libobs/obs-output.h +++ b/libobs/obs-output.h @@ -77,6 +77,9 @@ struct obs_output_info { /* raw audio callback for multi track outputs */ void (*raw_audio2)(void *data, size_t idx, struct audio_data *frames); + + /* required if OBS_OUTPUT_SERVICE */ + const char *protocols; }; EXPORT void obs_register_output_s(const struct obs_output_info *info, diff --git a/libobs/obs.c b/libobs/obs.c index cb17d5d8840832..3cd697a072defa 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -1078,6 +1078,10 @@ static void obs_free_data(void) da_free(data->draw_callbacks); da_free(data->tick_callbacks); obs_data_release(data->private_data); + + for (size_t i = 0; i < data->protocols.num; i++) + bfree(data->protocols.array[i]); + da_free(data->protocols); } static const char *obs_signals[] = { @@ -3452,3 +3456,13 @@ bool obs_weak_object_references_object(obs_weak_object_t *weak, { return weak && object && weak->object == object; } + +bool obs_is_output_protocol_registered(const char *protocol) +{ + for (size_t i = 0; i < obs->data.protocols.num; i++) { + if (strcmp(protocol, obs->data.protocols.array[i]) == 0) + return true; + } + + return false; +} diff --git a/libobs/obs.h b/libobs/obs.h index f5b84b7082fba7..f6e08e33a47568 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2211,6 +2211,10 @@ obs_output_get_supported_video_codecs(const obs_output_t *output); EXPORT const char * obs_output_get_supported_audio_codecs(const obs_output_t *output); +EXPORT const char *obs_output_get_protocols(const obs_output_t *output); + +EXPORT bool obs_is_output_protocol_registered(const char *protocol); + /* ------------------------------------------------------------------------- */ /* Functions used by outputs */ From 855956f60ecdac1013fa28b7124f1bfb03935ab9 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Sat, 21 Aug 2021 08:53:21 +0200 Subject: [PATCH 02/16] obs-outputs,obs-ffmpeg: Add protocol to service outputs --- plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c | 1 + plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c | 1 + plugins/obs-ffmpeg/obs-ffmpeg-mux.c | 1 + plugins/obs-outputs/ftl-stream.c | 1 + plugins/obs-outputs/rtmp-stream.c | 5 +++++ 5 files changed, 9 insertions(+) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c index 017ecba76aef5f..d8546bf26014ce 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-hls-mux.c @@ -331,6 +331,7 @@ struct obs_output_info ffmpeg_hls_muxer = { .id = "ffmpeg_hls_muxer", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK | OBS_OUTPUT_SERVICE, + .protocols = "HLS", #ifdef ENABLE_HEVC .encoded_video_codecs = "h264;hevc", #else diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c index 5a20aae5132cfa..f9cc4ae7e38b5d 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c @@ -1289,6 +1289,7 @@ struct obs_output_info ffmpeg_mpegts_muxer = { .id = "ffmpeg_mpegts_muxer", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK | OBS_OUTPUT_SERVICE, + .protocols = "SRT;RIST", #ifdef ENABLE_HEVC .encoded_video_codecs = "h264;hevc;av1", #else diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c index a605e9af352fc3..d4156b5153e283 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c @@ -944,6 +944,7 @@ struct obs_output_info ffmpeg_mpegts_muxer = { .id = "ffmpeg_mpegts_muxer", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK | OBS_OUTPUT_SERVICE, + .protocols = "SRT;RIST", .encoded_video_codecs = "h264;av1", .encoded_audio_codecs = "aac", .get_name = ffmpeg_mpegts_mux_getname, diff --git a/plugins/obs-outputs/ftl-stream.c b/plugins/obs-outputs/ftl-stream.c index a0556d2a5d36d6..ef305420fc57c2 100644 --- a/plugins/obs-outputs/ftl-stream.c +++ b/plugins/obs-outputs/ftl-stream.c @@ -1159,6 +1159,7 @@ static int _ftl_error_to_obs_error(int status) struct obs_output_info ftl_output_info = { .id = "ftl_output", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE, + .protocols = "FTL", .encoded_video_codecs = "h264", .encoded_audio_codecs = "opus", .get_name = ftl_stream_getname, diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index f186c36161280e..6058d4f7fdf65b 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -1642,6 +1642,11 @@ struct obs_output_info rtmp_output_info = { .id = "rtmp_output", .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE | OBS_OUTPUT_MULTI_TRACK, +#ifdef NO_CRYPTO + .protocols = "RTMP", +#else + .protocols = "RTMP;RTMPS", +#endif .encoded_video_codecs = "h264", .encoded_audio_codecs = "aac", .get_name = rtmp_stream_getname, From 513c6bc4896ff97e0981125b6d8c9837bae712f5 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Mon, 25 Apr 2022 12:54:48 +0200 Subject: [PATCH 03/16] rtmp-services: Add protocols to services JSON If the server URL is not an RTMP(S) URL, the protocol field becomes required. The output field becomes required on non-RTMP(S) services to keep backward compatibility. Also skip service if the protocol is not available. --- plugins/rtmp-services/data/package.json | 4 +- .../data/schema/service-schema-v4.json | 24 +++++++++++ plugins/rtmp-services/data/services.json | 5 +++ plugins/rtmp-services/rtmp-common.c | 43 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/plugins/rtmp-services/data/package.json b/plugins/rtmp-services/data/package.json index 89e6d4f31d1c14..a9c3e5f824ebab 100644 --- a/plugins/rtmp-services/data/package.json +++ b/plugins/rtmp-services/data/package.json @@ -1,11 +1,11 @@ { "$schema": "schema/package-schema.json", "url": "https://obsproject.com/obs2_update/rtmp-services/v4", - "version": 220, + "version": 221, "files": [ { "name": "services.json", - "version": 220 + "version": 221 } ] } diff --git a/plugins/rtmp-services/data/schema/service-schema-v4.json b/plugins/rtmp-services/data/schema/service-schema-v4.json index 61f3c0cf2d37e9..e5afe45851ea15 100644 --- a/plugins/rtmp-services/data/schema/service-schema-v4.json +++ b/plugins/rtmp-services/data/schema/service-schema-v4.json @@ -16,6 +16,18 @@ "description": "Name of the streaming service. Will be displayed in the Service dropdown.", "minLength": 1 }, + "protocol" : { + "type": "string", + "description": "Protocol used by the service. If missing the service is considered using RTMP or RTMPS.", + "enum": [ + "RTMP", + "RTMPS", + "HLS", + "FTL", + "SRT", + "RIST" + ] + }, "common": { "type": "boolean", "description": "Whether or not the service is shown in the list before it is expanded to all services by the user.", @@ -200,6 +212,18 @@ "required": [ "name", "servers" + ], + "allOf": [ + { + "$comment": "Require protocol field if not an RTMP(S) URL", + "if": { "not": { "properties": { "servers": { "items": { "properties": { "url": { "format": "uri", "pattern": "^rtmps?://" } } } } } } }, + "then": { "required": ["protocol"] } + }, + { + "$comment": "Require recommended output field if protocol field is not RTMP(S)", + "if": { "required": ["protocol"], "properties": { "protocol": { "pattern": "^(HLS|SRT|RIST|FTL)$" } } }, + "then": { "properties": { "recommended": { "required": ["output"] } } } + } ] }, "additionalItems": true diff --git a/plugins/rtmp-services/data/services.json b/plugins/rtmp-services/data/services.json index c9f8391ddd722e..e0fb4e5a489849 100644 --- a/plugins/rtmp-services/data/services.json +++ b/plugins/rtmp-services/data/services.json @@ -204,6 +204,7 @@ "common": false, "more_info_link": "https://developers.google.com/youtube/v3/live/guides/ingestion-protocol-comparison", "stream_key_link": "https://www.youtube.com/live_dashboard", + "protocol": "HLS", "supported video codecs": [ "h264", "hevc" @@ -1445,6 +1446,7 @@ { "name": "YouNow", "common": false, + "protocol": "FTL", "supported audio codecs": [ "opus" ], @@ -1687,6 +1689,7 @@ }, { "name": "SHOWROOM", + "protocol": "RTMP", "servers": [ { "name": "Default", @@ -1821,6 +1824,7 @@ { "name": "Glimesh", "stream_key_link": "https://glimesh.tv/users/settings/stream", + "protocol": "FTL", "supported audio codecs": [ "opus" ], @@ -2024,6 +2028,7 @@ }, { "name": "Dacast", + "protocol": "RTMP", "servers": [ { "name": "Default", diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 1aa20ec6758772..778d06d499a062 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -208,6 +208,35 @@ static inline bool get_bool_val(json_t *service, const char *key) return json_is_true(bool_val); } +#define RTMP_PREFIX "rtmp://" +#define RTMPS_PREFIX "rtmps://" + +static bool is_protocol_available(json_t *service) +{ + const char *protocol = get_string_val(service, "protocol"); + if (protocol) + return obs_is_output_protocol_registered(protocol); + + /* Test RTMP and RTMPS if no protocol found */ + json_t *servers; + size_t index; + json_t *server; + const char *url; + bool ret = false; + + servers = json_object_get(service, "servers"); + json_array_foreach (servers, index, server) { + url = get_string_val(server, "url"); + + if (strncmp(url, RTMP_PREFIX, strlen(RTMP_PREFIX)) == 0) + ret |= obs_is_output_protocol_registered("RTMP"); + else if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) + ret |= obs_is_output_protocol_registered("RTMPS"); + } + + return ret; +} + static void add_service(obs_property_t *list, json_t *service, bool show_all, const char *cur_service) { @@ -258,6 +287,9 @@ static void add_services(obs_property_t *list, json_t *root, bool show_all, } json_array_foreach (root, index, service) { + /* Skip service with non-available protocol */ + if (!is_protocol_available(service)) + continue; add_service(list, service, show_all, cur_service); } @@ -399,11 +431,13 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service, return; } + /* Assumption: Twitch should be RTMP only, so no RTMPS check */ if (strcmp(name, "Twitch") == 0) { if (fill_twitch_servers(servers_prop)) return; } + /* Assumption: Nimo TV should be RTMP only, so no RTMPS check in the ingest */ if (strcmp(name, "Nimo TV") == 0) { obs_property_list_add_string( servers_prop, obs_module_text("Server.Auto"), "auto"); @@ -416,6 +450,11 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service, if (!server_name || !url) continue; + /* Skip RTMPS server if protocol not registered */ + if ((!obs_is_output_protocol_registered("RTMPS")) && + (strncmp(url, "rtmps://", 8) == 0)) + continue; + obs_property_list_add_string(servers_prop, server_name, url); } } @@ -449,6 +488,10 @@ static inline json_t *find_service(json_t *root, const char *name, *p_new_name = NULL; json_array_foreach (root, index, service) { + /* skip service with non-available protocol */ + if (!is_protocol_available(service)) + continue; + const char *cur_name = get_string_val(service, "name"); if (strcmp(name, cur_name) == 0) From 813b3b2763a9942a54b14ab4050d9d96bd0d62ea Mon Sep 17 00:00:00 2001 From: tytan652 Date: Mon, 5 Sep 2022 14:00:08 +0200 Subject: [PATCH 04/16] libobs,docs: Add protocol to Services API --- docs/sphinx/reference-services.rst | 10 ++++++++++ libobs/obs-module.c | 1 + libobs/obs-service.c | 8 ++++++++ libobs/obs-service.h | 2 ++ libobs/obs.h | 3 +++ 5 files changed, 24 insertions(+) diff --git a/docs/sphinx/reference-services.rst b/docs/sphinx/reference-services.rst index 85a0c876235c0e..7c6a89dfe379b1 100644 --- a/docs/sphinx/reference-services.rst +++ b/docs/sphinx/reference-services.rst @@ -148,6 +148,10 @@ Service Definition Structure the data manually (typically best to use strlist_split to generate this) +.. member:: const char *(*obs_service_info.get_protocol)(void *data) + + :return: The protocol used by the service + General Service Functions ------------------------- @@ -305,6 +309,12 @@ General Service Functions for the service, terminated with a *NULL* pointer. Does not need to be freed +--------------------- + +.. function:: const char *obs_service_get_protocol(const obs_service_t *service) + + :return: Protocol currently used for this service + .. --------------------------------------------------------------------------- .. _libobs/obs-service.h: https://github.com/obsproject/obs-studio/blob/master/libobs/obs-service.h diff --git a/libobs/obs-module.c b/libobs/obs-module.c index 73c8228a57c1f2..cd7841b6c1960d 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -928,6 +928,7 @@ void obs_register_service_s(const struct obs_service_info *info, size_t size) CHECK_REQUIRED_VAL_(info, get_name, obs_register_service); CHECK_REQUIRED_VAL_(info, create, obs_register_service); CHECK_REQUIRED_VAL_(info, destroy, obs_register_service); + CHECK_REQUIRED_VAL_(info, get_protocol, obs_register_service); #undef CHECK_REQUIRED_VAL_ REGISTER_OBS_DEF(size, obs_service_info, obs->service_types, info); diff --git a/libobs/obs-service.c b/libobs/obs-service.c index a2cd0140c6d1da..607844f30016e8 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -477,3 +477,11 @@ obs_service_get_supported_video_codecs(const obs_service_t *service) service->context.data); return NULL; } + +const char *obs_service_get_protocol(const obs_service_t *service) +{ + if (!obs_service_valid(service, "obs_service_get_protocol")) + return NULL; + + return service->info.get_protocol(service->context.data); +} diff --git a/libobs/obs-service.h b/libobs/obs-service.h index f025260ff1b70f..e02ed822e7776f 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -88,6 +88,8 @@ struct obs_service_info { int *audio_bitrate); const char **(*get_supported_video_codecs)(void *data); + + const char *(*get_protocol)(void *data); }; EXPORT void obs_register_service_s(const struct obs_service_info *info, diff --git a/libobs/obs.h b/libobs/obs.h index f6e08e33a47568..f646f4b21dc12b 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2539,6 +2539,9 @@ obs_service_get_supported_video_codecs(const obs_service_t *service); * date. */ EXPORT const char *obs_service_get_output_type(const obs_service_t *service); +/** Returns the protocol for this service context */ +EXPORT const char *obs_service_get_protocol(const obs_service_t *service); + /* ------------------------------------------------------------------------- */ /* Source frame allocation functions */ EXPORT void obs_source_frame_init(struct obs_source_frame *frame, From 56411eda8e96c2c35043dbfa151a3eafe7a6780b Mon Sep 17 00:00:00 2001 From: tytan652 Date: Mon, 5 Sep 2022 14:58:27 +0200 Subject: [PATCH 05/16] libobs,docs: Add protocol enumeration functions --- docs/sphinx/reference-outputs.rst | 18 ++++++++++++++++++ libobs/obs-output.c | 28 ++++++++++++++++++++++++++++ libobs/obs.c | 9 +++++++++ libobs/obs.h | 6 ++++++ 4 files changed, 61 insertions(+) diff --git a/docs/sphinx/reference-outputs.rst b/docs/sphinx/reference-outputs.rst index eb8b94c0b1e652..e7c85a7e8d7ef3 100644 --- a/docs/sphinx/reference-outputs.rst +++ b/docs/sphinx/reference-outputs.rst @@ -709,6 +709,24 @@ General Output Functions --------------------- +.. function:: bool obs_enum_output_protocols(size_t idx, char **protocol) + + Enumerates all registered protocol. + +--------------------- + +.. function:: void obs_enum_output_types_with_protocol(const char *protocol, void *data, bool (*enum_cb)(void *data, const char *id)) + + Enumerates through a callback all available output types for the given protocol. + + :param protocol: Protocol of the outputs to enumerate + :param data: Data passed to the callback + :param enum_cb: Callback used when a matching output is found, the id + of the output is passed to the callback + :return: When all outputs are enumerated or if the callback return *false* + +--------------------- + Functions used by outputs ------------------------- diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 148b31b4a13ece..c9543e4e8f035b 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -2723,3 +2723,31 @@ const char *obs_output_get_protocols(const obs_output_t *output) ? output->info.protocols : NULL; } + +void obs_enum_output_types_with_protocol(const char *protocol, void *data, + bool (*enum_cb)(void *data, + const char *id)) +{ + if (!obs_is_output_protocol_registered(protocol)) + return; + + size_t protocol_len = strlen(protocol); + for (size_t i = 0; i < obs->output_types.num; i++) { + if (!(obs->output_types.array[i].flags & OBS_OUTPUT_SERVICE)) + continue; + + const char *substr = obs->output_types.array[i].protocols; + while (substr && substr[0] != '\0') { + const char *next = strchr(substr, ';'); + size_t len = next ? (size_t)(next - substr) + : strlen(substr); + if (protocol_len == len && + strncmp(substr, protocol, len) == 0) { + if (!enum_cb(data, + obs->output_types.array[i].id)) + return; + } + substr = next ? next + 1 : NULL; + } + } +} diff --git a/libobs/obs.c b/libobs/obs.c index 3cd697a072defa..73e281eaa83733 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -3466,3 +3466,12 @@ bool obs_is_output_protocol_registered(const char *protocol) return false; } + +bool obs_enum_output_protocols(size_t idx, char **protocol) +{ + if (idx >= obs->data.protocols.num) + return false; + + *protocol = obs->data.protocols.array[idx]; + return true; +} diff --git a/libobs/obs.h b/libobs/obs.h index f646f4b21dc12b..5f82929c362230 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2215,6 +2215,12 @@ EXPORT const char *obs_output_get_protocols(const obs_output_t *output); EXPORT bool obs_is_output_protocol_registered(const char *protocol); +EXPORT bool obs_enum_output_protocols(size_t idx, char **protocol); + +EXPORT void obs_enum_output_types_with_protocol( + const char *protocol, void *data, + bool (*enum_cb)(void *data, const char *id)); + /* ------------------------------------------------------------------------- */ /* Functions used by outputs */ From fa58a38b24efef88a6baf67c41d280a9e8b29575 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Mon, 5 Sep 2022 15:19:32 +0200 Subject: [PATCH 06/16] rtmp-services: Add protocol getter to services --- plugins/rtmp-services/rtmp-common.c | 36 +++++++++++++++++++++++++++++ plugins/rtmp-services/rtmp-custom.c | 25 ++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 778d06d499a062..984053ffb790ec 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -13,6 +13,7 @@ struct rtmp_common { char *service; + char *protocol; char *server; char *key; @@ -118,11 +119,13 @@ static void rtmp_common_update(void *data, obs_data_t *settings) bfree(service->supported_resolutions); bfree(service->video_codecs); bfree(service->service); + bfree(service->protocol); bfree(service->server); bfree(service->output); bfree(service->key); service->service = bstrdup(obs_data_get_string(settings, "service")); + service->protocol = bstrdup(obs_data_get_string(settings, "protocol")); service->server = bstrdup(obs_data_get_string(settings, "server")); service->key = bstrdup(obs_data_get_string(settings, "key")); service->supports_additional_audio_track = false; @@ -166,6 +169,7 @@ static void rtmp_common_destroy(void *data) bfree(service->supported_resolutions); bfree(service->video_codecs); bfree(service->service); + bfree(service->protocol); bfree(service->server); bfree(service->output); bfree(service->key); @@ -478,6 +482,29 @@ static void fill_stream_key_link(json_t *service, obs_data_t *settings) stream_key_link); } +static void update_protocol(json_t *service, obs_data_t *settings) +{ + const char *protocol = get_string_val(service, "protocol"); + if (protocol) { + obs_data_set_string(settings, "protocol", protocol); + return; + } + + json_t *servers = json_object_get(service, "servers"); + if (!json_is_array(servers)) + return; + + json_t *server = json_array_get(servers, 0); + const char *url = get_string_val(server, "url"); + + if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) { + obs_data_set_string(settings, "protocol", "RTMPS"); + return; + } + + obs_data_set_string(settings, "protocol", "RTMP"); +} + static inline json_t *find_service(json_t *root, const char *name, const char **p_new_name) { @@ -546,6 +573,7 @@ static bool service_selected(obs_properties_t *props, obs_property_t *p, fill_servers(obs_properties_get(props, "server"), service, name); fill_more_info_link(service, settings); fill_stream_key_link(service, settings); + update_protocol(service, settings); return true; } @@ -968,6 +996,13 @@ static const char *rtmp_common_password(void *data) return NULL; } +static const char *rtmp_common_get_protocol(void *data) +{ + struct rtmp_common *service = data; + + return service->protocol ? service->protocol : "RTMP"; +} + struct obs_service_info rtmp_common_service = { .id = "rtmp_common", .get_name = rtmp_common_getname, @@ -975,6 +1010,7 @@ struct obs_service_info rtmp_common_service = { .destroy = rtmp_common_destroy, .update = rtmp_common_update, .get_properties = rtmp_common_properties, + .get_protocol = rtmp_common_get_protocol, .get_url = rtmp_common_url, .get_key = rtmp_common_key, .get_username = rtmp_common_username, diff --git a/plugins/rtmp-services/rtmp-custom.c b/plugins/rtmp-services/rtmp-custom.c index 51276d6c8a45d0..53ef3d4bf6fd09 100644 --- a/plugins/rtmp-services/rtmp-custom.c +++ b/plugins/rtmp-services/rtmp-custom.c @@ -110,6 +110,30 @@ static const char *rtmp_custom_password(void *data) return service->password; } +#define RTMPS_PREFIX "rtmps://" +#define FTL_PREFIX "ftl://" +#define SRT_PREFIX "srt://" +#define RIST_PREFIX "rist://" + +static const char *rtmp_custom_get_protocol(void *data) +{ + struct rtmp_custom *service = data; + + if (strncmp(service->server, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) + return "RTMPS"; + + if (strncmp(service->server, FTL_PREFIX, strlen(FTL_PREFIX)) == 0) + return "FTL"; + + if (strncmp(service->server, SRT_PREFIX, strlen(SRT_PREFIX)) == 0) + return "SRT"; + + if (strncmp(service->server, RIST_PREFIX, strlen(RIST_PREFIX)) == 0) + return "RIST"; + + return "RTMP"; +} + #define RTMP_PROTOCOL "rtmp" static void rtmp_custom_apply_settings(void *data, obs_data_t *video_settings, @@ -131,6 +155,7 @@ struct obs_service_info rtmp_custom_service = { .destroy = rtmp_custom_destroy, .update = rtmp_custom_update, .get_properties = rtmp_custom_properties, + .get_protocol = rtmp_custom_get_protocol, .get_url = rtmp_custom_url, .get_key = rtmp_custom_key, .get_username = rtmp_custom_username, From 48dc6dde68aa698a35fbe40205da976fe28ff1b1 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Wed, 28 Sep 2022 18:18:24 +0200 Subject: [PATCH 07/16] libobs,docs: Add supported codecs functions with output id --- docs/sphinx/reference-outputs.rst | 2 ++ libobs/obs-output.c | 12 ++++++++++++ libobs/obs.h | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/docs/sphinx/reference-outputs.rst b/docs/sphinx/reference-outputs.rst index e7c85a7e8d7ef3..ba60f70ebbaf83 100644 --- a/docs/sphinx/reference-outputs.rst +++ b/docs/sphinx/reference-outputs.rst @@ -679,7 +679,9 @@ General Output Functions --------------------- .. function:: const char *obs_output_get_supported_video_codecs(const obs_output_t *output) + const char *obs_get_output_supported_video_codecs(const char *id) const char *obs_output_get_supported_audio_codecs(const obs_output_t *output) + const char *obs_get_output_supported_video_codecs(const char *id) :return: Supported video/audio codecs of an encoded output, separated by semicolon diff --git a/libobs/obs-output.c b/libobs/obs-output.c index c9543e4e8f035b..b968fefd25b41e 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -2751,3 +2751,15 @@ void obs_enum_output_types_with_protocol(const char *protocol, void *data, } } } + +const char *obs_get_output_supported_video_codecs(const char *id) +{ + const struct obs_output_info *info = find_output(id); + return info ? info->encoded_video_codecs : NULL; +} + +const char *obs_get_output_supported_audio_codecs(const char *id) +{ + const struct obs_output_info *info = find_output(id); + return info ? info->encoded_audio_codecs : NULL; +} diff --git a/libobs/obs.h b/libobs/obs.h index 5f82929c362230..fc3ef201930b58 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2221,6 +2221,10 @@ EXPORT void obs_enum_output_types_with_protocol( const char *protocol, void *data, bool (*enum_cb)(void *data, const char *id)); +EXPORT const char *obs_get_output_supported_video_codecs(const char *id); + +EXPORT const char *obs_get_output_supported_audio_codecs(const char *id); + /* ------------------------------------------------------------------------- */ /* Functions used by outputs */ From 5a409347d5bf7dec50c41641660a131c265fb4ad Mon Sep 17 00:00:00 2001 From: tytan652 Date: Wed, 21 Sep 2022 07:53:47 +0200 Subject: [PATCH 08/16] UI: Refactor Qt slots in stream settings page --- UI/window-basic-settings-stream.cpp | 81 ++++++++++++++--------------- UI/window-basic-settings.hpp | 13 +++-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index f691baefce4f47..6dd8ecdbd9d615 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -78,29 +78,10 @@ void OBSBasicSettings::InitStreamPage() ui->twitchAddonDropdown->addItem( QTStr("Basic.Settings.Stream.TTVAddon.Both")); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateServerList())); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateKeyLink())); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateVodTrackSetting())); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateServiceRecommendations())); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateResFPSLimits())); - connect(ui->customServer, SIGNAL(textChanged(const QString &)), this, - SLOT(UpdateKeyLink())); connect(ui->ignoreRecommended, SIGNAL(clicked(bool)), this, SLOT(DisplayEnforceWarning(bool))); connect(ui->ignoreRecommended, SIGNAL(toggled(bool)), this, SLOT(UpdateResFPSLimits())); - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateMoreInfoLink())); - - connect(ui->service, SIGNAL(currentIndexChanged(int)), this, - SLOT(UpdateAdvNetworkGroup())); - connect(ui->customServer, SIGNAL(textChanged(const QString &)), this, - SLOT(UpdateAdvNetworkGroup())); } void OBSBasicSettings::LoadStream1Settings() @@ -458,13 +439,37 @@ void OBSBasicSettings::UseStreamKeyAdvClicked() ui->streamKeyWidget->setVisible(true); } -void OBSBasicSettings::on_service_currentIndexChanged(int) +void OBSBasicSettings::on_service_currentIndexChanged(int idx) { - bool showMore = ui->service->currentData().toInt() == - (int)ListOpt::ShowAll; - if (showMore) + if (ui->service->currentData().toInt() == (int)ListOpt::ShowAll) { + LoadServices(true); + ui->service->showPopup(); return; + } + + ServiceChanged(); + + UpdateMoreInfoLink(); + UpdateServerList(); + UpdateKeyLink(); + UpdateServiceRecommendations(); + + UpdateVodTrackSetting(); + UpdateAdvNetworkGroup(); + + if (ServiceSupportsCodecCheck() && UpdateResFPSLimits()) + lastServiceIdx = idx; +} +void OBSBasicSettings::on_customServer_textChanged(const QString &) +{ + UpdateKeyLink(); + + UpdateAdvNetworkGroup(); +} + +void OBSBasicSettings::ServiceChanged() +{ std::string service = QT_TO_UTF8(ui->service->currentText()); bool custom = IsCustomService(); @@ -518,16 +523,8 @@ void OBSBasicSettings::on_service_currentIndexChanged(int) void OBSBasicSettings::UpdateServerList() { QString serviceName = ui->service->currentText(); - bool showMore = ui->service->currentData().toInt() == - (int)ListOpt::ShowAll; - if (showMore) { - LoadServices(true); - ui->service->showPopup(); - return; - } else { - lastService = serviceName; - } + lastService = serviceName; obs_properties_t *props = obs_get_service_properties("rtmp_common"); obs_property_t *services = obs_properties_get(props, "service"); @@ -985,17 +982,14 @@ extern void set_closest_res(int &cx, int &cy, * which as of this writing, and hopefully for the foreseeable future, there is * only one. */ -void OBSBasicSettings::UpdateResFPSLimits() +bool OBSBasicSettings::UpdateResFPSLimits() { if (loading) - return; - - if (!ServiceSupportsCodecCheck()) - return; + return false; int idx = ui->service->currentIndex(); if (idx == -1) - return; + return false; bool ignoreRecommended = ui->ignoreRecommended->isChecked(); BPtr res_list; @@ -1073,8 +1067,7 @@ void OBSBasicSettings::UpdateResFPSLimits() ui->ignoreRecommended->setProperty("changed", true); stream1Changed = true; EnableApplyButton(true); - UpdateResFPSLimits(); - return; + return UpdateResFPSLimits(); } QMessageBox::StandardButton button; @@ -1106,7 +1099,7 @@ void OBSBasicSettings::UpdateResFPSLimits() "setChecked", Qt::QueuedConnection, Q_ARG(bool, true)); - return; + return false; } } @@ -1187,7 +1180,8 @@ void OBSBasicSettings::UpdateResFPSLimits() /* ------------------------------------ */ lastIgnoreRecommended = (int)ignoreRecommended; - lastServiceIdx = idx; + + return true; } bool OBSBasicSettings::IsServiceOutputHasNetworkFeatures() @@ -1287,6 +1281,9 @@ static QString get_simple_fallback(const QString &enc) bool OBSBasicSettings::ServiceSupportsCodecCheck() { + if (loading) + return false; + if (ServiceAndCodecCompatible()) { if (lastServiceIdx != ui->service->currentIndex()) ResetEncoders(true); diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 51f00336f8e98c..7c2a9e2b0a793c 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -263,14 +263,18 @@ class OBSBasicSettings : public QDialog { QString lastService; int prevLangIndex; bool prevBrowserAccel; -private slots: + + void ServiceChanged(); void UpdateServerList(); void UpdateKeyLink(); void UpdateVodTrackSetting(); void UpdateServiceRecommendations(); - void RecreateOutputResolutionWidget(); - void UpdateResFPSLimits(); void UpdateMoreInfoLink(); + void UpdateAdvNetworkGroup(); + +private slots: + void RecreateOutputResolutionWidget(); + bool UpdateResFPSLimits(); void DisplayEnforceWarning(bool checked); void on_show_clicked(); void on_authPwShow_clicked(); @@ -382,6 +386,7 @@ private slots: void on_buttonBox_clicked(QAbstractButton *button); void on_service_currentIndexChanged(int idx); + void on_customServer_textChanged(const QString &text); void on_simpleOutputBrowse_clicked(); void on_advOutRecPathBrowse_clicked(); void on_advOutFFPathBrowse_clicked(); @@ -435,8 +440,6 @@ private slots: void UpdateStreamDelayEstimate(); - void UpdateAdvNetworkGroup(); - void UpdateAutomaticReplayBufferCheckboxes(); void AdvOutSplitFileChanged(); From e00e2712cf86bc0026828d4915029e517b15194c Mon Sep 17 00:00:00 2001 From: tytan652 Date: Wed, 28 Sep 2022 18:23:08 +0200 Subject: [PATCH 09/16] UI: Use protocol to list compatible codecs --- UI/window-basic-settings-stream.cpp | 119 +++++++++++++++++++++++++--- UI/window-basic-settings.hpp | 3 + 2 files changed, 109 insertions(+), 13 deletions(-) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 6dd8ecdbd9d615..5ca0c293b6256d 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -99,11 +99,13 @@ void OBSBasicSettings::LoadStream1Settings() const char *service = obs_data_get_string(settings, "service"); const char *server = obs_data_get_string(settings, "server"); const char *key = obs_data_get_string(settings, "key"); + protocol = QT_UTF8(obs_service_get_protocol(service_obj)); if (strcmp(type, "rtmp_custom") == 0) { ui->service->setCurrentIndex(0); ui->customServer->setText(server); lastServiceIdx = 0; + lastCustomServer = ui->customServer->text(); bool use_auth = obs_data_get_bool(settings, "use_auth"); const char *username = @@ -455,17 +457,26 @@ void OBSBasicSettings::on_service_currentIndexChanged(int idx) UpdateServiceRecommendations(); UpdateVodTrackSetting(); + + protocol = FindProtocol(); UpdateAdvNetworkGroup(); - if (ServiceSupportsCodecCheck() && UpdateResFPSLimits()) + if (ServiceSupportsCodecCheck() && UpdateResFPSLimits()) { lastServiceIdx = idx; + if (idx == 0) + lastCustomServer = ui->customServer->text(); + } } void OBSBasicSettings::on_customServer_textChanged(const QString &) { UpdateKeyLink(); + protocol = FindProtocol(); UpdateAdvNetworkGroup(); + + if (ServiceSupportsCodecCheck()) + lastCustomServer = ui->customServer->text(); } void OBSBasicSettings::ServiceChanged() @@ -520,6 +531,43 @@ void OBSBasicSettings::ServiceChanged() } } +QString OBSBasicSettings::FindProtocol() +{ + if (IsCustomService() && !ui->customServer->text().isEmpty()) { + + QString server = ui->customServer->text(); + + if (server.startsWith("rtmps://")) + return QString("RTMPS"); + + if (server.startsWith("ftl://")) + return QString("FTL"); + + if (server.startsWith("srt://")) + return QString("SRT"); + + if (server.startsWith("rist://")) + return QString("RIST"); + + } else { + obs_properties_t *props = + obs_get_service_properties("rtmp_common"); + obs_property_t *services = obs_properties_get(props, "service"); + + OBSDataAutoRelease settings = obs_data_create(); + + obs_data_set_string(settings, "service", + QT_TO_UTF8(ui->service->currentText())); + obs_property_modified(services, settings); + + obs_properties_destroy(props); + + return QT_UTF8(obs_data_get_string(settings, "protocol")); + } + + return QString("RTMP"); +} + void OBSBasicSettings::UpdateServerList() { QString serviceName = ui->service->currentText(); @@ -1228,17 +1276,19 @@ static inline bool service_supports_encoder(const char **codecs, return service_supports_codec(codecs, codec); } -bool OBSBasicSettings::ServiceAndCodecCompatible() +static bool return_first_id(void *data, const char *id) { - if (IsCustomService()) - return true; - if (ui->service->currentData().toInt() == (int)ListOpt::ShowAll) - return true; + const char **output = (const char **)data; + + *output = id; + return false; +} +bool OBSBasicSettings::ServiceAndCodecCompatible() +{ bool simple = (ui->outputMode->currentIndex() == 0); + bool ret; - OBSService service = SpawnTempService(); - const char **codecs = obs_service_get_supported_video_codecs(service); const char *codec; if (simple) { @@ -1251,7 +1301,29 @@ bool OBSBasicSettings::ServiceAndCodecCompatible() codec = obs_get_encoder_codec(QT_TO_UTF8(encoder)); } - return service_supports_codec(codecs, codec); + OBSService service = SpawnTempService(); + const char **codecs = obs_service_get_supported_video_codecs(service); + + if (!codecs || IsCustomService()) { + const char *output; + char **output_codecs; + + obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol), + &output, return_first_id); + + output_codecs = strlist_split( + obs_get_output_supported_video_codecs(output), ';', + false); + + ret = service_supports_codec((const char **)output_codecs, + codec); + + strlist_free(output_codecs); + } else { + ret = service_supports_codec(codecs, codec); + } + + return ret; } /* we really need a way to find fallbacks in a less hardcoded way. maybe. */ @@ -1285,7 +1357,8 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() return false; if (ServiceAndCodecCompatible()) { - if (lastServiceIdx != ui->service->currentIndex()) + if (lastServiceIdx != ui->service->currentIndex() || + IsCustomService()) ResetEncoders(true); return true; } @@ -1324,9 +1397,17 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() #undef WARNING_VAL if (button == QMessageBox::No) { - QMetaObject::invokeMethod(ui->service, "setCurrentIndex", - Qt::QueuedConnection, - Q_ARG(int, lastServiceIdx)); + if (lastServiceIdx == 0 && + lastServiceIdx == ui->service->currentIndex()) + QMetaObject::invokeMethod(ui->customServer, "setText", + Qt::QueuedConnection, + Q_ARG(QString, + lastCustomServer)); + else + QMetaObject::invokeMethod(ui->service, + "setCurrentIndex", + Qt::QueuedConnection, + Q_ARG(int, lastServiceIdx)); return false; } @@ -1344,8 +1425,20 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) OBSService service = SpawnTempService(); const char **codecs = obs_service_get_supported_video_codecs(service); const char *type; + BPtr output_codecs; size_t idx = 0; + if (!codecs || IsCustomService()) { + const char *output; + + obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol), + &output, return_first_id); + output_codecs = strlist_split( + obs_get_output_supported_video_codecs(output), ';', + false); + codecs = (const char **)output_codecs.Get(); + } + QSignalBlocker s1(ui->simpleOutStrEncoder); QSignalBlocker s2(ui->advOutEncoder); diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 7c2a9e2b0a793c..18a6e43b473323 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -261,10 +261,13 @@ class OBSBasicSettings : public QDialog { void OnOAuthStreamKeyConnected(); void OnAuthConnected(); QString lastService; + QString protocol; + QString lastCustomServer; int prevLangIndex; bool prevBrowserAccel; void ServiceChanged(); + QString FindProtocol(); void UpdateServerList(); void UpdateKeyLink(); void UpdateVodTrackSetting(); From 9d8d98b142135304f82572936572865dcfbb51b7 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Fri, 7 Oct 2022 15:10:43 +0200 Subject: [PATCH 10/16] rtmp-services: Remove fallback to H264 if no codec found The fallback in now inside the output. --- plugins/rtmp-services/rtmp-common.c | 31 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 984053ffb790ec..3a62fbc00cd7d7 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -117,7 +117,8 @@ static void rtmp_common_update(void *data, obs_data_t *settings) struct rtmp_common *service = data; bfree(service->supported_resolutions); - bfree(service->video_codecs); + if (service->video_codecs) + bfree(service->video_codecs); bfree(service->service); bfree(service->protocol); bfree(service->server); @@ -167,7 +168,8 @@ static void rtmp_common_destroy(void *data) struct rtmp_common *service = data; bfree(service->supported_resolutions); - bfree(service->video_codecs); + if (service->video_codecs) + bfree(service->video_codecs); bfree(service->service); bfree(service->protocol); bfree(service->server); @@ -945,21 +947,20 @@ static const char **rtmp_common_get_supported_video_codecs(void *data) json_t *json_video_codecs = json_object_get(json_service, "supported video codecs"); - if (json_is_array(json_video_codecs)) { - size_t index; - json_t *item; + if (!json_is_array(json_video_codecs)) { + goto fail; + } - json_array_foreach (json_video_codecs, index, item) { - char codec[16]; + size_t index; + json_t *item; - snprintf(codec, sizeof(codec), "%s", - json_string_value(item)); - if (codecs.len) - dstr_cat(&codecs, ";"); - dstr_cat(&codecs, codec); - } - } else { - dstr_cat(&codecs, "h264;"); + json_array_foreach (json_video_codecs, index, item) { + char codec[16]; + + snprintf(codec, sizeof(codec), "%s", json_string_value(item)); + if (codecs.len) + dstr_cat(&codecs, ";"); + dstr_cat(&codecs, codec); } service->video_codecs = strlist_split(codecs.array, ';', false); From 44ca002d02ea2586a2cbb6de9c9449e88766e723 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Wed, 28 Sep 2022 18:23:47 +0200 Subject: [PATCH 11/16] UI: Use protocol to enable network options --- UI/window-basic-settings-stream.cpp | 17 ----------------- UI/window-basic-settings.cpp | 2 +- UI/window-basic-settings.hpp | 2 -- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 5ca0c293b6256d..4a3530559007b7 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -1232,23 +1232,6 @@ bool OBSBasicSettings::UpdateResFPSLimits() return true; } -bool OBSBasicSettings::IsServiceOutputHasNetworkFeatures() -{ - if (IsCustomService()) - return ui->customServer->text().startsWith("rtmp"); - - OBSServiceAutoRelease service = SpawnTempService(); - const char *output = obs_service_get_output_type(service); - - if (!output) - return true; - - if (strcmp(output, "rtmp_output") == 0) - return true; - - return false; -} - static bool service_supports_codec(const char **codecs, const char *codec) { if (!codecs) diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 2a5ac8d88287d6..0e372b8397efc2 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -5621,7 +5621,7 @@ void OBSBasicSettings::RecreateOutputResolutionWidget() void OBSBasicSettings::UpdateAdvNetworkGroup() { - bool enabled = IsServiceOutputHasNetworkFeatures(); + bool enabled = protocol.contains("RTMP"); ui->advNetworkDisabled->setVisible(!enabled); diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index 18a6e43b473323..e535f88a25cfc2 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -377,8 +377,6 @@ private slots: OBSService GetStream1Service(); - bool IsServiceOutputHasNetworkFeatures(); - bool ServiceAndCodecCompatible(); bool ServiceSupportsCodecCheck(); From 8d6cacc35b51b84641ff435faf1f2ce7f63c2955 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Wed, 28 Sep 2022 18:24:18 +0200 Subject: [PATCH 12/16] UI: Remove hardcoded stream codec list --- UI/window-basic-settings-stream.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index 4a3530559007b7..a82cf3b53aad47 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -1447,27 +1447,13 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) if (obs_get_encoder_type(type) != OBS_ENCODER_VIDEO) continue; - const char *streaming_codecs[] = { - "h264", -#ifdef ENABLE_HEVC - "hevc", -#endif - }; - - bool is_streaming_codec = false; - for (const char *test_codec : streaming_codecs) { - if (strcmp(codec, test_codec) == 0) { - is_streaming_codec = true; - break; - } - } if ((caps & ENCODER_HIDE_FLAGS) != 0) continue; QString qName = QT_UTF8(name); QString qType = QT_UTF8(type); - if (is_streaming_codec && service_supports_codec(codecs, codec)) + if (service_supports_codec(codecs, codec)) ui->advOutEncoder->addItem(qName, qType); if (!streamOnly) ui->advOutRecEncoder->addItem(qName, qType); From e317c88f5556fa5395fc8a0d652817018868fbda Mon Sep 17 00:00:00 2001 From: tytan652 Date: Thu, 19 Jan 2023 10:50:26 +0100 Subject: [PATCH 13/16] obs-ffmpeg: Remove AV1 from SRT/RIST supported codecs MPEG-TS standard is not ready for AV1 --- plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c | 4 ++-- plugins/obs-ffmpeg/obs-ffmpeg-mux.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c index f9cc4ae7e38b5d..15014a25bd8ce6 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mpegts.c @@ -1291,9 +1291,9 @@ struct obs_output_info ffmpeg_mpegts_muxer = { OBS_OUTPUT_SERVICE, .protocols = "SRT;RIST", #ifdef ENABLE_HEVC - .encoded_video_codecs = "h264;hevc;av1", + .encoded_video_codecs = "h264;hevc", #else - .encoded_video_codecs = "h264;av1", + .encoded_video_codecs = "h264", #endif .encoded_audio_codecs = "aac", .get_name = ffmpeg_mpegts_getname, diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c index d4156b5153e283..8d8539fbe3531b 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-mux.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-mux.c @@ -945,7 +945,7 @@ struct obs_output_info ffmpeg_mpegts_muxer = { .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK | OBS_OUTPUT_SERVICE, .protocols = "SRT;RIST", - .encoded_video_codecs = "h264;av1", + .encoded_video_codecs = "h264", .encoded_audio_codecs = "aac", .get_name = ffmpeg_mpegts_mux_getname, .create = ffmpeg_mux_create, From 1e0f4a6ebfc9cb57004f43e65c75c6a878638e8c Mon Sep 17 00:00:00 2001 From: tytan652 Date: Fri, 7 Oct 2022 11:30:45 +0200 Subject: [PATCH 14/16] libobs,docs: Add preferred output type to Service API --- docs/sphinx/reference-services.rst | 8 +++++++- libobs/obs-service.c | 27 +++++++++++++++++---------- libobs/obs-service.h | 1 + libobs/obs.h | 6 +++++- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/sphinx/reference-services.rst b/docs/sphinx/reference-services.rst index 7c6a89dfe379b1..2cd8a624850a17 100644 --- a/docs/sphinx/reference-services.rst +++ b/docs/sphinx/reference-services.rst @@ -137,7 +137,7 @@ Service Definition Structure (Optional) - :return: The output type that should be used with this service + :return: The output type that should be preferred with this service .. member:: const char **(*get_supported_video_codecs)(void *data) @@ -315,6 +315,12 @@ General Service Functions :return: Protocol currently used for this service +--------------------- + +.. function:: const char *obs_service_get_preferred_output_type(const obs_service_t *service) + + :return: The output type that should be preferred with this service + .. --------------------------------------------------------------------------- .. _libobs/obs-service.h: https://github.com/obsproject/obs-studio/blob/master/libobs/obs-service.h diff --git a/libobs/obs-service.c b/libobs/obs-service.c index 607844f30016e8..9b7b5f085f46e3 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -411,16 +411,6 @@ const char *obs_service_get_id(const obs_service_t *service) : NULL; } -const char *obs_service_get_output_type(const obs_service_t *service) -{ - if (!obs_service_valid(service, "obs_service_get_output_type")) - return NULL; - - if (service->info.get_output_type) - return service->info.get_output_type(service->context.data); - return NULL; -} - void obs_service_get_supported_resolutions( const obs_service_t *service, struct obs_service_resolution **resolutions, size_t *count) @@ -485,3 +475,20 @@ const char *obs_service_get_protocol(const obs_service_t *service) return service->info.get_protocol(service->context.data); } + +/* OBS_DEPRECATED */ +const char *obs_service_get_output_type(const obs_service_t *service) +{ + return obs_service_get_preferred_output_type(service); +} + +const char *obs_service_get_preferred_output_type(const obs_service_t *service) +{ + if (!obs_service_valid(service, + "obs_service_get_preferred_output_type")) + return NULL; + + if (service->info.get_output_type) + return service->info.get_output_type(service->context.data); + return NULL; +} diff --git a/libobs/obs-service.h b/libobs/obs-service.h index e02ed822e7776f..56a93ad85a7ce5 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -77,6 +77,7 @@ struct obs_service_info { void *type_data; void (*free_type_data)(void *type_data); + /* TODO: Rename to 'get_preferred_output_type' once a API/ABI break happen */ const char *(*get_output_type)(void *data); void (*get_supported_resolutions)( diff --git a/libobs/obs.h b/libobs/obs.h index fc3ef201930b58..24983fead4eb16 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -2547,11 +2547,15 @@ obs_service_get_supported_video_codecs(const obs_service_t *service); /* NOTE: This function is temporary and should be removed/replaced at a later * date. */ -EXPORT const char *obs_service_get_output_type(const obs_service_t *service); +OBS_DEPRECATED EXPORT const char * +obs_service_get_output_type(const obs_service_t *service); /** Returns the protocol for this service context */ EXPORT const char *obs_service_get_protocol(const obs_service_t *service); +EXPORT const char * +obs_service_get_preferred_output_type(const obs_service_t *service); + /* ------------------------------------------------------------------------- */ /* Source frame allocation functions */ EXPORT void obs_source_frame_init(struct obs_source_frame *frame, From 213eb613d3d276be30db1d8012162ced8b0d9288 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Fri, 7 Oct 2022 11:31:22 +0200 Subject: [PATCH 15/16] rtmp-services: Remove output getter from rtmp_common --- .../data/schema/service-schema-v4.json | 2 +- plugins/rtmp-services/rtmp-common.c | 18 ------------------ 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/plugins/rtmp-services/data/schema/service-schema-v4.json b/plugins/rtmp-services/data/schema/service-schema-v4.json index e5afe45851ea15..88b33abd3a93b8 100644 --- a/plugins/rtmp-services/data/schema/service-schema-v4.json +++ b/plugins/rtmp-services/data/schema/service-schema-v4.json @@ -118,7 +118,7 @@ }, "output": { "type": "string", - "description": "OBS output module used.", + "description": "OBS output module used. Unused since OBS Studio 30, kept for backward compatibility.", "enum": [ "rtmp_output", "ffmpeg_hls_muxer", diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 3a62fbc00cd7d7..94bba3f6c8baba 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -17,7 +17,6 @@ struct rtmp_common { char *server; char *key; - char *output; struct obs_service_resolution *supported_resolutions; size_t supported_resolutions_count; int max_fps; @@ -77,10 +76,6 @@ static void ensure_valid_url(struct rtmp_common *service, json_t *json, static void update_recommendations(struct rtmp_common *service, json_t *rec) { - const char *out = get_string_val(rec, "output"); - if (out) - service->output = bstrdup(out); - json_t *sr = json_object_get(rec, "supported resolutions"); if (sr && json_is_array(sr)) { DARRAY(struct obs_service_resolution) res_list; @@ -122,7 +117,6 @@ static void rtmp_common_update(void *data, obs_data_t *settings) bfree(service->service); bfree(service->protocol); bfree(service->server); - bfree(service->output); bfree(service->key); service->service = bstrdup(obs_data_get_string(settings, "service")); @@ -130,7 +124,6 @@ static void rtmp_common_update(void *data, obs_data_t *settings) service->server = bstrdup(obs_data_get_string(settings, "server")); service->key = bstrdup(obs_data_get_string(settings, "key")); service->supports_additional_audio_track = false; - service->output = NULL; service->video_codecs = NULL; service->supported_resolutions = NULL; service->supported_resolutions_count = 0; @@ -158,9 +151,6 @@ static void rtmp_common_update(void *data, obs_data_t *settings) } } json_decref(root); - - if (!service->output) - service->output = bstrdup("rtmp_output"); } static void rtmp_common_destroy(void *data) @@ -173,7 +163,6 @@ static void rtmp_common_destroy(void *data) bfree(service->service); bfree(service->protocol); bfree(service->server); - bfree(service->output); bfree(service->key); bfree(service); } @@ -740,12 +729,6 @@ static void rtmp_common_apply_settings(void *data, obs_data_t *video_settings, } } -static const char *rtmp_common_get_output_type(void *data) -{ - struct rtmp_common *service = data; - return service->output; -} - static const char *rtmp_common_url(void *data) { struct rtmp_common *service = data; @@ -1017,7 +1000,6 @@ struct obs_service_info rtmp_common_service = { .get_username = rtmp_common_username, .get_password = rtmp_common_password, .apply_encoder_settings = rtmp_common_apply_settings, - .get_output_type = rtmp_common_get_output_type, .get_supported_resolutions = rtmp_common_get_supported_resolutions, .get_max_fps = rtmp_common_get_max_fps, .get_max_bitrate = rtmp_common_get_max_bitrate, From ea2858705cee8da9189604454925c0f28ca3b596 Mon Sep 17 00:00:00 2001 From: tytan652 Date: Fri, 7 Oct 2022 11:42:12 +0200 Subject: [PATCH 16/16] UI: Select streaming output based on the protocol --- UI/data/locale/en-US.ini | 1 + UI/window-basic-auto-config-test.cpp | 52 ++++++++++++++- UI/window-basic-main-outputs.cpp | 97 +++++++++++++++++++++------- 3 files changed, 123 insertions(+), 27 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index cc32edf6ebbe47..a5dd0da3a9395c 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -218,6 +218,7 @@ Basic.AutoConfig.TestPage="Final Results" Basic.AutoConfig.TestPage.SubTitle.Testing="The program is now executing a set of tests to estimate the ideal settings" Basic.AutoConfig.TestPage.SubTitle.Complete="Testing complete" Basic.AutoConfig.TestPage.TestingBandwidth="Performing bandwidth test, this may take a few minutes..." +Basic.AutoConfig.TestPage.TestingBandwidth.NoOutput="No output for the protocol of this service was found" Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Connecting to: %1..." Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Failed to connect to any servers, please check your internet connection and try again." Basic.AutoConfig.TestPage.TestingBandwidth.Server="Testing bandwidth for: %1" diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index ceb2e1cfbaa026..c6d7a694267623 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -88,6 +88,7 @@ class TestMode { #define SUBTITLE_TESTING TEST_STR("Subtitle.Testing") #define SUBTITLE_COMPLETE TEST_STR("Subtitle.Complete") #define TEST_BW TEST_STR("TestingBandwidth") +#define TEST_BW_NO_OUTPUT TEST_STR("TestingBandwidth.NoOutput") #define TEST_BW_CONNECTING TEST_STR("TestingBandwidth.Connecting") #define TEST_BW_CONNECT_FAIL TEST_STR("TestingBandwidth.ConnectFailed") #define TEST_BW_SERVER TEST_STR("TestingBandwidth.Server") @@ -155,6 +156,23 @@ static inline void string_depad_key(string &key) const char *FindAudioEncoderFromCodec(const char *type); +static inline bool can_use_output(const char *prot, const char *output, + const char *prot_test1, + const char *prot_test2 = nullptr) +{ + return (strcmp(prot, prot_test1) == 0 || + (prot_test2 && strcmp(prot, prot_test2) == 0)) && + (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; +} + +static bool return_first_id(void *data, const char *id) +{ + const char **output = (const char **)data; + + *output = id; + return false; +} + void AutoConfigTestPage::TestBandwidthThread() { bool connected = false; @@ -268,9 +286,37 @@ void AutoConfigTestPage::TestBandwidthThread() /* -----------------------------------*/ /* create output */ - const char *output_type = obs_service_get_output_type(service); - if (!output_type) - output_type = "rtmp_output"; + /* Check if the service has a preferred output type */ + const char *output_type = + obs_service_get_preferred_output_type(service); + if (!output_type || + (obs_get_output_flags(output_type) & OBS_OUTPUT_SERVICE) == 0) { + /* Otherwise, prefer first-party output types */ + const char *protocol = obs_service_get_protocol(service); + + if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { + output_type = "rtmp_output"; + } else if (can_use_output(protocol, "ffmpeg_hls_muxer", + "HLS")) { + output_type = "ffmpeg_hls_muxer"; + } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", + "SRT", "RIST")) { + output_type = "ffmpeg_mpegts_muxer"; + } + + /* If third-party protocol, use the first enumerated type */ + if (!output_type) + obs_enum_output_types_with_protocol( + protocol, &output_type, return_first_id); + + /* If none, fail */ + if (!output_type) { + QMetaObject::invokeMethod( + this, "Failure", + Q_ARG(QString, QTStr(TEST_BW_NO_OUTPUT))); + return; + } + } OBSOutputAutoRelease output = obs_output_create(output_type, "test_stream", nullptr, nullptr); diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 8a78d3240a45a7..534b0d69f52f3d 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -210,6 +210,73 @@ static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate, return false; } +static inline bool can_use_output(const char *prot, const char *output, + const char *prot_test1, + const char *prot_test2 = nullptr) +{ + return (strcmp(prot, prot_test1) == 0 || + (prot_test2 && strcmp(prot, prot_test2) == 0)) && + (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; +} + +static bool return_first_id(void *data, const char *id) +{ + const char **output = (const char **)data; + + *output = id; + return false; +} + +static const char *GetStreamOutputType(const obs_service_t *service) +{ + const char *protocol = obs_service_get_protocol(service); + const char *output = nullptr; + + if (!protocol) { + blog(LOG_WARNING, "The service '%s' has no protocol set", + obs_service_get_id(service)); + return nullptr; + } + + if (!obs_is_output_protocol_registered(protocol)) { + blog(LOG_WARNING, "The protocol '%s' is not registered", + protocol); + return nullptr; + } + + /* Check if the service has a preferred output type */ + output = obs_service_get_preferred_output_type(service); + if (output) { + if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) + return output; + + blog(LOG_WARNING, + "The output '%s' is not registered, fallback to another one", + output); + } + + /* Otherwise, prefer first-party output types */ + if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { + return "rtmp_output"; + } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { + return "ffmpeg_hls_muxer"; + } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", + "RIST")) { + return "ffmpeg_mpegts_muxer"; + } + + /* If third-party protocol, use the first enumerated type */ + obs_enum_output_types_with_protocol(protocol, &output, return_first_id); + if (output) + return output; + + blog(LOG_WARNING, + "No output compatible with the service '%s' is registered", + obs_service_get_id(service)); + + return nullptr; +} + /* ------------------------------------------------------------------------ */ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) @@ -913,18 +980,9 @@ bool SimpleOutput::SetupStreaming(obs_service_t *service) /* --------------------- */ - const char *type = obs_service_get_output_type(service); - if (!type) { - type = "rtmp_output"; - const char *url = obs_service_get_url(service); - if (url != NULL && - strncmp(url, FTL_PROTOCOL, strlen(FTL_PROTOCOL)) == 0) { - type = "ftl_output"; - } else if (url != NULL && strncmp(url, RTMP_PROTOCOL, - strlen(RTMP_PROTOCOL)) != 0) { - type = "ffmpeg_mpegts_muxer"; - } - } + const char *type = GetStreamOutputType(service); + if (!type) + return false; /* XXX: this is messy and disgusting and should be refactored */ if (outputType != type) { @@ -1898,18 +1956,9 @@ bool AdvancedOutput::SetupStreaming(obs_service_t *service) /* --------------------- */ - const char *type = obs_service_get_output_type(service); - if (!type) { - type = "rtmp_output"; - const char *url = obs_service_get_url(service); - if (url != NULL && - strncmp(url, FTL_PROTOCOL, strlen(FTL_PROTOCOL)) == 0) { - type = "ftl_output"; - } else if (url != NULL && strncmp(url, RTMP_PROTOCOL, - strlen(RTMP_PROTOCOL)) != 0) { - type = "ffmpeg_mpegts_muxer"; - } - } + const char *type = GetStreamOutputType(service); + if (!type) + return false; /* XXX: this is messy and disgusting and should be refactored */ if (outputType != type) {