+ if (frame_lower.pts == frame_upper.pts) {
+ auto display_func = [this, primary_stream_idx, frame_lower, secondary_frame, fade_alpha]{
+ destination->setFrame(primary_stream_idx, frame_lower, secondary_frame, fade_alpha);
+ };
+ if (video_stream == nullptr) {
+ display_func();
+ } else {
+ if (secondary_stream_idx == -1) {
+ video_stream->schedule_original_frame(
+ next_frame_start, pts, display_func, QueueSpotHolder(this),
+ frame_lower);
+ } else {
+ assert(secondary_frame.pts != -1);
+ video_stream->schedule_faded_frame(next_frame_start, pts, display_func,
+ QueueSpotHolder(this), frame_lower,
+ secondary_frame, fade_alpha);
+ }
+ }
+ continue;
+ }
+
+ // Snap to input frame: If we can do so with less than 1% jitter
+ // (ie., move less than 1% of an _output_ frame), do so.
+ // TODO: Snap secondary (fade-to) clips in the same fashion.
+ bool snapped = false;
+ for (int64_t snap_pts : { frame_lower.pts, frame_upper.pts }) {
+ double snap_pts_as_frameno = (snap_pts - in_pts_origin) * output_framerate / TIMEBASE / speed;
+ if (fabs(snap_pts_as_frameno - frameno) < 0.01) {
+ FrameOnDisk snap_frame = frame_lower;
+ snap_frame.pts = snap_pts;
+ auto display_func = [this, primary_stream_idx, snap_frame, secondary_frame, fade_alpha]{
+ destination->setFrame(primary_stream_idx, snap_frame, secondary_frame, fade_alpha);
+ };
+ if (video_stream == nullptr) {
+ display_func();
+ } else {
+ if (secondary_stream_idx == -1) {
+ video_stream->schedule_original_frame(
+ next_frame_start, pts, display_func,
+ QueueSpotHolder(this), snap_frame);
+ } else {
+ assert(secondary_frame.pts != -1);
+ video_stream->schedule_faded_frame(
+ next_frame_start, pts, display_func, QueueSpotHolder(this),
+ snap_frame, secondary_frame, fade_alpha);
+ }
+ }
+ in_pts_origin += snap_pts - in_pts;
+ snapped = true;
+ break;
+ }
+ }
+ if (snapped) {
+ continue;
+ }
+
+ if (time_behind >= milliseconds(100)) {
+ fprintf(stderr, "WARNING: %ld ms behind, dropping an interpolated frame.\n",
+ lrint(1e3 * duration<double>(time_behind).count()));
+ continue;
+ }
+
+ double alpha = double(in_pts - frame_lower.pts) / (frame_upper.pts - frame_lower.pts);
+
+ if (video_stream == nullptr) {
+ // Previews don't do any interpolation.
+ assert(secondary_stream_idx == -1);
+ destination->setFrame(primary_stream_idx, frame_lower);
+ } else {
+ auto display_func = [this](shared_ptr<Frame> frame) {
+ destination->setFrame(frame);
+ };
+ video_stream->schedule_interpolated_frame(
+ next_frame_start, pts, display_func, QueueSpotHolder(this),
+ frame_lower, frame_upper, alpha,
+ secondary_frame, fade_alpha);
+ }
+ }
+
+ // The clip ended.
+
+ // Last-ditch effort to get the next clip (if e.g. the fade time was zero seconds).
+ if (!got_next_clip && next_clip_callback != nullptr) {
+ tie(next_clip, next_clip_idx) = next_clip_callback();
+ if (next_clip.pts_in != -1) {
+ got_next_clip = true;
+ in_pts_start_next_clip = next_clip.pts_in;
+ }
+ }
+
+ // Switch to next clip if we got it.
+ if (got_next_clip) {
+ clip = next_clip;
+ clip_idx = next_clip_idx;
+ stream_idx = next_clip.stream_idx; // Override is used for previews only, and next_clip is used for live ony.
+ if (done_callback != nullptr) {
+ done_callback();
+ }
+ got_next_clip = false;