#include <QOpenGLFunctions>
#include <QWheelEvent>
#include <QMouseEvent>
+#include <QMouseEvent>
+#include <QHBoxLayout>
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
queue.pop_front();
queued_frames = std::move(queue);
}
- shared_ptr<Frame> new_frame = make_video_frame(frame.get());
- {
- lock_guard lock(current_frame_mu);
- current_frame = std::move(new_frame);
- }
+ video_window->set_current_frame(make_video_frame(frame.get()));
update();
store_pts(frame->pts);
break;
if (frame == nullptr || error) {
return true;
}
- shared_ptr<Frame> new_frame = make_video_frame(frame.get());
- {
- lock_guard lock(current_frame_mu);
- current_frame = std::move(new_frame);
- }
+ video_window->set_current_frame(make_video_frame(frame.get()));
update();
store_pts(frame->pts);
}
}
VideoWidget::VideoWidget(QWidget *parent)
- : QOpenGLWidget(parent) {}
+ : QWidget(parent),
+ video_window(new VideoWindow(this)) {
+ setLayout(new QHBoxLayout);
+ layout()->addWidget(QWidget::createWindowContainer(video_window));
+
+ // ...
+ connect(video_window, &VideoWindow::mouse_wheel, this, &VideoWidget::wheelEvent);
+ connect(video_window, &VideoWindow::mouse_pressed, this, &VideoWidget::mousePressEvent);
+ connect(video_window, &VideoWindow::mouse_released, this, &VideoWidget::mouseReleaseEvent);
+ connect(video_window, &VideoWindow::mouse_moved, this, &VideoWidget::mouseMoveEvent);
+}
GLuint compile_shader(const string &shader_src, GLenum type)
{
return obj;
}
-void VideoWidget::initializeGL()
+void VideoWindow::initializeGL()
{
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glSamplerParameteri(bilinear_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
-void VideoWidget::resizeGL(int w, int h)
+void VideoWindow::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
display_aspect = double(w) / h;
return levels;
}
-void VideoWidget::paintGL()
+void VideoWindow::paintGL()
{
- std::shared_ptr<Frame> frame;
+ std::shared_ptr<VideoWidget::Frame> frame;
{
lock_guard lock(current_frame_mu);
frame = current_frame;
glEnd();
}
+void VideoWindow::set_current_frame(shared_ptr<VideoWidget::Frame> new_frame)
+{
+ {
+ lock_guard lock(current_frame_mu);
+ current_frame = std::move(new_frame);
+ }
+ update();
+}
+
void matmul3x3(const double a[9], const double b[9], double res[9])
{
for (int i = 0; i < 3; ++i) {
matmul3x3(tmp2, translation_matrix, zoom_matrix);
fixup_zoom_matrix();
+ video_window->set_zoom_matrix(zoom_matrix);
update();
}
zoom_matrix[6] += dx;
zoom_matrix[7] -= dy;
fixup_zoom_matrix();
+ video_window->set_zoom_matrix(zoom_matrix);
last_drag_x = e->position().x();
last_drag_y = e->position().y();
bool finished_wakeup;
finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
if (finished_wakeup) {
- shared_ptr<Frame> new_frame = make_video_frame(frame.get());
- {
- lock_guard lock(current_frame_mu);
- current_frame = std::move(new_frame);
- }
+ video_window->set_current_frame(make_video_frame(frame.get()));
last_frame = steady_clock::now();
update();
break;
if (paused) {
// Just paused, so present the frame immediately and then go into deep sleep.
- shared_ptr<Frame> new_frame = make_video_frame(frame.get());
- {
- lock_guard lock(current_frame_mu);
- current_frame = std::move(new_frame);
- }
+ video_window->set_current_frame(make_video_frame(frame.get()));
last_frame = steady_clock::now();
update();
break;
size_t len = frame->width * frame->height + 2 * frame->chroma_width * frame->chroma_height;
+ while (!video_window->isValid()) {
+ usleep(100000);
+ }
+
// Augh :-)
mutex mu;
condition_variable done_cv;
bool done = false;
post_to_main_thread([this, &frame, len, &done, &mu, &done_cv]{
- makeCurrent();
+ video_window->makeCurrent();
glCreateBuffers(1, &frame->pbo);
glNamedBufferStorage(frame->pbo, len, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT);
frame->data = (uint8_t *)glMapNamedBufferRange(frame->pbo, 0, len, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_PERSISTENT_BIT);
- doneCurrent();
+ video_window->doneCurrent();
lock_guard lock(mu);
done = true;
if (self->frame_freelist.size() >= 16) {
GLuint pbo = frame->pbo;
post_to_main_thread([self, pbo]{
- self->makeCurrent();
+ self->video_window->makeCurrent();
glUnmapNamedBuffer(pbo);
glDeleteBuffers(1, &pbo);
- self->doneCurrent();
+ self->video_window->doneCurrent();
});
delete self->frame_freelist.front();
self->frame_freelist.pop_front();
AV_CEIL_RSHIFT(int(frame->height), desc->log2_chroma_h));
// We always assume left chroma placement for now.
- cbcr_offset[0] = compute_chroma_offset(0.0f, 1 << desc->log2_chroma_w, video_frame->chroma_width);
- cbcr_offset[1] = compute_chroma_offset(0.5f, 1 << desc->log2_chroma_h, video_frame->chroma_height);
+ video_window->set_cbcr_offset(
+ compute_chroma_offset(0.0f, 1 << desc->log2_chroma_w, video_frame->chroma_width),
+ compute_chroma_offset(0.5f, 1 << desc->log2_chroma_h, video_frame->chroma_height)
+ );
pic_data[0] = video_frame->data;
linesizes[0] = frame->width;
#ifndef _VIDEO_WIDGET_H
#define _VIDEO_WIDGET_H 1
-#include <QOpenGLWidget>
+#include <QWidget>
+#include <QOpenGLWindow>
#include <string>
#include <atomic>
#include <thread>
// Because QVideoWidget sucks, sadly. (Don't use GStreamer, kids.)
-class VideoWidget : public QOpenGLWidget {
+class VideoWindow;
+
+class VideoWidget : public QWidget {
Q_OBJECT
public:
void seek_frames(int64_t relative_frames); // Relative seek.
void seek_absolute(int64_t position_ms); // Absolute seek.
- void initializeGL() override;
- void resizeGL(int w, int h) override;
- void paintGL() override;
void wheelEvent(QWheelEvent *event) override;
// For dragging, and for back/forward button presses.
std::deque<Frame *> frame_freelist; // Protected by freelist_mu.
std::deque<AVFrameWithDeleter> queued_frames; // Frames decoded but not displayed. Only used when frame-stepping backwards.
- GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program;
- GLuint bilinear_sampler;
-
- GLuint tex[3];
- GLuint last_width = 0, last_height = 0;
- GLuint last_chroma_width = 0, last_chroma_height = 0;
- GLfloat cbcr_offset[2];
- double display_aspect = 1.0;
double zoom_matrix[9] = { // Column-major, to match with OpenGL.
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
bool dragging = false;
float last_drag_x, last_drag_y;
+ VideoWindow *video_window;
+
void producer_thread_func();
bool play_video(const std::string &pathname);
void internal_rewind();
void fixup_zoom_matrix();
};
+class VideoWindow : public QOpenGLWindow { // Private implementation of VideoWidget, but moc does not support nested classes.
+ Q_OBJECT;
+
+public:
+ VideoWindow(VideoWidget *video) : video(video) {}
+ void initializeGL() override;
+ void resizeGL(int w, int h) override;
+ void paintGL() override;
+ void set_current_frame(std::shared_ptr<VideoWidget::Frame> new_frame); // Takes ownership. Thread-safe.
+ void set_cbcr_offset(float x, float y) // FIXME: Thread-safe?
+ {
+ cbcr_offset[0] = x;
+ cbcr_offset[1] = y;
+ }
+ void set_zoom_matrix(double new_zoom_matrix[9]) {
+ memcpy(zoom_matrix, new_zoom_matrix, sizeof(zoom_matrix));
+ update();
+ }
+
+ void wheelEvent(QWheelEvent *e) override { emit mouse_wheel(e); }
+ void mousePressEvent(QMouseEvent *e) override { emit mouse_pressed(e); }
+ void mouseReleaseEvent(QMouseEvent *e) override { emit mouse_released(e); }
+ void mouseMoveEvent(QMouseEvent *e) override { emit mouse_moved(e); }
+
+signals:
+ void mouse_wheel(QWheelEvent *e);
+ void mouse_pressed(QMouseEvent *e);
+ void mouse_released(QMouseEvent *e);
+ void mouse_moved(QMouseEvent *e);
+
+private:
+ VideoWidget *video;
+ GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program;
+ GLuint bilinear_sampler;
+ GLuint tex[3];
+ GLuint last_width = 0, last_height = 0;
+ GLuint last_chroma_width = 0, last_chroma_height = 0;
+ double display_aspect = 1.0;
+ GLfloat cbcr_offset[2];
+
+ double zoom_matrix[9] = { // Column-major, to match with OpenGL.
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0,
+ };
+
+ std::mutex current_frame_mu;
+ std::shared_ptr<VideoWidget::Frame> current_frame; // Protected by current_frame_mu.
+};
+
#endif // !defined(_VIDEO_WIDGET_H)