diff --git a/CHANGES b/CHANGES index afb21e008..8e02fa02e 100644 --- a/CHANGES +++ b/CHANGES @@ -106,6 +106,7 @@ Misc: - Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941) - Qt: Improve cheat parsing (fixes mgba.io/i/2297) - Qt: Change lossless setting to use WavPack audio + - Qt: Use FFmpeg to convert additional camera formats, if available - SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531) - Windows: Attach to console if present - Vita: Add bilinear filtering option (closes mgba.io/i/344) diff --git a/src/platform/qt/VideoDumper.cpp b/src/platform/qt/VideoDumper.cpp index 392030277..7c5345c55 100644 --- a/src/platform/qt/VideoDumper.cpp +++ b/src/platform/qt/VideoDumper.cpp @@ -23,6 +23,9 @@ bool VideoDumper::present(const QVideoFrame& frame) { QVideoFrame::PixelFormat vFormat = mappedFrame.pixelFormat(); QImage::Format format = QVideoFrame::imageFormatFromPixelFormat(vFormat); bool swap = false; +#ifdef USE_FFMPEG + bool useScaler = false; +#endif if (format == QImage::Format_Invalid) { if (vFormat < QVideoFrame::Format_BGRA5658_Premultiplied) { vFormat = static_cast(vFormat - QVideoFrame::Format_BGRA32 + QVideoFrame::Format_ARGB32); @@ -34,11 +37,68 @@ bool VideoDumper::present(const QVideoFrame& frame) { } swap = true; } else { +#ifdef USE_FFMPEG + enum AVPixelFormat pixelFormat; + switch (vFormat) { + case QVideoFrame::Format_YUV420P: + pixelFormat = AV_PIX_FMT_YUV420P; + break; + case QVideoFrame::Format_YUV422P: + pixelFormat = AV_PIX_FMT_YUV422P; + break; + case QVideoFrame::Format_YUYV: + pixelFormat = AV_PIX_FMT_YUYV422; + break; + case QVideoFrame::Format_UYVY: + pixelFormat = AV_PIX_FMT_UYVY422; + break; + case QVideoFrame::Format_NV12: + pixelFormat = AV_PIX_FMT_NV12; + break; + case QVideoFrame::Format_NV21: + pixelFormat = AV_PIX_FMT_NV12; + break; + default: + return false; + } + format = QImage::Format_RGB888; + useScaler = true; + if (pixelFormat != m_pixfmt || m_scalerSize != mappedFrame.size()) { + if (m_scaler) { + sws_freeContext(m_scaler); + } + m_scaler = sws_getContext(mappedFrame.width(), mappedFrame.height(), pixelFormat, + mappedFrame.width(), mappedFrame.height(), AV_PIX_FMT_RGB24, + SWS_POINT, nullptr, nullptr, nullptr); + m_scalerSize = mappedFrame.size(); + m_pixfmt = pixelFormat; + } +#else return false; +#endif } } uchar* bits = mappedFrame.bits(); +#ifdef USE_FFMPEG + QImage image; + if (!useScaler) { + image = QImage(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format); + } + if (useScaler) { + image = QImage(mappedFrame.width(), mappedFrame.height(), format); + const uint8_t* planes[8] = {0}; + int strides[8] = {0}; + for (int plane = 0; plane < mappedFrame.planeCount(); ++plane) { + planes[plane] = mappedFrame.bits(plane); + strides[plane] = mappedFrame.bytesPerLine(plane); + } + uint8_t* outBits = image.bits(); + int outStride = image.bytesPerLine(); + sws_scale(m_scaler, planes, strides, 0, mappedFrame.height(), &outBits, &outStride); + } else +#else QImage image(bits, mappedFrame.width(), mappedFrame.height(), mappedFrame.bytesPerLine(), format); +#endif if (swap) { image = image.rgbSwapped(); } else if (surfaceFormat().scanLineDirection() != QVideoSurfaceFormat::BottomToTop) { @@ -66,5 +126,13 @@ QList VideoDumper::supportedPixelFormats(QAbstractVide list.append(QVideoFrame::Format_BGRA32_Premultiplied); list.append(QVideoFrame::Format_BGR565); list.append(QVideoFrame::Format_BGR555); +#ifdef USE_FFMPEG + list.append(QVideoFrame::Format_YUYV); + list.append(QVideoFrame::Format_UYVY); + list.append(QVideoFrame::Format_YUV422P); + list.append(QVideoFrame::Format_YUV420P); + list.append(QVideoFrame::Format_NV12); + list.append(QVideoFrame::Format_NV21); +#endif return list; } diff --git a/src/platform/qt/VideoDumper.h b/src/platform/qt/VideoDumper.h index 081fe3b45..c22688c2b 100644 --- a/src/platform/qt/VideoDumper.h +++ b/src/platform/qt/VideoDumper.h @@ -7,6 +7,12 @@ #include +#ifdef USE_FFMPEG +extern "C" { +#include +} +#endif + namespace QGBA { class VideoDumper : public QAbstractVideoSurface { @@ -20,6 +26,13 @@ public: signals: void imageAvailable(const QImage& image); + +private: +#ifdef USE_FFMPEG + AVPixelFormat m_pixfmt = AV_PIX_FMT_NONE; + SwsContext* m_scaler = nullptr; + QSize m_scalerSize; +#endif }; }