]> git.sesse.net Git - nageru/blobdiff - jpeg_frame_view.cpp
Allow symlinked frame files. Useful for testing.
[nageru] / jpeg_frame_view.cpp
index b33b76b945a5ca25b8228e17124584ed9f3bc242..3d3383fe887ea17c6fcab08abcd13752181a0bda 100644 (file)
@@ -1,6 +1,7 @@
 #include "jpeg_frame_view.h"
 
 #include "defs.h"
+#include "jpeg_destroyer.h"
 #include "post_to_main_thread.h"
 #include "video_stream.h"
 #include "ycbcr_converter.h"
 using namespace movit;
 using namespace std;
 
+namespace {
+
 // Just an arbitrary order for std::map.
-struct JPEGIDLexicalOrder
+struct FrameOnDiskLexicalOrder
 {
-       bool operator() (const JPEGID &a, const JPEGID &b) const
+       bool operator() (const FrameOnDisk &a, const FrameOnDisk &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;
+               if (a.offset != b.offset)
+                       return a.offset < b.offset;
+               if (a.filename_idx != b.filename_idx)
+                       return a.filename_idx < b.filename_idx;
+               assert(a.size == b.size);
+               return false;
        }
 };
 
+inline size_t frame_size(const Frame &frame)
+{
+       size_t y_size = frame.width * frame.height;
+       size_t cbcr_size = y_size / frame.chroma_subsampling_x / frame.chroma_subsampling_y;
+       return y_size + cbcr_size * 2;
+}
+
 struct LRUFrame {
        shared_ptr<Frame> frame;
        size_t last_used;
 };
 
 struct PendingDecode {
-       JPEGID primary, secondary;
-       float fade_alpha;  // Irrelevant if secondary.stream_idx == -1.
        JPEGFrameView *destination;
+
+       // For actual decodes (only if frame below is nullptr).
+       FrameOnDisk primary, secondary;
+       float fade_alpha;  // Irrelevant if secondary.stream_idx == -1.
+
+       // 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
+
 thread JPEGFrameView::jpeg_decoder_thread;
 mutex cache_mu;
-map<JPEGID, LRUFrame, JPEGIDLexicalOrder> cache;  // Under cache_mu.
-condition_variable any_pending_decodes, cache_updated;
+map<FrameOnDisk, LRUFrame, FrameOnDiskLexicalOrder> cache;  // Under cache_mu.
+size_t cache_bytes_used = 0;  // Under cache_mu.
+condition_variable any_pending_decodes;
 deque<PendingDecode> pending_decodes;  // Under cache_mu.
 atomic<size_t> event_counter{0};
 extern QGLWidget *global_share_widget;
@@ -76,6 +99,7 @@ shared_ptr<Frame> decode_jpeg(const string &filename)
        jpeg_error_mgr jerr;
        dinfo.err = jpeg_std_error(&jerr);
        jpeg_create_decompress(&dinfo);
+       JPEGDestroyer destroy_dinfo(&dinfo);
 
        FILE *fp = fopen(filename.c_str(), "rb");
        if (fp == nullptr) {
@@ -146,7 +170,6 @@ shared_ptr<Frame> decode_jpeg(const string &filename)
        }
 
        (void)jpeg_finish_decompress(&dinfo);
-       jpeg_destroy_decompress(&dinfo);
        fclose(fp);
 
        return frame;
@@ -155,16 +178,28 @@ shared_ptr<Frame> decode_jpeg(const string &filename)
 void prune_cache()
 {
        // Assumes cache_mu is held.
-       vector<size_t> lru_timestamps;
+       int64_t bytes_still_to_remove = cache_bytes_used - (size_t(CACHE_SIZE_MB) * 1024 * 1024) * 9 / 10;
+       if (bytes_still_to_remove <= 0) return;
+
+       vector<pair<size_t, size_t>> lru_timestamps_and_size;
        for (const auto &key_and_value : cache) {
-               lru_timestamps.push_back(key_and_value.second.last_used);
+               lru_timestamps_and_size.emplace_back(
+                       key_and_value.second.last_used,
+                       frame_size(*key_and_value.second.frame));
+       }
+       sort(lru_timestamps_and_size.begin(), lru_timestamps_and_size.end());
+
+       // Remove the oldest ones until we are below 90% of the cache used.
+       size_t lru_cutoff_point = 0;
+       for (const pair<size_t, size_t> &it : lru_timestamps_and_size) {
+               lru_cutoff_point = it.first;
+               bytes_still_to_remove -= it.second;
+               if (bytes_still_to_remove <= 0) break;
        }
 
-       size_t cutoff_point = CACHE_SIZE / 10;  // Prune away the 10% oldest ones.
-       nth_element(lru_timestamps.begin(), lru_timestamps.begin() + cutoff_point, lru_timestamps.end());
-       size_t must_be_used_after = lru_timestamps[cutoff_point];
        for (auto it = cache.begin(); it != cache.end(); ) {
-               if (it->second.last_used < must_be_used_after) {
+               if (it->second.last_used <= lru_cutoff_point) {
+                       cache_bytes_used -= frame_size(*it->second.frame);
                        it = cache.erase(it);
                } else {
                        ++it;
@@ -172,12 +207,12 @@ void prune_cache()
        }
 }
 
-shared_ptr<Frame> decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss_behavior, bool *did_decode)
+shared_ptr<Frame> decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, FrameReader *frame_reader, bool *did_decode)
 {
        *did_decode = false;
        {
                unique_lock<mutex> lock(cache_mu);
-               auto it = cache.find(id);
+               auto it = cache.find(frame_spec);
                if (it != cache.end()) {
                        it->second.last_used = event_counter++;
                        return it->second.frame;
@@ -188,20 +223,20 @@ shared_ptr<Frame> decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss
                return nullptr;
        }
 
-       assert(!id.interpolated);
        *did_decode = true;
-       shared_ptr<Frame> frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts));
+       shared_ptr<Frame> frame = decode_jpeg(frame_reader->read_frame(frame_spec));
 
        unique_lock<mutex> lock(cache_mu);
-       cache[id] = LRUFrame{ frame, event_counter++ };
+       cache_bytes_used += frame_size(*frame);
+       cache[frame_spec] = LRUFrame{ frame, event_counter++ };
 
-       if (cache.size() > CACHE_SIZE) {
+       if (cache_bytes_used > size_t(CACHE_SIZE_MB) * 1024 * 1024) {
                prune_cache();
        }
        return frame;
 }
 
-void jpeg_decoder_thread_func()
+void JPEGFrameView::jpeg_decoder_thread_func()
 {
        size_t num_decoded = 0, num_dropped = 0;
 
@@ -230,45 +265,26 @@ void jpeg_decoder_thread_func()
                        }
                }
 
+               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) {
-                       const JPEGID &id = (subframe_idx == 0 ? decode.primary : decode.secondary);
-                       if (id.stream_idx == (unsigned)-1) {
+                       const FrameOnDisk &frame_spec = (subframe_idx == 0 ? decode.primary : decode.secondary);
+                       if (frame_spec.pts == -1) {
                                // No secondary frame.
                                continue;
                        }
 
                        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);
-                               cache_updated.wait(lock, [id] {
-                                       return cache.count(id) != 0 || should_quit.load();
-                               });
-                               if (should_quit.load())
-                                       break;
-                               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);
-                       }
+                       shared_ptr<Frame> frame = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &decode.destination->frame_reader, &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;
                        }
@@ -281,9 +297,9 @@ void jpeg_decoder_thread_func()
                                }
                        }
                        if (subframe_idx == 0) {
-                               primary_frame = move(frame);
+                               primary_frame = std::move(frame);
                        } else {
-                               secondary_frame = move(frame);
+                               secondary_frame = std::move(frame);
                        }
                }
                if (drop) {
@@ -307,35 +323,28 @@ JPEGFrameView::JPEGFrameView(QWidget *parent)
 {
 }
 
-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, FrameOnDisk frame, FrameOnDisk secondary_frame, float fade_alpha)
 {
        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 = frame;
+       decode.secondary = secondary_frame;
        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[id] = LRUFrame{ std::move(frame), event_counter++ };
-       cache_updated.notify_all();
+       PendingDecode decode;
+       decode.frame = std::move(frame);
+       decode.destination = this;
+       pending_decodes.push_back(decode);
+       any_pending_decodes.notify_all();
 }
 
 ResourcePool *resource_pool = nullptr;