1 /******************************************************************************
2     Copyright (C) 2016 by Hugh Bailey <obs.jim@gmail.com>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17 
18 #include "obs-internal.h"
19 
ready_deinterlace_frames(obs_source_t * source,uint64_t sys_time)20 static bool ready_deinterlace_frames(obs_source_t *source, uint64_t sys_time)
21 {
22 	struct obs_source_frame *next_frame = source->async_frames.array[0];
23 	struct obs_source_frame *prev_frame = NULL;
24 	struct obs_source_frame *frame = NULL;
25 	uint64_t sys_offset = sys_time - source->last_sys_timestamp;
26 	uint64_t frame_time = next_frame->timestamp;
27 	uint64_t frame_offset = 0;
28 	size_t idx = 1;
29 
30 	if (source->async_unbuffered) {
31 		while (source->async_frames.num > 2) {
32 			da_erase(source->async_frames, 0);
33 			remove_async_frame(source, next_frame);
34 			next_frame = source->async_frames.array[0];
35 		}
36 
37 		if (source->async_frames.num == 2) {
38 			bool prev_frame = true;
39 			if (source->async_unbuffered &&
40 			    source->deinterlace_offset) {
41 				const uint64_t timestamp =
42 					source->async_frames.array[0]->timestamp;
43 				const uint64_t after_timestamp =
44 					source->async_frames.array[1]->timestamp;
45 				const uint64_t duration =
46 					after_timestamp - timestamp;
47 				const uint64_t frame_end =
48 					timestamp + source->deinterlace_offset +
49 					duration;
50 				if (sys_time < frame_end) {
51 					// Don't skip ahead prematurely.
52 					prev_frame = false;
53 					source->deinterlace_frame_ts =
54 						timestamp - duration;
55 				}
56 			}
57 			source->async_frames.array[0]->prev_frame = prev_frame;
58 		}
59 		source->deinterlace_offset = 0;
60 		source->last_frame_ts = next_frame->timestamp;
61 		return true;
62 	}
63 
64 	/* account for timestamp invalidation */
65 	if (frame_out_of_bounds(source, frame_time)) {
66 		source->last_frame_ts = next_frame->timestamp;
67 		source->deinterlace_offset = 0;
68 		return true;
69 	} else {
70 		frame_offset = frame_time - source->last_frame_ts;
71 		source->last_frame_ts += sys_offset;
72 	}
73 
74 	while (source->last_frame_ts > next_frame->timestamp) {
75 
76 		/* this tries to reduce the needless frame duplication, also
77 		 * helps smooth out async rendering to frame boundaries.  In
78 		 * other words, tries to keep the framerate as smooth as
79 		 * possible */
80 		if ((source->last_frame_ts - next_frame->timestamp) < 2000000)
81 			break;
82 
83 		if (prev_frame) {
84 			da_erase(source->async_frames, 0);
85 			remove_async_frame(source, prev_frame);
86 		}
87 
88 		if (source->async_frames.num <= 2) {
89 			bool exit = true;
90 
91 			if (prev_frame) {
92 				prev_frame->prev_frame = true;
93 
94 			} else if (!frame && source->async_frames.num == 2) {
95 				exit = false;
96 			}
97 
98 			if (exit) {
99 				source->deinterlace_offset = 0;
100 				return true;
101 			}
102 		}
103 
104 		if (frame)
105 			idx = 2;
106 		else
107 			idx = 1;
108 
109 		prev_frame = frame;
110 		frame = next_frame;
111 		next_frame = source->async_frames.array[idx];
112 
113 		/* more timestamp checking and compensating */
114 		if ((next_frame->timestamp - frame_time) > MAX_TS_VAR) {
115 			source->last_frame_ts =
116 				next_frame->timestamp - frame_offset;
117 			source->deinterlace_offset = 0;
118 		}
119 
120 		frame_time = next_frame->timestamp;
121 		frame_offset = frame_time - source->last_frame_ts;
122 	}
123 
124 	if (prev_frame)
125 		prev_frame->prev_frame = true;
126 
127 	return frame != NULL;
128 }
129 
first_frame(obs_source_t * s)130 static inline bool first_frame(obs_source_t *s)
131 {
132 	if (s->last_frame_ts)
133 		return false;
134 
135 	if (s->async_frames.num >= 2)
136 		s->async_frames.array[0]->prev_frame = true;
137 	return true;
138 }
139 
uint64_diff(uint64_t ts1,uint64_t ts2)140 static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2)
141 {
142 	return (ts1 < ts2) ? (ts2 - ts1) : (ts1 - ts2);
143 }
144 
145 #define TWOX_TOLERANCE 1000000
146 #define TS_JUMP_THRESHOLD 70000000ULL
147 
deinterlace_get_closest_frames(obs_source_t * s,uint64_t sys_time)148 static inline void deinterlace_get_closest_frames(obs_source_t *s,
149 						  uint64_t sys_time)
150 {
151 	const struct video_output_info *info;
152 	uint64_t half_interval;
153 
154 	if (s->async_unbuffered && s->deinterlace_offset) {
155 		// Want to keep frame if it has not elapsed.
156 		const uint64_t frame_end =
157 			s->deinterlace_frame_ts + s->deinterlace_offset +
158 			((uint64_t)s->deinterlace_half_duration * 2) -
159 			TWOX_TOLERANCE;
160 		if (sys_time < frame_end) {
161 			// Process new frames if we think time jumped.
162 			const uint64_t diff = frame_end - sys_time;
163 			if (diff < TS_JUMP_THRESHOLD) {
164 				return;
165 			}
166 		}
167 	}
168 
169 	if (!s->async_frames.num)
170 		return;
171 
172 	info = video_output_get_info(obs->video.video);
173 	half_interval = (uint64_t)info->fps_den * 500000000ULL /
174 			(uint64_t)info->fps_num;
175 
176 	if (first_frame(s) || ready_deinterlace_frames(s, sys_time)) {
177 		uint64_t offset;
178 
179 		s->prev_async_frame = NULL;
180 		s->cur_async_frame = s->async_frames.array[0];
181 
182 		da_erase(s->async_frames, 0);
183 
184 		if (s->cur_async_frame->prev_frame) {
185 			s->prev_async_frame = s->cur_async_frame;
186 			s->cur_async_frame = s->async_frames.array[0];
187 
188 			da_erase(s->async_frames, 0);
189 
190 			s->deinterlace_half_duration =
191 				(uint32_t)((s->cur_async_frame->timestamp -
192 					    s->prev_async_frame->timestamp) /
193 					   2);
194 		} else {
195 			s->deinterlace_half_duration =
196 				(uint32_t)((s->cur_async_frame->timestamp -
197 					    s->deinterlace_frame_ts) /
198 					   2);
199 		}
200 
201 		if (!s->last_frame_ts)
202 			s->last_frame_ts = s->cur_async_frame->timestamp;
203 
204 		s->deinterlace_frame_ts = s->cur_async_frame->timestamp;
205 
206 		offset = obs->video.video_time - s->deinterlace_frame_ts;
207 
208 		if (!s->deinterlace_offset) {
209 			s->deinterlace_offset = offset;
210 		} else {
211 			uint64_t offset_diff =
212 				uint64_diff(s->deinterlace_offset, offset);
213 			if (offset_diff > half_interval)
214 				s->deinterlace_offset = offset;
215 		}
216 	}
217 }
218 
deinterlace_process_last_frame(obs_source_t * s,uint64_t sys_time)219 void deinterlace_process_last_frame(obs_source_t *s, uint64_t sys_time)
220 {
221 	if (s->prev_async_frame) {
222 		remove_async_frame(s, s->prev_async_frame);
223 		s->prev_async_frame = NULL;
224 	}
225 	if (s->cur_async_frame) {
226 		remove_async_frame(s, s->cur_async_frame);
227 		s->cur_async_frame = NULL;
228 	}
229 
230 	deinterlace_get_closest_frames(s, sys_time);
231 }
232 
set_deinterlace_texture_size(obs_source_t * source)233 void set_deinterlace_texture_size(obs_source_t *source)
234 {
235 	if (source->async_gpu_conversion) {
236 		source->async_prev_texrender =
237 			gs_texrender_create(GS_BGRX, GS_ZS_NONE);
238 
239 		for (int c = 0; c < source->async_channel_count; c++)
240 			source->async_prev_textures[c] = gs_texture_create(
241 				source->async_convert_width[c],
242 				source->async_convert_height[c],
243 				source->async_texture_formats[c], 1, NULL,
244 				GS_DYNAMIC);
245 
246 	} else {
247 		enum gs_color_format format =
248 			convert_video_format(source->async_format);
249 
250 		source->async_prev_textures[0] = gs_texture_create(
251 			source->async_width, source->async_height, format, 1,
252 			NULL, GS_DYNAMIC);
253 	}
254 }
255 
get_prev_frame(obs_source_t * source,bool * updated)256 static inline struct obs_source_frame *get_prev_frame(obs_source_t *source,
257 						      bool *updated)
258 {
259 	struct obs_source_frame *frame = NULL;
260 
261 	pthread_mutex_lock(&source->async_mutex);
262 
263 	*updated = source->cur_async_frame != NULL;
264 	frame = source->prev_async_frame;
265 	source->prev_async_frame = NULL;
266 
267 	if (frame)
268 		os_atomic_inc_long(&frame->refs);
269 
270 	pthread_mutex_unlock(&source->async_mutex);
271 
272 	return frame;
273 }
274 
deinterlace_update_async_video(obs_source_t * source)275 void deinterlace_update_async_video(obs_source_t *source)
276 {
277 	struct obs_source_frame *frame;
278 	bool updated;
279 
280 	if (source->deinterlace_rendered)
281 		return;
282 
283 	frame = get_prev_frame(source, &updated);
284 
285 	source->deinterlace_rendered = true;
286 	if (frame)
287 		frame = filter_async_video(source, frame);
288 
289 	if (frame) {
290 		if (set_async_texture_size(source, frame)) {
291 			update_async_textures(source, frame,
292 					      source->async_prev_textures,
293 					      source->async_prev_texrender);
294 		}
295 
296 		obs_source_release_frame(source, frame);
297 
298 	} else if (updated) { /* swap cur/prev if no previous texture */
299 		for (size_t c = 0; c < MAX_AV_PLANES; c++) {
300 			gs_texture_t *prev_tex = source->async_prev_textures[c];
301 			source->async_prev_textures[c] =
302 				source->async_textures[c];
303 			source->async_textures[c] = prev_tex;
304 		}
305 
306 		if (source->async_texrender) {
307 			gs_texrender_t *prev = source->async_prev_texrender;
308 			source->async_prev_texrender = source->async_texrender;
309 			source->async_texrender = prev;
310 		}
311 	}
312 }
313 
get_effect(enum obs_deinterlace_mode mode)314 static inline gs_effect_t *get_effect(enum obs_deinterlace_mode mode)
315 {
316 	switch (mode) {
317 	case OBS_DEINTERLACE_MODE_DISABLE:
318 		return NULL;
319 	case OBS_DEINTERLACE_MODE_DISCARD:
320 		return obs_load_effect(&obs->video.deinterlace_discard_effect,
321 				       "deinterlace_discard.effect");
322 	case OBS_DEINTERLACE_MODE_RETRO:
323 		return obs_load_effect(
324 			&obs->video.deinterlace_discard_2x_effect,
325 			"deinterlace_discard_2x.effect");
326 	case OBS_DEINTERLACE_MODE_BLEND:
327 		return obs_load_effect(&obs->video.deinterlace_blend_effect,
328 				       "deinterlace_blend.effect");
329 	case OBS_DEINTERLACE_MODE_BLEND_2X:
330 		return obs_load_effect(&obs->video.deinterlace_blend_2x_effect,
331 				       "deinterlace_blend_2x.effect");
332 	case OBS_DEINTERLACE_MODE_LINEAR:
333 		return obs_load_effect(&obs->video.deinterlace_linear_effect,
334 				       "deinterlace_linear.effect");
335 	case OBS_DEINTERLACE_MODE_LINEAR_2X:
336 		return obs_load_effect(&obs->video.deinterlace_linear_2x_effect,
337 				       "deinterlace_linear_2x.effect");
338 	case OBS_DEINTERLACE_MODE_YADIF:
339 		return obs_load_effect(&obs->video.deinterlace_yadif_effect,
340 				       "deinterlace_yadif.effect");
341 	case OBS_DEINTERLACE_MODE_YADIF_2X:
342 		return obs_load_effect(&obs->video.deinterlace_yadif_2x_effect,
343 				       "deinterlace_yadif_2x.effect");
344 	}
345 
346 	return NULL;
347 }
348 
deinterlace_linear_required(enum obs_deinterlace_mode mode)349 static bool deinterlace_linear_required(enum obs_deinterlace_mode mode)
350 {
351 	switch (mode) {
352 	case OBS_DEINTERLACE_MODE_DISABLE:
353 	case OBS_DEINTERLACE_MODE_DISCARD:
354 	case OBS_DEINTERLACE_MODE_RETRO:
355 		return false;
356 	case OBS_DEINTERLACE_MODE_BLEND:
357 	case OBS_DEINTERLACE_MODE_BLEND_2X:
358 	case OBS_DEINTERLACE_MODE_LINEAR:
359 	case OBS_DEINTERLACE_MODE_LINEAR_2X:
360 	case OBS_DEINTERLACE_MODE_YADIF:
361 	case OBS_DEINTERLACE_MODE_YADIF_2X:
362 		return true;
363 	}
364 
365 	return false;
366 }
367 
deinterlace_render(obs_source_t * s)368 void deinterlace_render(obs_source_t *s)
369 {
370 	gs_effect_t *effect = s->deinterlace_effect;
371 
372 	uint64_t frame2_ts;
373 	gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
374 	gs_eparam_t *prev =
375 		gs_effect_get_param_by_name(effect, "previous_image");
376 	gs_eparam_t *field = gs_effect_get_param_by_name(effect, "field_order");
377 	gs_eparam_t *frame2 = gs_effect_get_param_by_name(effect, "frame2");
378 	gs_eparam_t *dimensions =
379 		gs_effect_get_param_by_name(effect, "dimensions");
380 	struct vec2 size = {(float)s->async_width, (float)s->async_height};
381 
382 	gs_texture_t *cur_tex =
383 		s->async_texrender
384 			? gs_texrender_get_texture(s->async_texrender)
385 			: s->async_textures[0];
386 	gs_texture_t *prev_tex =
387 		s->async_prev_texrender
388 			? gs_texrender_get_texture(s->async_prev_texrender)
389 			: s->async_prev_textures[0];
390 
391 	if (!cur_tex || !prev_tex || !s->async_width || !s->async_height)
392 		return;
393 
394 	const bool linear_srgb =
395 		gs_get_linear_srgb() ||
396 		deinterlace_linear_required(s->deinterlace_mode);
397 
398 	const bool previous = gs_framebuffer_srgb_enabled();
399 	gs_enable_framebuffer_srgb(linear_srgb);
400 
401 	if (linear_srgb) {
402 		gs_effect_set_texture_srgb(image, cur_tex);
403 		gs_effect_set_texture_srgb(prev, prev_tex);
404 	} else {
405 		gs_effect_set_texture(image, cur_tex);
406 		gs_effect_set_texture(prev, prev_tex);
407 	}
408 
409 	gs_effect_set_int(field, s->deinterlace_top_first);
410 	gs_effect_set_vec2(dimensions, &size);
411 
412 	frame2_ts = s->deinterlace_frame_ts + s->deinterlace_offset +
413 		    s->deinterlace_half_duration - TWOX_TOLERANCE;
414 
415 	gs_effect_set_bool(frame2, obs->video.video_time >= frame2_ts);
416 
417 	while (gs_effect_loop(effect, "Draw"))
418 		gs_draw_sprite(NULL, s->async_flip ? GS_FLIP_V : 0,
419 			       s->async_width, s->async_height);
420 
421 	gs_enable_framebuffer_srgb(previous);
422 }
423 
enable_deinterlacing(obs_source_t * source,enum obs_deinterlace_mode mode)424 static void enable_deinterlacing(obs_source_t *source,
425 				 enum obs_deinterlace_mode mode)
426 {
427 	obs_enter_graphics();
428 
429 	if (source->async_format != VIDEO_FORMAT_NONE &&
430 	    source->async_width != 0 && source->async_height != 0)
431 		set_deinterlace_texture_size(source);
432 
433 	source->deinterlace_mode = mode;
434 	source->deinterlace_effect = get_effect(mode);
435 
436 	pthread_mutex_lock(&source->async_mutex);
437 	if (source->prev_async_frame) {
438 		remove_async_frame(source, source->prev_async_frame);
439 		source->prev_async_frame = NULL;
440 	}
441 	pthread_mutex_unlock(&source->async_mutex);
442 
443 	obs_leave_graphics();
444 }
445 
disable_deinterlacing(obs_source_t * source)446 static void disable_deinterlacing(obs_source_t *source)
447 {
448 	obs_enter_graphics();
449 	gs_texture_destroy(source->async_prev_textures[0]);
450 	gs_texture_destroy(source->async_prev_textures[1]);
451 	gs_texture_destroy(source->async_prev_textures[2]);
452 	gs_texrender_destroy(source->async_prev_texrender);
453 	source->deinterlace_mode = OBS_DEINTERLACE_MODE_DISABLE;
454 	source->async_prev_textures[0] = NULL;
455 	source->async_prev_textures[1] = NULL;
456 	source->async_prev_textures[2] = NULL;
457 	source->async_prev_texrender = NULL;
458 	obs_leave_graphics();
459 }
460 
obs_source_set_deinterlace_mode(obs_source_t * source,enum obs_deinterlace_mode mode)461 void obs_source_set_deinterlace_mode(obs_source_t *source,
462 				     enum obs_deinterlace_mode mode)
463 {
464 	if (!obs_source_valid(source, "obs_source_set_deinterlace_mode"))
465 		return;
466 	if (source->deinterlace_mode == mode)
467 		return;
468 
469 	if (source->deinterlace_mode == OBS_DEINTERLACE_MODE_DISABLE) {
470 		enable_deinterlacing(source, mode);
471 	} else if (mode == OBS_DEINTERLACE_MODE_DISABLE) {
472 		disable_deinterlacing(source);
473 	} else {
474 		obs_enter_graphics();
475 		source->deinterlace_mode = mode;
476 		source->deinterlace_effect = get_effect(mode);
477 		obs_leave_graphics();
478 	}
479 }
480 
481 enum obs_deinterlace_mode
obs_source_get_deinterlace_mode(const obs_source_t * source)482 obs_source_get_deinterlace_mode(const obs_source_t *source)
483 {
484 	return obs_source_valid(source, "obs_source_set_deinterlace_mode")
485 		       ? source->deinterlace_mode
486 		       : OBS_DEINTERLACE_MODE_DISABLE;
487 }
488 
obs_source_set_deinterlace_field_order(obs_source_t * source,enum obs_deinterlace_field_order field_order)489 void obs_source_set_deinterlace_field_order(
490 	obs_source_t *source, enum obs_deinterlace_field_order field_order)
491 {
492 	if (!obs_source_valid(source, "obs_source_set_deinterlace_field_order"))
493 		return;
494 
495 	source->deinterlace_top_first = field_order ==
496 					OBS_DEINTERLACE_FIELD_ORDER_TOP;
497 }
498 
499 enum obs_deinterlace_field_order
obs_source_get_deinterlace_field_order(const obs_source_t * source)500 obs_source_get_deinterlace_field_order(const obs_source_t *source)
501 {
502 	if (!obs_source_valid(source, "obs_source_set_deinterlace_field_order"))
503 		return OBS_DEINTERLACE_FIELD_ORDER_TOP;
504 
505 	return source->deinterlace_top_first
506 		       ? OBS_DEINTERLACE_FIELD_ORDER_TOP
507 		       : OBS_DEINTERLACE_FIELD_ORDER_BOTTOM;
508 }
509