Przeglądaj źródła

Fix drawing bug with async video sources

Before, async video sources would flicker because they were only being
drawn when they were updated.  So when updated, they'd draw that frame,
then it would stop drawing it until it updated again.  This fixes that
issue and they should now draw properly.

Also, fix a few other minor bugs and issues relating to async video,
and make it so that non-async video filters can be properly applied to
them.

For the purposes of testing, change the 'test-random' source to an async
video source that updates every quarter of a second with a new random
face.

Also fix a bug where non-async video sources wouldn't have filter
effects applied properly.
jp9000 11 lat temu
rodzic
commit
2451b80ef6
5 zmienionych plików z 156 dodań i 98 usunięć
  1. 7 1
      libobs/obs-internal.h
  2. 3 2
      libobs/obs-module.c
  3. 85 54
      libobs/obs-source.c
  4. 10 3
      libobs/obs-source.h
  5. 51 38
      test/test-input/test-random.c

+ 7 - 1
libobs/obs-internal.h

@@ -193,6 +193,7 @@ struct obs_source {
 	signal_handler_t                signals;
 	proc_handler_t                  procs;
 
+	/* signals to call the source update in the video thread */
 	bool                            defer_update;
 
 	/* ensures show/hide are only called once */
@@ -237,9 +238,14 @@ struct obs_source {
 	float                           transition_volume;
 
 	/* async video data */
-	texture_t                       output_texture;
+	texture_t                       async_texture;
+	enum video_format               async_format;
+	float                           async_color_matrix[16];
+	bool                            async_flip;
 	DARRAY(struct source_frame*)    video_frames;
 	pthread_mutex_t                 video_mutex;
+	uint32_t                        async_width;
+	uint32_t                        async_height;
 
 	/* filters */
 	struct obs_source               *filter_parent;

+ 3 - 2
libobs/obs-module.c

@@ -128,8 +128,9 @@ void obs_register_source_s(const struct obs_source_info *info, size_t size)
 	CHECK_REQUIRED_VAL(info, create,    obs_register_source);
 	CHECK_REQUIRED_VAL(info, destroy,   obs_register_source);
 
-	if (info->type == OBS_SOURCE_TYPE_INPUT &&
-	    info->output_flags & OBS_SOURCE_VIDEO) {
+	if (info->type == OBS_SOURCE_TYPE_INPUT          &&
+	    (info->output_flags & OBS_SOURCE_VIDEO) != 0 &&
+	    (info->output_flags & OBS_SOURCE_ASYNC) == 0) {
 		CHECK_REQUIRED_VAL(info, getwidth,  obs_register_source);
 		CHECK_REQUIRED_VAL(info, getheight, obs_register_source);
 	}

+ 85 - 54
libobs/obs-source.c

@@ -240,7 +240,7 @@ static void obs_source_destroy(struct obs_source *source)
 		source_frame_destroy(source->video_frames.array[i]);
 
 	gs_entercontext(obs->video.graphics);
-	texture_destroy(source->output_texture);
+	texture_destroy(source->async_texture);
 	gs_leavecontext();
 
 	if (source->data)
@@ -529,7 +529,7 @@ static inline void handle_ts_jump(obs_source_t source, uint64_t expected,
 	                source->name, diff, expected, ts);
 
 	/* if has video, ignore audio data until reset */
-	if (source->info.output_flags & OBS_SOURCE_ASYNC_VIDEO)
+	if (source->info.output_flags & OBS_SOURCE_ASYNC)
 		os_atomic_dec_long(&source->audio_reset_ref);
 	else 
 		reset_audio_timing(source, ts);
@@ -577,21 +577,25 @@ static void source_output_audio_line(obs_source_t source,
 	audio_line_output(source->audio_line, &in);
 }
 
-static bool set_texture_size(obs_source_t source, struct source_frame *frame)
+static inline bool set_async_texture_size(struct obs_source *source,
+		struct source_frame *frame)
 {
-	if (source->output_texture) {
-		uint32_t width  = texture_getwidth(source->output_texture);
-		uint32_t height = texture_getheight(source->output_texture);
-
-		if (width == frame->width && height == frame->height)
+	if (source->async_texture) {
+		if (source->async_width  == frame->width &&
+		    source->async_height == frame->height)
 			return true;
 	}
 
-	texture_destroy(source->output_texture);
-	source->output_texture = gs_create_texture(frame->width, frame->height,
+	texture_destroy(source->async_texture);
+	source->async_texture = gs_create_texture(frame->width, frame->height,
 			GS_RGBA, 1, NULL, GS_DYNAMIC);
 
-	return source->output_texture != NULL;
+	if (!source->async_texture)
+		return false;
+
+	source->async_width  = frame->width;
+	source->async_height = frame->height;
+	return true;
 }
 
 enum convert_type {
@@ -626,11 +630,18 @@ static inline enum convert_type get_convert_type(enum video_format format)
 	return CONVERT_NONE;
 }
 
-static bool upload_frame(texture_t tex, const struct source_frame *frame)
+static bool update_async_texture(struct obs_source *source,
+		const struct source_frame *frame)
 {
-	void *ptr;
-	uint32_t linesize;
+	texture_t         tex  = source->async_texture;
 	enum convert_type type = get_convert_type(frame->format);
+	void              *ptr;
+	uint32_t          linesize;
+
+	source->async_format = frame->format;
+	source->async_flip   = frame->flip;
+	memcpy(source->async_color_matrix, frame->color_matrix,
+			sizeof(frame->color_matrix));
 
 	if (type == CONVERT_NONE) {
 		texture_setimage(tex, frame->data[0], frame->linesize[0],
@@ -663,44 +674,59 @@ static bool upload_frame(texture_t tex, const struct source_frame *frame)
 	return true;
 }
 
-static void obs_source_draw_texture(texture_t tex, struct source_frame *frame)
+static inline void obs_source_draw_texture(struct obs_source *source,
+		effect_t effect, float *color_matrix)
 {
-	effect_t    effect = obs->video.default_effect;
-	bool        yuv    = format_is_yuv(frame->format);
-	const char  *type  = yuv ? "DrawMatrix" : "Draw";
-	technique_t tech;
-	eparam_t    param;
-
-	if (!upload_frame(tex, frame))
-		return;
+	texture_t tex = source->async_texture;
+	eparam_t  param;
 
-	tech = effect_gettechnique(effect, type);
-	technique_begin(tech);
-	technique_beginpass(tech, 0);
-
-	if (yuv) {
+	if (color_matrix) {
 		param = effect_getparambyname(effect, "color_matrix");
-		effect_setval(effect, param, frame->color_matrix,
-				sizeof(float) * 16);
+		effect_setval(effect, param, color_matrix, sizeof(float) * 16);
 	}
 
 	param = effect_getparambyname(effect, "image");
 	effect_settexture(effect, param, tex);
 
-	gs_draw_sprite(tex, frame->flip ? GS_FLIP_V : 0, 0, 0);
+	gs_draw_sprite(tex, source->async_flip ? GS_FLIP_V : 0, 0, 0);
+}
 
-	technique_endpass(tech);
-	technique_end(tech);
+static void obs_source_draw_async_texture(struct obs_source *source)
+{
+	effect_t    effect   = gs_geteffect();
+	bool        yuv      = format_is_yuv(source->async_format);
+	const char  *type    = yuv ? "DrawMatrix" : "Draw";
+	bool        def_draw = (!effect);
+	technique_t tech;
+
+	if (def_draw) {
+		effect = obs_get_default_effect();
+		tech = effect_gettechnique(effect, type);
+		technique_begin(tech);
+		technique_beginpass(tech, 0);
+	}
+
+	obs_source_draw_texture(source, effect,
+			yuv ? source->async_color_matrix : NULL);
+
+	if (def_draw) {
+		technique_endpass(tech);
+		technique_end(tech);
+	}
 }
 
 static void obs_source_render_async_video(obs_source_t source)
 {
 	struct source_frame *frame = obs_source_getframe(source);
-	if (!frame)
-		return;
+	if (frame) {
+		if (!set_async_texture_size(source, frame))
+			return;
+		if (!update_async_texture(source, frame))
+			return;
+	}
 
-	if (set_texture_size(source, frame))
-		obs_source_draw_texture(source->output_texture, frame);
+	if (source->async_texture)
+		obs_source_draw_async_texture(source);
 
 	obs_source_releaseframe(source, frame);
 }
@@ -731,48 +757,53 @@ static inline void obs_source_default_render(obs_source_t source,
 
 static inline void obs_source_main_render(obs_source_t source)
 {
-	uint32_t flags = source->info.output_flags;
-	bool color_matrix = (flags & OBS_SOURCE_COLOR_MATRIX) != 0;
+	uint32_t flags      = source->info.output_flags;
+	bool color_matrix   = (flags & OBS_SOURCE_COLOR_MATRIX) != 0;
+	bool custom_draw    = (flags & OBS_SOURCE_CUSTOM_DRAW) != 0;
 	bool default_effect = !source->filter_parent &&
 	                      source->filters.num == 0 &&
-	                      (flags & OBS_SOURCE_CUSTOM_DRAW) == 0;
+	                      !custom_draw;
 
 	if (default_effect)
 		obs_source_default_render(source, color_matrix);
 	else
-		source->info.video_render(source->data, NULL);
+		source->info.video_render(source->data,
+				custom_draw ? NULL : gs_geteffect());
 }
 
 void obs_source_video_render(obs_source_t source)
 {
 	if (!source) return;
 
-	if (source->info.video_render) {
-		if (source->filters.num && !source->rendering_filter)
-			obs_source_render_filters(source);
-		else
-			obs_source_main_render(source);
+	if (source->filters.num && !source->rendering_filter)
+		obs_source_render_filters(source);
 
-	} else if (source->filter_target) {
+	else if (source->info.video_render)
+		obs_source_main_render(source);
+
+	else if (source->filter_target)
 		obs_source_video_render(source->filter_target);
 
-	} else {
+	else
 		obs_source_render_async_video(source);
-	}
 }
 
 uint32_t obs_source_getwidth(obs_source_t source)
 {
-	if (source && source->info.getwidth)
+	if (!source) return 0;
+
+	if (source->info.getwidth)
 		return source->info.getwidth(source->data);
-	return 0;
+	return source->async_width;
 }
 
 uint32_t obs_source_getheight(obs_source_t source)
 {
-	if (source && source->info.getheight)
+	if (!source) return 0;
+
+	if (source->info.getheight)
 		return source->info.getheight(source->data);
-	return 0;
+	return source->async_height;
 }
 
 obs_source_t obs_filter_getparent(obs_source_t filter)
@@ -1102,7 +1133,7 @@ void obs_source_output_audio(obs_source_t source,
 	output = filter_async_audio(source, &source->audio_data);
 
 	if (output) {
-		bool async = (flags & OBS_SOURCE_ASYNC_VIDEO) != 0;
+		bool async = (flags & OBS_SOURCE_ASYNC) != 0;
 
 		pthread_mutex_lock(&source->audio_mutex);
 

+ 10 - 3
libobs/obs-source.h

@@ -23,6 +23,7 @@
 extern "C" {
 #endif
 
+
 enum obs_source_type {
 	OBS_SOURCE_TYPE_INPUT,
 	OBS_SOURCE_TYPE_FILTER,
@@ -31,6 +32,7 @@ enum obs_source_type {
 	OBS_SOURCE_TYPE_SCENE = 0x80000000
 };
 
+
 /**
  * @name Source output flags
  *
@@ -55,6 +57,9 @@ enum obs_source_type {
  */
 #define OBS_SOURCE_AUDIO        (1<<1)
 
+/** Async video flag (use OBS_SOURCE_ASYNC_VIDEO) */
+#define OBS_SOURCE_ASYNC        (1<<2)
+
 /**
  * Source passes raw video data via RAM.
  *
@@ -66,7 +71,7 @@ enum obs_source_type {
  * obs_source_getframe to get the current frame data, and
  * obs_source_releaseframe to release the data when complete.
  */
-#define OBS_SOURCE_ASYNC_VIDEO  ((1<<2) | OBS_SOURCE_VIDEO)
+#define OBS_SOURCE_ASYNC_VIDEO  (OBS_SOURCE_ASYNC | OBS_SOURCE_VIDEO)
 
 /**
  * Source uses custom drawing, rather than a default effect.
@@ -132,10 +137,12 @@ struct obs_source_info {
 	/** Destroys the private data for the source */
 	void (*destroy)(void *data);
 
-	/** Returns the width of the source.  Required if input and video */
+	/** Returns the width of the source.  Required if this is an input
+	 * source and has non-async video */
 	uint32_t (*getwidth)(void *data);
 
-	/** Returns the height of the source.  Required if input and video */
+	/** Returns the height of the source.  Required if this is an input
+	 * source and has non-async video */
 	uint32_t (*getheight)(void *data);
 
 	/* ----------------------------------------------------------------- */

+ 51 - 38
test/test-input/test-random.c

@@ -1,8 +1,13 @@
 #include <stdlib.h>
+#include <util/threading.h>
+#include <util/platform.h>
 #include <obs.h>
 
 struct random_tex {
-	texture_t texture;
+	obs_source_t source;
+	os_event_t   stop_signal;
+	pthread_t    thread;
+	bool         initialized;
 };
 
 static const char *random_getname(const char *locale)
@@ -16,19 +21,18 @@ static void random_destroy(void *data)
 	struct random_tex *rt = data;
 
 	if (rt) {
-		gs_entercontext(obs_graphics());
+		if (rt->initialized) {
+			os_event_signal(rt->stop_signal);
+			pthread_join(rt->thread, NULL);
+		}
 
-		texture_destroy(rt->texture);
+		os_event_destroy(rt->stop_signal);
 		bfree(rt);
-
-		gs_leavecontext();
 	}
 }
 
-static void *random_create(obs_data_t settings, obs_source_t source)
+static inline void fill_texture(uint32_t *pixels)
 {
-	struct random_tex *rt = bzalloc(sizeof(struct random_tex));
-	uint32_t *pixels = bmalloc(20*20*4);
 	size_t x, y;
 
 	for (y = 0; y < 20; y++) {
@@ -41,53 +45,62 @@ static void *random_create(obs_data_t settings, obs_source_t source)
 			pixels[y*20 + x] = pixel;
 		}
 	}
+}
+
+static void *video_thread(void *data)
+{
+	struct random_tex   *rt = data;
+	uint32_t            pixels[20*20];
+	uint64_t            cur_time = os_gettime_ns();
+
+	struct source_frame frame = {
+		.data     = {[0] = (uint8_t*)pixels},
+		.linesize = {[0] = 20*4},
+		.width    = 20,
+		.height   = 20,
+		.format   = VIDEO_FORMAT_BGRX
+	};
+
+	while (os_event_try(rt->stop_signal) == EAGAIN) {
+		fill_texture(pixels);
 
-	gs_entercontext(obs_graphics());
+		frame.timestamp = cur_time;
 
-	rt->texture = gs_create_texture(20, 20, GS_RGBA, 1,
-			(const void**)&pixels, 0);
-	bfree(pixels);
+		obs_source_output_video(rt->source, &frame);
+
+		os_sleepto_ns(cur_time += 250000000);
+	}
+
+	return NULL;
+}
+
+static void *random_create(obs_data_t settings, obs_source_t source)
+{
+	struct random_tex *rt = bzalloc(sizeof(struct random_tex));
+	rt->source = source;
 
-	if (!rt->texture) {
+	if (os_event_init(&rt->stop_signal, OS_EVENT_TYPE_MANUAL) != 0) {
 		random_destroy(rt);
 		return NULL;
 	}
 
-	gs_leavecontext();
+	if (pthread_create(&rt->thread, NULL, video_thread, rt) != 0) {
+		random_destroy(rt);
+		return NULL;
+	}
+
+	rt->initialized = true;
 
 	UNUSED_PARAMETER(settings);
 	UNUSED_PARAMETER(source);
 	return rt;
 }
 
-static void random_video_render(void *data, effect_t effect)
-{
-	struct random_tex *rt = data;
-	eparam_t image = effect_getparambyname(effect, "image");
-	effect_settexture(effect, image, rt->texture);
-	gs_draw_sprite(rt->texture, 0, 0, 0);
-}
-
-static uint32_t random_getwidth(void *data)
-{
-	struct random_tex *rt = data;
-	return texture_getwidth(rt->texture);
-}
-
-static uint32_t random_getheight(void *data)
-{
-	struct random_tex *rt = data;
-	return texture_getheight(rt->texture);
-}
-
 struct obs_source_info test_random = {
 	.id           = "random",
 	.type         = OBS_SOURCE_TYPE_INPUT,
-	.output_flags = OBS_SOURCE_VIDEO,
+	.output_flags = OBS_SOURCE_ASYNC_VIDEO,
 	.getname      = random_getname,
 	.create       = random_create,
 	.destroy      = random_destroy,
-	.video_render = random_video_render,
-	.getwidth     = random_getwidth,
-	.getheight    = random_getheight
 };