]> git.sesse.net Git - pkanalytics/blob - video_widget.cpp
Make persistent PBOs for faster texture upload.
[pkanalytics] / video_widget.cpp
1 #define GL_GLEXT_PROTOTYPES
2
3 #include "video_widget.h"
4
5 #include <assert.h>
6 #include <pthread.h>
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13
14 extern "C" {
15 #include <libavcodec/avcodec.h>
16 #include <libavformat/avformat.h>
17 #include <libavutil/avutil.h>
18 #include <libavutil/error.h>
19 #include <libavutil/frame.h>
20 #include <libavutil/imgutils.h>
21 #include <libavutil/mem.h>
22 #include <libavutil/pixfmt.h>
23 #include <libavutil/opt.h>
24 #include <libswscale/swscale.h>
25 }
26
27 #include <chrono>
28 #include <cstdint>
29 #include <utility>
30 #include <vector>
31 #include <unordered_set>
32
33 #include "post_to_main_thread.h"
34
35 #include <QOpenGLFunctions>
36 #include <QWheelEvent>
37 #include <QMouseEvent>
38
39 #define BUFFER_OFFSET(i) ((char *)nullptr + (i))
40
41 using namespace std;
42 using namespace std::chrono;
43
44 namespace {
45
46 bool is_full_range(const AVPixFmtDescriptor *desc)
47 {
48         // This is horrible, but there's no better way that I know of.
49         return (strchr(desc->name, 'j') != nullptr);
50 }
51
52 AVPixelFormat decide_dst_format(AVPixelFormat src_format)
53 {
54         // If this is a non-Y'CbCr format, just convert to 4:4:4 Y'CbCr
55         // and be done with it. It's too strange to spend a lot of time on.
56         // (Let's hope there's no alpha.)
57         const AVPixFmtDescriptor *src_desc = av_pix_fmt_desc_get(src_format);
58         if (src_desc == nullptr ||
59             src_desc->nb_components != 3 ||
60             (src_desc->flags & AV_PIX_FMT_FLAG_RGB)) {
61                 return AV_PIX_FMT_YUV444P;
62         }
63
64         // The best for us would be Cb and Cr together if possible,
65         // but FFmpeg doesn't support that except in the special case of
66         // NV12, so we need to go to planar even for the case of NV12.
67         // Thus, look for the closest (but no worse) 8-bit planar Y'CbCr format
68         // that matches in color range. (This will also include the case of
69         // the source format already being acceptable.)
70         bool src_full_range = is_full_range(src_desc);
71         const char *best_format = "yuv444p";
72         unsigned best_score = numeric_limits<unsigned>::max();
73         for (const AVPixFmtDescriptor *desc = av_pix_fmt_desc_next(nullptr);
74              desc;
75              desc = av_pix_fmt_desc_next(desc)) {
76                 // Find planar Y'CbCr formats only.
77                 if (desc->nb_components != 3) continue;
78                 if (desc->flags & AV_PIX_FMT_FLAG_RGB) continue;
79                 if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR)) continue;
80                 if (desc->comp[0].plane != 0 ||
81                     desc->comp[1].plane != 1 ||
82                     desc->comp[2].plane != 2) continue;
83
84                 // 8-bit formats only.
85                 if (desc->flags & AV_PIX_FMT_FLAG_BE) continue;
86                 if (desc->comp[0].depth != 8) continue;
87
88                 // Same or better chroma resolution only.
89                 int chroma_w_diff = src_desc->log2_chroma_w - desc->log2_chroma_w;
90                 int chroma_h_diff = src_desc->log2_chroma_h - desc->log2_chroma_h;
91                 if (chroma_w_diff < 0 || chroma_h_diff < 0)
92                         continue;
93
94                 // Matching full/limited range only.
95                 if (is_full_range(desc) != src_full_range)
96                         continue;
97
98                 // Pick something with as little excess chroma resolution as possible.
99                 unsigned score = (1 << (chroma_w_diff)) << chroma_h_diff;
100                 if (score < best_score) {
101                         best_score = score;
102                         best_format = desc->name;
103                 }
104         }
105         return av_get_pix_fmt(best_format);
106 }
107
108 }  // namespace
109
110 bool VideoWidget::process_queued_commands(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx, int video_stream_index, bool *seeked)
111 {
112         // Process any queued commands from other threads.
113         vector<QueuedCommand> commands;
114         {
115                 lock_guard<mutex> lock(queue_mu);
116                 swap(commands, command_queue);
117         }
118
119         for (const QueuedCommand &cmd : commands) {
120                 switch (cmd.command) {
121                 case QueuedCommand::PAUSE:
122                         paused = true;
123                         break;
124                 case QueuedCommand::RESUME:
125                         paused = false;
126                         pts_origin = last_pts;
127                         start = next_frame_start = steady_clock::now();
128                         break;
129                 case QueuedCommand::SEEK:
130                 case QueuedCommand::SEEK_ABSOLUTE:
131                         // Dealt with below.
132                         break;
133                 }
134         }
135
136         // Combine all seeks into one big one. (There are edge cases where this is probably
137         // subtly wrong, but we'll live with it.)
138         int64_t base_pts = last_pts;
139         int64_t relative_seek_ms = 0;
140         int64_t relative_seek_frames = 0;
141         for (const QueuedCommand &cmd : commands) {
142                 if (cmd.command == QueuedCommand::SEEK) {
143                         relative_seek_ms += cmd.relative_seek_ms;
144                         relative_seek_frames += cmd.relative_seek_frames;
145                 } else if (cmd.command == QueuedCommand::SEEK_ABSOLUTE) {
146                         base_pts = av_rescale_q(cmd.seek_ms, AVRational{ 1, 1000 }, video_timebase);
147                         relative_seek_ms = 0;
148                         relative_seek_frames = 0;
149                 }
150         }
151         int64_t relative_seek_pts = av_rescale_q(relative_seek_ms, AVRational{ 1, 1000 }, video_timebase);
152         if (relative_seek_ms != 0 && relative_seek_pts == 0) {
153                 // Just to be sure rounding errors don't move us into nothingness.
154                 relative_seek_pts = (relative_seek_ms > 0) ? 1 : -1;
155         }
156         int64_t goal_pts = base_pts + relative_seek_pts;
157         if (goal_pts != last_pts || relative_seek_frames < 0) {
158                 avcodec_flush_buffers(video_codec_ctx);
159                 queued_frames.clear();
160
161                 // Seek to the last keyframe before this point.
162                 int64_t seek_pts = goal_pts;
163                 if (relative_seek_frames < 0) {
164                         // If we're frame-skipping backwards, add 100 ms of slop for each frame
165                         // so we're fairly certain we are able to see the ones we want.
166                         seek_pts -= av_rescale_q(-relative_seek_frames, AVRational{ 1, 10 }, video_timebase);
167                 }
168                 av_seek_frame(format_ctx, video_stream_index, seek_pts, AVSEEK_FLAG_BACKWARD);
169
170                 // Decode frames until EOF, or until we see something past our seek point.
171                 std::deque<AVFrameWithDeleter> queue;
172                 for ( ;; ) {
173                         bool error = false;
174                         AVFrameWithDeleter frame = decode_frame(format_ctx, video_codec_ctx,
175                                 pathname, video_stream_index, &error);
176                         if (frame == nullptr || error) {
177                                 break;
178                         }
179
180                         int64_t frame_pts = frame->pts;
181                         if (relative_seek_frames < 0) {
182                                 // Buffer this frame; don't display it unless we know it's the Nth-latest.
183                                 queue.push_back(std::move(frame));
184                                 if (queue.size() > uint64_t(-relative_seek_frames) + 1) {
185                                         queue.pop_front();
186                                 }
187                         }
188                         if (frame_pts >= goal_pts) {
189                                 if (relative_seek_frames > 0) {
190                                         --relative_seek_frames;
191                                 } else {
192                                         if (relative_seek_frames < 0) {
193                                                 // Hope we have the right amount.
194                                                 // The rest will remain in the queue for when we play forward again.
195                                                 frame = std::move(queue.front());
196                                                 queue.pop_front();
197                                                 queued_frames = std::move(queue);
198                                         }
199                                         shared_ptr<Frame> new_frame = make_video_frame(frame.get());
200                                         {
201                                                 lock_guard lock(current_frame_mu);
202                                                 current_frame = std::move(new_frame);
203                                         }
204                                         update();
205                                         store_pts(frame->pts);
206                                         break;
207                                 }
208                         }
209                 }
210
211                 // NOTE: We keep pause status as-is.
212
213                 pts_origin = last_pts;
214                 start = next_frame_start = last_frame = steady_clock::now();
215                 if (seeked) {
216                         *seeked = true;
217                 }
218         } else if (relative_seek_frames > 0) {
219                 // The base PTS is fine, we only need to skip a few frames forwards.
220                 while (relative_seek_frames > 1) {
221                         // Eat a frame (ignore errors).
222                         bool error;
223                         decode_frame(format_ctx, video_codec_ctx, pathname, video_stream_index, &error);
224                         --relative_seek_frames;
225                 }
226
227                 // Display the last one.
228                 bool error;
229                 AVFrameWithDeleter frame = decode_frame(format_ctx, video_codec_ctx,
230                         pathname, video_stream_index, &error);
231                 if (frame == nullptr || error) {
232                         return true;
233                 }
234                 shared_ptr<Frame> new_frame = make_video_frame(frame.get());
235                 {
236                         lock_guard lock(current_frame_mu);
237                         current_frame = std::move(new_frame);
238                 }
239                 update();
240                 store_pts(frame->pts);
241         }
242         return false;
243 }
244
245 VideoWidget::VideoWidget(QWidget *parent)
246         : QOpenGLWidget(parent) {}
247
248 GLuint compile_shader(const string &shader_src, GLenum type)
249 {
250         GLuint obj = glCreateShader(type);
251         const GLchar* source[] = { shader_src.data() };
252         const GLint length[] = { (GLint)shader_src.size() };
253         glShaderSource(obj, 1, source, length);
254         glCompileShader(obj);
255
256         GLchar info_log[4096];
257         GLsizei log_length = sizeof(info_log) - 1;
258         glGetShaderInfoLog(obj, log_length, &log_length, info_log);
259         info_log[log_length] = 0;
260         if (strlen(info_log) > 0) {
261                 fprintf(stderr, "Shader compile log: %s\n", info_log);
262         }
263
264         GLint status;
265         glGetShaderiv(obj, GL_COMPILE_STATUS, &status);
266         if (status == GL_FALSE) {
267                 // Add some line numbers to easier identify compile errors.
268                 string src_with_lines = "/*   1 */ ";
269                 size_t lineno = 1;
270                 for (char ch : shader_src) {
271                         src_with_lines.push_back(ch);
272                         if (ch == '\n') {
273                                 char buf[32];
274                                 snprintf(buf, sizeof(buf), "/* %3zu */ ", ++lineno);
275                                 src_with_lines += buf;
276                         }
277                 }
278
279                 fprintf(stderr, "Failed to compile shader:\n%s\n", src_with_lines.c_str());
280                 exit(1);
281         }
282
283         return obj;
284 }
285
286 void VideoWidget::initializeGL()
287 {
288         glDisable(GL_BLEND);
289         glDisable(GL_DEPTH_TEST);
290         glDepthMask(GL_FALSE);
291         glCreateTextures(GL_TEXTURE_2D, 3, tex);
292
293         ycbcr_vertex_shader = compile_shader(R"(
294 #version 460 core
295
296 layout(location = 0) in vec2 position;
297 layout(location = 1) in vec2 texcoord;
298 out vec2 tc;
299
300 void main()
301 {
302         // The result of glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0) is:
303         //
304         //   2.000  0.000  0.000 -1.000
305         //   0.000  2.000  0.000 -1.000
306         //   0.000  0.000 -2.000 -1.000
307         //   0.000  0.000  0.000  1.000
308         gl_Position = vec4(2.0 * position.x - 1.0, 2.0 * position.y - 1.0, -1.0, 1.0);
309         tc = texcoord;
310         tc.y = 1.0f - tc.y;
311 }
312 )", GL_VERTEX_SHADER);
313         ycbcr_fragment_shader = compile_shader(R"(
314 #version 460 core
315
316 layout(location = 0) uniform sampler2D tex_y;
317 layout(location = 1) uniform sampler2D tex_cb;
318 layout(location = 2) uniform sampler2D tex_cr;
319 layout(location = 3) uniform vec2 cbcr_offset;
320
321 in vec2 tc;
322 out vec4 FragColor;
323
324 // Computed statically by Movit, for limited-range BT.709.
325 // (We don't check whether the input could be BT.601 or BT.2020 currently, or full-range)
326 const mat3 inv_ycbcr_matrix = mat3(
327         1.16438f, 1.16438f, 1.16438f,
328         0.0f, -0.21325f, 2.11240f,
329         1.79274f, -0.53291f, 0.0f
330 );
331
332 void main()
333 {
334         if (tc.x < 0.0 || tc.x > 1.0 || tc.y < 0.0 || tc.y > 1.0) {
335                 FragColor.rgba = vec4(0.0f, 0.0f, 0.0f, 1.0f);
336                 return;
337         }
338
339         vec3 ycbcr;
340         ycbcr.r = texture(tex_y, tc).r;
341         ycbcr.g = texture(tex_cb, tc + cbcr_offset).r;
342         ycbcr.b = texture(tex_cr, tc + cbcr_offset).r;
343         ycbcr -= vec3(16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f);
344         FragColor.rgb = inv_ycbcr_matrix * ycbcr;
345         FragColor.a = 1.0f;
346 }
347 )", GL_FRAGMENT_SHADER);
348         ycbcr_program = glCreateProgram();
349         glAttachShader(ycbcr_program, ycbcr_vertex_shader);
350         glAttachShader(ycbcr_program, ycbcr_fragment_shader);
351         glLinkProgram(ycbcr_program);
352
353         GLint success;
354         glGetProgramiv(ycbcr_program, GL_LINK_STATUS, &success);
355         if (success == GL_FALSE) {
356                 GLchar error_log[1024] = {0};
357                 glGetProgramInfoLog(ycbcr_program, 1024, nullptr, error_log);
358                 fprintf(stderr, "Error linking program: %s\n", error_log);
359                 exit(1);
360         }
361
362         glCreateSamplers(1, &bilinear_sampler);
363         glSamplerParameteri(bilinear_sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
364         glSamplerParameteri(bilinear_sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
365         glSamplerParameteri(bilinear_sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
366         glSamplerParameteri(bilinear_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
367 }
368
369 void VideoWidget::resizeGL(int w, int h)
370 {
371         glViewport(0, 0, w, h);
372         display_aspect = double(w) / h;
373 }
374
375 int num_levels(GLuint width, GLuint height)
376 {
377         int levels = 1;
378         while (width > 1 || height > 1) {
379                 width = max(width / 2, 1u);
380                 height = max(height / 2, 1u);
381                 ++levels;
382         }
383         return levels;
384 }
385
386 void VideoWidget::paintGL()
387 {
388         std::shared_ptr<Frame> frame;
389         {
390                 lock_guard lock(current_frame_mu);
391                 frame = current_frame;
392         }
393         if (frame == nullptr) {
394                 glClear(GL_COLOR_BUFFER_BIT);
395                 return;
396         }
397
398         glUseProgram(ycbcr_program);
399         if (frame->width != last_width || frame->height != last_height) {
400                 glTextureStorage2D(tex[0], num_levels(frame->width, frame->height), GL_R8, frame->width, frame->height);
401         }
402         if (frame->chroma_width != last_chroma_width || frame->chroma_height != last_chroma_height) {
403                 for (GLuint num : { tex[1], tex[2] }) {
404                         glTextureStorage2D(num, num_levels(frame->chroma_width, frame->chroma_height), GL_R8, frame->chroma_width, frame->chroma_height);
405                 }
406         }
407
408         glBindBuffer(GL_PIXEL_UNPACK_BUFFER, frame->pbo);
409
410         if (frame->need_flush_len > 0) {
411                 glFlushMappedNamedBufferRange(frame->pbo, 0, frame->need_flush_len);
412                 frame->need_flush_len = 0;
413         }
414
415         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
416         glTextureSubImage2D(tex[0], 0, 0, 0, frame->width, frame->height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
417         glGenerateTextureMipmap(tex[0]);
418
419         glTextureSubImage2D(tex[1], 0, 0, 0, frame->chroma_width, frame->chroma_height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(frame->width * frame->height));
420         glGenerateTextureMipmap(tex[1]);
421
422         glTextureSubImage2D(tex[2], 0, 0, 0, frame->chroma_width, frame->chroma_height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(frame->width * frame->height + frame->chroma_width * frame->chroma_height));
423         glGenerateTextureMipmap(tex[2]);
424
425         glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
426
427         glBindTextureUnit(0, tex[0]);
428         glBindTextureUnit(1, tex[1]);
429         glBindTextureUnit(2, tex[2]);
430         glBindSampler(0, bilinear_sampler);
431         glBindSampler(1, bilinear_sampler);
432         glBindSampler(2, bilinear_sampler);
433         glProgramUniform1i(ycbcr_program, 0, 0);
434         glProgramUniform1i(ycbcr_program, 1, 1);
435         glProgramUniform1i(ycbcr_program, 2, 2);
436         glProgramUniform2f(ycbcr_program, 3, cbcr_offset[0], -cbcr_offset[1]);
437
438         float tx1 = 0.0f;
439         float tx2 = 1.0f;
440         float ty1 = 0.0f;
441         float ty2 = 1.0f;
442
443         double video_aspect = double(frame->width) / frame->height;
444         if (display_aspect > video_aspect) {
445                 double extra_width = frame->height * display_aspect - frame->width;
446                 tx1 = -0.5 * extra_width / frame->width;
447                 tx2 = 1.0 + 0.5 * extra_width / frame->width;
448         } else if (display_aspect < video_aspect) {
449                 double extra_height = frame->width / display_aspect - frame->height;
450                 ty1 = -0.5 * extra_height / frame->height;
451                 ty2 = 1.0 + 0.5 * extra_height / frame->height;
452         }
453
454         glBegin(GL_QUADS);
455
456         // (0,0)
457         glVertexAttrib2f(1, tx1, ty1);
458         glVertex2f(zoom_matrix[2 * 3 + 0], zoom_matrix[2 * 3 + 1]);
459
460         // (0,1)
461         glVertexAttrib2f(1, tx1, ty2);
462         glVertex2f(zoom_matrix[1 * 3 + 0] + zoom_matrix[2 * 3 + 0], zoom_matrix[1 * 3 + 1] + zoom_matrix[2 * 3 + 1]);
463
464         // (1,1)
465         glVertexAttrib2f(1, tx2, ty2);
466         glVertex2f(zoom_matrix[0 * 3 + 0] + zoom_matrix[1 * 3 + 0] + zoom_matrix[2 * 3 + 0],
467                    zoom_matrix[1 * 3 + 0] + zoom_matrix[1 * 3 + 1] + zoom_matrix[2 * 3 + 1]);
468
469         // (1,0)
470         glVertexAttrib2f(1, tx2, ty1);
471         glVertex2f(zoom_matrix[0 * 3 + 0] + zoom_matrix[2 * 3 + 0],
472                    zoom_matrix[1 * 3 + 0] + zoom_matrix[2 * 3 + 1]);
473
474         glEnd();
475 }
476
477 void matmul3x3(const double a[9], const double b[9], double res[9])
478 {
479         for (int i = 0; i < 3; ++i) {
480                 for (int j = 0; j < 3; ++j) {
481                         double sum = 0.0;
482                         for (int k = 0; k < 3; ++k) {
483                                 sum += a[i * 3 + k] * b[k * 3 + j];
484                         }
485                         res[i * 3 + j] = sum;
486                 }
487         }
488 }
489
490 void VideoWidget::wheelEvent(QWheelEvent *event)
491 {
492         int delta = event->angleDelta().y();
493         if (delta == 0) {
494                 return;
495         }
496         double x = event->position().x() / width();
497         double y = 1.0 - event->position().y() / height();
498         double zoom = delta > 0 ? pow(1.005, delta) : pow(1/1.005, -delta);
499
500         const double inv_translation_matrix[9] = {
501                 1.0, 0.0, 0.0,
502                 0.0, 1.0, 0.0,
503                 -x, -y, 1.0
504         };
505         const double scale_matrix[9] = {
506                 zoom, 0.0, 0.0,
507                 0.0, zoom, 0.0,
508                 0.0, 0.0, 1.0
509         };
510         const double translation_matrix[9] = {
511                 1.0, 0.0, 0.0,
512                 0.0, 1.0, 0.0,
513                 x, y, 1.0
514         };
515         double tmp1[9], tmp2[9];
516         matmul3x3(zoom_matrix, inv_translation_matrix, tmp1);
517         matmul3x3(tmp1, scale_matrix, tmp2);
518         matmul3x3(tmp2, translation_matrix, zoom_matrix);
519
520         fixup_zoom_matrix();
521         update();
522 }
523
524 void VideoWidget::mousePressEvent(QMouseEvent *e)
525 {
526         if (e->button() == Qt::BackButton) {
527                 emit mouse_back_clicked();
528         } else if (e->button() == Qt::ForwardButton) {
529                 emit mouse_forward_clicked();
530         } else if (e->button() == Qt::LeftButton) {
531                 dragging = true;
532                 last_drag_x = e->position().x();
533                 last_drag_y = e->position().y();
534         }
535 }
536
537 void VideoWidget::mouseReleaseEvent(QMouseEvent *e)
538 {
539         if (e->button() == Qt::LeftButton) {
540                 dragging = false;
541         }
542 }
543
544 void VideoWidget::mouseMoveEvent(QMouseEvent *e)
545 {
546         if (!dragging) {
547                 return;
548         }
549         float dx = (e->position().x() - last_drag_x) / width();
550         float dy = (e->position().y() - last_drag_y) / height();
551
552         //zoom_matrix[6] += dx * zoom_matrix[0];
553         //zoom_matrix[7] += dy * zoom_matrix[4];
554         zoom_matrix[6] += dx;
555         zoom_matrix[7] -= dy;
556         fixup_zoom_matrix();
557
558         last_drag_x = e->position().x();
559         last_drag_y = e->position().y();
560
561         update();
562 }
563
564 // Normalize the matrix so that we never get skew or similar,
565 // and also never can zoom or pan too far out.
566 void VideoWidget::fixup_zoom_matrix()
567 {
568         // Correct for any numerical errors (we know the matrix must be orthogonal
569         // and have zero rotation).
570         zoom_matrix[4] = zoom_matrix[0];
571         zoom_matrix[1] = zoom_matrix[2] = zoom_matrix[3] = zoom_matrix[5] = 0.0;
572         zoom_matrix[8] = 1.0;
573
574         // We can't zoom further out than 1:1. (Perhaps it would be nice to
575         // reuse the last zoom-in point to do this, but the center will have to do
576         // for now.)
577         if (zoom_matrix[0] < 1.0) {
578                 const double zoom = 1.0 / zoom_matrix[0];
579                 const double inv_translation_matrix[9] = {
580                         1.0, 0.0, 0.0,
581                         0.0, 1.0, 0.0,
582                         -0.5, -0.5, 1.0
583                 };
584                 const double scale_matrix[9] = {
585                         zoom, 0.0, 0.0,
586                         0.0, zoom, 0.0,
587                         0.0, 0.0, 1.0
588                 };
589                 const double translation_matrix[9] = {
590                         1.0, 0.0, 0.0,
591                         0.0, 1.0, 0.0,
592                         0.5, 0.5, 1.0
593                 };
594                 double tmp1[9], tmp2[9];
595                 matmul3x3(zoom_matrix, inv_translation_matrix, tmp1);
596                 matmul3x3(tmp1, scale_matrix, tmp2);
597                 matmul3x3(tmp2, translation_matrix, zoom_matrix);
598         }
599
600         // Looking at the points we'll draw with glVertex2f(), make sure none of them are
601         // inside the square (which would generally mean we've panned ourselves out-of-bounds).
602         // We simply adjust the translation, which is possible because we fixed scaling above.
603         zoom_matrix[6] = min(zoom_matrix[6], 0.0);  // Left side (x=0).
604         zoom_matrix[7] = min(zoom_matrix[7], 0.0);  // Bottom side (y=0).
605         zoom_matrix[6] = std::max(zoom_matrix[6], 1.0 - zoom_matrix[0]);  // Right side (x=1).
606         zoom_matrix[7] = std::max(zoom_matrix[7], 1.0 - zoom_matrix[4]);  // Top side (y=1).
607 }
608
609 bool VideoWidget::open(const string &filename)
610 {
611         stop();
612         internal_rewind();
613         pathname = filename;
614         play();
615
616         while (running == STARTING) {
617                 // Poor man's condition variable...
618                 usleep(10000);
619                 sched_yield();
620         }
621         return (running != VIDEO_FILE_ERROR);   
622 }
623
624 void VideoWidget::play()
625 {
626         if (running != NOT_RUNNING && running != VIDEO_FILE_ERROR) {
627                 std::lock_guard<std::mutex> lock(queue_mu);
628                 command_queue.push_back(QueuedCommand { QueuedCommand::RESUME });
629                 producer_thread_should_quit.wakeup();
630                 return;
631         }
632         running = STARTING;
633         producer_thread_should_quit.unquit();
634         if (producer_thread.joinable()) {
635                 producer_thread.join();
636         }
637         producer_thread = std::thread(&VideoWidget::producer_thread_func, this);
638 }
639
640 void VideoWidget::pause()
641 {
642         if (running == NOT_RUNNING || running == VIDEO_FILE_ERROR) {
643                 return;
644         }
645         std::lock_guard<std::mutex> lock(queue_mu);
646         command_queue.push_back(QueuedCommand { QueuedCommand::PAUSE });
647         producer_thread_should_quit.wakeup();
648 }
649
650 void VideoWidget::seek(int64_t relative_seek_ms)
651 {
652         if (running == NOT_RUNNING || running == VIDEO_FILE_ERROR) {
653                 return;
654         }
655         std::lock_guard<std::mutex> lock(queue_mu);
656         command_queue.push_back(QueuedCommand { QueuedCommand::SEEK, relative_seek_ms, 0, 0 });
657         producer_thread_should_quit.wakeup();
658 }
659
660 void VideoWidget::seek_frames(int64_t relative_seek_frames)
661 {
662         if (running == NOT_RUNNING || running == VIDEO_FILE_ERROR) {
663                 return;
664         }
665         std::lock_guard<std::mutex> lock(queue_mu);
666         command_queue.push_back(QueuedCommand { QueuedCommand::SEEK, 0, relative_seek_frames, 0 });
667         producer_thread_should_quit.wakeup();
668 }
669
670 void VideoWidget::seek_absolute(int64_t position_ms)
671 {
672         if (running == NOT_RUNNING || running == VIDEO_FILE_ERROR) {
673                 return;
674         }
675         std::lock_guard<std::mutex> lock(queue_mu);
676         command_queue.push_back(QueuedCommand { QueuedCommand::SEEK_ABSOLUTE, 0, 0, position_ms });
677         producer_thread_should_quit.wakeup();
678 }
679
680 void VideoWidget::stop()
681 {
682         if (running == NOT_RUNNING || running == VIDEO_FILE_ERROR) {
683                 return;
684         }
685         producer_thread_should_quit.quit();
686         producer_thread.join();
687 }
688
689 void VideoWidget::producer_thread_func()
690 {
691         if (!producer_thread_should_quit.should_quit()) {
692                 if (!play_video(pathname)) {
693                         running = VIDEO_FILE_ERROR;
694                 } else {
695                         running = NOT_RUNNING;
696                 }
697         }
698 }
699
700 void VideoWidget::internal_rewind()
701 {
702         pts_origin = last_pts = 0;
703         last_position = 0;
704         start = next_frame_start = steady_clock::now();
705 }
706
707 template<AVHWDeviceType type>
708 AVPixelFormat get_hw_format(AVCodecContext *ctx, const AVPixelFormat *fmt)
709 {
710         bool found_config_of_right_type = false;
711         for (int i = 0;; ++i) {  // Termination condition inside loop.
712                 const AVCodecHWConfig *config = avcodec_get_hw_config(ctx->codec, i);
713                 if (config == nullptr) {  // End of list.
714                         break;
715                 }
716                 if (!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) ||
717                     config->device_type != type) {
718                         // Not interesting for us.
719                         continue;
720                 }
721
722                 // We have a config of the right type, but does it actually support
723                 // the pixel format we want? (Seemingly, FFmpeg's way of signaling errors
724                 // is to just replace the pixel format with a software-decoded one,
725                 // such as yuv420p.)
726                 found_config_of_right_type = true;
727                 for (const AVPixelFormat *fmt_ptr = fmt; *fmt_ptr != -1; ++fmt_ptr) {
728                         if (config->pix_fmt == *fmt_ptr) {
729                                 fprintf(stderr, "Initialized '%s' hardware decoding for codec '%s'.\n",
730                                         av_hwdevice_get_type_name(type), ctx->codec->name);
731                                 if (ctx->profile == FF_PROFILE_H264_BASELINE) {
732                                         fprintf(stderr, "WARNING: Stream claims to be H.264 Baseline, which is generally poorly supported in hardware decoders.\n");
733                                         fprintf(stderr, "         Consider encoding it as Constrained Baseline, Main or High instead.\n");
734                                         fprintf(stderr, "         Decoding might fail and fall back to software.\n");
735                                 }
736                                 return config->pix_fmt;
737                         }
738                 }
739                 fprintf(stderr, "Decoder '%s' supports only these pixel formats:", ctx->codec->name);
740                 unordered_set<AVPixelFormat> seen;
741                 for (const AVPixelFormat *fmt_ptr = fmt; *fmt_ptr != -1; ++fmt_ptr) {
742                         if (!seen.count(*fmt_ptr)) {
743                                 fprintf(stderr, " %s", av_get_pix_fmt_name(*fmt_ptr));
744                                 seen.insert(*fmt_ptr);
745                         }
746                 }
747                 fprintf(stderr, " (wanted %s for hardware acceleration)\n", av_get_pix_fmt_name(config->pix_fmt));
748
749         }
750
751         if (!found_config_of_right_type) {
752                 fprintf(stderr, "Decoder '%s' does not support device type '%s'.\n", ctx->codec->name, av_hwdevice_get_type_name(type));
753         }
754
755         // We found no VA-API formats, so take the first software format.
756         for (const AVPixelFormat *fmt_ptr = fmt; *fmt_ptr != -1; ++fmt_ptr) {
757                 if ((av_pix_fmt_desc_get(*fmt_ptr)->flags & AV_PIX_FMT_FLAG_HWACCEL) == 0) {
758                         fprintf(stderr, "Falling back to software format %s.\n", av_get_pix_fmt_name(*fmt_ptr));
759                         return *fmt_ptr;
760                 }
761         }
762
763         // Fallback: Just return anything. (Should never really happen.)
764         return fmt[0];
765 }
766
767 AVFrameWithDeleter VideoWidget::decode_frame(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx,
768         const std::string &pathname, int video_stream_index,
769         bool *error)
770 {
771         *error = false;
772
773         if (!queued_frames.empty()) {
774                 AVFrameWithDeleter frame = std::move(queued_frames.front());
775                 queued_frames.pop_front();
776                 return frame;
777         }
778
779         // Read packets until we have a frame or there are none left.
780         bool frame_finished = false;
781         AVFrameWithDeleter video_avframe = av_frame_alloc_unique();
782         bool eof = false;
783         do {
784                 AVPacket *pkt = av_packet_alloc();
785                 unique_ptr<AVPacket, decltype(av_packet_unref)*> pkt_cleanup(
786                         pkt, av_packet_unref);
787                 pkt->data = nullptr;
788                 pkt->size = 0;
789                 if (av_read_frame(format_ctx, pkt) == 0) {
790                         if (pkt->stream_index == video_stream_index) {
791                                 if (avcodec_send_packet(video_codec_ctx, pkt) < 0) {
792                                         fprintf(stderr, "%s: Cannot send packet to video codec.\n", pathname.c_str());
793                                         *error = true;
794                                         return AVFrameWithDeleter(nullptr);
795                                 }
796                         }
797                 } else {
798                         eof = true;  // Or error, but ignore that for the time being.
799                 }
800
801                 // Decode video, if we have a frame.
802                 int err = avcodec_receive_frame(video_codec_ctx, video_avframe.get());
803                 if (err == 0) {
804                         frame_finished = true;
805                         break;
806                 } else if (err != AVERROR(EAGAIN)) {
807                         fprintf(stderr, "%s: Cannot receive frame from video codec.\n", pathname.c_str());
808                         *error = true;
809                         return AVFrameWithDeleter(nullptr);
810                 }
811         } while (!eof);
812
813         if (frame_finished)
814                 return video_avframe;
815         else
816                 return AVFrameWithDeleter(nullptr);
817 }
818
819 int find_stream_index(AVFormatContext *ctx, AVMediaType media_type)
820 {
821         for (unsigned i = 0; i < ctx->nb_streams; ++i) {
822                 if (ctx->streams[i]->codecpar->codec_type == media_type) {
823                         return i;
824                 }
825         }
826         return -1;
827 }
828
829 steady_clock::time_point compute_frame_start(int64_t frame_pts, int64_t pts_origin, const AVRational &video_timebase, const steady_clock::time_point &origin, double rate)
830 {
831         const duration<double> pts((frame_pts - pts_origin) * double(video_timebase.num) / double(video_timebase.den));
832         return origin + duration_cast<steady_clock::duration>(pts / rate);
833 }
834
835 bool VideoWidget::play_video(const string &pathname)
836 {
837         queued_frames.clear();
838         AVFormatContextWithCloser format_ctx = avformat_open_input_unique(pathname.c_str(), /*fmt=*/nullptr,
839                 /*options=*/nullptr);
840         if (format_ctx == nullptr) {
841                 fprintf(stderr, "%s: Error opening file\n", pathname.c_str());
842                 return false;
843         }
844
845         if (avformat_find_stream_info(format_ctx.get(), nullptr) < 0) {
846                 fprintf(stderr, "%s: Error finding stream info\n", pathname.c_str());
847                 return false;
848         }
849
850         int video_stream_index = find_stream_index(format_ctx.get(), AVMEDIA_TYPE_VIDEO);
851         if (video_stream_index == -1) {
852                 fprintf(stderr, "%s: No video stream found\n", pathname.c_str());
853                 return false;
854         }
855
856         // Open video decoder.
857         const AVCodecParameters *video_codecpar = format_ctx->streams[video_stream_index]->codecpar;
858         const AVCodec *video_codec = avcodec_find_decoder(video_codecpar->codec_id);
859
860         video_timebase = format_ctx->streams[video_stream_index]->time_base;
861         AVCodecContextWithDeleter video_codec_ctx = avcodec_alloc_context3_unique(nullptr);
862         if (avcodec_parameters_to_context(video_codec_ctx.get(), video_codecpar) < 0) {
863                 fprintf(stderr, "%s: Cannot fill video codec parameters\n", pathname.c_str());
864                 return false;
865         }
866         if (video_codec == nullptr) {
867                 fprintf(stderr, "%s: Cannot find video decoder\n", pathname.c_str());
868                 return false;
869         }
870
871         // Seemingly, it's not too easy to make something that just initializes
872         // “whatever goes”, so we don't get CUDA or VULKAN or whatever here
873         // without enumerating through several different types.
874         // VA-API and VDPAU will do for now. We prioritize VDPAU for the
875         // simple reason that there's a VA-API-via-VDPAU emulation for NVidia
876         // cards that seems to work, but just hangs when trying to transfer the frame.
877         //
878         // Note that we don't actually check codec support beforehand,
879         // so if you have a low-end VDPAU device but a high-end VA-API device,
880         // you lose out on the extra codec support from the latter.
881         AVBufferRef *hw_device_ctx = nullptr;
882         if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VDPAU, nullptr, nullptr, 0) >= 0) {
883                 video_codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
884                 video_codec_ctx->get_format = get_hw_format<AV_HWDEVICE_TYPE_VDPAU>;
885         } else if (av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0) >= 0) {
886                 video_codec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
887                 video_codec_ctx->get_format = get_hw_format<AV_HWDEVICE_TYPE_VAAPI>;
888         } else {
889                 fprintf(stderr, "Failed to initialize VA-API or VDPAU for FFmpeg acceleration. Decoding video in software.\n");
890         }
891
892         if (avcodec_open2(video_codec_ctx.get(), video_codec, nullptr) < 0) {
893                 fprintf(stderr, "%s: Cannot open video decoder\n", pathname.c_str());
894                 return false;
895         }
896         unique_ptr<AVCodecContext, decltype(avcodec_close)*> video_codec_ctx_cleanup(
897                 video_codec_ctx.get(), avcodec_close);
898
899         internal_rewind();
900
901         running = RUNNING;
902
903         // Main loop.
904         int consecutive_errors = 0;
905         double rate = 1.0;
906         while (!producer_thread_should_quit.should_quit()) {
907                 if (process_queued_commands(format_ctx.get(), video_codec_ctx.get(), video_stream_index, /*seeked=*/nullptr)) {
908                         return true;
909                 }
910                 if (paused) {
911                         producer_thread_should_quit.sleep_for(hours(1));
912                         continue;
913                 }
914
915                 bool error;
916                 AVFrameWithDeleter frame = decode_frame(format_ctx.get(), video_codec_ctx.get(),
917                         pathname, video_stream_index, &error);
918                 if (error) {
919                         if (++consecutive_errors >= 100) {
920                                 fprintf(stderr, "More than 100 consecutive video frames, aborting playback.\n");
921                                 return false;
922                         } else {
923                                 continue;
924                         }
925                 } else {
926                         consecutive_errors = 0;
927                 }
928                 if (frame == nullptr) {
929                         // EOF.
930                         return false;
931                 }
932
933                 // Sleep until it's time to present this frame.
934                 for ( ;; ) {
935                         if (last_pts == 0 && pts_origin == 0) {
936                                 pts_origin = frame->pts;
937                         }
938                         steady_clock::time_point now = steady_clock::now();
939                         next_frame_start = compute_frame_start(frame->pts, pts_origin, video_timebase, start, rate);
940
941                         if (duration<double>(now - next_frame_start).count() >= 0.1) {
942                                 // If we don't have enough CPU to keep up, or if we have a live stream
943                                 // where the initial origin was somehow wrong, we could be behind indefinitely.
944                                 fprintf(stderr, "%s: Playback %.0f ms behind, resetting time scale\n",
945                                         pathname.c_str(),
946                                         1e3 * duration<double>(now - next_frame_start).count());
947                                 pts_origin = frame->pts;
948                                 start = next_frame_start = now;
949                         }
950                         bool finished_wakeup;
951                         finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
952                         if (finished_wakeup) {
953                                 shared_ptr<Frame> new_frame = make_video_frame(frame.get());
954                                 {
955                                         lock_guard lock(current_frame_mu);
956                                         current_frame = std::move(new_frame);
957                                 }
958                                 last_frame = steady_clock::now();
959                                 update();
960                                 break;
961                         } else {
962                                 if (producer_thread_should_quit.should_quit()) break;
963
964                                 bool seeked = false;
965                                 if (process_queued_commands(format_ctx.get(), video_codec_ctx.get(), video_stream_index, &seeked)) {
966                                         return true;
967                                 }
968
969                                 if (paused) {
970                                         // Just paused, so present the frame immediately and then go into deep sleep.
971                                         shared_ptr<Frame> new_frame = make_video_frame(frame.get());
972                                         {
973                                                 lock_guard lock(current_frame_mu);
974                                                 current_frame = std::move(new_frame);
975                                         }
976                                         last_frame = steady_clock::now();
977                                         update();
978                                         break;
979                                 }
980
981                                 // If we just seeked, drop this frame on the floor and be done.
982                                 if (seeked) {
983                                         break;
984                                 }
985                         }
986                 }
987                 store_pts(frame->pts);
988         }
989         return true;
990 }
991
992 void VideoWidget::store_pts(int64_t pts)
993 {
994         last_pts = pts;
995         last_position = lrint(pts * double(video_timebase.num) / double(video_timebase.den) * 1000);
996         post_to_main_thread([this, last_position{last_position.load()}] {
997                 emit position_changed(last_position);
998         });
999 }
1000
1001 // Taken from Movit (see the comment there for explanation)
1002 float compute_chroma_offset(float pos, unsigned subsampling_factor, unsigned resolution)
1003 {
1004         float local_chroma_pos = (0.5 + pos * (subsampling_factor - 1)) / subsampling_factor;
1005         if (fabs(local_chroma_pos - 0.5) < 1e-10) {
1006                 // x + (-0) can be optimized away freely, as opposed to x + 0.
1007                 return -0.0;
1008         } else {
1009                 return (0.5 - local_chroma_pos) / resolution;
1010         }
1011 }
1012
1013 shared_ptr<VideoWidget::Frame> VideoWidget::alloc_frame(unsigned width, unsigned height, unsigned chroma_width, unsigned chroma_height)
1014 {
1015         lock_guard lock(freelist_mu);
1016         for (auto it = frame_freelist.begin(); it != frame_freelist.end(); ++it) {
1017                 if ((*it)->width == width &&
1018                     (*it)->height == height &&
1019                     (*it)->chroma_width == chroma_width &&
1020                     (*it)->chroma_height == chroma_height) {
1021                         Frame *frame = *it;
1022                         frame_freelist.erase(it);
1023                         return shared_ptr<Frame>{frame, free_frame};
1024                 }
1025         }
1026
1027         Frame *frame = new Frame;
1028         frame->owner = this;
1029         frame->width = width;
1030         frame->height = height;
1031         frame->chroma_width = chroma_width;
1032         frame->chroma_height = chroma_height;
1033
1034         size_t len = frame->width * frame->height + 2 * frame->chroma_width * frame->chroma_height;
1035
1036         // Augh :-)
1037         mutex mu;
1038         condition_variable done_cv;
1039         bool done = false;
1040
1041         post_to_main_thread([this, &frame, len, &done, &mu, &done_cv]{
1042                 makeCurrent();
1043                 glCreateBuffers(1, &frame->pbo);
1044                 glNamedBufferStorage(frame->pbo, len, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
1045                 frame->data = (uint8_t *)glMapNamedBufferRange(frame->pbo, 0, len, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_PERSISTENT_BIT);
1046                 doneCurrent();
1047
1048                 lock_guard lock(mu);
1049                 done = true;
1050                 done_cv.notify_all();
1051         });
1052         {
1053                 unique_lock lock(mu);
1054                 done_cv.wait(lock, [&done]{ return done; });
1055         }
1056
1057         return shared_ptr<Frame>{frame, free_frame};
1058 }
1059
1060 void VideoWidget::free_frame(VideoWidget::Frame *frame)
1061 {
1062         VideoWidget *self = frame->owner;
1063         lock_guard lock(self->freelist_mu);
1064         if (self->frame_freelist.size() >= 16) {
1065                 GLuint pbo = frame->pbo;
1066                 post_to_main_thread([self, pbo]{
1067                         self->makeCurrent();
1068                         glUnmapNamedBuffer(pbo);
1069                         glDeleteBuffers(1, &pbo);
1070                         self->doneCurrent();
1071                 });
1072                 delete self->frame_freelist.front();
1073                 self->frame_freelist.pop_front();
1074         }
1075         self->frame_freelist.push_back(frame);
1076 }
1077
1078 shared_ptr<VideoWidget::Frame> VideoWidget::make_video_frame(const AVFrame *frame)
1079 {
1080         AVFrameWithDeleter sw_frame;
1081
1082         if (frame->format == AV_PIX_FMT_VAAPI ||
1083             frame->format == AV_PIX_FMT_VDPAU) {
1084                 // Get the frame down to the CPU. (TODO: See if we can keep it
1085                 // on the GPU all the way, since it will be going up again later.
1086                 // However, this only works if the OpenGL GPU is the same one.)
1087                 sw_frame = av_frame_alloc_unique();
1088                 int err = av_hwframe_transfer_data(sw_frame.get(), frame, 0);
1089                 if (err != 0) {
1090                         fprintf(stderr, "%s: Cannot transfer hardware video frame to software.\n", pathname.c_str());
1091                 } else {
1092                         sw_frame->pts = frame->pts;
1093                         sw_frame->pkt_duration = frame->pkt_duration;
1094                         frame = sw_frame.get();
1095                 }
1096         }
1097
1098         if (sws_ctx == nullptr ||
1099             sws_last_width != frame->width ||
1100             sws_last_height != frame->height ||
1101             sws_last_src_format != frame->format) {
1102                 sws_dst_format = decide_dst_format(AVPixelFormat(frame->format));
1103                 sws_ctx.reset(
1104                         sws_getContext(frame->width, frame->height, AVPixelFormat(frame->format),
1105                                 frame->width, frame->height, sws_dst_format,
1106                                 SWS_BICUBIC, nullptr, nullptr, nullptr));
1107                 sws_last_width = frame->width;
1108                 sws_last_height = frame->height;
1109                 sws_last_src_format = frame->format;
1110         }
1111         if (sws_ctx == nullptr) {
1112                 fprintf(stderr, "Could not create scaler context\n");
1113                 abort();
1114         }
1115
1116         uint8_t *pic_data[4] = { nullptr, nullptr, nullptr, nullptr };
1117         int linesizes[4] = { 0, 0, 0, 0 };
1118         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(sws_dst_format);
1119
1120         shared_ptr<Frame> video_frame = alloc_frame(
1121                 frame->width,
1122                 frame->height,
1123                 AV_CEIL_RSHIFT(int(frame->width), desc->log2_chroma_w),
1124                 AV_CEIL_RSHIFT(int(frame->height), desc->log2_chroma_h));
1125
1126         // We always assume left chroma placement for now.
1127         cbcr_offset[0] = compute_chroma_offset(0.0f, 1 << desc->log2_chroma_w, video_frame->chroma_width);
1128         cbcr_offset[1] = compute_chroma_offset(0.5f, 1 << desc->log2_chroma_h, video_frame->chroma_height);
1129
1130         pic_data[0] = video_frame->data;
1131         linesizes[0] = frame->width;
1132
1133         pic_data[1] = pic_data[0] + frame->width * frame->height;
1134         linesizes[1] = video_frame->chroma_width;
1135
1136         pic_data[2] = pic_data[1] + video_frame->chroma_width * video_frame->chroma_height;
1137         linesizes[2] = video_frame->chroma_width;
1138
1139         sws_scale(sws_ctx.get(), frame->data, frame->linesize, 0, frame->height, pic_data, linesizes);
1140
1141         video_frame->need_flush_len = video_frame->width * video_frame->height + 2 * video_frame->chroma_width * video_frame->chroma_height;
1142
1143         return video_frame;
1144 }
1145