]> git.sesse.net Git - pkanalytics/blob - video_widget.h
Support filtering passes by thrower and receiver.
[pkanalytics] / video_widget.h
1 #ifndef _VIDEO_WIDGET_H
2 #define _VIDEO_WIDGET_H 1
3
4 #include <QWidget>
5 #include <QOpenGLWindow>
6 #include <string>
7 #include <atomic>
8 #include <thread>
9 #include <chrono>
10 #include <deque>
11
12 extern "C" {
13 #include <libavutil/pixfmt.h>
14 #include <libavutil/rational.h>
15 }
16
17 #include "ffmpeg_raii.h"
18 #include "quittable_sleeper.h"
19
20 class QOpenGLFunctions_4_5_Compatibility;
21
22 // Because QVideoWidget sucks, sadly. (Don't use GStreamer, kids.)
23
24 class VideoWindow;
25
26 class VideoWidget : public QWidget {
27         Q_OBJECT
28
29 public:
30         VideoWidget(QWidget *parent);
31         ~VideoWidget();
32
33         bool open(const std::string &filename);  // False on error.
34         void play();
35         uint64_t get_position() const { return last_position; }  // In milliseconds.
36         void pause();
37         void stop();
38         void seek(int64_t relative_seek_ms);  // Relative seek.
39         void seek_frames(int64_t relative_frames);  // Relative seek.
40         void seek_absolute(int64_t position_ms);  // Absolute seek.
41
42         void wheelEvent(QWheelEvent *event) override;
43
44         // For dragging, and for back/forward button presses.
45         void mousePressEvent(QMouseEvent *e);
46         void mouseReleaseEvent(QMouseEvent *e);
47         void mouseMoveEvent(QMouseEvent *e);
48
49         // Public due to shared_ptr.
50         struct Frame {
51                 unsigned width, height;
52                 unsigned chroma_width, chroma_height;
53                 VideoWidget *owner;  // For the freelist.
54                 GLuint pbo;
55                 uint8_t *data;  // Persistently mapped into the PBO. Y, followed by Cb, followed by Cr.
56                 size_t need_flush_len;  // 0 = no flush needed.
57         };
58
59 signals:
60         void position_changed(uint64_t pos);
61         void mouse_back_clicked();
62         void mouse_forward_clicked();
63
64 private:
65         std::shared_ptr<Frame> alloc_frame(unsigned width, unsigned height, unsigned chroma_width, unsigned chroma_height);
66         static void free_frame(Frame *frame);
67
68         std::mutex current_frame_mu;
69         std::shared_ptr<Frame> current_frame;  // Protected by current_frame_mu.
70         std::mutex freelist_mu;
71         std::deque<Frame *> frame_freelist;  // Protected by freelist_mu.
72         std::deque<AVFrameWithDeleter> queued_frames;  // Frames decoded but not displayed. Only used when frame-stepping backwards.
73
74         double zoom_matrix[9] = {  // Column-major, to match with OpenGL.
75                 1.0, 0.0, 0.0,
76                 0.0, 1.0, 0.0,
77                 0.0, 0.0, 1.0,
78         };
79
80         int64_t pts_origin;
81         int64_t last_pts;
82         std::atomic<uint64_t> last_position{0};  // TODO: sort of redundant wrt. last_pts (but this one can be read from the other thread)
83         enum RunState { NOT_RUNNING, STARTING, RUNNING, VIDEO_FILE_ERROR };  // NOT_RUNNING and VIDEO_FILE_ERROR both imply that the thread isn't running and can freely be restarted.
84         std::atomic<RunState> running{NOT_RUNNING};
85         bool paused = false;
86         std::chrono::steady_clock::time_point start, next_frame_start, last_frame;
87
88         SwsContextWithDeleter sws_ctx;
89         int sws_last_width = -1, sws_last_height = -1, sws_last_src_format = -1;
90         AVPixelFormat sws_dst_format = AVPixelFormat(-1);  // In practice, always initialized.
91         AVRational video_timebase, audio_timebase;
92
93         QuittableSleeper producer_thread_should_quit;
94         std::thread producer_thread;
95
96         std::mutex queue_mu;
97         struct QueuedCommand {
98                 enum Command { PAUSE, RESUME, SEEK, SEEK_ABSOLUTE } command;
99                 int64_t relative_seek_ms;  // For SEEK.
100                 int64_t relative_seek_frames;  // For SEEK.
101                 int64_t seek_ms;  // For SEEK_ABSOLUTE.
102         };
103         std::vector<QueuedCommand> command_queue;  // Protected by <queue_mu>.
104
105         std::string pathname;
106
107         bool dragging = false;
108         float last_drag_x, last_drag_y;
109
110         VideoWindow *video_window;
111
112         void producer_thread_func();
113         bool play_video(const std::string &pathname);
114         void internal_rewind();
115         AVFrameWithDeleter decode_frame(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx,
116                 const std::string &pathname, int video_stream_index,
117                 bool *error);
118         std::shared_ptr<Frame> make_video_frame(const AVFrame *frame);
119         bool process_queued_commands(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx, int video_stream_index, bool *seeked);
120         void store_pts(int64_t pts);
121
122         void fixup_zoom_matrix();
123 };
124
125 class VideoWindow : public QOpenGLWindow {  // Private implementation of VideoWidget, but moc does not support nested classes.
126         Q_OBJECT;
127
128 public:
129         VideoWindow(VideoWidget *video) : video(video) {}
130         void initializeGL() override;
131         void resizeGL(int w, int h) override;
132         void paintGL() override;
133         void set_current_frame(std::shared_ptr<VideoWidget::Frame> new_frame);  // Takes ownership. Thread-safe.
134         void set_cbcr_offset(float x, float y)  // FIXME: Thread-safe?
135         {
136                 cbcr_offset[0] = x;
137                 cbcr_offset[1] = y;
138         }
139         void set_zoom_matrix(double new_zoom_matrix[9]) {
140                 memcpy(zoom_matrix, new_zoom_matrix, sizeof(zoom_matrix));
141                 update();
142         }
143
144         void wheelEvent(QWheelEvent *e) override { emit mouse_wheel(e); }
145         void mousePressEvent(QMouseEvent *e) override { emit mouse_pressed(e); }
146         void mouseReleaseEvent(QMouseEvent *e) override { emit mouse_released(e); }
147         void mouseMoveEvent(QMouseEvent *e) override { emit mouse_moved(e); }
148
149 signals:
150         void mouse_wheel(QWheelEvent *e);
151         void mouse_pressed(QMouseEvent *e);
152         void mouse_released(QMouseEvent *e);
153         void mouse_moved(QMouseEvent *e);
154
155 private:
156         QOpenGLFunctions_4_5_Compatibility *gl = nullptr;
157         VideoWidget *video;
158         GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program;
159         GLuint bilinear_sampler;
160         GLuint tex[3];
161         GLuint last_width = 0, last_height = 0;
162         GLuint last_chroma_width = 0, last_chroma_height = 0;
163         double display_aspect = 1.0;
164         GLfloat cbcr_offset[2];
165
166         double zoom_matrix[9] = {  // Column-major, to match with OpenGL.
167                 1.0, 0.0, 0.0,
168                 0.0, 1.0, 0.0,
169                 0.0, 0.0, 1.0,
170         };
171
172         std::mutex current_frame_mu;
173         std::shared_ptr<VideoWidget::Frame> current_frame;  // Protected by current_frame_mu.
174 };
175
176 #endif  // !defined(_VIDEO_WIDGET_H)