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


#include "easygl/fpsdetector.h"
#include "lib_qt_common/qlogtools.h"
using namespace EasyGL;
Q_DEVELOPER_LOG_CATGS(easygl, FpsDetector)
FpsDetector::FpsDetector(const QString &name, size_t maxSamples, qint64 stutterThresholdMs, qint64 deviationThresholdMs, QObject *parent)
    : QObject(parent)
    , mName(name)
    , mMaxSamples(maxSamples)
    , mStutterThresholdMs(stutterThresholdMs)
    , mDeviationThresholdMs(deviationThresholdMs)
{
    mTimer.start();
}

void FpsDetector::recordFrame()
{
    qint64 currentTime = mTimer.elapsed();
    qint64 delta = 0;
    if (mLastTime != 0)
    {
        delta = currentTime - mLastTime;
        updateDeltas(mDeltas, delta);
        mAvgFps = calculateAverageFps(mDeltas);
        // qCDebug(catg) << "[" << mName << "] Frame recorded. Delta:" << delta << "ms"

        //               << "Avg FPS (last" << mDeltas.size() << "frames):" << mAvgFps;
        // detectStutter(delta);
    }
    mLastTime = currentTime;
}

void FpsDetector::updateDeltas(std::vector<qint64> &deltas, qint64 delta)
{
    deltas.push_back(delta);
    if (deltas.size() > mMaxSamples)
    {
        deltas.erase(deltas.begin());
    }
}

double FpsDetector::calculateAverageFps(const std::vector<qint64> &deltas) const
{
    if (deltas.empty())
        return 0.0;
    double sum = 0.0;
    for (qint64 d : deltas)
        sum += d;
    double avgMs = sum / deltas.size();
    return (avgMs > 0) ? 1000.0 / avgMs : 0.0;
}

void FpsDetector::detectStutter(qint64 delta)
{
    // Absolute check (keep for big drops)
    if (delta > mStutterThresholdMs)
    {
        qCDebug(catgV) << "[" << mName << "] Absolute stutter detected! Delta:" << delta << "ms";
    }

    if (mAvgFps > 0.0 && mDeltas.size() >= 10)
    {
        double expectedDelta = 1000.0 / mAvgFps;
        double deviation = std::abs(static_cast<double>(delta) - expectedDelta);
        if (deviation > mDeviationThresholdMs)
        {
            qCDebug(catgV) << "[" << mName << "] Relative stutter detected! Delta:" << delta << "ms"
                           << "Expected:" << expectedDelta << "ms Deviation:" << deviation << "ms";
        }
    }

    double stdDev = calculateStdDev(mDeltas);
    if (stdDev > 5.0)
    {
        qCDebug(catgV) << "[" << mName << "] High jitter detected! Std dev:" << stdDev << "ms";
    }
}
#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";
}

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");
}

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());
        }
        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
как мне по поводу getAvrgFps.
у нас это вынесено отдельно в FpsDetector
варианта 2: зарегистрировать его отдельно и регистрировать фрейм наа тот же слот или засунуть его в videoglwidget и прокинуть геттер как сделать