X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=jpeg_frame_view.cpp;h=73030ffa2ce02a2b53f5ae23eef3f4b77eb66d22;hb=75564b80c6283970c355e43d5e06493a99e32489;hp=5ef2f0b8ab3a66fb661ebb384eb2717420c5423c;hpb=a004d0069fc646197554fa470e65e51f111895a1;p=nageru diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index 5ef2f0b..73030ff 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,6 +11,9 @@ #include #include +#include +#include + #include #include #include @@ -21,9 +25,18 @@ using namespace movit; using namespace std; -bool operator< (const JPEGID &a, const JPEGID &b) { - return make_pair(a.stream_idx, a.pts) < make_pair(b.stream_idx, b.pts); -} +// Just an arbitrary order for std::map. +struct JPEGIDLexicalOrder +{ + bool operator() (const JPEGID &a, const JPEGID &b) const + { + 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; + } +}; struct LRUFrame { shared_ptr frame; @@ -31,8 +44,8 @@ struct LRUFrame { }; mutex cache_mu; -map cache; // Under cache_mu. -condition_variable any_pending_decodes; +map cache; // Under cache_mu. +condition_variable any_pending_decodes, cache_updated; deque> pending_decodes; // Under cache_mu. atomic event_counter{0}; extern QGLWidget *global_share_widget; @@ -158,6 +171,7 @@ shared_ptr decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss return nullptr; } + assert(!id.interpolated); *did_decode = true; shared_ptr frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts)); @@ -200,10 +214,33 @@ void jpeg_decoder_thread() } bool found_in_cache; - shared_ptr frame = decode_jpeg_with_cache(id, cache_miss_behavior, &found_in_cache); + shared_ptr frame; + if (id.interpolated) { + // Interpolated frames are never decoded by us, + // put directly into the cache from VideoStream. + unique_lock lock(cache_mu); + cache_updated.wait(lock, [id] { + return cache.count(id) != 0; + }); + found_in_cache = true; // Don't count it as a decode. + + auto it = cache.find(id); + assert(it != cache.end()); + + it->second.last_used = event_counter++; + frame = it->second.frame; + if (frame == nullptr) { + // We inserted a nullptr as signal that the frame was never + // interpolated and that we should stop waiting. + // But don't let it linger in the cache anymore. + cache.erase(it); + } + } else { + frame = decode_jpeg_with_cache(id, cache_miss_behavior, &found_in_cache); + } if (frame == nullptr) { - assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE); + assert(id.interpolated || cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE); ++num_dropped; continue; } @@ -216,6 +253,7 @@ void jpeg_decoder_thread() } } + // TODO: Could we get jitter between non-interpolated and interpolated frames here? dest->setDecodedFrame(frame); } } @@ -224,13 +262,28 @@ JPEGFrameView::JPEGFrameView(QWidget *parent) : QGLWidget(parent, global_share_widget) { } -void JPEGFrameView::update_frame() +void JPEGFrameView::setFrame(unsigned stream_idx, int64_t pts, bool interpolated) { + current_stream_idx = stream_idx; + unique_lock lock(cache_mu); - pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this); + pending_decodes.emplace_back(JPEGID{ stream_idx, pts, interpolated }, this); any_pending_decodes.notify_all(); } +void JPEGFrameView::insert_interpolated_frame(unsigned stream_idx, int64_t pts, shared_ptr frame) +{ + JPEGID id{ stream_idx, pts, true }; + + // 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 lock(cache_mu); + cache[id] = LRUFrame{ std::move(frame), event_counter++ }; + cache_updated.notify_all(); +} + ResourcePool *resource_pool = nullptr; void JPEGFrameView::initializeGL() @@ -271,6 +324,12 @@ void JPEGFrameView::initializeGL() check_error(); chain->finalize(); check_error(); + + overlay_chain.reset(new EffectChain(overlay_base_width, overlay_base_height, resource_pool)); + overlay_input = (movit::FlatInput *)overlay_chain->add_input(new FlatInput(image_format, FORMAT_GRAYSCALE, GL_UNSIGNED_BYTE, overlay_base_width, overlay_base_height)); + + overlay_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + overlay_chain->finalize(); } void JPEGFrameView::resizeGL(int width, int height) @@ -278,10 +337,15 @@ void JPEGFrameView::resizeGL(int width, int height) check_error(); glViewport(0, 0, width, height); check_error(); + + // Save these, as width() and height() will lie with DPI scaling. + gl_width = width; + gl_height = height; } void JPEGFrameView::paintGL() { + glViewport(0, 0, gl_width, gl_height); if (current_frame == nullptr) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -290,6 +354,16 @@ void JPEGFrameView::paintGL() check_error(); chain->render_to_screen(); + + if (overlay_image != nullptr) { + if (overlay_input_needs_refresh) { + overlay_input->set_width(overlay_width); + overlay_input->set_height(overlay_height); + overlay_input->set_pixel_data(overlay_image->bits()); + } + glViewport(gl_width - overlay_width, 0, overlay_width, overlay_height); + overlay_chain->render_to_screen(); + } } void JPEGFrameView::setDecodedFrame(std::shared_ptr frame) @@ -310,3 +384,37 @@ void JPEGFrameView::setDecodedFrame(std::shared_ptr frame) update(); }); } + +void JPEGFrameView::mousePressEvent(QMouseEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) { + emit clicked(); + } +} + +void JPEGFrameView::set_overlay(const string &text) +{ + if (text.empty()) { + overlay_image.reset(); + return; + } + + float dpr = QGuiApplication::primaryScreen()->devicePixelRatio(); + overlay_width = lrint(overlay_base_width * dpr); + overlay_height = lrint(overlay_base_height * dpr); + + overlay_image.reset(new QImage(overlay_width, overlay_height, QImage::Format_Grayscale8)); + overlay_image->setDevicePixelRatio(dpr); + overlay_image->fill(0); + QPainter painter(overlay_image.get()); + + painter.setPen(Qt::white); + QFont font = painter.font(); + font.setPointSize(12); + painter.setFont(font); + + painter.drawText(QRectF(0, 0, overlay_base_width, overlay_base_height), Qt::AlignCenter, QString::fromStdString(text)); + + // Don't refresh immediately; we might not have an OpenGL context here. + overlay_input_needs_refresh = true; +}