obs-studio UI: Implement stream settings UI

- Updated the services API so that it links up with an output and
   the output gets data from that service rather than via settings.
   This allows the service context to have control over how an output is
   used, and makes it so that the URL/key/etc isn't necessarily some
   static setting.

   Also, if the service is attached to an output, it will stick around
   until the output is destroyed.

 - The settings interface has been updated so that it can allow the
   usage of service plugins.  What this means is that now you can create
   a service plugin that can control aspects of the stream, and it
   allows each service to create their own user interface if they create
   a service plugin module.

 - Testing out saving of current service information.  Saves/loads from
   JSON in to obs_data_t, seems to be working quite nicely, and the
   service object information is saved/preserved on exit, and loaded
   again on startup.

 - I agonized over the settings user interface for days, and eventually
   I just decided that the only way that users weren't going to be
   fumbling over options was to split up the settings in to simple/basic
   output, pre-configured, and then advanced for advanced use (such as
   multiple outputs or services, which I'll implement later).

   This was particularly painful to really design right, I wanted more
   features and wanted to include everything in one interface but
   ultimately just realized from experience that users are just not
   technically knowledgable about it and will end up fumbling with the
   settings rather than getting things done.

   Basically, what this means is that casual users only have to enter in
   about 3 things to configure their stream:  Stream key, audio bitrate,
   and video bitrate.  I am really happy with this interface for those
   types of users, but it definitely won't be sufficient for advanced
   usage or for custom outputs, so that stuff will have to be separated.

 - Improved the JSON usage for the 'common streaming services' context,
   I realized that JSON arrays are there to ensure sorting, while
   forgetting that general items are optimized for hashing.  So
   basically I'm just using arrays now to sort items in it.
master
jp9000 2014-04-24 01:49:07 -07:00
parent 3cbc711f02
commit 8830c4102f
26 changed files with 1303 additions and 451 deletions

View File

@ -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"
}
]
}
]

View File

@ -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"

View File

@ -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}

View File

@ -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) ?

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 */
};

View File

@ -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;

View File

@ -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);
/* ------------------------------------------------------------------------- */

View File

@ -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);
}

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>841</width>
<height>479</height>
<width>1072</width>
<height>441</height>
</rect>
</property>
<property name="windowTitle">

View File

@ -47,7 +47,16 @@
</item>
<item>
<property name="text">
<string>Outputs</string>
<string>Stream</string>
</property>
<property name="icon">
<iconset resource="obs.qrc">
<normaloff>:/settings/images/settings/network.png</normaloff>:/settings/images/settings/network.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Output</string>
</property>
<property name="icon">
<iconset resource="obs.qrc">
@ -118,95 +127,337 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="outputsPage">
<layout class="QFormLayout" name="formLayout_5">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
<widget class="QWidget" name="streamPage">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QLabel" name="label_16">
<property name="text">
<string>NOTE: This is a test, just some temporary user interface</string>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="streamContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="wordWrap">
<bool>true</bool>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="widget_5" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_6" native="true">
<layout class="QFormLayout" name="formLayout_8">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_21">
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Stream Type</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="streamType"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="outputPage">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="widget" native="true">
<layout class="QFormLayout" name="formLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_16">
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Mode</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="outputMode">
<property name="enabled">
<bool>false</bool>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Simple</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Video Bitrate:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>URL/Filename:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="streamURL"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Stream Key:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="streamKey">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="streamVBitrate">
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>60000</number>
</property>
<property name="value">
<number>2500</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="streamABitrate">
<property name="minimum">
<number>24</number>
</property>
<property name="maximum">
<number>320</number>
</property>
<property name="value">
<number>128</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Audio Bitrate:</string>
<item>
<widget class="QStackedWidget" name="outputModePages">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="easyOutputsPage">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="simpleOutputContainer" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QFormLayout" name="formLayout_6">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_18">
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Save Path</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLineEdit" name="simpleOutputPath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="simpleOutputBrowse">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>Video Bitrate</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="simpleOutputVBitrate">
<property name="minimum">
<number>200</number>
</property>
<property name="maximum">
<number>16000</number>
</property>
<property name="value">
<number>2000</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Audio Bitrate</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="simpleOutputABitrate">
<property name="currentIndex">
<number>4</number>
</property>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
<item>
<property name="text">
<string>96</string>
</property>
</item>
<item>
<property name="text">
<string>128</string>
</property>
</item>
<item>
<property name="text">
<string>160</string>
</property>
</item>
<item>
<property name="text">
<string>192</string>
</property>
</item>
<item>
<property name="text">
<string>256</string>
</property>
</item>
<item>
<property name="text">
<string>320</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="customOutputsPage"/>
</widget>
</item>
</layout>
@ -721,12 +972,28 @@
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>286</x>
<y>155</y>
<x>329</x>
<y>168</y>
</hint>
<hint type="destinationlabel">
<x>340</x>
<y>154</y>
<x>577</x>
<y>183</y>
</hint>
</hints>
</connection>
<connection>
<sender>outputMode</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>outputModePages</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>379</x>
<y>30</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>427</y>
</hint>
</hints>
</connection>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -14,6 +14,7 @@
<file>images/up.ico</file>
</qresource>
<qresource prefix="settings">
<file>images/settings/network.png</file>
<file>images/settings/video-display-3.png</file>
<file>images/settings/decibel_audio_player.png</file>
<file>images/settings/applications-system-2.png</file>

View File

@ -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<OBSBasic*>(data),
"StreamingStart");
}
static void OBSStopStreaming(void *data, calldata_t params)
{
int code = (int)calldata_int(params, "errorcode");
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data),
"StreamingStop", Q_ARG(int, code));
}
#define SERVICE_PATH "obs-studio/basic/service.json"
void OBSBasic::SaveService()
{
if (!service)
return;
BPtr<char> 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<char> serviceJsonPath(os_get_config_path(SERVICE_PATH));
if (!serviceJsonPath)
return false;
BPtr<char> 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)
void OBSBasic::StreamingStart()
{
ui->streamButton->setText("Stop Streaming");
}
void OBSBasic::StreamingStop(int errorcode)
{
UNUSED_PARAMETER(errorcode);
ui->streamButton->setText("Start Streaming");
}
void OBSBasic::OutputStart()
{
ui->streamButton->setText("Stop Streaming");
}
static void OBSOutputStart(void *data, calldata_t params)
{
UNUSED_PARAMETER(params);
QMetaObject::invokeMethod(static_cast<OBSBasic*>(data), "OutputStart");
}
static void OBSOutputStop(void *data, calldata_t params)
{
int code = (int)calldata_int(params, "errorcode");
QMetaObject::invokeMethod(static_cast<OBSBasic*>(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);
}
/* 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);
}
}

View File

@ -36,31 +36,41 @@ class OBSBasic : public OBSMainWindow {
private:
std::unordered_map<obs_source_t, int> 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;
ConfigFile basicConfig;
int previewX, previewY;
float previewScale;
int resizeTimer;
ConfigFile basicConfig;
QPointer<OBSBasicProperties> 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();

View File

@ -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<OBSBasic*>(parent)),
ui (new Ui::OBSBasicSettings),
generalChanged (false),
outputsChanged (false),
audioChanged (false),
videoChanged (false),
pageIndex (0),
loading (true)
: QDialog (parent),
main (qobject_cast<OBSBasic*>(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::LoadSimpleOutputSettings()
{
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(), "SimpleOutput",
"ABitrate");
ui->simpleOutputPath->setText(path);
ui->simpleOutputVBitrate->setValue(videoBitrate);
SetComboByName(ui->simpleOutputABitrate,
std::to_string(audioBitrate).c_str());
}
void OBSBasicSettings::LoadOutputSettings()
{
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",
"VBitrate");
int audioBitrate = config_get_uint(main->Config(), "OutputTemp",
"ABitrate");
ui->streamURL->setText(QT_UTF8(url));
ui->streamKey->setText(QT_UTF8(key));
ui->streamVBitrate->setValue(videoBitrate);
ui->streamABitrate->setValue(audioBitrate);
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();

View File

@ -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();

View File

@ -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,

View File

@ -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)

View File

@ -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);

View File

@ -25,26 +25,26 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
@ -94,7 +94,8 @@
<ModuleDefinitionFile>../../../deps/jansson/src/jansson.def</ModuleDefinitionFile>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/32bit/$(TargetName)$(TargetExt)"</Command>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -113,7 +114,8 @@
<ModuleDefinitionFile>../../../deps/jansson/src/jansson.def</ModuleDefinitionFile>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/64bit/$(TargetName)$(TargetExt)"</Command>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -136,7 +138,8 @@
<ModuleDefinitionFile>../../../deps/jansson/src/jansson.def</ModuleDefinitionFile>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/32bit/$(TargetName)$(TargetExt)"</Command>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -159,7 +162,8 @@
<ModuleDefinitionFile>../../../deps/jansson/src/jansson.def</ModuleDefinitionFile>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/bin/64bit/$(TargetName)$(TargetExt)"</Command>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>

View File

@ -205,7 +205,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../libobs/util/vc;../../../deps/jansson/src</AdditionalIncludeDirectories>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
@ -227,7 +227,7 @@
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../libobs/util/vc;../../../deps/jansson/src</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -250,7 +250,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../libobs/util/vc;../../../deps/jansson/src</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -275,7 +275,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOBS_EXPORTS;PTW32_STATIC_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<AdditionalIncludeDirectories>../../../libobs/util/vc</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>../../../libobs/util/vc;../../../deps/jansson/src</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@ -93,6 +93,9 @@
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>jansson.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
@ -109,6 +112,9 @@
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>jansson.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
@ -129,6 +135,9 @@
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>jansson.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/32bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@ -149,6 +158,9 @@
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>jansson.lib;libobs.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(OutDir)$(TargetName)$(TargetExt)" "../../../build/obs-plugins/64bit/$(TargetName)$(TargetExt)"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\..\..\plugins\rtmp-services\rtmp-common.c" />