diff options
Diffstat (limited to 'src/frontend/qt_sdl/CameraManager.cpp')
| -rw-r--r-- | src/frontend/qt_sdl/CameraManager.cpp | 612 | 
1 files changed, 612 insertions, 0 deletions
| diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp new file mode 100644 index 0000000..19cf8d4 --- /dev/null +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -0,0 +1,612 @@ +/* +    Copyright 2016-2022 melonDS team + +    This file is part of melonDS. + +    melonDS 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. + +    melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include "CameraManager.h" +#include "Config.h" + + +#if QT_VERSION >= 0x060000 + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) +{ +    cam = (CameraManager*)parent; + +    connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present); +} + +void CameraFrameDumper::present(const QVideoFrame& _frame) +{ +    QVideoFrame frame(_frame); +    if (!frame.map(QVideoFrame::ReadOnly)) +        return; +    if (!frame.isReadable()) +    { +        frame.unmap(); +        return; +    } + +    switch (frame.pixelFormat()) +    { +    case QVideoFrameFormat::Format_XRGB8888: +    case QVideoFrameFormat::Format_YUYV: +        cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV); +        break; + +    case QVideoFrameFormat::Format_UYVY: +        cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); +        break; + +    case QVideoFrameFormat::Format_NV12: +        cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); +        break; +    } + +    frame.unmap(); +} + +#else + +CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent) +{ +    cam = (CameraManager*)parent; +} + +bool CameraFrameDumper::present(const QVideoFrame& _frame) +{ +    QVideoFrame frame(_frame); +    if (!frame.map(QAbstractVideoBuffer::ReadOnly)) +        return false; +    if (!frame.isReadable()) +    { +        frame.unmap(); +        return false; +    } + +    switch (frame.pixelFormat()) +    { +    case QVideoFrame::Format_RGB32: +    case QVideoFrame::Format_YUYV: +        cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV); +        break; + +    case QVideoFrame::Format_UYVY: +        cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height()); +        break; + +    case QVideoFrame::Format_NV12: +        cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height()); +        break; +    } + +    frame.unmap(); + +    return true; +} + +QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const +{ +    QList<QVideoFrame::PixelFormat> ret; + +    ret.append(QVideoFrame::Format_RGB32); +    ret.append(QVideoFrame::Format_YUYV); +    ret.append(QVideoFrame::Format_UYVY); +    ret.append(QVideoFrame::Format_NV12); + +    return ret; +} + +#endif + + +CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +{ +    this->num = num; + +    startNum = 0; + +    // QCamera needs to be controlled from the UI thread, hence this +    connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart())); +    connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop())); + +    frameWidth = width; +    frameHeight = height; +    frameFormatYUV = yuv; + +    int fbsize = frameWidth * frameHeight; +    if (yuv) fbsize /= 2; +    frameBuffer = new u32[fbsize]; +    tempFrameBuffer = new u32[fbsize]; + +    inputType = -1; +    xFlip = false; +    init(); +} + +CameraManager::~CameraManager() +{ +    deInit(); + +    // save settings here? + +    delete[] frameBuffer; +} + +void CameraManager::init() +{ +    if (inputType != -1) +        deInit(); + +    startNum = 0; + +    inputType = Config::Camera[num].InputType; +    imagePath = QString::fromStdString(Config::Camera[num].ImagePath); +    camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + +    camDevice = nullptr; + +    { +        // fill the framebuffer with black + +        int total = frameWidth * frameHeight; +        u32 fill = 0; +        if (frameFormatYUV) +        { +            total /= 2; +            fill = 0x80008000; +        } + +        for (int i = 0; i < total; i++) +            frameBuffer[i] = fill; +    } + +    if (inputType == 1) +    { +        // still image + +        QImage img(imagePath); +        if (!img.isNull()) +        { +            QImage imgconv = img.convertToFormat(QImage::Format_RGB32); +            if (frameFormatYUV) +            { +                copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(), +                                   frameBuffer, frameWidth, frameHeight, +                                   false); +            } +            else +            { +                copyFrame_Straight((u32*)img.bits(), img.width(), img.height(), +                                   frameBuffer, frameWidth, frameHeight, +                                   false, false); +            } +        } +    } +    else if (inputType == 2) +    { +        // physical camera + +#if QT_VERSION >= 0x060000 +        const QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); +        for (const QCameraDevice& cam : cameras) +        { +            if (QString(cam.id()) == camDeviceName) +            { +                camDevice = new QCamera(cam); +                break; +            } +        } + +        if (camDevice) +        { +            const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats(); +            bool good = false; +            for (const QCameraFormat& item : supported) +            { +                if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV && +                    item.pixelFormat() != QVideoFrameFormat::Format_UYVY && +                    item.pixelFormat() != QVideoFrameFormat::Format_NV12 && +                    item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888) +                    continue; + +                if (item.resolution().width() != 640 && item.resolution().height() != 480) +                    continue; + +                camDevice->setCameraFormat(item); +                good = true; +                break; +            } + +            if (!good) +            { +                delete camDevice; +                camDevice = nullptr; +            } +            else +            { +                camDumper = new CameraFrameDumper(this); + +                camSession = new QMediaCaptureSession(this); +                camSession->setCamera(camDevice); +                camSession->setVideoOutput(camDumper); +            } +        } +#else +        camDevice = new QCamera(camDeviceName.toUtf8()); +        if (camDevice->error() != QCamera::NoError) +        { +            delete camDevice; +            camDevice = nullptr; +        } + +        if (camDevice) +        { +            camDevice->load(); + +            const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings(); +            bool good = false; +            for (const QCameraViewfinderSettings& item : supported) +            { +                if (item.pixelFormat() != QVideoFrame::Format_YUYV && +                    item.pixelFormat() != QVideoFrame::Format_UYVY && +                    item.pixelFormat() != QVideoFrame::Format_NV12 && +                    item.pixelFormat() != QVideoFrame::Format_RGB32) +                    continue; + +                if (item.resolution().width() != 640 && item.resolution().height() != 480) +                    continue; + +                camDevice->setViewfinderSettings(item); +                good = true; +                break; +            } + +            camDevice->unload(); + +            if (!good) +            { +                delete camDevice; +                camDevice = nullptr; +            } +            else +            { +                camDumper = new CameraFrameDumper(this); +                camDevice->setViewfinder(camDumper); +            } +        } +#endif +    } +} + +void CameraManager::deInit() +{ +    if (inputType == 2) +    { +        if (camDevice) +        { +            camDevice->stop(); +            delete camDevice; +            delete camDumper; +#if QT_VERSION >= 0x060000 +            delete camSession; +#endif +        } +    } + +    camDevice = nullptr; +    inputType = -1; +} + +void CameraManager::start() +{ +    if (startNum == 1) return; +    startNum = 1; + +    if (inputType == 2) +    { +        emit camStartSignal(); +    } +} + +void CameraManager::stop() +{ +    if (startNum == 0) return; +    startNum = 0; + +    if (inputType == 2) +    { +        emit camStopSignal(); +    } +} + +bool CameraManager::isStarted() +{ +    return startNum != 0; +} + +void CameraManager::camStart() +{ +    if (camDevice) +        camDevice->start(); +} + +void CameraManager::camStop() +{ +    if (camDevice) +        camDevice->stop(); +} + +void CameraManager::setXFlip(bool flip) +{ +    xFlip = flip; +} + +void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv) +{ +    frameMutex.lock(); + +    if ((width == frameWidth) && +        (height == frameHeight) && +        (yuv == frameFormatYUV) && +        (!xFlip)) +    { +        int len = width * height; +        if (yuv) len /= 2; +        memcpy(frame, frameBuffer, len * sizeof(u32)); +    } +    else +    { +        if (yuv == frameFormatYUV) +        { +            copyFrame_Straight(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip, yuv); +        } +        else if (yuv) +        { +            copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip); +        } +        else +        { +            copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight, +                               frame, width, height, +                               xFlip); +        } +    } + +    frameMutex.unlock(); +} + +void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv) +{ +    frameMutex.lock(); + +    if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV) +    { +        int len = width * height; +        if (yuv) len /= 2; +        memcpy(frameBuffer, frame, len * sizeof(u32)); +    } +    else +    { +        if (yuv == frameFormatYUV) +        { +            copyFrame_Straight(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false, yuv); +        } +        else if (yuv) +        { +            copyFrame_RGBtoYUV(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false); +        } +        else +        { +            copyFrame_YUVtoRGB(frame, width, height, +                               frameBuffer, frameWidth, frameHeight, +                               false); +        } +    } + +    frameMutex.unlock(); +} + +void CameraManager::feedFrame_UYVY(u32* frame, int width, int height) +{ +    for (int y = 0; y < frameHeight; y++) +    { +        int sy = (y * height) / frameHeight; + +        for (int x = 0; x < frameWidth; x+=2) +        { +            int sx = (x * width) / frameWidth; + +            u32 val = frame[((sy*width) + sx) >> 1]; + +            val = ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8); + +            tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; +        } +    } + +    feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height) +{ +    for (int y = 0; y < frameHeight; y++) +    { +        int sy = (y * height) / frameHeight; + +        for (int x = 0; x < frameWidth; x+=2) +        { +            int sx1 = (x * width) / frameWidth; +            int sx2 = ((x+1) * width) / frameWidth; + +            u32 val; + +            u8 y1 = planeY[(sy*width) + sx1]; +            u8 y2 = planeY[(sy*width) + sx2]; + +            int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1)); +            u8 u = planeUV[uvpos << 1]; +            u8 v = planeUV[(uvpos << 1) + 1]; + +            val = y1 | (u << 8) | (y2 << 16) | (v << 24); +            tempFrameBuffer[((y*frameWidth) + x) >> 1] = val; +        } +    } + +    feedFrame(tempFrameBuffer, frameWidth, frameHeight, true); +} + +void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv) +{ +    if (yuv) +    { +        swidth /= 2; +        dwidth /= 2; +    } + +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx++) +        { +            int sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 val = src[(sy * swidth) + sx]; + +            if (yuv) +            { +                if (xflip) +                    val = (val & 0xFF00FF00) | +                          ((val >> 16) & 0xFF) | +                          ((val & 0xFF) << 16); +            } +            else +                val |= 0xFF000000; + +            dst[(dy * dwidth) + dx] = val; +        } +    } +} + +void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx+=2) +        { +            int sx; + +            sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 pixel1 = src[sy*swidth + sx]; + +            sx = ((dx+1) * swidth) / dwidth; +            if (xflip) sx = swidth-1 - sx; + +            u32 pixel2 = src[sy*swidth + sx]; + +            int r1 = (pixel1 >> 16) & 0xFF; +            int g1 = (pixel1 >> 8) & 0xFF; +            int b1 = pixel1 & 0xFF; + +            int r2 = (pixel2 >> 16) & 0xFF; +            int g2 = (pixel2 >> 8) & 0xFF; +            int b2 = pixel2 & 0xFF; + +            int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16; +            int u1 = ((b1 - y1) * 32244) >> 16; +            int v1 = ((r1 - y1) * 57475) >> 16; + +            int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16; +            int u2 = ((b2 - y2) * 32244) >> 16; +            int v2 = ((r2 - y2) * 57475) >> 16; + +            u1 += 128; v1 += 128; +            u2 += 128; v2 += 128; + +            y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255); +            y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255); + +            // huh +            u1 = (u1 + u2) >> 1; +            v1 = (v1 + v2) >> 1; + +            dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24); +        } +    } +} + +void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip) +{ +    for (int dy = 0; dy < dheight; dy++) +    { +        int sy = (dy * sheight) / dheight; + +        for (int dx = 0; dx < dwidth; dx+=2) +        { +            int sx = (dx * swidth) / dwidth; +            if (xflip) sx = swidth-2 - sx; + +            u32 val = src[(sy*swidth + sx) / 2]; + +            int y1, y2; +            if (xflip) +            { +                y1 = (val >> 16) & 0xFF; +                y2 = val & 0xFF; +            } +            else +            { +                y1 = val & 0xFF; +                y2 = (val >> 16) & 0xFF; +            } +            int u = (val >> 8) & 0xFF; +            int v = (val >> 24) & 0xFF; + +            u -= 128; v -= 128; + +            int r1 = y1 + ((v * 91881) >> 16); +            int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16); +            int b1 = y1 + ((u * 116129) >> 16); + +            int r2 = y2 + ((v * 91881) >> 16); +            int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16); +            int b2 = y2 + ((u * 116129) >> 16); + +            r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255); +            r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255); + +            u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1; +            u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2; + +            dst[dy*dwidth + dx  ] = col1; +            dst[dy*dwidth + dx+1] = col2; +        } +    } +} |