Загрузка данных


 int frameNumCorrection = (int)armDelay / (1000.0 / dynamic_cast<EasyGL::VideoGlWidget *>(videoDisplay->getWtVideo())->getAverageFps());

#include "cameramanager.h"
#include "configkeys.h"
#include "lib_qt_common/qlogtools.h"
#include "uiconst.h"
#include "videocontrol/widgets/ivideoprocessor.h"
#include "easyav/transport/clients/livetransportclient.h"
#include "easyapi_avadapter/defaultoptions.h"
#include "easygl/videoglwidget.h"
#include <QDebug>
#include <QLoggingCategory>
#include <QShortcut>
#include <QPointer>

Q_DEVELOPER_LOG_CATGS(arm, CameraManager)


CameraManager::CameraManager(QObject *parent)
    : QObject(parent)
    , mCamId(0)
{
}

void CameraManager::start(CamId camId, const QString &rtpUrl)
{
    qCDebug(catg) << Q_FUNC_INFO << this << "Запуск камеры:" << camId << "URL:" << rtpUrl;
    mCamId = camId;
    mPaused = false;

    if (mReciever.isPipelineExists(1))
    {
        mReciever.stopPipeline(1);
        mReciever.removePipeline(1);
    }

              // Конфигурируем RTSP/RTP клиент на минимальную задержку (Low Delay)
    AVDictionary *dict = EasyApi::DefaultOptions::makeRTPLowDelayFormatClientDict();
    auto liveClient = std::make_shared<EasyAV::LiveTransportClient>(rtpUrl.toStdString(), dict);
    av_dict_free(&dict);

              // Создаем пайплайн с фиксированным ID = 1 (внутри этого менеджера)
    mReciever.addMediaPipeline(1, liveClient);
}

void CameraManager::setRendererToStream(CameraWidget *wtCamera, CamId camId)
{
    qCDebug(catg) << Q_FUNC_INFO << this << "Привязка рендерера для camId:" << camId;

    if (!wtCamera) return;

    // Проверяем, что настраиваем именно ту камеру, которая была запущена
    if (camId != mCamId)
    {
        qCWarning(catg) << "Запрошена привязка для camId" << camId << ", но текущий mCamId =" << mCamId;
        return;
    }

    // Извлекаем указатель на новый OpenGL виджет
    auto* videoGlWidget = qobject_cast<EasyGL::VideoGlWidget*>(wtCamera->getWtVideo());
    if (!videoGlWidget)
    {
        qCWarning(catg) << "Внутри CameraWidget используется старый виджет, ожидался EasyGL::VideoGlWidget!";
    }

    // Выставляем дефолтный Aspect Ratio до получения первого кадра
    wtCamera->setVideoLayoutAspectRatio({ProjectUiConst::DefaultResolutionWidth, ProjectUiConst::DefaultResolutionHeight});

    QPointer<CameraManager> guard(this);

    // Коллбэк для пула потоков MediaReciever
    EasyAV::MediaRecievePipeline::CbRenderer videoRenderer = [guard, videoGlWidget, wtCamera](std::shared_ptr<EasyApi::IEasyFrame> frame) {
        if (!guard || guard->mPaused || !frame)
            return;

        if (frame->getMediaType() != EasyAV::MediaType::video)
            return;

                  // Перенаправляем обработку и отрисовку кадра в главный GUI-поток Qt
        QMetaObject::invokeMethod(guard.data(), [guard, videoGlWidget, wtCamera, frame]() {
            if (!guard || guard->mPaused)
                return;

            auto frameInfo = frame->getFrameInfo();

            // Безопасно извлекаем структуру VideoInfo с реальными размерами
            if (auto* videoInfo = std::get_if<EasyApi::VideoInfo>(&frameInfo))
            {
                QSize originalSize(videoInfo->width, videoInfo->height);

                if (videoGlWidget)
                {
                    videoGlWidget->submitFrame(frame);
                }

                if (guard->mShMemResolution != originalSize)
                {
                    guard->mShMemResolution = originalSize;
                    emit guard->resolutionChanged(guard->mCamId, originalSize);
                }

                QSize curAspectRatio = wtCamera->getVideoLayoutAspectRatio();
                int64_t left = static_cast<int64_t>(originalSize.width()) * curAspectRatio.height();
                int64_t right = static_cast<int64_t>(originalSize.height()) * curAspectRatio.width();

                if (left != right)
                {
                    wtCamera->setVideoLayoutAspectRatio(originalSize);
                }

                emit guard->frameUpdated(guard->mCamId, originalSize, frame->getPts().count());
            }
        }, Qt::QueuedConnection);
    };

    mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer);
    mReciever.playPipeline(1);
}

void CameraManager::pause(CamId camId)
{
    qCDebug(catg) << Q_FUNC_INFO << this << "Пауза для camId:" << camId;
    if (camId == mCamId)
    {
        mPaused = true;
        if (mReciever.isPipelineExists(1))
        {
            mReciever.pausePipeline(1);
        }
    }
}

QSize CameraManager::getShMemResolution() const
{
    return mShMemResolution;
}
#include "easygl/videoglwidget.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QEvent>
#include <QPointer>

Q_DEVELOPER_LOG_CATGS(easygl, VideoGlWidget)

namespace
{
void checkGLError(const QString &location)
{
    GLenum err = glGetError();
    if (err != GL_NO_ERROR)
    {
        qCWarning(catg) << "OpenGL error at" << location << ":" << Qt::hex << err;
    }
}

const char *vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}
)";

const char *fragmentShaderSource = R"(
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D screenTexture;
void main()
{
    FragColor = texture(screenTexture, TexCoord);
}
)";

const float vertices[] = {
    // positions        // texture coords
    -1.0f, 1.0f,  0.0f, 0.0f, 1.0f,    // top-left
    1.0f,  1.0f,  0.0f, 1.0f, 1.0f,    // top-right
    -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,    // bottom-left
    1.0f,  -1.0f, 0.0f, 1.0f, 0.0f     // bottom-right
};
}    // namespace

namespace EasyGL
{

VideoGlWidget::VideoGlWidget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    qCDebug(catgMem) << _FUNC_RAW_ << _THR_ << "Invoked constructor VideoGlWidget";
    mFpsDetector = new FpsDetector("RenderGL",60,25,8,this);
}

VideoGlWidget::~VideoGlWidget()
{
    mShuttingDown.store(true, std::memory_order_release);
    if (mContextCleanUpConnect)
    {
        disconnect(mContextCleanUpConnect);
        mContextCleanUpConnect = {};
    }
    QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
    qCDebug(catgMem) << _FUNC_RAW_ << _THR_ << "Invoked destructor VideoGlWidget";
    cleanUp();
}

void VideoGlWidget::submitFrame(const std::shared_ptr<const EasyApi::IEasyFrame> &frame)
{
    if (mShuttingDown.load(std::memory_order_acquire))
        return;

    if (!frame)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Frame is null, ignoring";
        return;
    }
    QPointer<VideoGlWidget> guard(this);
    auto frameCopy = frame;
    QMetaObject::invokeMethod(
        this,
        [guard, frameCopy]() {
            if (!guard || guard->mShuttingDown.load(std::memory_order_acquire))
                return;

            guard->mLastFrame = frameCopy;
            guard->onFrameSubmitted();
        },
        Qt::QueuedConnection);
}

void VideoGlWidget::onFrameSubmitted()
{
    update();
}

void VideoGlWidget::fillWithColor(QColor color)
{
    qCDebug(catg) << _FUNC_RAW_ << color;
    mBackgroundColor = color;
    update();
}

void VideoGlWidget::initializeGL()
{
    qCDebug(catg) << _FUNC_RAW_ << _THR_;
    initializeOpenGLFunctions();
    if (mContextCleanUpConnect)
        disconnect(mContextCleanUpConnect);

    glClearColor(mBackgroundColor.redF(), mBackgroundColor.greenF(), mBackgroundColor.blueF(), mBackgroundColor.alphaF());

    // Создаём шейдерную программу для отображения текстуры
    mDisplayProgram = new QOpenGLShaderProgram(this);
    mDisplayProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    mDisplayProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    mDisplayProgram->link();

    // Создаём VAO и VBO для прямоугольника
    mDisplayVao = new QOpenGLVertexArrayObject(this);
    mDisplayVao->create();
    mDisplayVao->bind();

    mDisplayVbo = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    mDisplayVbo->create();
    mDisplayVbo->bind();
    mDisplayVbo->allocate(vertices, sizeof(vertices));

    // Атрибуты
    int posLoc = mDisplayProgram->attributeLocation("aPos");
    int texLoc = mDisplayProgram->attributeLocation("aTexCoord");

    mDisplayProgram->enableAttributeArray(posLoc);
    mDisplayProgram->setAttributeBuffer(posLoc, GL_FLOAT, 0, 3, 5 * sizeof(float));

    mDisplayProgram->enableAttributeArray(texLoc);
    mDisplayProgram->setAttributeBuffer(texLoc, GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));

    mDisplayVbo->release();
    mDisplayVao->release();

    mContextCleanUpConnect = connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &VideoGlWidget::cleanUp, Qt::DirectConnection);
    mCleanedUp = false;
    checkGLError("initializeGL end");
}

int VideoGlWidget::getAverageFps() const
{
    if(mFpsDetector)
    {
        return 0;
    }

    return static_cast<int>(std::round(mFpsDetector->getAverageFps()));
}

void VideoGlWidget::paintGL()
{
    glViewport(0, 0, width(), height());
    glClearColor(mBackgroundColor.redF(), mBackgroundColor.greenF(), mBackgroundColor.blueF(), mBackgroundColor.alphaF());
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    auto frame = mLastFrame;
    if (frame && prepareRenderer(frame))
    {
        QSize fbSize = mTextureSize.isEmpty() ? size() : mTextureSize;
        if (!mFbo || mFbo->size() != fbSize)
        {
            mFbo.reset(new QOpenGLFramebufferObject(fbSize, QOpenGLFramebufferObject::NoAttachment));
        }

        mFbo->bind();
        glViewport(0, 0, fbSize.width(), fbSize.height());
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        bool renderOk = mFrameRenderer->render(frame, mFbo.get());
        mFbo->release();
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

        if (renderOk)
        {
            setupViewport();
            renderFboTexture(mFbo->texture());
            if(mFpsDetector)
            {
                mFpsDetector->recordFrame();
            }
        }
        else
        {
            qCWarning(catg) << _FUNC_RAW_ << "renderFrame failed";
        }
    }
    else
    {
        qCDebug(catgV) << _FUNC_RAW_ << "No frame to render";
    }

    GLenum err = glGetError();
    if (err != GL_NO_ERROR)
    {
        qCWarning(catg) << _FUNC_RAW_ << "OpenGL error after paintGL:" << Qt::hex << err;
    }
}

void VideoGlWidget::resizeGL(int w, int h)
{
    qCDebug(catg) << _FUNC_RAW_ << w << h;
    glViewport(0, 0, w, h);
}

void VideoGlWidget::setupViewport()
{
    if (mTextureSize.isEmpty() || width() <= 0 || height() <= 0)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Texture size is empty, setting viewport to full widget";
        glViewport(0, 0, width(), height());
        return;
    }

    float textureAspect = float(mTextureSize.width()) / mTextureSize.height();
    float widgetAspect = float(width()) / height();

    int renderWidth, renderHeight;
    if (widgetAspect > textureAspect)
    {
        renderHeight = height();
        renderWidth = int(renderHeight * textureAspect);
    }
    else
    {
        renderWidth = width();
        renderHeight = int(renderWidth / textureAspect);
    }

    int posX = (width() - renderWidth) / 2;
    int posY = (height() - renderHeight) / 2;

    glViewport(posX, posY, renderWidth, renderHeight);
}

void VideoGlWidget::renderFboTexture(GLuint textureId)
{
    if (!mDisplayProgram || !mDisplayVao)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Display shader not initialized";
        return;
    }

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    mDisplayProgram->bind();
    mDisplayVao->bind();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureId);
    mDisplayProgram->setUniformValue("screenTexture", 0);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glBindTexture(GL_TEXTURE_2D, 0);
    mDisplayVao->release();
    mDisplayProgram->release();

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
}

bool VideoGlWidget::prepareRenderer(const std::shared_ptr<const EasyApi::IEasyFrame> &frame)
{
    if (!frame)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Null frame";
        return false;
    }

    auto mt = frame->getMediaType();
    if (mt != EasyApi::MediaType::video)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Frame is not video";
        return false;
    }

    const auto &frameInfo = frame->getFrameInfo();
    const auto &videoInfo = std::get<EasyApi::VideoInfo>(frameInfo);
    auto pixelFormat = videoInfo.format.getNamedValue();

    bool formatChanged = (mCurrentFormat != pixelFormat);
    bool sizeChanged = (mCurrentWidth != videoInfo.width || mCurrentHeight != videoInfo.height);

    if (formatChanged || sizeChanged)
    {
        qCInfo(catg) << _FUNC_RAW_ << "Format/size changed: old" << mCurrentFormat << mCurrentWidth << "x" << mCurrentHeight << "new"
                     << pixelFormat << videoInfo.width << "x" << videoInfo.height;
        mCurrentFormat = pixelFormat;
        mCurrentWidth = videoInfo.width;
        mCurrentHeight = videoInfo.height;
        mTextureSize = QSize(videoInfo.width, videoInfo.height);
    }

    // Пересоздаём рендерер только при смене формата (не при изменении размера)
    if (!mFrameRenderer || formatChanged)
    {
        qCInfo(catg) << _FUNC_RAW_ << "Need to create new renderer" << (!mFrameRenderer ? "(no renderer)" : "(format changed)");
        if (!createRenderer(frame))
        {
            qCWarning(catg) << _FUNC_RAW_ << "Error creating render";
            return false;
        }
    }

    return true;
}

bool VideoGlWidget::createRenderer(const std::shared_ptr<const EasyApi::IEasyFrame> &frame)
{
    qCDebug(catg) << _FUNC_RAW_ << "Creating new renderer";

    if (mFrameRenderer)
    {
        mFrameRenderer->cleanTextures();
        mFrameRenderer.reset();
    }

    const auto &frameInfo = frame->getFrameInfo();
    const auto &videoInfo = std::get<EasyApi::VideoInfo>(frameInfo);
    mFrameRenderer = FrameRendererFactory::createFrameRenderer(videoInfo.format, QOpenGLContext::currentContext());

    if (!mFrameRenderer)
    {
        qCWarning(catg) << _FUNC_RAW_ << "Failed to create renderer for format" << videoInfo.format.getNamedValue();
        return false;
    }

    qCDebug(catg) << _FUNC_RAW_ << "Renderer created successfully";
    return true;
}

void VideoGlWidget::cleanUp()
{
    if (mCleanedUp)
        return;
    mCleanedUp = true;
    if (mContextCleanUpConnect)
    {
        disconnect(mContextCleanUpConnect);
        mContextCleanUpConnect = {};
    }

    qCDebug(catg) << _FUNC_RAW_ << _THR_ << "Cleaning up OpenGL resources";

    if (context())
        makeCurrent();

    if (mFrameRenderer)
        mFrameRenderer.reset();

    if (mDisplayProgram)
    {
        delete mDisplayProgram;
        mDisplayProgram = nullptr;
    }
    if (mDisplayVao)
    {
        mDisplayVao->destroy();
        delete mDisplayVao;
        mDisplayVao = nullptr;
    }
    if (mDisplayVbo)
    {
        mDisplayVbo->destroy();
        delete mDisplayVbo;
        mDisplayVbo = nullptr;
    }
    mFbo.reset();

    mCurrentFormat = EasyApi::PixelFormat::unknown;
    mCurrentWidth = 0;
    mCurrentHeight = 0;
    mTextureSize = QSize();

    if (context())
        doneCurrent();
}

}    // namespace EasyGL
    mCamManager->start(mGunnerCameraId,"rtp://127.0.0.1:1234");
    camManager->start(mGunnerCameraId,"rtp://127.0.0.1:1234");