Skip to content
Snippets Groups Projects
Select Git revision
  • 08a9e830a7eda34b4521535c4774e77c9a053b4c
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

README

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    sinkclient.cpp 10.70 KiB
    /*
     *  Copyright (C) 2012-2019 Savoir-faire Linux Inc.
     *
     *  Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
     *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
     *  Author: Alexandre Lision <alexandre.lision@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, write to the Free Software
     *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
     */
    
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    
    #include "sinkclient.h"
    
    #if HAVE_SHM
    #include "shm_header.h"
    #endif // HAVE_SHM
    
    #include "media_buffer.h"
    #include "logger.h"
    #include "noncopyable.h"
    #include "client/ring_signal.h"
    #include "dring/videomanager_interface.h"
    #include "libav_utils.h"
    #include "video_scaler.h"
    #include "smartools.h"
    
    #ifdef RING_ACCEL
    #include "accel.h"
    #endif
    
    #ifndef _WIN32
    #include <sys/mman.h>
    #endif
    #include <ciso646> // fix windows compiler bug
    #include <fcntl.h>
    #include <cstdio>
    #include <sstream>
    #include <unistd.h>
    #include <cerrno>
    #include <cstring>
    #include <stdexcept>
    
    namespace ring { namespace video {
    
    #if HAVE_SHM
    // RAII class helper on sem_wait/sem_post sempahore operations
    class SemGuardLock {
        public:
            explicit SemGuardLock(sem_t& mutex) : m_(mutex) {
                auto ret = ::sem_wait(&m_);
                if (ret < 0) {
                    std::ostringstream msg;
                    msg << "SHM mutex@" << &m_
                        << " lock failed (" << ret << ")";
                    throw std::logic_error {msg.str()};
                }
            }
    
            ~SemGuardLock() {
                ::sem_post(&m_);
            }
    
        private:
            sem_t& m_;
    };
    
    class ShmHolder
    {
        public:
            ShmHolder(const std::string& name={});
            ~ShmHolder();
    
            std::string name() const noexcept {
                return openedName_;
            }
    
            void renderFrame(const VideoFrame& src) noexcept;
    
        private:
            bool resizeArea(std::size_t desired_length) noexcept;
            char* getShmAreaDataPtr() noexcept;
    
            void unMapShmArea() noexcept {
                if (area_ != MAP_FAILED and ::munmap(area_, areaSize_) < 0) {
                    RING_ERR("ShmHolder[%s]: munmap(%zu) failed with errno %d",
                             openedName_.c_str(), areaSize_, errno);
                }
            }
    
            SHMHeader* area_ {static_cast<SHMHeader*>(MAP_FAILED)};
            std::size_t areaSize_ {0};
            std::string openedName_;
            int fd_ {-1};
    };
    
    ShmHolder::ShmHolder(const std::string& name)
    {
        static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
        static constexpr int perms = S_IRUSR | S_IWUSR;
    
        static auto shmFailedWithErrno = [this](const std::string& what) {
            std::ostringstream msg;
            msg << "ShmHolder[" << openedName_ << "]: "
            << what << " failed, errno=" << errno;
            throw std::runtime_error {msg.str()};
        };
    
        if (not name.empty()) {
            openedName_ = name;
            fd_ = ::shm_open(openedName_.c_str(), flags, perms);
            if (fd_ < 0)
                shmFailedWithErrno("shm_open");
        } else {
            for (int i = 0; fd_ < 0; ++i) {
                std::ostringstream tmpName;
                tmpName << PACKAGE_NAME << "_shm_" << getpid() << "_" << i;
                openedName_ = tmpName.str();
                fd_ = ::shm_open(openedName_.c_str(), flags, perms);
                if (fd_ < 0 and errno != EEXIST)
                    shmFailedWithErrno("shm_open");
            }
        }
    
        // Set size enough for header only (no frame data)
        if (!resizeArea(0))
            shmFailedWithErrno("resizeArea");
    
        // Header fields initialization
        std::memset(area_, 0, areaSize_);
    
        if (::sem_init(&area_->mutex, 1, 1) < 0)
            shmFailedWithErrno("sem_init(mutex)");
    
        if (::sem_init(&area_->frameGenMutex, 1, 0) < 0)
            shmFailedWithErrno("sem_init(frameGenMutex)");
    
        RING_DBG("ShmHolder: new holder '%s'", openedName_.c_str());
    }
    
    ShmHolder::~ShmHolder()
    {
        if (fd_ < 0)
            return;
    
        ::close(fd_);
        ::shm_unlink(openedName_.c_str());
    
        if (area_ == MAP_FAILED)
            return;
    
        ::sem_wait(&area_->mutex);
        area_->frameSize = 0;
        ::sem_post(&area_->mutex);
    
        ::sem_post(&area_->frameGenMutex); // unlock waiting client before leaving
        unMapShmArea();
    }
    
    bool
    ShmHolder::resizeArea(std::size_t frameSize) noexcept
    {
        // aligned on 16-byte boundary frameSize
        frameSize = (frameSize + 15) & ~15;
    
        if (area_ != MAP_FAILED and frameSize == area_->frameSize)
            return true;
    
        // full area size: +15 to take care of maximum padding size
        const auto areaSize = sizeof(SHMHeader) + 2 * frameSize + 15;
        RING_DBG("ShmHolder[%s]: new sizes: f=%zu, a=%zu", openedName_.c_str(),
                 frameSize, areaSize);
    
        unMapShmArea();
    
        if (::ftruncate(fd_, areaSize) < 0) {
            RING_ERR("ShmHolder[%s]: ftruncate(%zu) failed with errno %d",
                     openedName_.c_str(), areaSize, errno);
            return false;
        }
    
        area_ = static_cast<SHMHeader*>(::mmap(nullptr, areaSize,
                                               PROT_READ | PROT_WRITE,
                                               MAP_SHARED, fd_, 0));
    
        if (area_ == MAP_FAILED) {
            areaSize_ = 0;
            RING_ERR("ShmHolder[%s]: mmap(%zu) failed with errno %d",
                     openedName_.c_str(), areaSize, errno);
            return false;
        }
    
        areaSize_ = areaSize;
    
        if (frameSize) {
            SemGuardLock lk {area_->mutex};
    
            area_->frameSize = frameSize;
            area_->mapSize = areaSize;
    
            // Compute aligned IO pointers
            // Note: we not using std::align as not implemented in 4.9
            // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57350
            auto p = reinterpret_cast<std::uintptr_t>(area_->data);
            area_->writeOffset = ((p + 15) & ~15) - p;
            area_->readOffset = area_->writeOffset + frameSize;
        }
    
        return true;
    }
    
    void
    ShmHolder::renderFrame(const VideoFrame& src) noexcept
    {
        const auto width = src.width();
        const auto height = src.height();
        const auto format = AV_PIX_FMT_BGRA;
        const auto frameSize = videoFrameSize(format, width, height);
    
        if (!resizeArea(frameSize)) {
            RING_ERR("ShmHolder[%s]: could not resize area",
                     openedName_.c_str());
            return;
        }
    
        {
            VideoFrame dst;
            VideoScaler scaler;
    
            dst.setFromMemory(area_->data + area_->writeOffset, format, width, height);
            scaler.scale(src, dst);
        }
    
        {
            SemGuardLock lk {area_->mutex};
    
            ++area_->frameGen;
            std::swap(area_->readOffset, area_->writeOffset);
            ::sem_post(&area_->frameGenMutex);
        }
    }
    
    std::string
    SinkClient::openedName() const noexcept
    {
        if (shm_)
            return shm_->name();
        return {};
    }
    
    bool
    SinkClient::start() noexcept
    {
        if (not shm_) {
            try {
                shm_ = std::make_shared<ShmHolder>();
            } catch (const std::runtime_error& e) {
                RING_ERR("SHMHolder ctor failure: %s", e.what());
            }
        }
        return static_cast<bool>(shm_);
    }
    
    bool
    SinkClient::stop() noexcept
    {
        setFrameSize(0, 0);
        shm_.reset();
        return true;
    }
    
    #else // HAVE_SHM
    
    std::string
    SinkClient::openedName() const noexcept
    {
        return {};
    }
    
    bool
    SinkClient::start() noexcept
    {
        return true;
    }
    
    bool
    SinkClient::stop() noexcept
    {
        setFrameSize(0, 0);
        return true;
    }
    
    #endif // !HAVE_SHM
    
    SinkClient::SinkClient(const std::string& id, bool mixer)
        : id_ {id}
        , mixer_(mixer)
        , scaler_(new VideoScaler())
    #ifdef DEBUG_FPS
        , frameCount_(0u)
        , lastFrameDebug_(std::chrono::system_clock::now())
    #endif
    {}
    
    void
    SinkClient::update(Observable<std::shared_ptr<MediaFrame>>* /*obs*/,
                       const std::shared_ptr<MediaFrame>& frame_p)
    {
        auto& f = *std::static_pointer_cast<VideoFrame>(frame_p);
    
    #ifdef DEBUG_FPS
        auto currentTime = std::chrono::system_clock::now();
        const std::chrono::duration<double> seconds = currentTime - lastFrameDebug_;
        ++frameCount_;
        if (seconds.count() > 1) {
            std::ostringstream fps;
            fps << frameCount_ / seconds.count();
            // Send the framerate in smartInfo
            Smartools::getInstance().setFrameRate(id_, fps.str());
            frameCount_ = 0;
            lastFrameDebug_ = currentTime;
        }
    #endif
    
        if (avTarget_.push) {
            auto outFrame = std::make_unique<VideoFrame>();
            outFrame->copyFrom(f);
            avTarget_.push(std::move(outFrame));
        }
    
        bool doTransfer = (target_.pull != nullptr);
    #if HAVE_SHM
        doTransfer |= (shm_ != nullptr);
    #endif
    
        if (doTransfer) {
    #ifdef RING_ACCEL
            auto framePtr = transferToMainMemory(f, AV_PIX_FMT_NV12);
            const auto& swFrame = *framePtr;
    #else
            const auto& swFrame = f;
    #endif
    #if HAVE_SHM
            shm_->renderFrame(swFrame);
    #endif
            if (target_.pull) {
                VideoFrame dst;
                const int width = swFrame.width();
                const int height = swFrame.height();
    #if defined(__ANDROID__) || (defined(__APPLE__) && !TARGET_OS_IPHONE)
                const int format = AV_PIX_FMT_RGBA;
    #else
                const int format = AV_PIX_FMT_BGRA;
    #endif
                const auto bytes = videoFrameSize(format, width, height);
                if (bytes > 0) {
                    if (auto buffer_ptr = target_.pull(bytes)) {
                        buffer_ptr->format = format;
                        buffer_ptr->width = width;
                        buffer_ptr->height = height;
                        dst.setFromMemory(buffer_ptr->ptr, format, width, height);
                        scaler_->scale(swFrame, dst);
                        target_.push(std::move(buffer_ptr));
                    }
                }
            }
        }
    }
    
    void
    SinkClient::setFrameSize(int width, int height)
    {
        width_ = width;
        height_ = height;
        if (width > 0 and height > 0) {
            RING_WARN("Start sink <%s / %s>, size=%dx%d, mixer=%u",
                     getId().c_str(), openedName().c_str(), width, height, mixer_);
            emitSignal<DRing::VideoSignal::DecodingStarted>(getId(), openedName(), width, height, mixer_);
            started_ = true;
        } else if (started_) {
            RING_ERR("Stop sink <%s / %s>, mixer=%u",
                     getId().c_str(), openedName().c_str(), mixer_);
            emitSignal<DRing::VideoSignal::DecodingStopped>(getId(), openedName(), mixer_);
            started_ = false;
        }
    }
    
    }} // namespace ring::video