]> git.sesse.net Git - pkanalytics/blob - video_widget.h
Make Frame allocated on a freelist, in preparation for PBOs.
[pkanalytics] / video_widget.h
1 #ifndef _VIDEO_WIDGET_H
2 #define _VIDEO_WIDGET_H 1
3
4 #include <QOpenGLWidget>
5 #include <string>
6 #include <atomic>
7 #include <thread>
8 #include <chrono>
9 #include <deque>
10
11 extern "C" {
12 #include <libavutil/pixfmt.h>
13 #include <libavutil/rational.h>
14 }
15
16 #include "ffmpeg_raii.h"
17 #include "quittable_sleeper.h"
18
19 // Because QVideoWidget sucks, sadly. (Don't use GStreamer, kids.)
20
21 class VideoWidget : public QOpenGLWidget {
22         Q_OBJECT
23
24 public:
25         VideoWidget(QWidget *parent);
26         ~VideoWidget() { stop(); }
27
28         bool open(const std::string &filename);  // False on error.
29         void play();
30         uint64_t get_position() const { return last_position; }  // In milliseconds.
31         void pause();
32         void stop();
33         void seek(int64_t relative_seek_ms);  // Relative seek.
34         void seek_frames(int64_t relative_frames);  // Relative seek.
35         void seek_absolute(int64_t position_ms);  // Absolute seek.
36
37         void initializeGL() override;
38         void resizeGL(int w, int h) override;
39         void paintGL() override;
40         void wheelEvent(QWheelEvent *event) override;
41
42         // For dragging, and for back/forward button presses.
43         void mousePressEvent(QMouseEvent *e);
44         void mouseReleaseEvent(QMouseEvent *e);
45         void mouseMoveEvent(QMouseEvent *e);
46
47         // Should really have a PBO, but this is OK for now.
48         // public due to shared_ptr.
49         struct Frame {
50                 unsigned width, height;
51                 unsigned chroma_width, chroma_height;
52                 std::unique_ptr<uint8_t[]> data;  // Y, followed by Cb, followed by Cr.
53                 VideoWidget *owner;  // For the freelist.
54         };
55
56 signals:
57         void position_changed(uint64_t pos);
58         void mouse_back_clicked();
59         void mouse_forward_clicked();
60
61 private:
62         std::shared_ptr<Frame> alloc_frame(unsigned width, unsigned height, unsigned chroma_width, unsigned chroma_height);
63         static void free_frame(Frame *frame);
64
65         std::mutex current_frame_mu;
66         std::shared_ptr<Frame> current_frame;  // Protected by current_frame_mu.
67         std::mutex freelist_mu;
68         std::deque<Frame *> frame_freelist;  // Protected by freelist_mu.
69         std::deque<AVFrameWithDeleter> queued_frames;  // Frames decoded but not displayed. Only used when frame-stepping backwards.
70
71         GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program;
72         GLuint bilinear_sampler;
73
74         GLuint tex[3];
75         GLuint last_width = 0, last_height = 0;
76         GLuint last_chroma_width = 0, last_chroma_height = 0;
77         GLfloat cbcr_offset[2];
78         double display_aspect = 1.0;
79         double zoom_matrix[9] = {  // Column-major, to match with OpenGL.
80                 1.0, 0.0, 0.0,
81                 0.0, 1.0, 0.0,
82                 0.0, 0.0, 1.0,
83         };
84
85         int64_t pts_origin;
86         int64_t last_pts;
87         std::atomic<uint64_t> last_position{0};  // TODO: sort of redundant wrt. last_pts (but this one can be read from the other thread)
88         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.
89         std::atomic<RunState> running{NOT_RUNNING};
90         bool paused = false;
91         std::chrono::steady_clock::time_point start, next_frame_start, last_frame;
92
93         SwsContextWithDeleter sws_ctx;
94         int sws_last_width = -1, sws_last_height = -1, sws_last_src_format = -1;
95         AVPixelFormat sws_dst_format = AVPixelFormat(-1);  // In practice, always initialized.
96         AVRational video_timebase, audio_timebase;
97
98         QuittableSleeper producer_thread_should_quit;
99         std::thread producer_thread;
100
101         std::mutex queue_mu;
102         struct QueuedCommand {
103                 enum Command { PAUSE, RESUME, SEEK, SEEK_ABSOLUTE } command;
104                 int64_t relative_seek_ms;  // For SEEK.
105                 int64_t relative_seek_frames;  // For SEEK.
106                 int64_t seek_ms;  // For SEEK_ABSOLUTE.
107         };
108         std::vector<QueuedCommand> command_queue;  // Protected by <queue_mu>.
109
110         std::string pathname;
111
112         bool dragging = false;
113         float last_drag_x, last_drag_y;
114
115         void producer_thread_func();
116         bool play_video(const std::string &pathname);
117         void internal_rewind();
118         AVFrameWithDeleter decode_frame(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx,
119                 const std::string &pathname, int video_stream_index,
120                 bool *error);
121         std::shared_ptr<Frame> make_video_frame(const AVFrame *frame);
122         bool process_queued_commands(AVFormatContext *format_ctx, AVCodecContext *video_codec_ctx, int video_stream_index, bool *seeked);
123         void store_pts(int64_t pts);
124
125         void fixup_zoom_matrix();
126 };
127
128 #endif  // !defined(_VIDEO_WIDGET_H)