{
if (a.stream_idx != b.stream_idx)
return a.stream_idx < b.stream_idx;
- if (a.pts != b.pts)
- return a.pts < b.pts;
- return a.interpolated < b.interpolated;
+ return a.pts < b.pts;
}
};
};
struct PendingDecode {
+ JPEGFrameView *destination;
+
+ // For actual decodes (only if frame below is nullptr).
JPEGID primary, secondary;
float fade_alpha; // Irrelevant if secondary.stream_idx == -1.
- JPEGFrameView *destination;
+
+ // Already-decoded frames are also sent through PendingDecode,
+ // so that they get drawn in the right order. If frame is nullptr,
+ // it's a real decode.
+ shared_ptr<Frame> frame;
};
} // namespace
return nullptr;
}
- assert(!id.interpolated);
*did_decode = true;
shared_ptr<Frame> frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts));
}
}
+ if (decode.frame != nullptr) {
+ // Already decoded, so just show it.
+ decode.destination->setDecodedFrame(decode.frame, nullptr, 1.0f);
+ continue;
+ }
+
shared_ptr<Frame> primary_frame, secondary_frame;
bool drop = false;
for (int subframe_idx = 0; subframe_idx < 2; ++subframe_idx) {
}
bool found_in_cache;
- shared_ptr<Frame> frame;
- if (id.interpolated) {
- // Interpolated frames are never decoded by us,
- // put directly into the cache from VideoStream.
- unique_lock<mutex> lock(cache_mu);
- auto it = cache.find(id);
- if (it != cache.end()) {
- it->second.last_used = event_counter++;
- frame = it->second.frame;
- } else {
- // This can only really happen if it disappeared out of the
- // LRU really, really fast. Which shouldn't happen.
- fprintf(stderr, "WARNING: Interpolated JPEG was supposed to be in the cache, but was not\n");
- }
- found_in_cache = true; // Don't count it as a decode.
- } else {
- frame = decode_jpeg_with_cache(id, cache_miss_behavior, &found_in_cache);
- }
+ shared_ptr<Frame> frame = decode_jpeg_with_cache(id, cache_miss_behavior, &found_in_cache);
if (frame == nullptr) {
- assert(id.interpolated || cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE);
+ assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE);
drop = true;
break;
}
{
}
-void JPEGFrameView::setFrame(unsigned stream_idx, int64_t pts, bool interpolated, int secondary_stream_idx, int64_t secondary_pts, float fade_alpha)
+void JPEGFrameView::setFrame(unsigned stream_idx, int64_t pts, int secondary_stream_idx, int64_t secondary_pts, float fade_alpha)
{
+ if (secondary_stream_idx != -1) assert(secondary_pts != -1);
current_stream_idx = stream_idx; // TODO: Does this interact with fades?
unique_lock<mutex> lock(cache_mu);
PendingDecode decode;
- if (interpolated && secondary_stream_idx != -1) {
- // The frame will already be faded for us, so ask for only one; we shouldn't fade it against anything.
- decode.primary = create_jpegid_for_interpolated_fade(stream_idx, pts, secondary_stream_idx, secondary_pts);
- decode.secondary = JPEGID{ (unsigned)-1, -1, /*interpolated=*/false };
- } else {
- decode.primary = JPEGID{ stream_idx, pts, interpolated };
- decode.secondary = JPEGID{ (unsigned)secondary_stream_idx, secondary_pts, /*interpolated=*/false };
- }
+ decode.primary = JPEGID{ stream_idx, pts };
+ decode.secondary = JPEGID{ (unsigned)secondary_stream_idx, secondary_pts };
decode.fade_alpha = fade_alpha;
decode.destination = this;
pending_decodes.push_back(decode);
any_pending_decodes.notify_all();
}
-void JPEGFrameView::insert_interpolated_frame(JPEGID id, shared_ptr<Frame> frame)
+void JPEGFrameView::setFrame(shared_ptr<Frame> frame)
{
- // We rely on the frame not being evicted from the cache before
- // jpeg_decoder_thread() sees it and can display it (otherwise,
- // that thread would hang). With a default cache of 1000 elements,
- // that would sound like a reasonable assumption.
unique_lock<mutex> lock(cache_mu);
- cache_bytes_used += frame_size(*frame);
- cache[id] = LRUFrame{ std::move(frame), event_counter++ };
- if (cache_bytes_used > size_t(CACHE_SIZE_MB) * 1024 * 1024) {
- prune_cache();
- }
+ PendingDecode decode;
+ decode.frame = std::move(frame);
+ decode.destination = this;
+ pending_decodes.push_back(decode);
+ any_pending_decodes.notify_all();
}
ResourcePool *resource_pool = nullptr;
struct JPEGID {
unsigned stream_idx;
int64_t pts;
- bool interpolated;
};
enum CacheMissBehavior {
DECODE_IF_NOT_IN_CACHE,
RETURN_NULLPTR_IF_NOT_IN_CACHE
};
-// This is, well, a hack. We hope for no collisions.
-inline JPEGID create_jpegid_for_interpolated_fade(unsigned stream_idx, int64_t pts, unsigned secondary_stream_idx, int64_t secondary_pts)
-{
- JPEGID id;
- id.stream_idx = (stream_idx << 8) | secondary_stream_idx;
-
- uint64_t rot = secondary_stream_idx;
- rot = (rot << 32) | (rot >> 32);
- id.pts = pts ^ int64_t(rot);
- id.interpolated = true;
- return id;
-}
-
std::string filename_for_frame(unsigned stream_idx, int64_t pts);
std::shared_ptr<Frame> decode_jpeg(const std::string &filename);
std::shared_ptr<Frame> decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss_behavior, bool *did_decode);
public:
JPEGFrameView(QWidget *parent);
- void setFrame(unsigned stream_idx, int64_t pts, bool interpolated, int secondary_stream_idx = -1, int64_t secondary_pts = -1, float fade_alpha = 0.0f);
- static void insert_interpolated_frame(JPEGID id, std::shared_ptr<Frame> frame);
+ void setFrame(unsigned stream_idx, int64_t pts, int secondary_stream_idx = -1, int64_t secondary_pts = -1, float fade_alpha = 0.0f);
+ void setFrame(std::shared_ptr<Frame> frame);
void mousePressEvent(QMouseEvent *event) override;
post_to_main_thread([pkt, pts] {
if (pkt.stream_index == 0) {
- global_mainwindow->ui->input1_display->setFrame(pkt.stream_index, pts, /*interpolated=*/false);
+ global_mainwindow->ui->input1_display->setFrame(pkt.stream_index, pts);
} else if (pkt.stream_index == 1) {
- global_mainwindow->ui->input2_display->setFrame(pkt.stream_index, pts, /*interpolated=*/false);
+ global_mainwindow->ui->input2_display->setFrame(pkt.stream_index, pts);
} else if (pkt.stream_index == 2) {
- global_mainwindow->ui->input3_display->setFrame(pkt.stream_index, pts, /*interpolated=*/false);
+ global_mainwindow->ui->input3_display->setFrame(pkt.stream_index, pts);
} else if (pkt.stream_index == 3) {
- global_mainwindow->ui->input4_display->setFrame(pkt.stream_index, pts, /*interpolated=*/false);
+ global_mainwindow->ui->input4_display->setFrame(pkt.stream_index, pts);
}
});
if (in_pts_lower == in_pts_upper) {
auto display_func = [this, primary_stream_idx, in_pts_lower, secondary_stream_idx, secondary_pts, fade_alpha]{
- destination->setFrame(primary_stream_idx, in_pts_lower, /*interpolated=*/false, secondary_stream_idx, secondary_pts, fade_alpha);
+ destination->setFrame(primary_stream_idx, in_pts_lower, secondary_stream_idx, secondary_pts, fade_alpha);
};
if (video_stream == nullptr) {
display_func();
next_frame_start, pts, display_func, QueueSpotHolder(this),
primary_stream_idx, in_pts_lower);
} else {
+ assert(secondary_pts != -1);
video_stream->schedule_faded_frame(next_frame_start, pts, display_func,
QueueSpotHolder(this), primary_stream_idx, in_pts_lower,
secondary_stream_idx, secondary_pts, fade_alpha);
double snap_pts_as_frameno = (snap_pts - in_pts_origin) * output_framerate / TIMEBASE / speed;
if (fabs(snap_pts_as_frameno - frameno) < 0.01) {
auto display_func = [this, primary_stream_idx, snap_pts, secondary_stream_idx, secondary_pts, fade_alpha]{
- destination->setFrame(primary_stream_idx, snap_pts, /*interpolated=*/false, secondary_stream_idx, secondary_pts, fade_alpha);
+ destination->setFrame(primary_stream_idx, snap_pts, secondary_stream_idx, secondary_pts, fade_alpha);
};
if (video_stream == nullptr) {
display_func();
next_frame_start, pts, display_func,
QueueSpotHolder(this), primary_stream_idx, snap_pts);
} else {
+ assert(secondary_pts != -1);
video_stream->schedule_faded_frame(
next_frame_start, pts, display_func, QueueSpotHolder(this),
primary_stream_idx, snap_pts, secondary_stream_idx, secondary_pts, fade_alpha);
if (video_stream == nullptr) {
// Previews don't do any interpolation.
assert(secondary_stream_idx == -1);
- destination->setFrame(primary_stream_idx, in_pts_lower, /*interpolated=*/false);
+ destination->setFrame(primary_stream_idx, in_pts_lower);
} else {
- auto display_func = [this, primary_stream_idx, pts, secondary_stream_idx, secondary_pts, fade_alpha]{
- destination->setFrame(primary_stream_idx, pts, /*interpolated=*/true, secondary_stream_idx, secondary_pts, fade_alpha);
+ auto display_func = [this](shared_ptr<Frame> frame) {
+ destination->setFrame(frame);
};
video_stream->schedule_interpolated_frame(
next_frame_start, pts, display_func, QueueSpotHolder(this),
if (it == frames[stream_idx].end()) {
return;
}
- destination->setFrame(stream_idx, *it, /*interpolated=*/false);
+ destination->setFrame(stream_idx, *it);
}
void Player::take_queue_spot()
JPEGID jpeg_id1;
jpeg_id1.stream_idx = stream_idx;
jpeg_id1.pts = input_pts;
- jpeg_id1.interpolated = false;
shared_ptr<Frame> frame1 = decode_jpeg_with_cache(jpeg_id1, DECODE_IF_NOT_IN_CACHE, &did_decode);
JPEGID jpeg_id2;
jpeg_id2.stream_idx = secondary_stream_idx;
jpeg_id2.pts = secondary_input_pts;
- jpeg_id2.interpolated = false;
shared_ptr<Frame> frame2 = decode_jpeg_with_cache(jpeg_id2, DECODE_IF_NOT_IN_CACHE, &did_decode);
ycbcr_semiplanar_converter->prepare_chain_for_fade(frame1, frame2, fade_alpha)->render_to_fbo(resources->fade_fbo, 1280, 720);
}
void VideoStream::schedule_interpolated_frame(steady_clock::time_point local_pts,
- int64_t output_pts, function<void()> &&display_func,
+ int64_t output_pts, function<void(shared_ptr<Frame>)> &&display_func,
QueueSpotHolder &&queue_spot_holder,
unsigned stream_idx, int64_t input_first_pts,
int64_t input_second_pts, float alpha,
fprintf(stderr, "output_pts=%ld interpolated input_pts1=%ld input_pts2=%ld alpha=%.3f\n", output_pts, input_first_pts, input_second_pts, alpha);
}
- JPEGID id;
- if (secondary_stream_idx == -1) {
- id = JPEGID{ stream_idx, output_pts, /*interpolated=*/true };
- } else {
- id = create_jpegid_for_interpolated_fade(stream_idx, output_pts, secondary_stream_idx, secondary_input_pts);
- }
-
// Get the temporary OpenGL resources we need for doing the interpolation.
BorrowedInterpolatedFrameResources resources;
{
qf.type = (secondary_stream_idx == -1) ? QueuedFrame::INTERPOLATED : QueuedFrame::FADED_INTERPOLATED;
qf.output_pts = output_pts;
qf.stream_idx = stream_idx;
- qf.id = id;
- qf.display_func = move(display_func);
+ qf.display_decoded_func = move(display_func);
qf.queue_spot_holder = move(queue_spot_holder);
qf.local_pts = local_pts;
JPEGID jpeg_id;
jpeg_id.stream_idx = stream_idx;
jpeg_id.pts = frame_no == 1 ? input_second_pts : input_first_pts;
- jpeg_id.interpolated = false;
bool did_decode;
shared_ptr<Frame> frame = decode_jpeg_with_cache(jpeg_id, DECODE_IF_NOT_IN_CACHE, &did_decode);
ycbcr_converter->prepare_chain_for_conversion(frame)->render_to_fbo(resources->input_fbos[frame_no], 1280, 720);
JPEGID jpeg_id;
jpeg_id.stream_idx = secondary_stream_idx;
jpeg_id.pts = secondary_input_pts;
- jpeg_id.interpolated = false;
bool did_decode;
shared_ptr<Frame> frame2 = decode_jpeg_with_cache(jpeg_id, DECODE_IF_NOT_IN_CACHE, &did_decode);
} else if (qf.type == QueuedFrame::INTERPOLATED || qf.type == QueuedFrame::FADED_INTERPOLATED) {
glClientWaitSync(qf.fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
- // Send a copy of the frame on to display.
+ // Send it on to display.
shared_ptr<Frame> frame = frame_from_pbo(qf.resources->pbo_contents, 1280, 720);
- JPEGFrameView::insert_interpolated_frame(qf.id, frame);
+ if (qf.display_decoded_func != nullptr) {
+ qf.display_decoded_func(frame);
+ }
// Now JPEG encode it, and send it on to the stream.
vector<uint8_t> jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), 1280, 720);
unsigned stream_idx, int64_t input_pts, int secondary_stream_idx,
int64_t secondary_input_pts, float fade_alpha);
void schedule_interpolated_frame(std::chrono::steady_clock::time_point, int64_t output_pts,
- std::function<void()> &&display_func,
+ std::function<void(std::shared_ptr<Frame>)> &&display_func,
QueueSpotHolder &&queue_spot_holder,
unsigned stream_idx, int64_t input_first_pts, int64_t input_second_pts,
float alpha, int secondary_stream_idx = -1, int64_t secondary_inputs_pts = -1,
JPEGID id;
std::function<void()> display_func; // Called when the image is done decoding.
+ std::function<void(std::shared_ptr<Frame>)> display_decoded_func; // Same, except for INTERPOLATED and FADED_INTERPOLATED.
QueueSpotHolder queue_spot_holder;
};