X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=jpeg_frame_view.cpp;h=be53688a03ee33bca3bb50688030978eb411ade2;hb=bdef311c334b674ba39a931805fb7d32ce8698da;hp=3d95a7701299a6c96ccf51c6cc57e50b6dab4af9;hpb=44e59d9e9aac7082e1bab9f5b492e439b12d0fea;p=nageru diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index 3d95a77..be53688 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -1,55 +1,83 @@ #include "jpeg_frame_view.h" -#include -#include -#include +#include "defs.h" +#include "jpeg_destroyer.h" +#include "post_to_main_thread.h" +#include "video_stream.h" +#include "ycbcr_converter.h" +#include +#include #include #include #include +#include +#include +#include +#include #include +#include #include +#include #include -#include -#include - -#include -#include -#include - -#include "defs.h" -#include "post_to_main_thread.h" +// Must come after the Qt stuff. #include "vaapi_jpeg_decoder.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; size_t last_used; }; +struct PendingDecode { + 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; -deque> pending_decodes; // Under cache_mu. +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; extern atomic should_quit; @@ -71,6 +99,7 @@ shared_ptr 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) { @@ -140,8 +169,7 @@ shared_ptr decode_jpeg(const string &filename) jpeg_read_raw_data(&dinfo, data, v_mcu_size); } - (void) jpeg_finish_decompress(&dinfo); - jpeg_destroy_decompress(&dinfo); + (void)jpeg_finish_decompress(&dinfo); fclose(fp); return frame; @@ -150,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; @@ -167,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; @@ -183,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; @@ -202,22 +242,21 @@ void jpeg_decoder_thread_func() pthread_setname_np(pthread_self(), "JPEGDecoder"); while (!should_quit.load()) { - JPEGID id; - JPEGFrameView *dest; + PendingDecode decode; CacheMissBehavior cache_miss_behavior = DECODE_IF_NOT_IN_CACHE; { unique_lock lock(cache_mu); // TODO: Perhaps under another lock? any_pending_decodes.wait(lock, [] { return !pending_decodes.empty() || should_quit.load(); }); - if (should_quit.load()) break; - id = pending_decodes.front().first; - dest = pending_decodes.front().second; + if (should_quit.load()) + break; + decode = pending_decodes.front(); pending_decodes.pop_front(); size_t num_pending = 0; - for (const pair &decode : pending_decodes) { - if (decode.second == dest) { + for (const PendingDecode &other_decode : pending_decodes) { + if (other_decode.destination == decode.destination) { ++num_pending; } } @@ -226,49 +265,50 @@ void jpeg_decoder_thread_func() } } - 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. + if (decode.frame != nullptr) { + // Already decoded, so just show it. + decode.destination->setDecodedFrame(decode.frame, nullptr, 1.0f); + continue; + } - auto it = cache.find(id); - assert(it != cache.end()); + shared_ptr primary_frame, secondary_frame; + bool drop = false; + for (int subframe_idx = 0; subframe_idx < 2; ++subframe_idx) { + 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 = decode_jpeg_with_cache(frame_spec, cache_miss_behavior, &found_in_cache); - 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); + assert(cache_miss_behavior == RETURN_NULLPTR_IF_NOT_IN_CACHE); + drop = true; + break; } - } 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); + if (!found_in_cache) { + ++num_decoded; + if (num_decoded % 1000 == 0) { + fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n", + num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped)); + } + } + if (subframe_idx == 0) { + primary_frame = move(frame); + } else { + secondary_frame = move(frame); + } + } + if (drop) { ++num_dropped; continue; } - if (!found_in_cache) { - ++num_decoded; - if (num_decoded % 1000 == 0) { - fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n", - num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped)); - } - } - // TODO: Could we get jitter between non-interpolated and interpolated frames here? - dest->setDecodedFrame(frame); + decode.destination->setDecodedFrame(primary_frame, secondary_frame, decode.fade_alpha); } } @@ -279,29 +319,32 @@ void JPEGFrameView::shutdown() } JPEGFrameView::JPEGFrameView(QWidget *parent) - : QGLWidget(parent, global_share_widget) { + : QGLWidget(parent, global_share_widget) +{ } -void JPEGFrameView::setFrame(unsigned stream_idx, int64_t pts, bool interpolated) +void JPEGFrameView::setFrame(unsigned stream_idx, FrameOnDisk frame, FrameOnDisk secondary_frame, float fade_alpha) { - current_stream_idx = stream_idx; + current_stream_idx = stream_idx; // TODO: Does this interact with fades? unique_lock lock(cache_mu); - pending_decodes.emplace_back(JPEGID{ stream_idx, pts, interpolated }, this); + PendingDecode decode; + 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(unsigned stream_idx, int64_t pts, shared_ptr frame) +void JPEGFrameView::setFrame(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(); + PendingDecode decode; + decode.frame = std::move(frame); + decode.destination = this; + pending_decodes.push_back(decode); + any_pending_decodes.notify_all(); } ResourcePool *resource_pool = nullptr; @@ -365,11 +408,21 @@ void JPEGFrameView::paintGL() } } -void JPEGFrameView::setDecodedFrame(std::shared_ptr frame) +namespace { + +} // namespace + +void JPEGFrameView::setDecodedFrame(shared_ptr frame, shared_ptr secondary_frame, float fade_alpha) { - post_to_main_thread([this, frame] { + post_to_main_thread([this, frame, secondary_frame, fade_alpha] { current_frame = frame; - current_chain = ycbcr_converter->prepare_chain_for_conversion(frame); + current_secondary_frame = secondary_frame; + + if (secondary_frame != nullptr) { + current_chain = ycbcr_converter->prepare_chain_for_fade(frame, secondary_frame, fade_alpha); + } else { + current_chain = ycbcr_converter->prepare_chain_for_conversion(frame); + } update(); }); }