1 #include "jpeg_frame_view.h"
6 #include <condition_variable>
11 #include <QGraphicsPixmapItem>
15 #include "post_to_main_thread.h"
19 string filename_for_frame(unsigned stream_idx, int64_t pts);
25 bool operator< (const JPEGID &a, const JPEGID &b) {
26 return make_pair(a.stream_idx, a.pts) < make_pair(b.stream_idx, b.pts);
30 shared_ptr<QPixmap> pixmap;
35 map<JPEGID, LRUPixmap> cache; // Under cache_mu.
36 condition_variable any_pending_decodes;
37 deque<pair<JPEGID, JPEGFrameView *>> pending_decodes; // Under cache_mu.
38 atomic<size_t> event_counter{0};
42 // Assumes cache_mu is held.
43 vector<size_t> lru_timestamps;
44 for (const auto &key_and_value : cache) {
45 lru_timestamps.push_back(key_and_value.second.last_used);
48 size_t cutoff_point = CACHE_SIZE / 10; // Prune away the 10% oldest ones.
49 nth_element(lru_timestamps.begin(), lru_timestamps.begin() + cutoff_point, lru_timestamps.end());
50 size_t must_be_used_after = lru_timestamps[cutoff_point];
51 for (auto it = cache.begin(); it != cache.end(); ) {
52 if (it->second.last_used < must_be_used_after) {
60 void jpeg_decoder_thread()
62 pthread_setname_np(pthread_self(), "JPEGDecoder");
66 shared_ptr<QPixmap> pixmap;
68 unique_lock<mutex> lock(cache_mu);
69 any_pending_decodes.wait(lock, [] {
70 return !pending_decodes.empty();
72 id = pending_decodes.front().first;
73 dest = pending_decodes.front().second;
74 pending_decodes.pop_front();
76 auto it = cache.find(id);
77 if (it != cache.end()) {
78 pixmap = it->second.pixmap;
79 it->second.last_used = event_counter++;
83 if (pixmap == nullptr) {
84 // Not found in the cache, so we need to do a decode or drop the request.
85 // Prune the queue if there are too many pending for this destination.
86 // TODO: Could we get starvation here?
87 size_t num_pending = 0;
88 for (const pair<JPEGID, JPEGFrameView *> &decode : pending_decodes) {
89 if (decode.second == dest) {
93 if (num_pending > 3) {
98 new QPixmap(QString::fromStdString(filename_for_frame(id.stream_idx, id.pts))));
100 unique_lock<mutex> lock(cache_mu);
101 cache[id] = LRUPixmap{ pixmap, event_counter++ };
103 if (cache.size() > CACHE_SIZE) {
108 dest->setPixmap(pixmap);
112 JPEGFrameView::JPEGFrameView(QWidget *parent)
113 : QGraphicsView(parent) {
114 scene.addItem(&item);
116 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
117 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
119 static once_flag once;
121 std::thread(&jpeg_decoder_thread).detach();
125 void JPEGFrameView::update_frame()
127 unique_lock<mutex> lock(cache_mu);
128 pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this);
129 any_pending_decodes.notify_all();
132 void JPEGFrameView::resizeEvent(QResizeEvent *event)
134 fitInView(&item, Qt::KeepAspectRatio);
137 void JPEGFrameView::setPixmap(std::shared_ptr<QPixmap> pixmap)
139 post_to_main_thread([this, pixmap] {
140 item.setPixmap(*pixmap);
141 fitInView(&item, Qt::KeepAspectRatio);