Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
  • beta/202506161038
  • stable/20250613.0
  • nightly/20250613.0
  • beta/202506101658
  • stable/20250610.0
  • nightly/20250610.0
  • beta/202506091027
  • beta/202506061543
  • nightly/20250605.0
  • beta/202506051039
  • beta/202506051002
  • beta/202506041611
  • beta/202506041335
  • beta/202505231812
  • stable/20250523.0
  • nightly/20250523.0
  • nightly/20250515.0
  • nightly/20250510.0
  • nightly/20250509.1
  • nightly/20250509.0
21 results

avmodel.cpp

  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    avmodel.cpp 29.53 KiB
    /*
     *  Copyright (C) 2018-2023 Savoir-faire Linux Inc.
     *  Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
     *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
     *
     *  This library is free software; you can redistribute it and/or
     *  modify it under the terms of the GNU Lesser General Public
     *  License as published by the Free Software Foundation; either
     *  version 2.1 of the License, or (at your option) any later version.
     *
     *  This library 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
     *  Lesser 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 "api/avmodel.h"
    
    #include "api/video.h"
    #include "api/lrc.h"
    #ifdef ENABLE_LIBWRAP
    #include "directrenderer.h"
    #else
    #include "shmrenderer.h"
    #endif
    #include "callbackshandler.h"
    #include "dbus/callmanager.h"
    #include "dbus/configurationmanager.h"
    #include "dbus/videomanager.h"
    #include "authority/storagehelper.h"
    
    #include <media_const.h>
    
    #include <QtCore/QStandardPaths>
    #include <QtCore/QDir>
    #include <QUrl>
    #include <QSize>
    #include <QReadWriteLock>
    
    #include <algorithm> // std::sort
    #include <chrono>
    #include <csignal>
    #include <iomanip> // for std::put_time
    #include <fstream>
    #include <mutex>
    #include <thread>
    #include <string>
    #include <sstream>
    
    #if defined(Q_OS_UNIX) && !defined(__APPLE__)
    #include <xcb/xcb.h>
    #endif
    #ifdef WIN32
    #include "Windows.h"
    #include <tchar.h>
    #include <dwmapi.h>
    #endif
    
    namespace lrc {
    
    using namespace api;
    using namespace api::video;
    using namespace lrc::video;
    
    class AVModelPimpl : public QObject
    {
        Q_OBJECT
    public:
        AVModelPimpl(AVModel& linked, const CallbacksHandler& callbacksHandler);
    
        const CallbacksHandler& callbacksHandler;
        QString getRecordingPath() const;
        static const QString recorderSavesSubdir;
        AVModel& linked_;
    
        QReadWriteLock renderersMutex_;
        std::map<QString, std::unique_ptr<Renderer>> renderers_;
        QString currentVideoCaptureDevice_ {};
    
    #ifndef ENABLE_LIBWRAP
        // TODO: Init Video Renderers from daemon (see:
        // https://git.jami.net/savoirfairelinux/ring-daemon/issues/59)
        static void stopCameraAndQuit(int);
        static uint32_t SIZE_RENDERER;
    #endif
    
        /**
         * Get device via its type
         * @param type
         * @return the device name
         */
        QString getDevice(int type) const;
    
        /**
         * Add video::Renderer to renderers_ and start it
         * @param id
         * @param size
         * @param shmPath
         */
        void addRenderer(const QString& id, const QSize& res, const QString& shmPath = {});
    
        /**
         * Remove renderer from renderers_. If the returned renderer is ignored, it
         * will be deleted.
         * @param id
         * @return the renderer
         */
        std::unique_ptr<Renderer> removeRenderer(const QString& id);
    
        bool hasRenderer(const QString& id);
        QSize getRendererSize(const QString& id);
        Frame getRendererFrame(const QString& id);
    
    public Q_SLOTS:
        /**
         * Listen from CallbacksHandler when a renderer starts
         * @param id
         * @param shmPath
         * @param width
         * @param height
         */
        void onDecodingStarted(const QString& id, const QString& shmPath, int width, int height);
        /**
         * Listen from CallbacksHandler when a renderer stops
         * @param id
         * @param shmPath
         */
        void onDecodingStopped(const QString& id, const QString& shmPath);
        /**
         * Detect when a video device is plugged or unplugged
         */
        void slotDeviceEvent();
        /**
         * Detect when an audio device is plugged or unplugged
         */
        void slotAudioDeviceEvent();
        /**
         * Audio volume level
         * @param id Ringbuffer id
         * @param level Volume in range [0, 1]
         */
        void slotAudioMeter(const QString& id, float level);
        /**
         * Listen from CallbacksHandler when a recorder stopped notice is incoming
         * @param filePath
         */
        void slotRecordPlaybackStopped(const QString& filePath);
    };
    
    const QString AVModelPimpl::recorderSavesSubdir = "sent_data";
    #ifndef ENABLE_LIBWRAP
    uint32_t AVModelPimpl::SIZE_RENDERER = 0;
    #endif
    
    AVModel::AVModel(const CallbacksHandler& callbacksHandler)
        : QObject(nullptr)
        , pimpl_(std::make_unique<AVModelPimpl>(*this, callbacksHandler))
    {
    #ifndef ENABLE_LIBWRAP
        // Because the client uses DBUS, if a crash occurs, the daemon will not
        // be able to know it. So, stop the camera if the user was just previewing.
        std::signal(SIGSEGV, AVModelPimpl::stopCameraAndQuit);
        std::signal(SIGINT, AVModelPimpl::stopCameraAndQuit);
    #endif
    }
    
    AVModel::~AVModel()
    {
        QWriteLocker lk(&pimpl_->renderersMutex_);
        for (auto r = pimpl_->renderers_.begin(); r != pimpl_->renderers_.end(); ++r) {
            (*r).second.reset();
        }
    }
    
    QList<MapStringString>
    AVModel::getRenderersInfo(QString id)
    {
        QList<MapStringString> infoList;
        QReadLocker lk(&pimpl_->renderersMutex_);
        for (auto r = pimpl_->renderers_.begin(); r != pimpl_->renderers_.end(); r++) {
            MapStringString qmap;
            auto& rend = r->second;
            MapStringString mapInfo = rend->getInfos();
            if (id.isEmpty() || mapInfo["RENDERER_ID"] == id) {
                qmap.insert(rend->RES, mapInfo["RES"]);
                qmap.insert(rend->RENDERER_ID, mapInfo["RENDERER_ID"]);
                qmap.insert(rend->FPS, mapInfo["FPS"]);
                infoList.append(qmap);
            }
        }
        return infoList;
        return {};
    }
    
    void
    AVModel::updateRenderersFPSInfo(QString rendererId)
    {
        QReadLocker lk(&pimpl_->renderersMutex_);
        auto it = std::find_if(pimpl_->renderers_.begin(),
                               pimpl_->renderers_.end(),
                               [&rendererId](const auto& c) {
                                   return rendererId == c.second->getInfos()["RENDERER_ID"];
                               });
        if (it != pimpl_->renderers_.end()) {
            auto fpsInfo = qMakePair(rendererId, it->second->getInfos()["FPS"]);
            lk.unlock();
            Q_EMIT onRendererFpsChange(fpsInfo);
        }
    }
    
    bool
    AVModel::getDecodingAccelerated() const
    {
        bool result = VideoManager::instance().getDecodingAccelerated();
        return result;
    }
    
    void
    AVModel::setDecodingAccelerated(bool accelerate)
    {
        VideoManager::instance().setDecodingAccelerated(accelerate);
    }
    
    bool
    AVModel::getEncodingAccelerated() const
    {
        bool result = VideoManager::instance().getEncodingAccelerated();
        return result;
    }
    
    void
    AVModel::setEncodingAccelerated(bool accelerate)
    {
        VideoManager::instance().setEncodingAccelerated(accelerate);
    }
    
    bool
    AVModel::getHardwareAcceleration() const
    {
        bool result = getDecodingAccelerated() && getEncodingAccelerated();
        return result;
    }
    void
    AVModel::setHardwareAcceleration(bool accelerate)
    {
        setDecodingAccelerated(accelerate);
        setEncodingAccelerated(accelerate);
    }
    
    QVector<QString>
    AVModel::getDevices() const
    {
        QStringList devices = VideoManager::instance().getDeviceList();
        VectorString result;
        for (const auto& device : devices) {
            result.push_back(device);
        }
        return (QVector<QString>) result;
    }
    
    QString
    AVModel::getDefaultDevice() const
    {
        return VideoManager::instance().getDefaultDevice();
    }
    
    void
    AVModel::setDefaultDevice(const QString& deviceId)
    {
        VideoManager::instance().setDefaultDevice(deviceId);
    }
    
    Settings
    AVModel::getDeviceSettings(const QString& deviceId) const
    {
        if (deviceId.isEmpty()) {
            return video::Settings();
        }
        MapStringString settings = VideoManager::instance().getSettings(deviceId);
        if (settings["id"] != deviceId) {
            throw std::out_of_range("Device '" + deviceId.toStdString() + "' not found");
        }
        video::Settings result;
        result.name = settings["name"];
        result.id = settings["id"];
        result.channel = settings["channel"];
        result.size = settings["size"];
        result.rate = settings["rate"].toFloat();
        return result;
    }
    
    Capabilities
    AVModel::getDeviceCapabilities(const QString& deviceId) const
    {
        // Channel x Resolution x Framerate
        QMap<QString, QMap<QString, QVector<QString>>> capabilites = VideoManager::instance()
                                                                         .getCapabilities(deviceId);
        video::Capabilities result;
        for (auto& channel : capabilites.toStdMap()) {
            video::ResRateList channelCapabilities;
            for (auto& resToRates : channel.second.toStdMap()) {
                video::FrameratesList rates;
                QVectorIterator<QString> itRates(resToRates.second);
                while (itRates.hasNext()) {
                    rates.push_back(itRates.next().toFloat());
                }
                std::sort(rates.begin(), rates.end(), std::greater<int>());
                channelCapabilities.push_back(qMakePair(resToRates.first, rates));
            }
            // sort by resolution widths
            std::sort(channelCapabilities.begin(),
                      channelCapabilities.end(),
                      [](const QPair<video::Resolution, video::FrameratesList>& lhs,
                         const QPair<video::Resolution, video::FrameratesList>& rhs) {
                          auto lhsWidth = lhs.first.left(lhs.first.indexOf("x")).toLongLong();
                          auto rhsWidth = rhs.first.left(rhs.first.indexOf("x")).toLongLong();
                          return lhsWidth > rhsWidth;
                      });
            result.insert(channel.first, channelCapabilities);
        }
        return result;
    }
    
    void
    AVModel::setDeviceSettings(video::Settings& settings)
    {
        MapStringString newSettings;
        auto rate = QString::number(settings.rate, 'f', 7);
        rate = rate.left(rate.length() - 1);
        newSettings["channel"] = settings.channel;
        newSettings["name"] = settings.name;
        newSettings["id"] = settings.id;
        newSettings["rate"] = rate;
        newSettings["size"] = settings.size;
        VideoManager::instance().applySettings(settings.id, newSettings);
    
        // If the preview is running, reload it
        // doing this during a call will cause re-invite, this is unwanted
        QReadLocker lk(&pimpl_->renderersMutex_);
        auto it = pimpl_->renderers_.find(video::PREVIEW_RENDERER_ID);
        if (it != pimpl_->renderers_.end() && it->second && pimpl_->renderers_.size() == 1) {
            lk.unlock();
            stopPreview(video::PREVIEW_RENDERER_ID);
            startPreview(video::PREVIEW_RENDERER_ID);
        }
    }
    
    QString
    AVModel::getDeviceIdFromName(const QString& deviceName) const
    {
        auto devices = getDevices();
        auto iter = std::find_if(devices.begin(), devices.end(), [this, deviceName](const QString& d) {
            auto settings = getDeviceSettings(d);
            return settings.name == deviceName;
        });
        if (iter == devices.end()) {
            qWarning() << "Couldn't find device: " << deviceName;
            return {};
        }
        return *iter;
    }
    
    VectorString
    AVModel::getSupportedAudioManagers() const
    {
        QStringList managers = ConfigurationManager::instance().getSupportedAudioManagers();
        VectorString result;
        for (const auto& manager : managers) {
            result.push_back(manager);
        }
        return result;
    }
    
    QString
    AVModel::getAudioManager() const
    {
        return ConfigurationManager::instance().getAudioManager();
    }
    
    QVector<QString>
    AVModel::getAudioOutputDevices() const
    {
        QStringList devices = ConfigurationManager::instance().getAudioOutputDeviceList();
    
        // A fix for ring-daemon#43
        if (ConfigurationManager::instance().getAudioManager() == QStringLiteral("pulseaudio")) {
            if (devices.at(0) == QStringLiteral("default")) {
                devices[0] = QObject::tr("default");
            }
        }
    
        VectorString result;
        for (const auto& device : devices) {
            result.push_back(device);
        }
        return (QVector<QString>) result;
    }
    
    QVector<QString>
    AVModel::getAudioInputDevices() const
    {
        QStringList devices = ConfigurationManager::instance().getAudioInputDeviceList();
    
        // A fix for ring-daemon#43
        if (ConfigurationManager::instance().getAudioManager() == QStringLiteral("pulseaudio")) {
            if (devices.at(0) == QStringLiteral("default")) {
                devices[0] = QObject::tr("default");
            }
        }
    
        VectorString result;
        for (const auto& device : devices) {
            result.push_back(device);
        }
        return (QVector<QString>) result;
    }
    
    QString
    AVModel::getRingtoneDevice() const
    {
        const int RINGTONE_IDX = 2;
        return pimpl_->getDevice(RINGTONE_IDX);
    }
    
    QString
    AVModel::getOutputDevice() const
    {
        const int OUTPUT_IDX = 0;
        return pimpl_->getDevice(OUTPUT_IDX);
    }
    
    QString
    AVModel::getInputDevice() const
    {
        const int INPUT_IDX = 1;
        return pimpl_->getDevice(INPUT_IDX);
    }
    
    bool
    AVModel::isAudioMeterActive(const QString& id) const
    {
        return ConfigurationManager::instance().isAudioMeterActive(id);
    }
    
    void
    AVModel::setAudioMeterState(bool active, const QString& id) const
    {
        ConfigurationManager::instance().setAudioMeterState(id, active);
    }
    
    void
    AVModel::startAudioDevice() const
    {
        VideoManager::instance().startAudioDevice();
    }
    
    void
    AVModel::stopAudioDevice() const
    {
        VideoManager::instance().stopAudioDevice();
    }
    
    bool
    AVModel::setAudioManager(const QString& name)
    {
        return ConfigurationManager::instance().setAudioManager(name);
    }
    
    void
    AVModel::setRingtoneDevice(int idx)
    {
        ConfigurationManager::instance().setAudioRingtoneDevice(idx);
    }
    
    void
    AVModel::setOutputDevice(int idx)
    {
        ConfigurationManager::instance().setAudioOutputDevice(idx);
    }
    
    void
    AVModel::setInputDevice(int idx)
    {
        ConfigurationManager::instance().setAudioInputDevice(idx);
    }
    
    void
    AVModel::stopLocalRecorder(const QString& path) const
    {
        if (path.isEmpty()) {
            qWarning("stopLocalRecorder: can't stop non existing recording");
            return;
        }
    
        VideoManager::instance().stopLocalRecorder(path);
    }
    
    QString
    AVModel::startLocalMediaRecorder(const QString& videoInputId) const
    {
        const QString path = pimpl_->getRecordingPath();
        const QString finalPath = VideoManager::instance().startLocalMediaRecorder(videoInputId, path);
        return finalPath;
    }
    
    QString
    AVModel::getRecordPath() const
    {
        return ConfigurationManager::instance().getRecordPath();
    }
    
    void
    AVModel::setRecordPath(const QString& path) const
    {
        ConfigurationManager::instance().setRecordPath(path.toUtf8());
    }
    
    bool
    AVModel::getAlwaysRecord() const
    {
        return ConfigurationManager::instance().getIsAlwaysRecording();
    }
    
    void
    AVModel::setAlwaysRecord(const bool& rec) const
    {
        ConfigurationManager::instance().setIsAlwaysRecording(rec);
    }
    
    bool
    AVModel::getRecordPreview() const
    {
        return ConfigurationManager::instance().getRecordPreview();
    }
    
    void
    AVModel::setRecordPreview(const bool& rec) const
    {
        ConfigurationManager::instance().setRecordPreview(rec);
    }
    
    int
    AVModel::getRecordQuality() const
    {
        return ConfigurationManager::instance().getRecordQuality();
    }
    
    void
    AVModel::setRecordQuality(const int& rec) const
    {
        ConfigurationManager::instance().setRecordQuality(rec);
    }
    
    QString
    AVModel::startPreview(const QString& resource)
    {
        return VideoManager::instance().openVideoInput(resource);
    }
    
    void
    AVModel::stopPreview(const QString& resource)
    {
        VideoManager::instance().closeVideoInput(resource);
    }
    
    #ifdef WIN32
    BOOL
    IsAltTabWindow(HWND hwnd)
    {
        auto styles = (DWORD) GetWindowLongPtr(hwnd, GWL_STYLE);
        auto ex_styles = (DWORD) GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    
        if (ex_styles & WS_EX_TOOLWINDOW)
            return false;
        if (styles & WS_CHILD)
            return false;
    
        DWORD cloaked = FALSE;
        HRESULT hrTemp = DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloaked, sizeof(cloaked));
        if (SUCCEEDED(hrTemp) && cloaked == DWM_CLOAKED_SHELL) {
            return false;
        }
    
        // Start at the root owner
        HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);
    
        // See if we are the last active visible popup
        HWND hwndTry;
        while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
            if (IsWindowVisible(hwndTry))
                break;
            hwndWalk = hwndTry;
        }
        return hwndWalk == hwnd;
    }
    
    BOOL CALLBACK
    CbEnumAltTab(HWND hwnd, LPARAM lParam)
    {
        const size_t MAX_WINDOW_NAME = 256;
        TCHAR windowName[MAX_WINDOW_NAME];
        GetWindowText(hwnd, windowName, MAX_WINDOW_NAME);
    
        // Do not show windows that has no caption
        if (0 == windowName[0])
            return TRUE;
    
        std::wstring msg = std::wstring(windowName);
        auto name = QString::fromStdWString(msg);
    
        // Do not show invisible windows
        if (!IsWindowVisible(hwnd))
            return TRUE;
    
        // Alt-tab test as described by Raymond Chen
        if (!IsAltTabWindow(hwnd))
            return TRUE;
    
        auto isShellWindow = hwnd == GetShellWindow();
    
        if (isShellWindow)
            return TRUE;
    
        QMap<QString, QVariant>* windowList = reinterpret_cast<QMap<QString, QVariant>*>(lParam);
        auto keys = windowList->keys();
        if (keys.indexOf(name) > 0) {
            return FALSE;
        } else {
            std::stringstream ss;
            ss << hwnd;
            windowList->insert(name, QString::fromStdString(ss.str()));
        }
    
        return TRUE;
    }
    #endif
    
    #if defined(Q_OS_UNIX) && !defined(__APPLE__)
    static xcb_atom_t
    getAtom(xcb_connection_t* c, const std::string& atomName)
    {
        xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom(c, 0, atomName.size(), atomName.c_str());
        if (auto* rep = xcb_intern_atom_reply(c, atom_cookie, nullptr)) {
            xcb_atom_t atom = rep->atom;
            free(rep);
            return atom;
        }
        return {};
    }
    #endif
    
    const QVariantMap
    AVModel::getListWindows() const
    {
        QMap<QString, QVariant> ret {};
    
    #if defined(Q_OS_UNIX) && !defined(__APPLE__)
        std::unique_ptr<xcb_connection_t, void (*)(xcb_connection_t*)> c(xcb_connect(nullptr, nullptr),
                                                                         [](xcb_connection_t* ptr) {
                                                                             xcb_disconnect(ptr);
                                                                         });
    
        if (xcb_connection_has_error(c.get())) {
            qDebug() << "xcb connection has error";
            return ret;
        }
    
        auto atomNetClient = getAtom(c.get(), "_NET_CLIENT_LIST");
        auto atomWMVisibleName = getAtom(c.get(), "_NET_WM_NAME");
        if (!atomNetClient || !atomWMVisibleName)
            return ret;
    
        auto* screen = xcb_setup_roots_iterator(xcb_get_setup(c.get())).data;
    
        xcb_get_property_cookie_t propCookieList = xcb_get_property(c.get(),
                                                                    0,
                                                                    screen->root,
                                                                    atomNetClient,
                                                                    XCB_GET_PROPERTY_TYPE_ANY,
                                                                    0,
                                                                    100);
    
        using propertyPtr
            = std::unique_ptr<xcb_get_property_reply_t, void (*)(xcb_get_property_reply_t*)>;
    
        xcb_generic_error_t* e;
        propertyPtr replyPropList(xcb_get_property_reply(c.get(), propCookieList, &e),
                                  [](auto* ptr) { free(ptr); });
        if (e) {
            qDebug() << "Error: " << e->error_code;
            free(e);
        }
        if (replyPropList.get()) {
            int valueLegth = xcb_get_property_value_length(replyPropList.get());
            if (valueLegth) {
                auto* win = static_cast<xcb_window_t*>(xcb_get_property_value(replyPropList.get()));
                for (int i = 0; i < valueLegth / 4; i++) {
                    xcb_get_property_cookie_t prop_cookie = xcb_get_property(c.get(),
                                                                             0,
                                                                             win[i],
                                                                             atomWMVisibleName,
                                                                             XCB_GET_PROPERTY_TYPE_ANY,
                                                                             0,
                                                                             1000);
                    propertyPtr replyProp {xcb_get_property_reply(c.get(), prop_cookie, &e),
                                           [](auto* ptr) {
                                               free(ptr);
                                           }};
                    if (e) {
                        qDebug() << "Error: " << e->error_code;
                        free(e);
                    }
                    if (replyProp.get()) {
                        int v_size = xcb_get_property_value_length(replyProp.get());
                        if (v_size) {
                            auto v = std::string(reinterpret_cast<char*>(
                                                     xcb_get_property_value(replyProp.get())),
                                                 v_size);
                            auto name = QString::fromUtf8(v.c_str());
                            if (ret.find(name) != ret.end())
                                name += QString(" - 0x%1").arg(win[i], 0, 16);
                            ret.insert(name, QVariant(QString("0x%1").arg(win[i], 0, 16)));
                        }
                    }
                }
            }
        }
    #endif
    #ifdef WIN32
        try {
            auto newWindow = true;
            LPARAM lParam = reinterpret_cast<LPARAM>(&ret);
            while (newWindow) {
                newWindow = EnumWindows(CbEnumAltTab, lParam);
            }
            auto finishedloop = true;
        } catch (...) {
        }
    #endif
        return ret;
    }
    
    void
    AVModel::setCurrentVideoCaptureDevice(const QString& currentVideoCaptureDevice)
    {
        pimpl_->currentVideoCaptureDevice_ = currentVideoCaptureDevice;
    }
    
    QString
    AVModel::getCurrentVideoCaptureDevice() const
    {
        return pimpl_->currentVideoCaptureDevice_;
    }
    
    void
    AVModel::clearCurrentVideoCaptureDevice()
    {
        pimpl_->currentVideoCaptureDevice_.clear();
    }
    
    void
    AVModel::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
    {
        pimpl_->addRenderer(id, res, shmPath);
    }
    
    bool
    AVModel::hasRenderer(const QString& id)
    {
        return pimpl_->hasRenderer(id);
    }
    
    QSize
    AVModel::getRendererSize(const QString& id)
    {
        return pimpl_->getRendererSize(id);
    }
    
    Frame
    AVModel::getRendererFrame(const QString& id)
    {
        return pimpl_->getRendererFrame(id);
    }
    
    bool
    AVModel::useDirectRenderer() const
    {
    #ifdef ENABLE_LIBWRAP
        return true;
    #else
        return false;
    #endif
    }
    
    AVModelPimpl::AVModelPimpl(AVModel& linked, const CallbacksHandler& callbacksHandler)
        : callbacksHandler(callbacksHandler)
        , linked_(linked)
    {
        std::srand(std::time(nullptr));
    #ifndef ENABLE_LIBWRAP
        SIZE_RENDERER = renderers_.size();
    #endif
        connect(&callbacksHandler, &CallbacksHandler::deviceEvent, this, &AVModelPimpl::slotDeviceEvent);
        connect(&callbacksHandler,
                &CallbacksHandler::audioDeviceEvent,
                this,
                &AVModelPimpl::slotAudioDeviceEvent);
        connect(&callbacksHandler, &CallbacksHandler::audioMeter, this, &AVModelPimpl::slotAudioMeter);
        connect(&callbacksHandler,
                &CallbacksHandler::recordPlaybackStopped,
                this,
                &AVModelPimpl::slotRecordPlaybackStopped);
    
        // render connections
        connect(&callbacksHandler,
                &CallbacksHandler::decodingStarted,
                this,
                &AVModelPimpl::onDecodingStarted,
                Qt::DirectConnection);
        connect(&callbacksHandler,
                &CallbacksHandler::decodingStopped,
                this,
                &AVModelPimpl::onDecodingStopped,
                Qt::DirectConnection);
    
        auto startedPreview = false;
        auto restartRenderers = [&](const QStringList& callList) {
            for (const auto& callId : callList) {
                MapStringString rendererInfos = VideoManager::instance().getRenderer(callId);
                auto shmPath = rendererInfos[libjami::Media::Details::SHM_PATH];
                auto width = rendererInfos[libjami::Media::Details::WIDTH].toInt();
                auto height = rendererInfos[libjami::Media::Details::HEIGHT].toInt();
                if (width > 0 && height > 0) {
                    startedPreview = true;
                    onDecodingStarted(callId, shmPath, width, height);
                }
            }
        };
        restartRenderers(CallManager::instance().getCallList(""));
        auto confIds = lrc::api::Lrc::getConferences();
        QStringList list;
        Q_FOREACH (QString confId, confIds) {
            list << confId;
        }
        restartRenderers(list);
        if (startedPreview)
            restartRenderers({"local"});
        currentVideoCaptureDevice_ = VideoManager::instance().getDefaultDevice();
    }
    
    QString
    AVModelPimpl::getRecordingPath() const
    {
        const QDir dir = authority::storage::getPath() + "/" + recorderSavesSubdir;
        dir.mkpath(".");
    
        std::chrono::time_point<std::chrono::system_clock> time_now = std::chrono::system_clock::now();
        std::time_t time_now_t = std::chrono::system_clock::to_time_t(time_now);
        std::tm now_tm = *std::localtime(&time_now_t);
    
        std::stringstream ss;
        ss << dir.path().toStdString();
        ss << "/";
        ss << std::put_time(&now_tm, "%Y%m%d-%H%M%S");
        ss << "-";
        ss << std::rand();
    
        QDir file_path(ss.str().c_str());
    
        return file_path.path();
    }
    
    void
    AVModelPimpl::onDecodingStarted(const QString& id, const QString& shmPath, int width, int height)
    {
        addRenderer(id, QSize(width, height), shmPath);
    }
    
    void
    AVModelPimpl::onDecodingStopped(const QString& id, const QString& shmPath)
    {
        Q_UNUSED(shmPath)
        removeRenderer(id);
    }
    
    #ifndef ENABLE_LIBWRAP
    void
    AVModelPimpl::stopCameraAndQuit(int)
    {
        if (SIZE_RENDERER == 1) {
            // This will stop the preview if needed (not in a call).
            VideoManager::instance().closeVideoInput(PREVIEW_RENDERER_ID);
            // HACK: this sleep is just here to let the camera stop and
            // avoid immediate raise
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
        std::raise(SIGTERM);
    }
    
    #endif
    QString
    AVModelPimpl::getDevice(int type) const
    {
        if (type < 0 || type > 2)
            return {}; // No device
        QString result;
        VectorString devices;
        switch (type) {
        case 1: // INPUT
            devices = linked_.getAudioInputDevices();
            break;
        case 0: // OUTPUT
        case 2: // RINGTONE
            devices = linked_.getAudioOutputDevices();
            break;
        default:
            break;
        }
        QStringList currentDevicesIdx = ConfigurationManager::instance().getCurrentAudioDevicesIndex();
        try {
            // Should not happen, but cannot retrieve current ringtone device
            if (currentDevicesIdx.size() < 3)
                return "";
            auto deviceIdx = currentDevicesIdx[type].toInt();
            if (deviceIdx < devices.size())
                result = devices.at(deviceIdx);
        } catch (std::bad_alloc& ba) {
            qWarning() << "bad_alloc caught: " << ba.what();
            return "";
        }
        return result;
    }
    
    static std::unique_ptr<Renderer>
    createRenderer(const QString& id, const QSize& res, const QString& shmPath = {})
    {
    #ifdef ENABLE_LIBWRAP
        Q_UNUSED(shmPath)
        return std::make_unique<DirectRenderer>(id, res);
    #else
        return std::make_unique<ShmRenderer>(id, res, shmPath);
    #endif
    }
    
    void
    AVModelPimpl::addRenderer(const QString& id, const QSize& res, const QString& shmPath)
    {
        // Remove the renderer if it already exists.
        std::ignore = removeRenderer(id);
    
        {
            QWriteLocker lk(&renderersMutex_);
            renderers_[id] = createRenderer(id, res, shmPath);
        }
    
        QReadLocker lk(&renderersMutex_);
        if (auto* renderer = renderers_.find(id)->second.get()) {
            connect(
                renderer,
                &Renderer::fpsChanged,
                this,
                [this, id](void) { linked_.updateRenderersFPSInfo(id); },
                Qt::QueuedConnection);
            connect(
                renderer,
                &Renderer::started,
                this,
                [this, id](const QSize& size) { Q_EMIT linked_.rendererStarted(id, size); },
                Qt::DirectConnection);
    #ifdef ENABLE_LIBWRAP
            connect(
                renderer,
                &Renderer::frameBufferRequested,
                this,
                [this, id](AVFrame* frame) { Q_EMIT linked_.frameBufferRequested(id, frame); },
                Qt::DirectConnection);
    #endif
            connect(
                renderer,
                &Renderer::frameUpdated,
                this,
                [this, id] { Q_EMIT linked_.frameUpdated(id); },
                Qt::DirectConnection);
            connect(
                renderer,
                &Renderer::stopped,
                this,
                [this, id] { Q_EMIT linked_.rendererStopped(id); },
                Qt::DirectConnection);
    
            renderer->startRendering();
        }
    }
    
    std::unique_ptr<Renderer>
    AVModelPimpl::removeRenderer(const QString& id)
    {
        QWriteLocker lk(&renderersMutex_);
        auto it = renderers_.find(id);
        if (it == renderers_.end()) {
            qWarning() << "Cannot remove renderer. " << id << "not found";
            return {};
        }
        auto removed = std::move(it->second);
        renderers_.erase(it);
        return removed;
    }
    
    bool
    AVModelPimpl::hasRenderer(const QString& id)
    {
        QReadLocker lk(&renderersMutex_);
        return renderers_.find(id) != renderers_.end();
    }
    
    QSize
    AVModelPimpl::getRendererSize(const QString& id)
    {
        QReadLocker lk(&renderersMutex_);
        auto it = renderers_.find(id);
        if (it != renderers_.end()) {
            return it->second->size();
        }
        return {};
    }
    
    Frame
    AVModelPimpl::getRendererFrame(const QString& id)
    {
        QReadLocker lk(&renderersMutex_);
        auto it = renderers_.find(id);
        if (it != renderers_.end()) {
            return it->second->currentFrame();
        }
        return {};
    }
    
    void
    AVModelPimpl::slotDeviceEvent()
    {
        Q_EMIT linked_.deviceEvent();
    }
    
    void
    AVModelPimpl::slotAudioDeviceEvent()
    {
        Q_EMIT linked_.audioDeviceEvent();
    }
    
    void
    AVModelPimpl::slotAudioMeter(const QString& id, float level)
    {
        Q_EMIT linked_.audioMeter(id, level);
    }
    
    void
    AVModelPimpl::slotRecordPlaybackStopped(const QString& filePath)
    {
        Q_EMIT linked_.recordPlaybackStopped(filePath);
    }
    
    } // namespace lrc
    
    #include "api/moc_avmodel.cpp"
    #include "avmodel.moc"