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


#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>

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<IVideoProcessor *>(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 "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_ARAVIS_SUPPORT_EASYSTREAMER
#include "easyapi_arvadapter/arveasyframe.h"
#endif
#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::initAravisCamera(const std::optional<QString> &cameraName)
{
#ifdef BUILD_ARAVIS_SUPPORT_EASYSTREAMER
    if (mAravisTimer)
    {
        mAravisTimer->stop();
        mAravisTimer->deleteLater();
        mAravisTimer = nullptr;
    }
    arv_update_device_list();

    qDebug() << "Attempting to open camera:" << cameraName;
    unsigned int n_devices = arv_get_n_devices();
    qDebug() << "Found Aravis devices:" << n_devices;

    QByteArray idBytes;
    const char *targetId = nullptr;
    if (cameraName.has_value() && !cameraName->isEmpty())
    {
        idBytes = cameraName->toUtf8();
        targetId = idBytes.constData();
        qDebug() << "Searching for specific camera:" << targetId;
    }

    GError *error = nullptr;
    ArvCamera *camera = arv_camera_new(targetId, &error);
    if (error != nullptr)
    {
        qCCritical(QLoggingCategory("EasyStreamer")) << "Failed to open Aravis camera:" << error->message;
        g_clear_error(&error);
        return;
    }

    GError* setErr = nullptr;
    arv_camera_set_pixel_format(camera, ARV_PIXEL_FORMAT_MONO_8, &setErr);
    if (setErr)
    {
        qCWarning(QLoggingCategory("EasyStreamer")) << "Could not set формат:" << setErr->message;
        g_clear_error(&setErr);
    }

    ArvStream *stream = arv_camera_create_stream(camera, nullptr, nullptr, &error);
    if (error != nullptr)
    {
        qCCritical(QLoggingCategory("EasyStreamer")) << "Failed to create stream:" << error->message;
        g_object_unref(camera);
        g_clear_error(&error);
        return;
    }

    gint payload_size = arv_camera_get_payload(camera, &error);
    for (int i = 0; i < 30; i++)
    {
        arv_stream_push_buffer(stream, arv_buffer_new(payload_size, nullptr));
    }

    arv_camera_start_acquisition(camera, &error);

    QPointer<MediaPlayerWt> guard(this);

    mAravisTimer = new QTimer(this);
    connect(mAravisTimer, &QTimer::timeout, this, [guard, stream]() {
        if (!guard || guard->isClosing())
            return;

        ArvBuffer *buffer = arv_stream_pop_buffer(stream);
        if (buffer != nullptr)
        {
            if (arv_buffer_get_status(buffer) == ARV_BUFFER_STATUS_SUCCESS)
            {
                auto easyFrame = std::make_shared<EasyApi::ArvEasyFrame>(buffer);
                guard->submitVideoFrame(easyFrame);
            }
            arv_stream_push_buffer(stream, buffer);
        }
    });

    connect(mAravisTimer, &QObject::destroyed, [camera, stream]() {
        arv_camera_stop_acquisition(camera, nullptr);
        g_object_unref(stream);
        g_object_unref(camera);
    });

    mAravisTimer->start(10);
    emit ready();
#endif
}

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;
}#include "easyav/reciever/mediareciever.h"
#include "pure_common/logtools.h"

RAW_DEVELOPER_LOG_CATGS(easyav, MediaReciever)

using namespace EasyAV;

MediaReciever::MediaReciever()
    : mDecodersFactory()
{
    RawLogger::debug(catgMem) << _P_FUNC_RAW_;
    av_log_set_level(AV_LOG_WARNING);
#ifdef NOFFMPEGLOG
    av_log_set_level(AV_LOG_QUIET);
#endif
}

MediaReciever::~MediaReciever()
{
    RawLogger::debug(catgMem) << _P_FUNC_RAW_;
    stopThreads();
}
bool MediaReciever::pipelineExists(PipelineId id)
{
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        return false;
    }
    return true;
}

bool MediaReciever::addMediaPipeline(PipelineId id, std::shared_ptr<ITransportClient> client)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_ << id;
    if (!mThreadsInited)
        initThreads();
    if (!mThreadPools.decodingVideoThreadPool || !mThreadPools.decodingAudioThreadPool || !mThreadPools.decodingMiscThreadPool ||
        !mThreadPools.demuxThreadPool || !mThreadPools.syncThreadPool || !mThreadPools.filterThreadPool)
    {
        RawLogger::debug(catg) << _P_FUNC_RAW_ << "can't create pipeline, threads are not initialized";
        return false;
    }
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto mediaPipeline = std::make_shared<MediaRecievePipeline>(id, client, mDecodersFactory, mThreadPools);
    mPipelines.insert({id, mediaPipeline});
    return true;
}

// std::mutex &MediaReciever::getGpuMutex(PipelineId id, int streamIdx)
// {
//     auto itPipe = mPipelines.find(id);
//     if (itPipe == mPipelines.end())
//     {
//         RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
//     }
//     return itPipe->second->mStreamsById[streamIdx].decoder->gpuMutex;
// }

std::vector<StreamInfo> MediaReciever::getStreamInfos(PipelineId id)
{
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return {};
    }
    return itPipe->second->getStreamInfos();
}

bool MediaReciever::addRenderer(PipelineId id, SourceIdx streamIdx, const MediaRecievePipeline::CbRenderer &renderer)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_ << id << streamIdx << static_cast<bool>(renderer);
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->addRenderer(streamIdx, renderer);
    return true;
}

bool MediaReciever::addRenderer(PipelineId id, MediaType mediaType, const MediaRecievePipeline::CbRenderer &renderer)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_ << id << toString(mediaType) << static_cast<bool>(renderer);
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->addRenderer(mediaType, renderer);
    return true;
}

bool MediaReciever::addRenderer(PipelineId id, MediaType mediaType, const MediaRecievePipeline::CbRenderer &renderer,
                                const FilterConfig &filterConfig)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_ << id << toString(mediaType) << static_cast<bool>(renderer);
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->addRenderer(mediaType, renderer, filterConfig);
    return true;
}

bool MediaReciever::addRenderer(PipelineId id, SourceIdx streamIdx, const MediaRecievePipeline::CbRenderer &renderer,
                                const FilterConfig &filterConfig)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_ << id << streamIdx << static_cast<bool>(renderer);
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->addRenderer(streamIdx, renderer, filterConfig);
    return true;
}

void MediaReciever::startThreads()
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    if (mThreadPools.demuxThreadPool)
        mThreadPools.demuxThreadPool->start();
    if (mThreadPools.decodingAudioThreadPool)
        mThreadPools.decodingAudioThreadPool->start();
    if (mThreadPools.decodingVideoThreadPool)
        mThreadPools.decodingVideoThreadPool->start();
    if (mThreadPools.decodingMiscThreadPool)
        mThreadPools.decodingMiscThreadPool->start();
    if (mThreadPools.syncThreadPool)
        mThreadPools.syncThreadPool->start();
    if (mThreadPools.filterThreadPool)
        mThreadPools.filterThreadPool->start();
}

void MediaReciever::stopThreads()
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    if (mThreadPools.demuxThreadPool)
        mThreadPools.demuxThreadPool->stop();
    if (mThreadPools.decodingAudioThreadPool)
        mThreadPools.decodingAudioThreadPool->stop();
    if (mThreadPools.decodingVideoThreadPool)
        mThreadPools.decodingVideoThreadPool->stop();
    if (mThreadPools.decodingMiscThreadPool)
        mThreadPools.decodingMiscThreadPool->stop();
    if (mThreadPools.syncThreadPool)
        mThreadPools.syncThreadPool->stop();
    if (mThreadPools.filterThreadPool)
        mThreadPools.filterThreadPool->stop();
}

void MediaReciever::stopAllPipelines()
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    for (auto &[_, pipe] : mPipelines)
    {
        pipe->stop(EasyAV::MediaRecievePipeline::StopMode::fullReset);
    }
}

bool MediaReciever::isPipelineExists(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        return false;
    }
    return true;
}

void MediaReciever::seekForPipeline(PipelineId id, std::chrono::milliseconds timepoint)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return;
    }
    auto pipe = itPipe->second;
    pipe->seek(timepoint);
}

void MediaReciever::setFinishCb(PipelineId id, std::function<void()> cbOnFinish)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return;
    }
    auto pipe = itPipe->second;
    pipe->setCbOnFinish(cbOnFinish);
}
bool MediaReciever::initPipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    return pipe->init();
}

bool MediaReciever::playPipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    std::shared_ptr<MediaRecievePipeline> pipe = itPipe->second;
    if (pipe->getState() == MediaRecievePipeline::State::idle)
        pipe->init();
    return pipe->play();
}

bool MediaReciever::stopPipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->stop(EasyAV::MediaRecievePipeline::StopMode::keepReady);
    return true;
}

bool MediaReciever::pausePipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->pause();
    return true;
}

bool MediaReciever::resumePipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->resume();
    return true;
}

bool MediaReciever::removePipeline(PipelineId id)
{
    RawLogger::debug(catg) << _P_FUNC_RAW_;
    std::unique_lock<std::mutex> ml(mPipelinesMutex);
    auto itPipe = mPipelines.find(id);
    if (itPipe == mPipelines.end())
    {
        RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
        return false;
    }
    auto pipe = itPipe->second;
    pipe->stop(EasyAV::MediaRecievePipeline::StopMode::fullReset);

    mPipelines.erase(itPipe);
    return true;
}

void MediaReciever::initThreads()
{
    uint8_t demuxThreadCount{1};
    uint8_t videoDecodingThreadCount{2};
    uint8_t audioDecodingThreadCount{1};
    uint8_t miscDecodingThreadCount{1};
    uint8_t syncThreadCount{1};
    uint8_t filterThreadCount{1};
    mThreadPools.demuxThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    mThreadPools.decodingVideoThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    mThreadPools.decodingAudioThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    mThreadPools.decodingMiscThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    mThreadPools.syncThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    mThreadPools.filterThreadPool = std::make_shared<WorkingTools::ThreadPool>();
    for (int i = 0; i < demuxThreadCount; i++)
    {
        mThreadPools.demuxThreadPool->addThread(1ms);
    }
    for (int i = 0; i < videoDecodingThreadCount; i++)
    {
        mThreadPools.decodingVideoThreadPool->addThread(1ms);
    }
    for (int i = 0; i < audioDecodingThreadCount; i++)
    {
        mThreadPools.decodingAudioThreadPool->addThread(1ms);
    }
    for (int i = 0; i < miscDecodingThreadCount; i++)
    {
        mThreadPools.decodingMiscThreadPool->addThread(5ms);
    }
    for (int i = 0; i < syncThreadCount; i++)
    {
        mThreadPools.syncThreadPool->addThread(1ms);
    }
    for (int i = 0; i < filterThreadCount; i++)
    {
        mThreadPools.filterThreadPool->addThread(1ms);
    }
    startThreads();
    mThreadsInited = true;
}

void MediaReciever::setHardwareDeviceForCodec(PipelineId id, HardwareConfig hwConfig) { mDecodersFactory.setHardwareDevice(id, hwConfig); }

void MediaReciever::setCustomVideoCodecName(PipelineId id, std::string customCodecName)
{
    mDecodersFactory.setVideoCodecName(id, customCodecName);
}

// bool MediaReciever::addCustomStream(PipelineId id, EasyAV::StreamInfo &info)
// {
//     RawLogger::debug(catg) << _P_FUNC_RAW_ << id;
//     auto itPipe = mPipelines.find(id);
//     if (itPipe == mPipelines.end())
//     {
//         RawLogger::critical(catg) << _P_FUNC_RAW_ << "can't find pipeline " << id;
//         return false;
//     }
//     return itPipe->second->addCustomStream(info);
// }

в cameramanager заменить получение на mediareciever с rtpcliet посмотри как реализровано в  MediaPlayerWt с rtp