From 8830c4102fdaadc95a3cadc6e088b5becd7ecba7 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Thu, 24 Apr 2014 01:49:07 -0700 Subject: [PATCH] obs-studio UI: Implement stream settings UI - Updated the services API so that it links up with an output and the output gets data from that service rather than via settings. This allows the service context to have control over how an output is used, and makes it so that the URL/key/etc isn't necessarily some static setting. Also, if the service is attached to an output, it will stick around until the output is destroyed. - The settings interface has been updated so that it can allow the usage of service plugins. What this means is that now you can create a service plugin that can control aspects of the stream, and it allows each service to create their own user interface if they create a service plugin module. - Testing out saving of current service information. Saves/loads from JSON in to obs_data_t, seems to be working quite nicely, and the service object information is saved/preserved on exit, and loaded again on startup. - I agonized over the settings user interface for days, and eventually I just decided that the only way that users weren't going to be fumbling over options was to split up the settings in to simple/basic output, pre-configured, and then advanced for advanced use (such as multiple outputs or services, which I'll implement later). This was particularly painful to really design right, I wanted more features and wanted to include everything in one interface but ultimately just realized from experience that users are just not technically knowledgable about it and will end up fumbling with the settings rather than getting things done. Basically, what this means is that casual users only have to enter in about 3 things to configure their stream: Stream key, audio bitrate, and video bitrate. I am really happy with this interface for those types of users, but it definitely won't be sufficient for advanced usage or for custom outputs, so that stuff will have to be separated. - Improved the JSON usage for the 'common streaming services' context, I realized that JSON arrays are there to ensure sorting, while forgetting that general items are optimized for hashing. So basically I'm just using arrays now to sort items in it. --- .../obs-plugins/rtmp-services/services.json | 331 ++++++++----- build/data/obs-studio/locale/en.txt | 9 +- libobs/CMakeLists.txt | 1 + libobs/obs-encoder.c | 56 ++- libobs/obs-internal.h | 8 + libobs/obs-module.c | 2 +- libobs/obs-output.c | 64 ++- libobs/obs-service.c | 74 ++- libobs/obs-service.h | 6 + libobs/obs.c | 40 ++ libobs/obs.h | 52 ++- libobs/util/darray.h | 2 - obs/forms/OBSBasicProperties.ui | 4 +- obs/forms/OBSBasicSettings.ui | 433 ++++++++++++++---- obs/forms/images/settings/network.png | Bin 0 -> 4332 bytes obs/forms/obs.qrc | 1 + obs/window-basic-main.cpp | 261 +++++++---- obs/window-basic-main.hpp | 41 +- obs/window-basic-settings.cpp | 193 +++++--- obs/window-basic-settings.hpp | 15 +- plugins/obs-outputs/rtmp-stream.c | 13 +- plugins/obs-x264/obs-x264.c | 15 + plugins/rtmp-services/rtmp-common.c | 83 +++- vs/2013/jansson/jansson.vcxproj | 20 +- vs/2013/libobs/libobs.vcxproj | 8 +- vs/2013/rtmp-services/rtmp-services.vcxproj | 12 + 26 files changed, 1298 insertions(+), 446 deletions(-) create mode 100644 obs/forms/images/settings/network.png diff --git a/build/data/obs-plugins/rtmp-services/services.json b/build/data/obs-plugins/rtmp-services/services.json index 7786d6046..2ff614037 100644 --- a/build/data/obs-plugins/rtmp-services/services.json +++ b/build/data/obs-plugins/rtmp-services/services.json @@ -1,102 +1,229 @@ -{ - "Twitch / Justin.tv": { - "servers": { - "US West: San Francisco, CA" : "rtmp://live.justin.tv/app", - "Asia: Singapore" : "rtmp://live-sin-backup.justin.tv/app", - "EU: Amsterdam, NL" : "rtmp://live-ams.justin.tv/app", - "EU: Frankfurt, DE" : "rtmp://live-fra.justin.tv/app", - "EU: London, UK" : "rtmp://live-lhr.justin.tv/app", - "EU: Paris, FR" : "rtmp://live-cdg.justin.tv/app", - "EU: Prague, CZ" : "rtmp://live-prg.justin.tv/app", - "EU: Stockholm, SE" : "rtmp://live-arn.justin.tv/app", - "US Central: Dallas, TX" : "rtmp://live-dfw.justin.tv/app", - "US East: Ashburn, VA" : "rtmp://live-iad.justin.tv/app", - "US East: Miami, FL" : "rtmp://live-mia.justin.tv/app", - "US East: New York, NY" : "rtmp://live-jfk.justin.tv/app", - "US Midwest: Chicago, IL" : "rtmp://live-ord.justin.tv/app", - "US West: Los Angeles, CA" : "rtmp://live-lax.justin.tv/app" - }, - "recommended": { - "keyint" : 2, - "cbr" : true, - "profile" : "main", - "max video bitrate" : 3500, - "max audio bitrate" : 160 - } - }, - "Youtube": { - "servers": { - "Primary Youtube ingest server" : "rtmp://a.rtmp.youtube.com/live2", - "Backup Youtube ingest server" : "rtmp://b.rtmp.youtube.com/live2?backup=1" - }, - "recommended": { - "keyint" : 2, - "cbr" : true, - "profile" : "main", - "max video bitrate" : 3500, - "max audio bitrate" : 160 - } - }, - "hitbox.tv": { - "servers": { - "Default" : "rtmp://live.hitbox.tv/push", - "EU-East" : "rtmp://live.vie.hitbox.tv/push", - "EU-Central" : "rtmp://live.nbg.hitbox.tv/push", - "EU-West" : "rtmp://live.fra.hitbox.tv/push", - "EU-North" : "rtmp://live.ams.hitbox.tv/push", - "US-East" : "rtmp://live.vgn.hitbox.tv/push", - "US-West" : "rtmp://live.lax.hitbox.tv/push", - "South America" : "rtmp://live.gru.hitbox.tv/push", - "Asia" : "rtmp://live.lax.hitbox.tv/push" - }, - "recommended": { - "keyint" : 2, - "cbr" : true, - "profile" : "main", - "max video bitrate" : 3500, - "max audio bitrate" : 160 - } - }, - "Vaughn Live / iNSTAGIB.tv": { - "servers": { - "US: Primary" : "rtmp://live.vaughnsoft.net:443/live", - "US: Virginia, USA" : "rtmp://live-iad.vaughnsoft.net:443/live", - "US: Chicago, IL" : "rtmp://live-ord.vaughnsoft.net:443/live", - "EU: Frankfurt, Germany" : "rtmp://live-de.vaughnsoft.net:443/live" - } - }, - "DailyMotion": { - "servers": { - "Primary" : "rtmp://publish.dailymotion.com/publish-dm" - } - }, - "connectcast.tv": { - "servers": { - "Default" : "rtmp://stream.connectcast.tv/live" - } - }, - "iNSTAGIB.tv": { - "servers": { - "US Chicago (Primary)" : "rtmp://live.instagib.tv:443/live" - } - }, - "GoodGame.ru": { - "servers": { - "Moscow M9" : "rtmp://stream.goodgame.ru:1940/live", - "Moscow M10" : "rtmp://stream2.goodgame.ru:1940/live", - "Saint-Petersburg" : "rtmp://spb1.goodgame.ru:1940/live" - } - }, - "CyberGame.TV": { - "servers": { - "RU Origin" : "rtmp://st.cybergame.tv:1953/live", - "RU Premium" : "rtmp://premium.cybergame.tv:1953/premium" - } - }, - "CashPlay.tv": { - "servers": { - "Primary, UK" : "rtmp://live.cashplay.tv/live", - "Low Priority, DE" : "rtmp://de.live.cashplay.tv/live" - } - } -} +[ + { + "name": "Twitch / Justin.tv", + "servers": [ + { + "name": "US West: San Francisco, CA", + "url": "rtmp://live.justin.tv/app" + }, + { + "name": "Asia: Singapore", + "url": "rtmp://live-sin-backup.justin.tv/app" + }, + { + "name": "EU: Amsterdam, NL", + "url": "rtmp://live-ams.justin.tv/app" + }, + { + "name": "EU: Frankfurt, DE", + "url": "rtmp://live-fra.justin.tv/app" + }, + { + "name": "EU: London, UK", + "url": "rtmp://live-lhr.justin.tv/app" + }, + { + "name": "EU: Paris, FR", + "url": "rtmp://live-cdg.justin.tv/app" + }, + { + "name": "EU: Prague, CZ", + "url": "rtmp://live-prg.justin.tv/app" + }, + { + "name": "EU: Stockholm, SE", + "url": "rtmp://live-arn.justin.tv/app" + }, + { + "name": "US Central: Dallas, TX", + "url": "rtmp://live-dfw.justin.tv/app" + }, + { + "name": "US East: Ashburn, VA", + "url": "rtmp://live-iad.justin.tv/app" + }, + { + "name": "US East: Miami, FL", + "url": "rtmp://live-mia.justin.tv/app" + }, + { + "name": "US East: New York, NY", + "url": "rtmp://live-jfk.justin.tv/app" + }, + { + "name": "US Midwest: Chicago, IL", + "url": "rtmp://live-ord.justin.tv/app" + }, + { + "name": "US West: Los Angeles, CA", + "url": "rtmp://live-lax.justin.tv/app" + } + ], + "recommended": { + "keyint": 2, + "cbr": true, + "profile": "main", + "max video bitrate": 3500, + "max audio bitrate": 160 + } + }, + { + "name": "Youtube", + "servers": [ + { + "name": "Primary Youtube ingest server", + "url": "rtmp://a.rtmp.youtube.com/live2" + }, + { + "name": "Backup Youtube ingest server", + "url": "rtmp://b.rtmp.youtube.com/live2?backup=1" + } + ], + "recommended": { + "keyint": 2, + "cbr": true, + "profile": "main", + "max video bitrate": 3500, + "max audio bitrate": 160 + } + }, + { + "name": "hitbox.tv", + "servers": [ + { + "name": "Default", + "url": "rtmp://live.hitbox.tv/push" + }, + { + "name": "EU-East", + "url": "rtmp://live.vie.hitbox.tv/push" + }, + { + "name": "EU-Central", + "url": "rtmp://live.nbg.hitbox.tv/push" + }, + { + "name": "EU-West", + "url": "rtmp://live.fra.hitbox.tv/push" + }, + { + "name": "EU-North", + "url": "rtmp://live.ams.hitbox.tv/push" + }, + { + "name": "US-East", + "url": "rtmp://live.vgn.hitbox.tv/push" + }, + { + "name": "US-West", + "url": "rtmp://live.lax.hitbox.tv/push" + }, + { + "name": "South America", + "url": "rtmp://live.gru.hitbox.tv/push" + }, + { + "name": "Asia", + "url": "rtmp://live.lax.hitbox.tv/push" + } + ], + "recommended": { + "keyint": 2, + "cbr": true, + "profile": "main", + "max video bitrate": 3500, + "max audio bitrate": 160 + } + }, + { + "name": "Vaughn Live / iNSTAGIB.tv", + "servers": [ + { + "name": "US: Primary", + "url": "rtmp://live.vaughnsoft.net:443/live" + }, + { + "name": "US: Virginia, USA", + "url": "rtmp://live-iad.vaughnsoft.net:443/live" + }, + { + "name": "US: Chicago, IL", + "url": "rtmp://live-ord.vaughnsoft.net:443/live" + }, + { + "name": "EU: Frankfurt, Germany", + "url": "rtmp://live-de.vaughnsoft.net:443/live" + } + ] + }, + { + "name": "DailyMotion", + "servers": [ + { + "name": "Primary", + "url": "rtmp://publish.dailymotion.com/publish-dm" + } + ] + }, + { + "name": "connectcast.tv", + "servers": [ + { + "name": "Default", + "url": "rtmp://stream.connectcast.tv/live" + } + ] + }, + { + "name": "iNSTAGIB.tv", + "servers": [ + { + "name": "US Chicago (Primary)", + "url": "rtmp://live.instagib.tv:443/live" + } + ] + }, + { + "name": "GoodGame.ru", + "servers": [ + { + "name": "Moscow M9", + "url": "rtmp://stream.goodgame.ru:1940/live" + }, + { + "name": "Moscow M10", + "url": "rtmp://stream2.goodgame.ru:1940/live" + }, + { + "name": "Saint-Petersburg", + "url": "rtmp://spb1.goodgame.ru:1940/live" + } + ] + }, + { + "name": "CyberGame.TV", + "servers": [ + { + "name": "RU Origin", + "url": "rtmp://st.cybergame.tv:1953/live" + }, + { + "name": "RU Premium", + "url": "rtmp://premium.cybergame.tv:1953/premium" + } + ] + }, + { + "name": "CashPlay.tv", + "servers": [ + { + "name": "Primary, UK", + "url": "rtmp://live.cashplay.tv/live" + }, + { + "name": "Low Priority, DE", + "url": "rtmp://de.live.cashplay.tv/live" + } + ] + } +] diff --git a/build/data/obs-studio/locale/en.txt b/build/data/obs-studio/locale/en.txt index fc7e16028..232ea1c2e 100644 --- a/build/data/obs-studio/locale/en.txt +++ b/build/data/obs-studio/locale/en.txt @@ -1,8 +1,5 @@ Language="English" -OK="OK" -Cancel="Cancel" - MainMenu.File="File" MainMenu.File.New="New" @@ -41,6 +38,12 @@ Settings.Confirm="You have unsaved changes. Save changes?" Settings.General="General" Settings.General.Language="Language:" +Settings.Streams="Streams" +Settings.Streams.AddName.Title="Add Stream" +Settings.Streams.AddName.Text="Please enter the name of the stream" +Settings.Streams.Exists.Title="Stream already exists" +Settings.Streams.Exists.Text="The name is already in use by another stream" + Settings.Outputs="Outputs" Settings.Video="Video" diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index b310b3f48..f00e1bb25 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -224,6 +224,7 @@ set_target_properties(libobs PROPERTIES VERSION "0" SOVERSION "0") target_link_libraries(libobs + jansson ${libobs_PLATFORM_DEPS} ${Libswscale_LIBRARIES} ${Libswresample_LIBRARIES} diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 9e7347f32..0fa02fe37 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -57,8 +57,7 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name, static struct obs_encoder *create_encoder(const char *id, enum obs_encoder_type type, const char *name, - obs_data_t settings, void *media, - uint32_t timebase_num, uint32_t timebase_den) + obs_data_t settings) { struct obs_encoder *encoder; struct obs_encoder_info *ei = get_encoder_info(id); @@ -68,10 +67,7 @@ static struct obs_encoder *create_encoder(const char *id, return NULL; encoder = bzalloc(sizeof(struct obs_encoder)); - encoder->info = *ei; - encoder->media = media; - encoder->timebase_num = timebase_num; - encoder->timebase_den = timebase_den; + encoder->info = *ei; success = init_encoder(encoder, name, settings); if (!success) { @@ -87,29 +83,17 @@ static struct obs_encoder *create_encoder(const char *id, } obs_encoder_t obs_video_encoder_create(const char *id, const char *name, - obs_data_t settings, video_t video) + obs_data_t settings) { - const struct video_output_info *voi; - - if (!name || !id || !video) - return NULL; - - voi = video_output_getinfo(video); - return create_encoder(id, OBS_ENCODER_VIDEO, name, settings, video, - voi->fps_den, voi->fps_num); + if (!name || !id) return NULL; + return create_encoder(id, OBS_ENCODER_VIDEO, name, settings); } obs_encoder_t obs_audio_encoder_create(const char *id, const char *name, - obs_data_t settings, audio_t audio) + obs_data_t settings) { - const struct audio_output_info *aoi; - - if (!name || !id || !audio) - return NULL; - - aoi = audio_output_getinfo(audio); - return create_encoder(id, OBS_ENCODER_AUDIO, name, settings, audio, - 1, aoi->samples_per_sec); + if (!name || !id) return NULL; + return create_encoder(id, OBS_ENCODER_AUDIO, name, settings); } static void receive_video(void *param, struct video_data *frame); @@ -418,6 +402,30 @@ const char *obs_encoder_get_codec(obs_encoder_t encoder) return encoder ? encoder->info.codec : NULL; } +void obs_encoder_set_video(obs_encoder_t encoder, video_t video) +{ + const struct video_output_info *voi; + + if (!video || !encoder || encoder->info.type != OBS_ENCODER_VIDEO) + return; + + voi = video_output_getinfo(video); + + encoder->media = video; + encoder->timebase_num = voi->fps_den; + encoder->timebase_den = voi->fps_num; +} + +void obs_encoder_set_audio(obs_encoder_t encoder, audio_t audio) +{ + if (!audio || !encoder || encoder->info.type != OBS_ENCODER_AUDIO) + return; + + encoder->media = audio; + encoder->timebase_num = 1; + encoder->timebase_den = audio_output_samplerate(audio); +} + video_t obs_encoder_video(obs_encoder_t encoder) { return (encoder && encoder->info.type == OBS_ENCODER_VIDEO) ? diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 4a7b1597d..40185684e 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -322,6 +322,7 @@ struct obs_output { audio_t audio; obs_encoder_t video_encoder; obs_encoder_t audio_encoder; + obs_service_t service; bool video_conversion_set; bool audio_conversion_set; @@ -404,4 +405,11 @@ extern void obs_encoder_remove_output(struct obs_encoder *encoder, struct obs_service { struct obs_context_data context; struct obs_service_info info; + + bool active; + bool destroy; + struct obs_output *output; }; + +void obs_service_activate(struct obs_service *service); +void obs_service_deactivate(struct obs_service *service, bool remove); diff --git a/libobs/obs-module.c b/libobs/obs-module.c index 6df1c7179..f081fbcdd 100644 --- a/libobs/obs-module.c +++ b/libobs/obs-module.c @@ -63,7 +63,7 @@ int obs_load_module(const char *path) mod.module = os_dlopen(plugin_path); bfree(plugin_path); if (!mod.module) { - blog(LOG_DEBUG, "Module '%s' not found", path); + blog(LOG_WARNING, "Module '%s' not found", path); return MODULE_FILE_NOT_FOUND; } diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 6ef231d84..860078824 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -108,6 +108,8 @@ void obs_output_destroy(obs_output_t output) if (output->valid && output->active) output->info.stop(output->context.data); + if (output->service) + output->service->output = NULL; free_packets(output); @@ -283,6 +285,22 @@ obs_encoder_t obs_output_get_audio_encoder(obs_output_t output) return output ? output->audio_encoder : NULL; } +void obs_output_set_service(obs_output_t output, obs_service_t service) +{ + if (!output || output->active || !service || service->active) return; + + if (service->output) + service->output->service = NULL; + + output->service = service; + service->output = output; +} + +obs_service_t obs_output_get_service(obs_output_t output) +{ + return output ? output->service : NULL; +} + void obs_output_set_video_conversion(obs_output_t output, const struct video_scale_info *conversion) { @@ -302,7 +320,7 @@ void obs_output_set_audio_conversion(obs_output_t output, } static bool can_begin_data_capture(struct obs_output *output, bool encoded, - bool has_video, bool has_audio) + bool has_video, bool has_audio, bool has_service) { if (has_video) { if (encoded) { @@ -324,6 +342,9 @@ static bool can_begin_data_capture(struct obs_output *output, bool encoded, } } + if (has_service && !output->service) + return false; + return true; } @@ -480,7 +501,8 @@ static inline void signal_stop(struct obs_output *output, int code) } static inline void convert_flags(struct obs_output *output, uint32_t flags, - bool *encoded, bool *has_video, bool *has_audio) + bool *encoded, bool *has_video, bool *has_audio, + bool *has_service) { *encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0; if (!flags) @@ -488,30 +510,34 @@ static inline void convert_flags(struct obs_output *output, uint32_t flags, else flags &= output->info.flags; - *has_video = (flags & OBS_OUTPUT_VIDEO) != 0; - *has_audio = (flags & OBS_OUTPUT_AUDIO) != 0; + *has_video = (flags & OBS_OUTPUT_VIDEO) != 0; + *has_audio = (flags & OBS_OUTPUT_AUDIO) != 0; + *has_service = (flags & OBS_OUTPUT_SERVICE) != 0; } bool obs_output_can_begin_data_capture(obs_output_t output, uint32_t flags) { - bool encoded, has_video, has_audio; + bool encoded, has_video, has_audio, has_service; if (!output) return false; if (output->active) return false; - convert_flags(output, flags, &encoded, &has_video, &has_audio); + convert_flags(output, flags, &encoded, &has_video, &has_audio, + &has_service); - return can_begin_data_capture(output, encoded, has_video, has_audio); + return can_begin_data_capture(output, encoded, has_video, has_audio, + has_service); } bool obs_output_initialize_encoders(obs_output_t output, uint32_t flags) { - bool encoded, has_video, has_audio; + bool encoded, has_video, has_audio, has_service; if (!output) return false; if (output->active) return false; - convert_flags(output, flags, &encoded, &has_video, &has_audio); + convert_flags(output, flags, &encoded, &has_video, &has_audio, + &has_service); if (!encoded) return false; @@ -532,17 +558,23 @@ bool obs_output_initialize_encoders(obs_output_t output, uint32_t flags) bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags) { - bool encoded, has_video, has_audio; + bool encoded, has_video, has_audio, has_service; if (!output) return false; if (output->active) return false; - convert_flags(output, flags, &encoded, &has_video, &has_audio); + convert_flags(output, flags, &encoded, &has_video, &has_audio, + &has_service); - if (!can_begin_data_capture(output, encoded, has_video, has_audio)) + if (!can_begin_data_capture(output, encoded, has_video, has_audio, + has_service)) return false; hook_data_capture(output, encoded, has_video, has_audio); + + if (has_service) + obs_service_activate(output->service); + output->active = true; signal_start(output); return true; @@ -550,14 +582,15 @@ bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags) void obs_output_end_data_capture(obs_output_t output) { - bool encoded, has_video, has_audio; + bool encoded, has_video, has_audio, has_service; void (*encoded_callback)(void *data, struct encoder_packet *packet); void *param; if (!output) return; if (!output->active) return; - convert_flags(output, 0, &encoded, &has_video, &has_audio); + convert_flags(output, 0, &encoded, &has_video, &has_audio, + &has_service); if (encoded) { encoded_callback = (has_video && has_audio) ? @@ -582,6 +615,9 @@ void obs_output_end_data_capture(obs_output_t output) output->context.data); } + if (has_service) + obs_service_deactivate(output->service, false); + output->active = false; } diff --git a/libobs/obs-service.c b/libobs/obs-service.c index 90e00ca0e..59a9d69d9 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -53,6 +53,13 @@ obs_service_t obs_service_create(const char *id, const char *name, service->info = *info; + service->context.data = service->info.create(service->context.settings, + service); + if (!service->context.data) { + obs_service_destroy(service); + return NULL; + } + obs_context_data_insert(&service->context, &obs->data.services_mutex, &obs->data.first_service); @@ -60,19 +67,37 @@ obs_service_t obs_service_create(const char *id, const char *name, return service; } +static void actually_destroy_service(struct obs_service *service) +{ + if (service->context.data) + service->info.destroy(service->context.data); + + if (service->output) + service->output->service = NULL; + + obs_context_data_free(&service->context); + bfree(service); +} + void obs_service_destroy(obs_service_t service) { if (service) { obs_context_data_remove(&service->context); - if (service->context.data) - service->info.destroy(service->context.data); + service->destroy = true; - obs_context_data_free(&service->context); - bfree(service); + /* do NOT destroy the service until the service is no + * longer in use */ + if (!service->active) + actually_destroy_service(service); } } +const char *obs_service_getname(obs_service_t service) +{ + return service ? service->context.name : NULL; +} + static inline obs_data_t get_defaults(const struct obs_service_info *info) { obs_data_t settings = obs_data_create(); @@ -115,6 +140,11 @@ obs_properties_t obs_service_properties(obs_service_t service, return NULL; } +const char *obs_service_gettype(obs_service_t service) +{ + return service ? service->info.id : NULL; +} + void obs_service_update(obs_service_t service, obs_data_t settings) { if (!service) return; @@ -156,3 +186,39 @@ const char *obs_service_get_key(obs_service_t service) if (!service || !service->info.get_key) return NULL; return service->info.get_key(service->context.data); } + +const char *obs_service_get_username(obs_service_t service) +{ + if (!service || !service->info.get_username) return NULL; + return service->info.get_username(service->context.data); +} + +const char *obs_service_get_password(obs_service_t service) +{ + if (!service || !service->info.get_password) return NULL; + return service->info.get_password(service->context.data); +} + +void obs_service_activate(struct obs_service *service) +{ + if (!service || !service->output || service->active) return; + + if (service->info.activate) + service->info.activate(service->context.data, + service->context.settings); + service->active = true; +} + +void obs_service_deactivate(struct obs_service *service, bool remove) +{ + if (!service || !service->output || !service->active) return; + + if (service->info.deactivate) + service->info.deactivate(service->context.data); + service->active = false; + + if (service->destroy) + actually_destroy_service(service); + else if (remove) + service->output = NULL; +} diff --git a/libobs/obs-service.h b/libobs/obs-service.h index 383d7cc38..82b439919 100644 --- a/libobs/obs-service.h +++ b/libobs/obs-service.h @@ -26,6 +26,9 @@ struct obs_service_info { void (*destroy)(void *data); /* optional */ + void (*activate)(void *data, obs_data_t settings); + void (*deactivate)(void *data); + void (*update)(void *data, obs_data_t settings); void (*defaults)(obs_data_t settings); @@ -35,6 +38,9 @@ struct obs_service_info { const char *(*get_url)(void *data); const char *(*get_key)(void *data); + const char *(*get_username)(void *data); + const char *(*get_password)(void *data); + /* TODO: more stuff later */ }; diff --git a/libobs/obs.c b/libobs/obs.c index 49519788a..59fb27623 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -908,6 +908,46 @@ obs_source_t obs_get_source_by_name(const char *name) return source; } +static inline void *get_context_by_name(void *vfirst, const char *name, + pthread_mutex_t *mutex) +{ + struct obs_context_data **first = vfirst; + struct obs_context_data *context; + + pthread_mutex_lock(mutex); + + context = *first; + while (context) { + if (strcmp(context->name, name) == 0) + break; + context = context->next; + } + + pthread_mutex_unlock(mutex); + return context; +} + +obs_output_t obs_get_output_by_name(const char *name) +{ + if (!obs) return NULL; + return get_context_by_name(&obs->data.first_output, name, + &obs->data.outputs_mutex); +} + +obs_encoder_t obs_get_encoder_by_name(const char *name) +{ + if (!obs) return NULL; + return get_context_by_name(&obs->data.first_encoder, name, + &obs->data.encoders_mutex); +} + +obs_service_t obs_get_service_by_name(const char *name) +{ + if (!obs) return NULL; + return get_context_by_name(&obs->data.first_service, name, + &obs->data.services_mutex); +} + effect_t obs_get_default_effect(void) { if (!obs) return NULL; diff --git a/libobs/obs.h b/libobs/obs.h index 515a0ddbe..010392efb 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -288,6 +288,10 @@ EXPORT void obs_enum_outputs(bool (*enum_proc)(void*, obs_output_t), EXPORT void obs_enum_encoders(bool (*enum_proc)(void*, obs_encoder_t), void *param); +/** Enumerates encoders */ +EXPORT void obs_enum_services(bool (*enum_proc)(void*, obs_service_t), + void *param); + /** * Gets a source by its name. * @@ -296,6 +300,15 @@ EXPORT void obs_enum_encoders(bool (*enum_proc)(void*, obs_encoder_t), */ EXPORT obs_source_t obs_get_source_by_name(const char *name); +/** Gets an output by its name. */ +EXPORT obs_output_t obs_get_output_by_name(const char *name); + +/** Gets an encoder by its name. */ +EXPORT obs_encoder_t obs_get_encoder_by_name(const char *name); + +/** Gets an service by its name. */ +EXPORT obs_service_t obs_get_service_by_name(const char *name); + /** * Returns the location of a plugin data file. * @@ -737,6 +750,12 @@ EXPORT obs_encoder_t obs_output_get_video_encoder(obs_output_t output); /** Returns the current audio encoder associated with this output */ EXPORT obs_encoder_t obs_output_get_audio_encoder(obs_output_t output); +/** Sets the current service associated with this output. */ +EXPORT void obs_output_set_service(obs_output_t output, obs_service_t service); + +/** Gets the current service associated with this output. */ +EXPORT obs_service_t obs_output_get_service(obs_output_t output); + /* ------------------------------------------------------------------------- */ /* Functions used by outputs */ @@ -793,11 +812,10 @@ EXPORT const char *obs_encoder_getdisplayname(const char *id, * @param id Video encoder ID * @param name Name to assign to this context * @param settings Settings - * @param video Video output context to encode data from * @return The video encoder context, or NULL if failed or not found. */ EXPORT obs_encoder_t obs_video_encoder_create(const char *id, const char *name, - obs_data_t settings, video_t video); + obs_data_t settings); /** * Creates an audio encoder context @@ -805,11 +823,10 @@ EXPORT obs_encoder_t obs_video_encoder_create(const char *id, const char *name, * @param id Audio Encoder ID * @param name Name to assign to this context * @param settings Settings - * @param audio Audio output context to encode data from * @return The video encoder context, or NULL if failed or not found. */ EXPORT obs_encoder_t obs_audio_encoder_create(const char *id, const char *name, - obs_data_t settings, audio_t audio); + obs_data_t settings); /** Destroys an encoder context */ EXPORT void obs_encoder_destroy(obs_encoder_t encoder); @@ -844,6 +861,12 @@ EXPORT bool obs_encoder_get_extra_data(obs_encoder_t encoder, /** Returns the current settings for this encoder */ EXPORT obs_data_t obs_encoder_get_settings(obs_encoder_t encoder); +/** Sets the video output context to be used with this encoder */ +EXPORT void obs_encoder_set_video(obs_encoder_t encoder, video_t video); + +/** Sets the audio output context to be used with this encoder */ +EXPORT void obs_encoder_set_audio(obs_encoder_t encoder, audio_t audio); + /** * Returns the video output context used with this encoder, or NULL if not * a video context @@ -873,6 +896,8 @@ EXPORT obs_service_t obs_service_create(const char *id, const char *name, obs_data_t settings); EXPORT void obs_service_destroy(obs_service_t service); +EXPORT const char *obs_service_getname(obs_service_t service); + /** Gets the default settings for a service */ EXPORT obs_data_t obs_service_defaults(const char *id); @@ -887,11 +912,26 @@ EXPORT obs_properties_t obs_get_service_properties(const char *id, EXPORT obs_properties_t obs_service_properties(obs_service_t service, const char *locale); +/** Gets the service type */ +EXPORT const char *obs_service_gettype(obs_service_t service); + +/** Updates the settings of the service context */ +EXPORT void obs_service_update(obs_service_t service, obs_data_t settings); + +/** Returns the current settings for this service */ +EXPORT obs_data_t obs_service_get_settings(obs_service_t service); + /** Returns the URL for this service context */ -const char *obs_service_get_url(obs_service_t service); +EXPORT const char *obs_service_get_url(obs_service_t service); /** Returns the stream key (if any) for this service context */ -const char *obs_service_get_key(obs_service_t service); +EXPORT const char *obs_service_get_key(obs_service_t service); + +/** Returns the username (if any) for this service context */ +EXPORT const char *obs_service_get_username(obs_service_t service); + +/** Returns the password (if any) for this service context */ +EXPORT const char *obs_service_get_password(obs_service_t service); /* ------------------------------------------------------------------------- */ diff --git a/libobs/util/darray.h b/libobs/util/darray.h index b48e9af2d..8962b588a 100644 --- a/libobs/util/darray.h +++ b/libobs/util/darray.h @@ -359,8 +359,6 @@ static inline void darray_pop_back(const size_t element_size, static inline void darray_join(const size_t element_size, struct darray *dst, struct darray *da) { - assert(element_size == element_size); - darray_push_back_darray(element_size, dst, da); darray_free(da); } diff --git a/obs/forms/OBSBasicProperties.ui b/obs/forms/OBSBasicProperties.ui index f8260da98..d438461a7 100644 --- a/obs/forms/OBSBasicProperties.ui +++ b/obs/forms/OBSBasicProperties.ui @@ -6,8 +6,8 @@ 0 0 - 841 - 479 + 1072 + 441 diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index 503f114dd..3afeaa290 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -47,7 +47,16 @@ - Outputs + Stream + + + + :/settings/images/settings/network.png:/settings/images/settings/network.png + + + + + Output @@ -118,95 +127,337 @@ - - - - QFormLayout::AllNonFixedFieldsGrow + + + + 0 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 0 - - - - NOTE: This is a test, just some temporary user interface - - - true - - - - - - - - 170 - 0 - - - - Video Bitrate: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - URL/Filename: - - - - - - - - - - Stream Key: - - - - - - - QLineEdit::Password + + 0 + + + 0 + + + + + + 0 + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + 170 + 0 + + + + Stream Type + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + Qt::Horizontal + + + + + + + - - - - 100 - - - 60000 - - - 2500 - + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 170 + 0 + + + + Mode + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + 0 + + + + Simple + + + + + Custom + + + + + - - - - 24 - - - 320 - - - 128 + + + + Qt::Horizontal - - - - Audio Bitrate: + + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 170 + 0 + + + + Save Path + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + false + + + + + + + false + + + Browse + + + + + + + + + Video Bitrate + + + + + + + 200 + + + 16000 + + + 2000 + + + + + + + Audio Bitrate + + + + + + + 4 + + + + 32 + + + + + 64 + + + + + 96 + + + + + 128 + + + + + 160 + + + + + 192 + + + + + 256 + + + + + 320 + + + + + + + + + + + + + @@ -721,12 +972,28 @@ setCurrentIndex(int) - 286 - 155 + 329 + 168 + + + 577 + 183 + + + + + outputMode + currentIndexChanged(int) + outputModePages + setCurrentIndex(int) + + + 379 + 30 - 340 - 154 + 294 + 427 diff --git a/obs/forms/images/settings/network.png b/obs/forms/images/settings/network.png new file mode 100644 index 0000000000000000000000000000000000000000..736c74d3108444d7ec59f8c40b16237546407423 GIT binary patch literal 4332 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 JR;wc-{sUgU?A8DP literal 0 HcmV?d00001 diff --git a/obs/forms/obs.qrc b/obs/forms/obs.qrc index 50dfc7b1c..f2e2ec30d 100644 --- a/obs/forms/obs.qrc +++ b/obs/forms/obs.qrc @@ -14,6 +14,7 @@ images/up.ico + images/settings/network.png images/settings/video-display-3.png images/settings/decibel_audio_player.png images/settings/applications-system-2.png diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 8413a3cbe..c0e175187 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -47,7 +47,8 @@ Q_DECLARE_METATYPE(OBSSceneItem); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow (parent), - outputTest (nullptr), + streamOutput (nullptr), + service (nullptr), aac (nullptr), x264 (nullptr), sceneChanging (false), @@ -84,6 +85,111 @@ static inline bool HasAudioDevices(const char *source_id) return count != 0; } +static void OBSStartStreaming(void *data, calldata_t params) +{ + UNUSED_PARAMETER(params); + QMetaObject::invokeMethod(static_cast(data), + "StreamingStart"); +} + +static void OBSStopStreaming(void *data, calldata_t params) +{ + int code = (int)calldata_int(params, "errorcode"); + QMetaObject::invokeMethod(static_cast(data), + "StreamingStop", Q_ARG(int, code)); +} + +#define SERVICE_PATH "obs-studio/basic/service.json" + +void OBSBasic::SaveService() +{ + if (!service) + return; + + BPtr serviceJsonPath(os_get_config_path(SERVICE_PATH)); + if (!serviceJsonPath) + return; + + obs_data_t data = obs_data_create(); + obs_data_t settings = obs_service_get_settings(service); + + obs_data_setstring(data, "type", obs_service_gettype(service)); + obs_data_setobj(data, "settings", settings); + + const char *json = obs_data_getjson(data); + + os_quick_write_utf8_file(serviceJsonPath, json, strlen(json), false); + + obs_data_release(settings); + obs_data_release(data); +} + +bool OBSBasic::LoadService() +{ + const char *type; + + BPtr serviceJsonPath(os_get_config_path(SERVICE_PATH)); + if (!serviceJsonPath) + return false; + + BPtr jsonText = os_quick_read_utf8_file(serviceJsonPath); + if (!jsonText) + return false; + + obs_data_t data = obs_data_create_from_json(jsonText); + + obs_data_set_default_string(data, "type", "rtmp_common"); + type = obs_data_getstring(data, "type"); + + obs_data_t settings = obs_data_getobj(data, "settings"); + + service = obs_service_create(type, "default", settings); + + obs_data_release(settings); + obs_data_release(data); + + return !!service; +} + +bool OBSBasic::InitOutputs() +{ + streamOutput = obs_output_create("rtmp_output", "default", nullptr); + if (!streamOutput) + return false; + + signal_handler_connect(obs_output_signalhandler(streamOutput), + "start", OBSStartStreaming, this); + signal_handler_connect(obs_output_signalhandler(streamOutput), + "stop", OBSStopStreaming, this); + + return true; +} + +bool OBSBasic::InitEncoders() +{ + aac = obs_audio_encoder_create("ffmpeg_aac", "aac", nullptr); + if (!aac) + return false; + + x264 = obs_video_encoder_create("obs_x264", "h264", nullptr); + if (!x264) + return false; + + return true; +} + +bool OBSBasic::InitService() +{ + if (LoadService()) + return true; + + service = obs_service_create("rtmp_common", nullptr, nullptr); + if (!service) + return false; + + return true; +} + bool OBSBasic::InitBasicConfigDefaults() { bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); @@ -107,10 +213,10 @@ bool OBSBasic::InitBasicConfigDefaults() uint32_t cy = monitors[0].cy; /* TODO: temporary */ - config_set_default_string(basicConfig, "OutputTemp", "URL", ""); - config_set_default_string(basicConfig, "OutputTemp", "Key", ""); - config_set_default_uint (basicConfig, "OutputTemp", "VBitrate", 2500); - config_set_default_uint (basicConfig, "OutputTemp", "ABitrate", 128); + config_set_default_string(basicConfig, "SimpleOutput", "path", ""); + config_set_default_uint (basicConfig, "SimpleOutput", "VBitrate", + 2500); + config_set_default_uint (basicConfig, "SimpleOutput", "ABitrate", 128); config_set_default_uint (basicConfig, "Video", "BaseCX", cx); config_set_default_uint (basicConfig, "Video", "BaseCY", cy); @@ -186,6 +292,7 @@ void OBSBasic::OBSInit() obs_load_module("obs-ffmpeg"); obs_load_module("obs-x264"); obs_load_module("obs-outputs"); + obs_load_module("rtmp-services"); #ifdef __APPLE__ obs_load_module("mac-capture"); #elif _WIN32 @@ -196,11 +303,20 @@ void OBSBasic::OBSInit() obs_load_module("linux-pulseaudio"); #endif + if (!InitOutputs()) + throw "Failed to initialize outputs"; + if (!InitEncoders()) + throw "Failed to initialize encoders"; + if (!InitService()) + throw "Failed to initialize service"; + ResetAudioDevices(); } OBSBasic::~OBSBasic() { + SaveService(); + if (properties) delete properties; @@ -431,6 +547,22 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) /* Main class functions */ +obs_service_t OBSBasic::GetService() +{ + if (!service) + service = obs_service_create("rtmp_common", NULL, NULL); + return service; +} + +void OBSBasic::SetService(obs_service_t newService) +{ + if (newService) { + if (service) + obs_service_destroy(service); + service = newService; + } +} + bool OBSBasic::ResetVideo() { struct obs_video_info ovi; @@ -446,7 +578,6 @@ bool OBSBasic::ResetVideo() "Video", "OutputCX"); ovi.output_height = (uint32_t)config_get_uint(basicConfig, "Video", "OutputCY"); - //ovi.output_format = VIDEO_FORMAT_I420; ovi.output_format = VIDEO_FORMAT_NV12; ovi.adapter = 0; ovi.gpu_conversion = true; @@ -821,116 +952,50 @@ void OBSBasic::on_actionSourceDown_triggered() { } -void OBSBasic::OutputStop(int errorcode) -{ - UNUSED_PARAMETER(errorcode); - ui->streamButton->setText("Start Streaming"); -} - -void OBSBasic::OutputStart() +void OBSBasic::StreamingStart() { ui->streamButton->setText("Stop Streaming"); } -static void OBSOutputStart(void *data, calldata_t params) +void OBSBasic::StreamingStop(int errorcode) { - UNUSED_PARAMETER(params); - QMetaObject::invokeMethod(static_cast(data), "OutputStart"); -} - -static void OBSOutputStop(void *data, calldata_t params) -{ - int code = (int)calldata_int(params, "errorcode"); - QMetaObject::invokeMethod(static_cast(data), "OutputStop", - Q_ARG(int, code)); -} - -void OBSBasic::TempFileOutput(const char *path, int vBitrate, int aBitrate) -{ - obs_data_t data = obs_data_create(); - obs_data_setstring(data, "filename", path); - obs_data_setint(data, "audio_bitrate", aBitrate); - obs_data_setint(data, "video_bitrate", vBitrate); - - outputTest = obs_output_create("ffmpeg_output", "test", data); - obs_data_release(data); -} - -void OBSBasic::TempStreamOutput(const char *url, const char *key, - int vBitrate, int aBitrate) -{ - obs_data_t aac_settings = obs_data_create(); - obs_data_t x264_settings = obs_data_create(); - obs_data_t output_settings = obs_data_create(); - stringstream ss; - - ss << "filler=1:crf=0:bitrate=" << vBitrate; - - obs_data_setint(aac_settings, "bitrate", aBitrate); - - obs_data_setint(x264_settings, "bitrate", vBitrate); - obs_data_setint(x264_settings, "buffer_size", vBitrate); - obs_data_setint(x264_settings, "keyint_sec", 2); - obs_data_setstring(x264_settings, "x264opts", ss.str().c_str()); - - obs_data_setstring(output_settings, "path", url); - obs_data_setstring(output_settings, "key", key); - - aac = obs_audio_encoder_create("ffmpeg_aac", "blabla1", - aac_settings, obs_audio()); - x264 = obs_video_encoder_create("obs_x264", "blabla2", - x264_settings, obs_video()); - outputTest = obs_output_create("rtmp_output", "test", output_settings); - - obs_output_set_video_encoder(outputTest, x264); - obs_output_set_audio_encoder(outputTest, aac); - - obs_data_release(aac_settings); - obs_data_release(x264_settings); - obs_data_release(output_settings); + UNUSED_PARAMETER(errorcode); + ui->streamButton->setText("Start Streaming"); } -/* TODO: lots of temporary code */ void OBSBasic::on_streamButton_clicked() { - if (obs_output_active(outputTest)) { - obs_output_stop(outputTest); + if (obs_output_active(streamOutput)) { + obs_output_stop(streamOutput); + } else { - const char *url = config_get_string(basicConfig, "OutputTemp", - "URL"); - const char *key = config_get_string(basicConfig, "OutputTemp", - "Key"); - int vBitrate = config_get_uint(basicConfig, "OutputTemp", + obs_data_t x264Settings = obs_data_create(); + obs_data_t aacSettings = obs_data_create(); + + int videoBitrate = config_get_uint(basicConfig, "SimpleOutput", "VBitrate"); - int aBitrate = config_get_uint(basicConfig, "OutputTemp", + int audioBitrate = config_get_uint(basicConfig, "SimpleOutput", "ABitrate"); - if (!url) - return; + SaveService(); - obs_output_destroy(outputTest); - obs_encoder_destroy(aac); - obs_encoder_destroy(x264); - outputTest = nullptr; - aac = nullptr; - x264 = nullptr; + obs_data_setint(x264Settings, "bitrate", videoBitrate); + obs_data_setbool(x264Settings, "cbr", true); - if (strstr(url, "rtmp://") != NULL) - TempStreamOutput(url, key, vBitrate, aBitrate); - else - TempFileOutput(url, vBitrate, aBitrate); + obs_data_setint(aacSettings, "bitrate", audioBitrate); - if (!outputTest) { - OutputStop(OBS_OUTPUT_FAIL); - return; - } + obs_encoder_update(x264, x264Settings); + obs_encoder_update(aac, aacSettings); - signal_handler_connect(obs_output_signalhandler(outputTest), - "start", OBSOutputStart, this); - signal_handler_connect(obs_output_signalhandler(outputTest), - "stop", OBSOutputStop, this); + obs_data_release(x264Settings); + obs_data_release(aacSettings); - obs_output_start(outputTest); + obs_encoder_set_video(x264, obs_video()); + obs_encoder_set_audio(aac, obs_audio()); + obs_output_set_video_encoder(streamOutput, x264); + obs_output_set_audio_encoder(streamOutput, aac); + obs_output_set_service(streamOutput, service); + obs_output_start(streamOutput); } } diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 1798bed69..d0c4730a9 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -36,31 +36,41 @@ class OBSBasic : public OBSMainWindow { private: std::unordered_map sourceSceneRefs; - obs_output_t outputTest; + + obs_output_t streamOutput; + obs_service_t service; obs_encoder_t aac; obs_encoder_t x264; - bool sceneChanging; - int previewX, previewY; - float previewScale; - int resizeTimer; + bool sceneChanging; + + int previewX, previewY; + float previewScale; + int resizeTimer; - ConfigFile basicConfig; + ConfigFile basicConfig; QPointer properties; + void SaveService(); + bool LoadService(); + + bool InitOutputs(); + bool InitEncoders(); + bool InitService(); + + bool InitBasicConfigDefaults(); + bool InitBasicConfig(); + + OBSScene GetCurrentScene(); + OBSSceneItem GetCurrentSceneItem(); + void GetFPSCommon(uint32_t &num, uint32_t &den) const; void GetFPSInteger(uint32_t &num, uint32_t &den) const; void GetFPSFraction(uint32_t &num, uint32_t &den) const; void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; void GetConfigFPS(uint32_t &num, uint32_t &den) const; - bool InitBasicConfigDefaults(); - bool InitBasicConfig(); - - OBSScene GetCurrentScene(); - OBSSceneItem GetCurrentSceneItem(); - void UpdateSources(OBSScene scene); void InsertSceneItem(obs_sceneitem_t item); @@ -69,8 +79,8 @@ private: int vBitrate, int aBitrate); public slots: - void OutputStart(); - void OutputStop(int errorcode); + void StreamingStart(); + void StreamingStop(int errorcode); private slots: void AddSceneItem(OBSSceneItem item); @@ -94,6 +104,9 @@ private: void AddSourcePopupMenu(const QPoint &pos); public: + obs_service_t GetService(); + void SetService(obs_service_t service); + bool ResetVideo(); bool ResetAudio(); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 7b84ce2ae..a7452ba40 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -25,6 +25,7 @@ #include "obs-app.hpp" #include "platform.hpp" +#include "properties-view.hpp" #include "qt-wrappers.hpp" #include "window-basic-main.hpp" #include "window-basic-settings.hpp" @@ -78,6 +79,20 @@ static bool ConvertResText(const char *res, uint32_t &cx, uint32_t &cy) return true; } +static inline void SetComboByName(QComboBox *combo, const char *name) +{ + int idx = combo->findText(QT_UTF8(name)); + if (idx != -1) + combo->setCurrentIndex(idx); +} + +static inline void SetComboByValue(QComboBox *combo, const char *name) +{ + int idx = combo->findData(QT_UTF8(name)); + if (idx != -1) + combo->setCurrentIndex(idx); +} + void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, const char *slot) { @@ -99,15 +114,16 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, #define VIDEO_CHANGED SLOT(VideoChanged()) OBSBasicSettings::OBSBasicSettings(QWidget *parent) - : QDialog (parent), - main (qobject_cast(parent)), - ui (new Ui::OBSBasicSettings), - generalChanged (false), - outputsChanged (false), - audioChanged (false), - videoChanged (false), - pageIndex (0), - loading (true) + : QDialog (parent), + main (qobject_cast(parent)), + ui (new Ui::OBSBasicSettings), + generalChanged (false), + outputsChanged (false), + audioChanged (false), + videoChanged (false), + pageIndex (0), + loading (true), + streamProperties (nullptr) { string path; @@ -118,33 +134,74 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) if (localeIni.Open(path.c_str(), CONFIG_OPEN_EXISTING) != 0) throw "Could not open locale.ini"; - HookWidget(ui->language, COMBO_CHANGED, GENERAL_CHANGED); - HookWidget(ui->streamVBitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); - HookWidget(ui->streamABitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); - HookWidget(ui->streamURL, EDIT_CHANGED, OUTPUTS_CHANGED); - HookWidget(ui->streamKey, EDIT_CHANGED, OUTPUTS_CHANGED); - HookWidget(ui->channelSetup, COMBO_CHANGED, AUDIO_RESTART); - HookWidget(ui->sampleRate, COMBO_CHANGED, AUDIO_RESTART); - HookWidget(ui->desktopAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); - HookWidget(ui->desktopAudioDevice2, COMBO_CHANGED, AUDIO_CHANGED); - HookWidget(ui->auxAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); - HookWidget(ui->auxAudioDevice2, COMBO_CHANGED, AUDIO_CHANGED); - HookWidget(ui->auxAudioDevice3, COMBO_CHANGED, AUDIO_CHANGED); - HookWidget(ui->renderer, COMBO_CHANGED, VIDEO_RESTART); - HookWidget(ui->adapter, COMBO_CHANGED, VIDEO_RESTART); - HookWidget(ui->baseResolution, CBEDIT_CHANGED, VIDEO_RES); - HookWidget(ui->outputResolution, CBEDIT_CHANGED, VIDEO_RES); - HookWidget(ui->downscaleFilter, COMBO_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsType, COMBO_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsCommon, COMBO_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsInteger, SCROLL_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsInteger, SCROLL_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsNumerator, SCROLL_CHANGED, VIDEO_CHANGED); - HookWidget(ui->fpsDenominator, SCROLL_CHANGED, VIDEO_CHANGED); - + HookWidget(ui->language, COMBO_CHANGED, GENERAL_CHANGED); + HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->simpleOutputPath, EDIT_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->simpleOutputVBitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->simpleOutputABitrate, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->channelSetup, COMBO_CHANGED, AUDIO_RESTART); + HookWidget(ui->sampleRate, COMBO_CHANGED, AUDIO_RESTART); + HookWidget(ui->desktopAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); + HookWidget(ui->desktopAudioDevice2, COMBO_CHANGED, AUDIO_CHANGED); + HookWidget(ui->auxAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); + HookWidget(ui->auxAudioDevice2, COMBO_CHANGED, AUDIO_CHANGED); + HookWidget(ui->auxAudioDevice3, COMBO_CHANGED, AUDIO_CHANGED); + HookWidget(ui->renderer, COMBO_CHANGED, VIDEO_RESTART); + HookWidget(ui->adapter, COMBO_CHANGED, VIDEO_RESTART); + HookWidget(ui->baseResolution, CBEDIT_CHANGED, VIDEO_RES); + HookWidget(ui->outputResolution, CBEDIT_CHANGED, VIDEO_RES); + HookWidget(ui->downscaleFilter, COMBO_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsType, COMBO_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsCommon, COMBO_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsInteger, SCROLL_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsInteger, SCROLL_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsNumerator, SCROLL_CHANGED, VIDEO_CHANGED); + HookWidget(ui->fpsDenominator, SCROLL_CHANGED, VIDEO_CHANGED); + + LoadServiceTypes(); + LoadServiceInfo(); LoadSettings(false); } +void OBSBasicSettings::LoadServiceTypes() +{ + const char *type; + size_t idx = 0; + + while (obs_enum_service_types(idx++, &type)) { + const char *name = obs_service_getdisplayname(type, + App()->GetLocale()); + QString qName = QT_UTF8(name); + QString qType = QT_UTF8(type); + + ui->streamType->addItem(qName, qType); + } + + type = obs_service_gettype(main->GetService()); + SetComboByValue(ui->streamType, type); +} + +void OBSBasicSettings::LoadServiceInfo() +{ + QLayout *layout = ui->streamContainer->layout(); + obs_service_t service = main->GetService(); + obs_data_t settings = obs_service_get_settings(service); + obs_properties_t properties = obs_service_properties(service, + App()->GetLocale()); + + delete streamProperties; + streamProperties = new OBSPropertiesView( + settings, + properties, + service, + (PropertiesUpdateCallback)obs_service_update, + 170); + + layout->addWidget(streamProperties); + + obs_data_release(settings); +} + void OBSBasicSettings::LoadLanguageList() { const char *currentLang = config_get_string(GetGlobalConfig(), @@ -316,23 +373,27 @@ void OBSBasicSettings::LoadVideoSettings() loading = false; } -void OBSBasicSettings::LoadOutputSettings() +void OBSBasicSettings::LoadSimpleOutputSettings() { - loading = true; - - const char *url = config_get_string(main->Config(), "OutputTemp", - "URL"); - const char *key = config_get_string(main->Config(), "OutputTemp", - "Key"); - int videoBitrate = config_get_uint(main->Config(), "OutputTemp", + const char *path = config_get_string(main->Config(), "SimpleOutput", + "path"); + int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = config_get_uint(main->Config(), "OutputTemp", + int audioBitrate = config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - ui->streamURL->setText(QT_UTF8(url)); - ui->streamKey->setText(QT_UTF8(key)); - ui->streamVBitrate->setValue(videoBitrate); - ui->streamABitrate->setValue(audioBitrate); + ui->simpleOutputPath->setText(path); + ui->simpleOutputVBitrate->setValue(videoBitrate); + + SetComboByName(ui->simpleOutputABitrate, + std::to_string(audioBitrate).c_str()); +} + +void OBSBasicSettings::LoadOutputSettings() +{ + loading = true; + + LoadSimpleOutputSettings(); loading = false; } @@ -492,15 +553,16 @@ void OBSBasicSettings::SaveVideoSettings() /* TODO: Temporary! */ void OBSBasicSettings::SaveOutputSettings() { - int videoBitrate = ui->streamVBitrate->value(); - int audioBitrate = ui->streamABitrate->value(); - QString url = ui->streamURL->text(); - QString key = ui->streamKey->text(); + int videoBitrate = ui->simpleOutputVBitrate->value(); + QString audioBitrate = ui->simpleOutputABitrate->currentText(); + QString path = ui->simpleOutputPath->text(); - config_set_uint(main->Config(), "OutputTemp", "VBitrate", videoBitrate); - config_set_uint(main->Config(), "OutputTemp", "ABitrate", audioBitrate); - config_set_string(main->Config(), "OutputTemp", "URL", QT_TO_UTF8(url)); - config_set_string(main->Config(), "OutputTemp", "Key", QT_TO_UTF8(key)); + config_set_uint(main->Config(), "SimpleOutput", "VBitrate", + videoBitrate); + config_set_string(main->Config(), "SimpleOutput", "ABitrate", + QT_TO_UTF8(audioBitrate)); + config_set_string(main->Config(), "SimpleOutput", "path", + QT_TO_UTF8(path)); } static inline QString GetComboData(QComboBox *combo) @@ -626,6 +688,29 @@ void OBSBasicSettings::on_buttonBox_clicked(QAbstractButton *button) } } +void OBSBasicSettings::on_streamType_currentIndexChanged(int idx) +{ + QString val = ui->streamType->itemData(idx).toString(); + obs_service_t newService; + + if (loading) + return; + + delete streamProperties; + streamProperties = nullptr; + + newService = obs_service_create(QT_TO_UTF8(val), nullptr, nullptr); + if (newService) + main->SetService(newService); + + LoadServiceInfo(); +} + +static inline bool StreamExists(const char *name) +{ + return obs_get_service_by_name(name) != nullptr; +} + static bool ValidResolutions(Ui::OBSBasicSettings *ui) { QString baseRes = ui->baseResolution->lineEdit()->text(); diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp index c776dbc5c..ef54be101 100644 --- a/obs/window-basic-settings.hpp +++ b/obs/window-basic-settings.hpp @@ -26,6 +26,7 @@ class OBSBasic; class QAbstractButton; class QComboBox; +class OBSPropertiesView; #include "ui_OBSBasicSettings.h" @@ -44,10 +45,12 @@ private: int pageIndex; bool loading; + OBSPropertiesView *streamProperties; + inline bool Changed() const { - return generalChanged || outputsChanged || audioChanged || - videoChanged; + return generalChanged || outputsChanged || + audioChanged || videoChanged; } inline void ClearChanged() @@ -62,6 +65,9 @@ private: bool QueryChanges(); + void LoadServiceTypes(); + void LoadServiceInfo(); + void LoadGeneralSettings(); void LoadOutputSettings(); void LoadAudioSettings(); @@ -71,6 +77,9 @@ private: /* general */ void LoadLanguageList(); + /* output */ + void LoadSimpleOutputSettings(); + /* audio */ void LoadListValues(QComboBox *widget, obs_property_t prop, const char *configName); @@ -92,6 +101,8 @@ private slots: void on_listWidget_itemSelectionChanged(); void on_buttonBox_clicked(QAbstractButton *button); + void on_streamType_currentIndexChanged(int idx); + void on_baseResolution_editTextChanged(const QString &text); void GeneralChanged(); diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index 29740f3bd..b6a91c1ec 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -399,6 +399,7 @@ static void *connect_thread(void *data) static bool rtmp_stream_start(void *data) { struct rtmp_stream *stream = data; + obs_service_t service = obs_output_get_service(stream->output); obs_data_t settings; if (!obs_output_can_begin_data_capture(stream->output, 0)) @@ -407,10 +408,10 @@ static bool rtmp_stream_start(void *data) return false; settings = obs_output_get_settings(stream->output); - dstr_copy(&stream->path, obs_data_getstring(settings, "path")); - dstr_copy(&stream->key, obs_data_getstring(settings, "key")); - dstr_copy(&stream->username, obs_data_getstring(settings, "username")); - dstr_copy(&stream->password, obs_data_getstring(settings, "password")); + dstr_copy(&stream->path, obs_service_get_url(service)); + dstr_copy(&stream->key, obs_service_get_key(service)); + dstr_copy(&stream->username, obs_service_get_username(service)); + dstr_copy(&stream->password, obs_service_get_password(service)); stream->drop_threshold_usec = (int64_t)obs_data_getint(settings, "drop_threshold"); obs_data_release(settings); @@ -555,7 +556,9 @@ static obs_properties_t rtmp_stream_properties(const char *locale) struct obs_output_info rtmp_output_info = { .id = "rtmp_output", - .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED, + .flags = OBS_OUTPUT_AV | + OBS_OUTPUT_ENCODED | + OBS_OUTPUT_SERVICE, .getname = rtmp_stream_getname, .create = rtmp_stream_create, .destroy = rtmp_stream_destroy, diff --git a/plugins/obs-x264/obs-x264.c b/plugins/obs-x264/obs-x264.c index c64a92dfb..b04fac4dd 100644 --- a/plugins/obs-x264/obs-x264.c +++ b/plugins/obs-x264/obs-x264.c @@ -76,6 +76,8 @@ static void obs_x264_defaults(obs_data_t settings) obs_data_set_default_int (settings, "bitrate", 1000); obs_data_set_default_int (settings, "buffer_size", 1000); obs_data_set_default_int (settings, "keyint_sec", 0); + obs_data_set_default_int (settings, "crf", 23); + obs_data_set_default_bool (settings, "cbr", false); obs_data_set_default_string(settings, "preset", "veryfast"); obs_data_set_default_string(settings, "profile", ""); @@ -221,6 +223,8 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t settings, int bitrate = (int)obs_data_getint(settings, "bitrate"); int buffer_size = (int)obs_data_getint(settings, "buffer_size"); int keyint_sec = (int)obs_data_getint(settings, "keyint_sec"); + int crf = (int)obs_data_getint(settings, "crf"); + bool cbr = obs_data_getbool(settings, "cbr"); if (keyint_sec) obsx264->params.i_keyint_max = @@ -237,6 +241,17 @@ static void update_params(struct obs_x264 *obsx264, obs_data_t settings, obsx264->params.pf_log = log_x264; obsx264->params.i_log_level = X264_LOG_WARNING; + /* use the new filler method for CBR to allow real-time adjusting of + * the bitrate */ + if (cbr) { + obsx264->params.rc.b_filler = true; + obsx264->params.rc.f_rf_constant = 0.0f; + obsx264->params.rc.i_rc_method = X264_RC_ABR; + } else { + obsx264->params.rc.i_rc_method = X264_RC_CRF; + obsx264->params.rc.f_rf_constant = (float)crf; + } + if (voi->format == VIDEO_FORMAT_NV12) obsx264->params.i_csp = X264_CSP_NV12; else if (voi->format == VIDEO_FORMAT_I420) diff --git a/plugins/rtmp-services/rtmp-common.c b/plugins/rtmp-services/rtmp-common.c index 25b500bff..fa213d805 100644 --- a/plugins/rtmp-services/rtmp-common.c +++ b/plugins/rtmp-services/rtmp-common.c @@ -48,19 +48,35 @@ static void *rtmp_common_create(obs_data_t settings, obs_service_t service) return data; } -static void add_service(obs_property_t list, const char *name, - json_t *service) +static inline const char *get_string_val(json_t *service, const char *key) +{ + json_t *str_val = json_object_get(service, key); + if (!str_val || !json_is_string(str_val)) + return NULL; + + return json_string_value(str_val); +} + +static void add_service(obs_property_t list, json_t *service) { json_t *servers; + const char *name; if (!json_is_object(service)) { blog(LOG_WARNING, "rtmp-common.c: [add_service] service " - "'%s' is not an object", name); + "is not an object"); + return; + } + + name = get_string_val(service, "name"); + if (!name) { + blog(LOG_WARNING, "rtmp-common.c: [add_service] service " + "has no name"); return; } servers = json_object_get(service, "servers"); - if (!servers) { + if (!servers || !json_is_array(servers)) { blog(LOG_WARNING, "rtmp-common.c: [add_service] service " "'%s' has no servers", name); return; @@ -72,16 +88,16 @@ static void add_service(obs_property_t list, const char *name, static void add_services(obs_property_t list, const char *file, json_t *root) { json_t *service; - const char *name; + size_t index; - if (!json_is_object(root)) { + if (!json_is_array(root)) { blog(LOG_WARNING, "rtmp-common.c: [add_services] JSON file " - "'%s' root is not an object", file); + "'%s' root is not an array", file); return; } - json_object_foreach (root, name, service) { - add_service(list, name, service); + json_array_foreach (root, index, service) { + add_service(list, service); } } @@ -115,35 +131,62 @@ static void properties_data_destroy(void *data) json_decref(root); } -static void fill_servers(obs_property_t servers, json_t *service, +static void fill_servers(obs_property_t servers_prop, json_t *service, const char *name) { - json_t *server; - const char *server_name; + json_t *servers, *server; + size_t index; - obs_property_list_clear(servers); + obs_property_list_clear(servers_prop); - if (!json_is_object(service)) { + servers = json_object_get(service, "servers"); + + if (!json_is_array(servers)) { blog(LOG_WARNING, "rtmp-common.c: [fill_servers] " "Servers for service '%s' not a valid object", name); return; } - json_object_foreach (service, server_name, server) { - if (json_is_string(server)) { - obs_property_list_add_string(servers, server_name, - json_string_value(server)); - } + json_array_foreach (servers, index, server) { + const char *server_name = get_string_val(server, "name"); + const char *url = get_string_val(server, "url"); + + if (!server_name || !url) + continue; + + obs_property_list_add_string(servers_prop, server_name, url); } } +static inline json_t *find_service(json_t *root, const char *name) +{ + size_t index; + json_t *service; + + json_array_foreach (root, index, service) { + const char *cur_name = get_string_val(service, "name"); + + if (strcmp(name, cur_name) == 0) + return service; + } + + return NULL; +} + static bool service_selected(obs_properties_t props, obs_property_t p, obs_data_t settings) { const char *name = obs_data_getstring(settings, "service"); json_t *root = obs_properties_get_param(props); - json_t *service = json_object_get(root, name); + json_t *service; + + if (!name || !*name) + return false; + + service = find_service(root, name); + if (!service) + return false; fill_servers(obs_properties_get(props, "server"), service, name); diff --git a/vs/2013/jansson/jansson.vcxproj b/vs/2013/jansson/jansson.vcxproj index 91f9deeab..2a8cb4af5 100644 --- a/vs/2013/jansson/jansson.vcxproj +++ b/vs/2013/jansson/jansson.vcxproj @@ -25,26 +25,26 @@ - DynamicLibrary + StaticLibrary true v120 Unicode - DynamicLibrary + StaticLibrary true v120 Unicode - DynamicLibrary + StaticLibrary false v120 true Unicode - DynamicLibrary + StaticLibrary false v120 true @@ -94,7 +94,8 @@ ../../../deps/jansson/src/jansson.def - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/32bit/$(TargetName)$(TargetExt)" + + @@ -113,7 +114,8 @@ ../../../deps/jansson/src/jansson.def - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/64bit/$(TargetName)$(TargetExt)" + + @@ -136,7 +138,8 @@ ../../../deps/jansson/src/jansson.def - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/32bit/$(TargetName)$(TargetExt)" + + @@ -159,7 +162,8 @@ ../../../deps/jansson/src/jansson.def - copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/64bit/$(TargetName)$(TargetExt)" + + diff --git a/vs/2013/libobs/libobs.vcxproj b/vs/2013/libobs/libobs.vcxproj index dd731ce86..7b025e3a3 100644 --- a/vs/2013/libobs/libobs.vcxproj +++ b/vs/2013/libobs/libobs.vcxproj @@ -205,7 +205,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions) false - ../../../libobs/util/vc + ../../../libobs/util/vc;../../../deps/jansson/src ProgramDatabase @@ -227,7 +227,7 @@ Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions) false - ../../../libobs/util/vc + ../../../libobs/util/vc;../../../deps/jansson/src Windows @@ -250,7 +250,7 @@ true WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions) false - ../../../libobs/util/vc + ../../../libobs/util/vc;../../../deps/jansson/src Windows @@ -275,7 +275,7 @@ true WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions) false - ../../../libobs/util/vc + ../../../libobs/util/vc;../../../deps/jansson/src Windows diff --git a/vs/2013/rtmp-services/rtmp-services.vcxproj b/vs/2013/rtmp-services/rtmp-services.vcxproj index 3262d627f..675da6ea2 100644 --- a/vs/2013/rtmp-services/rtmp-services.vcxproj +++ b/vs/2013/rtmp-services/rtmp-services.vcxproj @@ -93,6 +93,9 @@ $(OutDir);%(AdditionalLibraryDirectories) jansson.lib;libobs.lib;%(AdditionalDependencies) + + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" + @@ -109,6 +112,9 @@ $(OutDir);%(AdditionalLibraryDirectories) jansson.lib;libobs.lib;%(AdditionalDependencies) + + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" + @@ -129,6 +135,9 @@ $(OutDir);%(AdditionalLibraryDirectories) jansson.lib;libobs.lib;%(AdditionalDependencies) + + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)" + @@ -149,6 +158,9 @@ $(OutDir);%(AdditionalLibraryDirectories) jansson.lib;libobs.lib;%(AdditionalDependencies) + + copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)" + -- GitLab