Commit 98fd6f05 authored by Pierre Lespagnol's avatar Pierre Lespagnol Committed by Sébastien Blin

recorder: add conference recording

Change-Id: I9d2915350f6b4e295d72111d66ea66d548b2034c
parent 143c62f5
......@@ -325,8 +325,11 @@ Conference::getDisplayNames() const
bool
Conference::toggleRecording()
{
const bool startRecording = Recordable::toggleRecording();
return startRecording;
if (not isRecording())
initRecorder(recorder_);
else
deinitRecorder(recorder_);
return Recordable::toggleRecording();
}
const std::string&
......@@ -354,4 +357,47 @@ Conference::getVideoMixer()
}
#endif
void
Conference::initRecorder(std::shared_ptr<MediaRecorder>& rec)
{
// Video
if (videoMixer_) {
if (auto ob = rec->addStream(videoMixer_->getStream("v:mixer"))) {
videoMixer_->attach(ob);
}
}
// Audio
// Create ghost participant for ringbufferpool
auto& rbPool = Manager::instance().getRingBufferPool();
ghostRingBuffer_ = rbPool.createRingBuffer(getConfID());
// Bind it to ringbufferpool in order to get the all mixed frames
bindParticipant(getConfID());
// Add stream to recorder
audioMixer_ = jami::getAudioInput(getConfID());
if (auto ob = rec->addStream(audioMixer_->getInfo("a:mixer"))) {
audioMixer_->attach(ob);
}
}
void
Conference::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
{
// Video
if (videoMixer_) {
if (auto ob = rec->getStream("v:mixer")) {
videoMixer_->detach(ob);
}
}
// Audio
if (auto ob = rec->getStream("a:mixer"))
audioMixer_->detach(ob);
audioMixer_.reset();
Manager::instance().getRingBufferPool().unBindAll(getConfID());
ghostRingBuffer_.reset();
}
} // namespace jami
......@@ -29,6 +29,9 @@
#include <memory>
#include <vector>
#include "audio/audio_input.h"
#include <json/json.h>
#include "recordable.h"
......@@ -221,11 +224,17 @@ private:
// We need to convert call to frame
std::mutex videoToCallMtx_;
std::map<Observable<std::shared_ptr<MediaFrame>>*, std::string> videoToCall_ {};
std::shared_ptr<RingBuffer> ghostRingBuffer_;
#ifdef ENABLE_VIDEO
std::string mediaInput_ {};
std::shared_ptr<video::VideoMixer> videoMixer_;
#endif
std::shared_ptr<jami::AudioInput> audioMixer_;
void initRecorder(std::shared_ptr<MediaRecorder>& rec);
void deinitRecorder(std::shared_ptr<MediaRecorder>& rec);
};
} // namespace jami
......@@ -385,4 +385,12 @@ AudioInput::getInfo() const
return ms;
}
MediaStream
AudioInput::getInfo(const std::string& name) const
{
std::lock_guard<std::mutex> lk(fmtMutex_);
auto ms = MediaStream(name, format_, sent_samples);
return ms;
}
} // namespace jami
......@@ -57,6 +57,7 @@ public:
void setFormat(const AudioFormat& fmt);
void setMuted(bool isMuted);
MediaStream getInfo() const;
MediaStream getInfo(const std::string& name) const;
void updateStartTime(int64_t start);
void setPaused(bool paused);
void configureFilePlayback(const std::string& path,
......
......@@ -915,11 +915,10 @@ MediaEncoder::initVP8(AVCodecContext* encoderCtx, uint64_t br)
av_opt_set(encoderCtx, "deadline", "good", AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "cpu-used", 0, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "vprofile", 0, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "qmax", 50, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "qmax", 23, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "qmin", 0, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "slices", 4, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "crf", 18, AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(encoderCtx, "b", 0, AV_OPT_SEARCH_CHILDREN);
JAMI_DBG("VP8 encoder setup: crf=18");
} else {
// 1- if quality is set use it
......
......@@ -255,7 +255,7 @@ MediaRecorder::getStream(const std::string& name) const
void
MediaRecorder::onFrame(const std::string& name, const std::shared_ptr<MediaFrame>& frame)
{
if (!isRecording_)
if (not isRecording_)
return;
// copy frame to not mess with the original frame's pts (does not actually copy frame data)
......@@ -384,32 +384,46 @@ MediaRecorder::initRecord()
MediaStream
MediaRecorder::setupVideoOutput()
{
MediaStream encoderStream, peer, local;
MediaStream encoderStream, peer, local, mixer;
auto it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return pair.second->info.isVideo
&& pair.second->info.name.find("remote") != std::string::npos;
});
if (it != streams_.end()) {
if (it != streams_.end())
peer = it->second->info;
}
it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return pair.second->info.isVideo
&& pair.second->info.name.find("local") != std::string::npos;
});
if (it != streams_.end()) {
if (it != streams_.end())
local = it->second->info;
}
it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return pair.second->info.isVideo
&& pair.second->info.name.find("mixer") != std::string::npos;
});
if (it != streams_.end())
mixer = it->second->info;
// vp8 supports only yuv420p
videoFilter_.reset(new MediaFilter);
int ret = -1;
int streams = peer.isValid() + local.isValid();
int streams = peer.isValid() + local.isValid() + mixer.isValid();
switch (streams) {
case 1: {
auto inputStream = peer.isValid() ? peer : local;
MediaStream inputStream;
if (peer.isValid())
inputStream = peer;
else if (local.isValid())
inputStream = local;
else if (mixer.isValid())
inputStream = mixer;
else {
JAMI_ERR("Trying to record a stream but none is valid");
break;
}
ret = videoFilter_->initialize(buildVideoFilter({}, inputStream), {inputStream});
break;
}
......@@ -472,32 +486,45 @@ MediaRecorder::buildVideoFilter(const std::vector<MediaStream>& peers,
MediaStream
MediaRecorder::setupAudioOutput()
{
MediaStream encoderStream, peer, local;
MediaStream encoderStream, peer, local, mixer;
auto it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return !pair.second->info.isVideo
&& pair.second->info.name.find("remote") != std::string::npos;
});
if (it != streams_.end()) {
if (it != streams_.end())
peer = it->second->info;
}
it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return !pair.second->info.isVideo
&& pair.second->info.name.find("local") != std::string::npos;
});
if (it != streams_.end())
local = it->second->info;
if (it != streams_.end()) {
it = std::find_if(streams_.begin(), streams_.end(), [](const auto& pair) {
return !pair.second->info.isVideo
&& pair.second->info.name.find("mixer") != std::string::npos;
});
if (it != streams_.end())
local = it->second->info;
}
// resample to common audio format, so any player can play the file
audioFilter_.reset(new MediaFilter);
int ret = -1;
int streams = peer.isValid() + local.isValid();
int streams = peer.isValid() + local.isValid() + mixer.isValid();
switch (streams) {
case 1: {
auto inputStream = peer.isValid() ? peer : local;
MediaStream inputStream;
if (peer.isValid())
inputStream = peer;
else if (local.isValid())
inputStream = local;
else if (mixer.isValid())
inputStream = mixer;
else {
JAMI_ERR("Trying to record a stream but none is valid");
break;
}
ret = audioFilter_->initialize(buildAudioFilter({}, inputStream), {inputStream});
break;
}
......
......@@ -279,6 +279,12 @@ VideoMixer::process()
}
}
output.pointer()->pts = av_rescale_q_rnd(av_gettime() - startTime_,
{1, AV_TIME_BASE},
{1, MIXER_FRAMERATE},
static_cast<AVRounding>(AV_ROUND_NEAR_INF
| AV_ROUND_PASS_MINMAX));
lastTimestamp_ = output.pointer()->pts;
publishFrame();
}
......@@ -389,6 +395,7 @@ VideoMixer::setParameters(int width, int height, AVPixelFormat format)
start_sink();
layoutUpdated_ += 1;
startTime_ = av_gettime();
}
void
......@@ -435,5 +442,21 @@ VideoMixer::getPixelFormat() const
return format_;
}
MediaStream
VideoMixer::getStream(const std::string& name) const
{
MediaStream ms;
ms.name = name;
ms.format = format_;
ms.isVideo = true;
ms.height = height_;
ms.width = width_;
ms.frameRate = {MIXER_FRAMERATE, 1};
ms.timeBase = {1, MIXER_FRAMERATE};
ms.firstTimestamp = lastTimestamp_;
return ms;
}
} // namespace video
} // namespace jami
......@@ -26,6 +26,7 @@
#include "video_scaler.h"
#include "threadloop.h"
#include "rw_mutex.h"
#include "media_stream.h"
#include <list>
#include <chrono>
......@@ -82,6 +83,8 @@ public:
void setOnSourcesUpdated(OnSourcesUpdatedCb&& cb) { onSourcesUpdated_ = std::move(cb); }
MediaStream getStream(const std::string& name) const;
private:
NON_COPYABLE(VideoMixer);
struct VideoMixerSource;
......@@ -117,6 +120,9 @@ private:
std::atomic_int layoutUpdated_ {0};
OnSourcesUpdatedCb onSourcesUpdated_ {};
int64_t startTime_;
int64_t lastTimestamp_;
};
} // namespace video
......
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