]> git.sesse.net Git - nageru/blob - jpeg_frame_view.cpp
Convert Y'CbCr using Movit instead of in software with Qt. Saves a fair amount of...
[nageru] / jpeg_frame_view.cpp
1 #include "jpeg_frame_view.h"
2
3 #include <jpeglib.h>
4 #include <stdint.h>
5
6 #include <atomic>
7 #include <condition_variable>
8 #include <deque>
9 #include <mutex>
10 #include <thread>
11 #include <utility>
12
13 #include <movit/resource_pool.h>
14 #include <movit/init.h>
15 #include <movit/util.h>
16
17 #include "defs.h"
18 #include "post_to_main_thread.h"
19
20 using namespace movit;
21 using namespace std;
22
23 string filename_for_frame(unsigned stream_idx, int64_t pts);
24
25 struct JPEGID {
26         unsigned stream_idx;
27         int64_t pts;
28 };
29 bool operator< (const JPEGID &a, const JPEGID &b) {
30         return make_pair(a.stream_idx, a.pts) < make_pair(b.stream_idx, b.pts);
31 }
32
33 struct LRUFrame {
34         shared_ptr<Frame> frame;
35         size_t last_used;
36 };
37
38 mutex cache_mu;
39 map<JPEGID, LRUFrame> cache;  // Under cache_mu.
40 condition_variable any_pending_decodes;
41 deque<pair<JPEGID, JPEGFrameView *>> pending_decodes;  // Under cache_mu.
42 atomic<size_t> event_counter{0};
43 extern QGLWidget *global_share_widget;
44
45 // TODO: Decode using VA-API if available.
46 shared_ptr<Frame> decode_jpeg(const string &filename)
47 {
48         shared_ptr<Frame> frame(new Frame);
49
50         jpeg_decompress_struct dinfo;
51         jpeg_error_mgr jerr;
52         dinfo.err = jpeg_std_error(&jerr);
53         jpeg_create_decompress(&dinfo);
54
55         FILE *fp = fopen(filename.c_str(), "rb");
56         if (fp == nullptr) {
57                 perror(filename.c_str());
58                 exit(1);
59         }
60         jpeg_stdio_src(&dinfo, fp);
61
62         jpeg_read_header(&dinfo, true);
63
64         if (dinfo.num_components != 3 ||
65             dinfo.comp_info[0].h_samp_factor != 2 ||
66             dinfo.comp_info[0].v_samp_factor != 2 ||
67             dinfo.comp_info[1].h_samp_factor != 1 ||
68             dinfo.comp_info[1].v_samp_factor != 2 ||
69             dinfo.comp_info[2].h_samp_factor != 1 ||
70             dinfo.comp_info[2].v_samp_factor != 2) {
71                 fprintf(stderr, "Not 4:2:2 JPEG! (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n",
72                         dinfo.num_components,
73                         dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor,
74                         dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor,
75                         dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor);
76                 exit(1);
77         }
78         dinfo.raw_data_out = true;
79
80         jpeg_start_decompress(&dinfo);
81
82         frame->width = dinfo.output_width;
83         frame->height = dinfo.output_height;
84
85         unsigned chroma_width_blocks = (dinfo.output_width + 15) / 16;
86         unsigned width_blocks = chroma_width_blocks * 2;
87         unsigned height_blocks = (dinfo.output_height + 15) / 16;
88
89         // TODO: Decode into a PBO.
90         frame->y.reset(new uint8_t[width_blocks * (height_blocks * 2) * DCTSIZE2]);
91         frame->cb.reset(new uint8_t[chroma_width_blocks * (height_blocks * 2) * DCTSIZE2]);
92         frame->cr.reset(new uint8_t[chroma_width_blocks * (height_blocks * 2) * DCTSIZE2]);
93
94         JSAMPROW yptr[16], cbptr[16], crptr[16];
95         JSAMPARRAY data[3] = { yptr, cbptr, crptr };
96         for (unsigned y = 0; y < height_blocks; ++y) {
97                 for (unsigned yy = 0; yy < DCTSIZE * 2; ++yy) {
98                         yptr[yy] = frame->y.get() + (y * DCTSIZE * 2 + yy) * width_blocks * DCTSIZE;
99                         cbptr[yy] = frame->cb.get() + (y * DCTSIZE * 2 + yy) * chroma_width_blocks * DCTSIZE;
100                         crptr[yy] = frame->cr.get() + (y * DCTSIZE * 2 + yy) * chroma_width_blocks * DCTSIZE;
101                 }
102
103                 jpeg_read_raw_data(&dinfo, data, /*num_lines=*/16);
104         }
105
106         (void) jpeg_finish_decompress(&dinfo);
107         jpeg_destroy_decompress(&dinfo);
108         fclose(fp);
109
110         return frame;
111 }
112
113 void prune_cache()
114 {
115         // Assumes cache_mu is held.
116         vector<size_t> lru_timestamps;
117         for (const auto &key_and_value : cache) {
118                 lru_timestamps.push_back(key_and_value.second.last_used);
119         }
120
121         size_t cutoff_point = CACHE_SIZE / 10;  // Prune away the 10% oldest ones.
122         nth_element(lru_timestamps.begin(), lru_timestamps.begin() + cutoff_point, lru_timestamps.end());
123         size_t must_be_used_after = lru_timestamps[cutoff_point];
124         for (auto it = cache.begin(); it != cache.end(); ) {
125                 if (it->second.last_used < must_be_used_after) {
126                         it = cache.erase(it);
127                 } else {
128                         ++it;
129                 }
130         }
131 }
132
133 void jpeg_decoder_thread()
134 {
135         size_t num_decoded = 0, num_dropped = 0;
136
137         pthread_setname_np(pthread_self(), "JPEGDecoder");
138         for ( ;; ) {
139                 JPEGID id;
140                 JPEGFrameView *dest;
141                 shared_ptr<Frame> frame;
142                 {
143                         unique_lock<mutex> lock(cache_mu);
144                         any_pending_decodes.wait(lock, [] {
145                                 return !pending_decodes.empty();
146                         });
147                         id = pending_decodes.front().first;
148                         dest = pending_decodes.front().second;
149                         pending_decodes.pop_front();
150
151                         auto it = cache.find(id);
152                         if (it != cache.end()) {
153                                 frame = it->second.frame;
154                                 it->second.last_used = event_counter++;
155                         }
156                 }
157
158                 if (frame == nullptr) {
159                         // Not found in the cache, so we need to do a decode or drop the request.
160                         // Prune the queue if there are too many pending for this destination.
161                         // TODO: Could we get starvation here?
162                         size_t num_pending = 0;
163                         for (const pair<JPEGID, JPEGFrameView *> &decode : pending_decodes) {
164                                 if (decode.second == dest) {
165                                         ++num_pending;
166                                 }
167                         }
168                         if (num_pending > 3) {
169                                 ++num_dropped;
170                                 continue;
171                         }
172
173                         frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts));
174
175                         unique_lock<mutex> lock(cache_mu);
176                         cache[id] = LRUFrame{ frame, event_counter++ };
177
178                         if (cache.size() > CACHE_SIZE) {
179                                 prune_cache();
180                         }
181                         ++num_decoded;
182                         if (num_decoded % 1000 == 0) {
183                                 fprintf(stderr, "Decoded %zu images, dropped %zu (%.2f%% dropped)\n",
184                                         num_decoded, num_dropped, (100.0 * num_dropped) / (num_decoded + num_dropped));
185                         }
186                 }
187
188                 dest->setDecodedFrame(frame);
189         }
190 }
191
192 JPEGFrameView::JPEGFrameView(QWidget *parent)
193         : QGLWidget(parent, global_share_widget) {
194 }
195
196 void JPEGFrameView::update_frame()
197 {
198         unique_lock<mutex> lock(cache_mu);
199         pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this);
200         any_pending_decodes.notify_all();
201 }
202
203 ResourcePool *resource_pool = nullptr;
204
205 void JPEGFrameView::initializeGL()
206 {
207         glDisable(GL_BLEND);
208         glDisable(GL_DEPTH_TEST);
209         glDepthMask(GL_FALSE);
210         check_error();
211
212         static once_flag once;
213         call_once(once, [] {
214                 CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
215                 resource_pool = new ResourcePool;
216
217                 std::thread(&jpeg_decoder_thread).detach();
218         });
219
220         chain.reset(new EffectChain(1280, 720, resource_pool));
221         ImageFormat image_format;
222         image_format.color_space = COLORSPACE_sRGB;
223         image_format.gamma_curve = GAMMA_sRGB;
224         YCbCrFormat ycbcr_format;
225         ycbcr_format.luma_coefficients = YCBCR_REC_709;
226         ycbcr_format.full_range = false;
227         ycbcr_format.num_levels = 256;
228         ycbcr_format.chroma_subsampling_x = 2;
229         ycbcr_format.chroma_subsampling_y = 1;
230         ycbcr_format.cb_x_position = 0.0f;  // H.264 -- _not_ JPEG, even though our input is MJPEG-encoded
231         ycbcr_format.cb_y_position = 0.5f;  // Irrelevant.
232         ycbcr_format.cr_x_position = 0.0f;
233         ycbcr_format.cr_y_position = 0.5f;
234         ycbcr_input = (movit::YCbCrInput *)chain->add_input(new YCbCrInput(image_format, ycbcr_format, 1280, 720));
235
236         ImageFormat inout_format;
237         inout_format.color_space = COLORSPACE_sRGB;
238         inout_format.gamma_curve = GAMMA_sRGB;
239
240         check_error();
241         chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
242         check_error();
243         chain->set_dither_bits(8);
244         check_error();
245         chain->finalize();
246         check_error();
247 }
248
249 void JPEGFrameView::resizeGL(int width, int height)
250 {
251         check_error();
252         glViewport(0, 0, width, height);
253         check_error();
254 }
255
256 void JPEGFrameView::paintGL()
257 {
258         //glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
259         //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
260
261         check_error();
262         chain->render_to_screen();
263 }
264
265 void JPEGFrameView::setDecodedFrame(std::shared_ptr<Frame> frame)
266 {
267         post_to_main_thread([this, frame] {
268                 current_frame = frame;
269                 int width_blocks = (frame->width + 15) / 16;
270                 ycbcr_input->set_width(frame->width);
271                 ycbcr_input->set_height(frame->height);
272                 ycbcr_input->set_pixel_data(0, frame->y.get());
273                 ycbcr_input->set_pixel_data(1, frame->cb.get());
274                 ycbcr_input->set_pixel_data(2, frame->cr.get());
275                 ycbcr_input->set_pitch(0, width_blocks * 16);
276                 ycbcr_input->set_pitch(1, width_blocks * 8);
277                 ycbcr_input->set_pitch(2, width_blocks * 8);
278                 update();
279         });
280 }