Select Git revision
dhtnode.cpp
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
videomanager.cpp 14.37 KiB
/*
* Copyright (C) 2004-2018 Savoir-faire Linux Inc.
*
* Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com>
* Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
* Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@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.
*/
#include "videomanager_interface.h"
#include "videomanager.h"
#include "localrecorder.h"
#include "localrecordermanager.h"
#include "libav_utils.h"
#include "video/video_input.h"
#include "video/video_device_monitor.h"
#include "account.h"
#include "logger.h"
#include "manager.h"
#include "system_codec_container.h"
#include "video/sinkclient.h"
#include "client/ring_signal.h"
#include "audio/ringbufferpool.h"
#include "dring/media_const.h"
#include "libav_utils.h"
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <new> // std::bad_alloc
#include <cstdlib>
#include <cstring> // std::memset
#include <ciso646> // fix windows compiler bug
namespace DRing {
MediaFrame::MediaFrame()
: frame_ {av_frame_alloc(), [](AVFrame* frame){ av_frame_free(&frame); }}
{
if (not frame_)
throw std::bad_alloc();
}
void
MediaFrame::copyFrom(const MediaFrame& o)
{
reset();
av_frame_ref(frame_.get(), o.frame_.get());
}
void
MediaFrame::reset() noexcept
{
av_frame_unref(frame_.get());
}
AudioFrame::AudioFrame(const ring::AudioFormat& format, size_t nb_samples)
: MediaFrame()
{
setFormat(format);
if (nb_samples)
reserve(nb_samples);
}
void
AudioFrame::setFormat(const ring::AudioFormat& format)
{
auto d = pointer();
d->channels = format.nb_channels;
d->channel_layout = av_get_default_channel_layout(format.nb_channels);
d->sample_rate = format.sample_rate;
d->format = format.sampleFormat;
}
void
AudioFrame::reserve(size_t nb_samples)
{
if (nb_samples != 0) {
auto d = pointer();
d->nb_samples = nb_samples;
int err;
if ((err = av_frame_get_buffer(d, 0)) < 0) {
throw std::bad_alloc();
}
}
}
void
AudioFrame::mix(const AudioFrame& frame)
{
auto& f = *pointer();
auto& fIn = *frame.pointer();
if (f.channels != fIn.channels || f.format != fIn.format || f.sample_rate != fIn.sample_rate) {
throw std::invalid_argument("Can't mix frames with different formats");
}
if (f.nb_samples == 0) {
reserve(fIn.nb_samples);
ring::libav_utils::fillWithSilence(&f);
} else if (f.nb_samples != fIn.nb_samples) {
throw std::invalid_argument("Can't mix frames with different length");
}
AVSampleFormat fmt = (AVSampleFormat)f.format;
bool isPlanar = av_sample_fmt_is_planar(fmt);
unsigned samplesPerChannel = isPlanar ? f.nb_samples : f.nb_samples * f.channels;
unsigned channels = isPlanar ? f.channels : 1;
if (fmt == AV_SAMPLE_FMT_S16 || fmt == AV_SAMPLE_FMT_S16P) {
for (unsigned i=0; i < channels; i++) {
auto c = (int16_t*)f.extended_data[i];
auto cIn = (int16_t*)fIn.extended_data[i];
for (unsigned s=0; s < samplesPerChannel; s++) {
int32_t n = (int32_t)c[s] + (int32_t)cIn[s];
n = std::min<int32_t>(n, std::numeric_limits<int16_t>::max());
n = std::max<int32_t>(n, std::numeric_limits<int16_t>::min());
c[s] = n;
}
}
} else if (fmt == AV_SAMPLE_FMT_FLT || fmt == AV_SAMPLE_FMT_FLTP) {
for (unsigned i=0; i < channels; i++) {
auto c = (float*)f.extended_data[i];
auto cIn = (float*)fIn.extended_data[i];
for (unsigned s=0; s < samplesPerChannel; s++) {
c[s] += cIn[s];
}
}
} else {
throw std::invalid_argument(std::string("Unsupported format for mixing: ") + av_get_sample_fmt_name(fmt));
}
}
VideoFrame::~VideoFrame()
{
if (releaseBufferCb_)
releaseBufferCb_(ptr_);
}
void
VideoFrame::reset() noexcept
{
MediaFrame::reset();
allocated_ = false;
releaseBufferCb_ = {};
}
void
VideoFrame::copyFrom(const VideoFrame& o)
{
MediaFrame::copyFrom(o);
ptr_ = o.ptr_;
allocated_ = o.allocated_;
}
size_t
VideoFrame::size() const noexcept
{
return av_image_get_buffer_size((AVPixelFormat)frame_->format, frame_->width, frame_->height, 1);
}
int
VideoFrame::format() const noexcept
{
return frame_->format;
}
int
VideoFrame::width() const noexcept
{
return frame_->width;
}
int
VideoFrame::height() const noexcept
{
return frame_->height;
}
void
VideoFrame::setGeometry(int format, int width, int height) noexcept
{
frame_->format = format;
frame_->width = width;
frame_->height = height;
}
void
VideoFrame::reserve(int format, int width, int height)
{
auto libav_frame = frame_.get();
if (allocated_) {
// nothing to do if same properties
if (width == libav_frame->width
and height == libav_frame->height
and format == libav_frame->format)
av_frame_unref(libav_frame);
}
setGeometry(format, width, height);
if (av_frame_get_buffer(libav_frame, 32))
throw std::bad_alloc();
allocated_ = true;
releaseBufferCb_ = {};
}
void
VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height) noexcept
{
reset();
setGeometry(format, width, height);
if (not ptr)
return;
av_image_fill_arrays(frame_->data, frame_->linesize, (uint8_t*)ptr,
(AVPixelFormat)frame_->format, width, height, 1);
}
void
VideoFrame::setFromMemory(uint8_t* ptr, int format, int width, int height,
std::function<void(uint8_t*)> cb) noexcept
{
setFromMemory(ptr, format, width, height);
if (cb) {
releaseBufferCb_ = cb;
ptr_ = ptr;
}
}
void
VideoFrame::setReleaseCb(std::function<void(uint8_t*)> cb) noexcept
{
if (cb) {
releaseBufferCb_ = cb;
}
}
void
VideoFrame::noise()
{
auto f = frame_.get();
if (f->data[0] == nullptr)
return;
for (std::size_t i=0 ; i < size(); ++i) {
f->data[0][i] = std::rand() & 255;
}
}
VideoFrame* getNewFrame()
{
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
return &input->getNewFrame();
return nullptr;
}
void publishFrame()
{
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
return input->publishFrame();
}
void
registerVideoHandlers(const std::map<std::string,
std::shared_ptr<CallbackWrapperBase>>&handlers)
{
registerSignalHandlers(handlers);
}
std::vector<std::string>
getDeviceList()
{
return ring::Manager::instance().getVideoManager().videoDeviceMonitor.getDeviceList();
}
VideoCapabilities
getCapabilities(const std::string& name)
{
return ring::Manager::instance().getVideoManager().videoDeviceMonitor.getCapabilities(name);
}
std::string
getDefaultDevice()
{
return ring::Manager::instance().getVideoManager().videoDeviceMonitor.getDefaultDevice();
}
void
setDefaultDevice(const std::string& name)
{
RING_DBG("Setting default device to %s", name.c_str());
ring::Manager::instance().getVideoManager().videoDeviceMonitor.setDefaultDevice(name);
ring::Manager::instance().saveConfig();
}
std::map<std::string, std::string>
getDeviceParams(const std::string& name)
{
auto params = ring::Manager::instance().getVideoManager().videoDeviceMonitor.getDeviceParams(name);
std::stringstream width, height, rate;
width << params.width;
height << params.height;
rate << params.framerate;
return {
{"format", params.format},
{"width", width.str()},
{"height", height.str()},
{"rate", rate.str()}
};
}
std::map<std::string, std::string>
getSettings(const std::string& name)
{
return ring::Manager::instance().getVideoManager().videoDeviceMonitor.getSettings(name).to_map();
}
void
applySettings(const std::string& name,
const std::map<std::string, std::string>& settings)
{
ring::Manager::instance().getVideoManager().videoDeviceMonitor.applySettings(name, settings);
}
void
startCamera()
{
ring::Manager::instance().getVideoManager().videoPreview = ring::getVideoCamera();
ring::Manager::instance().getVideoManager().started = switchToCamera();
}
void
stopCamera()
{
if (switchInput(""))
ring::Manager::instance().getVideoManager().started = false;
ring::Manager::instance().getVideoManager().videoPreview.reset();
}
std::string
startLocalRecorder(const bool& audioOnly, const std::string& filepath)
{
if (!audioOnly && !ring::Manager::instance().getVideoManager().started) {
RING_ERR("Couldn't start local video recorder (camera is not started)");
return "";
}
auto rec = std::make_unique<ring::LocalRecorder>(audioOnly);
rec->setPath(filepath);
// retrieve final path (containing file extension)
auto path = rec->getPath();
try {
ring::LocalRecorderManager::instance().insertRecorder(path, std::move(rec));
} catch (const std::invalid_argument&) {
return "";
}
auto ret = ring::LocalRecorderManager::instance().getRecorderByPath(path)->startRecording();
if (!ret) {
ring::LocalRecorderManager::instance().removeRecorderByPath(filepath);
return "";
}
return path;
}
void
stopLocalRecorder(const std::string& filepath)
{
ring::LocalRecorder *rec = ring::LocalRecorderManager::instance().getRecorderByPath(filepath);
if (!rec) {
RING_WARN("Can't stop non existing local recorder.");
return;
}
rec->stopRecording();
ring::LocalRecorderManager::instance().removeRecorderByPath(filepath);
}
bool
switchInput(const std::string& resource)
{
if (auto call = ring::Manager::instance().getCurrentCall()) {
// TODO remove this part when clients are updated to use Callring::Manager::switchInput
call->switchInput(resource);
return true;
} else {
bool ret = true;
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
ret = input->switchInput(resource).valid();
else
RING_WARN("Video input not initialized");
if (auto input = ring::getAudioInput(ring::RingBufferPool::DEFAULT_ID))
ret &= input->switchInput(resource).valid();
return ret;
}
}
bool
switchToCamera()
{
return switchInput(ring::Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice());
}
bool
hasCameraStarted()
{
return ring::Manager::instance().getVideoManager().started;
}
void
registerSinkTarget(const std::string& sinkId, const SinkTarget& target)
{
if (auto sink = ring::Manager::instance().getSinkClient(sinkId))
sink->registerTarget(target);
else
RING_WARN("No sink found for id '%s'", sinkId.c_str());
}
std::map<std::string, std::string>
getRenderer(const std::string& callId)
{
if (auto sink = ring::Manager::instance().getSinkClient(callId))
return {
{DRing::Media::Details::CALL_ID, callId},
{DRing::Media::Details::SHM_PATH, sink->openedName()},
{DRing::Media::Details::WIDTH, ring::to_string(sink->getWidth())},
{DRing::Media::Details::HEIGHT, ring::to_string(sink->getHeight())},
};
else
return {
{DRing::Media::Details::CALL_ID, callId},
{DRing::Media::Details::SHM_PATH, ""},
{DRing::Media::Details::WIDTH, "0"},
{DRing::Media::Details::HEIGHT, "0"},
};
}
bool
getDecodingAccelerated()
{
#ifdef RING_ACCEL
return ring::Manager::instance().videoPreferences.getDecodingAccelerated();
#else
return false;
#endif
}
void
setDecodingAccelerated(bool state)
{
#ifdef RING_ACCEL
RING_DBG("%s hardware acceleration", (state ? "Enabling" : "Disabling"));
ring::Manager::instance().videoPreferences.setDecodingAccelerated(state);
#endif
}
#if defined(__ANDROID__) || defined(RING_UWP) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
void
addVideoDevice(const std::string &node, std::vector<std::map<std::string, std::string>> const * devInfo)
{
ring::Manager::instance().getVideoManager().videoDeviceMonitor.addDevice(node, devInfo);
}
void
removeVideoDevice(const std::string &node)
{
ring::Manager::instance().getVideoManager().videoDeviceMonitor.removeDevice(node);
}
void*
obtainFrame(int length)
{
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
return (*input).obtainFrame(length);
return nullptr;
}
void
releaseFrame(void* frame)
{
if (auto input = ring::Manager::instance().getVideoManager().videoInput.lock())
(*input).releaseFrame(frame);
}
#endif
} // namespace DRing
namespace ring {
std::shared_ptr<video::VideoFrameActiveWriter>
getVideoCamera()
{
auto& vmgr = Manager::instance().getVideoManager();
if (auto input = vmgr.videoInput.lock())
return input;
vmgr.started = false;
auto input = std::make_shared<video::VideoInput>();
vmgr.videoInput = input;
return input;
}
video::VideoDeviceMonitor&
getVideoDeviceMonitor()
{
return Manager::instance().getVideoManager().videoDeviceMonitor;
}
std::shared_ptr<AudioInput>
getAudioInput(const std::string& id)
{
auto& vmgr = Manager::instance().getVideoManager();
std::lock_guard<std::mutex> lk(vmgr.audioMutex);
// erase expired audio inputs
for (auto it = vmgr.audioInputs.cbegin(); it != vmgr.audioInputs.cend();) {
if (it->second.expired())
it = vmgr.audioInputs.erase(it);
else
++it;
}
auto it = vmgr.audioInputs.find(id);
if (it != vmgr.audioInputs.end()) {
if (auto input = it->second.lock()) {
return input;
}
}
auto input = std::make_shared<AudioInput>(id);
vmgr.audioInputs[id] = input;
return input;
}
} // namespace ring