1 #ifndef _VIDEO_WIDGET_H
2 #define _VIDEO_WIDGET_H 1
5 #include <QOpenGLWindow>
13 #include <libavutil/pixfmt.h>
14 #include <libavutil/rational.h>
17 #include "ffmpeg_raii.h"
18 #include "quittable_sleeper.h"
20 class QOpenGLFunctions_4_5_Compatibility;
22 // Because QVideoWidget sucks, sadly. (Don't use GStreamer, kids.)
26 class VideoWidget : public QWidget {
30 VideoWidget(QWidget *parent);
33 bool open(const std::string &filename); // False on error.
35 uint64_t get_position() const { return last_position; } // In milliseconds.
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.
42 void wheelEvent(QWheelEvent *event) override;
44 // For dragging, and for back/forward button presses.
45 void mousePressEvent(QMouseEvent *e);
46 void mouseReleaseEvent(QMouseEvent *e);
47 void mouseMoveEvent(QMouseEvent *e);
49 // Public due to shared_ptr.
51 unsigned width, height;
52 unsigned chroma_width, chroma_height;
53 VideoWidget *owner; // For the freelist.
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.
60 void position_changed(uint64_t pos);
61 void mouse_back_clicked();
62 void mouse_forward_clicked();
65 std::shared_ptr<Frame> alloc_frame(unsigned width, unsigned height, unsigned chroma_width, unsigned chroma_height);
66 static void free_frame(Frame *frame);
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.
74 double zoom_matrix[9] = { // Column-major, to match with OpenGL.
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};
86 std::chrono::steady_clock::time_point start, next_frame_start, last_frame;
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;
93 QuittableSleeper producer_thread_should_quit;
94 std::thread producer_thread;
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.
103 std::vector<QueuedCommand> command_queue; // Protected by <queue_mu>.
105 std::string pathname;
107 bool dragging = false;
108 float last_drag_x, last_drag_y;
110 VideoWindow *video_window;
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,
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);
122 void fixup_zoom_matrix();
125 class VideoWindow : public QOpenGLWindow { // Private implementation of VideoWidget, but moc does not support nested classes.
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?
139 void set_zoom_matrix(double new_zoom_matrix[9]) {
140 memcpy(zoom_matrix, new_zoom_matrix, sizeof(zoom_matrix));
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); }
150 void mouse_wheel(QWheelEvent *e);
151 void mouse_pressed(QMouseEvent *e);
152 void mouse_released(QMouseEvent *e);
153 void mouse_moved(QMouseEvent *e);
156 QOpenGLFunctions_4_5_Compatibility *gl = nullptr;
158 GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program;
159 GLuint bilinear_sampler;
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];
166 double zoom_matrix[9] = { // Column-major, to match with OpenGL.
172 std::mutex current_frame_mu;
173 std::shared_ptr<VideoWidget::Frame> current_frame; // Protected by current_frame_mu.
176 #endif // !defined(_VIDEO_WIDGET_H)