From 76a3d67f5ea32891d3ebb23c02be165a0b9d2fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 10 Jun 2019 20:55:42 +0200 Subject: [PATCH] Very dirty Video Display --- cmake/FindFFMPEG.cmake | 193 +++++++++++++++++++++++++++++++++++++ cmake/FindQtAV.cmake | 56 ----------- gui/CMakeLists.txt | 15 ++- gui/include/streamwindow.h | 82 +--------------- gui/include/videodecoder.h | 52 ++++++++++ gui/src/main.cpp | 32 ++++-- gui/src/streamwindow.cpp | 39 +++----- gui/src/videodecoder.cpp | 101 +++++++++++++++++++ 8 files changed, 391 insertions(+), 179 deletions(-) create mode 100644 cmake/FindFFMPEG.cmake delete mode 100644 cmake/FindQtAV.cmake create mode 100644 gui/include/videodecoder.h create mode 100644 gui/src/videodecoder.cpp diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake new file mode 100644 index 0000000..4587845 --- /dev/null +++ b/cmake/FindFFMPEG.cmake @@ -0,0 +1,193 @@ +#[==[ +Provides the following variables: + + * `FFMPEG_INCLUDE_DIRS`: Include directories necessary to use FFMPEG. + * `FFMPEG_LIBRARIES`: Libraries necessary to use FFMPEG. Note that this only + includes libraries for the components requested. + * `FFMPEG_VERSION`: The version of FFMPEG found. + +The following components are supported: + + * `avcodec` + * `avdevice` + * `avfilter` + * `avformat` + * `avresample` + * `avutil` + * `swresample` + * `swscale` + +For each component, the following are provided: + + * `FFMPEG__FOUND`: Libraries for the component. + * `FFMPEG__INCLUDE_DIRS`: Include directories for + the component. + * `FFMPEG__LIBRARIES`: Libraries for the component. + * `FFMPEG::`: A target to use with `target_link_libraries`. + +Note that only components requested with `COMPONENTS` or `OPTIONAL_COMPONENTS` +are guaranteed to set these variables or provide targets. +#]==] + +function (_ffmpeg_find component headername) + find_path("FFMPEG_${component}_INCLUDE_DIR" + NAMES + "lib${component}/${headername}" + PATHS + "${FFMPEG_ROOT}/include" + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include + /usr/include + /sw/include # Fink + /opt/local/include # DarwinPorts + /opt/csw/include # Blastwave + /opt/include + /usr/freeware/include + PATH_SUFFIXES + ffmpeg + DOC "FFMPEG's ${component} include directory") + mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR") + + # On Windows, static FFMPEG is sometimes built as `lib.a`. + if (WIN32) + list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib") + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib") + endif () + + find_library("FFMPEG_${component}_LIBRARY" + NAMES + "${component}" + PATHS + "${FFMPEG_ROOT}/lib" + ~/Library/Frameworks + /Library/Frameworks + /usr/local/lib + /usr/local/lib64 + /usr/lib + /usr/lib64 + /sw/lib + /opt/local/lib + /opt/csw/lib + /opt/lib + /usr/freeware/lib64 + "${FFMPEG_ROOT}/bin" + DOC "FFMPEG's ${component} library") + mark_as_advanced("FFMPEG_${component}_LIBRARY") + + if (FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR) + set(_deps_found TRUE) + set(_deps_link) + foreach (_ffmpeg_dep IN LISTS ARGN) + if (TARGET "FFMPEG::${_ffmpeg_dep}") + list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}") + else () + set(_deps_found FALSE) + endif () + endforeach () + if (_deps_found) + add_library("FFMPEG::${component}" UNKNOWN IMPORTED) + set_target_properties("FFMPEG::${component}" PROPERTIES + IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}") + set("FFMPEG_${component}_FOUND" 1 + PARENT_SCOPE) + + set(version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h") + if (EXISTS "${version_header_path}") + string(TOUPPER "${component}" component_upper) + file(STRINGS "${version_header_path}" version + REGEX "#define *LIB${component_upper}_VERSION_(MAJOR|MINOR|MICRO) ") + string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${version}") + string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}") + string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}") + if (NOT major STREQUAL "" AND + NOT minor STREQUAL "" AND + NOT micro STREQUAL "") + set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}" + PARENT_SCOPE) + endif () + endif () + else () + set("FFMPEG_${component}_FOUND" 0 + PARENT_SCOPE) + set(what) + if (NOT FFMPEG_${component}_LIBRARY) + set(what "library") + endif () + if (NOT FFMPEG_${component}_INCLUDE_DIR) + if (what) + string(APPEND what " or headers") + else () + set(what "headers") + endif () + endif () + set("FFMPEG_${component}_NOT_FOUND_MESSAGE" + "Could not find the ${what} for ${component}." + PARENT_SCOPE) + endif () + endif () +endfunction () + +_ffmpeg_find(avutil avutil.h) +_ffmpeg_find(avresample avresample.h + avutil) +_ffmpeg_find(swresample swresample.h + avutil) +_ffmpeg_find(swscale swscale.h + avutil) +_ffmpeg_find(avcodec avcodec.h + avutil) +_ffmpeg_find(avformat avformat.h + avcodec avutil) +_ffmpeg_find(avfilter avfilter.h + avutil) +_ffmpeg_find(avdevice avdevice.h + avformat avutil) + +if (TARGET FFMPEG::avutil) + set(_ffmpeg_version_header_path "${FFMPEG_avutil_INCLUDE_DIR}/libavutil/ffversion.h") + if (EXISTS "${_ffmpeg_version_header_path}") + file(STRINGS "${_ffmpeg_version_header_path}" _ffmpeg_version + REGEX "FFMPEG_VERSION") + string(REGEX REPLACE ".*\"n?\(.*\)\"" "\\1" FFMPEG_VERSION "${_ffmpeg_version}") + unset(_ffmpeg_version) + else () + set(FFMPEG_VERSION FFMPEG_VERSION-NOTFOUND) + endif () + unset(_ffmpeg_version_header_path) +endif () + +set(FFMPEG_INCLUDE_DIRS) +set(FFMPEG_LIBRARIES) +set(_ffmpeg_required_vars) +foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS) + if (TARGET "FFMPEG::${_ffmpeg_component}") + set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}") + set(FFMPEG_${_ffmpeg_component}_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARY}") + list(APPEND FFMPEG_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}") + list(APPEND FFMPEG_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARIES}") + if (FFMEG_FIND_REQUIRED_${_ffmpeg_component}) + list(APPEND _ffmpeg_required_vars + "FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS" + "FFMPEG_${_ffmpeg_required_vars}_LIBRARIES") + endif () + endif () +endforeach () +unset(_ffmpeg_component) + +if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) +endif () + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFMPEG + REQUIRED_VARS FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES ${_ffmpeg_required_vars} + VERSION_VAR FFMPEG_VERSION + HANDLE_COMPONENTS) +unset(_ffmpeg_required_vars) diff --git a/cmake/FindQtAV.cmake b/cmake/FindQtAV.cmake deleted file mode 100644 index 0c4f3a5..0000000 --- a/cmake/FindQtAV.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# - Try to find the QtAV library -# -# Once done this will define -# -# QTAV_FOUND - system has libqtav -# QTAV_INCLUDE_DIRS - the libqtav include directory -# QTAV_LIBRARIES - Link these to use libqtav - -find_package(Qt5 QUIET REQUIRED NO_MODULE COMPONENTS Core) - -get_target_property(qmake Qt5::qmake LOCATION) -execute_process( - COMMAND ${qmake} -query QT_INSTALL_HEADERS - OUTPUT_VARIABLE QT_INSTALL_HEADERS - OUTPUT_STRIP_TRAILING_WHITESPACE -) -execute_process( - COMMAND ${qmake} -query QT_INSTALL_LIBS - OUTPUT_VARIABLE QT_INSTALL_LIBS - OUTPUT_STRIP_TRAILING_WHITESPACE -) - -find_path(QTAV_INCLUDE_DIR NAMES QtAV.h - HINTS ${QT_INSTALL_HEADERS} - PATH_SUFFIXES QtAV -) -find_library(QTAV_LIBRARY NAMES QtAV QtAV1 - HINTS ${QT_INSTALL_LIBS} -) - -find_path(QTAVWIDGETS_INCLUDE_DIR NAMES QtAVWidgets.h - HINTS ${QT_INSTALL_HEADERS} - PATH_SUFFIXES QtAVWidgets -) -find_library(QTAVWIDGETS_LIBRARY NAMES QtAVWidgets QtAVWidgets1 - HINTS ${QT_INSTALL_LIBS} -) - -set(QTAV_INCLUDE_DIRS ${QTAV_INCLUDE_DIR} ${QTAV_INCLUDE_DIR}/..) -set(QTAV_LIBRARIES ${QTAV_LIBRARY}) -if(NOT QTAVWIDGETS_INCLUDE_DIR MATCHES "QTAVWIDGETS_INCLUDE_DIR-NOTFOUND") - set(QTAVWIDGETS_INCLUDE_DIRS ${QTAVWIDGETS_INCLUDE_DIR} ${QTAVWIDGETS_INCLUDE_DIR}/.. ${QTAV_INCLUDE_DIRS}) -endif() -if(NOT QTAV_LIBRARIES MATCHES "QTAV_LIBRARIES-NOTFOUND") - set(QTAVWIDGETS_LIBRARIES ${QTAVWIDGETS_LIBRARY} ${QTAV_LIBRARY}) -endif() - -find_package(PackageHandleStandardArgs REQUIRED) -find_package_handle_standard_args(QtAV REQUIRED_VARS QTAV_LIBRARIES QTAV_INCLUDE_DIRS) -mark_as_advanced(QTAV_INCLUDE_DIRS QTAV_LIBRARIES QTAVWIDGETS_INCLUDE_DIRS QTAVWIDGETS_LIBRARIES) - -message("QtAV_FOUND = ${QTAV_FOUND}") -message("QTAV_INCLUDE_DIRS = ${QTAV_INCLUDE_DIRS}") -message("QTAV_LIBRARIES = ${QTAV_LIBRARIES}") -message("QTAVWIDGETS_INCLUDE_DIRS = ${QTAVWIDGETS_INCLUDE_DIRS}") -message("QTAVWIDGETS_LIBRARIES = ${QTAVWIDGETS_LIBRARIES}") diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 021d5ac..70b5072 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -3,19 +3,18 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Multimedia OpenGL) +find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Multimedia) -find_package(QtAV REQUIRED) +find_package(FFMPEG REQUIRED COMPONENTS avcodec) add_executable(chiaki src/main.cpp include/streamwindow.h - src/streamwindow.cpp) + src/streamwindow.cpp + include/videodecoder.h + src/videodecoder.cpp) target_include_directories(chiaki PRIVATE include) target_link_libraries(chiaki chiaki-lib) - -target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::OpenGL Qt5::Multimedia) - -target_include_directories(chiaki PRIVATE ${QTAV_INCLUDE_DIRS} ${QTAVWIDGETS_INCLUDE_DIRS}) -target_link_libraries(chiaki ${QTAV_LIBRARIES} ${QTAVWIDGETS_LIBRARIES}) \ No newline at end of file +target_link_libraries(chiaki FFMPEG::avcodec) +target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Multimedia) diff --git a/gui/include/streamwindow.h b/gui/include/streamwindow.h index 7fc804c..f7122f8 100644 --- a/gui/include/streamwindow.h +++ b/gui/include/streamwindow.h @@ -20,79 +20,7 @@ #include -namespace QtAV -{ - class VideoOutput; - class AVPlayer; -} - -#include -#include -#include -#include - -class StreamRelayIODevice : public QIODevice -{ - private: - QMutex *mutex; - QByteArray buffer; - - public: - explicit StreamRelayIODevice(QObject *parent = nullptr) - : QIODevice(parent), - mutex(new QMutex(QMutex::Recursive)) - { - setOpenMode(OpenModeFlag::ReadOnly); - } - - ~StreamRelayIODevice() override = default; - - void PushSample(uint8_t *buf, size_t size) - { - { - QMutexLocker locker(mutex); - printf("push sample %zu\n", size); - buffer.append(reinterpret_cast(buf), static_cast(size)); - } - emit readyRead(); - } - - - qint64 pos() const override { return 0; } - bool open(QIODevice::OpenMode mode) override { return true; } - bool isSequential() const override { return true; } - - qint64 bytesAvailable() const override - { - QMutexLocker locker(mutex); - return buffer.size() + QIODevice::bytesAvailable(); - } - - protected: - qint64 readData(char *data, qint64 maxSize) - { - while(true) - { - { - QMutexLocker locker(mutex); - if(buffer.size() >= maxSize) - { - printf("read %lld\n", maxSize); - memcpy(data, buffer.constData(), maxSize); - buffer.remove(0, maxSize); - return maxSize; - } - } - QThread::msleep(100); - } - } - - qint64 writeData(const char *data, qint64 maxSize) - { - return -1; - } -}; - +class QLabel; class StreamWindow: public QMainWindow { @@ -102,14 +30,10 @@ class StreamWindow: public QMainWindow explicit StreamWindow(QWidget *parent = nullptr); ~StreamWindow(); - StreamRelayIODevice *GetIODevice() { return io_device; } + void SetImage(const QImage &image); private: - QtAV::VideoOutput *video_output; - QtAV::AVPlayer *av_player; - - StreamRelayIODevice *io_device; - + QLabel *imageLabel; }; #endif // CHIAKI_GUI_STREAMWINDOW_H diff --git a/gui/include/videodecoder.h b/gui/include/videodecoder.h new file mode 100644 index 0000000..7eb7630 --- /dev/null +++ b/gui/include/videodecoder.h @@ -0,0 +1,52 @@ +/* + * This file is part of Chiaki. + * + * Chiaki is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chiaki is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chiaki. If not, see . + */ + +#ifndef CHIAKI_VIDEODECODER_H +#define CHIAKI_VIDEODECODER_H + +#include +#include + +extern "C" +{ +#include +}; + +#include + +class VideoDecoder: public QObject +{ + Q_OBJECT + + public: + VideoDecoder(); + ~VideoDecoder(); + + void PutFrame(uint8_t *buf, size_t buf_size); + QImage PullFrame(); + + signals: + void FramesAvailable(); + + private: + QMutex mutex; + + AVCodec *codec; + AVCodecContext *codec_context; +}; + +#endif // CHIAKI_VIDEODECODER_H diff --git a/gui/src/main.cpp b/gui/src/main.cpp index 79cd97a..5da505c 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -15,8 +16,7 @@ QAudioOutput *audio_out; QIODevice *audio_io; -QFile *video_out_file; -size_t file_size = 0; +VideoDecoder video_decoder; void audio_frame_cb(int16_t *buf, size_t samples_count, void *user) @@ -26,12 +26,13 @@ void audio_frame_cb(int16_t *buf, size_t samples_count, void *user) void video_sample_cb(uint8_t *buf, size_t buf_size, void *user) { - if(!video_out_file) + video_decoder.PutFrame(buf, buf_size); + /*if(!video_out_file) return; printf("writing %#zx to file, start: %#zx\n", buf_size, file_size); chiaki_log_hexdump(nullptr, CHIAKI_LOG_DEBUG, buf, buf_size); file_size += buf_size; - video_out_file->write((const char *)buf, buf_size); + video_out_file->write((const char *)buf, buf_size);*/ //StreamRelayIODevice *io_device = reinterpret_cast(user); //io_device->PushSample(buf, buf_size); } @@ -98,13 +99,28 @@ int main(int argc, char *argv[]) audio_io = audio_out->start(); - video_out_file = nullptr; + //video_out_file = nullptr; //video_out_file->open(QFile::ReadWrite); + QObject::connect(&video_decoder, &VideoDecoder::FramesAvailable, &window, [&window]() { + QImage prev; + QImage image; + do + { + prev = image; + image = video_decoder.PullFrame(); + } while(!image.isNull()); + + if(!prev.isNull()) + { + window.SetImage(prev); + } + }); + ChiakiSession session; chiaki_session_init(&session, &connect_info); - chiaki_session_set_audio_frame_cb(&session, audio_frame_cb, NULL); - chiaki_session_set_video_sample_cb(&session, video_sample_cb, window.GetIODevice()); + chiaki_session_set_audio_frame_cb(&session, audio_frame_cb, nullptr); + chiaki_session_set_video_sample_cb(&session, video_sample_cb, nullptr); chiaki_session_start(&session); app.setQuitOnLastWindowClosed(true); @@ -113,7 +129,7 @@ int main(int argc, char *argv[]) //video_out_file->close(); - printf("CLOSED!!! filesize: %zu\n", file_size); + //printf("CLOSED!!! filesize: %zu\n", file_size); chiaki_session_join(&session); chiaki_session_fini(&session); diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index a3a9747..984ca98 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -17,42 +17,25 @@ #include -#include -#include - -#include - +#include StreamWindow::StreamWindow(QWidget *parent) : QMainWindow(parent) { - video_output = new QtAV::VideoOutput(QtAV::VideoRendererId_GLWidget2, this); - setCentralWidget(video_output->widget()); + imageLabel = new QLabel(this); + setCentralWidget(imageLabel); - av_player = new QtAV::AVPlayer(this); + imageLabel->setBackgroundRole(QPalette::Base); + imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + imageLabel->setScaledContents(true); - connect(av_player, &QtAV::AVPlayer::stateChanged, this, [](QtAV::AVPlayer::State state){ - printf("state changed to %d\n", state); - }); - - av_player->setRenderer(video_output); - - io_device = new StreamRelayIODevice(this); - io_device->open(QIODevice::ReadOnly); - - QString test_filename = QProcessEnvironment::systemEnvironment().value("CHIAKI_TEST_VIDEO"); - if(!test_filename.isEmpty()) - { - QFile *test_file = new QFile(test_filename, this); - test_file->open(QIODevice::OpenModeFlag::ReadOnly); - QByteArray sample = test_file->readAll(); - io_device->PushSample((uint8_t *)sample.constData(), (size_t)sample.size()); - test_file->close(); - av_player->setIODevice(io_device); - av_player->play(); - } } StreamWindow::~StreamWindow() { +} + +void StreamWindow::SetImage(const QImage &image) +{ + imageLabel->setPixmap(QPixmap::fromImage(image)); } \ No newline at end of file diff --git a/gui/src/videodecoder.cpp b/gui/src/videodecoder.cpp new file mode 100644 index 0000000..e1fc80a --- /dev/null +++ b/gui/src/videodecoder.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of Chiaki. + * + * Chiaki is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chiaki is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Chiaki. If not, see . + */ + +#include + +#include + +#include + +VideoDecoder::VideoDecoder() +{ + codec = avcodec_find_decoder(AV_CODEC_ID_H264); // TODO: handle !codec + codec_context = avcodec_alloc_context3(codec); // TODO: handle !codec_context + + if(codec->capabilities & AV_CODEC_CAP_TRUNCATED) + codec_context->flags |= AV_CODEC_FLAG_TRUNCATED; + + avcodec_open2(codec_context, codec, nullptr); // TODO: handle < 0 +} + +VideoDecoder::~VideoDecoder() +{ + avcodec_close(codec_context); + avcodec_free_context(&codec_context); // TODO: does close by itself too? + // TODO: free codec? +} + +void VideoDecoder::PutFrame(uint8_t *buf, size_t buf_size) +{ + { + QMutexLocker locker(&mutex); + + AVPacket packet; + av_init_packet(&packet); + packet.data = buf; + packet.size = buf_size; + avcodec_send_packet(codec_context, &packet); + } + + emit FramesAvailable(); +} + +QImage VideoDecoder::PullFrame() +{ + QMutexLocker locker(&mutex); + + AVFrame *frame = av_frame_alloc(); // TODO: handle !frame + int r = avcodec_receive_frame(codec_context, frame); + + if(r != 0) + { + if(r != AVERROR(EAGAIN)) + printf("decoding with ffmpeg failed!!\n"); + av_frame_free(&frame); + return QImage(); + } + + switch(frame->format) + { + case AV_PIX_FMT_YUV420P: + break; + default: + printf("unknown format %d\n", frame->format); + av_frame_free(&frame); + return QImage(); + } + + + QImage image(frame->width, frame->height, QImage::Format_RGB32); + for(int y=0; yheight; y++) + { + for(int x=0; xwidth; x++) + { + int Y = frame->data[0][y * frame->linesize[0] + x] - 16; + int U = frame->data[1][(y/2) * frame->linesize[1] + (x/2)] - 128; + int V = frame->data[2][(y/2) * frame->linesize[2] + (x/2)] - 128; + int r = qBound(0, (298 * Y + 409 * V + 128) >> 8, 255); + int g = qBound(0, (298 * Y - 100 * U - 208 * V + 128) >> 8, 255); + int b = qBound(0, (298 * Y + 516 * U + 128) >> 8, 255); + image.setPixel(x, y, qRgb(r, g, b)); + } + } + + av_frame_free(&frame); + + return image; +} \ No newline at end of file