Browse Source

libobs: Improve timing of unbuffered deinterlacing

There are devices like the GV-USB2 that produce frames with smmoth
timestamps at an uneven pace, which causes OBS to stutter because the
unbuffered path is designed to aggressively operate on the latest frame.

We can make the unbuffered path work by making two adjustments:

- Don't discard the current frame until it has elapsed.
- Don't skip frames in the queue until they have elapsed.

The buffered path still has problems with deinterlacing GV-USB2 output,
but the unbuffered path is better anyway.

Testing:

GV-USB2, Unbuffered: Stuttering is gone!
GV-USB2, Buffered: No regression (still broken).
SC-512N1-L/DVI, Unbuffered: No regression (still works).
SC-512N1-L/DVI, Buffered: No regression (still works).
jpark37 6 years ago
parent
commit
3ea98b8b0d
1 changed files with 40 additions and 4 deletions
  1. 40 4
      libobs/obs-source-deinterlace.c

+ 40 - 4
libobs/obs-source-deinterlace.c

@@ -34,8 +34,28 @@ static bool ready_deinterlace_frames(obs_source_t *source, uint64_t sys_time)
 			next_frame = source->async_frames.array[0];
 			next_frame = source->async_frames.array[0];
 		}
 		}
 
 
-		if (source->async_frames.num == 2)
-			source->async_frames.array[0]->prev_frame = true;
+		if (source->async_frames.num == 2) {
+			bool prev_frame = true;
+			if (source->async_unbuffered &&
+			    source->deinterlace_offset) {
+				const uint64_t timestamp =
+					source->async_frames.array[0]->timestamp;
+				const uint64_t after_timestamp =
+					source->async_frames.array[1]->timestamp;
+				const uint64_t duration =
+					after_timestamp - timestamp;
+				const uint64_t frame_end =
+					timestamp + source->deinterlace_offset +
+					duration;
+				if (sys_time < frame_end) {
+					// Don't skip ahead prematurely.
+					prev_frame = false;
+					source->deinterlace_frame_ts =
+						timestamp - duration;
+				}
+			}
+			source->async_frames.array[0]->prev_frame = prev_frame;
+		}
 		source->deinterlace_offset = 0;
 		source->deinterlace_offset = 0;
 		source->last_frame_ts = next_frame->timestamp;
 		source->last_frame_ts = next_frame->timestamp;
 		return true;
 		return true;
@@ -122,12 +142,30 @@ static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2)
 	return (ts1 < ts2) ? (ts2 - ts1) : (ts1 - ts2);
 	return (ts1 < ts2) ? (ts2 - ts1) : (ts1 - ts2);
 }
 }
 
 
+#define TWOX_TOLERANCE 1000000
+#define TS_JUMP_THRESHOLD 70000000ULL
+
 static inline void deinterlace_get_closest_frames(obs_source_t *s,
 static inline void deinterlace_get_closest_frames(obs_source_t *s,
 						  uint64_t sys_time)
 						  uint64_t sys_time)
 {
 {
 	const struct video_output_info *info;
 	const struct video_output_info *info;
 	uint64_t half_interval;
 	uint64_t half_interval;
 
 
+	if (s->async_unbuffered && s->deinterlace_offset) {
+		// Want to keep frame if it has not elapsed.
+		const uint64_t frame_end =
+			s->deinterlace_frame_ts + s->deinterlace_offset +
+			((uint64_t)s->deinterlace_half_duration * 2) -
+			TWOX_TOLERANCE;
+		if (sys_time < frame_end) {
+			// Process new frames if we think time jumped.
+			const uint64_t diff = frame_end - sys_time;
+			if (diff < TS_JUMP_THRESHOLD) {
+				return;
+			}
+		}
+	}
+
 	if (!s->async_frames.num)
 	if (!s->async_frames.num)
 		return;
 		return;
 
 
@@ -303,8 +341,6 @@ static inline gs_effect_t *get_effect(enum obs_deinterlace_mode mode)
 	return NULL;
 	return NULL;
 }
 }
 
 
-#define TWOX_TOLERANCE 1000000
-
 void deinterlace_render(obs_source_t *s)
 void deinterlace_render(obs_source_t *s)
 {
 {
 	gs_effect_t *effect = s->deinterlace_effect;
 	gs_effect_t *effect = s->deinterlace_effect;