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 size_t num_decoded = 0, num_dropped = 0;
64 pthread_setname_np(pthread_self(), "JPEGDecoder");
68 shared_ptr<QPixmap> pixmap;
70 unique_lock<mutex> lock(cache_mu);
71 any_pending_decodes.wait(lock, [] {
72 return !pending_decodes.empty();
74 id = pending_decodes.front().first;
75 dest = pending_decodes.front().second;
76 pending_decodes.pop_front();
78 auto it = cache.find(id);
79 if (it != cache.end()) {
80 pixmap = it->second.pixmap;
81 it->second.last_used = event_counter++;
85 if (pixmap == nullptr) {
86 // Not found in the cache, so we need to do a decode or drop the request.
87 // Prune the queue if there are too many pending for this destination.
88 // TODO: Could we get starvation here?
89 size_t num_pending = 0;
90 for (const pair<JPEGID, JPEGFrameView *> &decode : pending_decodes) {
91 if (decode.second == dest) {
95 if (num_pending > 3) {
101 new QPixmap(QString::fromStdString(filename_for_frame(id.stream_idx, id.pts))));
103 unique_lock<mutex> lock(cache_mu);
104 cache[id] = LRUPixmap{ pixmap, event_counter++ };
106 if (cache.size() > CACHE_SIZE) {
110 if (num_decoded % 1000 == 0) {
111 fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n",
112 num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped));
116 dest->setPixmap(pixmap);
120 JPEGFrameView::JPEGFrameView(QWidget *parent)
121 : QGraphicsView(parent) {
122 scene.addItem(&item);
124 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
125 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
127 static once_flag once;
129 std::thread(&jpeg_decoder_thread).detach();
133 void JPEGFrameView::update_frame()
135 unique_lock<mutex> lock(cache_mu);
136 pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this);
137 any_pending_decodes.notify_all();
140 void JPEGFrameView::resizeEvent(QResizeEvent *event)
142 fitInView(&item, Qt::KeepAspectRatio);
145 void JPEGFrameView::setPixmap(std::shared_ptr<QPixmap> pixmap)
147 post_to_main_thread([this, pixmap] {
148 item.setPixmap(*pixmap);
149 fitInView(&item, Qt::KeepAspectRatio);