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)"
+