diff --git a/build/data/obs-plugins/rtmp-services/services.json b/build/data/obs-plugins/rtmp-services/services.json index 7786d60462811afc1c50ae860ecdebab4562074b..2ff614037c43f697c7c4e3df94c3db6b6c3e512c 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 fc7e16028f8cb790aecdac5dfc069cdb40d0a2df..232ea1c2e2b005ed1eae9b9e5fb366d0f7a5f511 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 b310b3f48ee8e85fd73db98526089d029dd0a971..f00e1bb25a8897d146a8b34e10f957c1e4de2be1 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 9e7347f32eff75a6e41cba1e4abe17b7c9a39938..0fa02fe37c31d86522abe249542f06e0667cf4d0 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 4a7b1597d13b6393b8057c53b65155aeff9a405c..40185684ef8985f7bf1218337d0613b542300207 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 6df1c71792f6877977d786798e5af83feac440de..f081fbcddb70f31110b895c06b5f37fd644d2e04 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 6ef231d842ea5b1a86b04ad17ac3f15cfd682791..86007882468e08191299ce11b6405d7398374cc1 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 90e00ca0e971303f18e743972f7fede5de26b289..59a9d69d9f1797ad1f52b0dda0d39954afffaf19 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 383d7cc3835349b2e3f87e4055b9690b583a1c5c..82b439919cee9c04e972efedd59f4d8fcfc19dce 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 49519788acf3db45538871c635b2d2c072dca3e1..59fb27623053e8c6fe7a28990724312713196734 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 515a0ddbe8cc45125d1034c56e4a9d31d60da51b..010392efb433ed480401aa298f986c2f21d4ad00 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 b48e9af2d8e8005a32c725b650b41813e341a8ba..8962b588af8a4c343f8062e02b9aca4117eb605d 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 f8260da9804a0afce98eec5c107674ab7b7acab0..d438461a7535fc40f87d32642d1368e2673319ca 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 503f114dd937a8127c0b94c6b6592f70ac8de78f..3afeaa29092e255d7d62667709382ebb1fa1f2fe 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 Binary files /dev/null and b/obs/forms/images/settings/network.png differ diff --git a/obs/forms/obs.qrc b/obs/forms/obs.qrc index 50dfc7b1cca4827104af24a0d72f641036370552..f2e2ec30d891ed1e0a54b26f815b4412b1b96b63 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 8413a3cbebf389ca91296f3c499a3277d8668fbc..c0e1751873ec112da82087d5467a38d95eb5d3e3 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 1798bed69e3d56ea396ad85ff8e551a20971ea16..d0c4730a92d733dca1e610b3cc5244ec7e60973e 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 7b84ce2ae27b5fef037faca837b128932a7a3d0b..a7452ba4007167c8f1b475f1fb8712d538a5cd8c 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 c776dbc5c36a65b985195474490c73f3089a3c50..ef54be101fc24821097319441d4e1dc7f2e24085 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 29740f3bda2b61dfc2c0453e7b2d43b8b8f97795..b6a91c1eccb8fea15f1fd918131ce7580d9ddf91 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 c64a92dfb478d044de40a68757ed85f3f5485b3d..b04fac4ddb7ed11e092879905de7ea4b2baf3aea 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 25b500bff93280fc8a9438ee803563eb04c2c2da..fa213d805d2ca2a1419265c5d94e8f9f92a3405d 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 91f9deeabeadba278531b42c0f5d719e98f0692d..2a8cb4af51eb35c8238d3c651d642d4a8e4c190a 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 dd731ce866db85879057e6e9b44f2eeb0f70c0de..7b025e3a31b2b1b5291b082c96c1347011b0f3bd 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 3262d627f1934ff7200866e01b8c2b3fe39b6a73..675da6ea2852d9303cb37ae48ca21ae5d54a272b 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)" +