blob: dfd095bbbf98341ae07f427a279302ad33474c84 [file] [log] [blame]
/*
* Copyright (C) 2019-2020 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "rendermanager.h"
#include <QtConcurrent/QtConcurrent>
#include <stdexcept>
using namespace lrc::api;
FrameWrapper::FrameWrapper(AVModel &avModel, const QString &id)
: avModel_(avModel)
, id_(id)
, isRendering_(false)
{}
FrameWrapper::~FrameWrapper()
{
if (id_ == video::PREVIEW_RENDERER_ID) {
avModel_.stopPreview();
}
}
void
FrameWrapper::connectStartRendering()
{
QObject::disconnect(renderConnections_.started);
renderConnections_.started = QObject::connect(&avModel_,
&AVModel::rendererStarted,
this,
&FrameWrapper::slotRenderingStarted);
}
bool
FrameWrapper::startRendering()
{
try {
renderer_ = const_cast<video::Renderer *>(&avModel_.getRenderer(id_));
} catch (std::out_of_range &e) {
qWarning() << e.what();
return false;
}
QObject::disconnect(renderConnections_.updated);
QObject::disconnect(renderConnections_.stopped);
renderConnections_.updated = QObject::connect(&avModel_,
&AVModel::frameUpdated,
this,
&FrameWrapper::slotFrameUpdated);
renderConnections_.stopped = QObject::connect(&avModel_,
&AVModel::rendererStopped,
this,
&FrameWrapper::slotRenderingStopped);
return true;
}
QImage *
FrameWrapper::getFrame()
{
return image_.get();
}
bool
FrameWrapper::isRendering()
{
return isRendering_;
}
void
FrameWrapper::slotRenderingStarted(const QString &id)
{
if (id != id_) {
return;
}
if (!startRendering()) {
qWarning() << "Couldn't start rendering for id: " << id_;
return;
}
isRendering_ = true;
emit renderingStarted(id);
}
void
FrameWrapper::slotFrameUpdated(const QString &id)
{
if (id != id_) {
return;
}
if (!renderer_ || !renderer_->isRendering()) {
return;
}
{
QMutexLocker lock(&mutex_);
frame_ = renderer_->currentFrame();
unsigned int width = renderer_->size().width();
unsigned int height = renderer_->size().height();
#ifndef Q_OS_LINUX
unsigned int size = frame_.storage.size();
/*
* If the frame is empty or not the expected size,
* do nothing and keep the last rendered QImage.
*/
if (size != 0 && size == width * height * 4) {
buffer_ = std::move(frame_.storage);
image_.reset(new QImage((uchar *) buffer_.data(),
width,
height,
QImage::Format_ARGB32_Premultiplied));
#else
if (frame_.ptr) {
image_.reset(new QImage(frame_.ptr, width, height, QImage::Format_ARGB32));
#endif
}
}
emit frameUpdated(id);
}
void
FrameWrapper::slotRenderingStopped(const QString &id)
{
if (id != id_) {
return;
}
QObject::disconnect(renderConnections_.updated);
QObject::disconnect(renderConnections_.stopped);
renderer_ = nullptr;
/*
* The object's QImage pointer is reset before renderingStopped
* is emitted, allowing the listener to invoke specific behavior
* like clearing the widget or changing the UI entirely.
*/
image_.reset();
isRendering_ = false;
emit renderingStopped(id);
}
RenderManager::RenderManager(AVModel &avModel)
: avModel_(avModel)
{
deviceListSize_ = avModel_.getDevices().size();
connect(&avModel_, &lrc::api::AVModel::deviceEvent, this, &RenderManager::slotDeviceEvent);
previewFrameWrapper_ = std::make_unique<FrameWrapper>(avModel_);
QObject::connect(previewFrameWrapper_.get(),
&FrameWrapper::renderingStarted,
[this](const QString &id) {
Q_UNUSED(id);
emit previewRenderingStarted();
});
QObject::connect(previewFrameWrapper_.get(),
&FrameWrapper::frameUpdated,
[this](const QString &id) {
Q_UNUSED(id);
emit previewFrameUpdated();
});
QObject::connect(previewFrameWrapper_.get(),
&FrameWrapper::renderingStopped,
[this](const QString &id) {
Q_UNUSED(id);
emit previewRenderingStopped();
});
previewFrameWrapper_->connectStartRendering();
}
RenderManager::~RenderManager()
{
previewFrameWrapper_.reset();
for (auto &dfw : distantFrameWrapperMap_) {
dfw.second.reset();
}
}
bool
RenderManager::isPreviewing()
{
return previewFrameWrapper_->isRendering();
}
QImage *
RenderManager::getPreviewFrame()
{
return previewFrameWrapper_->getFrame();
}
void
RenderManager::stopPreviewing(bool async)
{
if (!previewFrameWrapper_->isRendering()) {
return;
}
if (async) {
QtConcurrent::run([this] { avModel_.stopPreview(); });
} else {
avModel_.stopPreview();
}
}
void
RenderManager::startPreviewing(bool force, bool async)
{
if (previewFrameWrapper_->isRendering() && !force) {
return;
}
auto restart = [this] {
if (previewFrameWrapper_->isRendering()) {
avModel_.stopPreview();
}
avModel_.startPreview();
};
if (async) {
QtConcurrent::run(restart);
} else {
restart();
}
}
QImage *
RenderManager::getFrame(const QString &id)
{
auto dfwIt = distantFrameWrapperMap_.find(id);
if (dfwIt != distantFrameWrapperMap_.end()) {
return dfwIt->second->getFrame();
}
return nullptr;
}
void
RenderManager::addDistantRenderer(const QString &id)
{
/*
* Check if a FrameWrapper with this id exists.
*/
auto dfwIt = distantFrameWrapperMap_.find(id);
if (dfwIt != distantFrameWrapperMap_.end()) {
if (!dfwIt->second->startRendering()) {
qWarning() << "Couldn't start rendering for id: " << id;
}
} else {
auto dfw = std::make_unique<FrameWrapper>(avModel_, id);
/*
* Connect this to the FrameWrapper.
*/
distantConnectionMap_[id].started = QObject::connect(dfw.get(),
&FrameWrapper::renderingStarted,
[this](const QString &id) {
emit distantRenderingStarted(id);
});
distantConnectionMap_[id].updated = QObject::connect(dfw.get(),
&FrameWrapper::frameUpdated,
[this](const QString &id) {
emit distantFrameUpdated(id);
});
distantConnectionMap_[id].stopped = QObject::connect(dfw.get(),
&FrameWrapper::renderingStopped,
[this](const QString &id) {
emit distantRenderingStopped(id);
});
/*
* Connect FrameWrapper to avmodel.
*/
dfw->connectStartRendering();
/*
* Add to map.
*/
distantFrameWrapperMap_.insert(std::make_pair(id, std::move(dfw)));
}
}
void
RenderManager::removeDistantRenderer(const QString &id)
{
auto dfwIt = distantFrameWrapperMap_.find(id);
if (dfwIt != distantFrameWrapperMap_.end()) {
/*
* Disconnect FrameWrapper from this.
*/
auto dcIt = distantConnectionMap_.find(id);
if (dcIt != distantConnectionMap_.end()) {
QObject::disconnect(dcIt->second.started);
QObject::disconnect(dcIt->second.updated);
QObject::disconnect(dcIt->second.stopped);
}
/*
* Erase.
*/
distantFrameWrapperMap_.erase(dfwIt);
}
}
void
RenderManager::slotDeviceEvent()
{
auto defaultDevice = avModel_.getDefaultDevice();
auto currentCaptureDevice = avModel_.getCurrentVideoCaptureDevice();
/*
* Decide whether a device has plugged, unplugged, or nothing has changed.
*/
auto deviceList = avModel_.getDevices();
auto currentDeviceListSize = deviceList.size();
DeviceEvent deviceEvent{DeviceEvent::None};
if (currentDeviceListSize > deviceListSize_) {
deviceEvent = DeviceEvent::Added;
} else if (currentDeviceListSize < deviceListSize_) {
/*
* Check if the currentCaptureDevice is still in the device list.
*/
if (std::find(std::begin(deviceList), std::end(deviceList), currentCaptureDevice)
== std::end(deviceList)) {
deviceEvent = DeviceEvent::RemovedCurrent;
}
}
if (previewFrameWrapper_->isRendering()) {
if (currentDeviceListSize == 0) {
avModel_.clearCurrentVideoCaptureDevice();
avModel_.switchInputTo({});
stopPreviewing();
} else if (deviceEvent == DeviceEvent::RemovedCurrent && currentDeviceListSize > 0) {
avModel_.setCurrentVideoCaptureDevice(defaultDevice);
startPreviewing(true);
} else {
startPreviewing();
}
} else if (deviceEvent == DeviceEvent::Added && currentDeviceListSize == 1) {
avModel_.setCurrentVideoCaptureDevice(defaultDevice);
}
emit videoDeviceListChanged();
deviceListSize_ = currentDeviceListSize;
}