X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=jpeg_frame_view.cpp;h=be53688a03ee33bca3bb50688030978eb411ade2;hb=bdef311c334b674ba39a931805fb7d32ce8698da;hp=7870f1c090dd2cb2cfec6031c420689d2316c1be;hpb=cd1ce41a608914be60f8703fe4ddac9edf1e4d80;p=nageru
diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp
index 7870f1c..be53688 100644
--- a/jpeg_frame_view.cpp
+++ b/jpeg_frame_view.cpp
@@ -27,34 +27,56 @@
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;
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;
};
+} // namespace
+
thread JPEGFrameView::jpeg_decoder_thread;
mutex cache_mu;
-map cache; // Under cache_mu.
-condition_variable any_pending_decodes, cache_updated;
+map cache; // Under cache_mu.
+size_t cache_bytes_used = 0; // Under cache_mu.
+condition_variable any_pending_decodes;
deque pending_decodes; // Under cache_mu.
atomic event_counter{0};
extern QGLWidget *global_share_widget;
@@ -156,16 +178,28 @@ shared_ptr decode_jpeg(const string &filename)
void prune_cache()
{
// Assumes cache_mu is held.
- vector 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> 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 &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;
@@ -173,12 +207,12 @@ void prune_cache()
}
}
-shared_ptr decode_jpeg_with_cache(JPEGID id, CacheMissBehavior cache_miss_behavior, bool *did_decode)
+shared_ptr decode_jpeg_with_cache(FrameOnDisk frame_spec, CacheMissBehavior cache_miss_behavior, bool *did_decode)
{
*did_decode = false;
{
unique_lock 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;
@@ -189,14 +223,14 @@ 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));
+ shared_ptr frame = decode_jpeg(read_frame(frame_spec));
unique_lock 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;
@@ -231,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 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;
- 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 || 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 = decode_jpeg_with_cache(frame_spec, 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;
}
@@ -308,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 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)
+void JPEGFrameView::setFrame(shared_ptr 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 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;