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


/*#include "cameramanager.h"
#include "configkeys.h"
#include "lib_qt_common/qlogtools.h"
#include "uiconst.h"
#include "videocontrol/widgets/ivideoprocessor.h"
#include <QDebug>
#include <QLoggingCategory>
#include <QShortcut>
#include "easygl/videoglwidget.h"

Q_DEVELOPER_LOG_CATGS(arm, CameraManager)

CameraManager::CameraManager(VideoReceiver *shMemClient, QObject *parent)
    : QObject(parent)
    , mVideoReciever(shMemClient)
{}

void CameraManager::start(CamId camId)
{
    qCDebug(catg) << Q_FUNC_INFO << this << camId;
    mShMemCamId = camId;
    mPaused = false;
}

void CameraManager::setRendererToStream(CameraWidget *wtCamera, CamId camId)
{
    qCDebug(catg) << Q_FUNC_INFO << this << camId;

    if (camId == mShMemCamId)
    {
        wtCamera->setVideoLayoutAspectRatio({ProjectUiConst::DefaultResolutionWidth, ProjectUiConst::DefaultResolutionHeight});
        mVideoReciever->setScreenSize({ProjectUiConst::DefaultResolutionWidth, ProjectUiConst::DefaultResolutionHeight});
        auto iVideoReciever = dynamic_cast<EasyGL::VideoGlWidget *>(wtCamera->getWtVideo());
        connect(
            mVideoReciever, &VideoReceiver::newData, this,
            [wtCamera, iVideoReciever, this](quint64 frameNum, quint64 timestamp, QPixmap frame, QSize screenSize, QSize originalSize) {
                if (mPaused)
                    return;
                if (mShMemResolution != screenSize)
                {
                    mShMemResolution = screenSize;
                    emit resolutionChanged(mShMemCamId, {originalSize.width(), originalSize.height()});
                }
                // adjust aspect ratio
                QSize size = {frame.width(), frame.height()};
                QSize curAspectRatio = wtCamera->getVideoLayoutAspectRatio();
                int64_t left = size.width() * curAspectRatio.height();
                int64_t right = size.height() * curAspectRatio.width();

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

                // update frame
                auto wtSize = wtCamera->videoWidgetSize();
                iVideoReciever->updatePixmap(frame.scaled(wtSize.width(), wtSize.height()), frameNum);
                emit frameUpdated(mShMemCamId, originalSize, frameNum);
            },
            Qt::QueuedConnection);
        return;
    }
}

void CameraManager::pause(CamId camId)
{
    qCDebug(catg) << Q_FUNC_INFO << this;
    mPaused = true;
}

QSize CameraManager::getShMemResolution() const { return mShMemResolution; }
*/

#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;
}
#ifndef CAMERAMANAGER_H
#define CAMERAMANAGER_H

#include "libs/lib_framedatabridge/src/receiver/videoreceiver.h"
#include "libs/wgt_common/src/camerawidget.h"
#include "easyav/reciever/mediareciever.h"
#include <QObject>
using CamId = uint32_t;
using FrameId = uint32_t;
using FrameSize = std::pair<uint16_t, uint16_t>;
struct CamStreamInfo
{
    uint32_t id;
    std::string ip;
    std::string port;
    std::string camName = "unknown cam";
    FrameSize resolution{0, 0};
};
/*!
  \brief Класс управляющий видеопотоками
*/
/*
class CameraManager : public QObject
{
    Q_OBJECT
  public:
    explicit CameraManager(VideoReceiver *shMemClient, QObject *parent = nullptr);

    void start(CamId camId);
    void start(const QVector<CamId> &camIds);

    void setRendererToStream(CameraWidget *wtCamera, CamId camId);

    void pause(CamId camId);

    QSize getShMemResolution() const;

  signals:
    void resolutionChanged(CamId, FrameSize);
    void frameUpdated(CamId camId, QSize size, FrameId frameId);

  private:
    VideoReceiver *mVideoReciever{nullptr};
    bool mPaused{false};
    QSize mShMemResolution{0, 0};
    CamId mShMemCamId{0};
};
#endif    // CAMERAMANAGER_H
*/

class CameraManager : public QObject
{
    Q_OBJECT
  public:
    explicit CameraManager(QObject *parent = nullptr);
    ~CameraManager() override = default;

              // Добавляем URL для инициализации RTP потока
    void start(CamId camId, const QString &rtpUrl);
    void pause(CamId camId);
    void setRendererToStream(CameraWidget *wtCamera, CamId camId);

    QSize getShMemResolution() const;

  signals:
    void resolutionChanged(CamId camId, QSize originalSize);
    void frameUpdated(CamId camId, QSize originalSize, quint64 frameNum);

  private:
    EasyAV::MediaReciever mReciever;
    CamId mCamId;
    QSize mShMemResolution;
    bool mPaused = false;
};
#endif    // CAMERAMANAGER_H

Нужно сделать так чтобы он принимал информацию с .rtp файла а не как сейчас ведь без него чисты ртп не воспринимает контекст можешь ориентироваться на mediaplyaerwt initfrom media #include "mediaplayerwt.h"
#include "./ui_mediaplayerwt.h"

#include "apparguments.h"
#include "easyav/transport/clients/filetransportclient.h"
#include "easyav/transport/clients/livetransportclient.h"
#include "easyav/transport/clients/shmemtransportclient.h"
#include "easyapi_avadapter/defaultoptions.h"
#include "lib_qt_common/qlogtools.h"
#include <QCoreApplication>
#include <QEvent>
#include <QPointer>
#include <QVBoxLayout>
#ifdef BUILD_OPENCV_SUPPORT_EASYSTREAMER
#include "easyapi_cvadapter/cveasyframe.h"
#include <opencv2/core/mat.hpp>
#endif
extern "C" {
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
Q_DEVELOPER_LOG_CATGS(easystreamer, MediaPlayerWt);

namespace
{
QOpenGLWidget *createVideoWidget(QWidget *parent)
{
    const QString backend = AppArguments::argParser().value(AppArguments::videoWidgetOpt).trimmed().toLower();
    if (backend == "shared" || backend == "sharedvideoglwidget" || backend == "shared-video-gl-widget")
    {
        qCInfo(catg) << "Using SharedVideoGlWidget backend";
        return new EasyGL::SharedVideoGlWidget(parent);
    }

    if (backend != "videoglwidget" && backend != "video")
        qCWarning(catg) << "Unknown video widget backend" << backend << ", using VideoGlWidget";

    qCInfo(catg) << "Using VideoGlWidget backend";
    return new EasyGL::VideoGlWidget(parent);
}

bool isRtpLowDelaySource(const QString &source)
{
    const QString normalized = source.trimmed().toLower();
    return normalized.startsWith("rtp://") || normalized.startsWith("rtp_mpegts://") || normalized.startsWith("udp://") ||
           normalized.endsWith(".sdp");
}

AVDictionary *makeLiveClientOptions(const QString &source, bool useRtpLowDelay)
{
    if (!useRtpLowDelay)
        return nullptr;

    if (!isRtpLowDelaySource(source))
    {
        qCWarning(catg) << "RTP low-delay receive options ignored for non-RTP source:" << source;
        return nullptr;
    }

    qCInfo(catg) << "Using RTP low-delay receive options for source:" << source;
    return EasyApi::DefaultOptions::makeRTPLowDelayFormatClientDict();
}
}    // namespace

MediaPlayerWt::MediaPlayerWt(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MediaPlayerWt)
    , mReciever()
    , mWtVideoGl(createVideoWidget(this))
    , mWtCodecHwConfig(new HwConfigWt(this))
    , mWtFilterHwConfig(new HwConfigWt(this))
    , mWtControl(new MediaControlWt(this))
    , mVolumeSlider(new QSlider(Qt::Horizontal, this))
    , mTimeBar(new QSlider(Qt::Horizontal, this))
{
    qCDebug(catgMem) << _FUNC_RAW_;
    ui->setupUi(this);
    // setupLayout();
    setupWidgets();
    // loadSettings();
}

MediaPlayerWt::~MediaPlayerWt()
{
    dbgMemStart(catgMem);
    mClosing.store(true, std::memory_order_release);
    QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
#ifdef BUILD_OPENCV_SUPPORT_EASYSTREAMER
    if (mCvTimer)
    {
        mCvTimer->stop();
        delete mCvTimer;
        mCvTimer = nullptr;
    }
    mCvCapture.reset();
#endif
    mReciever.stopAllPipelines();
    QCoreApplication::removePostedEvents(this, QEvent::MetaCall);

    // saveSettings();
    delete ui;
    dbgMemFinish(catgMem);
}

void MediaPlayerWt::setRecieveSettingsVisible(bool visible) { ui->wtSettings->setVisible(visible); }

bool MediaPlayerWt::isVideoDetached() const { return mDetachedVideoDialog != nullptr; }

void MediaPlayerWt::setVideoDetached(bool detached)
{
    if (detached == isVideoDetached())
        return;

    if (detached)
    {
        mDetachedVideoDialog = new QDialog(this, Qt::Window | Qt::WindowStaysOnTopHint);
        mDetachedVideoDialog->setObjectName("detachedVideoDialog");
        mDetachedVideoDialog->setWindowTitle("Video");
        mDetachedVideoDialog->setMinimumSize(320, 240);
        mDetachedVideoDialog->installEventFilter(this);

        auto layout = new QVBoxLayout(mDetachedVideoDialog);
        layout->setContentsMargins(0, 0, 0, 0);
        layout->setSpacing(0);

        QSize dialogSize = mWtVideoGl->size();
        if (!dialogSize.isValid() || dialogSize.isEmpty())
            dialogSize = {960, 540};

        ui->wtCenter->removeWidget(mWtVideoGl);
        if (mVideoPlaceholder)
            ui->wtCenter->setCurrentWidget(mVideoPlaceholder);

        mWtVideoGl->setParent(mDetachedVideoDialog);
        layout->addWidget(mWtVideoGl);
        mWtVideoGl->show();
        mDetachedVideoDialog->resize(dialogSize);
        mDetachedVideoDialog->show();
        emit videoDetachedChanged(true);
        return;
    }

    QDialog *dialog = mDetachedVideoDialog;
    mDetachedVideoDialog = nullptr;
    dialog->removeEventFilter(this);

    if (auto layout = dialog->layout())
        layout->removeWidget(mWtVideoGl);

    if (ui->wtCenter->indexOf(mWtVideoGl) < 0)
        ui->wtCenter->addWidget(mWtVideoGl);
    ui->wtCenter->setCurrentWidget(mWtVideoGl);

    dialog->deleteLater();
    emit videoDetachedChanged(false);
}

void MediaPlayerWt::initFromMediaFile(const QString &filename)
{
    auto fileClient = std::make_shared<EasyAV::FileTransportClient>(filename.toStdString(), nullptr);
    if (mReciever.isPipelineExists(1))
    {
        mReciever.stopPipeline(1);
        mReciever.removePipeline(1);
    }

    auto config = mWtCodecHwConfig->getConfig();

    if (config.devicePath.empty() && config.deviceType == EasyAV::HWDeviceType::vaapi)
    {
        config.devicePath = "/dev/dri/renderD128";
    }

    mReciever.setHardwareDeviceForCodec(1, config);

    mReciever.setCustomVideoCodecName(1, ui->cbCodec->currentText().toStdString());
    mReciever.addMediaPipeline(1, fileClient);

    // mReciever.setFinishCb(1, [this]() {
    //     QMetaObject::invokeMethod(this, [this]() { mReciever.seekForPipeline(1, 0s); });
    // });
    if (!isVideoDetached())
        ui->wtCenter->setCurrentWidget(mWtVideoGl);
    // EasyGL::IVideoProcessor *destWgt;
    // if (ui->cbDisplay->currentIndex() == 0)
    //     destWgt = this->mWtVideoQt;
    QPointer<MediaPlayerWt> guard(this);
    EasyAV::MediaRecievePipeline::CbRenderer videoRenderer = [guard, fileClient](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing() || !frame)
            return;
        auto pts = frame->getPts();
        guard->submitVideoFrame(frame);
        guard->invokeOnGui([pts, fileClient, frame](MediaPlayerWt &self) {
            if (!self.mDurationSetuped)
            {

                auto duration = fileClient->getDuration();
                self.mTimeBar->setRange(0, duration.count());
                self.mTimeBar->setSingleStep(10);    // Step size of 1 second
                self.mDurationSetuped = true;
            }
            if (!self.mSeeking)
                self.mTimeBar->setValue(std::chrono::duration_cast<std::chrono::milliseconds>(pts).count());
        });
    };
    EasyAV::MediaRecievePipeline::CbRenderer audioRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing())
            return;
        guard->mAudioRenderer.render(frame);
    };

    // mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer, ui->leFilter->text().toStdString(),
    // mWtFilterHwConfig->getConfig());
    mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer, {ui->leFilter->text().toStdString(), mWtFilterHwConfig->getConfig()});
    mReciever.addRenderer(1, EasyAV::MediaType::audio, audioRenderer, {mAudioRenderer.buildFilterChain(), std::nullopt});

    bool inited = mReciever.initPipeline(1);
    if (!inited)
        return;
    emit ready();

    mWtControl->reset();
}

void MediaPlayerWt::initFromSdpFile(const QString &sdp)
{
    AVDictionary *dict = makeLiveClientOptions(sdp, ui->cbRtpLowDelay->isChecked());

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

    auto liveClient = std::make_shared<EasyAV::LiveTransportClient>(sdp.toStdString(), dict);
    av_dict_free(&dict);

    mReciever.setHardwareDeviceForCodec(1, mWtCodecHwConfig->getConfig());
    mReciever.setCustomVideoCodecName(1, ui->cbCodec->currentText().toStdString());
    mReciever.addMediaPipeline(1, liveClient);

    // mReciever.setFinishCb(1, [this]() {
    //     QMetaObject::invokeMethod(this, [this]() { mReciever.seekForPipeline(1, 0s); });
    // });

    if (!isVideoDetached())
        ui->wtCenter->setCurrentWidget(mWtVideoGl);
    // EasyGL::IVideoProcessor *destWgt;
    QPointer<MediaPlayerWt> guard(this);
    EasyAV::MediaRecievePipeline::CbRenderer videoRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing() || !frame)
            return;
        guard->submitVideoFrame(frame);
        auto pts = frame->getPts();
        guard->invokeOnGui([pts](MediaPlayerWt &self) {
            if (!self.mSeeking)
                self.mTimeBar->setValue(std::chrono::duration_cast<std::chrono::milliseconds>(pts).count());
        });
    };
    EasyAV::MediaRecievePipeline::CbRenderer audioRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing())
            return;
        guard->mAudioRenderer.render(frame);
    };

    mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer, {ui->leFilter->text().toStdString(), mWtFilterHwConfig->getConfig()});
    mReciever.addRenderer(1, EasyAV::MediaType::audio, audioRenderer, {mAudioRenderer.buildFilterChain(), std::nullopt});

    // auto duration = liveClient->getDuration();

    mTimeBar->setRange(0, 10000);
    mTimeBar->setSingleStep(10);    // Step size of 1 second
    emit ready();

    mWtControl->reset();
}

void MediaPlayerWt::initNetworkStream(const QString &url)
{
    AVDictionary *dict = makeLiveClientOptions(url, ui->cbRtpLowDelay->isChecked());
    auto liveClient = std::make_shared<EasyAV::LiveTransportClient>(url.toStdString(), dict);
    av_dict_free(&dict);
    if (mReciever.isPipelineExists(1))
    {
        mReciever.stopPipeline(1);
        mReciever.removePipeline(1);
    }

    mReciever.setHardwareDeviceForCodec(1, mWtCodecHwConfig->getConfig());
    mReciever.setCustomVideoCodecName(1, ui->cbCodec->currentText().toStdString());
    mReciever.addMediaPipeline(1, liveClient);

    // mReciever.setFinishCb(1, [this]() {
    //     QMetaObject::invokeMethod(this, [this]() { mReciever.seekForPipeline(1, 0s); });
    // });

    if (!isVideoDetached())
        ui->wtCenter->setCurrentWidget(mWtVideoGl);
    QPointer<MediaPlayerWt> guard(this);
    EasyAV::MediaRecievePipeline::CbRenderer videoRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing() || !frame)
            return;
        guard->submitVideoFrame(frame);
        auto pts = frame->getPts();
        guard->invokeOnGui([pts](MediaPlayerWt &self) {
            if (!self.mSeeking)
                self.mTimeBar->setValue(std::chrono::duration_cast<std::chrono::milliseconds>(pts).count());
        });
    };
    EasyAV::MediaRecievePipeline::CbRenderer audioRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing())
            return;
        guard->mAudioRenderer.render(frame);
    };
    EasyAV::MediaRecievePipeline::CbRenderer overlayRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing())
            return;
        if (!frame || frame->getMediaType() != EasyAV::MediaType::data)
            return;
        std::vector<const uint8_t *> planes;
        std::vector<int> strides;
        if (frame->getDataPointers(planes, strides) && !planes.empty())
        {
            QByteArray payload(reinterpret_cast<const char *>(planes[0]), strides[0]);
            guard->invokeOnGui([payload](MediaPlayerWt &self) {
                if (self.mOverlay)
                    self.mOverlay->applyPayload(payload);
            });
        }
    };

    mReciever.addRenderer(1, EasyAV::MediaType::data, overlayRenderer);

    mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer, {ui->leFilter->text().toStdString(), mWtFilterHwConfig->getConfig()});
    mReciever.addRenderer(1, EasyAV::MediaType::audio, audioRenderer, {mAudioRenderer.buildFilterChain(), std::nullopt});

    // auto duration = liveClient->getDuration();

    mTimeBar->setRange(0, 10000);
    mTimeBar->setSingleStep(10);    // Step size of 1 second
    mReciever.playPipeline(1);
    emit ready();

    mWtControl->reset();
}

void MediaPlayerWt::initShMemStream(const std::string &memKey, const std::string &framerate, const std::string &videoSize,
                                    const std::string &pixelFormat)
{
    AVDictionary *dict = nullptr;
    av_dict_set(&dict, "video_size", videoSize.data(), 0);
    av_dict_set(&dict, "pixel_format", pixelFormat.data(), 0);
    av_dict_set(&dict, "framerate", framerate.data(), 0);
    auto shmemClient = std::make_shared<EasyAV::ShMemTransportClient>(memKey, dict);
    if (mReciever.isPipelineExists(1))
    {
        mReciever.stopPipeline(1);
        mReciever.removePipeline(1);
    }

    mReciever.setHardwareDeviceForCodec(1, mWtCodecHwConfig->getConfig());
    mReciever.setCustomVideoCodecName(1, ui->cbCodec->currentText().toStdString());
    mReciever.addMediaPipeline(1, shmemClient);

    // mReciever.setFinishCb(1, [this]() {
    //     QMetaObject::invokeMethod(this, [this]() { mReciever.seekForPipeline(1, 0s); });
    // });

    if (!isVideoDetached())
        ui->wtCenter->setCurrentWidget(mWtVideoGl);
    QPointer<MediaPlayerWt> guard(this);
    EasyAV::MediaRecievePipeline::CbRenderer videoRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing() || !frame)
            return;
        guard->submitVideoFrame(frame);
        auto pts = frame->getPts();
        guard->invokeOnGui([pts](MediaPlayerWt &self) {
            if (!self.mSeeking)
                self.mTimeBar->setValue(std::chrono::duration_cast<std::chrono::milliseconds>(pts).count());
        });
    };
    EasyAV::MediaRecievePipeline::CbRenderer audioRenderer = [guard](std::shared_ptr<EasyAV::IEasyFrame> frame) {
        if (!guard || guard->isClosing())
            return;
        guard->mAudioRenderer.render(frame);
    };

    // EasyAV::MediaRecievePipeline::CbRenderer audioRenderer =
    //     [this](std::shared_ptr<EasyAV::EasyFrame> frame) {
    //         // QMetaObject::invokeMethod(this,
    //         //                           [this, frame]() mutable { mAudioRenderer.render(frame); });
    //         mAudioRenderer.render(frame);
    //     };

    mReciever.addRenderer(1, EasyAV::MediaType::video, videoRenderer, {ui->leFilter->text().toStdString(), mWtFilterHwConfig->getConfig()});
    // mReciever.addRenderer(1,
    //                       EasyAV::MediaType::audio,
    //                       audioRenderer,
    //                       mAudioRenderer.buildFilterChain(),
    //                       std::nullopt);

    // auto duration = liveClient->getDuration();

    mTimeBar->setRange(0, 10000);
    mTimeBar->setSingleStep(10);    // Step size of 1 second
    emit ready();
}

void MediaPlayerWt::initFromOpenCvCamera(int index)
{
#ifdef BUILD_OPENCV_SUPPORT_EASYSTREAMER
    if (mCvTimer)
    {
        mCvTimer->stop();
        mCvTimer->deleteLater();
    }
    mCvCapture = std::make_unique<cv::VideoCapture>("/dev/video" + std::to_string(index));
    if (!mCvCapture->isOpened())
    {
        qCCritical(QLoggingCategory("EasyStreamer")) << "Failed to open camera:" << index;
        return;
    }

    mCvTimer = new QTimer(this);
    connect(mCvTimer, &QTimer::timeout, this, [this]() {
        cv::Mat frame;
        if (!mCvCapture->read(frame))
            return;
        auto easyFrame = EasyApi::CVEasyFrame::createFromMat(frame);
        std::shared_ptr<EasyAV::IEasyFrame> yuvFrame;
        mCvYuvFilter->process(easyFrame, yuvFrame);
        submitVideoFrame(yuvFrame);
    });
    mCvYuvFilter = std::make_unique<EasyAV::EasyFilter>("format=yuv420p");
    mCvTimer->start(30);    // ~30 FPS
    emit ready();
#endif
}

void MediaPlayerWt::initFromOpenCvFile(const QString &path)
{
#ifdef BUILD_OPENCV_SUPPORT_EASYSTREAMER
    if (mCvTimer)
    {
        mCvTimer->stop();
        mCvTimer->deleteLater();
    }
    mCvCapture = std::make_unique<cv::VideoCapture>(path.toStdString(), cv::CAP_FFMPEG);
    //  qCInfo(QLoggingCategory("EasyStreamer")) << "Backend:" << mCvCapture->getBackendName().c_str();
    if (!mCvCapture->isOpened())
    {
        qCCritical(QLoggingCategory("EasyStreamer")) << "Failed to open OpenCV file:" << path;
        return;
    }

    mCvTimer = new QTimer(this);
    connect(mCvTimer, &QTimer::timeout, this, [this]() {
        cv::Mat frame;
        if (!mCvCapture->read(frame))
            return;
        auto easyFrame = EasyApi::CVEasyFrame::createFromMat(frame);
        std::shared_ptr<EasyAV::IEasyFrame> yuvFrame;
        mCvYuvFilter->process(easyFrame, yuvFrame);
        submitVideoFrame(yuvFrame);
    });
    mCvYuvFilter = std::make_unique<EasyAV::EasyFilter>("format=yuv420p");
    mCvTimer->start(33);
    emit ready();
#endif
}

EasyAV::MediaReciever &MediaPlayerWt::reciever() { return mReciever; }

AudioRenderer &MediaPlayerWt::audioRenderer() { return mAudioRenderer; }

bool MediaPlayerWt::isClosing() const { return mClosing.load(std::memory_order_acquire); }

void MediaPlayerWt::invokeOnGui(std::function<void(MediaPlayerWt &)> cb)
{
    if (!cb || isClosing())
        return;

    QPointer<MediaPlayerWt> guard(this);
    QMetaObject::invokeMethod(
        this,
        [guard, cb = std::move(cb)]() mutable {
            if (!guard || guard->isClosing())
                return;
            cb(*guard);
        },
        Qt::QueuedConnection);
}

void MediaPlayerWt::submitVideoFrame(const std::shared_ptr<EasyAV::IEasyFrame> &frame)
{
    if (isClosing())
        return;

    if (auto widget = qobject_cast<EasyGL::VideoGlWidget *>(mWtVideoGl))
    {
        widget->submitFrame(frame);
        return;
    }

    if (auto widget = qobject_cast<EasyGL::SharedVideoGlWidget *>(mWtVideoGl))
    {
        widget->submitFrame(frame);
        return;
    }

    qCWarning(catg) << _FUNC_RAW_ << "Unsupported video widget backend";
}

void MediaPlayerWt::setupWidgets()
{
    qCDebug(catg) << _FUNC_RAW_;
    ui->wtCenter->addWidget(mWtVideoGl);
    mVideoPlaceholder = new QWidget(this);
    mVideoPlaceholder->setObjectName("videoDetachedPlaceholder");
    ui->wtCenter->addWidget(mVideoPlaceholder);
    ui->wtCenter->setCurrentWidget(mWtVideoGl);
    // ui->wtCenter->addWidget(mWtVideoQt);
    ui->loBottom->addWidget(mTimeBar, 0, 0);
    ui->loBottom->addWidget(mWtControl, 1, 0, Qt::AlignLeft);
    ui->loBottom->addWidget(mVolumeSlider, 1, 1, Qt::AlignLeft);

    ui->loHwConfig->addWidget(mWtCodecHwConfig);
    ui->loHwConfigFilter->addWidget(mWtFilterHwConfig);

    mOverlay = new OverlayWidget(mWtVideoGl);
    mOverlay->setGeometry(mWtVideoGl->rect());
    mOverlay->setParent(mWtVideoGl);
    mOverlay->hide();     // по умолчанию скрыт
    mOverlay->raise();    // поверх видео
    // по умолчанию не перехватывает клики (см. OverlayWidget::setEnabledForClicks)
    mOverlay->setEnabledForClicks(false);

    // следим за изменением размера mWtVideoGl чтобы подгонять overlay
    mWtVideoGl->installEventFilter(this);

    // сигнал из overlay о клике (payload) — можно подключить к отправке в encoder/pipeline
    connect(mOverlay, &OverlayWidget::overlayClicked, this, [this](const QByteArray &payload) { emit overlayPayloadReady(payload); });

    ui->cbCodec->insertItem(0, "auto");
    ui->cbCodec->insertItem(1, "h264");
    ui->cbCodec->insertItem(2, "h265");
    // ui->cbCodec->insertItem(3, "h264_vaapi");
    // ui->cbCodec->insertItem(4, "h264_rkmpp");
    ui->cbCodec->insertItem(5, "h264_cuvid");
    // TODO add more

    mWtControl->setPlayCallback([this]() -> bool { return onPlay(); });
    mWtControl->setStopCallback([this]() { return onStop(); });
    mWtControl->setPauseCallback([this]() { return onPause(); });
    mWtControl->setResumeCallback([this]() { return onResume(); });

    mVolumeSlider->setMaximum(100);
    mVolumeSlider->setMinimum(0);
    mVolumeSlider->setSliderPosition(100);

    connect(mVolumeSlider, &QSlider::sliderMoved, this, [this](int pos) {
        qreal volume = static_cast<qreal>(pos) / 100.0;
        mAudioRenderer.getAudioSink()->setVolume(volume);
    });
    connect(mTimeBar, &QSlider::sliderReleased, this, [=, this]() {
        mReciever.seekForPipeline(1, std::chrono::milliseconds(mTimeBar->value()));
        mSeeking = false;
    });
    connect(mTimeBar, &QSlider::sliderPressed, this, [=, this]() { mSeeking = true; });
    mTimeBar->setObjectName("timeBar_sof");
}

void MediaPlayerWt::loadSettings()
{
    // TODO
    //  ui->leDeviceName->setText("/dev/dri/renderD128");
    //  ui->cbHwAccel->setCurrentText("vaapi"); //vaapi;
    mWtCodecHwConfig->setConfig(EasyAV::HardwareConfig{EasyAV::HWDeviceType::vaapi, "/dev/dri/renderD128"});
    mWtFilterHwConfig->setConfig(EasyAV::HardwareConfig{EasyAV::HWDeviceType::vaapi, "/dev/dri/renderD128"});
    ui->leFilter->setText("");
}

void MediaPlayerWt::saveSettings()
{
    // TODO
}

bool MediaPlayerWt::onPlay() { return mReciever.playPipeline(1); }

void MediaPlayerWt::onPause() { mReciever.pausePipeline(1); }

void MediaPlayerWt::onResume() { mReciever.resumePipeline(1); }

void MediaPlayerWt::onStop() { mReciever.stopPipeline(1); }

void MediaPlayerWt::setOverlayEnabled(bool enabled)
{
    mOverlayEnabled = enabled;
    if (!mOverlay)
        return;
    mOverlay->setEnabledForClicks(enabled);
    if (enabled)
        mOverlay->show();
    else
        mOverlay->hide();
}

QByteArray MediaPlayerWt::getOverlayData() { return mOverlay->getPayload(); }

bool MediaPlayerWt::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == mDetachedVideoDialog && event->type() == QEvent::Close)
    {
        setVideoDetached(false);
        event->ignore();
        return true;
    }

    if (watched == mWtVideoGl && event->type() == QEvent::Resize)
    {
        if (mOverlay)
        {
            mOverlay->setGeometry(mWtVideoGl->rect());
            mOverlay->raise();
        }
    }
    return QWidget::eventFilter(watched, event);
}

bool MediaPlayerWt::getOverlayEnabled() const { return mOverlayEnabled; }

QImage MediaPlayerWt::makeSwScaledImage(AVFrame *frameNV12, int scaledWidth, int scaledHeight)
{
    qCDebug(catgV) << _FUNC_RAW_;

    if (!frameNV12 || frameNV12->width <= 0 || frameNV12->height <= 0)
    {
        qCWarning(catg) << "Invalid input frame. Frame is null or has non-positive dimensions:" << "width="
                        << (frameNV12 ? frameNV12->width : -1) << "height=" << (frameNV12 ? frameNV12->height : -1);
        return {};
    }
    AVFrame *frameRGB = av_frame_alloc();
    if (!frameRGB)
    {
        qCWarning(catg) << "Failed to allocate AVFrame for RGB conversion.";
        return {};
    }

    SwsContext *convert_context = sws_getContext(frameNV12->width, frameNV12->height, AVPixelFormat(frameNV12->format), scaledWidth,
                                                 scaledHeight, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);

    if (!convert_context)
    {
        qCWarning(catg) << "Failed to create SwsContext:" << "srcWidth=" << frameNV12->width << "srcHeight=" << frameNV12->height
                        << "srcFormat=" << frameNV12->format << "dstWidth=" << scaledWidth << "dstHeight=" << scaledHeight
                        << "dstFormat=RGB24";
        av_frame_free(&frameRGB);
        return {};
    }

    frameRGB->width = scaledWidth;
    frameRGB->height = scaledHeight;
    frameRGB->format = AVPixelFormat::AV_PIX_FMT_RGB24;

    if (av_image_alloc(frameRGB->data, frameRGB->linesize, frameRGB->width, frameRGB->height, AVPixelFormat::AV_PIX_FMT_RGB24, 16) < 0)
    {
        qCWarning(catg) << "Failed to allocate image buffer for RGB frame:" << "width=" << frameRGB->width << "height=" << frameRGB->height
                        << "format=RGB24";
        sws_freeContext(convert_context);
        av_frame_free(&frameRGB);
        return {};
    }

    sws_scale(convert_context, frameNV12->data, frameNV12->linesize, 0, frameNV12->height, frameRGB->data, frameRGB->linesize);
    sws_freeContext(convert_context);

    QImage rawImage(frameRGB->width, frameRGB->height, QImage::Format_RGB888);
    if (rawImage.isNull())
    {
        qCWarning(catg) << "Failed to create QImage from RGB frame:" << "width=" << frameRGB->width << "height=" << frameRGB->height;
        av_freep(&frameRGB->data[0]);
        av_frame_free(&frameRGB);
        return {};
    }

    for (std::size_t y = 0; y < frameRGB->height; ++y)
    {
        memcpy(rawImage.scanLine(y), frameRGB->data[0] + y * frameRGB->linesize[0], frameRGB->width * 3);
    }

    av_freep(&frameRGB->data[0]);
    av_frame_free(&frameRGB);

    return rawImage;
}