Загрузка данных
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");