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