33#include <opendht/thread_pool.h>
47replaceAll(
const std::string&
str,
const std::string& from,
const std::string& to)
65 std::function<
void(
const std::shared_ptr<MediaFrame>&)>
func)
71 while (observablesFrames_.size() > 0) {
72 auto obs = observablesFrames_.begin();
75 auto it = observablesFrames_.find(*
obs);
76 if (
it != observablesFrames_.end())
77 observablesFrames_.erase(
it);
82 const std::shared_ptr<MediaFrame>&
m)
override
88 std::shared_ptr<VideoFrame>
framePtr;
91 (
AVPixelFormat) (std::static_pointer_cast<VideoFrame>(
m))->format());
96 }
catch (
const std::runtime_error&
e) {
102 framePtr = std::static_pointer_cast<VideoFrame>(
m);
104 if (
angle != rotation_) {
113 if (videoRotationFilter_) {
115 auto rotated = videoRotationFilter_->readOutput();
131 observablesFrames_.insert(
obs);
136 auto it = observablesFrames_.find(
obs);
137 if (
it != observablesFrames_.end())
138 observablesFrames_.erase(
it);
143 std::function<
void(
const std::shared_ptr<MediaFrame>&)> cb_;
144 std::unique_ptr<MediaFilter> videoRotationFilter_ {};
146 std::set<Observable<std::shared_ptr<MediaFrame>>*> observablesFrames_;
167 return path_ +
".ogg";
169 return path_ +
".webm";
194 std::time_t t = std::time(
nullptr);
195 startTime_ = *std::localtime(&t);
198 std::lock_guard
lk(encoderMtx_);
202 if (initRecord() >= 0) {
205 std::lock_guard
lk(mutexStreamSetup_);
206 for (
auto&
media : streams_) {
207 if (
media.second->info.isVideo) {
208 std::lock_guard
lk2(mutexFilterVideo_);
211 std::lock_guard
lk2(mutexFilterAudio_);
214 media.second->isEnabled =
true;
219 std::lock_guard
lk(
rec->encoderMtx_);
220 while (
rec->isRecording()) {
221 std::shared_ptr<MediaFrame>
frame;
224 std::unique_lock
lk(
rec->mutexFrameBuff_);
226 return rec->interrupted_
or not rec->frameBuff_.empty();
228 if (
rec->interrupted_) {
231 frame = std::move(
rec->frameBuff_.front());
232 rec->frameBuff_.pop_front();
238 bool isVideo = (
frame->pointer()->width > 0 &&
frame->pointer()->height > 0);
239 rec->encoder_->encode(
frame->pointer(),
240 isVideo ?
rec->videoIdx_ :
rec->audioIdx_);
242 rec->encoder_->encode(
frame->pointer(),
rec->audioIdx_);
246 JAMI_ERR() <<
"Failed to record frame: " <<
e.what();
253 interrupted_ =
false;
264 isRecording_ =
false;
266 std::lock_guard
lk(mutexStreamSetup_);
267 for (
auto&
media : streams_) {
268 media.second->isEnabled =
false;
278 std::lock_guard
lk(mutexStreamSetup_);
279 if (audioOnly_ && ms.
isVideo) {
280 JAMI_ERR() <<
"Attempting to add video stream to audio only recording";
284 JAMI_ERR() <<
"Attempting to add invalid stream to recording";
288 auto it = streams_.find(ms.
name);
289 if (
it == streams_.end()) {
290 auto streamPtr = std::make_unique<StreamObserver>(ms,
292 ms](
const std::shared_ptr<MediaFrame>&
frame) {
296 JAMI_LOG(
"[Recorder: {:p}] Recorder input #{}: {:s}", fmt::ptr(
this), streams_.size(), ms.
name);
298 if (ms ==
it->second->info)
299 JAMI_LOG(
"[Recorder: {:p}] Recorder already has '{:s}' as input", fmt::ptr(
this), ms.
name);
302 = std::make_unique<StreamObserver>(ms,
304 ms](
const std::shared_ptr<MediaFrame>&
frame) {
309 it->second->isEnabled = isRecording_;
310 return it->second.get();
316 std::lock_guard
lk(mutexStreamSetup_);
318 auto it = streams_.find(ms.
name);
319 if (
it == streams_.end()) {
320 JAMI_LOG(
"[Recorder: {:p}] Recorder no stream to remove", fmt::ptr(
this));
322 JAMI_LOG(
"[Recorder: {:p}] Recorder removing '{:s}'", fmt::ptr(
this), ms.
name);
335 const auto it = streams_.find(name);
336 if (
it != streams_.cend())
337 return it->second.get();
342MediaRecorder::onFrame(
const std::string& name,
const std::shared_ptr<MediaFrame>&
frame)
344 if (
not isRecording_ || interrupted_)
347 std::lock_guard
lk(mutexStreamSetup_);
350 std::unique_ptr<MediaFrame>
clone;
351 const auto& ms = streams_[name]->info;
352#if defined(ENABLE_VIDEO) && defined(RING_ACCEL)
359 *std::static_pointer_cast<VideoFrame>(
frame),
361 }
catch (
const std::runtime_error&
e) {
366 clone = std::make_unique<MediaFrame>();
371 clone = std::make_unique<MediaFrame>();
373#if defined(ENABLE_VIDEO) && defined(RING_ACCEL)
383 if (ms.isVideo && videoFilter_ && outputVideoFilter_) {
384 std::lock_guard
lk(mutexFilterVideo_);
385 videoFilter_->feedInput(
clone->pointer(), name);
389 while (
auto fFrame = outputVideoFilter_->readOutput()) {
393 }
else if (audioFilter_ && outputAudioFilter_) {
395 std::lock_guard
lkA(mutexFilterAudio_);
396 audioFilter_->feedInput(
clone->pointer(), name);
407 std::lock_guard
lk(mutexFrameBuff_);
408 frameBuff_.emplace_back(std::move(
fFrame));
414MediaRecorder::initRecord()
422 if (title_.empty()) {
423 title_ =
"Conversation at %TIMESTAMP";
427 if (description_.empty()) {
428 description_ =
"Recorded with Jami https://jami.net";
432 encoder_->setMetadata(title_, description_);
433 encoder_->openOutput(
getPath());
436 encoder_->enableAccel(
false);
449 auto audioCodec = std::static_pointer_cast<jami::SystemAudioCodecInfo>(
451 audioIdx_ = encoder_->addStream(*
audioCodec.get());
453 JAMI_ERR() <<
"Failed to add audio stream to encoder";
471 MediaDescription
args;
474 encoder_->setOptions(
args);
476 auto videoCodec = std::static_pointer_cast<jami::SystemVideoCodecInfo>(
478 videoIdx_ = encoder_->addStream(*
videoCodec.get());
480 JAMI_ERR() <<
"Failed to add video stream to encoder";
486 encoder_->setIOContext(
nullptr);
488 JAMI_DBG() <<
"Recording initialized";
493MediaRecorder::setupVideoOutput()
496 auto it = std::find_if(streams_.begin(), streams_.end(), [](
const auto&
pair) {
497 return pair.second->info.isVideo
498 && pair.second->info.name.find(
"remote") != std::string::npos;
500 if (
it != streams_.end())
501 peer =
it->second->info;
503 it = std::find_if(streams_.begin(), streams_.end(), [](
const auto&
pair) {
504 return pair.second->info.isVideo
505 && pair.second->info.name.find(
"local") != std::string::npos;
507 if (
it != streams_.end())
510 it = std::find_if(streams_.begin(), streams_.end(), [](
const auto&
pair) {
511 return pair.second->info.isVideo
512 && pair.second->info.name.find(
"mixer") != std::string::npos;
514 if (
it != streams_.end())
518 videoFilter_.reset(
new MediaFilter);
523 JAMI_WARN() <<
"Attempting to record a video stream but none is valid";
530 else if (
local.isValid())
532 else if (
mixer.isValid())
535 JAMI_ERR(
"Attempting to record a stream but none is valid");
543 ret = videoFilter_->initialize(buildVideoFilter({peer},
local), {peer,
local});
546 JAMI_ERR() <<
"Recording more than 2 video streams is not supported";
552 JAMI_ERR() <<
"Failed to initialize video filter";
560 if (outputVideoFilter_) {
561 outputVideoFilter_->flush();
562 outputVideoFilter_.reset();
565 outputVideoFilter_.reset(
new MediaFilter);
572 std::ostringstream
f;
574 <<
",pad=1280:720:(ow-iw)/2:(oh-ih)/2,format=pix_fmts=yuv420p,fps=30";
579 JAMI_ERR() <<
"Failed to initialize output video filter";
588MediaRecorder::buildVideoFilter(
const std::vector<MediaStream>& peers,
589 const MediaStream&
local)
const
591 std::ostringstream v;
593 switch (peers.size()) {
595 v <<
"[" <<
local.name <<
"] fps=30, format=pix_fmts=yuv420p";
605 v <<
"[" << p.name <<
"] fps=30, scale=-2:" <<
newHeight
608 v <<
"[" << p.name <<
"] fps=30 [v:m]; ";
613 v <<
"[v:m] [v:o] overlay=main_w-overlay_w:main_h-overlay_h"
614 <<
", format=pix_fmts=yuv420p";
617 JAMI_ERR() <<
"Video recordings with more than 2 video streams are not supported";
625MediaRecorder::setupAudioOutput()
630 audioFilter_.reset(
new MediaFilter);
633 if (streams_.empty()) {
634 JAMI_WARN() <<
"Attempting to record a audio stream but none is valid";
638 std::vector<MediaStream> peers {};
639 for (
const auto&
media : streams_) {
640 if (!
media.second->info.isVideo &&
media.second->info.isValid())
641 peers.emplace_back(
media.second->info);
644 ret = audioFilter_->initialize(buildAudioFilter(peers), peers);
647 JAMI_ERR() <<
"Failed to initialize audio filter";
656 if (outputAudioFilter_) {
657 outputAudioFilter_->flush();
658 outputAudioFilter_.reset();
661 outputAudioFilter_.reset(
new MediaFilter);
662 ret = outputAudioFilter_->initialize(
663 "[input]aformat=sample_fmts=s16:sample_rates=48000:channel_layouts=stereo",
667 JAMI_ERR() <<
"Failed to initialize output audio filter";
674MediaRecorder::buildAudioFilter(
const std::vector<MediaStream>& peers)
const
676 std::string
baseFilter =
"aresample=osr=48000:ochl=stereo:osf=s16";
677 std::ostringstream
a;
679 for (
const auto& ms : peers)
680 a <<
"[" << ms.name <<
"] ";
681 a <<
" amix=inputs=" << peers.size() <<
", " <<
baseFilter;
686MediaRecorder::flush()
689 std::lock_guard
lk(mutexFilterVideo_);
691 videoFilter_->flush();
692 if (outputVideoFilter_)
693 outputVideoFilter_->flush();
696 std::lock_guard
lk(mutexFilterAudio_);
698 audioFilter_->flush();
699 if (outputAudioFilter_)
700 outputAudioFilter_->flush();
707MediaRecorder::reset()
710 std::lock_guard
lk(mutexFrameBuff_);
713 videoIdx_ = audioIdx_ = -1;
715 std::lock_guard
lk(mutexStreamSetup_);
717 std::lock_guard
lk2(mutexFilterVideo_);
718 videoFilter_.reset();
719 outputVideoFilter_.reset();
722 std::lock_guard
lk2(mutexFilterAudio_);
723 audioFilter_.reset();
724 outputAudioFilter_.reset();
static LIBJAMI_TEST_EXPORT Manager & instance()
static std::unique_ptr< VideoFrame > transferToMainMemory(const VideoFrame &frame, AVPixelFormat desiredFormat)
Transfers hardware frame to main memory.
#define JAMI_LOG(formatstr,...)
std::unique_ptr< MediaFilter > getTransposeFilter(int rotation, std::string inputName, int width, int height, int format, bool rescale)
decltype(getGlobalInstance< SystemCodecContainer >) & getSystemCodecContainer
const constexpr char ROTATION_FILTER_INPUT_NAME[]
void emitSignal(Args... args)
static std::string replaceAll(const std::string &str, const std::string &from, const std::string &to)