From 7e39ee291cce955580b00978d2f96c391b05fb72 Mon Sep 17 00:00:00 2001 From: Chip Bradford Date: Wed, 1 Jun 2022 14:34:13 -0700 Subject: [PATCH 1/3] libobs: Add support for multiple video mixes Split render_texture and derived fields in obs_core_video into new obs_core_video_mix struct. Add new APIs to add additional obs_view to the render loop, each with a separate render_texture / obs_core_video_mix. --- libobs/obs-encoder.c | 2 +- libobs/obs-internal.h | 74 ++++--- libobs/obs-source-deinterlace.c | 5 +- libobs/obs-video-gpu-encode.c | 26 ++- libobs/obs-video.c | 187 +++++++++++------- libobs/obs-view.c | 47 +++++ libobs/obs.c | 329 +++++++++++++++++++------------- libobs/obs.h | 6 + 8 files changed, 427 insertions(+), 249 deletions(-) diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index b03487ac5..38371180a 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -187,7 +187,7 @@ static inline bool has_scaling(const struct obs_encoder *encoder) static inline bool gpu_encode_available(const struct obs_encoder *encoder) { - struct obs_core_video *const video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; return (encoder->info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 && (video->using_p010_tex || video->using_nv12_tex); } diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 86c4627fa..32effee37 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -245,8 +245,9 @@ struct obs_task_info { void *param; }; -struct obs_core_video { - graphics_t *graphics; +struct obs_core_video_mix { + struct obs_view *view; + gs_stagesurf_t *active_copy_surfaces[NUM_TEXTURES][NUM_CHANNELS]; gs_stagesurf_t *copy_surfaces[NUM_TEXTURES][NUM_CHANNELS]; gs_texture_t *convert_textures[NUM_CHANNELS]; @@ -264,6 +265,41 @@ struct obs_core_video { bool using_p010_tex; struct circlebuf vframe_info_buffer; struct circlebuf vframe_info_buffer_gpu; + gs_stagesurf_t *mapped_surfaces[NUM_CHANNELS]; + int cur_texture; + volatile long raw_active; + volatile long gpu_encoder_active; + bool gpu_was_active; + bool raw_was_active; + bool was_active; + pthread_mutex_t gpu_encoder_mutex; + struct circlebuf gpu_encoder_queue; + struct circlebuf gpu_encoder_avail_queue; + DARRAY(obs_encoder_t *) gpu_encoders; + os_sem_t *gpu_encode_semaphore; + os_event_t *gpu_encode_inactive; + pthread_t gpu_encode_thread; + bool gpu_encode_thread_initialized; + volatile bool gpu_encode_stop; + + video_t *video; + + bool gpu_conversion; + const char *conversion_techs[NUM_CHANNELS]; + bool conversion_needed; + float conversion_width_i; + float conversion_height_i; + + float color_matrix[16]; + enum obs_scale_type scale_type; +}; + +extern int obs_init_video_mix(struct obs_video_info *ovi, + struct obs_core_video_mix *video); +extern void obs_free_video_mix(struct obs_core_video_mix *video); + +struct obs_core_video { + graphics_t *graphics; gs_effect_t *default_effect; gs_effect_t *default_rect_effect; gs_effect_t *opaque_effect; @@ -276,42 +312,19 @@ struct obs_core_video { gs_effect_t *bilinear_lowres_effect; gs_effect_t *premultiplied_alpha_effect; gs_samplerstate_t *point_sampler; - gs_stagesurf_t *mapped_surfaces[NUM_CHANNELS]; - int cur_texture; - volatile long raw_active; - volatile long gpu_encoder_active; - pthread_mutex_t gpu_encoder_mutex; - struct circlebuf gpu_encoder_queue; - struct circlebuf gpu_encoder_avail_queue; - DARRAY(obs_encoder_t *) gpu_encoders; - os_sem_t *gpu_encode_semaphore; - os_event_t *gpu_encode_inactive; - pthread_t gpu_encode_thread; - bool gpu_encode_thread_initialized; - volatile bool gpu_encode_stop; uint64_t video_time; uint64_t video_frame_interval_ns; + uint64_t video_half_frame_interval_ns; uint64_t video_avg_frame_time_ns; double video_fps; - video_t *video; pthread_t video_thread; uint32_t total_frames; uint32_t lagged_frames; bool thread_initialized; - bool gpu_conversion; - const char *conversion_techs[NUM_CHANNELS]; - bool conversion_needed; - float conversion_width_i; - float conversion_height_i; - - uint32_t output_width; - uint32_t output_height; uint32_t base_width; uint32_t base_height; - float color_matrix[16]; - enum obs_scale_type scale_type; gs_texture_t *transparent_texture; @@ -330,6 +343,10 @@ struct obs_core_video { pthread_mutex_t task_mutex; struct circlebuf tasks; + + pthread_mutex_t mixes_mutex; + DARRAY(struct obs_core_video_mix) mixes; + struct obs_core_video_mix *main_mix; }; struct audio_monitor; @@ -463,11 +480,6 @@ struct obs_graphics_context { uint64_t frame_time_total_ns; uint64_t fps_total_ns; uint32_t fps_total_frames; -#ifdef _WIN32 - bool gpu_was_active; -#endif - bool raw_was_active; - bool was_active; const char *video_thread_name; }; diff --git a/libobs/obs-source-deinterlace.c b/libobs/obs-source-deinterlace.c index ca0db4244..d8906d0c3 100644 --- a/libobs/obs-source-deinterlace.c +++ b/libobs/obs-source-deinterlace.c @@ -148,7 +148,6 @@ static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2) static inline void deinterlace_get_closest_frames(obs_source_t *s, uint64_t sys_time) { - const struct video_output_info *info; uint64_t half_interval; if (s->async_unbuffered && s->deinterlace_offset) { @@ -169,9 +168,7 @@ static inline void deinterlace_get_closest_frames(obs_source_t *s, if (!s->async_frames.num) return; - info = video_output_get_info(obs->video.video); - half_interval = (uint64_t)info->fps_den * 500000000ULL / - (uint64_t)info->fps_num; + half_interval = obs->video.video_half_frame_interval_ns; if (first_frame(s) || ready_deinterlace_frames(s, sys_time)) { uint64_t offset; diff --git a/libobs/obs-video-gpu-encode.c b/libobs/obs-video-gpu-encode.c index e98f35283..7317e4519 100644 --- a/libobs/obs-video-gpu-encode.c +++ b/libobs/obs-video-gpu-encode.c @@ -17,14 +17,12 @@ #include "obs-internal.h" -static void *gpu_encode_thread(void *unused) +static void *gpu_encode_thread(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; - uint64_t interval = video_output_get_frame_time(obs->video.video); + uint64_t interval = video_output_get_frame_time(video->video); DARRAY(obs_encoder_t *) encoders; int wait_frames = NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT; - UNUSED_PARAMETER(unused); da_init(encoders); os_set_thread_name("obs gpu encode thread"); @@ -149,10 +147,10 @@ static void *gpu_encode_thread(void *unused) return NULL; } -bool init_gpu_encoding(struct obs_core_video *video) +bool init_gpu_encoding(struct obs_core_video_mix *video) { #ifdef _WIN32 - struct obs_video_info *ovi = &video->ovi; + const struct video_output_info *info = video_output_get_info(video->video); video->gpu_encode_stop = false; @@ -161,14 +159,14 @@ bool init_gpu_encoding(struct obs_core_video *video) gs_texture_t *tex; gs_texture_t *tex_uv; - if (ovi->output_format == VIDEO_FORMAT_P010) { - gs_texture_create_p010(&tex, &tex_uv, ovi->output_width, - ovi->output_height, + if (info->format == VIDEO_FORMAT_P010) { + gs_texture_create_p010(&tex, &tex_uv, info->width, + info->height, GS_RENDER_TARGET | GS_SHARED_KM_TEX); } else { - gs_texture_create_nv12(&tex, &tex_uv, ovi->output_width, - ovi->output_height, + gs_texture_create_nv12(&tex, &tex_uv, info->width, + info->height, GS_RENDER_TARGET | GS_SHARED_KM_TEX); } @@ -191,7 +189,7 @@ bool init_gpu_encoding(struct obs_core_video *video) 0) return false; if (pthread_create(&video->gpu_encode_thread, NULL, gpu_encode_thread, - NULL) != 0) + video) != 0) return false; os_event_signal(video->gpu_encode_inactive); @@ -204,7 +202,7 @@ bool init_gpu_encoding(struct obs_core_video *video) #endif } -void stop_gpu_encoding_thread(struct obs_core_video *video) +void stop_gpu_encoding_thread(struct obs_core_video_mix *video) { if (video->gpu_encode_thread_initialized) { os_atomic_set_bool(&video->gpu_encode_stop, true); @@ -214,7 +212,7 @@ void stop_gpu_encoding_thread(struct obs_core_video *video) } } -void free_gpu_encoding(struct obs_core_video *video) +void free_gpu_encoding(struct obs_core_video_mix *video) { if (video->gpu_encode_semaphore) { os_sem_destroy(video->gpu_encode_semaphore); diff --git a/libobs/obs-video.c b/libobs/obs-video.c index 4ac2deff3..d464aed85 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -38,7 +38,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) if (!last_time) last_time = cur_time - - video_output_get_frame_time(obs->video.video); + obs->video.video_frame_interval_ns; delta_time = cur_time - last_time; seconds = (float)((double)delta_time / 1000000000.0); @@ -113,7 +113,7 @@ static inline void set_render_size(uint32_t width, uint32_t height) gs_set_viewport(0, 0, width, height); } -static inline void unmap_last_surface(struct obs_core_video *video) +static inline void unmap_last_surface(struct obs_core_video_mix *video) { for (int c = 0; c < NUM_CHANNELS; ++c) { if (video->mapped_surfaces[c]) { @@ -124,8 +124,11 @@ static inline void unmap_last_surface(struct obs_core_video *video) } static const char *render_main_texture_name = "render_main_texture"; -static inline void render_main_texture(struct obs_core_video *video) +static inline void render_main_texture(struct obs_core_video_mix *video) { + uint32_t base_width = obs->video.base_width; + uint32_t base_height = obs->video.base_height; + profile_start(render_main_texture_name); GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_MAIN_TEXTURE, render_main_texture_name); @@ -137,7 +140,7 @@ static inline void render_main_texture(struct obs_core_video *video) video->render_space); gs_clear(GS_CLEAR_COLOR, &clear_color, 1.0f, 0); - set_render_size(video->base_width, video->base_height); + set_render_size(base_width, base_height); pthread_mutex_lock(&obs->data.draw_callbacks_mutex); @@ -145,13 +148,13 @@ static inline void render_main_texture(struct obs_core_video *video) struct draw_callback *callback; callback = obs->data.draw_callbacks.array + (i - 1); - callback->draw(callback->param, video->base_width, - video->base_height); + callback->draw(callback->param, base_width, + base_height); } pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); - obs_view_render(&obs->data.main_view); + obs_view_render(video->view); video->texture_rendered = true; @@ -160,17 +163,20 @@ static inline void render_main_texture(struct obs_core_video *video) } static inline gs_effect_t * -get_scale_effect_internal(struct obs_core_video *video) +get_scale_effect_internal(struct obs_core_video_mix *mix) { + struct obs_core_video *video = &obs->video; + const struct video_output_info *info = video_output_get_info(mix->video); + /* if the dimension is under half the size of the original image, * bicubic/lanczos can't sample enough pixels to create an accurate * image, so use the bilinear low resolution effect instead */ - if (video->output_width < (video->base_width / 2) && - video->output_height < (video->base_height / 2)) { + if (info->width < (video->base_width / 2) && + info->height < (video->base_height / 2)) { return video->bilinear_lowres_effect; } - switch (video->scale_type) { + switch (mix->scale_type) { case OBS_SCALE_BILINEAR: return video->default_effect; case OBS_SCALE_LANCZOS: @@ -193,15 +199,17 @@ static inline bool resolution_close(struct obs_core_video *video, return labs(width_cmp) <= 16 && labs(height_cmp) <= 16; } -static inline gs_effect_t *get_scale_effect(struct obs_core_video *video, +static inline gs_effect_t *get_scale_effect(struct obs_core_video_mix *mix, uint32_t width, uint32_t height) { + struct obs_core_video *video = &obs->video; + if (resolution_close(video, width, height)) { return video->default_effect; } else { /* if the scale method couldn't be loaded, use either bicubic * or bilinear by default */ - gs_effect_t *effect = get_scale_effect_internal(video); + gs_effect_t *effect = get_scale_effect_internal(mix); if (!effect) effect = !!video->bicubic_effect ? video->bicubic_effect @@ -211,17 +219,18 @@ static inline gs_effect_t *get_scale_effect(struct obs_core_video *video, } static const char *render_output_texture_name = "render_output_texture"; -static inline gs_texture_t *render_output_texture(struct obs_core_video *video) +static inline gs_texture_t *render_output_texture(struct obs_core_video_mix *mix) { - gs_texture_t *texture = video->render_texture; - gs_texture_t *target = video->output_texture; + struct obs_core_video *video = &obs->video; + gs_texture_t *texture = mix->render_texture; + gs_texture_t *target = mix->output_texture; uint32_t width = gs_texture_get_width(target); uint32_t height = gs_texture_get_height(target); - gs_effect_t *effect = get_scale_effect(video, width, height); + gs_effect_t *effect = get_scale_effect(mix, width, height); gs_technique_t *tech; - if (video->ovi.output_format == VIDEO_FORMAT_RGBA) { + if (video_output_get_format(mix->video) == VIDEO_FORMAT_RGBA) { tech = gs_effect_get_technique(effect, "DrawAlphaDivide"); } else { if ((effect == video->default_effect) && @@ -298,13 +307,13 @@ static void render_convert_plane(gs_effect_t *effect, gs_texture_t *target, } static const char *render_convert_texture_name = "render_convert_texture"; -static void render_convert_texture(struct obs_core_video *video, +static void render_convert_texture(struct obs_core_video_mix *video, gs_texture_t *const *const convert_textures, gs_texture_t *texture) { profile_start(render_convert_texture_name); - gs_effect_t *effect = video->conversion_effect; + gs_effect_t *effect = obs->video.conversion_effect; gs_eparam_t *color_vec0 = gs_effect_get_param_by_name(effect, "color_vec0"); gs_eparam_t *color_vec1 = @@ -330,7 +339,7 @@ static void render_convert_texture(struct obs_core_video *video, if (convert_textures[0]) { const float hdr_nominal_peak_level = - video->hdr_nominal_peak_level; + obs->video.hdr_nominal_peak_level; const float multiplier = obs_get_video_sdr_white_level() / 10000.f; gs_effect_set_texture(image, texture); @@ -381,7 +390,7 @@ static void render_convert_texture(struct obs_core_video *video, static const char *stage_output_texture_name = "stage_output_texture"; static inline void -stage_output_texture(struct obs_core_video *video, int cur_texture, +stage_output_texture(struct obs_core_video_mix *video, int cur_texture, gs_texture_t *const *const convert_textures, gs_stagesurf_t *const *const copy_surfaces, size_t channel_count) @@ -418,7 +427,7 @@ stage_output_texture(struct obs_core_video *video, int cur_texture, } #ifdef _WIN32 -static inline bool queue_frame(struct obs_core_video *video, bool raw_active, +static inline bool queue_frame(struct obs_core_video_mix *video, bool raw_active, struct obs_vframe_info *vframe_info) { bool duplicate = @@ -480,7 +489,7 @@ finish: extern void full_stop(struct obs_encoder *encoder); -static inline void encode_gpu(struct obs_core_video *video, bool raw_active, +static inline void encode_gpu(struct obs_core_video_mix *video, bool raw_active, struct obs_vframe_info *vframe_info) { while (queue_frame(video, raw_active, vframe_info)) @@ -488,7 +497,7 @@ static inline void encode_gpu(struct obs_core_video *video, bool raw_active, } static const char *output_gpu_encoders_name = "output_gpu_encoders"; -static void output_gpu_encoders(struct obs_core_video *video, bool raw_active) +static void output_gpu_encoders(struct obs_core_video_mix *video, bool raw_active) { profile_start(output_gpu_encoders_name); @@ -510,7 +519,7 @@ end: } #endif -static inline void render_video(struct obs_core_video *video, bool raw_active, +static inline void render_video(struct obs_core_video_mix *video, bool raw_active, const bool gpu_active, int cur_texture) { gs_begin_scene(); @@ -559,7 +568,7 @@ static inline void render_video(struct obs_core_video *video, bool raw_active, gs_end_scene(); } -static inline bool download_frame(struct obs_core_video *video, +static inline bool download_frame(struct obs_core_video_mix *video, int prev_texture, struct video_data *frame) { if (!video->textures_copied[prev_texture]) @@ -763,7 +772,7 @@ static inline void copy_rgbx_frame(struct video_frame *output, } } -static inline void output_video_data(struct obs_core_video *video, +static inline void output_video_data(struct obs_core_video_mix *video, struct video_data *input_frame, int count) { const struct video_output_info *info; @@ -786,8 +795,8 @@ static inline void output_video_data(struct obs_core_video *video, } } -static inline void video_sleep(struct obs_core_video *video, bool raw_active, - const bool gpu_active, uint64_t *p_time, +static inline void video_sleep(struct obs_core_video *video, + uint64_t *p_time, uint64_t interval_ns) { struct obs_vframe_info vframe_info; @@ -815,12 +824,20 @@ static inline void video_sleep(struct obs_core_video *video, bool raw_active, vframe_info.timestamp = cur_time; vframe_info.count = count; + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) { + struct obs_core_video_mix *video = obs->video.mixes.array + i; + bool raw_active = video->raw_was_active; + bool gpu_active = video->gpu_was_active; + if (raw_active) circlebuf_push_back(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info)); if (gpu_active) circlebuf_push_back(&video->vframe_info_buffer_gpu, &vframe_info, sizeof(vframe_info)); + } + pthread_mutex_unlock(&obs->video.mixes_mutex); } static const char *output_frame_gs_context_name = "gs_context(video->graphics)"; @@ -828,9 +845,11 @@ static const char *output_frame_render_video_name = "render_video"; static const char *output_frame_download_frame_name = "download_frame"; static const char *output_frame_gs_flush_name = "gs_flush"; static const char *output_frame_output_video_data_name = "output_video_data"; -static inline void output_frame(bool raw_active, const bool gpu_active) +static inline void output_frame(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; + const bool raw_active = video->raw_was_active; + const bool gpu_active = video->gpu_was_active; + int cur_texture = video->cur_texture; int prev_texture = cur_texture == 0 ? NUM_TEXTURES - 1 : cur_texture - 1; @@ -840,7 +859,7 @@ static inline void output_frame(bool raw_active, const bool gpu_active) memset(&frame, 0, sizeof(struct video_data)); profile_start(output_frame_gs_context_name); - gs_enter_context(video->graphics); + gs_enter_context(obs->video.graphics); profile_start(output_frame_render_video_name); GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_RENDER_VIDEO, @@ -877,28 +896,42 @@ static inline void output_frame(bool raw_active, const bool gpu_active) video->cur_texture = 0; } +static inline void output_frames(void) +{ + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) { + struct obs_core_video_mix *mix = obs->video.mixes.array + i; + if (mix->view) { + output_frame(mix); + } else { + obs_free_video_mix(mix); + da_erase(obs->video.mixes, i); + i--; + num--; + } + } + pthread_mutex_unlock(&obs->video.mixes_mutex); +} + #define NBSP "\xC2\xA0" -static void clear_base_frame_data(void) +static void clear_base_frame_data(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; video->texture_rendered = false; video->texture_converted = false; circlebuf_free(&video->vframe_info_buffer); video->cur_texture = 0; } -static void clear_raw_frame_data(void) +static void clear_raw_frame_data(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; memset(video->textures_copied, 0, sizeof(video->textures_copied)); circlebuf_free(&video->vframe_info_buffer); } #ifdef _WIN32 -static void clear_gpu_frame_data(void) +static void clear_gpu_frame_data(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; circlebuf_free(&video->vframe_info_buffer_gpu); } #endif @@ -1006,35 +1039,63 @@ static void uninit_winrt_state(struct winrt_state *winrt) static const char *tick_sources_name = "tick_sources"; static const char *render_displays_name = "render_displays"; static const char *output_frame_name = "output_frame"; -bool obs_graphics_thread_loop(struct obs_graphics_context *context) +static inline void update_active_state(struct obs_core_video_mix *video) { - /* defer loop break to clean up sources */ - const bool stop_requested = video_output_stopped(obs->video.video); + const bool raw_was_active = video->raw_was_active; + const bool gpu_was_active = video->gpu_was_active; + const bool was_active = video->was_active; - uint64_t frame_start = os_gettime_ns(); - uint64_t frame_time_ns; - bool raw_active = os_atomic_load_long(&obs->video.raw_active) > 0; + bool raw_active = os_atomic_load_long(&video->raw_active) > 0; #ifdef _WIN32 const bool gpu_active = - os_atomic_load_long(&obs->video.gpu_encoder_active) > 0; + os_atomic_load_long(&video->gpu_encoder_active) > 0; const bool active = raw_active || gpu_active; #else const bool gpu_active = 0; const bool active = raw_active; #endif - if (!context->was_active && active) - clear_base_frame_data(); - if (!context->raw_was_active && raw_active) - clear_raw_frame_data(); + if (!was_active && active) + clear_base_frame_data(video); + if (!raw_was_active && raw_active) + clear_raw_frame_data(video); #ifdef _WIN32 - if (!context->gpu_was_active && gpu_active) - clear_gpu_frame_data(); + if (!gpu_was_active && gpu_active) + clear_gpu_frame_data(video); - context->gpu_was_active = gpu_active; + video->gpu_was_active = gpu_active; #endif - context->raw_was_active = raw_active; - context->was_active = active; + video->raw_was_active = raw_active; + video->was_active = active; +} + +static inline void update_active_states(void) +{ + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) + update_active_state(obs->video.mixes.array + i); + pthread_mutex_unlock(&obs->video.mixes_mutex); +} + +static inline bool stop_requested(void) +{ + bool success = true; + + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) + if (!video_output_stopped(obs->video.mixes.array[i].video)) + success = false; + pthread_mutex_unlock(&obs->video.mixes_mutex); + + return success; +} + +bool obs_graphics_thread_loop(struct obs_graphics_context *context) +{ + uint64_t frame_start = os_gettime_ns(); + uint64_t frame_time_ns; + + update_active_states(); profile_start(context->video_thread_name); @@ -1056,7 +1117,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context) #endif profile_start(output_frame_name); - output_frame(raw_active, gpu_active); + output_frames(); profile_end(output_frame_name); profile_start(render_displays_name); @@ -1071,7 +1132,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context) profile_reenable_thread(); - video_sleep(&obs->video, raw_active, gpu_active, &obs->video.video_time, + video_sleep(&obs->video, &obs->video.video_time, context->interval); context->frame_time_total_ns += frame_time_ns; @@ -1091,7 +1152,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context) context->fps_total_frames = 0; } - return !stop_requested; + return !stop_requested(); } void *obs_graphics_thread(void *param) @@ -1103,10 +1164,9 @@ void *obs_graphics_thread(void *param) is_graphics_thread = true; - const uint64_t interval = video_output_get_frame_time(obs->video.video); + const uint64_t interval = obs->video.video_frame_interval_ns; obs->video.video_time = os_gettime_ns(); - obs->video.video_frame_interval_ns = interval; os_set_thread_name("libobs: graphics thread"); @@ -1118,16 +1178,11 @@ void *obs_graphics_thread(void *param) srand((unsigned int)time(NULL)); struct obs_graphics_context context; - context.interval = video_output_get_frame_time(obs->video.video); + context.interval = interval; context.frame_time_total_ns = 0; context.fps_total_ns = 0; context.fps_total_frames = 0; context.last_time = 0; -#ifdef _WIN32 - context.gpu_was_active = false; -#endif - context.raw_was_active = false; - context.was_active = false; context.video_thread_name = video_thread_name; #ifdef __APPLE__ diff --git a/libobs/obs-view.c b/libobs/obs-view.c index 69cf1dcde..d2092d9fd 100644 --- a/libobs/obs-view.c +++ b/libobs/obs-view.c @@ -139,3 +139,50 @@ void obs_view_render(obs_view_t *view) pthread_mutex_unlock(&view->channels_mutex); } + +static inline size_t find_mix_for_view(obs_view_t *view) +{ + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) { + if (obs->video.mixes.array[i].view == view) + return i; + } + + return DARRAY_INVALID; +} + +static inline void set_main_mix() +{ + size_t idx = find_mix_for_view(&obs->data.main_view); + + struct obs_core_video_mix *mix = NULL; + if (idx != DARRAY_INVALID) + mix = obs->video.mixes.array + idx; + obs->video.main_mix = mix; +} + +video_t *obs_view_add(obs_view_t *view) +{ + struct obs_core_video_mix mix = {0}; + mix.view = view; + if (obs_init_video_mix(&obs->video.ovi, &mix) != 0) { + obs_free_video_mix(&mix); + return NULL; + } + + pthread_mutex_lock(&obs->video.mixes_mutex); + da_push_back(obs->video.mixes, &mix); + set_main_mix(); + pthread_mutex_unlock(&obs->video.mixes_mutex); + + return mix.video; +} + +void obs_view_remove(obs_view_t *view) +{ + pthread_mutex_lock(&obs->video.mixes_mutex); + size_t idx = find_mix_for_view(view); + if (idx != DARRAY_INVALID) + obs->video.mixes.array[idx].view = NULL; + set_main_mix(); + pthread_mutex_unlock(&obs->video.mixes_mutex); +} diff --git a/libobs/obs.c b/libobs/obs.c index 2a1743366..680c05e65 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -44,9 +44,9 @@ static inline void make_video_info(struct video_output_info *vi, vi->cache_size = 6; } -static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi) +static inline void calc_gpu_conversion_sizes(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; + const struct video_output_info *info = video_output_get_info(video->video); video->conversion_needed = false; video->conversion_techs[0] = NULL; @@ -55,19 +55,19 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi) video->conversion_width_i = 0.f; video->conversion_height_i = 0.f; - switch ((uint32_t)ovi->output_format) { + switch ((uint32_t)info->format) { case VIDEO_FORMAT_I420: video->conversion_needed = true; video->conversion_techs[0] = "Planar_Y"; video->conversion_techs[1] = "Planar_U_Left"; video->conversion_techs[2] = "Planar_V_Left"; - video->conversion_width_i = 1.f / (float)ovi->output_width; + video->conversion_width_i = 1.f / (float)info->width; break; case VIDEO_FORMAT_NV12: video->conversion_needed = true; video->conversion_techs[0] = "NV12_Y"; video->conversion_techs[1] = "NV12_UV"; - video->conversion_width_i = 1.f / (float)ovi->output_width; + video->conversion_width_i = 1.f / (float)info->width; break; case VIDEO_FORMAT_I444: video->conversion_needed = true; @@ -77,13 +77,13 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi) break; case VIDEO_FORMAT_I010: video->conversion_needed = true; - video->conversion_width_i = 1.f / (float)ovi->output_width; - video->conversion_height_i = 1.f / (float)ovi->output_height; - if (ovi->colorspace == VIDEO_CS_2100_PQ) { + video->conversion_width_i = 1.f / (float)info->width; + video->conversion_height_i = 1.f / (float)info->height; + if (info->colorspace == VIDEO_CS_2100_PQ) { video->conversion_techs[0] = "I010_PQ_Y"; video->conversion_techs[1] = "I010_PQ_U"; video->conversion_techs[2] = "I010_PQ_V"; - } else if (ovi->colorspace == VIDEO_CS_2100_HLG) { + } else if (info->colorspace == VIDEO_CS_2100_HLG) { video->conversion_techs[0] = "I010_HLG_Y"; video->conversion_techs[1] = "I010_HLG_U"; video->conversion_techs[2] = "I010_HLG_V"; @@ -95,12 +95,12 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi) break; case VIDEO_FORMAT_P010: video->conversion_needed = true; - video->conversion_width_i = 1.f / (float)ovi->output_width; - video->conversion_height_i = 1.f / (float)ovi->output_height; - if (ovi->colorspace == VIDEO_CS_2100_PQ) { + video->conversion_width_i = 1.f / (float)info->width; + video->conversion_height_i = 1.f / (float)info->height; + if (info->colorspace == VIDEO_CS_2100_PQ) { video->conversion_techs[0] = "P010_PQ_Y"; video->conversion_techs[1] = "P010_PQ_UV"; - } else if (ovi->colorspace == VIDEO_CS_2100_HLG) { + } else if (info->colorspace == VIDEO_CS_2100_HLG) { video->conversion_techs[0] = "P010_HLG_Y"; video->conversion_techs[1] = "P010_HLG_UV"; } else { @@ -110,22 +110,23 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi) } } -static bool obs_init_gpu_conversion(struct obs_video_info *ovi) +static bool obs_init_gpu_conversion(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; + const struct video_output_info *info = + video_output_get_info(video->video); - calc_gpu_conversion_sizes(ovi); + calc_gpu_conversion_sizes(video); - video->using_nv12_tex = ovi->output_format == VIDEO_FORMAT_NV12 + video->using_nv12_tex = info->format == VIDEO_FORMAT_NV12 ? gs_nv12_available() : false; - video->using_p010_tex = ovi->output_format == VIDEO_FORMAT_P010 + video->using_p010_tex = info->format == VIDEO_FORMAT_P010 ? gs_p010_available() : false; if (!video->conversion_needed) { blog(LOG_INFO, "GPU conversion not available for format: %u", - (unsigned int)ovi->output_format); + (unsigned int)info->format); video->gpu_conversion = false; video->using_nv12_tex = false; video->using_p010_tex = false; @@ -154,7 +155,7 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) if (!gs_texture_create_nv12( &video->convert_textures_encode[0], &video->convert_textures_encode[1], - ovi->output_width, ovi->output_height, + info->width, info->height, GS_RENDER_TARGET | GS_SHARED_KM_TEX)) { return false; } @@ -162,7 +163,7 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) if (!gs_texture_create_p010( &video->convert_textures_encode[0], &video->convert_textures_encode[1], - ovi->output_width, ovi->output_height, + info->width, info->height, GS_RENDER_TARGET | GS_SHARED_KM_TEX)) { return false; } @@ -171,18 +172,16 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) bool success = true; - const struct video_output_info *info = - video_output_get_info(video->video); switch (info->format) { case VIDEO_FORMAT_I420: video->convert_textures[0] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R8, 1, NULL, GS_RENDER_TARGET); video->convert_textures[1] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1, + info->width / 2, info->height / 2, GS_R8, 1, NULL, GS_RENDER_TARGET); video->convert_textures[2] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1, + info->width / 2, info->height / 2, GS_R8, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) @@ -190,23 +189,23 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) break; case VIDEO_FORMAT_NV12: video->convert_textures[0] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R8, 1, NULL, GS_RENDER_TARGET); video->convert_textures[1] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8G8, + info->width / 2, info->height / 2, GS_R8G8, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1]) success = false; break; case VIDEO_FORMAT_I444: video->convert_textures[0] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R8, 1, NULL, GS_RENDER_TARGET); video->convert_textures[1] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R8, 1, NULL, GS_RENDER_TARGET); video->convert_textures[2] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R8, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) @@ -214,13 +213,13 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) break; case VIDEO_FORMAT_I010: video->convert_textures[0] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R16, 1, NULL, GS_RENDER_TARGET); video->convert_textures[1] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R16, + info->width / 2, info->height / 2, GS_R16, 1, NULL, GS_RENDER_TARGET); video->convert_textures[2] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R16, + info->width / 2, info->height / 2, GS_R16, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) @@ -228,10 +227,10 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) break; case VIDEO_FORMAT_P010: video->convert_textures[0] = - gs_texture_create(ovi->output_width, ovi->output_height, + gs_texture_create(info->width, info->height, GS_R16, 1, NULL, GS_RENDER_TARGET); video->convert_textures[1] = gs_texture_create( - ovi->output_width / 2, ovi->output_height / 2, GS_RG16, + info->width / 2, info->height / 2, GS_RG16, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1]) success = false; @@ -257,72 +256,70 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi) return success; } -static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i) +static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video, size_t i) { - struct obs_core_video *video = &obs->video; - const struct video_output_info *info = video_output_get_info(video->video); switch (info->format) { case VIDEO_FORMAT_I420: video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R8); + info->width, info->height, GS_R8); if (!video->copy_surfaces[i][0]) return false; video->copy_surfaces[i][1] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8); + info->width / 2, info->height / 2, GS_R8); if (!video->copy_surfaces[i][1]) return false; video->copy_surfaces[i][2] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8); + info->width / 2, info->height / 2, GS_R8); if (!video->copy_surfaces[i][2]) return false; break; case VIDEO_FORMAT_NV12: video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R8); + info->width, info->height, GS_R8); if (!video->copy_surfaces[i][0]) return false; video->copy_surfaces[i][1] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R8G8); + info->width / 2, info->height / 2, GS_R8G8); if (!video->copy_surfaces[i][1]) return false; break; case VIDEO_FORMAT_I444: video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R8); + info->width, info->height, GS_R8); if (!video->copy_surfaces[i][0]) return false; video->copy_surfaces[i][1] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R8); + info->width, info->height, GS_R8); if (!video->copy_surfaces[i][1]) return false; video->copy_surfaces[i][2] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R8); + info->width, info->height, GS_R8); if (!video->copy_surfaces[i][2]) return false; break; case VIDEO_FORMAT_I010: video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R16); + info->width, info->height, GS_R16); if (!video->copy_surfaces[i][0]) return false; video->copy_surfaces[i][1] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R16); + info->width / 2, info->height / 2, GS_R16); if (!video->copy_surfaces[i][1]) return false; video->copy_surfaces[i][2] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_R16); + info->width / 2, info->height / 2, GS_R16); if (!video->copy_surfaces[i][2]) return false; break; case VIDEO_FORMAT_P010: video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_R16); + info->width, info->height, GS_R16); if (!video->copy_surfaces[i][0]) return false; video->copy_surfaces[i][1] = gs_stagesurface_create( - ovi->output_width / 2, ovi->output_height / 2, GS_RG16); + info->width / 2, info->height / 2, GS_RG16); if (!video->copy_surfaces[i][1]) return false; break; @@ -333,9 +330,9 @@ static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i) return true; } -static bool obs_init_textures(struct obs_video_info *ovi) +static bool obs_init_textures(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; + const struct video_output_info *info = video_output_get_info(video->video); bool success = true; @@ -343,16 +340,16 @@ static bool obs_init_textures(struct obs_video_info *ovi) #ifdef _WIN32 if (video->using_nv12_tex) { video->copy_surfaces_encode[i] = - gs_stagesurface_create_nv12(ovi->output_width, - ovi->output_height); + gs_stagesurface_create_nv12(info->width, + info->height); if (!video->copy_surfaces_encode[i]) { success = false; break; } } else if (video->using_p010_tex) { video->copy_surfaces_encode[i] = - gs_stagesurface_create_p010(ovi->output_width, - ovi->output_height); + gs_stagesurface_create_p010(info->width, + info->height); if (!video->copy_surfaces_encode[i]) { success = false; break; @@ -361,13 +358,13 @@ static bool obs_init_textures(struct obs_video_info *ovi) #endif if (video->gpu_conversion) { - if (!obs_init_gpu_copy_surfaces(ovi, i)) { + if (!obs_init_gpu_copy_surfaces(video, i)) { success = false; break; } } else { video->copy_surfaces[i][0] = gs_stagesurface_create( - ovi->output_width, ovi->output_height, GS_RGBA); + info->width, info->height, GS_RGBA); if (!video->copy_surfaces[i][0]) { success = false; break; @@ -376,7 +373,7 @@ static bool obs_init_textures(struct obs_video_info *ovi) } enum gs_color_format format = GS_RGBA; - switch (ovi->output_format) { + switch (info->format) { case VIDEO_FORMAT_I010: case VIDEO_FORMAT_P010: case VIDEO_FORMAT_I210: @@ -386,27 +383,27 @@ static bool obs_init_textures(struct obs_video_info *ovi) } enum gs_color_space space = GS_CS_SRGB; - switch (ovi->colorspace) { + switch (info->colorspace) { case VIDEO_CS_2100_PQ: case VIDEO_CS_2100_HLG: space = GS_CS_709_EXTENDED; break; default: - switch (ovi->output_format) { + switch (info->format) { case VIDEO_FORMAT_I010: case VIDEO_FORMAT_P010: space = GS_CS_SRGB_16F; } } - video->render_texture = gs_texture_create(ovi->base_width, - ovi->base_height, format, 1, + video->render_texture = gs_texture_create(obs->video.base_width, + obs->video.base_height, format, 1, NULL, GS_RENDER_TARGET); if (!video->render_texture) success = false; - video->output_texture = gs_texture_create(ovi->output_width, - ovi->output_height, format, 1, + video->output_texture = gs_texture_create(info->width, + info->height, format, 1, NULL, GS_RENDER_TARGET); if (!video->output_texture) success = false; @@ -558,15 +555,15 @@ static int obs_init_graphics(struct obs_video_info *ovi) return success ? OBS_VIDEO_SUCCESS : OBS_VIDEO_FAIL; } -static inline void set_video_matrix(struct obs_core_video *video, - struct obs_video_info *ovi) +static inline void set_video_matrix(struct obs_core_video_mix *video, + struct video_output_info *info) { struct matrix4 mat; struct vec4 r_row; - if (format_is_yuv(ovi->output_format)) { + if (format_is_yuv(info->format)) { video_format_get_parameters_for_format( - ovi->colorspace, ovi->range, ovi->output_format, + info->colorspace, info->range, info->format, (float *)&mat, NULL, NULL); matrix4_inv(&mat, &mat); @@ -581,24 +578,23 @@ static inline void set_video_matrix(struct obs_core_video *video, memcpy(video->color_matrix, &mat, sizeof(float) * 16); } -static int obs_init_video(struct obs_video_info *ovi) +int obs_init_video_mix(struct obs_video_info *ovi, + struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; struct video_output_info vi; - int errorcode; + + pthread_mutex_init_value(&video->gpu_encoder_mutex); make_video_info(&vi, ovi); - video->base_width = ovi->base_width; - video->base_height = ovi->base_height; - video->output_width = ovi->output_width; - video->output_height = ovi->output_height; video->gpu_conversion = ovi->gpu_conversion; video->scale_type = ovi->scale_type; + video->gpu_was_active = false; + video->raw_was_active = false; + video->was_active = false; - set_video_matrix(video, ovi); - - errorcode = video_output_open(&video->video, &vi); + set_video_matrix(video, &vi); + int errorcode = video_output_open(&video->video, &vi); if (errorcode != VIDEO_OUTPUT_SUCCESS) { if (errorcode == VIDEO_OUTPUT_INVALIDPARAM) { blog(LOG_ERROR, "Invalid video parameters specified"); @@ -609,20 +605,35 @@ static int obs_init_video(struct obs_video_info *ovi) return OBS_VIDEO_FAIL; } - gs_enter_context(video->graphics); - - if (ovi->gpu_conversion && !obs_init_gpu_conversion(ovi)) + if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0) return OBS_VIDEO_FAIL; - if (!obs_init_textures(ovi)) + + gs_enter_context(obs->video.graphics); + + if (video->gpu_conversion && !obs_init_gpu_conversion(video)) + return OBS_VIDEO_FAIL; + if (!obs_init_textures(video)) return OBS_VIDEO_FAIL; gs_leave_context(); - if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0) - return OBS_VIDEO_FAIL; + return OBS_VIDEO_SUCCESS; +} + +static int obs_init_video(struct obs_video_info *ovi) +{ + struct obs_core_video *video = &obs->video; + video->base_width = ovi->base_width; + video->base_height = ovi->base_height; + video->video_frame_interval_ns = util_mul_div64(1000000000ULL, ovi->fps_den, ovi->fps_num); + video->video_half_frame_interval_ns = util_mul_div64(500000000ULL, ovi->fps_den, ovi->fps_num); + if (pthread_mutex_init(&video->task_mutex, NULL) < 0) return OBS_VIDEO_FAIL; + if (pthread_mutex_init(&video->mixes_mutex, NULL) < 0) + return OBS_VIDEO_FAIL; + int errorcode; #ifdef __APPLE__ errorcode = pthread_create(&video->video_thread, NULL, obs_graphics_thread_autorelease, obs); @@ -635,35 +646,35 @@ static int obs_init_video(struct obs_video_info *ovi) video->thread_initialized = true; video->ovi = *ovi; + + if (!obs_view_add(&obs->data.main_view)) + return OBS_VIDEO_FAIL; + return OBS_VIDEO_SUCCESS; } static void stop_video(void) { + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) + video_output_stop(obs->video.mixes.array[i].video); + pthread_mutex_unlock(&obs->video.mixes_mutex); + struct obs_core_video *video = &obs->video; void *thread_retval; - if (video->video) { - video_output_stop(video->video); if (video->thread_initialized) { pthread_join(video->video_thread, &thread_retval); video->thread_initialized = false; } - } } -static void obs_free_video(void) +static void obs_free_render_textures(struct obs_core_video_mix *video) { - struct obs_core_video *video = &obs->video; - - if (video->video) { - video_output_close(video->video); - video->video = NULL; - - if (!video->graphics) + if (!obs->video.graphics) return; - gs_enter_context(video->graphics); + gs_enter_context(obs->video.graphics); for (size_t c = 0; c < NUM_CHANNELS; c++) { if (video->mapped_surfaces[c]) { @@ -713,6 +724,15 @@ static void obs_free_video(void) video->output_texture = NULL; gs_leave_context(); +} + +void obs_free_video_mix(struct obs_core_video_mix *video) +{ + if (video->video) { + video_output_close(video->video); + video->video = NULL; + + obs_free_render_textures(video); circlebuf_free(&video->vframe_info_buffer); circlebuf_free(&video->vframe_info_buffer_gpu); @@ -726,15 +746,30 @@ static void obs_free_video(void) pthread_mutex_init_value(&video->gpu_encoder_mutex); da_free(video->gpu_encoders); - pthread_mutex_destroy(&video->task_mutex); - pthread_mutex_init_value(&video->task_mutex); - circlebuf_free(&video->tasks); - video->gpu_encoder_active = 0; video->cur_texture = 0; } } +static void obs_free_video(void) +{ + pthread_mutex_lock(&obs->video.mixes_mutex); + size_t num = obs->video.mixes.num; + if (num) + blog(LOG_WARNING, "%d views remain at shutdown", num); + for (size_t i = 0; i < num; i++) + obs_free_video_mix(obs->video.mixes.array + i); + pthread_mutex_unlock(&obs->video.mixes_mutex); + + pthread_mutex_destroy(&obs->video.mixes_mutex); + pthread_mutex_init_value(&obs->video.mixes_mutex); + da_free(obs->video.mixes); + + pthread_mutex_destroy(&obs->video.task_mutex); + pthread_mutex_init_value(&obs->video.task_mutex); + circlebuf_free(&obs->video.tasks); +} + static void obs_free_graphics(void) { struct obs_core_video *video = &obs->video; @@ -892,6 +927,7 @@ static void obs_free_data(void) data->valid = false; + obs_view_remove(&data->main_view); obs_main_view_free(&data->main_view); blog(LOG_INFO, "Freeing OBS context data"); @@ -1057,8 +1093,8 @@ static bool obs_init(const char *locale, const char *module_config_path, pthread_mutex_init_value(&obs->audio.monitoring_mutex); pthread_mutex_init_value(&obs->audio.task_mutex); - pthread_mutex_init_value(&obs->video.gpu_encoder_mutex); pthread_mutex_init_value(&obs->video.task_mutex); + pthread_mutex_init_value(&obs->video.mixes_mutex); obs->name_store_owned = !store; obs->name_store = store ? store : profiler_name_store_create(); @@ -1331,15 +1367,13 @@ int obs_reset_video(struct obs_video_info *ovi) return OBS_VIDEO_FAIL; /* don't allow changing of video settings if active. */ - if (obs->video.video && obs_video_active()) + if (obs_video_active()) return OBS_VIDEO_CURRENTLY_ACTIVE; if (!size_valid(ovi->output_width, ovi->output_height) || !size_valid(ovi->base_width, ovi->base_height)) return OBS_VIDEO_INVALID_PARAM; - struct obs_core_video *video = &obs->video; - stop_video(); obs_free_video(); @@ -1347,7 +1381,7 @@ int obs_reset_video(struct obs_video_info *ovi) ovi->output_width &= 0xFFFFFFFC; ovi->output_height &= 0xFFFFFFFE; - if (!video->graphics) { + if (!obs->video.graphics) { int errorcode = obs_init_graphics(ovi); if (errorcode != OBS_VIDEO_SUCCESS) { obs_free_graphics(); @@ -1461,12 +1495,10 @@ bool obs_reset_audio(const struct obs_audio_info *oai) bool obs_get_video_info(struct obs_video_info *ovi) { - struct obs_core_video *video = &obs->video; - - if (!video->graphics) + if (!obs->video.graphics) return false; - *ovi = video->ovi; + *ovi = obs->video.ovi; return true; } @@ -1617,7 +1649,7 @@ audio_t *obs_get_audio(void) video_t *obs_get_video(void) { - return obs->video.video; + return obs->video.main_mix->video; } /* TODO: optimize this later so it's not just O(N) string lookups */ @@ -1974,12 +2006,12 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c, enum gs_blend_type src_a, enum gs_blend_type dest_a) { - struct obs_core_video *video; + struct obs_core_video_mix *video; gs_texture_t *tex; gs_effect_t *effect; gs_eparam_t *param; - video = &obs->video; + video = obs->video.main_mix; if (!video->texture_rendered) return; @@ -2033,9 +2065,9 @@ void obs_render_main_texture_src_color_only(void) gs_texture_t *obs_get_main_texture(void) { - struct obs_core_video *video; + struct obs_core_video_mix *video; - video = &obs->video; + video = obs->video.main_mix; if (!video->texture_rendered) return NULL; @@ -2678,12 +2710,31 @@ uint32_t obs_get_lagged_frames(void) return obs->video.lagged_frames; } +struct obs_core_video_mix *get_mix_for_video(video_t *v) +{ + struct obs_core_video_mix *result = NULL; + + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) { + struct obs_core_video_mix *mix = obs->video.mixes.array + i; + + if (v == mix->video) { + result = mix; + break; + } + } + pthread_mutex_unlock(&obs->video.mixes_mutex); + + return result; +} + void start_raw_video(video_t *v, const struct video_scale_info *conversion, void (*callback)(void *param, struct video_data *frame), void *param) { - struct obs_core_video *video = &obs->video; - os_atomic_inc_long(&video->raw_active); + struct obs_core_video_mix *video = get_mix_for_video(v); + if (video) + os_atomic_inc_long(&video->raw_active); video_output_connect(v, conversion, callback, param); } @@ -2691,8 +2742,9 @@ void stop_raw_video(video_t *v, void (*callback)(void *param, struct video_data *frame), void *param) { - struct obs_core_video *video = &obs->video; - os_atomic_dec_long(&video->raw_active); + struct obs_core_video_mix *video = get_mix_for_video(v); + if (video) + os_atomic_dec_long(&video->raw_active); video_output_disconnect(v, callback, param); } @@ -2701,7 +2753,7 @@ void obs_add_raw_video_callback(const struct video_scale_info *conversion, struct video_data *frame), void *param) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; start_raw_video(video->video, conversion, callback, param); } @@ -2709,7 +2761,7 @@ void obs_remove_raw_video_callback(void (*callback)(void *param, struct video_data *frame), void *param) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; stop_raw_video(video->video, callback, param); } @@ -2752,13 +2804,13 @@ obs_data_t *obs_get_private_data(void) return private_data; } -extern bool init_gpu_encoding(struct obs_core_video *video); -extern void stop_gpu_encoding_thread(struct obs_core_video *video); -extern void free_gpu_encoding(struct obs_core_video *video); +extern bool init_gpu_encoding(struct obs_core_video_mix *video); +extern void stop_gpu_encoding_thread(struct obs_core_video_mix *video); +extern void free_gpu_encoding(struct obs_core_video_mix *video); bool start_gpu_encode(obs_encoder_t *encoder) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; bool success = true; obs_enter_graphics(); @@ -2784,7 +2836,7 @@ bool start_gpu_encode(obs_encoder_t *encoder) void stop_gpu_encode(obs_encoder_t *encoder) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; bool call_free = false; os_atomic_dec_long(&video->gpu_encoder_active); @@ -2811,21 +2863,32 @@ void stop_gpu_encode(obs_encoder_t *encoder) bool obs_video_active(void) { - struct obs_core_video *video = &obs->video; + bool result = false; - return os_atomic_load_long(&video->raw_active) > 0 || - os_atomic_load_long(&video->gpu_encoder_active) > 0; + pthread_mutex_lock(&obs->video.mixes_mutex); + for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) { + struct obs_core_video_mix *video = obs->video.mixes.array + i; + + if (os_atomic_load_long(&video->raw_active) > 0 || + os_atomic_load_long(&video->gpu_encoder_active) > 0) { + result = true; + break; + } + } + pthread_mutex_unlock(&obs->video.mixes_mutex); + + return result; } bool obs_nv12_tex_active(void) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; return video->using_nv12_tex; } bool obs_p010_tex_active(void) { - struct obs_core_video *video = &obs->video; + struct obs_core_video_mix *video = obs->video.main_mix; return video->using_p010_tex; } diff --git a/libobs/obs.h b/libobs/obs.h index 9df703527..43b8824aa 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -888,6 +888,12 @@ EXPORT obs_source_t *obs_view_get_source(obs_view_t *view, uint32_t channel); /** Renders the sources of this view context */ EXPORT void obs_view_render(obs_view_t *view); +/** Adds a view to the main render loop */ +EXPORT video_t *obs_view_add(obs_view_t *view); + +/** Removes a view from the main render loop */ +EXPORT void obs_view_remove(obs_view_t *view); + /* ------------------------------------------------------------------------- */ /* Display context */ From 0a87797a219e60432616816641fe7bd97e06207b Mon Sep 17 00:00:00 2001 From: Chip Bradford Date: Wed, 15 Jun 2022 10:07:50 -0700 Subject: [PATCH 2/3] libobs: Format changes for multiple video mixes --- libobs/obs-video-gpu-encode.c | 17 ++- libobs/obs-video.c | 41 +++---- libobs/obs.c | 205 +++++++++++++++++----------------- 3 files changed, 131 insertions(+), 132 deletions(-) diff --git a/libobs/obs-video-gpu-encode.c b/libobs/obs-video-gpu-encode.c index 7317e4519..0dfb11df0 100644 --- a/libobs/obs-video-gpu-encode.c +++ b/libobs/obs-video-gpu-encode.c @@ -150,7 +150,8 @@ static void *gpu_encode_thread(struct obs_core_video_mix *video) bool init_gpu_encoding(struct obs_core_video_mix *video) { #ifdef _WIN32 - const struct video_output_info *info = video_output_get_info(video->video); + const struct video_output_info *info = + video_output_get_info(video->video); video->gpu_encode_stop = false; @@ -160,15 +161,13 @@ bool init_gpu_encoding(struct obs_core_video_mix *video) gs_texture_t *tex_uv; if (info->format == VIDEO_FORMAT_P010) { - gs_texture_create_p010(&tex, &tex_uv, info->width, - info->height, - GS_RENDER_TARGET | - GS_SHARED_KM_TEX); + gs_texture_create_p010( + &tex, &tex_uv, info->width, info->height, + GS_RENDER_TARGET | GS_SHARED_KM_TEX); } else { - gs_texture_create_nv12(&tex, &tex_uv, info->width, - info->height, - GS_RENDER_TARGET | - GS_SHARED_KM_TEX); + gs_texture_create_nv12( + &tex, &tex_uv, info->width, info->height, + GS_RENDER_TARGET | GS_SHARED_KM_TEX); } if (!tex) { return false; diff --git a/libobs/obs-video.c b/libobs/obs-video.c index d464aed85..2584ed9be 100644 --- a/libobs/obs-video.c +++ b/libobs/obs-video.c @@ -37,8 +37,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time) float seconds; if (!last_time) - last_time = cur_time - - obs->video.video_frame_interval_ns; + last_time = cur_time - obs->video.video_frame_interval_ns; delta_time = cur_time - last_time; seconds = (float)((double)delta_time / 1000000000.0); @@ -148,8 +147,7 @@ static inline void render_main_texture(struct obs_core_video_mix *video) struct draw_callback *callback; callback = obs->data.draw_callbacks.array + (i - 1); - callback->draw(callback->param, base_width, - base_height); + callback->draw(callback->param, base_width, base_height); } pthread_mutex_unlock(&obs->data.draw_callbacks_mutex); @@ -166,7 +164,8 @@ static inline gs_effect_t * get_scale_effect_internal(struct obs_core_video_mix *mix) { struct obs_core_video *video = &obs->video; - const struct video_output_info *info = video_output_get_info(mix->video); + const struct video_output_info *info = + video_output_get_info(mix->video); /* if the dimension is under half the size of the original image, * bicubic/lanczos can't sample enough pixels to create an accurate @@ -219,7 +218,8 @@ static inline gs_effect_t *get_scale_effect(struct obs_core_video_mix *mix, } static const char *render_output_texture_name = "render_output_texture"; -static inline gs_texture_t *render_output_texture(struct obs_core_video_mix *mix) +static inline gs_texture_t * +render_output_texture(struct obs_core_video_mix *mix) { struct obs_core_video *video = &obs->video; gs_texture_t *texture = mix->render_texture; @@ -427,7 +427,8 @@ stage_output_texture(struct obs_core_video_mix *video, int cur_texture, } #ifdef _WIN32 -static inline bool queue_frame(struct obs_core_video_mix *video, bool raw_active, +static inline bool queue_frame(struct obs_core_video_mix *video, + bool raw_active, struct obs_vframe_info *vframe_info) { bool duplicate = @@ -497,7 +498,8 @@ static inline void encode_gpu(struct obs_core_video_mix *video, bool raw_active, } static const char *output_gpu_encoders_name = "output_gpu_encoders"; -static void output_gpu_encoders(struct obs_core_video_mix *video, bool raw_active) +static void output_gpu_encoders(struct obs_core_video_mix *video, + bool raw_active) { profile_start(output_gpu_encoders_name); @@ -519,8 +521,9 @@ end: } #endif -static inline void render_video(struct obs_core_video_mix *video, bool raw_active, - const bool gpu_active, int cur_texture) +static inline void render_video(struct obs_core_video_mix *video, + bool raw_active, const bool gpu_active, + int cur_texture) { gs_begin_scene(); @@ -795,8 +798,7 @@ static inline void output_video_data(struct obs_core_video_mix *video, } } -static inline void video_sleep(struct obs_core_video *video, - uint64_t *p_time, +static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time, uint64_t interval_ns) { struct obs_vframe_info vframe_info; @@ -830,12 +832,12 @@ static inline void video_sleep(struct obs_core_video *video, bool raw_active = video->raw_was_active; bool gpu_active = video->gpu_was_active; - if (raw_active) - circlebuf_push_back(&video->vframe_info_buffer, &vframe_info, - sizeof(vframe_info)); - if (gpu_active) - circlebuf_push_back(&video->vframe_info_buffer_gpu, - &vframe_info, sizeof(vframe_info)); + if (raw_active) + circlebuf_push_back(&video->vframe_info_buffer, + &vframe_info, sizeof(vframe_info)); + if (gpu_active) + circlebuf_push_back(&video->vframe_info_buffer_gpu, + &vframe_info, sizeof(vframe_info)); } pthread_mutex_unlock(&obs->video.mixes_mutex); } @@ -1132,8 +1134,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context) profile_reenable_thread(); - video_sleep(&obs->video, &obs->video.video_time, - context->interval); + video_sleep(&obs->video, &obs->video.video_time, context->interval); context->frame_time_total_ns += frame_time_ns; context->fps_total_ns += (obs->video.video_time - context->last_time); diff --git a/libobs/obs.c b/libobs/obs.c index 680c05e65..95223f464 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -46,7 +46,8 @@ static inline void make_video_info(struct video_output_info *vi, static inline void calc_gpu_conversion_sizes(struct obs_core_video_mix *video) { - const struct video_output_info *info = video_output_get_info(video->video); + const struct video_output_info *info = + video_output_get_info(video->video); video->conversion_needed = false; video->conversion_techs[0] = NULL; @@ -117,12 +118,10 @@ static bool obs_init_gpu_conversion(struct obs_core_video_mix *video) calc_gpu_conversion_sizes(video); - video->using_nv12_tex = info->format == VIDEO_FORMAT_NV12 - ? gs_nv12_available() - : false; - video->using_p010_tex = info->format == VIDEO_FORMAT_P010 - ? gs_p010_available() - : false; + video->using_nv12_tex = + info->format == VIDEO_FORMAT_NV12 ? gs_nv12_available() : false; + video->using_p010_tex = + info->format == VIDEO_FORMAT_P010 ? gs_p010_available() : false; if (!video->conversion_needed) { blog(LOG_INFO, "GPU conversion not available for format: %u", @@ -152,19 +151,19 @@ static bool obs_init_gpu_conversion(struct obs_core_video_mix *video) video->convert_textures_encode[1] = NULL; video->convert_textures_encode[2] = NULL; if (video->using_nv12_tex) { - if (!gs_texture_create_nv12( - &video->convert_textures_encode[0], - &video->convert_textures_encode[1], - info->width, info->height, - GS_RENDER_TARGET | GS_SHARED_KM_TEX)) { + if (!gs_texture_create_nv12(&video->convert_textures_encode[0], + &video->convert_textures_encode[1], + info->width, info->height, + GS_RENDER_TARGET | + GS_SHARED_KM_TEX)) { return false; } } else if (video->using_p010_tex) { - if (!gs_texture_create_p010( - &video->convert_textures_encode[0], - &video->convert_textures_encode[1], - info->width, info->height, - GS_RENDER_TARGET | GS_SHARED_KM_TEX)) { + if (!gs_texture_create_p010(&video->convert_textures_encode[0], + &video->convert_textures_encode[1], + info->width, info->height, + GS_RENDER_TARGET | + GS_SHARED_KM_TEX)) { return false; } } @@ -175,63 +174,63 @@ static bool obs_init_gpu_conversion(struct obs_core_video_mix *video) switch (info->format) { case VIDEO_FORMAT_I420: video->convert_textures[0] = - gs_texture_create(info->width, info->height, + gs_texture_create(info->width, info->height, GS_R8, 1, + NULL, GS_RENDER_TARGET); + video->convert_textures[1] = + gs_texture_create(info->width / 2, info->height / 2, + GS_R8, 1, NULL, GS_RENDER_TARGET); + video->convert_textures[2] = + gs_texture_create(info->width / 2, info->height / 2, GS_R8, 1, NULL, GS_RENDER_TARGET); - video->convert_textures[1] = gs_texture_create( - info->width / 2, info->height / 2, GS_R8, 1, - NULL, GS_RENDER_TARGET); - video->convert_textures[2] = gs_texture_create( - info->width / 2, info->height / 2, GS_R8, 1, - NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) success = false; break; case VIDEO_FORMAT_NV12: video->convert_textures[0] = - gs_texture_create(info->width, info->height, - GS_R8, 1, NULL, GS_RENDER_TARGET); - video->convert_textures[1] = gs_texture_create( - info->width / 2, info->height / 2, GS_R8G8, - 1, NULL, GS_RENDER_TARGET); + gs_texture_create(info->width, info->height, GS_R8, 1, + NULL, GS_RENDER_TARGET); + video->convert_textures[1] = + gs_texture_create(info->width / 2, info->height / 2, + GS_R8G8, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1]) success = false; break; case VIDEO_FORMAT_I444: video->convert_textures[0] = - gs_texture_create(info->width, info->height, - GS_R8, 1, NULL, GS_RENDER_TARGET); + gs_texture_create(info->width, info->height, GS_R8, 1, + NULL, GS_RENDER_TARGET); video->convert_textures[1] = - gs_texture_create(info->width, info->height, - GS_R8, 1, NULL, GS_RENDER_TARGET); + gs_texture_create(info->width, info->height, GS_R8, 1, + NULL, GS_RENDER_TARGET); video->convert_textures[2] = - gs_texture_create(info->width, info->height, - GS_R8, 1, NULL, GS_RENDER_TARGET); + gs_texture_create(info->width, info->height, GS_R8, 1, + NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) success = false; break; case VIDEO_FORMAT_I010: video->convert_textures[0] = - gs_texture_create(info->width, info->height, + gs_texture_create(info->width, info->height, GS_R16, 1, + NULL, GS_RENDER_TARGET); + video->convert_textures[1] = + gs_texture_create(info->width / 2, info->height / 2, + GS_R16, 1, NULL, GS_RENDER_TARGET); + video->convert_textures[2] = + gs_texture_create(info->width / 2, info->height / 2, GS_R16, 1, NULL, GS_RENDER_TARGET); - video->convert_textures[1] = gs_texture_create( - info->width / 2, info->height / 2, GS_R16, - 1, NULL, GS_RENDER_TARGET); - video->convert_textures[2] = gs_texture_create( - info->width / 2, info->height / 2, GS_R16, - 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1] || !video->convert_textures[2]) success = false; break; case VIDEO_FORMAT_P010: video->convert_textures[0] = - gs_texture_create(info->width, info->height, - GS_R16, 1, NULL, GS_RENDER_TARGET); - video->convert_textures[1] = gs_texture_create( - info->width / 2, info->height / 2, GS_RG16, - 1, NULL, GS_RENDER_TARGET); + gs_texture_create(info->width, info->height, GS_R16, 1, + NULL, GS_RENDER_TARGET); + video->convert_textures[1] = + gs_texture_create(info->width / 2, info->height / 2, + GS_RG16, 1, NULL, GS_RENDER_TARGET); if (!video->convert_textures[0] || !video->convert_textures[1]) success = false; break; @@ -256,7 +255,8 @@ static bool obs_init_gpu_conversion(struct obs_core_video_mix *video) return success; } -static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video, size_t i) +static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video, + size_t i) { const struct video_output_info *info = video_output_get_info(video->video); @@ -332,7 +332,8 @@ static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video, size_t static bool obs_init_textures(struct obs_core_video_mix *video) { - const struct video_output_info *info = video_output_get_info(video->video); + const struct video_output_info *info = + video_output_get_info(video->video); bool success = true; @@ -396,15 +397,14 @@ static bool obs_init_textures(struct obs_core_video_mix *video) } } - video->render_texture = gs_texture_create(obs->video.base_width, - obs->video.base_height, format, 1, - NULL, GS_RENDER_TARGET); + video->render_texture = + gs_texture_create(obs->video.base_width, obs->video.base_height, + format, 1, NULL, GS_RENDER_TARGET); if (!video->render_texture) success = false; - video->output_texture = gs_texture_create(info->width, - info->height, format, 1, - NULL, GS_RENDER_TARGET); + video->output_texture = gs_texture_create( + info->width, info->height, format, 1, NULL, GS_RENDER_TARGET); if (!video->output_texture) success = false; @@ -625,8 +625,10 @@ static int obs_init_video(struct obs_video_info *ovi) struct obs_core_video *video = &obs->video; video->base_width = ovi->base_width; video->base_height = ovi->base_height; - video->video_frame_interval_ns = util_mul_div64(1000000000ULL, ovi->fps_den, ovi->fps_num); - video->video_half_frame_interval_ns = util_mul_div64(500000000ULL, ovi->fps_den, ovi->fps_num); + video->video_frame_interval_ns = + util_mul_div64(1000000000ULL, ovi->fps_den, ovi->fps_num); + video->video_half_frame_interval_ns = + util_mul_div64(500000000ULL, ovi->fps_den, ovi->fps_num); if (pthread_mutex_init(&video->task_mutex, NULL) < 0) return OBS_VIDEO_FAIL; @@ -663,67 +665,64 @@ static void stop_video(void) struct obs_core_video *video = &obs->video; void *thread_retval; - if (video->thread_initialized) { - pthread_join(video->video_thread, &thread_retval); - video->thread_initialized = false; - } + if (video->thread_initialized) { + pthread_join(video->video_thread, &thread_retval); + video->thread_initialized = false; + } } static void obs_free_render_textures(struct obs_core_video_mix *video) { - if (!obs->video.graphics) - return; + if (!obs->video.graphics) + return; - gs_enter_context(obs->video.graphics); + gs_enter_context(obs->video.graphics); - for (size_t c = 0; c < NUM_CHANNELS; c++) { - if (video->mapped_surfaces[c]) { - gs_stagesurface_unmap( - video->mapped_surfaces[c]); - video->mapped_surfaces[c] = NULL; - } + for (size_t c = 0; c < NUM_CHANNELS; c++) { + if (video->mapped_surfaces[c]) { + gs_stagesurface_unmap(video->mapped_surfaces[c]); + video->mapped_surfaces[c] = NULL; } + } - for (size_t i = 0; i < NUM_TEXTURES; i++) { - for (size_t c = 0; c < NUM_CHANNELS; c++) { - if (video->copy_surfaces[i][c]) { - gs_stagesurface_destroy( - video->copy_surfaces[i][c]); - video->copy_surfaces[i][c] = NULL; - } - - video->active_copy_surfaces[i][c] = NULL; - } -#ifdef _WIN32 - if (video->copy_surfaces_encode[i]) { + for (size_t i = 0; i < NUM_TEXTURES; i++) { + for (size_t c = 0; c < NUM_CHANNELS; c++) { + if (video->copy_surfaces[i][c]) { gs_stagesurface_destroy( - video->copy_surfaces_encode[i]); - video->copy_surfaces_encode[i] = NULL; + video->copy_surfaces[i][c]); + video->copy_surfaces[i][c] = NULL; } -#endif + + video->active_copy_surfaces[i][c] = NULL; } - - gs_texture_destroy(video->render_texture); - - for (size_t c = 0; c < NUM_CHANNELS; c++) { - if (video->convert_textures[c]) { - gs_texture_destroy(video->convert_textures[c]); - video->convert_textures[c] = NULL; - } #ifdef _WIN32 - if (video->convert_textures_encode[c]) { - gs_texture_destroy( - video->convert_textures_encode[c]); - video->convert_textures_encode[c] = NULL; - } -#endif + if (video->copy_surfaces_encode[i]) { + gs_stagesurface_destroy(video->copy_surfaces_encode[i]); + video->copy_surfaces_encode[i] = NULL; } +#endif + } - gs_texture_destroy(video->output_texture); - video->render_texture = NULL; - video->output_texture = NULL; + gs_texture_destroy(video->render_texture); - gs_leave_context(); + for (size_t c = 0; c < NUM_CHANNELS; c++) { + if (video->convert_textures[c]) { + gs_texture_destroy(video->convert_textures[c]); + video->convert_textures[c] = NULL; + } +#ifdef _WIN32 + if (video->convert_textures_encode[c]) { + gs_texture_destroy(video->convert_textures_encode[c]); + video->convert_textures_encode[c] = NULL; + } +#endif + } + + gs_texture_destroy(video->output_texture); + video->render_texture = NULL; + video->output_texture = NULL; + + gs_leave_context(); } void obs_free_video_mix(struct obs_core_video_mix *video) From df446c3f6e0b8562de6b2b28f127252984ec5679 Mon Sep 17 00:00:00 2001 From: Chip Bradford Date: Tue, 19 Jul 2022 09:32:39 -0700 Subject: [PATCH 3/3] UI: Add Virtual Camera source selector dialog --- UI/CMakeLists.txt | 3 + UI/data/locale/en-US.ini | 11 ++ UI/forms/OBSBasicVCamConfig.ui | 113 ++++++++++++ UI/record-button.cpp | 142 +++++++++++++- UI/record-button.hpp | 28 ++- UI/window-basic-main-outputs.cpp | 11 +- UI/window-basic-main-transitions.cpp | 4 + UI/window-basic-main.cpp | 114 +++++------- UI/window-basic-main.hpp | 9 +- UI/window-basic-vcam-config.cpp | 264 +++++++++++++++++++++++++++ UI/window-basic-vcam-config.hpp | 27 +++ 11 files changed, 638 insertions(+), 88 deletions(-) create mode 100644 UI/forms/OBSBasicVCamConfig.ui create mode 100644 UI/window-basic-vcam-config.cpp create mode 100644 UI/window-basic-vcam-config.hpp diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 8e25b0d6c..1585a49d2 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -103,6 +103,7 @@ target_sources( forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicTransform.ui + forms/OBSBasicVCamConfig.ui forms/OBSExtraBrowsers.ui forms/OBSImporter.ui forms/OBSLogReply.ui @@ -257,6 +258,8 @@ target_sources( window-basic-transform.cpp window-basic-transform.hpp window-basic-preview.hpp + window-basic-vcam-config.cpp + window-basic-vcam-config.hpp window-dock.cpp window-dock.hpp window-importer.cpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 3403d966c..98e8755dc 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -700,6 +700,17 @@ Basic.Main.Ungroup="Ungroup" Basic.Main.GridMode="Grid Mode" Basic.Main.ListMode="List Mode" +# virtual camera configuration +Basic.Main.VirtualCamConfig="Configure Virtual Camera" +Basic.VCam.VirtualCamera="Virtual Camera" +Basic.VCam.OutputType="Output Type" +Basic.VCam.OutputSelection="Output Selection" +Basic.VCam.Internal="Internal" +Basic.VCam.InternalDefault="Program Output (Default)" +Basic.VCam.InternalPreview="Preview Output" +Basic.VCam.Start="Start" +Basic.VCam.Update="Update" + # basic mode file menu Basic.MainMenu.File="&File" Basic.MainMenu.File.Export="&Export" diff --git a/UI/forms/OBSBasicVCamConfig.ui b/UI/forms/OBSBasicVCamConfig.ui new file mode 100644 index 000000000..bc255b159 --- /dev/null +++ b/UI/forms/OBSBasicVCamConfig.ui @@ -0,0 +1,113 @@ + + + OBSBasicVCamConfig + + + + 0 + 0 + 400 + 170 + + + + Basic.VCam.VirtualCamera + + + + + + Basic.VCam.OutputType + + + + + + + + Basic.VCam.Internal + + + + + Basic.Scene + + + + + Basic.Main.Source + + + + + + + + Basic.VCam.OutputSelection + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + OBSBasicVCamConfig + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OBSBasicVCamConfig + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/UI/record-button.cpp b/UI/record-button.cpp index 0e080a796..a0f028938 100644 --- a/UI/record-button.cpp +++ b/UI/record-button.cpp @@ -18,19 +18,143 @@ void RecordButton::resizeEvent(QResizeEvent *event) event->accept(); } -void ReplayBufferButton::resizeEvent(QResizeEvent *event) +static QWidget *firstWidget(QLayoutItem *item) +{ + auto widget = item->widget(); + if (widget) + return widget; + + auto layout = item->layout(); + if (!layout) + return nullptr; + + auto n = layout->count(); + for (auto i = 0, n = layout->count(); i < n; i++) { + widget = firstWidget(layout->itemAt(i)); + if (widget) + return widget; + } + return nullptr; +} + +static QWidget *lastWidget(QLayoutItem *item) +{ + auto widget = item->widget(); + if (widget) + return widget; + + auto layout = item->layout(); + if (!layout) + return nullptr; + + auto n = layout->count(); + for (auto i = layout->count(); i > 0; i--) { + widget = lastWidget(layout->itemAt(i - 1)); + if (widget) + return widget; + } + return nullptr; +} + +static QWidget *getNextWidget(QBoxLayout *container, QLayoutItem *item) +{ + for (auto i = 1, n = container->count(); i < n; i++) { + if (container->itemAt(i - 1) == item) + return firstWidget(container->itemAt(i)); + } + return nullptr; +} + +ControlsSplitButton::ControlsSplitButton(const QString &text, + const QVariant &themeID, + void (OBSBasic::*clicked)()) + : QHBoxLayout(OBSBasic::Get()) +{ + button.reset(new QPushButton(text)); + button->setCheckable(true); + button->setProperty("themeID", themeID); + + button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + button->installEventFilter(this); + + OBSBasic *main = OBSBasic::Get(); + connect(button.data(), &QPushButton::clicked, main, clicked); + + addWidget(button.data()); +} + +void ControlsSplitButton::addIcon(const QString &name, const QVariant &themeID, + void (OBSBasic::*clicked)()) +{ + icon.reset(new QPushButton()); + icon->setAccessibleName(name); + icon->setToolTip(name); + icon->setChecked(false); + icon->setProperty("themeID", themeID); + + QSizePolicy sp; + sp.setHeightForWidth(true); + icon->setSizePolicy(sp); + + OBSBasic *main = OBSBasic::Get(); + connect(icon.data(), &QAbstractButton::clicked, main, clicked); + + addWidget(icon.data()); + QWidget::setTabOrder(button.data(), icon.data()); + + auto next = getNextWidget(main->ui->buttonsVLayout, this); + if (next) + QWidget::setTabOrder(icon.data(), next); +} + +void ControlsSplitButton::removeIcon() +{ + icon.reset(); +} + +void ControlsSplitButton::insert(int index) { OBSBasic *main = OBSBasic::Get(); - if (!main->replay) - return; + auto count = main->ui->buttonsVLayout->count(); + if (index < 0) + index = 0; + else if (index > count) + index = count; - QSize replaySize = main->replay->size(); - int height = main->ui->recordButton->size().height(); + main->ui->buttonsVLayout->insertLayout(index, this); - if (replaySize.height() != height || replaySize.width() != height) { - main->replay->setMinimumSize(height, height); - main->replay->setMaximumSize(height, height); + QWidget *prev = button.data(); + + if (index > 0) { + prev = lastWidget(main->ui->buttonsVLayout->itemAt(index - 1)); + if (prev) + QWidget::setTabOrder(prev, button.data()); + prev = button.data(); } - event->accept(); + if (icon) { + QWidget::setTabOrder(button.data(), icon.data()); + prev = icon.data(); + } + + if (index < count) { + auto next = firstWidget( + main->ui->buttonsVLayout->itemAt(index + 1)); + if (next) + QWidget::setTabOrder(prev, next); + } +} + +bool ControlsSplitButton::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Resize && icon) { + QSize iconSize = icon->size(); + int height = button->height(); + + if (iconSize.height() != height || iconSize.width() != height) { + icon->setMinimumSize(height, height); + icon->setMaximumSize(height, height); + } + } + return QObject::eventFilter(obj, event); } diff --git a/UI/record-button.hpp b/UI/record-button.hpp index d6c083e21..ea8d40c7d 100644 --- a/UI/record-button.hpp +++ b/UI/record-button.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include class RecordButton : public QPushButton { Q_OBJECT @@ -11,15 +13,27 @@ public: virtual void resizeEvent(QResizeEvent *event) override; }; -class ReplayBufferButton : public QPushButton { +class OBSBasic; + +class ControlsSplitButton : public QHBoxLayout { Q_OBJECT public: - inline ReplayBufferButton(const QString &text, - QWidget *parent = nullptr) - : QPushButton(text, parent) - { - } + ControlsSplitButton(const QString &text, const QVariant &themeID, + void (OBSBasic::*clicked)()); - virtual void resizeEvent(QResizeEvent *event) override; + void addIcon(const QString &name, const QVariant &themeID, + void (OBSBasic::*clicked)()); + void removeIcon(); + void insert(int index); + + inline QPushButton *first() { return button.data(); } + inline QPushButton *second() { return icon.data(); } + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event) override; + +private: + QScopedPointer button; + QScopedPointer icon; }; diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index dac421632..e423e176f 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -5,6 +5,7 @@ #include "audio-encoders.hpp" #include "window-basic-main.hpp" #include "window-basic-main-outputs.hpp" +#include "window-basic-vcam-config.hpp" using namespace std; @@ -178,6 +179,9 @@ static void OBSStopVirtualCam(void *data, calldata_t *params) os_atomic_set_bool(&virtualcam_active, false); QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); + + obs_output_set_media(output->virtualCam, nullptr, nullptr); + OBSBasicVCamConfig::StopVideo(); } /* ------------------------------------------------------------------------ */ @@ -226,8 +230,11 @@ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) bool BasicOutputHandler::StartVirtualCam() { if (main->vcamEnabled) { - obs_output_set_media(virtualCam, obs_get_video(), - obs_get_audio()); + video_t *video = OBSBasicVCamConfig::StartVideo(); + if (!video) + return false; + + obs_output_set_media(virtualCam, video, obs_get_audio()); if (!Active()) SetupOutputs(); diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp index 8a8d709fe..0c86dd272 100644 --- a/UI/window-basic-main-transitions.cpp +++ b/UI/window-basic-main-transitions.cpp @@ -21,6 +21,7 @@ #include #include #include "window-basic-main.hpp" +#include "window-basic-vcam-config.hpp" #include "display-helpers.hpp" #include "window-namedialog.hpp" #include "menu-button.hpp" @@ -283,6 +284,9 @@ void OBSBasic::OverrideTransition(OBSSource transition) obs_transition_swap_begin(transition, oldTransition); obs_set_output_source(0, transition); obs_transition_swap_end(transition, oldTransition); + + // Transition overrides don't raise an event so we need to call update directly + OBSBasicVCamConfig::UpdateOutputSource(); } } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 7736e03bf..a244b5039 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -52,6 +52,7 @@ #include "window-basic-main.hpp" #include "window-basic-stats.hpp" #include "window-basic-main-outputs.hpp" +#include "window-basic-vcam-config.hpp" #include "window-log-reply.hpp" #include "window-projector.hpp" #include "window-remux.hpp" @@ -1622,16 +1623,15 @@ void OBSBasic::ReplayBufferClicked() void OBSBasic::AddVCamButton() { - vcamButton = new ReplayBufferButton(QTStr("Basic.Main.StartVirtualCam"), - this); - vcamButton->setCheckable(true); - connect(vcamButton.data(), &QPushButton::clicked, this, - &OBSBasic::VCamButtonClicked); + OBSBasicVCamConfig::Init(); - vcamButton->setProperty("themeID", "vcamButton"); - ui->buttonsVLayout->insertWidget(2, vcamButton); - setTabOrder(ui->recordButton, vcamButton); - setTabOrder(vcamButton, ui->modeSwitch); + vcamButton = new ControlsSplitButton( + QTStr("Basic.Main.StartVirtualCam"), "vcamButton", + &OBSBasic::VCamButtonClicked); + vcamButton->addIcon(QTStr("Basic.Main.VirtualCamConfig"), + QStringLiteral("configIconSmall"), + &OBSBasic::VCamConfigButtonClicked); + vcamButton->insert(2); } void OBSBasic::ResetOutputs() @@ -1647,28 +1647,13 @@ void OBSBasic::ResetOutputs() : CreateSimpleOutputHandler(this)); delete replayBufferButton; - delete replayLayout; if (outputHandler->replayBuffer) { - replayBufferButton = new ReplayBufferButton( - QTStr("Basic.Main.StartReplayBuffer"), this); - replayBufferButton->setCheckable(true); - connect(replayBufferButton.data(), - &QPushButton::clicked, this, + replayBufferButton = new ControlsSplitButton( + QTStr("Basic.Main.StartReplayBuffer"), + "replayBufferButton", &OBSBasic::ReplayBufferClicked); - - replayBufferButton->setSizePolicy(QSizePolicy::Ignored, - QSizePolicy::Fixed); - - replayLayout = new QHBoxLayout(this); - replayLayout->addWidget(replayBufferButton); - - replayBufferButton->setProperty("themeID", - "replayBufferButton"); - ui->buttonsVLayout->insertLayout(2, replayLayout); - setTabOrder(ui->recordButton, replayBufferButton); - setTabOrder(replayBufferButton, - ui->buttonsVLayout->itemAt(3)->widget()); + replayBufferButton->insert(2); } if (sysTrayReplayBuffer) @@ -7257,19 +7242,19 @@ void OBSBasic::StartReplayBuffer() return; if (!UIValidation::NoSourcesConfirmation(this)) { - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } if (!OutputPathValid()) { OutputPathInvalidMessage(); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } if (LowDiskSpace()) { DiskSpaceMessage(); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); return; } @@ -7279,7 +7264,7 @@ void OBSBasic::StartReplayBuffer() SaveProject(); if (!outputHandler->StartReplayBuffer()) { - replayBufferButton->setChecked(false); + replayBufferButton->first()->setChecked(false); } else if (os_atomic_load_bool(&recording_paused)) { ShowReplayBufferPauseWarning(); } @@ -7290,10 +7275,12 @@ void OBSBasic::ReplayBufferStopping() if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StoppingReplayBuffer")); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StoppingReplayBuffer")); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); replayBufferStopping = true; if (api) @@ -7318,11 +7305,13 @@ void OBSBasic::ReplayBufferStart() if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer")); - replayBufferButton->setChecked(true); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StopReplayBuffer")); + replayBufferButton->first()->setChecked(true); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); replayBufferStopping = false; if (api) @@ -7375,11 +7364,13 @@ void OBSBasic::ReplayBufferStop(int code) if (!outputHandler || !outputHandler->replayBuffer) return; - replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer")); - replayBufferButton->setChecked(false); + replayBufferButton->first()->setText( + QTStr("Basic.Main.StartReplayBuffer")); + replayBufferButton->first()->setChecked(false); if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(replayBufferButton->text()); + sysTrayReplayBuffer->setText( + replayBufferButton->first()->text()); blog(LOG_INFO, REPLAY_BUFFER_STOP); @@ -7428,7 +7419,7 @@ void OBSBasic::StartVirtualCam() SaveProject(); if (!outputHandler->StartVirtualCam()) { - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); } } @@ -7450,10 +7441,10 @@ void OBSBasic::OnVirtualCamStart() if (!outputHandler || !outputHandler->virtualCam) return; - vcamButton->setText(QTStr("Basic.Main.StopVirtualCam")); + vcamButton->first()->setText(QTStr("Basic.Main.StopVirtualCam")); if (sysTrayVirtualCam) sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - vcamButton->setChecked(true); + vcamButton->first()->setChecked(true); if (api) api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); @@ -7468,10 +7459,10 @@ void OBSBasic::OnVirtualCamStop(int) if (!outputHandler || !outputHandler->virtualCam) return; - vcamButton->setText(QTStr("Basic.Main.StartVirtualCam")); + vcamButton->first()->setText(QTStr("Basic.Main.StartVirtualCam")); if (sysTrayVirtualCam) sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); if (api) api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); @@ -7623,7 +7614,7 @@ void OBSBasic::VCamButtonClicked() StopVirtualCam(); } else { if (!UIValidation::NoSourcesConfirmation(this)) { - vcamButton->setChecked(false); + vcamButton->first()->setChecked(false); return; } @@ -7631,6 +7622,12 @@ void OBSBasic::VCamButtonClicked() } } +void OBSBasic::VCamConfigButtonClicked() +{ + OBSBasicVCamConfig config(this); + config.exec(); +} + void OBSBasic::on_settingsButton_clicked() { on_action_Settings_triggered(); @@ -9757,6 +9754,7 @@ void OBSBasic::PauseRecording() os_atomic_set_bool(&recording_paused, true); + auto replay = replayBufferButton->second(); if (replay) replay->setEnabled(false); @@ -9801,6 +9799,7 @@ void OBSBasic::UnpauseRecording() os_atomic_set_bool(&recording_paused, false); + auto replay = replayBufferButton->second(); if (replay) replay->setEnabled(true); @@ -9877,28 +9876,13 @@ void OBSBasic::UpdateReplayBuffer(bool activate) { if (!activate || !outputHandler || !outputHandler->ReplayBufferActive()) { - replay.reset(); + replayBufferButton->removeIcon(); return; } - replay.reset(new QPushButton()); - replay->setAccessibleName(QTStr("Basic.Main.SaveReplay")); - replay->setToolTip(QTStr("Basic.Main.SaveReplay")); - replay->setChecked(false); - replay->setProperty("themeID", - QVariant(QStringLiteral("replayIconSmall"))); - - QSizePolicy sp; - sp.setHeightForWidth(true); - replay->setSizePolicy(sp); - - connect(replay.data(), &QAbstractButton::clicked, this, - &OBSBasic::ReplayBufferSave); - replayLayout->addWidget(replay.data()); - setTabOrder(replayLayout->itemAt(0)->widget(), - replayLayout->itemAt(1)->widget()); - setTabOrder(replayLayout->itemAt(1)->widget(), - ui->buttonsVLayout->itemAt(3)->widget()); + replayBufferButton->addIcon(QTStr("Basic.Main.SaveReplay"), + QStringLiteral("replayIconSmall"), + &OBSBasic::ReplayBufferSave); } #define MBYTE (1024ULL * 1024ULL) diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index d16dad7cd..ff9c6ac9b 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -178,7 +178,7 @@ class OBSBasic : public OBSMainWindow { friend class AutoConfig; friend class AutoConfigStreamPage; friend class RecordButton; - friend class ReplayBufferButton; + friend class ControlsSplitButton; friend class ExtraBrowsersModel; friend class ExtraBrowsersDelegate; friend class DeviceCaptureToolbar; @@ -298,12 +298,10 @@ private: QPointer startStreamMenu; QPointer transitionButton; - QPointer replayBufferButton; - QPointer replayLayout; + QPointer replayBufferButton; QScopedPointer pause; - QScopedPointer replay; - QPointer vcamButton; + QPointer vcamButton; bool vcamEnabled = false; QScopedPointer trayIcon; @@ -1038,6 +1036,7 @@ private slots: void on_streamButton_clicked(); void on_recordButton_clicked(); void VCamButtonClicked(); + void VCamConfigButtonClicked(); void on_settingsButton_clicked(); void Screenshot(OBSSource source_ = nullptr); void ScreenshotSelectedSource(); diff --git a/UI/window-basic-vcam-config.cpp b/UI/window-basic-vcam-config.cpp new file mode 100644 index 000000000..5f21f71e7 --- /dev/null +++ b/UI/window-basic-vcam-config.cpp @@ -0,0 +1,264 @@ +#include "window-basic-vcam-config.hpp" +#include "window-basic-main.hpp" +#include "qt-wrappers.hpp" +#include "remote-text.hpp" +#include +#include +#include +#include + +using namespace std; + +enum class VCamOutputType { + Internal, + Scene, + Source, +}; + +enum class VCamInternalType { + Default, + Preview, +}; + +struct VCamConfig { + VCamOutputType type = VCamOutputType::Internal; + VCamInternalType internal = VCamInternalType::Default; + string scene; + string source; +}; + +static VCamConfig *vCamConfig = nullptr; + +OBSBasicVCamConfig::OBSBasicVCamConfig(QWidget *parent) + : QDialog(parent), ui(new Ui::OBSBasicVCamConfig) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + ui->setupUi(this); + + auto type = (int)vCamConfig->type; + ui->outputType->setCurrentIndex(type); + OutputTypeChanged(type); + connect(ui->outputType, + static_cast( + &QComboBox::currentIndexChanged), + this, &OBSBasicVCamConfig::OutputTypeChanged); + + auto start = ui->buttonBox->button(QDialogButtonBox::Ok); + if (!obs_frontend_virtualcam_active()) + start->setText(QTStr("Basic.VCam.Start")); + else + start->setText(QTStr("Basic.VCam.Update")); + connect(start, &QPushButton::clicked, this, + &OBSBasicVCamConfig::SaveAndStart); +} + +void OBSBasicVCamConfig::OutputTypeChanged(int type) +{ + auto list = ui->outputSelection; + list->clear(); + + switch ((VCamOutputType)type) { + case VCamOutputType::Internal: + list->addItem(QTStr("Basic.VCam.InternalDefault")); + list->addItem(QTStr("Basic.VCam.InternalPreview")); + list->setCurrentIndex((int)vCamConfig->internal); + break; + + case VCamOutputType::Scene: { + // Scenes in default order + BPtr scenes = obs_frontend_get_scene_names(); + int idx = 0; + for (char **temp = scenes; *temp; temp++) { + list->addItem(*temp); + + if (vCamConfig->scene.compare(*temp) == 0) + list->setCurrentIndex(list->count() - 1); + } + break; + } + + case VCamOutputType::Source: { + // Sources in alphabetical order + vector sources; + auto AddSource = [&](obs_source_t *source) { + auto name = obs_source_get_name(source); + auto flags = obs_source_get_output_flags(source); + + if (!(obs_source_get_output_flags(source) & + OBS_SOURCE_VIDEO)) + return; + + sources.push_back(name); + }; + using AddSource_t = decltype(AddSource); + + obs_enum_sources( + [](void *data, obs_source_t *source) { + auto &AddSource = + *static_cast(data); + if (!obs_source_removed(source)) + AddSource(source); + return true; + }, + static_cast(&AddSource)); + + // Sort and select current item + sort(sources.begin(), sources.end()); + for (auto &&source : sources) { + list->addItem(source.c_str()); + + if (vCamConfig->source == source) + list->setCurrentIndex(list->count() - 1); + } + break; + } + } +} + +void OBSBasicVCamConfig::SaveAndStart() +{ + auto type = (VCamOutputType)ui->outputType->currentIndex(); + auto out = ui->outputSelection; + switch (type) { + case VCamOutputType::Internal: + vCamConfig->internal = (VCamInternalType)out->currentIndex(); + break; + case VCamOutputType::Scene: + vCamConfig->scene = out->currentText().toStdString(); + break; + case VCamOutputType::Source: + vCamConfig->source = out->currentText().toStdString(); + break; + default: + // unknown value, don't save type + return; + } + + vCamConfig->type = type; + + // Start the vcam if needed, if already running just update the source + if (!obs_frontend_virtualcam_active()) + obs_frontend_start_virtualcam(); + else + UpdateOutputSource(); +} + +static void SaveCallback(obs_data_t *data, bool saving, void *) +{ + if (saving) { + OBSDataAutoRelease obj = obs_data_create(); + + obs_data_set_int(obj, "type", (int)vCamConfig->type); + obs_data_set_int(obj, "internal", (int)vCamConfig->internal); + obs_data_set_string(obj, "scene", vCamConfig->scene.c_str()); + obs_data_set_string(obj, "source", vCamConfig->source.c_str()); + + obs_data_set_obj(data, "virtual-camera", obj); + } else { + OBSDataAutoRelease obj = + obs_data_get_obj(data, "virtual-camera"); + + vCamConfig->type = + (VCamOutputType)obs_data_get_int(obj, "type"); + vCamConfig->internal = + (VCamInternalType)obs_data_get_int(obj, "internal"); + vCamConfig->scene = obs_data_get_string(obj, "scene"); + vCamConfig->source = obs_data_get_string(obj, "source"); + } +} + +static void EventCallback(enum obs_frontend_event event, void *) +{ + if (vCamConfig->type != VCamOutputType::Internal) + return; + + // Update output source if the preview scene changes + // or if the default transition is changed + switch (event) { + case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: + if (vCamConfig->internal != VCamInternalType::Preview) + return; + break; + case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: + if (vCamConfig->internal != VCamInternalType::Default) + return; + break; + default: + return; + } + + OBSBasicVCamConfig::UpdateOutputSource(); +} + +void OBSBasicVCamConfig::Init() +{ + if (vCamConfig) + return; + + vCamConfig = new VCamConfig; + + obs_frontend_add_save_callback(SaveCallback, nullptr); + obs_frontend_add_event_callback(EventCallback, nullptr); +} + +static obs_view_t *view = nullptr; +static video_t *video = nullptr; + +video_t *OBSBasicVCamConfig::StartVideo() +{ + if (!video) { + view = obs_view_create(); + video = obs_view_add(view); + } + UpdateOutputSource(); + return video; +} + +void OBSBasicVCamConfig::StopVideo() +{ + if (view) { + obs_view_remove(view); + obs_view_set_source(view, 0, nullptr); + obs_view_destroy(view); + view = nullptr; + } + video = nullptr; +} + +void OBSBasicVCamConfig::UpdateOutputSource() +{ + if (!view) + return; + + obs_source_t *source = nullptr; + + switch ((VCamOutputType)vCamConfig->type) { + case VCamOutputType::Internal: + switch (vCamConfig->internal) { + case VCamInternalType::Default: + source = obs_get_output_source(0); + break; + case VCamInternalType::Preview: + OBSSource s = OBSBasic::Get()->GetCurrentSceneSource(); + obs_source_get_ref(s); + source = s; + break; + } + break; + + case VCamOutputType::Scene: + source = obs_get_source_by_name(vCamConfig->scene.c_str()); + break; + + case VCamOutputType::Source: + source = obs_get_source_by_name(vCamConfig->source.c_str()); + break; + } + + auto current = obs_view_get_source(view, 0); + if (source != current) + obs_view_set_source(view, 0, source); + obs_source_release(source); + obs_source_release(current); +} diff --git a/UI/window-basic-vcam-config.hpp b/UI/window-basic-vcam-config.hpp new file mode 100644 index 000000000..4a00e0219 --- /dev/null +++ b/UI/window-basic-vcam-config.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "ui_OBSBasicVCamConfig.h" + +class OBSBasicVCamConfig : public QDialog { + Q_OBJECT + +public: + static void Init(); + + static video_t *StartVideo(); + static void StopVideo(); + static void UpdateOutputSource(); + + explicit OBSBasicVCamConfig(QWidget *parent = 0); + +private slots: + void OutputTypeChanged(int type); + void SaveAndStart(); + +private: + std::unique_ptr ui; +};