Загрузка данных
#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 и прокинуть геттер как сделать