From: Steinar H. Gunderson Date: Sat, 16 Jun 2018 08:36:12 +0000 (+0200) Subject: Convert Y'CbCr using Movit instead of in software with Qt. Saves a fair amount of... X-Git-Tag: 1.8.0~76^2~272 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;h=1b8e3294b89ec9131f507bbb53ee9b8ad9abafee;p=nageru Convert Y'CbCr using Movit instead of in software with Qt. Saves a fair amount of CPU. --- diff --git a/Makefile b/Makefile index 42b5ea0..beebb67 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ CXX=g++ -PKG_MODULES := Qt5Core Qt5Gui Qt5Widgets Qt5PrintSupport libjpeg +PKG_MODULES := Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL Qt5PrintSupport libjpeg movit CXXFLAGS ?= -O2 -g -Wall # Will be overridden by environment. -CXXFLAGS += -std=gnu++11 -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread +CXXFLAGS += -std=gnu++11 -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\" -pthread -LDLIBS=$(shell pkg-config --libs $(PKG_MODULES)) -pthread -lavformat -lavcodec -lavutil -lswscale +LDLIBS=$(shell pkg-config --libs $(PKG_MODULES)) -pthread -lavformat -lavcodec -lavutil -lswscale -lGL # Qt objects OBJS_WITH_MOC = mainwindow.o jpeg_frame_view.o clip_list.o diff --git a/jpeg_frame_view.cpp b/jpeg_frame_view.cpp index e7ee5f1..65fccbb 100644 --- a/jpeg_frame_view.cpp +++ b/jpeg_frame_view.cpp @@ -1,5 +1,6 @@ #include "jpeg_frame_view.h" +#include #include #include @@ -8,12 +9,15 @@ #include #include #include -#include -#include + +#include +#include +#include #include "defs.h" #include "post_to_main_thread.h" +using namespace movit; using namespace std; string filename_for_frame(unsigned stream_idx, int64_t pts); @@ -26,16 +30,85 @@ bool operator< (const JPEGID &a, const JPEGID &b) { return make_pair(a.stream_idx, a.pts) < make_pair(b.stream_idx, b.pts); } -struct LRUPixmap { - shared_ptr pixmap; +struct LRUFrame { + shared_ptr frame; size_t last_used; }; mutex cache_mu; -map cache; // Under cache_mu. +map cache; // Under cache_mu. condition_variable any_pending_decodes; deque> pending_decodes; // Under cache_mu. atomic event_counter{0}; +extern QGLWidget *global_share_widget; + +// TODO: Decode using VA-API if available. +shared_ptr decode_jpeg(const string &filename) +{ + shared_ptr frame(new Frame); + + jpeg_decompress_struct dinfo; + jpeg_error_mgr jerr; + dinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&dinfo); + + FILE *fp = fopen(filename.c_str(), "rb"); + if (fp == nullptr) { + perror(filename.c_str()); + exit(1); + } + jpeg_stdio_src(&dinfo, fp); + + jpeg_read_header(&dinfo, true); + + if (dinfo.num_components != 3 || + dinfo.comp_info[0].h_samp_factor != 2 || + dinfo.comp_info[0].v_samp_factor != 2 || + dinfo.comp_info[1].h_samp_factor != 1 || + dinfo.comp_info[1].v_samp_factor != 2 || + dinfo.comp_info[2].h_samp_factor != 1 || + dinfo.comp_info[2].v_samp_factor != 2) { + fprintf(stderr, "Not 4:2:2 JPEG! (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n", + dinfo.num_components, + dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, + dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, + dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); + exit(1); + } + dinfo.raw_data_out = true; + + jpeg_start_decompress(&dinfo); + + frame->width = dinfo.output_width; + frame->height = dinfo.output_height; + + unsigned chroma_width_blocks = (dinfo.output_width + 15) / 16; + unsigned width_blocks = chroma_width_blocks * 2; + unsigned height_blocks = (dinfo.output_height + 15) / 16; + + // TODO: Decode into a PBO. + frame->y.reset(new uint8_t[width_blocks * (height_blocks * 2) * DCTSIZE2]); + frame->cb.reset(new uint8_t[chroma_width_blocks * (height_blocks * 2) * DCTSIZE2]); + frame->cr.reset(new uint8_t[chroma_width_blocks * (height_blocks * 2) * DCTSIZE2]); + + JSAMPROW yptr[16], cbptr[16], crptr[16]; + JSAMPARRAY data[3] = { yptr, cbptr, crptr }; + for (unsigned y = 0; y < height_blocks; ++y) { + for (unsigned yy = 0; yy < DCTSIZE * 2; ++yy) { + yptr[yy] = frame->y.get() + (y * DCTSIZE * 2 + yy) * width_blocks * DCTSIZE; + cbptr[yy] = frame->cb.get() + (y * DCTSIZE * 2 + yy) * chroma_width_blocks * DCTSIZE; + crptr[yy] = frame->cr.get() + (y * DCTSIZE * 2 + yy) * chroma_width_blocks * DCTSIZE; + } + + jpeg_read_raw_data(&dinfo, data, /*num_lines=*/16); + } + + (void) jpeg_finish_decompress(&dinfo); + jpeg_destroy_decompress(&dinfo); + fclose(fp); + + return frame; +} void prune_cache() { @@ -65,7 +138,7 @@ void jpeg_decoder_thread() for ( ;; ) { JPEGID id; JPEGFrameView *dest; - shared_ptr pixmap; + shared_ptr frame; { unique_lock lock(cache_mu); any_pending_decodes.wait(lock, [] { @@ -77,12 +150,12 @@ void jpeg_decoder_thread() auto it = cache.find(id); if (it != cache.end()) { - pixmap = it->second.pixmap; + frame = it->second.frame; it->second.last_used = event_counter++; } } - if (pixmap == nullptr) { + if (frame == nullptr) { // Not found in the cache, so we need to do a decode or drop the request. // Prune the queue if there are too many pending for this destination. // TODO: Could we get starvation here? @@ -97,11 +170,10 @@ void jpeg_decoder_thread() continue; } - pixmap.reset( - new QPixmap(QString::fromStdString(filename_for_frame(id.stream_idx, id.pts)))); + frame = decode_jpeg(filename_for_frame(id.stream_idx, id.pts)); unique_lock lock(cache_mu); - cache[id] = LRUPixmap{ pixmap, event_counter++ }; + cache[id] = LRUFrame{ frame, event_counter++ }; if (cache.size() > CACHE_SIZE) { prune_cache(); @@ -113,39 +185,96 @@ void jpeg_decoder_thread() } } - dest->setPixmap(pixmap); + dest->setDecodedFrame(frame); } } JPEGFrameView::JPEGFrameView(QWidget *parent) - : QGraphicsView(parent) { - scene.addItem(&item); - setScene(&scene); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + : QGLWidget(parent, global_share_widget) { +} + +void JPEGFrameView::update_frame() +{ + unique_lock lock(cache_mu); + pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this); + any_pending_decodes.notify_all(); +} + +ResourcePool *resource_pool = nullptr; + +void JPEGFrameView::initializeGL() +{ + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + check_error(); static once_flag once; call_once(once, [] { + CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF)); + resource_pool = new ResourcePool; + std::thread(&jpeg_decoder_thread).detach(); }); + + chain.reset(new EffectChain(1280, 720, resource_pool)); + ImageFormat image_format; + image_format.color_space = COLORSPACE_sRGB; + image_format.gamma_curve = GAMMA_sRGB; + YCbCrFormat ycbcr_format; + ycbcr_format.luma_coefficients = YCBCR_REC_709; + ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; + ycbcr_format.chroma_subsampling_x = 2; + ycbcr_format.chroma_subsampling_y = 1; + ycbcr_format.cb_x_position = 0.0f; // H.264 -- _not_ JPEG, even though our input is MJPEG-encoded + ycbcr_format.cb_y_position = 0.5f; // Irrelevant. + ycbcr_format.cr_x_position = 0.0f; + ycbcr_format.cr_y_position = 0.5f; + ycbcr_input = (movit::YCbCrInput *)chain->add_input(new YCbCrInput(image_format, ycbcr_format, 1280, 720)); + + ImageFormat inout_format; + inout_format.color_space = COLORSPACE_sRGB; + inout_format.gamma_curve = GAMMA_sRGB; + + check_error(); + chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); + check_error(); + chain->set_dither_bits(8); + check_error(); + chain->finalize(); + check_error(); } -void JPEGFrameView::update_frame() +void JPEGFrameView::resizeGL(int width, int height) { - unique_lock lock(cache_mu); - pending_decodes.emplace_back(JPEGID{ stream_idx, pts }, this); - any_pending_decodes.notify_all(); + check_error(); + glViewport(0, 0, width, height); + check_error(); } -void JPEGFrameView::resizeEvent(QResizeEvent *event) +void JPEGFrameView::paintGL() { - fitInView(&item, Qt::KeepAspectRatio); + //glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + check_error(); + chain->render_to_screen(); } -void JPEGFrameView::setPixmap(std::shared_ptr pixmap) +void JPEGFrameView::setDecodedFrame(std::shared_ptr frame) { - post_to_main_thread([this, pixmap] { - item.setPixmap(*pixmap); - fitInView(&item, Qt::KeepAspectRatio); + post_to_main_thread([this, frame] { + current_frame = frame; + int width_blocks = (frame->width + 15) / 16; + ycbcr_input->set_width(frame->width); + ycbcr_input->set_height(frame->height); + ycbcr_input->set_pixel_data(0, frame->y.get()); + ycbcr_input->set_pixel_data(1, frame->cb.get()); + ycbcr_input->set_pixel_data(2, frame->cr.get()); + ycbcr_input->set_pitch(0, width_blocks * 16); + ycbcr_input->set_pitch(1, width_blocks * 8); + ycbcr_input->set_pitch(2, width_blocks * 8); + update(); }); } diff --git a/jpeg_frame_view.h b/jpeg_frame_view.h index e5665cd..4341819 100644 --- a/jpeg_frame_view.h +++ b/jpeg_frame_view.h @@ -1,15 +1,22 @@ #ifndef _JPEG_FRAME_VIEW_H #define _JPEG_FRAME_VIEW_H 1 -#include -#include -#include +#include +#include #include +#include +#include + #include -class JPEGFrameView : public QGraphicsView { +struct Frame { + std::unique_ptr y, cb, cr; + unsigned width, height; +}; + +class JPEGFrameView : public QGLWidget { Q_OBJECT public: @@ -22,20 +29,22 @@ public: update_frame(); } - void setPixmap(std::shared_ptr pixmap); + void setDecodedFrame(std::shared_ptr frame); protected: - void resizeEvent(QResizeEvent *event) override; + void initializeGL() override; + void resizeGL(int width, int height) override; + void paintGL() override; private: void update_frame(); - QGraphicsPixmapItem item; - QGraphicsScene scene; - unsigned stream_idx; int64_t pts; - bool dirty = false; + + std::unique_ptr chain; + std::shared_ptr current_frame; // So that we hold on to the pixels. + movit::YCbCrInput *ycbcr_input; }; #endif // !defined(_JPEG_FRAME_VIEW_H) diff --git a/main.cpp b/main.cpp index d916f44..0f1e6fa 100644 --- a/main.cpp +++ b/main.cpp @@ -39,6 +39,7 @@ string filename_for_frame(unsigned stream_idx, int64_t pts) mutex frame_mu; vector frames[MAX_STREAMS]; +QGLWidget *global_share_widget; int record_thread_func(); @@ -47,7 +48,29 @@ int main(int argc, char **argv) av_register_all(); avformat_network_init(); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); + + QSurfaceFormat fmt; + fmt.setDepthBufferSize(0); + fmt.setStencilBufferSize(0); + fmt.setProfile(QSurfaceFormat::CoreProfile); + fmt.setMajorVersion(3); + fmt.setMinorVersion(1); + + // Turn off vsync, since Qt generally gives us at most frame rate + // (display frequency) / (number of QGLWidgets active). + fmt.setSwapInterval(0); + + QSurfaceFormat::setDefaultFormat(fmt); + + QGLFormat::setDefaultFormat(QGLFormat::fromSurfaceFormat(fmt)); + QApplication app(argc, argv); + global_share_widget = new QGLWidget(); + if (!global_share_widget->isValid()) { + fprintf(stderr, "Failed to initialize OpenGL. Futatabi needs at least OpenGL 3.1 to function properly.\n"); + exit(1); + } MainWindow mainWindow; mainWindow.show(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 84231b7..9a2c5f1 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -133,3 +133,17 @@ void MainWindow::live_player_clip_done() playlist_clips->set_currently_playing(-1); } } + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + + // Ask for a relayout, but only after the event loop is done doing relayout + // on everything else. + QMetaObject::invokeMethod(this, "relayout", Qt::QueuedConnection); +} + +void MainWindow::relayout() +{ + ui->live_display->setMinimumHeight(ui->live_display->width() * 9 / 16); +} diff --git a/mainwindow.h b/mainwindow.h index c037c77..4d09cb3 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -28,6 +28,11 @@ private: void preview_clicked(); void play_clicked(); void live_player_clip_done(); + + void resizeEvent(QResizeEvent *event) override; + +private slots: + void relayout(); }; extern MainWindow *global_mainwindow; diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 7e51adc..c3597d9 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -23,20 +23,20 @@ - + - + - + - + - + @@ -53,9 +53,9 @@ - + - + @@ -70,9 +70,9 @@ - + - + @@ -152,7 +152,7 @@ JPEGFrameView - QGraphicsView + QWidget
jpeg_frame_view.h