X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=jpeg_frame_view.cpp;h=73030ffa2ce02a2b53f5ae23eef3f4b77eb66d22;hb=1aab0848ee4807a0369656edd9eb42ce5adb56ae;hp=96243529b2b8184d0717ac382f606a7d92c76f46;hpb=dd7575b55cf9b50b535cd8ba5ae973ff0c12e510;p=nageru diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index 9624352..73030ff 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -10,25 +11,32 @@ #include #include +#include +#include + #include #include #include #include "defs.h" #include "post_to_main_thread.h" +#include "video_stream.h" using namespace movit; using namespace std; -string filename_for_frame(unsigned stream_idx, int64_t pts); - -struct JPEGID { - unsigned stream_idx; - int64_t 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; + } }; -bool operator< (const JPEGID &a, const JPEGID &b) { - return make_pair(a.stream_idx, a.pts) < make_pair(b.stream_idx, b.pts); -} struct LRUFrame { shared_ptr frame; @@ -36,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; @@ -147,6 +155,35 @@ void prune_cache() } } +shared_ptr decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss_behavior, bool *did_decode) +{ + *did_decode = false; + { + unique_lock lock(cache_mu); + auto it = cache.find(id); + if (it != cache.end()) { + it->second.last_used = event_counter++; + return it->second.frame; + } + } + + if (cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE) { + return nullptr; + } + + assert(!id.interpolated); + *did_decode = true; + shared_ptr frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts)); + + unique_lock lock(cache_mu); + cache[id] = LRUFrame{ frame, event_counter++ }; + + if (cache.size() > CACHE_SIZE) { + prune_cache(); + } + return frame; +} + void jpeg_decoder_thread() { size_t num_decoded = 0, num_dropped = 0; @@ -155,9 +192,9 @@ void jpeg_decoder_thread() for ( ;; ) { JPEGID id; JPEGFrameView *dest; - shared_ptr frame; + CacheMissBehavior cache_miss_behavior = DECODE_IF_NOT_IN_CACHE; { - unique_lock lock(cache_mu); + unique_lock lock(cache_mu); // TODO: Perhaps under another lock? any_pending_decodes.wait(lock, [] { return !pending_decodes.empty(); }); @@ -165,17 +202,6 @@ void jpeg_decoder_thread() dest = pending_decodes.front().second; pending_decodes.pop_front(); - auto it = cache.find(id); - if (it != cache.end()) { - frame = it->second.frame; - it->second.last_used = event_counter++; - } - } - - if (frame == nullptr) { - // Not found in the cache, so we need to do a decode or drop the request. - // Prune the queue if there are too many pending for this destination. - // TODO: Could we get starvation here? size_t num_pending = 0; for (const pair &decode : pending_decodes) { if (decode.second == dest) { @@ -183,18 +209,43 @@ void jpeg_decoder_thread() } } if (num_pending > 3) { - ++num_dropped; - continue; + cache_miss_behavior = RETURN_NULLPTR_IF_NOT_IN_CACHE; } + } - frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts)); - + bool 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[id] = LRUFrame{ frame, event_counter++ }; + cache_updated.wait(lock, [id] { + return cache.count(id) != 0; + }); + found_in_cache = true; // Don't count it as a decode. - if (cache.size() > CACHE_SIZE) { - prune_cache(); + 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(id.interpolated || cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE); + ++num_dropped; + continue; + } + + if (!found_in_cache) { ++num_decoded; if (num_decoded % 1000 == 0) { fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n", @@ -202,6 +253,7 @@ void jpeg_decoder_thread() } } + // TODO: Could we get jitter between non-interpolated and interpolated frames here? dest->setDecodedFrame(frame); } } @@ -210,27 +262,39 @@ 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() { glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); check_error(); static once_flag once; call_once(once, [] { - CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF)); resource_pool = new ResourcePool; - std::thread(&jpeg_decoder_thread).detach(); }); @@ -260,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) @@ -267,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); @@ -279,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) @@ -299,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; +}