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