Commit 1179ad57 authored by Hugo Lefeuvre's avatar Hugo Lefeuvre Committed by Adrien Béraud

localrecorder: add public interface

Initial import of the localrecorder.

The local recorder provides a public interface for recording audio and
video messages locally. For instance this can be used to record voice
or video messages that could be later sent via file transfer.

Change-Id: I0675fc0fb8588db24ae86302a76d4d68f5871b82
parent a774e0f4
......@@ -47,6 +47,24 @@
</arg>
</method>
<method name="startLocalRecorder" tp:name-for-bindings="startLocalRecorder">
<tp:docstring> Starts a local recorder. Video and/or audio are recorded from preferred devices.</tp:docstring>
<arg type="b" name="audioOnly" direction="in">
</arg>
<arg type="s" name="filepath" direction="in">
<tp:docstring> Base file path for local recording. This is not the final path as the file extension will be added (final path can be obtained as return value of this method).</tp:docstring>
</arg>
<arg type="s" name="finalPath" direction="out">
<tp:docstring> Output path of newly started local recording. This file path also serves as recording identifier for stopLocalRecorder.</tp:docstring>
</arg>
</method>
<method name="stopLocalRecorder" tp:name-for-bindings="stopLocalRecorder">
<arg type="s" name="filepath" direction="in">
<tp:docstring> Identifier for local recorder to be stopped (obtained as return value of startLocalRecorder).</tp:docstring>
</arg>
</method>
<method name="startCamera" tp:name-for-bindings="startCamera">
<tp:docstring> Starts the video camera, which renders the active v4l2 device's video to shared memory. Useful for testing/debugging camera settings</tp:docstring>
</method>
......
......@@ -96,3 +96,15 @@ DBusVideoManager::setDecodingAccelerated(const bool& state)
{
DRing::setDecodingAccelerated(state);
}
std::string
DBusVideoManager::startLocalRecorder(const bool& audioOnly, const std::string& filepath)
{
return DRing::startLocalRecorder(audioOnly, filepath);
}
void
DBusVideoManager::stopLocalRecorder(const std::string& filepath)
{
DRing::stopLocalRecorder(filepath);
}
......@@ -62,6 +62,8 @@ class DBusVideoManager :
bool hasCameraStarted();
bool getDecodingAccelerated();
void setDecodingAccelerated(const bool& state);
std::string startLocalRecorder(const bool& audioOnly, const std::string& filepath);
void stopLocalRecorder(const std::string& filepath);
};
#endif // __RING_DBUSVIDEOMANAGER_H__
......@@ -40,6 +40,8 @@ public:
virtual void stopCapture() {}
virtual void decodingStarted(const std::string& id, const std::string& shm_path, int w, int h, bool is_mixer) {}
virtual void decodingStopped(const std::string& id, const std::string& shm_path, bool is_mixer) {}
virtual std::string startLocalRecorder(const bool& audioOnly, const std::string& filepath) {}
virtual void stopLocalRecorder(const std::string& filepath) {}
};
%}
......
......@@ -36,6 +36,8 @@ public:
virtual void stopCapture() {}
virtual void decodingStarted(const std::string& id, const std::string& shm_path, int w, int h, bool is_mixer) {}
virtual void decodingStopped(const std::string& id, const std::string& shm_path, bool is_mixer) {}
virtual std::string startLocalRecorder(const bool& audioOnly, const std::string& filepath) {}
virtual void stopLocalRecorder(const std::string& filepath) {}
};
%}
......
......@@ -22,6 +22,8 @@
#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"
......@@ -116,6 +118,56 @@ stopCamera()
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 "";
}
std::unique_ptr<ring::LocalRecorder> rec;
std::shared_ptr<ring::video::VideoInput> input = nullptr;
if (!audioOnly) {
input = std::static_pointer_cast<ring::video::VideoInput>(ring::getVideoCamera());
}
/* in case of audio-only recording, nullptr is passed and LocalRecorder will
assume isAudioOnly_ = true, so no need to call Recordable::isAudioOnly(). */
rec.reset(new ring::LocalRecorder(input));
rec->setPath(filepath);
// retrieve final path (containing file extension)
auto path = rec->getPath();
try {
ring::LocalRecorderManager::instance().insertRecorder(path, std::move(rec));
} catch (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)
{
......
......@@ -75,6 +75,9 @@ bool switchInput(const std::string& resource);
bool switchToCamera();
void registerSinkTarget(const std::string& sinkId, const SinkTarget& target);
std::string startLocalRecorder(const bool& audioOnly, const std::string& filepath);
void stopLocalRecorder(const std::string& filepath);
#if defined(__ANDROID__) || defined(RING_UWP) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
void addVideoDevice(const std::string &node, const std::vector<std::map<std::string, std::string>>* devInfo=nullptr);
void removeVideoDevice(const std::string &node);
......
......@@ -58,6 +58,7 @@ using random_device = dht::crypto::random_device;
#include "audio/alsa/alsalayer.h"
#endif
#include "media/localrecordermanager.h"
#include "audio/sound/tonelist.h"
#include "audio/sound/dtmf.h"
#include "audio/ringbufferpool.h"
......@@ -952,7 +953,8 @@ Manager::answerCall(const std::string& call_id)
void
Manager::checkAudio()
{
if (getCallList().empty()) {
// FIXME dirty, the manager should not need to be aware of local recorders
if (getCallList().empty() && not ring::LocalRecorderManager::instance().hasRunningRecorders()) {
std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
if (pimpl_->audiodriver_)
pimpl_->audiodriver_->stopStream();
......@@ -1171,11 +1173,6 @@ Manager::refuseCall(const std::string& id)
stopTone();
if (getCallList().size() <= 1) {
std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
pimpl_->audiodriver_->stopStream();
}
call->refuse();
checkAudio();
......
......@@ -20,7 +20,9 @@ libmedia_la_SOURCES = \
srtp.c \
recordable.cpp \
media_filter.cpp \
media_recorder.cpp
media_recorder.cpp \
localrecorder.cpp \
localrecordermanager.cpp
noinst_HEADERS = \
rtp_session.h \
......@@ -39,7 +41,9 @@ noinst_HEADERS = \
decoder_finder.h \
media_filter.h \
media_stream.h \
media_recorder.h
media_recorder.h \
localrecorder.h \
localrecordermanager.h
libmedia_la_LIBADD = \
./audio/libaudio.la
......
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Hugo Lefeuvre <hugo.lefeuvre@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 "localrecorder.h"
#include "audio/ringbufferpool.h"
#include "audio/ringbuffer.h"
#include "media_stream.h"
#include "manager.h"
#include "logger.h"
namespace ring {
LocalRecorder::LocalRecorder(std::shared_ptr<ring::video::VideoInput> input) {
if (input) {
videoInput_ = input;
videoInputSet_ = true;
} else {
isAudioOnly_ = true;
}
recorder_->audioOnly(isAudioOnly_);
}
void
LocalRecorder::setPath(const std::string& path)
{
if (isRecording()) {
RING_ERR("can't set path while recording");
return;
}
recorder_->setPath(path);
path_ = path;
}
bool
LocalRecorder::startRecording()
{
if (isRecording()) {
RING_ERR("recording already started!");
return false;
}
if (path_.empty()) {
RING_ERR("could not start recording (path not set)");
return false;
}
if (!recorder_) {
RING_ERR("could not start recording (no recorder)");
return false;
}
// audio recording
auto rb = ring::Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID);
rb->createReadOffset(RingBufferPool::DEFAULT_ID);
ring::Manager::instance().startAudioDriverStream();
// TODO wait for AudioLayer::hardwareFormatAvailable callback, otherwise a race condition happens here
audioInput_.reset(new ring::audio::AudioInput(RingBufferPool::DEFAULT_ID));
audioInput_->initRecorder(recorder_);
#ifdef RING_VIDEO
// video recording
if (!isAudioOnly_) {
if (videoInputSet_) {
auto videoInputShpnt = videoInput_.lock();
videoInputShpnt->initRecorder(recorder_);
} else {
RING_ERR("[BUG] can't record video (video input pointer is null)");
return false;
}
}
#endif
return Recordable::startRecording(path_);
}
void
LocalRecorder::stopRecording()
{
if (audioInput_) {
auto rb = ring::Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID);
rb->removeReadOffset(RingBufferPool::DEFAULT_ID);
audioInput_.reset();
audioInput_ = nullptr;
} else {
RING_ERR("could not stop audio layer (audio input is null)");
}
Recordable::stopRecording();
}
} // namespace ring
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Hugo Lefeuvre <hugo.lefeuvre@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.
*/
#pragma once
#include "media/video/video_input.h"
#include "media/audio/audio_input.h"
#include "recordable.h"
namespace ring {
/*
* @file localrecorder.h
* @brief Class for recording messages locally
*/
/*
* The LocalRecorder class exposes the Recordable interface for
* recording messages locally.
*/
class LocalRecorder : public Recordable {
public:
/**
* Constructor of a LocalRecorder.
* Passed VideoInput pointer will be used for recording.
* If input pointer in null, video recording will be disabled on this
* recorder.
*/
LocalRecorder(std::shared_ptr<ring::video::VideoInput> input);
/**
* Start local recording. Return true if recording was successfully
* started, false otherwise.
*/
bool startRecording();
void stopRecording();
/**
* Set recording path
*/
void setPath(const std::string& path);
private:
bool videoInputSet_ = false;
std::weak_ptr<ring::video::VideoInput> videoInput_;
std::unique_ptr<ring::audio::AudioInput> audioInput_ = nullptr;
std::string path_;
};
} // namespace ring
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Hugo Lefeuvre <hugo.lefeuvre@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 "localrecordermanager.h"
namespace ring {
LocalRecorderManager&
LocalRecorderManager::instance()
{
static LocalRecorderManager instance;
return instance;
}
void
LocalRecorderManager::removeRecorderByPath(const std::string& path)
{
std::lock_guard<std::mutex> lock(recorderMapMutex_);
recorderMap_.erase(path);
}
void
LocalRecorderManager::insertRecorder(const std::string& path, std::unique_ptr<LocalRecorder> rec)
{
if (!rec) {
throw std::invalid_argument("couldn't insert null recorder");
}
std::lock_guard<std::mutex> lock(recorderMapMutex_);
auto ret = recorderMap_.emplace(path, std::move(rec));
if (!ret.second) {
throw std::invalid_argument("couldn't insert recorder (passed path is already used as key)");
}
}
LocalRecorder*
LocalRecorderManager::getRecorderByPath(const std::string& path)
{
auto rec = recorderMap_.find(path);
return (rec == recorderMap_.end()) ? nullptr : rec->second.get();
}
bool
LocalRecorderManager::hasRunningRecorders()
{
for (auto it = recorderMap_.begin(); it != recorderMap_.end(); ++it) {
if (it->second->isRecording())
return true;
}
return false;
}
} // namespace ring
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Hugo Lefeuvre <hugo.lefeuvre@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.
*/
#pragma once
#include <memory>
#include <localrecorder.h>
namespace ring {
class LocalRecorderManager {
public:
static LocalRecorderManager &instance();
/**
* Remove given local recorder instance from the map.
*/
void removeRecorderByPath(const std::string& path);
/**
* Insert passed local recorder into the map. Path is used as key.
*/
void insertRecorder(const std::string& path, std::unique_ptr<LocalRecorder> rec);
/**
* Get local recorder instance with passed path as key.
*/
LocalRecorder *getRecorderByPath(const std::string& path);
/**
* Return true if the manager owns at least one currently running
* local recorder.
*/
bool hasRunningRecorders();
private:
std::map<std::string, std::unique_ptr<LocalRecorder>> recorderMap_;
std::mutex recorderMapMutex_;
};
} // namespace ring
......@@ -116,7 +116,7 @@ MediaRecorder::setPath(const std::string& path)
if (!path.empty()) {
path_ = path;
}
RING_DBG() << "Recording will be saved as '" << path_ << "'";
RING_DBG() << "Recording will be saved as '" << getPath() << "'";
}
void
......
......@@ -596,7 +596,7 @@ VideoInput::foundDecOpts(const DeviceParams& params)
}
void
VideoInput::initRecorder(std::shared_ptr<MediaRecorder>& rec)
VideoInput::initRecorder(const std::shared_ptr<MediaRecorder>& rec)
{
recorder_ = rec;
rec->incrementExpectedStreams(1);
......
......@@ -89,7 +89,7 @@ public:
void releaseFrame(void *frame);
#endif
void initRecorder(std::shared_ptr<MediaRecorder>& rec);
void initRecorder(const std::shared_ptr<MediaRecorder>& rec);
private:
NON_COPYABLE(VideoInput);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment